Changeset 41648
- Timestamp:
- 09/29/2017 08:12:19 PM (7 years ago)
- Location:
- trunk
- Files:
-
- 1 added
- 16 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-admin/css/common.css
r41622 r41648 124 124 border: 0; 125 125 clip: rect(1px, 1px, 1px, 1px); 126 -webkit-clip-path: inset(50%); 126 127 clip-path: inset(50%); 127 128 height: 1px; -
trunk/src/wp-admin/css/customize-controls.css
r41626 r41648 590 590 591 591 #customize-theme-controls .customize-pane-child.open, 592 #customize-theme-controls .customize-pane-child.current-panel, 593 #customize-theme-controls .customize-themes-panel.customize-pane-child.current-panel { 592 #customize-theme-controls .customize-pane-child.current-panel { 594 593 -webkit-transform: none; 595 594 transform: none; 596 595 } 597 596 598 #customize-theme-controls .customize-themes-panel.customize-pane-child,599 597 .section-open #customize-theme-controls .customize-pane-parent, 600 598 .in-sub-panel #customize-theme-controls .customize-pane-parent, 601 599 .section-open #customize-info, 602 600 .in-sub-panel #customize-info, 603 .in-sub-panel.section-open #customize-theme-controls .customize-pane-child.current-panel, 604 .in-themes-panel #customize-theme-controls .customize-pane-parent, 605 .in-themes-panel #customize-info { 601 .in-sub-panel.section-open #customize-theme-controls .customize-pane-child.current-panel { 606 602 visibility: hidden; 607 603 height: 0; … … 613 609 .section-open #customize-theme-controls .customize-pane-parent.busy, 614 610 .in-sub-panel #customize-theme-controls .customize-pane-parent.busy, 615 .in-themes-panel #customize-theme-controls .customize-pane-parent.busy,616 611 .section-open #customize-info.busy, 617 612 .in-sub-panel #customize-info.busy, 618 .in-themes-panel #customize-info.busy,619 613 .busy.section-open.in-sub-panel #customize-theme-controls .customize-pane-child.current-panel, 620 614 #customize-theme-controls .customize-pane-child.open, … … 624 618 height: auto; 625 619 overflow: auto; 626 }627 628 .in-themes-panel #customize-theme-controls .customize-pane-parent,629 .in-themes-panel #customize-info {630 -webkit-transform: translateX(100%);631 transform: translateX(100%);632 620 } 633 621 … … 1573 1561 } 1574 1562 1575 /* #customize-container is reused from customize-loader.js, hence the naming. */ 1576 .wp-customizer .customize-loading #customize-container { 1563 .wp-customizer .customize-loading #customize-themes-loading-container { 1577 1564 display: block; 1578 -webkit-animation: customize-reload .75s; /* Can't use `transition` because `display` changes here. */ 1579 animation: customize-reload .75s; 1580 } 1581 1582 #customize-theme-controls .control-section-themes .accordion-section-title:hover, /* Not a focusable element. */ 1583 #customize-theme-controls .control-section-themes .accordion-section-title { 1565 -webkit-animation: customize-reload .5s; /* Can't use `transition` because `display` changes here. */ 1566 animation: customize-reload .5s; 1567 } 1568 1569 .customize-loading #customize-themes-loading-container span { 1570 clear: both; 1571 color: #191e23; 1572 font-size: 18px; 1573 font-style: normal; 1574 margin: 0; 1575 padding: 2em 0; 1576 text-align: center; 1577 width: 100%; 1578 display: block; 1579 top: 50%; 1580 position: relative; 1581 } 1582 1583 .customize-loading #customize-themes-loading-container .customize-loading-text { 1584 display: none; 1585 } 1586 1587 #customize-theme-controls .control-panel-themes { 1588 border-bottom: none; 1589 } 1590 1591 #customize-theme-controls .control-panel-themes > .accordion-section-title:hover, /* Not a focusable element. */ 1592 #customize-theme-controls .control-panel-themes > .accordion-section-title { 1584 1593 cursor: default; 1585 1594 background: #fff; … … 1588 1597 border-bottom: 1px solid #ddd; 1589 1598 border-left: none; 1590 margin-top: 0; 1591 } 1592 #customize-theme-controls .control-section-themes .customize-section-back { 1593 position: absolute; 1594 right: 0; 1595 top: 0; 1596 height: 80px; 1597 border-left: 1px solid #ddd; 1598 border-right: 4px solid #fff; 1599 } 1600 #customize-theme-controls .control-section-themes .customize-section-back:before { 1601 content: "\f345"; 1602 } 1603 #customize-theme-controls .control-section-themes .customize-section-back:hover, 1604 #customize-theme-controls .control-section-themes .customize-section-back:focus { 1605 border-right-color: #0073aa; 1599 border-right: none; 1600 margin: 0 0 15px 0; 1601 padding-right: 100px; /* Space for the button */ 1606 1602 } 1607 1603 … … 1626 1622 } 1627 1623 1624 .control-panel-themes .accordion-section-title span.customize-action, 1625 #customize-controls .customize-section-title span.customize-action, 1628 1626 #customize-controls .control-section-themes .accordion-section-title span.customize-action, 1629 1627 #customize-controls .customize-section-title span.customize-action, … … 1634 1632 } 1635 1633 1636 #customize-controls .control-section-themes .accordion-section-title .change-theme, 1637 #customize-controls .customize-themes-panel .accordion-section-title .customize-theme { 1634 #customize-theme-controls .control-panel-themes .accordion-section-title .change-theme { 1638 1635 position: absolute; 1639 1636 right: 10px; … … 1643 1640 } 1644 1641 1645 #customize- controls .control-section-themes .accordion-section-title:before{1642 #customize-theme-controls .control-panel-themes > .accordion-section-title:after { 1646 1643 display: none; 1647 1644 } 1648 1645 1649 #customize-controls .customize-themes-panel { 1650 padding: 0 8px; 1651 background: #f1f1f1; 1646 .control-panel-themes .customize-themes-full-container { 1647 position: fixed; 1648 top: 0; 1649 left: 0; 1650 transition: .18s left ease-in-out; 1651 margin: 46px 0 0 300px; 1652 padding: 25px 0; 1653 overflow-y: scroll; 1654 width: calc(100% - 300px); 1655 height: calc(100% - 96px); 1656 background: #eee; 1657 z-index: 20; 1658 } 1659 1660 /* Animations for opening the themes panel */ 1661 #customize-header-actions .save, 1662 #customize-header-actions .spinner, 1663 #customize-header-actions .customize-controls-preview-toggle { 1664 position: relative; 1665 top: 0; 1666 transition: .18s top ease-in-out; 1667 } 1668 1669 #customize-footer-actions, 1670 #customize-footer-actions .collapse-sidebar { 1671 bottom: 0; 1672 transition: .18s bottom ease-in-out; 1673 } 1674 1675 .in-themes-panel:not(.animating) #customize-header-actions .save, 1676 .in-themes-panel:not(.animating) #customize-header-actions #publish-settings, 1677 .in-themes-panel:not(.animating) #customize-header-actions .spinner, 1678 .in-themes-panel:not(.animating) #customize-header-actions .customize-controls-preview-toggle, 1679 .in-themes-panel:not(.animating) #customize-preview, 1680 .in-themes-panel:not(.animating) #customize-footer-actions { 1681 visibility: hidden; 1682 } 1683 1684 .wp-full-overlay.in-themes-panel { 1685 background: #eee; /* Prevents a black flash when fading in the panel */ 1686 } 1687 1688 .in-themes-panel #customize-header-actions .save, 1689 .in-themes-panel #customize-header-actions .spinner, 1690 .in-themes-panel #customize-header-actions .customize-controls-preview-toggle { 1691 top: -45px; 1692 } 1693 1694 .in-themes-panel #customize-footer-actions, 1695 .in-themes-panel #customize-footer-actions .collapse-sidebar { 1696 bottom: -45px; 1697 } 1698 1699 /* Don't show the theme count while the panel opens, as it's in the wrong place during the animation */ 1700 .in-themes-panel.animating .control-panel-themes .filter-themes-count { 1701 display: none; 1702 } 1703 1704 .in-themes-panel.wp-full-overlay .wp-full-overlay-sidebar-content { 1705 bottom: 0; 1706 } 1707 1708 .themes-filter-bar .feature-filter-toggle { 1709 float: right; 1710 margin: 3px 0 3px 25px; 1711 } 1712 1713 .themes-filter-bar .feature-filter-toggle:before { 1714 content: "\f111"; 1715 margin: 0 5px 0 0; 1716 font: normal 16px/1 dashicons; 1717 vertical-align: text-bottom; 1718 -webkit-font-smoothing: antialiased; 1719 -moz-osx-font-smoothing: grayscale; 1720 } 1721 1722 .themes-filter-bar .feature-filter-toggle.open { 1723 background: #eee; 1724 border-color: #999; 1725 box-shadow: inset 0 2px 5px -3px rgba( 0, 0, 0, 0.5 ); 1726 -webkit-transform: translateY(1px); 1727 transform: translateY(1px); 1728 } 1729 1730 .themes-filter-bar .feature-filter-toggle .filter-count-filters { 1731 display: none; 1732 } 1733 1734 .themes-filter-bar .filter-drawer { 1652 1735 box-sizing: border-box; 1653 } 1654 1655 #customize-controls .customize-themes-panel .accordion-section-title:first-child { 1656 margin-top: 0; 1657 } 1658 1659 #customize-controls .customize-themes-panel .accordion-section-title:nth-child(2) { 1736 width: 100%; 1737 position: absolute; 1738 top: 46px; 1739 left: 0; 1740 padding: 25px 0 25px 25px; 1741 border-top: 0; 1742 margin: 0; 1743 background: #eee; 1744 border-bottom: 1px solid #ddd; 1745 } 1746 1747 .themes-filter-bar .filter-group { 1748 margin: 0 25px 0 0; 1749 width: calc( (100% - 75px) / 3); 1750 min-width: 200px; 1751 max-width: 320px; 1752 } 1753 1754 /* Adds a delay before fading in to avoid it "jumping" */ 1755 @-webkit-keyframes themes-fade-in { 1756 0% { 1757 opacity: 0; 1758 } 1759 50% { 1760 opacity: 0; 1761 } 1762 100% { 1763 opacity: 1; 1764 } 1765 } 1766 @keyframes themes-fade-in { 1767 0% { 1768 opacity: 0; 1769 } 1770 50% { 1771 opacity: 0; 1772 } 1773 100% { 1774 opacity: 1; 1775 } 1776 } 1777 1778 .control-panel-themes .customize-themes-full-container.animate { 1779 -webkit-animation: .6s themes-fade-in 1; 1780 animation: .6s themes-fade-in 1; 1781 } 1782 1783 .in-themes-panel:not(.animating) .control-panel-themes .filter-themes-count { 1784 -webkit-animation: .6s themes-fade-in 1; 1785 animation: .6s themes-fade-in 1; 1786 } 1787 1788 .control-panel-themes .filter-themes-count { 1789 position: relative; 1790 float: right; 1791 line-height: 34px; 1792 } 1793 1794 .control-panel-themes .filter-themes-count .themes-displayed { 1795 font-weight: 600; 1796 color: #555d66; 1797 } 1798 1799 .customize-themes-notifications { 1800 margin: 0; 1801 } 1802 1803 .control-panel-themes .customize-themes-notifications .notice { 1804 margin: 0 0 25px 0; 1805 } 1806 1807 .customize-themes-full-container .customize-themes-section { 1808 display: none !important; /* There is unknown JS that perpetually tries to show all theme sections when more items are added. */ 1809 overflow: hidden; 1810 } 1811 1812 .customize-themes-full-container .customize-themes-section.current-section { 1813 display: list-item !important; /* There is unknown JS that perpetually tries to show all theme sections when more items are added. */ 1814 } 1815 1816 .control-section .customize-section-text-before { 1817 padding: 0 0 8px 15px; 1818 margin: 15px 0 0 0; 1819 line-height: 16px; 1820 border-bottom: 1px solid #ddd; 1821 color: #555d66; 1822 } 1823 1824 .control-panel-themes .customize-themes-section-title { 1825 width: 100%; 1826 background: #fff; 1827 box-shadow: none; 1828 outline: none; 1829 border-top: none; 1830 border-bottom: 1px solid #ddd; 1831 border-left: 4px solid #fff; 1832 border-right: none; 1833 cursor: pointer; 1834 padding: 10px 15px; 1835 position: relative; 1836 text-align: left; 1660 1837 font-size: 14px; 1661 1838 font-weight: 600; 1662 } 1663 1664 #customize-controls .customize-themes-panel > h2 { 1665 padding: 15px 8px 0 8px; 1666 } 1667 1668 #customize-theme-controls .customize-themes-panel .accordion-section-content { 1669 background: transparent; 1839 color: #555d66; 1840 text-shadow: none; 1841 } 1842 1843 .control-panel-themes #accordion-section-installed_themes { 1844 border-top: 1px solid #ddd; 1845 } 1846 1847 .control-panel-themes .theme-section { 1848 margin: 0; 1849 position: relative; 1850 } 1851 1852 .control-panel-themes .customize-themes-section-title:focus, 1853 .control-panel-themes .customize-themes-section-title:hover { 1854 border-left-color: #0073aa; 1855 color: #0073aa; 1856 background: #f5f5f5; 1857 } 1858 1859 .customize-themes-section-title:not(.selected):after { 1860 content: ""; 1670 1861 display: block; 1671 } 1672 1673 .customize-control.customize-control-theme { 1674 margin-bottom: 8px; 1862 position: absolute; 1863 top: 9px; 1864 right: 15px; 1865 width: 18px; 1866 height: 18px; 1867 border-radius: 100%; 1868 border: 1px solid #ccc; 1869 background: #fff; 1870 } 1871 1872 .control-panel-themes .theme-section .customize-themes-section-title.selected:after { 1873 content: "\f147"; 1874 font: 16px/1 dashicons; 1875 box-sizing: border-box; 1876 width: 20px; 1877 height: 20px; 1878 padding: 3px 3px 1px 1px; /* Re-align the icon to the smaller grid */ 1879 border-radius: 100%; 1880 position: absolute; 1881 top: 9px; 1882 right: 15px; 1883 background: #0073aa; 1884 color: #fff; 1885 } 1886 1887 .control-panel-themes .customize-themes-section-title.selected { 1888 color: #0073aa; 1675 1889 } 1676 1890 … … 1682 1896 } 1683 1897 1898 .loading .customize-themes-section .spinner { 1899 display: block; 1900 visibility: visible; 1901 position: relative; 1902 clear: both; 1903 width: 20px; 1904 height: 20px; 1905 left: calc(50% - 10px); 1906 float: none; 1907 margin-top: 50px; 1908 } 1909 1910 .customize-themes-section .no-themes, 1911 .customize-themes-section .no-themes-local { 1912 display: none; 1913 } 1914 1915 .themes-section-installed_themes .theme .notice-success { 1916 display: none; /* Hide "installed" notice on installed themes tab. */ 1917 } 1918 1919 .control-panel-themes .theme-browser .theme .theme-actions .button-primary { 1920 margin: 0 0 0 8px; 1921 } 1922 1923 .customize-control-theme .theme { 1924 width: 100%; 1925 margin: 0; 1926 border: 1px solid #ddd; 1927 background: #fff; 1928 } 1929 1930 .customize-control-theme .theme .theme-name, .customize-control-theme .theme .theme-actions { 1931 background: #fff; 1932 border: none; 1933 } 1934 1935 .customize-control.customize-control-theme { /* override most properties on .customize-control */ 1936 box-sizing: border-box; 1937 width: 25%; 1938 max-width: 600px; /* Max. screenshot size / 2 */ 1939 margin: 0 25px 25px 0; 1940 padding: 0; 1941 clear: none; 1942 } 1943 1944 /* 5 columns above 2100px */ 1945 @media screen and (min-width: 2101px) { 1946 .customize-control.customize-control-theme { 1947 width: calc( ( 100% - 125px ) / 5 - 1px ); /* 1px offset accounts for browser rounding, typical all grids */ 1948 } 1949 } 1950 1951 /* 4 columns up to 2100px */ 1952 @media screen and (min-width: 1601px) and (max-width: 2100px) { 1953 .customize-control.customize-control-theme { 1954 width: calc( ( 100% - 100px ) / 4 - 1px ); 1955 } 1956 } 1957 1958 /* 3 columns up to 1600px */ 1959 @media screen and (min-width: 1201px) and (max-width: 1600px) { 1960 .customize-control.customize-control-theme { 1961 width: calc( ( 100% - 75px ) / 3 - 1px ); 1962 } 1963 } 1964 1965 /* 2 columns up to 1200px */ 1966 @media screen and (min-width: 851px) and (max-width: 1200px) { 1967 .customize-control.customize-control-theme { 1968 width: calc( ( 100% - 50px ) / 2 - 1px ); 1969 1970 } 1971 } 1972 1973 /* 1 column up to 850 px */ 1974 @media screen and (max-width: 850px) { 1975 .customize-control.customize-control-theme { 1976 width: 100%; 1977 } 1978 } 1979 1684 1980 .wp-customizer .theme-browser .themes { 1685 padding-bottom: 8px; 1686 } 1687 1688 .wp-customizer .theme-browser .theme { 1689 margin: 0; 1690 width: 100%; 1981 padding: 0 0 25px 25px; 1982 transition: .18s margin-top linear; 1691 1983 } 1692 1984 1693 1985 .wp-customizer .theme-browser .theme .theme-actions { 1694 -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";1695 1986 opacity: 1; 1696 1987 } … … 1704 1995 } 1705 1996 1706 .wp-customizer #themes-filter { 1707 font-size: 16px; 1708 font-weight: 300; 1709 line-height: 1.5; 1710 width: 100%; 1711 } 1712 1713 .control-section-themes .accordion-section-title:after, 1714 .customize-themes-panel .accordion-section-title:after { 1997 .customize-preview-header.themes-filter-bar { 1998 position: fixed; 1999 top: 0; 2000 left: 300px; 2001 width: calc(100% - 300px); 2002 height: 46px; 2003 background: #eee; 2004 z-index: 10; 2005 padding: 6px 25px; 2006 box-sizing: border-box; 2007 border-bottom: 1px solid #ddd; 2008 } 2009 2010 .themes-filter-bar .themes-filter-container { 2011 margin: 0; 2012 padding: 0; 2013 } 2014 2015 .themes-filter-bar .wp-filter-search { 2016 line-height: 25px; 2017 padding: 3px 5px; 2018 max-width: 100%; 2019 width: 40%; 2020 min-width: 300px; 2021 position: absolute; 2022 top: 6px; 2023 left: 25px; 2024 } 2025 2026 /* Unstick the filter bar on short windows/screens. This breakpoint is based on the 2027 current length of .org feature filters assuming translations do not wrap lines. */ 2028 @media screen and (max-height:540px), screen and (max-width:1018px) { 2029 .customize-preview-header.themes-filter-bar { 2030 position: relative; 2031 left: 0; 2032 width: 100%; 2033 margin: 0 0 25px 0; 2034 } 2035 .wp-customizer .theme-browser .themes { 2036 padding: 0 0 25px 25px; 2037 overflow: hidden; 2038 } 2039 2040 .control-panel-themes .customize-themes-full-container { 2041 margin-top: 0; 2042 padding: 0; 2043 height: 100%; 2044 width: calc(100% - 300px); 2045 } 2046 } 2047 2048 @media screen and (max-width:1018px) { 2049 .themes-filter-bar .filter-group { 2050 width: calc( (100% - 50px) / 2); 2051 } 2052 } 2053 2054 @media screen and (max-width:900px) { 2055 .customize-preview-header.themes-filter-bar { 2056 height: 86px; 2057 padding-top: 46px; 2058 } 2059 2060 .themes-filter-bar .wp-filter-search { 2061 width: calc(100% - 50px); 2062 margin: 0; 2063 min-width: 200px; 2064 } 2065 2066 .themes-filter-bar .filter-drawer { 2067 top: 86px; 2068 } 2069 2070 .control-panel-themes .filter-themes-count { 2071 float: left; 2072 } 2073 } 2074 2075 @media screen and (max-width:792px) { 2076 .themes-filter-bar .filter-group { 2077 width: calc( 100% - 25px); 2078 } 2079 } 2080 2081 .control-panel-themes .customize-themes-mobile-back { 1715 2082 display: none; 1716 2083 } 1717 2084 1718 .customize-themes-panel.control-panel-content { 1719 border-top: 1px solid #ddd; 2085 /* Mobile - toggle between themes and filters */ 2086 @media screen and (max-width:600px) { 2087 2088 .wp-full-overlay.showing-themes .control-panel-themes .filter-themes-count .filter-themes { 2089 display: block; 2090 float: right; 2091 } 2092 2093 .control-panel-themes .customize-themes-full-container { 2094 width: 100%; 2095 margin: 0; 2096 top: 46px; 2097 height: calc(100% - 46px); 2098 z-index: 1; 2099 display: none; 2100 } 2101 2102 .showing-themes .control-panel-themes .customize-themes-full-container { 2103 display: block; 2104 } 2105 2106 .wp-customizer .showing-themes .control-panel-themes .customize-themes-mobile-back { 2107 display: block; 2108 position: fixed; 2109 top: 0; 2110 left: 0; 2111 background: #eee; 2112 color: #444; 2113 border-radius: 0; 2114 box-shadow: none; 2115 border: none; 2116 height: 46px; 2117 width: 100%; 2118 z-index: 10; 2119 text-align: left; 2120 text-shadow: none; 2121 border-bottom: 1px solid #ddd; 2122 border-left: 4px solid transparent; 2123 margin: 0; 2124 padding: 0; 2125 font-size: 0; 2126 overflow: hidden; 2127 } 2128 2129 .wp-customizer .showing-themes .control-panel-themes .customize-themes-mobile-back:before { 2130 left: 0; 2131 top: 0; 2132 height: 42px; 2133 width: 26px; 2134 display: block; 2135 line-height: 46px; 2136 padding: 0 8px 0 8px; 2137 border-right: 1px solid #ddd; 2138 } 2139 2140 .wp-customizer .showing-themes .control-panel-themes .customize-themes-mobile-back:hover, 2141 .wp-customizer .showing-themes .control-panel-themes .customize-themes-mobile-back:focus { 2142 color: #0073aa; 2143 background: #f3f3f5; 2144 border-left-color: #0073aa; 2145 outline: none; 2146 box-shadow: none; 2147 } 2148 2149 .showing-themes #customize-header-actions { 2150 display: none; 2151 } 1720 2152 } 1721 2153 … … 1734 2166 } 1735 2167 2168 /* Avoid a z-index war by resetting elements that should be under the overlay. 2169 This is likely required because of the way that sections and panels are positioned. */ 2170 .wp-customizer.modal-open #customize-header-actions, 2171 .wp-customizer.modal-open .control-panel-themes .filter-themes-count, 2172 .wp-customizer.modal-open .control-panel-themes .customize-themes-section-title.selected:after { 2173 z-index: -1; 2174 } 2175 1736 2176 .wp-customizer .theme-overlay .theme-backdrop { 1737 2177 background: rgba( 238, 238, 238, 0.75 ); 1738 2178 position: fixed; 1739 2179 z-index: 110; 2180 } 2181 2182 .wp-customizer .theme-overlay .star-rating { 2183 float: left; 2184 margin-right: 8px; 2185 } 2186 2187 .wp-customizer .theme-rating .num-ratings { 2188 line-height: 20px; 1740 2189 } 1741 2190 … … 1746 2195 bottom: 45px; 1747 2196 z-index: 120; 1748 max-width: 1740px; /* To ensure that theme screenshots are not displayed larger than 880px wide. */1749 2197 } 1750 2198 1751 2199 .wp-customizer .theme-overlay .theme-actions { 1752 text-align: right; /* Because there's only one action, match the pattern of media modals and right-align the action. */ 1753 } 1754 1755 .ie8 .wp-customizer .theme-overlay .theme-header, 1756 .ie8 .wp-customizer .theme-overlay .theme-about, 1757 .ie8 .wp-customizer .theme-overlay .theme-actions { 1758 position: static; 2200 text-align: right; /* Because there're only one or two actions, match the UI pattern of media modals and right-align the action. */ 2201 padding: 10px 25px; 2202 background: #eee; 2203 border-top: 1px solid #ddd; 2204 } 2205 2206 .wp-customizer .theme-overlay .theme-actions .theme-install.preview { 2207 margin-left: 8px; 2208 } 2209 2210 .control-panel-themes .theme-actions .delete-theme { 2211 left: 15px; /* these override themes.css on mobile */ 2212 right: auto; 2213 bottom: auto; 2214 position: absolute; 2215 } 2216 2217 .modal-open .in-themes-panel #customize-controls .wp-full-overlay-sidebar-content { 2218 overflow: visible; /* Prevent the top-level Customizer controls from becoming visible when elements on the right of the details modal are focused. */ 2219 } 2220 2221 .wp-customizer .theme-header { 2222 background: #eee; 2223 } 2224 2225 .wp-customizer .theme-overlay .theme-header button, 2226 .wp-customizer .theme-overlay .theme-header .close:before { 2227 color: #444; 2228 } 2229 2230 .wp-customizer .theme-overlay .theme-header .close:focus, 2231 .wp-customizer .theme-overlay .theme-header .close:hover, 2232 .wp-customizer .theme-overlay .theme-header .right:focus, 2233 .wp-customizer .theme-overlay .theme-header .right:hover, 2234 .wp-customizer .theme-overlay .theme-header .left:focus, 2235 .wp-customizer .theme-overlay .theme-header .left:hover { 2236 background: #fff; 2237 border-bottom: 4px solid #0073aa; 2238 color: #0073aa; 2239 } 2240 2241 .wp-customizer .theme-overlay .theme-header .close:focus:before, 2242 .wp-customizer .theme-overlay .theme-header .close:hover:before { 2243 color: #0073aa; 2244 } 2245 2246 .wp-customizer .theme-overlay .theme-header button.disabled, 2247 .wp-customizer .theme-overlay .theme-header button.disabled:hover, 2248 .wp-customizer .theme-overlay .theme-header button.disabled:focus { 2249 border-bottom: none; 2250 background: transparent; 2251 color: #ccc; 1759 2252 } 1760 2253 … … 1784 2277 border-bottom: 1px solid #ddd; 1785 2278 clear: both; 1786 color: # 666;2279 color: #555d66; 1787 2280 font-size: 24px; 1788 2281 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; -
trunk/src/wp-admin/css/install.css
r41622 r41648 405 405 border: 0; 406 406 clip: rect(1px, 1px, 1px, 1px); 407 -webkit-clip-path: inset(50%); 407 408 clip-path: inset(50%); 408 409 height: 1px; -
trunk/src/wp-admin/css/list-tables.css
r41622 r41648 1822 1822 .post-com-count .screen-reader-text { 1823 1823 position: static; 1824 -webkit-clip-path: none; 1824 1825 clip-path: none; 1825 1826 width: auto; -
trunk/src/wp-admin/css/nav-menus.css
r41622 r41648 600 600 .no-js.nav-menus-php .item-edit .screen-reader-text { 601 601 position: static; 602 -webkit-clip-path: none; 602 603 clip-path: none; 603 604 width: auto; -
trunk/src/wp-admin/css/themes.css
r41062 r41648 550 550 margin: 0 30px 0 0; 551 551 width: 55%; 552 max-width: 880px;552 max-width: 1200px; /* Recommended theme screenshot width, set here to avoid stretching */ 553 553 text-align: center; 554 554 } … … 1050 1050 } 1051 1051 1052 p.no-themes { 1052 p.no-themes, 1053 p.no-themes-local { 1053 1054 clear: both; 1054 1055 color: #666; … … 1706 1707 } 1707 1708 1708 #customize-container { 1709 #customize-container, 1710 #customize-themes-loading-container { 1709 1711 display: none; 1710 background: # fff;1712 background: #eee; 1711 1713 z-index: 500000; 1712 1714 position: fixed; … … 1721 1723 /* Make the Customizer and Theme installer overlays the only available content. */ 1722 1724 #customize-container, 1725 #customize-themes-loading-container, 1723 1726 .theme-install-overlay { 1724 1727 visibility: visible; … … 1825 1828 #customize-preview.wp-full-overlay-main:before, 1826 1829 .customize-loading #customize-container:before, 1830 .customize-loading #customize-themes-loading-container:before, 1827 1831 .theme-install-overlay .wp-full-overlay-main:before { 1828 1832 content: ""; … … 1862 1866 #customize-preview.wp-full-overlay-main:before, 1863 1867 .customize-loading #customize-container:before, 1868 .customize-loading #customize-themes-loading-container:before, 1864 1869 .theme-install-overlay .wp-full-overlay-main:before { 1865 1870 background-image: url(../images/spinner-2x.gif); -
trunk/src/wp-admin/customize.php
r41626 r41648 126 126 127 127 <script type="text/javascript"> 128 var ajaxurl = <?php echo wp_json_encode( admin_url( 'admin-ajax.php', 'relative' ) ); ?>; 128 var ajaxurl = <?php echo wp_json_encode( admin_url( 'admin-ajax.php', 'relative' ) ); ?>, 129 pagenow = 'customize'; 129 130 </script> 130 131 -
trunk/src/wp-admin/includes/theme.php
r41605 r41648 235 235 $features = array( 236 236 237 __( 'Layout' ) => array(238 'grid-layout' => __( 'Grid Layout' ),239 'one-column' => __( 'One Column' ),240 'two-columns' => __( 'Two Columns' ),241 'three-columns' => __( 'Three Columns' ),242 'four-columns' => __( 'Four Columns' ),243 'left-sidebar' => __( 'Left Sidebar' ),244 'right-sidebar' => __( 'Right Sidebar' ),245 ),246 247 __( 'Features' ) => array(248 'accessibility-ready' => __( 'Accessibility Ready' ),249 'buddypress' => __( 'BuddyPress' ),250 'custom-background' => __( 'Custom Background' ),251 'custom-colors' => __( 'Custom Colors' ),252 'custom-header' => __( 'Custom Header' ),253 'custom-logo' => __( 'Custom Logo' ),254 'custom-menu' => __( 'Custom Menu' ),255 'editor-style' => __( 'Editor Style' ),256 'featured-image-header' => __( 'Featured Image Header' ),257 'featured-images' => __( 'Featured Images' ),258 'flexible-header' => __( 'Flexible Header' ),259 'footer-widgets' => __( 'Footer Widgets' ),260 'front-page-post-form' => __( 'Front Page Posting' ),261 'full-width-template' => __( 'Full Width Template' ),262 'microformats' => __( 'Microformats' ),263 'post-formats' => __( 'Post Formats' ),264 'rtl-language-support' => __( 'RTL Language Support' ),265 'sticky-post' => __( 'Sticky Post' ),266 'theme-options' => __( 'Theme Options' ),267 'threaded-comments' => __( 'Threaded Comments' ),268 'translation-ready' => __( 'Translation Ready' ),269 ),270 271 237 __( 'Subject' ) => array( 272 238 'blog' => __( 'Blog' ), … … 279 245 'photography' => __( 'Photography' ), 280 246 'portfolio' => __( 'Portfolio' ), 247 ), 248 249 __( 'Features' ) => array( 250 'accessibility-ready' => __( 'Accessibility Ready' ), 251 'custom-background' => __( 'Custom Background' ), 252 'custom-colors' => __( 'Custom Colors' ), 253 'custom-header' => __( 'Custom Header' ), 254 'custom-logo' => __( 'Custom Logo' ), 255 'editor-style' => __( 'Editor Style' ), 256 'featured-image-header' => __( 'Featured Image Header' ), 257 'featured-images' => __( 'Featured Images' ), 258 'footer-widgets' => __( 'Footer Widgets' ), 259 'full-width-template' => __( 'Full Width Template' ), 260 'post-formats' => __( 'Post Formats' ), 261 'sticky-post' => __( 'Sticky Post' ), 262 'theme-options' => __( 'Theme Options' ), 263 ), 264 265 __( 'Layout' ) => array( 266 'grid-layout' => __( 'Grid Layout' ), 267 'one-column' => __( 'One Column' ), 268 'two-columns' => __( 'Two Columns' ), 269 'three-columns' => __( 'Three Columns' ), 270 'four-columns' => __( 'Four Columns' ), 271 'left-sidebar' => __( 'Left Sidebar' ), 272 'right-sidebar' => __( 'Right Sidebar' ), 281 273 ) 274 282 275 ); 283 276 … … 575 568 $parent = false; 576 569 if ( $theme->parent() ) { 577 $parent = $theme->parent()->display( 'Name' ); 578 $parents[ $slug ] = $theme->parent()->get_stylesheet(); 570 $parent = $theme->parent(); 571 $parents[ $slug ] = $parent->get_stylesheet(); 572 $parent = $parent->display( 'Name' ); 579 573 } 580 574 … … 636 630 */ 637 631 function customize_themes_print_templates() { 638 $preview_url = esc_url( add_query_arg( 'theme', '__THEME__' ) ); // Token because esc_url() strips curly braces.639 $preview_url = str_replace( '__THEME__', '{{ data.id }}', $preview_url );640 632 ?> 641 633 <script type="text/html" id="tmpl-customize-themes-details-view"> … … 649 641 <div class="theme-about wp-clearfix"> 650 642 <div class="theme-screenshots"> 651 <# if ( data.screenshot [0] ) { #>643 <# if ( data.screenshot && data.screenshot[0] ) { #> 652 644 <div class="screenshot"><img src="{{ data.screenshot[0] }}" alt="" /></div> 653 645 <# } else { #> … … 662 654 <h2 class="theme-name">{{{ data.name }}}<span class="theme-version"><?php printf( __( 'Version: %s' ), '{{ data.version }}' ); ?></span></h2> 663 655 <h3 class="theme-author"><?php printf( __( 'By %s' ), '{{{ data.authorAndUri }}}' ); ?></h3> 664 <p class="theme-description">{{{ data.description }}}</p> 656 657 <# if ( data.stars && 0 != data.num_ratings ) { #> 658 <div class="theme-rating"> 659 {{{ data.stars }}} 660 <span class="num-ratings"> 661 <?php 662 /* translators: %s is the number of ratings */ 663 echo sprintf( __( '(%s ratings)' ), '{{ data.num_ratings }}' ); 664 ?> 665 </span> 666 </div> 667 <# } #> 668 669 <# if ( data.hasUpdate ) { #> 670 <div class="notice notice-warning notice-alt notice-large" data-slug="{{ data.id }}"> 671 <h3 class="notice-title"><?php _e( 'Update Available' ); ?></h3> 672 {{{ data.update }}} 673 </div> 674 <# } #> 665 675 666 676 <# if ( data.parent ) { #> … … 668 678 <# } #> 669 679 680 <p class="theme-description">{{{ data.description }}}</p> 681 670 682 <# if ( data.tags ) { #> 671 <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{ data.tags}}</p>683 <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{{ data.tags }}}</p> 672 684 <# } #> 673 685 </div> 674 686 </div> 675 687 676 <# if ( ! data.active ) { #> 677 <div class="theme-actions"> 678 <div class="inactive-theme"> 679 <?php 680 /* translators: %s: Theme name */ 681 $aria_label = sprintf( __( 'Preview %s' ), '{{ data.name }}' ); 682 ?> 683 <a href="<?php echo $preview_url; ?>" target="_top" class="button button-primary" aria-label="<?php echo esc_attr( $aria_label ); ?>"><?php _e( 'Live Preview' ); ?></a> 684 </div> 685 </div> 686 <# } #> 688 <div class="theme-actions"> 689 <# if ( data.active ) { #> 690 <button type="button" class="button button-primary customize-theme"><?php _e( 'Customize' ); ?></a> 691 <# } else if ( 'installed' === data.type ) { #> 692 <?php if ( current_user_can( 'delete_themes' ) ) { ?> 693 <# if ( data.actions && data.actions['delete'] ) { #> 694 <a href="{{{ data.actions['delete'] }}}" data-slug="{{ data.id }}" class="button button-secondary delete-theme"><?php _e( 'Delete' ); ?></a> 695 <# } #> 696 <?php } ?> 697 <button type="button" class="button button-primary preview-theme" data-slug="{{ data.id }}"><?php _e( 'Live Preview' ); ?></span> 698 <# } else { #> 699 <button type="button" class="button theme-install" data-slug="{{ data.id }}"><?php _e( 'Install' ); ?></button> 700 <button type="button" class="button button-primary theme-install preview" data-slug="{{ data.id }}"><?php _e( 'Install & Preview' ); ?></button> 701 <# } #> 702 </div> 687 703 </div> 688 704 </script> -
trunk/src/wp-admin/js/customize-controls.js
r41626 r41648 1188 1188 section.containerParent = api.ensure( section.containerParent ); 1189 1189 1190 // Watch for changes to the panel state 1190 // Watch for changes to the panel state. 1191 1191 inject = function ( panelId ) { 1192 1192 var parentContainer; 1193 1193 if ( panelId ) { 1194 // The panel has been supplied, so wait until the panel object is registered 1194 // The panel has been supplied, so wait until the panel object is registered. 1195 1195 api.panel( panelId, function ( panel ) { 1196 // The panel has been registered, wait for it to become ready/initialized 1196 // The panel has been registered, wait for it to become ready/initialized. 1197 1197 panel.deferred.embedded.done( function () { 1198 1198 parentContainer = panel.contentContainer; … … 1219 1219 }; 1220 1220 section.panel.bind( inject ); 1221 inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one 1221 inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one. 1222 1222 }, 1223 1223 … … 1394 1394 * wp.customize.ThemesSection 1395 1395 * 1396 * Custom section for themes that functions similarly to a backwards panel,1397 * and alsohandles the theme-details view rendering and navigation.1396 * Custom section for themes that loads themes by category, and also 1397 * handles the theme-details view rendering and navigation. 1398 1398 * 1399 1399 * @constructor … … 1406 1406 template: '', 1407 1407 screenshotQueue: null, 1408 $window: $( window ), 1409 1410 /** 1408 $window: null, 1409 $body: null, 1410 loaded: 0, 1411 loading: false, 1412 fullyLoaded: false, 1413 term: '', 1414 tags: '', 1415 nextTerm: '', 1416 nextTags: '', 1417 filtersHeight: 0, 1418 headerContainer: null, 1419 1420 /** 1421 * Initialize. 1422 * 1423 * @since 4.9.0 1424 * 1425 * @param {string} id - ID. 1426 * @param {object} options - Options. 1427 * @returns {void} 1428 */ 1429 initialize: function( id, options ) { 1430 var section = this; 1431 section.headerContainer = $(); 1432 section.$window = $( window ); 1433 section.$body = $( document.body ); 1434 api.Section.prototype.initialize.call( section, id, options ); 1435 }, 1436 1437 /** 1438 * Embed the section in the DOM when the themes panel is ready. 1439 * 1440 * Insert the section before the themes container. Assume that a themes section is within a panel, but not necessarily the themes panel. 1441 * 1442 * @since 4.9.0 1443 */ 1444 embed: function() { 1445 var inject, 1446 section = this; 1447 1448 // Watch for changes to the panel state 1449 inject = function( panelId ) { 1450 var parentContainer; 1451 api.panel( panelId, function( panel ) { 1452 1453 // The panel has been registered, wait for it to become ready/initialized 1454 panel.deferred.embedded.done( function() { 1455 parentContainer = panel.contentContainer; 1456 if ( ! section.headContainer.parent().is( parentContainer ) ) { 1457 parentContainer.find( '.customize-themes-full-container-container' ).before( section.headContainer ); 1458 } 1459 if ( ! section.contentContainer.parent().is( section.headContainer ) ) { 1460 section.containerParent.append( section.contentContainer ); 1461 } 1462 section.deferred.embedded.resolve(); 1463 }); 1464 } ); 1465 }; 1466 section.panel.bind( inject ); 1467 inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one 1468 }, 1469 1470 /** 1471 * Set up. 1472 * 1411 1473 * @since 4.2.0 1412 */ 1413 initialize: function () { 1414 this.$customizeSidebar = $( '.wp-full-overlay-sidebar-content:first' ); 1415 return api.Section.prototype.initialize.apply( this, arguments ); 1416 }, 1417 1418 /** 1419 * @since 4.2.0 1420 */ 1421 ready: function () { 1474 * 1475 * @returns {void} 1476 */ 1477 ready: function() { 1422 1478 var section = this; 1423 1479 section.overlay = section.container.find( '.theme-overlay' ); … … 1442 1498 // Pressing the escape key fires a theme:collapse event 1443 1499 if ( 27 === event.keyCode ) { 1444 section.closeDetails(); 1500 if ( section.$body.hasClass( 'modal-open' ) ) { 1501 1502 // Escape from the details modal. 1503 section.closeDetails(); 1504 } else { 1505 1506 // Escape from the inifinite scroll list. 1507 section.headerContainer.find( '.customize-themes-section-title' ).focus(); 1508 } 1445 1509 event.stopPropagation(); // Prevent section from being collapsed. 1446 1510 } 1447 1511 }); 1448 1512 1449 _.bindAll( this, 'renderScreenshots' );1513 _.bindAll( this, 'renderScreenshots', 'loadMore', 'checkTerm', 'filtersChecked' ); 1450 1514 }, 1451 1515 … … 1454 1518 * 1455 1519 * Ignore the active states' of the contained theme controls, and just 1456 * use the section's own active state instead. This ensures empty search1457 * results for theme s to causethe section to become inactive.1520 * use the section's own active state instead. This prevents empty search 1521 * results for theme sections from causing the section to become inactive. 1458 1522 * 1459 1523 * @since 4.2.0 … … 1466 1530 1467 1531 /** 1532 * Attach events. 1533 * 1468 1534 * @since 4.2.0 1535 * 1536 * @returns {void} 1469 1537 */ 1470 1538 attachEvents: function () { 1471 var section = this ;1539 var section = this, debounced; 1472 1540 1473 1541 // Expand/Collapse accordion sections on click. … … 1480 1548 }); 1481 1549 1482 // Expand/Collapse section/panel. 1483 section.container.find( '.change-theme, .customize-theme' ).on( 'click keydown', function( event ) { 1484 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1485 return; 1486 } 1487 event.preventDefault(); // Keep this AFTER the key filter above 1488 1489 if ( section.expanded() ) { 1490 section.collapse(); 1491 } else { 1550 section.headerContainer = $( '#accordion-section-' + section.id ); 1551 1552 // Expand section/panel. Only collapse when opening another section. 1553 section.headerContainer.on( 'click', '.customize-themes-section-title', function() { 1554 1555 // Toggle accordion filters under section headers. 1556 if ( section.headerContainer.find( '.filter-details' ).length ) { 1557 section.headerContainer.find( '.customize-themes-section-title' ) 1558 .toggleClass( 'details-open' ) 1559 .attr( 'aria-expanded', function( i, attr ) { 1560 return 'true' === attr ? 'false' : 'true'; 1561 }); 1562 section.headerContainer.find( '.filter-details' ).slideToggle( 180 ); 1563 } 1564 1565 // Open the section. 1566 if ( ! section.expanded() ) { 1492 1567 section.expand(); 1493 1568 } 1494 1569 }); 1495 1570 1571 // Preview installed themes. 1572 section.container.on( 'click', '.theme-actions .preview-theme', function() { 1573 var themeId = $( this ).data( 'slug' ); 1574 1575 $( '.wp-full-overlay' ).addClass( 'customize-loading' ); 1576 api.panel( 'themes' ).loadThemePreview( themeId ).fail( function() { 1577 $( '.wp-full-overlay' ).removeClass( 'customize-loading' ); 1578 } ); 1579 }); 1580 1496 1581 // Theme navigation in details view. 1497 section.container.on( 'click keydown', '.left', function( event ) { 1498 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1499 return; 1500 } 1501 1502 event.preventDefault(); // Keep this AFTER the key filter above 1503 1582 section.container.on( 'click', '.left', function() { 1504 1583 section.previousTheme(); 1505 1584 }); 1506 1585 1507 section.container.on( 'click keydown', '.right', function( event ) { 1508 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1509 return; 1510 } 1511 1512 event.preventDefault(); // Keep this AFTER the key filter above 1513 1586 section.container.on( 'click', '.right', function() { 1514 1587 section.nextTheme(); 1515 1588 }); 1516 1589 1517 section.container.on( 'click keydown', '.theme-backdrop, .close', function( event ) { 1518 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1519 return; 1520 } 1521 1522 event.preventDefault(); // Keep this AFTER the key filter above 1523 1590 section.container.on( 'click', '.theme-backdrop, .close', function() { 1524 1591 section.closeDetails(); 1525 1592 }); 1526 1593 1527 var renderScreenshots = _.throttle( _.bind( section.renderScreenshots, this ), 100 ); 1528 section.container.on( 'input', '#themes-filter', function( event ) { 1529 var count, 1530 term = event.currentTarget.value.toLowerCase().trim().replace( '-', ' ' ), 1531 controls = section.controls(); 1532 1533 _.each( controls, function( control ) { 1534 control.filter( term ); 1594 // Filter-search all theme objects loaded in the section. 1595 section.container.on( 'input', '.wp-filter-search-themes', function( event ) { 1596 section.filterSearch( event.currentTarget ); 1597 }); 1598 1599 // Event listeners for remote wporg queries with user-entered terms. 1600 if ( 'wporg' === section.params.action ) { 1601 1602 // Search terms. 1603 debounced = _.debounce( section.checkTerm, 500 ); // Wait until there is no input for 500 milliseconds to initiate a search. 1604 section.contentContainer.on( 'input', '#wp-filter-search-input', function() { 1605 debounced( section ); 1606 if ( ! section.expanded() ) { 1607 section.expand(); 1608 } 1609 section.checkTerm( section ); 1535 1610 }); 1536 1611 1537 renderScreenshots(); 1538 1539 // Update theme count. 1540 count = section.container.find( 'li.customize-control:visible' ).length; 1541 section.container.find( '.theme-count' ).text( count ); 1542 }); 1543 1544 // Pre-load the first 3 theme screenshots. 1545 api.bind( 'ready', function () { 1546 _.each( section.controls().slice( 0, 3 ), function ( control ) { 1547 var img, src = control.params.theme.screenshot[0]; 1548 if ( src ) { 1549 img = new Image(); 1550 img.src = src; 1612 // Feature filters. 1613 section.contentContainer.on( 'click', '.filter-group input', function() { 1614 section.filtersChecked(); 1615 section.checkTerm( section ); 1616 }); 1617 1618 // Toggle feature filter sections. 1619 section.contentContainer.on( 'click', '.feature-filter-toggle', function( e ) { 1620 $( e.currentTarget ) 1621 .toggleClass( 'open' ) 1622 .attr( 'aria-expanded', function( i, attr ) { 1623 return 'true' === attr ? 'false' : 'true'; 1624 }) 1625 .next( '.filter-drawer' ).slideToggle( 180, 'linear', function() { 1626 if ( 0 === section.filtersHeight ) { 1627 section.filtersHeight = $( this ).height(); 1628 1629 // First time, so it's opened. 1630 section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 ); 1631 } 1632 }); 1633 if ( $( e.currentTarget ).hasClass( 'open' ) ) { 1634 section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 ); 1635 } else { 1636 section.contentContainer.find( '.themes' ).css( 'margin-top', 0 ); 1551 1637 } 1552 1638 }); 1639 } 1640 1641 // Setup section cross-linking. 1642 section.contentContainer.on( 'click', '.no-themes-local .search-dotorg-themes', function() { 1643 api.section( 'wporg_themes' ).focus(); 1644 }); 1645 1646 // Move section controls to the themes area. 1647 api.bind( 'ready', function () { 1648 section.contentContainer = section.container.find( '.customize-themes-section' ); 1649 section.contentContainer.appendTo( $( '.customize-themes-full-container' ) ); 1650 section.container.add( section.headerContainer ); 1553 1651 }); 1554 1652 }, … … 1562 1660 * @param {Object} args 1563 1661 * @param {Boolean} args.unchanged 1564 * @param {Callback} args.completeCallback 1662 * @param {Function} args.completeCallback 1663 * @returns {void} 1565 1664 */ 1566 1665 onChangeExpanded: function ( expanded, args ) { 1666 1667 // Note: there is a second argument 'args' passed 1668 var section = this, 1669 container = section.contentContainer.closest( '.customize-themes-full-container' ); 1567 1670 1568 1671 // Immediately call the complete callback if there were no changes … … 1574 1677 } 1575 1678 1576 // Note: there is a second argument 'args' passed 1577 var panel = this, 1578 section = panel.contentContainer, 1579 overlay = section.closest( '.wp-full-overlay' ), 1580 container = section.closest( '.wp-full-overlay-sidebar-content' ), 1581 customizeBtn = section.find( '.customize-theme' ), 1582 changeBtn = panel.headContainer.find( '.change-theme' ); 1583 1584 if ( expanded && ! section.hasClass( 'current-panel' ) ) { 1679 if ( expanded ) { 1680 1681 // Try to load controls if none are loaded yet. 1682 if ( 0 === section.loaded ) { 1683 section.loadControls(); 1684 } 1685 1585 1686 // Collapse any sibling sections/panels 1586 1687 api.section.each( function ( otherSection ) { 1587 if ( otherSection !== panel ) { 1688 var searchTerm; 1689 1690 if ( otherSection !== section ) { 1691 1692 // Try to sync the current search term to the new section. 1693 if ( 'themes' === otherSection.params.type ) { 1694 searchTerm = otherSection.contentContainer.find( '.wp-filter-search' ).val(); 1695 section.contentContainer.find( '.wp-filter-search' ).val( searchTerm ); 1696 1697 // Directly initialize an empty remote search to avoid a race condition. 1698 if ( '' === searchTerm && '' !== section.term && 'installed' !== section.params.action ) { 1699 section.term = ''; 1700 section.initializeNewQuery( section.term, section.tags ); 1701 } else { 1702 section.checkTerm( section ); 1703 } 1704 section.filterSearch( section.contentContainer.find( '.wp-filter-search' ).get( 0 ) ); 1705 } 1588 1706 otherSection.collapse( { duration: args.duration } ); 1589 1707 } 1590 1708 }); 1591 api.panel.each( function ( otherPanel ) { 1592 otherPanel.collapse( { duration: 0 } ); 1709 1710 section.contentContainer.addClass( 'current-section' ); 1711 container.scrollTop(); 1712 section.headerContainer.find( '.customize-themes-section-title' ).addClass( 'selected' ).attr( 'aria-expanded', 'true' ); 1713 1714 container.on( 'scroll', _.throttle( section.renderScreenshots, 300 ) ); 1715 container.on( 'scroll', _.throttle( section.loadMore, 300 ) ); 1716 1717 if ( args.completeCallback ) { 1718 args.completeCallback(); 1719 } 1720 section.updateCount(); // Show this section's count. 1721 } else { 1722 section.contentContainer.removeClass( 'current-section' ); 1723 1724 // Always hide, even if they don't exist or are already hidden. 1725 section.headerContainer.find( '.customize-themes-section-title' ).removeClass( 'selected details-open' ).attr( 'aria-expanded', 'false' ); 1726 section.headerContainer.find( '.filter-details' ).slideUp( 180 ); 1727 1728 container.off( 'scroll' ); 1729 1730 if ( args.completeCallback ) { 1731 args.completeCallback(); 1732 } 1733 } 1734 }, 1735 1736 /** 1737 * Return the section's content element without detaching from the parent. 1738 * 1739 * @since 4.9.0 1740 * 1741 * @returns {jQuery} 1742 */ 1743 getContent: function() { 1744 return this.container.find( '.control-section-content' ); 1745 }, 1746 1747 /** 1748 * Load theme data via Ajax and add themes to the section as controls. 1749 * 1750 * @since 4.9.0 1751 * 1752 * @returns {void} 1753 */ 1754 loadControls: function() { 1755 var section = this, params, page, request; 1756 1757 if ( section.loading ) { 1758 return; // We're already loading a batch of themes. 1759 } 1760 1761 // Parameters for every API query. Additional params are set in PHP. 1762 page = Math.ceil( section.loaded / 100 ) + 1; 1763 params = { 1764 'switch-themes-nonce': api.settings.nonce['switch-themes'], 1765 'wp_customize': 'on', 1766 'theme_action': section.params.action, 1767 'customized_theme': api.settings.theme.stylesheet, 1768 'page': page 1769 }; 1770 1771 // Add fields for wporg actions. 1772 if ( 'wporg' === section.params.action ) { 1773 params.search = section.term; 1774 params.tags = section.tags; 1775 } 1776 1777 // Load themes. 1778 section.headContainer.closest( '.wp-full-overlay' ).addClass( 'loading' ); 1779 section.loading = true; 1780 section.container.find( '.no-themes' ).hide(); 1781 request = wp.ajax.post( 'customize-load-themes', params ); 1782 request.done(function( data ) { 1783 var themes = data.themes, themeControl, newThemeControls; 1784 1785 // Stop and try again if the term changed while loading. 1786 if ( '' !== section.nextTerm || '' !== section.nextTags ) { 1787 if ( section.nextTerm ) { 1788 section.term = section.nextTerm; 1789 } 1790 if ( section.nextTags ) { 1791 section.tags = section.nextTags; 1792 } 1793 section.nextTerm = ''; 1794 section.nextTags = ''; 1795 section.loading = false; 1796 section.loadControls(); 1797 return; 1798 } 1799 1800 if ( 0 !== themes.length ) { 1801 newThemeControls = []; 1802 1803 // Add controls for each theme. 1804 _.each( themes, function( theme ) { 1805 var customizeId = section.params.action + '_theme_' + theme.id; 1806 themeControl = new api.controlConstructor.theme( customizeId, { 1807 params: { 1808 type: 'theme', 1809 content: '<li id="customize-control-theme-' + section.params.action + '_' + theme.id + '" class="customize-control customize-control-theme"></li>', 1810 section: section.params.id, 1811 active: true, 1812 theme: theme, 1813 priority: section.loaded + 1 1814 }, 1815 previewer: api.previewer 1816 } ); 1817 1818 api.control.add( customizeId, themeControl ); 1819 newThemeControls.push( themeControl ); 1820 section.loaded = section.loaded + 1; 1821 }); 1822 1823 if ( 1 === page ) { 1824 1825 // Pre-load the first 3 theme screenshots. 1826 _.each( section.controls().slice( 0, 3 ), function( control ) { 1827 var img, src = control.params.theme.screenshot[0]; 1828 if ( src ) { 1829 img = new Image(); 1830 img.src = src; 1831 } 1832 }); 1833 if ( 'installed' !== section.params.action ) { 1834 wp.a11y.speak( api.settings.l10n.themeSearchResults.replace( '%d', data.info.results ) ); 1835 } 1836 } else { 1837 Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue. 1838 } 1839 _.delay( section.renderScreenshots, 100 ); // Wait for the controls to become visible. 1840 1841 if ( 'installed' === section.params.action || 100 > themes.length ) { // If we have less than the requested 100 themes, it's the end of the list. 1842 section.fullyLoaded = true; 1843 } 1844 } else { 1845 if ( 0 === section.loaded ) { 1846 section.container.find( '.no-themes' ).show(); 1847 wp.a11y.speak( section.container.find( '.no-themes' ).text() ); 1848 } else { 1849 section.fullyLoaded = true; 1850 } 1851 } 1852 if ( 'installed' === section.params.action ) { 1853 section.updateCount(); // Count of visible theme controls. 1854 } else { 1855 section.updateCount( data.info.results ); // Total number of results including pages not yet loaded. 1856 } 1857 section.container.find( '.unexpected-error' ).hide(); // Hide error notice in case it was previously shown. 1858 1859 // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case. 1860 section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' ); 1861 section.loading = false; 1862 }); 1863 request.fail(function( data ) { 1864 if ( 'undefined' === typeof data ) { 1865 section.container.find( '.unexpected-error' ).show(); 1866 wp.a11y.speak( section.container.find( '.unexpected-error' ).text() ); 1867 } else if ( 'undefined' !== typeof console && console.error ) { 1868 console.error( data ); 1869 } 1870 1871 // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case. 1872 section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' ); 1873 section.loading = false; 1874 }); 1875 }, 1876 1877 /** 1878 * Determines whether more themes should be loaded, and loads them. 1879 * 1880 * @since 4.9.0 1881 * @returns {void} 1882 */ 1883 loadMore: function() { 1884 var section = this, container, bottom, threshold; 1885 if ( ! section.fullyLoaded && ! section.loading ) { 1886 container = section.container.closest( '.customize-themes-full-container' ); 1887 1888 bottom = container.scrollTop() + container.height(); 1889 threshold = container.prop( 'scrollHeight' ) - 3000; // Use a fixed distance to the bottom of loaded results to avoid unnecessarily loading results sooner when using a percentage of scroll distance. 1890 1891 if ( bottom > threshold ) { 1892 section.loadControls(); 1893 } 1894 } 1895 }, 1896 1897 /** 1898 * Event handler for search input that filters visible controls. 1899 * 1900 * @since 4.9.0 1901 * 1902 * @param {Element} el - The search input element as a raw JS object. 1903 * @returns {void} 1904 */ 1905 filterSearch: function( el ) { 1906 var count = 0, 1907 visible = false, 1908 section = this, 1909 noFilter = ( undefined !== api.section( 'wporg_themes' ) && 'wporg' !== section.params.action ) ? '.no-themes-local' : '.no-themes', 1910 term = el.value.toLowerCase().trim().replace( '-', ' ' ), 1911 controls = section.controls(), 1912 renderScreenshots; 1913 1914 if ( section.loading ) { 1915 return; 1916 } 1917 1918 _.each( controls, function( control ) { 1919 visible = control.filter( term ); 1920 if ( visible ) { 1921 count = count + 1; 1922 } 1923 }); 1924 1925 if ( 0 === count ) { 1926 section.container.find( noFilter ).show(); 1927 wp.a11y.speak( section.container.find( noFilter ).text() ); 1928 } else { 1929 section.container.find( noFilter ).hide(); 1930 } 1931 1932 renderScreenshots = _.throttle( _.bind( section.renderScreenshots, this ), 100 ); 1933 1934 renderScreenshots(); 1935 1936 // Update theme count. 1937 section.updateCount( count ); 1938 }, 1939 1940 /** 1941 * Event handler for search input that determines if the terms have changed and loads new controls as needed. 1942 * 1943 * @since 4.9.0 1944 * 1945 * @param {wp.customize.ThemesSection} section - The current theme section, passed through the debouncer. 1946 * @returns {void} 1947 */ 1948 checkTerm: function( section ) { 1949 var newTerm; 1950 if ( 'wporg' === section.params.action ) { 1951 newTerm = $( '#wp-filter-search-input' ).val(); 1952 if ( section.term !== newTerm ) { 1953 section.initializeNewQuery( newTerm, section.tags ); 1954 } 1955 } 1956 }, 1957 1958 /** 1959 * Check for filters checked in the feature filter list and initialize a new query. 1960 * 1961 * @since 4.9.0 1962 * 1963 * @returns {void} 1964 */ 1965 filtersChecked: function() { 1966 var section = this, 1967 items = section.container.find( '.filter-group' ).find( ':checkbox' ), 1968 tags = []; 1969 1970 _.each( items.filter( ':checked' ), function( item ) { 1971 tags.push( $( item ).prop( 'value' ) ); 1972 }); 1973 1974 // When no filters are checked, restore initial state. Update filter count. 1975 if ( 0 === tags.length ) { 1976 tags = ''; 1977 section.contentContainer.find( '.feature-filter-toggle .filter-count-0' ).show(); 1978 section.contentContainer.find( '.feature-filter-toggle .filter-count-filters' ).hide(); 1979 } else { 1980 section.contentContainer.find( '.feature-filter-toggle .theme-filter-count' ).text( tags.length ); 1981 section.contentContainer.find( '.feature-filter-toggle .filter-count-0' ).hide(); 1982 section.contentContainer.find( '.feature-filter-toggle .filter-count-filters' ).show(); 1983 } 1984 1985 // Check whether tags have changed, and either load or queue them. 1986 if ( ! _.isEqual( section.tags, tags ) ) { 1987 if ( section.loading ) { 1988 section.nextTags = tags; 1989 } else { 1990 section.initializeNewQuery( section.term, tags ); 1991 } 1992 } 1993 }, 1994 1995 /** 1996 * Reset the current query and load new results. 1997 * 1998 * @since 4.9.0 1999 * 2000 * @param {string} newTerm - New term. 2001 * @param {Array} newTags - New tags. 2002 * @returns {void} 2003 */ 2004 initializeNewQuery: function( newTerm, newTags ) { 2005 var section = this; 2006 2007 // Clear the controls in the section. 2008 _.each( section.controls(), function( control ) { 2009 control.container.remove(); 2010 api.control.remove( control.id ); 2011 }); 2012 section.loaded = 0; 2013 section.fullyLoaded = false; 2014 section.screenshotQueue = null; 2015 2016 // Run a new query, with loadControls handling paging, etc. 2017 if ( ! section.loading ) { 2018 section.term = newTerm; 2019 section.tags = newTags; 2020 section.loadControls(); 2021 } else { 2022 section.nextTerm = newTerm; // This will reload from loadControls() with the newest term once the current batch is loaded. 2023 section.nextTags = newTags; // This will reload from loadControls() with the newest tags once the current batch is loaded. 2024 } 2025 if ( ! section.expanded() ) { 2026 section.expand(); // Expand the section if it isn't expanded. 2027 } 2028 }, 2029 2030 /** 2031 * Render control's screenshot if the control comes into view. 2032 * 2033 * @since 4.2.0 2034 * 2035 * @returns {void} 2036 */ 2037 renderScreenshots: function() { 2038 var section = this; 2039 2040 // Fill queue initially, or check for more if empty. 2041 if ( null === section.screenshotQueue || 0 === section.screenshotQueue.length ) { 2042 2043 // Add controls that haven't had their screenshots rendered. 2044 section.screenshotQueue = _.filter( section.controls(), function( control ) { 2045 return ! control.screenshotRendered; 1593 2046 }); 1594 1595 panel._animateChangeExpanded( function() { 1596 changeBtn.attr( 'tabindex', '-1' ); 1597 customizeBtn.attr( 'tabindex', '0' ); 1598 1599 customizeBtn.focus(); 1600 section.css( 'top', '' ); 1601 container.scrollTop( 0 ); 1602 1603 if ( args.completeCallback ) { 1604 args.completeCallback(); 1605 } 1606 } ); 1607 1608 overlay.addClass( 'in-themes-panel' ); 1609 section.addClass( 'current-panel' ); 1610 _.delay( panel.renderScreenshots, 10 ); // Wait for the controls 1611 panel.$customizeSidebar.on( 'scroll.customize-themes-section', _.throttle( panel.renderScreenshots, 300 ) ); 1612 1613 } else if ( ! expanded && section.hasClass( 'current-panel' ) ) { 1614 panel._animateChangeExpanded( function() { 1615 changeBtn.attr( 'tabindex', '0' ); 1616 customizeBtn.attr( 'tabindex', '-1' ); 1617 1618 changeBtn.focus(); 1619 section.css( 'top', '' ); 1620 1621 if ( args.completeCallback ) { 1622 args.completeCallback(); 1623 } 1624 } ); 1625 1626 overlay.removeClass( 'in-themes-panel' ); 1627 section.removeClass( 'current-panel' ); 1628 panel.$customizeSidebar.off( 'scroll.customize-themes-section' ); 1629 } 1630 }, 1631 1632 /** 1633 * Render control's screenshot if the control comes into view. 1634 * 1635 * @since 4.2.0 1636 */ 1637 renderScreenshots: function( ) { 1638 var section = this; 1639 1640 // Fill queue initially. 1641 if ( section.screenshotQueue === null ) { 1642 section.screenshotQueue = section.controls(); 1643 } 1644 1645 // Are all screenshots rendered? 2047 } 2048 2049 // Are all screenshots rendered (for now)? 1646 2050 if ( ! section.screenshotQueue.length ) { 1647 2051 return; … … 1679 2083 1680 2084 /** 2085 * Get visible count. 2086 * 2087 * @since 4.9.0 2088 * 2089 * @returns {int} Visible count. 2090 */ 2091 getVisibleCount: function() { 2092 return this.contentContainer.find( 'li.customize-control:visible' ).length; 2093 }, 2094 2095 /** 2096 * Update the number of themes in the section. 2097 * 2098 * @since 4.9.0 2099 * 2100 * @returns {void} 2101 */ 2102 updateCount: function( count ) { 2103 var section = this, countEl, displayed; 2104 2105 if ( ! count && 0 !== count ) { 2106 count = section.getVisibleCount(); 2107 } 2108 2109 displayed = section.contentContainer.find( '.themes-displayed' ); 2110 countEl = section.contentContainer.find( '.theme-count' ); 2111 2112 if ( 0 === count ) { 2113 countEl.text( '0' ); 2114 } else { 2115 2116 // Animate the count change for emphasis. 2117 displayed.fadeOut( 180, function() { 2118 countEl.text( count ); 2119 displayed.fadeIn( 180 ); 2120 } ); 2121 wp.a11y.speak( api.settings.l10n.announceThemeCount.replace( '%d', count ) ); 2122 } 2123 }, 2124 2125 /** 1681 2126 * Advance the modal to the next theme. 1682 2127 * 1683 2128 * @since 4.2.0 2129 * 2130 * @returns {void} 1684 2131 */ 1685 2132 nextTheme: function () { … … 1696 2143 * 1697 2144 * @since 4.2.0 2145 * 2146 * @returns {object|boolean} Next theme. 1698 2147 */ 1699 2148 getNextTheme: function () { 1700 var control, next;1701 control = api.control( 'theme_' + this.currentTheme );2149 var section = this, control, next; 2150 control = api.control( section.params.action + '_theme_' + this.currentTheme ); 1702 2151 next = control.container.next( 'li.customize-control-theme' ); 1703 2152 if ( ! next.length ) { 1704 2153 return false; 1705 2154 } 1706 next = next[0].id.replace( 'customize-control- ', '' );2155 next = next[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' ); 1707 2156 control = api.control( next ); 1708 2157 … … 1714 2163 * 1715 2164 * @since 4.2.0 2165 * @returns {void} 1716 2166 */ 1717 2167 previousTheme: function () { … … 1728 2178 * 1729 2179 * @since 4.2.0 2180 * @returns {object|boolean} Previous theme. 1730 2181 */ 1731 2182 getPreviousTheme: function () { 1732 var control, previous;1733 control = api.control( 'theme_' + this.currentTheme );2183 var section = this, control, previous; 2184 control = api.control( section.params.action + '_theme_' + this.currentTheme ); 1734 2185 previous = control.container.prev( 'li.customize-control-theme' ); 1735 2186 if ( ! previous.length ) { 1736 2187 return false; 1737 2188 } 1738 previous = previous[0].id.replace( 'customize-control- ', '' );2189 previous = previous[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' ); 1739 2190 control = api.control( previous ); 1740 2191 … … 1746 2197 * 1747 2198 * @since 4.2.0 2199 * 2200 * @returns {void} 1748 2201 */ 1749 2202 updateLimits: function () { … … 1821 2274 * @since 4.2.0 1822 2275 * 1823 * @param {Object} theme 2276 * @param {object} theme - Theme. 2277 * @param {Function} [callback] - Callback once the details have been shown. 2278 * @returns {void} 1824 2279 */ 1825 2280 showDetails: function ( theme, callback ) { 1826 var section = this, link; 1827 callback = callback || function(){}; 2281 var section = this; 1828 2282 section.currentTheme = theme.id; 1829 2283 section.overlay.html( section.template( theme ) ) 1830 2284 .fadeIn( 'fast' ) 1831 2285 .focus(); 1832 $( 'body' ).addClass( 'modal-open' );2286 section.$body.addClass( 'modal-open' ); 1833 2287 section.containFocus( section.overlay ); 1834 2288 section.updateLimits(); 1835 1836 link = section.overlay.find( '.inactive-theme > a' ); 1837 1838 link.on( 'click', function( event ) { 1839 event.preventDefault(); 1840 1841 // Short-circuit if request is currently being made. 1842 if ( link.hasClass( 'disabled' ) ) { 1843 return; 1844 } 1845 link.addClass( 'disabled' ); 1846 1847 section.loadThemePreview( theme.id ).fail( function() { 1848 link.removeClass( 'disabled' ); 1849 } ); 1850 } ); 1851 callback(); 2289 wp.a11y.speak( api.settings.l10n.announceThemeDetails.replace( '%s', theme.name ) ); 2290 if ( callback ) { 2291 callback(); 2292 } 1852 2293 }, 1853 2294 … … 1856 2297 * 1857 2298 * @since 4.2.0 2299 * 2300 * @returns {void} 1858 2301 */ 1859 2302 closeDetails: function () { 1860 $( 'body' ).removeClass( 'modal-open' ); 1861 this.overlay.fadeOut( 'fast' ); 1862 api.control( 'theme_' + this.currentTheme ).focus(); 2303 var section = this; 2304 section.$body.removeClass( 'modal-open' ); 2305 section.overlay.fadeOut( 'fast' ); 2306 api.control( section.params.action + '_theme_' + section.currentTheme ).container.find( '.theme' ).focus(); 1863 2307 }, 1864 2308 … … 1867 2311 * 1868 2312 * @since 4.2.0 2313 * 2314 * @param {jQuery} el - Element to contain focus. 2315 * @returns {void} 1869 2316 */ 1870 2317 containFocus: function( el ) { … … 1919 2366 section.containerParent = '#customize-outer-theme-controls'; 1920 2367 section.containerPaneParent = '.customize-outer-pane-parent'; 1921 returnapi.Section.prototype.initialize.apply( section, arguments );2368 api.Section.prototype.initialize.apply( section, arguments ); 1922 2369 }, 1923 2370 … … 1940 2387 backBtn = content.find( '.customize-section-back' ), 1941 2388 sectionTitle = section.headContainer.find( '.accordion-section-title' ).first(), 1942 body = $( 'body'),2389 body = $( document.body ), 1943 2390 expand, panel; 1944 2391 … … 2059 2506 if ( ! panel.contentContainer.parent().is( panel.headContainer ) ) { 2060 2507 container.append( panel.contentContainer ); 2061 panel.renderContent();2062 }2508 } 2509 panel.renderContent(); 2063 2510 2064 2511 panel.deferred.embedded.resolve(); … … 2132 2579 * @since 4.1.0 2133 2580 * 2134 * @returns {boolean} 2581 * @returns {boolean} Whether contextually active. 2135 2582 */ 2136 2583 isContextuallyActive: function () { … … 2147 2594 2148 2595 /** 2149 * Update UI to reflect expanded state 2596 * Update UI to reflect expanded state. 2150 2597 * 2151 2598 * @since 4.1.0 … … 2155 2602 * @param {Boolean} args.unchanged 2156 2603 * @param {Function} args.completeCallback 2604 * @returns {void} 2157 2605 */ 2158 2606 onChangeExpanded: function ( expanded, args ) { … … 2262 2710 panel.contentContainer.html( template( panel.params ) ); 2263 2711 } 2712 } 2713 }); 2714 2715 /** 2716 * Class wp.customize.ThemesPanel. 2717 * 2718 * Custom section for themes that displays without the customize preview. 2719 * 2720 * @constructor 2721 * @augments wp.customize.Panel 2722 * @augments wp.customize.Container 2723 */ 2724 api.ThemesPanel = api.Panel.extend({ 2725 2726 /** 2727 * Initialize. 2728 * 2729 * @since 4.9.0 2730 * 2731 * @param {string} id - The ID for the panel. 2732 * @param {object} options - Options. 2733 * @returns {void} 2734 */ 2735 initialize: function( id, options ) { 2736 var panel = this; 2737 panel.installingThemes = []; 2738 api.Panel.prototype.initialize.call( panel, id, options ); 2739 }, 2740 2741 /** 2742 * Attach events. 2743 * 2744 * @since 4.9.0 2745 * @returns {void} 2746 */ 2747 attachEvents: function() { 2748 var panel = this; 2749 2750 // Attach regular panel events. 2751 api.Panel.prototype.attachEvents.apply( panel ); 2752 2753 // Collapse panel to customize the current theme. 2754 panel.contentContainer.on( 'click', '.customize-theme', function() { 2755 panel.collapse(); 2756 }); 2757 2758 // Toggle between filtering and browsing themes on mobile. 2759 panel.contentContainer.on( 'click', '.customize-themes-section-title, .customize-themes-mobile-back', function() { 2760 $( '.wp-full-overlay' ).toggleClass( 'showing-themes' ); 2761 }); 2762 2763 // Install (and maybe preview) a theme. 2764 panel.contentContainer.on( 'click', '.theme-install', function( event ) { 2765 panel.installTheme( event ); 2766 }); 2767 2768 // Update a theme. Theme cards have the class, the details modal has the id. 2769 panel.contentContainer.on( 'click', '.update-theme, #update-theme', function( event ) { 2770 2771 // #update-theme is a link. 2772 event.preventDefault(); 2773 event.stopPropagation(); 2774 2775 panel.updateTheme( event ); 2776 }); 2777 2778 // Delete a theme. 2779 panel.contentContainer.on( 'click', '.delete-theme', function( event ) { 2780 panel.deleteTheme( event ); 2781 }); 2782 2783 _.bindAll( panel, 'installTheme', 'updateTheme' ); 2784 }, 2785 2786 /** 2787 * Update UI to reflect expanded state 2788 * 2789 * @since 4.9.0 2790 * 2791 * @param {Boolean} expanded - Expanded state. 2792 * @param {Object} args - Args. 2793 * @param {Boolean} args.unchanged - Whether or not the state changed. 2794 * @param {Function} args.completeCallback - Callback to execute when the animation completes. 2795 * @returns {void} 2796 */ 2797 onChangeExpanded: function( expanded, args ) { 2798 var panel = this, overlay; 2799 2800 // Expand/collapse the panel normally. 2801 api.Panel.prototype.onChangeExpanded.apply( this, [ expanded, args ] ); 2802 2803 // Immediately call the complete callback if there were no changes 2804 if ( args.unchanged ) { 2805 if ( args.completeCallback ) { 2806 args.completeCallback(); 2807 } 2808 return; 2809 } 2810 2811 overlay = panel.headContainer.closest( '.wp-full-overlay' ); 2812 2813 if ( expanded ) { 2814 overlay 2815 .addClass( 'in-themes-panel' ) 2816 .delay( 200 ).find( '.customize-themes-full-container' ).addClass( 'animate' ); 2817 2818 // Automatically open the installed themes section (except on small screens). 2819 if ( 600 < window.innerWidth ) { 2820 api.section( 'installed_themes' ).expand(); 2821 } 2822 } else { 2823 overlay 2824 .removeClass( 'in-themes-panel' ) 2825 .find( '.customize-themes-full-container' ).removeClass( 'animate' ); 2826 } 2827 }, 2828 2829 /** 2830 * Install a theme via wp.updates. 2831 * 2832 * @since 4.9.0 2833 * 2834 * @returns {void} 2835 */ 2836 installTheme: function( event ) { 2837 var panel = this, preview = false, slug = $( event.target ).data( 'slug' ); 2838 2839 if ( _.contains( panel.installingThemes, slug ) ) { 2840 return; // Theme is already being installed. 2841 } 2842 2843 wp.updates.maybeRequestFilesystemCredentials( event ); 2844 2845 $( document ).one( 'wp-theme-install-success', function( event, response ) { 2846 var theme = false, customizeId, themeControl; 2847 if ( preview ) { 2848 2849 panel.loadThemePreview( slug ).fail( function() { 2850 $( '.wp-full-overlay' ).removeClass( 'customize-loading' ); 2851 } ); 2852 2853 } else { 2854 api.control.each( function( control ) { 2855 if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) { 2856 theme = control.params.theme; // Used below to add theme control. 2857 control.rerenderAsInstalled( true ); 2858 } 2859 }); 2860 2861 // Don't add the same theme more than once. 2862 if ( ! theme || api.control.has( 'installed_theme_' + theme.id ) ) { 2863 return; 2864 } 2865 2866 // Add theme control to installed section. 2867 theme.type = 'installed'; 2868 customizeId = 'installed_theme_' + theme.id; 2869 themeControl = new api.controlConstructor.theme( customizeId, { 2870 params: { 2871 type: 'theme', 2872 content: $( '<li class="customize-control customize-control-theme"></li>' ).attr( 'id', 'customize-control-theme-installed_' + theme.id ).prop( 'outerHTML' ), 2873 section: 'installed_themes', 2874 active: true, 2875 theme: theme, 2876 priority: 0 // Add all newly-installed themes to the top. 2877 }, 2878 previewer: api.previewer 2879 } ); 2880 2881 api.control.add( customizeId, themeControl ); 2882 api.control( customizeId ).container.trigger( 'render-screenshot' ); 2883 2884 // Close the details modal if it's open to the installed theme. 2885 api.section.each( function( section ) { 2886 if ( 'themes' === section.params.type ) { 2887 if ( theme.id === section.currentTheme ) { // Don't close the modal if the user has navigated elsewhere. 2888 section.closeDetails(); 2889 } 2890 } 2891 }); 2892 } 2893 } ); 2894 2895 panel.installingThemes.push( $( event.target ).data( 'slug' ) ); // Note: we don't remove elements from installingThemes, since they shouldn't be installed again. 2896 wp.updates.installTheme( { 2897 slug: slug 2898 } ); 2899 2900 // Also preview the theme as the event is triggered on Install & Preview. 2901 if ( $( event.target ).hasClass( 'preview' ) ) { 2902 preview = true; 2903 $( '.wp-full-overlay' ).addClass( 'customize-loading' ); 2904 wp.a11y.speak( $( '#customize-themes-loading-container .customize-loading-text-installing-theme' ).text() ); 2905 } 2906 }, 2907 2908 /** 2909 * Load theme preview. 2910 * 2911 * @since 4.9.0 2912 * 2913 * @param {string} themeId Theme ID. 2914 * @returns {jQuery.promise} Promise. 2915 */ 2916 loadThemePreview: function( themeId ) { 2917 var deferred = $.Deferred(), onceProcessingComplete, overlay, urlParser; 2918 2919 urlParser = document.createElement( 'a' ); 2920 urlParser.href = location.href; 2921 urlParser.search = $.param( _.extend( 2922 api.utils.parseQueryString( urlParser.search.substr( 1 ) ), 2923 { 2924 theme: themeId, 2925 changeset_uuid: api.settings.changeset.uuid 2926 } 2927 ) ); 2928 2929 // Update loading message. Everything else is handled by reloading the page. 2930 $( '#customize-themes-loading-container span' ).hide(); 2931 $( '#customize-themes-loading-container .customize-loading-text' ).css( 'display', 'block' ); 2932 wp.a11y.speak( $( '#customize-themes-loading-container .customize-loading-text' ).text() ); 2933 overlay = $( '.wp-full-overlay' ); 2934 overlay.addClass( 'customize-loading' ); 2935 2936 onceProcessingComplete = function() { 2937 var request; 2938 if ( api.state( 'processing' ).get() > 0 ) { 2939 return; 2940 } 2941 2942 api.state( 'processing' ).unbind( onceProcessingComplete ); 2943 2944 request = api.requestChangesetUpdate(); 2945 request.done( function() { 2946 deferred.resolve(); 2947 $( window ).off( 'beforeunload.customize-confirm' ); 2948 window.location.href = urlParser.href; 2949 } ); 2950 request.fail( function() { 2951 overlay.removeClass( 'customize-loading' ); 2952 deferred.reject(); 2953 } ); 2954 }; 2955 2956 if ( 0 === api.state( 'processing' ).get() ) { 2957 onceProcessingComplete(); 2958 } else { 2959 api.state( 'processing' ).bind( onceProcessingComplete ); 2960 } 2961 2962 return deferred.promise(); 2963 }, 2964 2965 /** 2966 * Update a theme via wp.updates. 2967 * 2968 * @since 4.9.0 2969 * 2970 * @param {jQuery.Event} event - Event. 2971 * @returns {void} 2972 */ 2973 updateTheme: function( event ) { 2974 wp.updates.maybeRequestFilesystemCredentials( event ); 2975 2976 $( document ).one( 'wp-theme-update-success', function( e, response ) { 2977 2978 // Rerender the control to reflect the update. 2979 api.control.each( function( control ) { 2980 if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) { 2981 control.params.theme.hasUpdate = false; 2982 control.rerenderAsInstalled( true ); 2983 } 2984 }); 2985 } ); 2986 2987 wp.updates.updateTheme( { 2988 slug: $( event.target ).closest( '.notice' ).data( 'slug' ) 2989 } ); 2990 }, 2991 2992 /** 2993 * Delete a theme via wp.updates. 2994 * 2995 * @since 4.9.0 2996 * 2997 * @param {jQuery.Event} event - Event. 2998 * @returns {void} 2999 */ 3000 deleteTheme: function( event ) { 3001 var theme, section; 3002 theme = $( event.target ).data( 'slug' ); 3003 section = api.section( 'installed_themes' ); 3004 3005 event.preventDefault(); 3006 3007 // Confirmation dialog for deleting a theme. 3008 if ( ! window.confirm( api.settings.l10n.confirmDeleteTheme ) ) { 3009 return; 3010 } 3011 3012 wp.updates.maybeRequestFilesystemCredentials( event ); 3013 3014 $( document ).one( 'wp-theme-delete-success', function() { 3015 var control = api.control( 'installed_theme_' + theme ); 3016 3017 // Remove theme control. 3018 control.container.remove(); 3019 api.control.remove( control.id ); 3020 3021 // Update installed count. 3022 section.loaded = section.loaded - 1; 3023 section.updateCount(); 3024 3025 // Rerender any other theme controls as uninstalled. 3026 api.control.each( function( control ) { 3027 if ( 'theme' === control.params.type && control.params.theme.id === theme ) { 3028 control.rerenderAsInstalled( false ); 3029 } 3030 }); 3031 } ); 3032 3033 wp.updates.deleteTheme( { 3034 slug: theme 3035 } ); 3036 3037 // Close modal and focus the section. 3038 section.closeDetails(); 3039 section.focus(); 2264 3040 } 2265 3041 }); … … 2614 3390 * @param {Object} args 2615 3391 * @param {Number} args.duration 2616 * @param { Callback} args.completeCallback3392 * @param {Function} args.completeCallback 2617 3393 */ 2618 3394 onChangeActive: function ( active, args ) { … … 3786 4562 3787 4563 touchDrag: false, 3788 isRendered: false, 3789 3790 /** 3791 * Defer rendering the theme control until the section is displayed. 3792 * 3793 * @since 4.2.0 3794 */ 3795 renderContent: function () { 3796 var control = this, 3797 renderContentArgs = arguments; 3798 3799 api.section( control.section(), function( section ) { 3800 if ( section.expanded() ) { 3801 api.Control.prototype.renderContent.apply( control, renderContentArgs ); 3802 control.isRendered = true; 3803 } else { 3804 section.expanded.bind( function( expanded ) { 3805 if ( expanded && ! control.isRendered ) { 3806 api.Control.prototype.renderContent.apply( control, renderContentArgs ); 3807 control.isRendered = true; 3808 } 3809 } ); 3810 } 3811 } ); 3812 }, 4564 screenshotRendered: false, 3813 4565 3814 4566 /** … … 3834 4586 3835 4587 // Prevent the modal from showing when the user clicks the action button. 3836 if ( $( event.target ).is( '.theme-actions .button ' ) ) {4588 if ( $( event.target ).is( '.theme-actions .button, .update-theme' ) ) { 3837 4589 return; 3838 4590 } 3839 4591 3840 api.section( control.section() ).loadThemePreview( control.params.theme.id );3841 });3842 3843 control.container.on( 'click keydown', '.theme-actions .theme-details', function( event ) {3844 if ( api.utils.isKeydownButNotEnterEvent( event ) ) {3845 return;3846 }3847 3848 4592 event.preventDefault(); // Keep this AFTER the key filter above 3849 3850 4593 api.section( control.section() ).showDetails( control.params.theme ); 3851 4594 }); … … 3858 4601 $screenshot.attr( 'src', source ); 3859 4602 } 3860 }); 3861 }, 3862 3863 /** 3864 * Show or hide the theme based on the presence of the term in the title, description, and author. 4603 control.screenshotRendered = true; 4604 }); 4605 }, 4606 4607 /** 4608 * Show or hide the theme based on the presence of the term in the title, description, tags, and author. 3865 4609 * 3866 4610 * @since 4.2.0 4611 * @returns {boolean} Whether a theme control was activated or not. 3867 4612 */ 3868 4613 filter: function( term ) { … … 3875 4620 if ( -1 !== haystack.search( term ) ) { 3876 4621 control.activate(); 4622 return true; 3877 4623 } else { 3878 4624 control.deactivate(); 3879 } 4625 return false; 4626 } 4627 }, 4628 4629 /** 4630 * Rerender the theme from its JS template with the installed type. 4631 * 4632 * @since 4.9.0 4633 * 4634 * @returns {void} 4635 */ 4636 rerenderAsInstalled: function( installed ) { 4637 var control = this, section; 4638 if ( installed ) { 4639 control.params.theme.type = 'installed'; 4640 } else { 4641 section = api.section( control.params.section ); 4642 control.params.theme.type = section.params.action; 4643 } 4644 control.renderContent(); // Replaces existing content. 4645 control.container.trigger( 'render-screenshot' ); 3880 4646 } 3881 4647 }); … … 5281 6047 code_editor: api.CodeEditorControl 5282 6048 }; 5283 api.panelConstructor = {}; 6049 api.panelConstructor = { 6050 themes: api.ThemesPanel 6051 }; 5284 6052 api.sectionConstructor = { 5285 6053 themes: api.ThemesSection, … … 5400 6168 // Sort the sections within each panel 5401 6169 api.panel.each( function ( panel ) { 6170 if ( 'themes' === panel.id ) { 6171 return; // Don't reflow theme sections, as doing so moves them after the themes container. 6172 } 6173 5402 6174 var sections = panel.sections(), 5403 6175 sectionHeadContainers = _.pluck( sections, 'headContainer' ); … … 6224 6996 }; 6225 6997 6226 /** 6227 * Deactivate themes section if changeset status is not auto-draft 6228 */ 6229 api.section( 'themes', function( section ) { 6998 // Deactivate themes panel if changeset status is not auto-draft. 6999 api.panel( 'themes', function( panel ) { 6230 7000 var canActivate; 6231 7001 … … 6234 7004 }; 6235 7005 6236 section.active.validate = canActivate;6237 section.active.set( canActivate() );7006 panel.active.validate = canActivate; 7007 panel.active.set( canActivate() ); 6238 7008 changesetStatus.bind( function() { 6239 section.active.set( canActivate() );7009 panel.active.set( canActivate() ); 6240 7010 } ); 6241 7011 } ); … … 6401 7171 6402 7172 // Keyboard shortcuts - esc to exit section/panel. 6403 $( 'body' ).on( 'keydown', function( event ) {7173 body.on( 'keydown', function( event ) { 6404 7174 var collapsedObject, expandedControls = [], expandedSections = [], expandedPanels = []; 6405 7175 … … 6441 7211 collapsedObject = expandedControls[0] || expandedSections[0] || expandedPanels[0]; 6442 7212 if ( collapsedObject ) { 7213 if ( 'themes' === collapsedObject.params.type ) { 7214 7215 // Themes panel or section. 7216 if ( body.hasClass( 'modal-open' ) ) { 7217 collapsedObject.closeDetails(); 7218 } else { 7219 7220 // If we're collapsing a section, collapse the panel also. 7221 wp.customize.panel( 'themes' ).collapse(); 7222 } 7223 return; 7224 } 6443 7225 collapsedObject.collapse(); 6444 7226 event.preventDefault(); -
trunk/src/wp-admin/js/updates.js
r41609 r41648 184 184 $notice.replaceWith( $adminNotice ); 185 185 } else { 186 $( '.wrap' ).find( '> h1' ).after( $adminNotice ); 186 if ( 'customize' === pagenow ) { 187 $( '.customize-themes-notifications' ).append( $adminNotice ); 188 } else { 189 $( '.wrap' ).find( '> h1' ).after( $adminNotice ); 190 } 187 191 } 188 192 … … 931 935 $notice = $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' ); 932 936 937 } else if ( 'customize' === pagenow ) { 938 939 // Update the theme details UI. 940 $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' ); 941 942 $notice.find( 'h3' ).remove(); 943 944 // Add the top-level UI, and update both. 945 $notice = $notice.add( $( '#customize-control-theme-installed_' + args.slug ).find( '.update-message' ) ); 946 $notice = $notice.addClass( 'updating-message' ).find( 'p' ); 947 933 948 } else { 934 949 $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' ); … … 973 988 $notice, newText; 974 989 990 if ( 'customize' === pagenow ) { 991 $theme = wp.customize.control( 'installed_theme_' + response.slug ).container; 992 } 993 975 994 if ( 'themes-network' === pagenow ) { 976 995 $notice = $theme.find( '.update-message' ); … … 1025 1044 if ( wp.updates.maybeHandleCredentialError( response, 'update-theme' ) ) { 1026 1045 return; 1046 } 1047 1048 if ( 'customize' === pagenow ) { 1049 $theme = wp.customize.control( 'installed_theme_' + response.slug ).container; 1027 1050 } 1028 1051 … … 1163 1186 } 1164 1187 1165 if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) { 1166 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1167 $card = $( '.install-theme-info' ).prepend( $message ); 1188 if ( 'customize' === pagenow ) { 1189 if ( $document.find( 'body' ).hasClass( 'modal-open' ) ) { 1190 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1191 $card = $( '.theme-overlay .theme-info' ).prepend( $message ); 1192 } else { 1193 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1194 $card = $button.closest( '.theme' ).addClass( 'theme-install-failed' ).append( $message ); 1195 } 1196 $( '.wp-full-overlay' ).removeClass( 'customize-loading' ); 1168 1197 } else { 1169 $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message ); 1170 $button = $card.find( '.theme-install' ); 1198 if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) { 1199 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1200 $card = $( '.install-theme-info' ).prepend( $message ); 1201 } else { 1202 $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message ); 1203 $button = $card.find( '.theme-install' ); 1204 } 1171 1205 } 1172 1206 -
trunk/src/wp-includes/class-wp-customize-manager.php
r41640 r41648 321 321 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' ); 322 322 323 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-panel.php' ); 323 324 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php' ); 324 325 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php' ); … … 376 377 add_action( 'wp_ajax_customize_save', array( $this, 'save' ) ); 377 378 add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) ); 379 add_action( 'wp_ajax_customize-load-themes', array( $this, 'load_themes_ajax' ) ); 378 380 add_action( 'wp_ajax_dismiss_customize_changeset_autosave', array( $this, 'handle_dismiss_changeset_autosave_request' ) ); 379 381 … … 393 395 // Export the settings to JS via the _wpCustomizeSettings variable. 394 396 add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 ); 397 398 // Add theme update notices. 399 if ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) ) { 400 require_once ABSPATH . '/wp-admin/includes/update.php'; 401 add_action( 'customize_controls_print_footer_scripts', 'wp_print_admin_notice_templates' ); 402 } 395 403 } 396 404 … … 3686 3694 $control->enqueue(); 3687 3695 } 3696 3697 if ( ! is_multisite() && ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) || current_user_can( 'delete_themes' ) ) ) { 3698 wp_enqueue_script( 'updates' ); 3699 } 3688 3700 } 3689 3701 … … 3890 3902 'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ), 3891 3903 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ), 3904 'switch-themes' => wp_create_nonce( 'switch-themes' ), 3892 3905 'dismiss_autosave' => wp_create_nonce( 'dismiss_customize_changeset_autosave' ), 3893 3906 ); … … 3996 4009 'documentTitleTmpl' => $this->get_document_title_template(), 3997 4010 'previewableDevices' => $this->get_previewable_devices(), 4011 'l10n' => array( 4012 'confirmDeleteTheme' => __( 'Are you sure you want to delete this theme?' ), 4013 /* translators: %d is the number of theme search results, which cannot currently consider singular vs. plural forms */ 4014 'themeSearchResults' => __( '%d themes found' ), 4015 /* translators: %d is the number of themes being displayed, which cannot currently consider singular vs. plural forms */ 4016 'announceThemeCount' => __( 'Displaying %d themes' ), 4017 /* translators: %s is the theme name */ 4018 'announceThemeDetails' => __( 'Showing details for theme: %s' ), 4019 ), 3998 4020 ); 3999 4021 … … 4099 4121 /* Panel, Section, and Control Types */ 4100 4122 $this->register_panel_type( 'WP_Customize_Panel' ); 4123 $this->register_panel_type( 'WP_Customize_Themes_Panel' ); 4101 4124 $this->register_section_type( 'WP_Customize_Section' ); 4102 4125 $this->register_section_type( 'WP_Customize_Sidebar_Section' ); 4126 $this->register_section_type( 'WP_Customize_Themes_Section' ); 4103 4127 $this->register_control_type( 'WP_Customize_Color_Control' ); 4104 4128 $this->register_control_type( 'WP_Customize_Media_Control' ); … … 4160 4184 ) ) ); 4161 4185 4162 /* Themes */ 4163 4164 $this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array( 4165 'title' => $this->theme()->display( 'Name' ), 4166 'capability' => 'switch_themes', 4167 'priority' => 0, 4186 /* Themes (controls are loaded via ajax) */ 4187 4188 $this->add_panel( new WP_Customize_Themes_Panel( $this, 'themes', array( 4189 'title' => $this->theme()->display( 'Name' ), 4190 'description' => __( 'Once themes are installed, you can live-preview them on your site, customize them, and publish your new design. Browse available themes via the filters in this menu.' ), 4191 'capability' => 'switch_themes', 4192 'priority' => 0, 4168 4193 ) ) ); 4194 4195 $this->add_section( new WP_Customize_Themes_Section( $this, 'installed_themes', array( 4196 'title' => __( 'Installed themes' ), 4197 'action' => 'installed', 4198 'capability' => 'switch_themes', 4199 'panel' => 'themes', 4200 'priority' => 0, 4201 ) ) ); 4202 4203 if ( ! is_multisite() ) { 4204 $this->add_section( new WP_Customize_Themes_Section( $this, 'wporg_themes', array( 4205 'title' => __( 'WordPress.org themes' ), 4206 'action' => 'wporg', 4207 'capability' => 'install_themes', 4208 'panel' => 'themes', 4209 'priority' => 5, 4210 ) ) ); 4211 } 4169 4212 4170 4213 // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience). … … 4172 4215 'capability' => 'switch_themes', 4173 4216 ) ) ); 4174 4175 require_once( ABSPATH . 'wp-admin/includes/theme.php' );4176 4177 // Theme Controls.4178 4179 // Add a control for the active/original theme.4180 if ( ! $this->is_theme_active() ) {4181 $themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) );4182 $active_theme = current( $themes );4183 $active_theme['isActiveTheme'] = true;4184 $this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array(4185 'theme' => $active_theme,4186 'section' => 'themes',4187 'settings' => 'active_theme',4188 ) ) );4189 }4190 4191 $themes = wp_prepare_themes_for_js();4192 foreach ( $themes as $theme ) {4193 if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) {4194 continue;4195 }4196 4197 $theme_id = 'theme_' . $theme['id'];4198 $theme['isActiveTheme'] = false;4199 $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(4200 'theme' => $theme,4201 'section' => 'themes',4202 'settings' => 'active_theme',4203 ) ) );4204 }4205 4217 4206 4218 /* Site Identity */ … … 4708 4720 4709 4721 /** 4722 * Load themes into the theme browsing/installation UI. 4723 * 4724 * @since 4.9.0 4725 */ 4726 public function load_themes_ajax() { 4727 check_ajax_referer( 'switch-themes', 'switch-themes-nonce' ); 4728 4729 if ( ! current_user_can( 'switch_themes' ) ) { 4730 wp_die( -1 ); 4731 } 4732 4733 if ( empty( $_POST['theme_action'] ) ) { 4734 wp_send_json_error( 'missing_theme_action' ); 4735 } 4736 $theme_action = sanitize_key( $_POST['theme_action'] ); 4737 $themes = array(); 4738 4739 require_once ABSPATH . 'wp-admin/includes/theme.php'; 4740 if ( 'installed' === $theme_action ) { 4741 $themes = array( 'themes' => wp_prepare_themes_for_js() ); 4742 foreach ( $themes['themes'] as &$theme ) { 4743 $theme['type'] = 'installed'; 4744 $theme['active'] = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme['id'] ); 4745 } 4746 } elseif ( 'wporg' === $theme_action ) { 4747 if ( ! current_user_can( 'install_themes' ) ) { 4748 wp_die( -1 ); 4749 } 4750 4751 // Arguments for all queries. 4752 $args = array( 4753 'per_page' => 100, 4754 'page' => isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1, 4755 'fields' => array( 4756 'screenshot_url' => true, 4757 'description' => true, 4758 'rating' => true, 4759 'downloaded' => true, 4760 'downloadlink' => true, 4761 'last_updated' => true, 4762 'homepage' => true, 4763 'num_ratings' => true, 4764 'tags' => true, 4765 'parent' => true, 4766 // 'extended_author' => true, @todo: WordPress.org throws a 500 server error when this is here. 4767 ), 4768 ); 4769 4770 // Define query filters based on user input. 4771 if ( ! array_key_exists( 'search', $_POST ) ) { 4772 $args['search'] = ''; 4773 } else { 4774 $args['search'] = sanitize_text_field( wp_unslash( $_POST['search'] ) ); 4775 } 4776 4777 if ( ! array_key_exists( 'tags', $_POST ) ) { 4778 $args['tag'] = ''; 4779 } else { 4780 $args['tag'] = array_map( 'sanitize_text_field', wp_unslash( (array) $_POST['tags'] ) ); 4781 } 4782 4783 if ( '' === $args['search'] && '' === $args['tag'] ) { 4784 $args['browse'] = 'new'; // Sort by latest themes by default. 4785 } 4786 4787 // Load themes from the .org API. 4788 $themes = themes_api( 'query_themes', $args ); 4789 if ( is_wp_error( $themes ) ) { 4790 wp_send_json_error(); 4791 } 4792 4793 // This list matches the allowed tags in wp-admin/includes/theme-install.php. 4794 $themes_allowedtags = array_fill_keys( 4795 array( 'a', 'abbr', 'acronym', 'code', 'pre', 'em', 'strong', 'div', 'p', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img' ), 4796 array() 4797 ); 4798 $themes_allowedtags['a'] = array_fill_keys( array( 'href', 'title', 'target' ), true ); 4799 $themes_allowedtags['acronym']['title'] = true; 4800 $themes_allowedtags['abbr']['title'] = true; 4801 $themes_allowedtags['img'] = array_fill_keys( array( 'src', 'class', 'alt' ), true ); 4802 4803 // Prepare a list of installed themes to check against before the loop. 4804 $installed_themes = array(); 4805 $wp_themes = wp_get_themes(); 4806 foreach ( $wp_themes as $theme ) { 4807 $installed_themes[] = $theme->get_stylesheet(); 4808 } 4809 $update_php = network_admin_url( 'update.php?action=install-theme' ); 4810 4811 // Set up properties for themes available on WordPress.org. 4812 foreach ( $themes->themes as &$theme ) { 4813 $theme->install_url = add_query_arg( array( 4814 'theme' => $theme->slug, 4815 '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ), 4816 ), $update_php ); 4817 4818 $theme->name = wp_kses( $theme->name, $themes_allowedtags ); 4819 $theme->author = wp_kses( $theme->author, $themes_allowedtags ); 4820 $theme->version = wp_kses( $theme->version, $themes_allowedtags ); 4821 $theme->description = wp_kses( $theme->description, $themes_allowedtags ); 4822 $theme->tags = implode( ', ', $theme->tags ); 4823 $theme->stars = wp_star_rating( array( 4824 'rating' => $theme->rating, 4825 'type' => 'percent', 4826 'number' => $theme->num_ratings, 4827 'echo' => false, 4828 ) ); 4829 $theme->num_ratings = number_format_i18n( $theme->num_ratings ); 4830 $theme->preview_url = set_url_scheme( $theme->preview_url ); 4831 4832 // Handle themes that are already installed as installed themes. 4833 if ( in_array( $theme->slug, $installed_themes, true ) ) { 4834 $theme->type = 'installed'; 4835 } else { 4836 $theme->type = $theme_action; 4837 } 4838 4839 // Set active based on customized theme. 4840 $theme->active = ( isset( $_POST['customized_theme'] ) && $_POST['customized_theme'] === $theme->slug ); 4841 4842 // Map available theme properties to installed theme properties. 4843 $theme->id = $theme->slug; 4844 $theme->screenshot = array( $theme->screenshot_url ); 4845 $theme->authorAndUri = $theme->author; 4846 $theme->parent = ( $theme->slug === $theme->template ) ? false : $theme->template; // The .org API does not seem to return the parent in a documented way; however, this check should yield a similar result in most cases. 4847 unset( $theme->slug ); 4848 unset( $theme->screenshot_url ); 4849 unset( $theme->author ); 4850 } // End foreach(). 4851 } // End if(). 4852 wp_send_json_success( $themes ); 4853 } 4854 4855 4856 /** 4710 4857 * Callback for validating the header_textcolor value. 4711 4858 * -
trunk/src/wp-includes/css/admin-bar.css
r41622 r41648 694 694 border: 0; 695 695 clip: rect(1px, 1px, 1px, 1px); 696 -webkit-clip-path: inset(50%); 696 697 clip-path: inset(50%); 697 698 height: 1px; -
trunk/src/wp-includes/css/wp-embed-template.css
r41622 r41648 12 12 border: 0; 13 13 clip: rect(1px, 1px, 1px, 1px); 14 -webkit-clip-path: inset(50%); 14 15 clip-path: inset(50%); 15 16 height: 1px; -
trunk/src/wp-includes/customize/class-wp-customize-theme-control.php
r41162 r41648 58 58 */ 59 59 public function content_template() { 60 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); 61 $active_url = esc_url( remove_query_arg( 'customize_theme', $current_url ) ); 62 $preview_url = esc_url( add_query_arg( 'customize_theme', '__THEME__', $current_url ) ); // Token because esc_url() strips curly braces. 63 $preview_url = str_replace( '__THEME__', '{{ data.theme.id }}', $preview_url ); 60 /* translators: %s: theme name */ 61 $details_label = sprintf( __( 'Details for theme: %s' ), '{{ data.theme.name }}' ); 62 /* translators: %s: theme name */ 63 $customize_label = sprintf( __( 'Customize theme: %s' ), '{{ data.theme.name }}' ); 64 /* translators: %s: theme name */ 65 $preview_label = sprintf( __( 'Live preview theme: %s' ), '{{ data.theme.name }}' ); 66 /* translators: %s: theme name */ 67 $install_label = sprintf( __( 'Install and preview theme: %s' ), '{{ data.theme.name }}' ); 64 68 ?> 65 <# if ( data.theme. isActiveTheme ) { #>66 <div class="theme active" tabindex="0" data-preview-url="<?php echo esc_attr( $active_url ); ?>" aria-describedby="{{ data.theme.id }}-action {{ data.theme.id }}-name">69 <# if ( data.theme.active ) { #> 70 <div class="theme active" tabindex="0" aria-describedby="{{ data.section }}-{{ data.theme.id }}-action {{ data.theme.id }}-name"> 67 71 <# } else { #> 68 <div class="theme" tabindex="0" data-preview-url="<?php echo esc_attr( $preview_url ); ?>" aria-describedby="{{ data.theme.id }}-action {{ data.theme.id }}-name">72 <div class="theme" tabindex="0" aria-describedby="{{ data.section }}-{{ data.theme.id }}-action {{ data.theme.id }}-name"> 69 73 <# } #> 70 74 71 <# if ( data.theme.screenshot [0] ) { #>75 <# if ( data.theme.screenshot && data.theme.screenshot[0] ) { #> 72 76 <div class="theme-screenshot"> 73 77 <img data-src="{{ data.theme.screenshot[0] }}" alt="" /> … … 77 81 <# } #> 78 82 79 <# if ( data.theme.isActiveTheme ) { #> 80 <span class="more-details" id="{{ data.theme.id }}-action"><?php _e( 'Customize' ); ?></span> 81 <# } else { #> 82 <span class="more-details" id="{{ data.theme.id }}-action"><?php _e( 'Live Preview' ); ?></span> 83 <# } #> 83 <span class="more-details theme-details" id="{{ data.section }}-{{ data.theme.id }}-action" aria-label="<?php echo esc_attr( $details_label ); ?>"><?php _e( 'Theme Details' ); ?></span> 84 84 85 85 <div class="theme-author"><?php … … 88 88 ?></div> 89 89 90 <# if ( data.theme.isActiveTheme ) { #> 91 <h3 class="theme-name" id="{{ data.theme.id }}-name"> 90 <# if ( 'installed' === data.theme.type && data.theme.hasUpdate ) { #> 91 <div class="update-message notice inline notice-warning notice-alt" data-slug="{{ data.theme.id }}"> 92 <p> 93 <?php 94 /* translators: %s is the linked update now button */ 95 printf( __( 'New version available. %s' ), '<button class="button-link update-theme" type="button">' . __( 'Update now' ) . '</button>' ); 96 ?> 97 </p> 98 </div> 99 <# } #> 100 101 <# if ( data.theme.active ) { #> 102 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name"> 92 103 <?php 93 104 /* translators: %s: theme name */ 94 printf( __( '<span> Active:</span> %s' ), '{{{ data.theme.name }}}' );105 printf( __( '<span>Previewing:</span> %s' ), '{{ data.theme.name }}' ); 95 106 ?> 96 107 </h3> 108 <div class="theme-actions"> 109 <button type="button" class="button button-primary customize-theme" aria-label="<?php echo esc_attr( $customize_label ); ?>"><?php _e( 'Customize' ); ?></button> 110 </div> 111 <div class="notice notice-success notice-alt"><p><?php _e( 'Installed' ); ?></p></div> 112 <# } else if ( 'installed' === data.theme.type ) { #> 113 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> 114 <div class="theme-actions"> 115 <button type="button" class="button button-primary preview-theme" aria-label="<?php echo esc_attr( $preview_label ); ?>" data-slug="{{ data.theme.id }}"><?php _e( 'Live Preview' ); ?></span> 116 </div> 117 <div class="notice notice-success notice-alt"><p><?php _e( 'Installed' ); ?></p></div> 97 118 <# } else { #> 98 <h3 class="theme-name" id="{{ data. theme.id }}-name">{{{ data.theme.name }}}</h3>119 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> 99 120 <div class="theme-actions"> 100 <button type="button" class="button theme-details"><?php _e( 'Theme Details' ); ?></button>121 <button type="button" class="button button-primary theme-install preview" aria-label="<?php echo esc_attr( $install_label ); ?>" data-slug="{{ data.theme.id }}" data-name="{{ data.theme.name }}"><?php _e( 'Install & Preview' ); ?></button> 101 122 </div> 102 123 <# } #> -
trunk/src/wp-includes/customize/class-wp-customize-themes-section.php
r41368 r41648 11 11 * Customize Themes Section class. 12 12 * 13 * A UI container for theme controls, which behaves like a backwards Panel.13 * A UI container for theme controls, which are displayed within sections. 14 14 * 15 15 * @since 4.2.0 … … 20 20 21 21 /** 22 * Customize section type.22 * Section type. 23 23 * 24 24 * @since 4.2.0 … … 28 28 29 29 /** 30 * Render the themes section, which behaves like a panel.30 * Theme section action. 31 31 * 32 * @since 4.2.0 32 * Defines the type of themes to load (installed, wporg, etc.). 33 * 34 * @since 4.9.0 35 * @var string 33 36 */ 34 protected function render() { 35 $classes = 'accordion-section control-section control-section-' . $this->type; 37 public $action = ''; 38 39 /** 40 * Get section parameters for JS. 41 * 42 * @since 4.9.0 43 * @return array Exported parameters. 44 */ 45 public function json() { 46 $exported = parent::json(); 47 $exported['action'] = $this->action; 48 49 return $exported; 50 } 51 52 /** 53 * Render a themes section as a JS template. 54 * 55 * The template is only rendered by PHP once, so all actions are prepared at once on the server side. 56 * 57 * @since 4.9.0 58 */ 59 protected function render_template() { 36 60 ?> 37 <li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>"> 38 <h3 class="accordion-section-title"> 39 <?php 40 if ( $this->manager->is_theme_active() ) { 41 echo '<span class="customize-action">' . __( 'Active theme' ) . '</span> ' . $this->title; 42 } else { 43 echo '<span class="customize-action">' . __( 'Previewing theme' ) . '</span> ' . $this->title; 44 } 45 ?> 46 47 <?php if ( count( $this->controls ) > 0 ) : ?> 48 <button type="button" class="button change-theme" tabindex="0"><?php _ex( 'Change', 'theme' ); ?></button> 49 <?php endif; ?> 50 </h3> 51 <div class="customize-themes-panel control-panel-content themes-php"> 52 <h3 class="accordion-section-title customize-section-title"> 53 <button class="customize-section-back" tabindex="0" type="button"><span class="screen-reader-text"><?php _e( 'Back' ); ?></span></button> 54 <span class="customize-action"><?php _e( 'Customizing' ); ?></span> 55 <?php _e( 'Themes' ); ?> 56 <span class="title-count theme-count"><?php echo count( $this->controls ) + 1 /* Active theme */; ?></span> 57 </h3> 58 <h3 class="accordion-section-title customize-section-title"> 59 <?php 60 if ( $this->manager->is_theme_active() ) { 61 echo '<span class="customize-action">' . __( 'Active theme' ) . '</span> ' . $this->title; 62 } else { 63 echo '<span class="customize-action">' . __( 'Previewing theme' ) . '</span> ' . $this->title; 64 } 65 ?> 66 <button type="button" class="button customize-theme"><?php _e( 'Customize' ); ?></button> 67 </h3> 68 61 <li id="accordion-section-{{ data.id }}" class="theme-section"> 62 <button type="button" class="customize-themes-section-title themes-section-{{ data.id }}">{{ data.title }}</button> 63 <?php if ( current_user_can( 'install_themes' ) || is_multisite() ) : // @todo: upload support ?> 64 <?php endif; ?> 65 <div class="customize-themes-section themes-section-{{ data.id }} control-section-content themes-php"> 69 66 <div class="theme-overlay" tabindex="0" role="dialog" aria-label="<?php esc_attr_e( 'Theme Details' ); ?>"></div> 70 71 <div id="customize-container"></div>72 <?php if ( count( $this->controls ) > 4 ) : ?>73 <p><label for="themes-filter">74 <span class="screen-reader-text"><?php _e( 'Search installed themes…' ); ?></span>75 <input type="text" id="themes-filter" placeholder="<?php esc_attr_e( 'Search installed themes…' ); ?>" />76 </label></p>77 <?php endif; ?>78 67 <div class="theme-browser rendered"> 79 <ul class="themes accordion-section-content"> 68 <div class="customize-preview-header themes-filter-bar"> 69 <?php $this->filter_bar_content_template(); ?> 70 </div> 71 <div class="error unexpected-error" style="display: none; "><p><?php _e( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the <a href="https://wordpress.org/support/">support forums</a>.' ); ?></p></div> 72 <ul class="themes"> 80 73 </ul> 74 <p class="no-themes"><?php _e( 'No themes found. Try a different search.' ); ?></p> 75 <p class="no-themes-local"> 76 <?php 77 /* translators: %s is the string, "search WordPress.org themes" */ 78 printf( __( 'No themes found. Try a different search, or %s.' ), 79 sprintf( '<button type="button" class="button-link search-dotorg-themes">%s</button>', __( 'Search WordPress.org themes' ) ) 80 ); 81 ?> 82 </p> 83 <p class="spinner"></p> 81 84 </div> 82 85 </div> 83 86 </li> 84 <?php } 87 <?php 88 } 89 90 /** 91 * Render the filter bar portion of a themes section as a JS template. 92 * 93 * The template is only rendered by PHP once, so all actions are prepared at once on the server side. 94 * The filter bar container is rendered by @see `render_template()`. 95 * 96 * @since 4.9.0 97 */ 98 protected function filter_bar_content_template() { 99 ?> 100 <button type="button" class="button button-primary customize-section-back customize-themes-mobile-back"><?php _e( 'Back to theme sources' ); ?></button> 101 <# if ( 'wporg' === data.action ) { #> 102 <div class="search-form"> 103 <label class="screen-reader-text" for="wp-filter-search-input"><?php _e( 'Search themes…' ); ?></label> 104 <input placeholder="<?php _e( 'Search themes…' ); ?>" type="search" aria-describedby="live-search-desc" id="wp-filter-search-input" class="wp-filter-search"> 105 <span id="live-search-desc" class="screen-reader-text"><?php _e( 'The search results will be updated as you type.' ); ?></span> 106 </div> 107 <button type="button" class="button feature-filter-toggle"> 108 <span class="filter-count-0"><?php _e( 'Filter themes' ); ?></span><span class="filter-count-filters"> 109 <?php 110 /* translators: %s: number of filters selected. */ 111 printf( __( 'Filter themes (%s)' ), '<span class="theme-filter-count">0</span>' ); 112 ?> 113 </span> 114 </button> 115 <div class="filter-drawer filter-details"> 116 <?php 117 $feature_list = get_theme_feature_list( false ); // @todo: Use the .org API instead of the local core feature list. The .org API is currently outdated and will be reconciled when the .org themes directory is next redesigned. 118 foreach ( $feature_list as $feature_name => $features ) { 119 echo '<fieldset class="filter-group">'; 120 echo '<legend>' . esc_html( $feature_name ) . '</legend>'; 121 echo '<div class="filter-group-feature">'; 122 foreach ( $features as $feature => $feature_name ) { 123 echo '<input type="checkbox" id="filter-id-' . esc_attr( $feature ) . '" value="' . esc_attr( $feature ) . '" /> '; 124 echo '<label for="filter-id-' . esc_attr( $feature ) . '">' . esc_html( $feature_name ) . '</label><br>'; 125 } 126 echo '</div>'; 127 echo '</fieldset>'; 128 } 129 ?> 130 </div> 131 <# } else { #> 132 <p class="themes-filter-container"> 133 <label for="themes-filter"> 134 <span class="screen-reader-text"><?php _e( 'Search themes…' ); ?></span> 135 <input type="search" id="themes-filter" placeholder="<?php esc_attr_e( 'Search themes…' ); ?>" aria-describedby="live-search-desc" class="wp-filter-search wp-filter-search-themes" /> 136 <span id="live-search-desc" class="screen-reader-text"><?php _e( 'The search results will be updated as you type.' ); ?></span> 137 </label> 138 </p> 139 <# } #> 140 <div class="filter-themes-count"> 141 <span class="themes-displayed"> 142 <?php 143 /* translators: %s: number of themes displayed. */ 144 echo sprintf( __( '%s themes' ), '<span class="theme-count">0</span>' ); 145 ?> 146 </span> 147 </div> 148 <?php 149 } 85 150 } -
trunk/tests/phpunit/tests/customize/manager.php
r41626 r41648 2554 2554 $this->assertNotEmpty( $data ); 2555 2555 2556 $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts', 'initialClientTimestamp', 'initialServerDate', 'initialServerTimestamp' ), array_keys( $data ) );2556 $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts', 'initialClientTimestamp', 'initialServerDate', 'initialServerTimestamp', 'l10n' ), array_keys( $data ) ); 2557 2557 $this->assertEquals( $autofocus, $data['autofocus'] ); 2558 2558 $this->assertArrayHasKey( 'save', $data['nonce'] );
Note: See TracChangeset
for help on using the changeset viewer.