Make WordPress Core

Changeset 38813


Ignore:
Timestamp:
10/19/2016 03:19:13 AM (10 years ago)
Author:
westonruter
Message:

Customize: Introduce a new experience for discovering, installing, and previewing themes within the customizer.

Unify the theme-browsing and theme-customization experiences by introducing a comprehensive theme browser and installer directly accessible in the customizer. Replaces the customizer theme switcher with a full-screen panel for discovering/browsing and installing themes available on WordPress.org. Themes can now be installed and previewed directly in the customizer without entering the wp-admin context.

For details, see https://make.wordpress.org/core/2016/10/03/feature-proposal-a-new-experience-for-discovering-installing-and-previewing-themes-in-the-customizer/

Fixes #37661, #34843.
Props celloexpressions, folletto, westonruter, karmatosed, afercia.

Location:
trunk
Files:
1 added
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/css/customize-controls.css

    r38709 r38813  
    272272
    273273#customize-theme-controls .customize-pane-child.open,
    274 #customize-theme-controls .customize-pane-child.current-panel,
    275 #customize-theme-controls .customize-themes-panel.customize-pane-child.current-panel {
     274#customize-theme-controls .customize-pane-child.current-panel {
    276275        -webkit-transform: none;
    277276        -ms-transform: none;
     
    279278}
    280279
    281 #customize-theme-controls .customize-themes-panel.customize-pane-child,
    282280.section-open #customize-theme-controls .customize-pane-parent,
    283281.in-sub-panel #customize-theme-controls .customize-pane-parent,
    284282.section-open #customize-info,
    285283.in-sub-panel #customize-info,
    286 .in-sub-panel.section-open #customize-theme-controls .customize-pane-child.current-panel,
    287 .in-themes-panel #customize-theme-controls .customize-pane-parent,
    288 .in-themes-panel #customize-info {
     284.in-sub-panel.section-open #customize-theme-controls .customize-pane-child.current-panel {
    289285        visibility: hidden;
    290286        height: 0;
     
    297293.section-open #customize-theme-controls .customize-pane-parent.busy,
    298294.in-sub-panel #customize-theme-controls .customize-pane-parent.busy,
    299 .in-themes-panel #customize-theme-controls .customize-pane-parent.busy,
    300295.section-open #customize-info.busy,
    301296.in-sub-panel #customize-info.busy,
    302 .in-themes-panel #customize-info.busy,
    303297.busy.section-open.in-sub-panel #customize-theme-controls .customize-pane-child.current-panel,
    304298#customize-theme-controls .customize-pane-child.open,
     
    308302        height: auto;
    309303        overflow: auto;
    310 }
    311 
    312 .in-themes-panel #customize-theme-controls .customize-pane-parent,
    313 .in-themes-panel #customize-info {
    314         -webkit-transform: translateX(100%);
    315         -ms-transform: translateX(100%);
    316         transform: translateX(100%);
    317304}
    318305
     
    407394        float: left;
    408395        width: 48px;
    409         height: 71px;
     396        height: 70px;
    410397        padding: 0 24px 0 0;
    411398        margin: 0;
     
    421408
    422409.customize-section-back {
    423         height: 74px;
     410        height: 73px;
    424411}
    425412
     
    997984}
    998985
    999 #customize-theme-controls .control-section-themes .accordion-section-title:hover, /* Not a focusable element. */
    1000 #customize-theme-controls .control-section-themes .accordion-section-title {
     986#customize-theme-controls .control-panel-themes {
     987        border-bottom: none;
     988}
     989
     990#customize-theme-controls .control-panel-themes > .accordion-section-title:hover, /* Not a focusable element. */
     991#customize-theme-controls .control-panel-themes > .accordion-section-title {
    1001992        cursor: default;
    1002993        background: #fff;
     
    1005996        border-bottom: 1px solid #ddd;
    1006997        border-left: none;
    1007         margin-top: 0;
     998        border-right: none;
     999        margin: 0 0 15px 0;
     1000        padding-right: 100px; /* Space for the button */
    10081001}
    10091002
     
    10131006}
    10141007
    1015 #customize-theme-controls .control-section-themes > .accordion-section-title:hover, /* Not a focusable element. */
    1016 #customize-theme-controls .control-section-themes > .accordion-section-title {
    1017         margin: 0 0 15px;
    1018 }
    1019 
    1020 #customize-controls .customize-themes-panel .accordion-section-title {
    1021         margin: 15px -8px;
    1022 }
    1023 
    1024 #customize-controls .control-section-themes .accordion-section-title,
    1025 #customize-controls .customize-themes-panel .accordion-section-title {
    1026         padding-right: 100px; /* Space for the button */
    1027 }
    1028 
    1029 #customize-controls .control-section-themes .accordion-section-title span.customize-action,
     1008.control-panel-themes .accordion-section-title span.customize-action,
    10301009#customize-controls .customize-section-title span.customize-action {
    10311010        font-size: 13px;
     
    10341013}
    10351014
    1036 #customize-controls .control-section-themes .accordion-section-title .change-theme,
    1037 #customize-controls .customize-themes-panel .accordion-section-title .customize-theme {
     1015.control-panel-themes .accordion-section-title .change-theme {
    10381016        position: absolute;
    10391017        right: 10px;
     
    10431021}
    10441022
    1045 #customize-controls .control-section-themes .accordion-section-title:before {
     1023#customize-theme-controls .control-panel-themes > .accordion-section-title:after {
    10461024        display: none;
    10471025}
    10481026
    1049 #customize-controls .customize-themes-panel {
    1050         padding: 0 8px;
    1051         background: #f1f1f1;
    1052         -webkit-box-sizing: border-box;
    1053         -moz-box-sizing: border-box;
    1054         box-sizing: border-box;
    1055 }
    1056 
    1057 #customize-controls .customize-themes-panel .accordion-section-title:first-child {
    1058         margin-top: 0;
    1059 }
    1060 
    1061 #customize-controls .customize-themes-panel .accordion-section-title:nth-child(2) {
     1027.control-panel-themes .customize-themes-full-container {
     1028        position: fixed;
     1029        top: 0;
     1030        left: 0;
     1031        transition: .18s left ease-in-out;
     1032        margin: 0 0 0 300px;
     1033        padding:25px;
     1034        overflow-y: scroll;
     1035        width: calc(100% - 350px);
     1036        height: calc(100% - 50px);
     1037        background: #eee;
     1038        z-index: 20;
     1039}
     1040
     1041/* Animations for opening the themes panel */
     1042#customize-header-actions .save,
     1043#customize-header-actions .spinner,
     1044#customize-header-actions .customize-controls-preview-toggle {
     1045        position: relative;
     1046        top: 0;
     1047        transition: .18s top ease-in-out;
     1048}
     1049
     1050#customize-footer-actions,
     1051#customize-footer-actions .collapse-sidebar {
     1052        bottom: 0;
     1053        transition: .18s bottom ease-in-out;
     1054}
     1055
     1056.in-themes-panel:not(.animating) #customize-header-actions .save,
     1057.in-themes-panel:not(.animating) #customize-header-actions .spinner,
     1058.in-themes-panel:not(.animating) #customize-header-actions .customize-controls-preview-toggle,
     1059.in-themes-panel:not(.animating) #customize-preview,
     1060.in-themes-panel:not(.animating) #customize-footer-actions {
     1061        visibility: hidden;
     1062}
     1063
     1064.wp-full-overlay.in-themes-panel {
     1065        background: #eee; /* Prevents a black flash when fading in the panel */
     1066}
     1067
     1068.in-themes-panel #customize-header-actions .save,
     1069.in-themes-panel #customize-header-actions .spinner,
     1070.in-themes-panel #customize-header-actions .customize-controls-preview-toggle {
     1071        top: -45px;
     1072}
     1073
     1074.in-themes-panel #customize-footer-actions,
     1075.in-themes-panel #customize-footer-actions .collapse-sidebar {
     1076        bottom: -45px;
     1077}
     1078
     1079/* Don't show the theme count while the panel opens, as it's in the wrong place during the animation */
     1080.in-themes-panel.animating .control-panel-themes .filter-themes-count {
     1081        display: none;
     1082}
     1083
     1084.in-themes-panel.wp-full-overlay .wp-full-overlay-sidebar-content {
     1085        bottom: 0;
     1086}
     1087
     1088/* Adds a delay before fading in to avoid it "jumping" */
     1089@keyframes themes-fade-in {
     1090        0% {
     1091                opacity: 0;
     1092        }
     1093        50% {
     1094                opacity: 0;
     1095        }
     1096        100% {
     1097                opacity: 1;
     1098        }
     1099}
     1100
     1101.control-panel-themes .customize-themes-full-container.animate {
     1102        animation: .6s themes-fade-in 1;
     1103}
     1104
     1105.in-themes-panel:not(.animating) .control-panel-themes .filter-themes-count {
     1106        animation: .6s themes-fade-in 1;
     1107}
     1108
     1109.control-panel-themes .filter-themes-count {
     1110        position: fixed;
     1111        top: 0;
     1112        left: 48px;
     1113        width: 222px;
     1114        padding: 6px 15px;
     1115        margin: 0;
     1116        line-height: 32px;
     1117        text-align: right;
     1118        z-index: 10;
     1119}
     1120
     1121.control-panel-themes .filter-themes-count .themes-displayed {
     1122        font-weight: 600;
     1123        color: #555d66;
     1124}
     1125
     1126.control-panel-themes .filter-themes-count .see-themes,
     1127.control-panel-themes .filter-themes-count .filter-themes {
     1128        display: none;
     1129}
     1130
     1131
     1132/* Mobile - toggle between themes and filters */
     1133@media screen and (max-width:600px) {
     1134
     1135        /* Show a spinner in the filters view also, reusing the main customize spinner */
     1136        .in-themes-panel.loading #customize-header-actions .spinner {
     1137                position: fixed;
     1138                top: 0;
     1139                left: 48px;
     1140                visibility: visible;
     1141        }
     1142
     1143        .in-themes-panel.loading.showing-themes #customize-header-actions .spinner {
     1144                visibility: hidden;
     1145        }
     1146
     1147        .control-panel-themes .filter-themes-count {
     1148                width: calc(100% - 93px);
     1149        }
     1150
     1151        .control-panel-themes .filter-themes-count .themes-displayed {
     1152                display: none;
     1153        }
     1154
     1155        .wp-full-overlay:not(.showing-themes) .control-panel-themes .filter-themes-count .see-themes {
     1156                display: block;
     1157                float: right;
     1158        }
     1159
     1160        .wp-full-overlay.showing-themes .control-panel-themes .filter-themes-count .filter-themes {
     1161                display: block;
     1162                float: right;
     1163        }
     1164
     1165        .in-themes-panel.showing-themes .control-panel-themes .customize-panel-back {
     1166                position: fixed;
     1167                top: 0;
     1168                left: 0;
     1169                z-index: 10;
     1170                height: 45px;
     1171                background: #eee;
     1172        }
     1173
     1174        .in-themes-panel.showing-themes .control-panel-themes .customize-panel-back:before {
     1175                line-height: 45px;
     1176        }
     1177
     1178        .control-panel-themes .customize-themes-full-container {
     1179                width: calc(100% - 50px);
     1180                margin: 0;
     1181                top: 46px;
     1182                height: calc(100% - 96px);
     1183                z-index: 1;
     1184                display: none;
     1185        }
     1186
     1187        .showing-themes .control-panel-themes .customize-themes-full-container {
     1188                display: block;
     1189        }
     1190}
     1191
     1192.control-panel-themes .customize-themes-notifications .notice {
     1193        margin: 0 0 25px 0;
     1194}
     1195
     1196.customize-themes-full-container .customize-themes-section {
     1197        display: none !important; /* There is unknown JS that perpetually tries to show all theme sections when more items are added. */
     1198        overflow: hidden;
     1199}
     1200
     1201.customize-themes-full-container .customize-themes-section.current-section {
     1202        display: list-item !important; /* There is unknown JS that perpetually tries to show all theme sections when more items are added. */
     1203}
     1204
     1205.theme-section .customize-themes-text-before {
     1206        padding: 0 0 8px 15px;
     1207        margin: 15px 0 0 0;
     1208        line-height: 16px;
     1209        border-bottom: 1px solid #ddd;
     1210        color: #555d66;
     1211}
     1212
     1213.control-panel-themes .customize-themes-section-title {
     1214        width: 100%;
     1215        background: #fff;
     1216        box-shadow: none;
     1217        outline: none;
     1218        border-top: none;
     1219        border-bottom: 1px solid #ddd;
     1220        border-left: 4px solid #fff;
     1221        border-right: none;
     1222        cursor: pointer;
     1223        padding: 10px 15px;
     1224        position: relative;
     1225        text-align: left;
    10621226        font-size: 14px;
    10631227        font-weight: 600;
    1064 }
    1065 
    1066 #customize-controls .customize-themes-panel > h2 {
    1067         padding: 15px 8px 0 8px;
    1068 }
    1069 
    1070 #customize-theme-controls .customize-themes-panel .accordion-section-content {
     1228        color: #555d66;
     1229        text-shadow: none;
     1230}
     1231
     1232.control-panel-themes .theme-section {
     1233        margin: 0;
     1234        position: relative;
     1235}
     1236
     1237.control-panel-themes .customize-themes-section-title:focus,
     1238.control-panel-themes .customize-themes-section-title:hover {
     1239        border-left-color: #0073aa;
     1240        color: #0073aa;
     1241        background: #f5f5f5;
     1242}
     1243
     1244.control-panel-themes .theme-section .customize-themes-section-title.selected:after {
     1245        content: "\f147";
     1246        font: 16px/1 dashicons;
     1247        box-sizing: border-box;
     1248        width: 20px;
     1249        height: 20px;
     1250        padding: 3px 3px 1px 1px; /* Re-align the icon to the smaller grid */
     1251        border-radius: 100%;
     1252        position: absolute;
     1253        top: 9px;
     1254        right: 15px;
     1255        background: #0073aa;
     1256        color: #fff;
     1257}
     1258
     1259.control-panel-themes .customize-themes-section-title.selected {
     1260        color: #0073aa;
     1261}
     1262
     1263.control-panel-themes .customize-themes-section-title.themes-section-search_themes {
     1264        border-left: none;
     1265        padding: 5px 10px 5px 15px;
     1266        width: auto;
     1267}
     1268
     1269.control-panel-themes .customize-themes-section-title.themes-section-feature_filter_themes:after,
     1270.control-panel-themes .customize-themes-section-title.themes-section-favorites_themes:after {
     1271        content: "\f140";
     1272        font: 20px/1 dashicons;
     1273        position: absolute;
     1274        right: 15px;
     1275        top: 8px;
     1276}
     1277
     1278.control-panel-themes .customize-themes-section-title.themes-section-search_themes .wp-filter-search {
     1279        width: 100%;
     1280}
     1281
     1282.control-panel-themes .customize-themes-section-title.themes-section-search_themes.selected,
     1283.control-panel-themes .customize-themes-section-title.themes-section-search_themes:hover {
     1284        background: #fff;
     1285        cursor: default;
     1286}
     1287
     1288.control-panel-themes .customize-themes-section-title.themes-section-feature_filter_themes {
     1289        margin-top: 15px;
     1290        border-top: 1px solid #ddd;
     1291}
     1292
     1293.control-panel-themes .filter-details {
     1294        background: #f5f5f5;
     1295        margin: 0;
     1296        padding: 8px 15px;
     1297        border-top: none;
     1298        border-bottom: 1px solid #ddd;
     1299        display: none;
     1300}
     1301
     1302.control-panel-themes .customize-themes-section-title.selected.details-open {
     1303        border-bottom-color: #f5f5f5;
     1304    border-left-color: #f5f5f5;
     1305        background: #f5f5f5;
     1306}
     1307
     1308.control-panel-themes .favorites-form.filter-details label {
     1309        padding-bottom: 6px;
     1310        display: inline-block;
     1311}
     1312
     1313.control-panel-themes .filter-details .filter-group {
     1314        float: none;
     1315        width: 100%;
    10711316        background: transparent;
    1072         display: block;
    1073 }
    1074 
    1075 .customize-control.customize-control-theme {
    1076         margin-bottom: 8px;
     1317        border: none;
     1318        padding: 0;
     1319        box-shadow: none;
     1320}
     1321
     1322.control-panel-themes .filter-details .filter-group legend button {
     1323        padding: 18px 15px 8px 10px;
     1324        line-height: 14px;
     1325        border-bottom: 1px solid #ddd;
     1326        width: 100%;
     1327        text-align: left;
     1328}
     1329
     1330.control-panel-themes .filter-details .filter-group legend {
     1331        position: relative;
     1332        top: 0;
     1333        width: 100%;
     1334}
     1335
     1336.control-panel-themes .filter-details .filter-group legend button:after {
     1337        content: "\f140";
     1338        font: 20px/1 dashicons;
     1339        position: absolute;
     1340        bottom: 6px;
     1341        right: 5px;
     1342}
     1343
     1344.control-panel-themes .filter-details .filter-group legend button:hover,
     1345.control-panel-themes .filter-details .filter-group legend button:focus {
     1346        color: #0073aa;
     1347        border-bottom-color: #0073aa; /* Color change for focus style should be acceptable because border-bottom is barely visible previously. */
     1348        outline: none;
     1349        box-shadow: none;
     1350}
     1351
     1352.control-panel-themes .filter-details .filter-group legend button.open:after {
     1353        content: "\f142";
     1354}
     1355
     1356.control-panel-themes .filter-details .filter-group .filter-group-feature {
     1357        display: none;
     1358        margin: 0;
     1359}
     1360
     1361.control-panel-themes .filter-details .filter-group-feature label {
     1362        border: 1px solid #ddd;
     1363        border-top: 0;
     1364        background: #fff;
     1365        color: #555d66;
     1366        margin: 0;
     1367        padding: 12px 10px 12px 34px;
     1368        width: calc(100% - 46px);
     1369        line-height: 16px;
     1370        font-weight: 600;
     1371}
     1372
     1373.control-panel-themes .filter-details .filter-group-feature input {
     1374        position: absolute;
     1375        margin: 12px 10px;
     1376}
     1377
     1378.control-panel-themes .filter-details .filter-group-feature label:hover {
     1379        color: #0073aa;
    10771380}
    10781381
     
    10841387}
    10851388
     1389.loading .customize-themes-section .spinner {
     1390        display: block;
     1391        visibility: visible;
     1392        position: relative;
     1393        clear: both;
     1394        width: 20px;
     1395        height: 20px;
     1396        left: calc(50% - 10px);
     1397        float: none;
     1398        margin-top: 50px;
     1399}
     1400
     1401.customize-themes-section .filter-drawer {
     1402        border-top: none;
     1403        display: block;
     1404        background: transparent;
     1405        padding-top: 5px;
     1406}
     1407
     1408.customize-themes-section .clear-filters {
     1409        margin-left: 8px;
     1410        display: none;
     1411}
     1412
     1413.customize-themes-section .no-themes {
     1414        display: none;
     1415}
     1416
     1417.themes-section-installed_themes .theme .notice-success {
     1418        display: none; /* Hide "installed" notice on installed themes tab. */
     1419}
     1420
     1421.control-panel-themes .theme-browser .theme .theme-actions .button-primary {
     1422        margin: 0 0 0 8px;
     1423}
     1424
     1425.customize-control-theme .theme {
     1426        width: 100%;
     1427        margin: 0;
     1428}
     1429
     1430.customize-control.customize-control-theme { /* override most properties on .customize-control */
     1431        box-sizing: border-box;
     1432        width: 18.4%;
     1433        margin: 0 2% 2% 0;
     1434        padding: 0;
     1435        clear: none;
     1436}
     1437
     1438/* 5 columns above 2100px */
     1439@media screen and (min-width: 2101px) {
     1440        .customize-control.customize-control-theme:nth-child(5n) {
     1441                margin-right: 0;
     1442        }
     1443}
     1444
     1445/* 4 columns up to 2100px */
     1446@media screen and (min-width: 1601px) and (max-width: 2100px) {
     1447        .customize-control.customize-control-theme {
     1448                width: 23.5%;
     1449        }
     1450
     1451        .customize-control.customize-control-theme:nth-child(4n) {
     1452                margin-right: 0;
     1453        }
     1454}
     1455
     1456/* 3 columns up to 1600px */
     1457@media screen and (min-width: 1201px) and (max-width: 1600px) {
     1458        .customize-control.customize-control-theme {
     1459                width: 32%;
     1460        }
     1461
     1462        .customize-control.customize-control-theme:nth-child(3n) {
     1463                margin-right: 0;
     1464        }
     1465}
     1466
     1467/* 2 columns up to 1200px */
     1468@media screen and (min-width: 851px) and (max-width: 1200px) {
     1469        .customize-control.customize-control-theme {
     1470                width: 49%;
     1471        }
     1472
     1473        .customize-control.customize-control-theme:nth-child(even) {
     1474                margin-right: 0;
     1475        }
     1476}
     1477
     1478/* 1 column up to 850 px */
     1479@media screen and (max-width: 850px) {
     1480        .customize-control.customize-control-theme {
     1481                width: 100%;
     1482                margin: 0 0 3% 0;
     1483        }
     1484}
     1485
    10861486.wp-customizer .theme-browser .themes {
    10871487        padding-bottom: 8px;
    10881488}
    10891489
    1090 .wp-customizer .theme-browser .theme {
    1091         margin: 0;
    1092         width: 100%;
    1093 }
    1094 
    10951490.wp-customizer .theme-browser .theme .theme-actions {
    1096         -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
    10971491        opacity: 1;
    10981492}
     
    11111505        line-height: 1.5;
    11121506        width: 100%;
    1113 }
    1114 
    1115 .control-section-themes .accordion-section-title:after,
    1116 .customize-themes-panel .accordion-section-title:after {
    1117         display: none;
    1118 }
    1119 
    1120 .customize-themes-panel.control-panel-content {
    1121         border-top: 1px solid #ddd;
    11221507}
    11231508
     
    11361521}
    11371522
     1523/* Avoid a z-index war by resetting elements that should be under the overlay.
     1524   This is likely required because of the way that sections and panels are positioned. */
     1525.wp-customizer.modal-open #customize-header-actions,
     1526.wp-customizer.modal-open .control-panel-themes .filter-themes-count,
     1527.wp-customizer.modal-open .control-panel-themes .customize-themes-section-title.selected:after {
     1528        z-index: -1;
     1529}
     1530
    11381531.wp-customizer .theme-overlay .theme-backdrop {
    11391532        background: rgba( 238, 238, 238, 0.75 );
    11401533        position: fixed;
    11411534        z-index: 110;
     1535}
     1536
     1537.wp-customizer .theme-overlay .star-rating {
     1538        float: left;
     1539        margin-right: 8px;
     1540}
     1541
     1542.wp-customizer .theme-rating .num-ratings {
     1543        line-height: 20px;
    11421544}
    11431545
     
    11481550        bottom: 45px;
    11491551        z-index: 120;
    1150         max-width: 1740px; /* To ensure that theme screenshots are not displayed larger than 880px wide. */
    11511552}
    11521553
    11531554.wp-customizer .theme-overlay .theme-actions {
    1154         text-align: right; /* Because there's only one action, match the pattern of media modals and right-align the action. */
    1155 }
    1156 
    1157 .ie8 .wp-customizer .theme-overlay .theme-header,
    1158 .ie8 .wp-customizer .theme-overlay .theme-about,
    1159 .ie8 .wp-customizer .theme-overlay .theme-actions {
    1160         position: static;
    1161 }
     1555        text-align: right; /* Because there're only one or two actions, match the UI pattern of media modals and right-align the action. */
     1556        padding: 10px 15px;
     1557}
     1558
     1559.wp-customizer .theme-overlay .theme-actions .theme-install.preview {
     1560        margin-left: 8px;
     1561}
     1562
     1563.control-panel-themes .theme-actions .delete-theme {
     1564        left: 15px; /* these override themes.css on mobile */
     1565        right: auto;
     1566        bottom: auto;
     1567        position: absolute;
     1568}
     1569
     1570.modal-open .in-themes-panel #customize-controls .wp-full-overlay-sidebar-content {
     1571        overflow: visible; /* Prevent the top-level Customizer controls from becoming visible when elements on the right of the details modal are focused. */
     1572}
     1573
    11621574
    11631575/* Small Screens */
  • trunk/src/wp-admin/css/themes.css

    r38795 r38813  
    571571        margin: 0 30px 0 0;
    572572        width: 55%;
    573         max-width: 880px;
     573        max-width: 1200px; /* Recommended theme screenshot width, set here to avoid stretching */
    574574        text-align: center;
    575575}
  • trunk/src/wp-admin/customize.php

    r38810 r38813  
    110110
    111111<script type="text/javascript">
    112 var ajaxurl = <?php echo wp_json_encode( admin_url( 'admin-ajax.php', 'relative' ) ); ?>;
     112var ajaxurl = <?php echo wp_json_encode( admin_url( 'admin-ajax.php', 'relative' ) ); ?>,
     113    pagenow = 'customize';
    113114</script>
    114115
  • trunk/src/wp-admin/includes/theme.php

    r38788 r38813  
    608608 */
    609609function customize_themes_print_templates() {
    610         $preview_url = esc_url( add_query_arg( 'theme', '__THEME__' ) ); // Token because esc_url() strips curly braces.
    611         $preview_url = str_replace( '__THEME__', '{{ data.id }}', $preview_url );
    612610        ?>
    613611        <script type="text/html" id="tmpl-customize-themes-details-view">
     
    621619                        <div class="theme-about wp-clearfix">
    622620                                <div class="theme-screenshots">
    623                                 <# if ( data.screenshot[0] ) { #>
     621                                <# if ( data.screenshot && data.screenshot[0] ) { #>
    624622                                        <div class="screenshot"><img src="{{ data.screenshot[0] }}" alt="" /></div>
    625623                                <# } else { #>
     
    634632                                        <h2 class="theme-name">{{{ data.name }}}<span class="theme-version"><?php printf( __( 'Version: %s' ), '{{ data.version }}' ); ?></span></h2>
    635633                                        <h3 class="theme-author"><?php printf( __( 'By %s' ), '{{{ data.authorAndUri }}}' ); ?></h3>
     634
     635                                        <# if ( data.stars && 0 != data.num_ratings ) { #>
     636                                                <div class="theme-rating">
     637                                                        {{{ data.stars }}}
     638                                                        <span class="num-ratings"><?php echo sprintf( __( '(%s ratings)' ), '{{ data.num_ratings }}' ); ?></span>
     639                                                </div>
     640                                        <# } #>
     641
     642                                        <# if ( data.hasUpdate ) { #>
     643                                                <div class="notice notice-warning notice-alt notice-large" data-slug="{{ data.id }}">
     644                                                        <h3 class="notice-title"><?php _e( 'Update Available' ); ?></h3>
     645                                                        {{{ data.update }}}
     646                                                </div>
     647                                        <# } #>
     648
    636649                                        <p class="theme-description">{{{ data.description }}}</p>
    637650
     
    639652                                                <p class="parent-theme"><?php printf( __( 'This is a child theme of %s.' ), '<strong>{{{ data.parent }}}</strong>' ); ?></p>
    640653                                        <# } #>
    641 
    642654                                        <# if ( data.tags ) { #>
    643                                                 <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{ data.tags }}</p>
     655                                                <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{{ data.tags }}}</p>
    644656                                        <# } #>
    645657                                </div>
    646658                        </div>
    647659
    648                         <# if ( ! data.active ) { #>
    649                                 <div class="theme-actions">
    650                                         <div class="inactive-theme">
    651                                                 <?php
    652                                                 /* translators: %s: Theme name */
    653                                                 $aria_label = sprintf( __( 'Preview %s' ), '{{ data.name }}' );
    654                                                 ?>
    655                                                 <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>
    656                                         </div>
    657                                 </div>
    658                         <# } #>
     660                        <div class="theme-actions">
     661                                <# if ( data.active ) { #>
     662                                        <button type="button" class="button button-primary customize-theme"><?php _e( 'Customize' ); ?></a>
     663                                <# } else if ( 'installed' === data.type ) { #>
     664                                        <?php if ( current_user_can( 'delete_themes' ) ) { ?>
     665                                                <# if ( data.actions && data.actions['delete'] ) { #>
     666                                                        <a href="{{{ data.actions['delete'] }}}" data-slug="{{ data.id }}" class="button button-secondary delete-theme"><?php _e( 'Delete' ); ?></a>
     667                                                <# } #>
     668                                        <?php } ?>
     669                                        <button type="button" class="button button-primary preview-theme" data-slug="{{ data.id }}"><?php _e( 'Live Preview' ); ?></span>
     670                                <# } else { #>
     671                                        <button type="button" class="button theme-install" data-slug="{{ data.id }}"><?php _e( 'Install' ); ?></button>
     672                                        <button type="button" class="button button-primary theme-install preview" data-slug="{{ data.id }}"><?php _e( 'Install & Preview' ); ?></button>
     673                                <# } #>
     674                        </div>
    659675                </div>
    660676        </script>
  • trunk/src/wp-admin/js/customize-controls.js

    r38810 r38813  
    1 /* global _wpCustomizeHeader, _wpCustomizeBackground, _wpMediaViewsL10n, MediaElementPlayer */
     1/* global _wpCustomizeHeader, _wpCustomizeBackground, _wpMediaViewsL10n, MediaElementPlayer, console */
    22(function( exports, $ ){
    33        var Container, focus, normalizedTransitionendEventName, api = wp.customize;
     
    865865                                container = $( '#customize-theme-controls' );
    866866
    867                         // Watch for changes to the panel state
     867                        // Watch for changes to the panel state.
    868868                        inject = function ( panelId ) {
    869869                                var parentContainer;
    870870                                if ( panelId ) {
    871                                         // The panel has been supplied, so wait until the panel object is registered
     871                                        // The panel has been supplied, so wait until the panel object is registered.
    872872                                        api.panel( panelId, function ( panel ) {
    873                                                 // The panel has been registered, wait for it to become ready/initialized
     873                                                // The panel has been registered, wait for it to become ready/initialized.
    874874                                                panel.deferred.embedded.done( function () {
    875875                                                        parentContainer = panel.contentContainer;
     
    896896                        };
    897897                        section.panel.bind( inject );
    898                         inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one
     898                        inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one.
    899899                },
    900900
     
    10401040         * wp.customize.ThemesSection
    10411041         *
    1042          * Custom section for themes that functions similarly to a backwards panel,
    1043          * and also handles the theme-details view rendering and navigation.
     1042         * Custom section for themes that loads themes by category, and also
     1043         * handles the theme-details view rendering and navigation.
    10441044         *
    10451045         * @constructor
     
    10531053                screenshotQueue: null,
    10541054                $window: $( window ),
    1055 
    1056                 /**
    1057                  * @since 4.2.0
    1058                  */
    1059                 initialize: function () {
    1060                         this.$customizeSidebar = $( '.wp-full-overlay-sidebar-content:first' );
    1061                         return api.Section.prototype.initialize.apply( this, arguments );
     1055                loaded: 0,
     1056                loading: false,
     1057                fullyLoaded: false,
     1058                term: '',
     1059                filterContainer: $(),
     1060
     1061                /**
     1062                 * Embed the section in the DOM when the themes panel is ready.
     1063                 *
     1064                 * Insert the section before the themes container. Assume that a themes section is within a panel, but not necessarily the themes panel.
     1065                 *
     1066                 * @since 4.7.0
     1067                 */
     1068                embed: function () {
     1069                        var inject,
     1070                                section = this,
     1071                                container = $( '#customize-theme-controls' );
     1072
     1073                        // Watch for changes to the panel state
     1074                        inject = function ( panelId ) {
     1075                                var parentContainer;
     1076                                api.panel( panelId, function ( panel ) {
     1077                                        // The panel has been registered, wait for it to become ready/initialized
     1078                                        panel.deferred.embedded.done( function () {
     1079                                                parentContainer = panel.contentContainer;
     1080                                                if ( ! section.headContainer.parent().is( parentContainer ) ) {
     1081                                                        parentContainer.find( '.customize-themes-full-container-container' ).before( section.headContainer );
     1082                                                }
     1083                                                if ( ! section.contentContainer.parent().is( section.headContainer ) ) {
     1084                                                        container.append( section.contentContainer );
     1085                                                }
     1086                                                section.deferred.embedded.resolve();
     1087                                        });
     1088                                } );
     1089                        };
     1090                        section.panel.bind( inject );
     1091                        inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one
    10621092                },
    10631093
     
    10921122                        });
    10931123
    1094                         _.bindAll( this, 'renderScreenshots' );
     1124                        _.bindAll( this, 'renderScreenshots', 'loadMore', 'checkTerm', 'filtersChecked' );
    10951125                },
    10961126
     
    10991129                 *
    11001130                 * Ignore the active states' of the contained theme controls, and just
    1101                  * use the section's own active state instead. This ensures empty search
    1102                  * results for themes to cause the section to become inactive.
     1131                 * use the section's own active state instead. This prevents empty search
     1132                 * results for theme sections from causing the section to become inactive.
    11031133                 *
    11041134                 * @since 4.2.0
     
    11161146                        var section = this;
    11171147
    1118                         // Expand/Collapse section/panel.
    1119                         section.container.find( '.change-theme, .customize-theme' ).on( 'click keydown', function( event ) {
    1120                                 if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    1121                                         return;
    1122                                 }
    1123                                 event.preventDefault(); // Keep this AFTER the key filter above
    1124 
    1125                                 if ( section.expanded() ) {
    1126                                         section.collapse();
    1127                                 } else {
     1148                        section.filterContainer = $( '#accordion-section-' + section.id );
     1149
     1150                        // Expand section/panel. Only collapse when opening another section.
     1151                        section.filterContainer.on( 'click', '.customize-themes-section-title', function() {
     1152                                // Open the section.
     1153                                if ( ! section.expanded() ) {
    11281154                                        section.expand();
    11291155                                }
     1156
     1157                                // Toggle filters.
     1158                                if ( section.filterContainer.find( '.filter-details' ).length ) {
     1159                                        section.filterContainer.find( '.customize-themes-section-title' )
     1160                                                .toggleClass( 'details-open' )
     1161                                                .attr('aria-expanded', function ( i, attr ) {
     1162                                                        return attr === 'true' ? 'false' : 'true';
     1163                                                });
     1164                                        section.filterContainer.find( '.filter-details' ).slideToggle( 180 );
     1165                                }
     1166                        });
     1167
     1168                        // Preview installed themes.
     1169                        section.container.on( 'click', '.theme-actions .preview-theme', function() {
     1170                                var themeId = $( this ).data( 'themeId' );
     1171
     1172                                $( '.wp-full-overlay' ).addClass( 'customize-loading' );
     1173                                api.panel( 'themes' ).loadThemePreview( themeId ).fail( function() {
     1174                                        $( '.wp-full-overlay' ).removeClass( 'customize-loading' );
     1175                                } );
    11301176                        });
    11311177
    11321178                        // Theme navigation in details view.
    1133                         section.container.on( 'click keydown', '.left', function( event ) {
    1134                                 if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    1135                                         return;
    1136                                 }
    1137 
    1138                                 event.preventDefault(); // Keep this AFTER the key filter above
    1139 
     1179                        section.container.on( 'click', '.left', function() {
    11401180                                section.previousTheme();
    11411181                        });
    11421182
    1143                         section.container.on( 'click keydown', '.right', function( event ) {
    1144                                 if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    1145                                         return;
    1146                                 }
    1147 
    1148                                 event.preventDefault(); // Keep this AFTER the key filter above
    1149 
     1183                        section.container.on( 'click', '.right', function() {
    11501184                                section.nextTheme();
    11511185                        });
    11521186
    1153                         section.container.on( 'click keydown', '.theme-backdrop, .close', function( event ) {
    1154                                 if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    1155                                         return;
    1156                                 }
    1157 
    1158                                 event.preventDefault(); // Keep this AFTER the key filter above
    1159 
     1187                        section.container.on( 'click', '.theme-backdrop, .close', function() {
    11601188                                section.closeDetails();
    11611189                        });
    11621190
    11631191                        var renderScreenshots = _.throttle( _.bind( section.renderScreenshots, this ), 100 );
    1164                         section.container.on( 'input', '#themes-filter', function( event ) {
     1192
     1193                        // Only used when there is only one section - installed themes.
     1194                        $( '.control-panel-themes' ).on( 'input', '#themes-filter', function( event ) {
    11651195                                var count,
    11661196                                        term = event.currentTarget.value.toLowerCase().trim().replace( '-', ' ' ),
     
    11741204
    11751205                                // Update theme count.
    1176                                 count = section.container.find( 'li.customize-control:visible' ).length;
    1177                                 section.container.find( '.theme-count' ).text( count );
    1178                         });
    1179 
    1180                         // Pre-load the first 3 theme screenshots.
    1181                         api.bind( 'ready', function () {
    1182                                 _.each( section.controls().slice( 0, 3 ), function ( control ) {
    1183                                         var img, src = control.params.theme.screenshot[0];
    1184                                         if ( src ) {
    1185                                                 img = new Image();
    1186                                                 img.src = src;
     1206                                count = section.contentContainer.find( 'li.customize-control:visible' ).length;
     1207                                $( '.control-panel-themes' ).find( '.theme-count' ).text( count );
     1208                        });
     1209
     1210                        // Event listeners for queries with user-entered terms.
     1211                        if ( 'search' === section.params.action ) {
     1212                                var debounced = _.debounce( section.checkTerm, 500 ); // Wait until there is no input for 500 milliseconds to initiate a search.
     1213                                $( '.control-panel-themes' ).on( 'input', '#wp-filter-search-input', function() {
     1214                                        debounced( section );
     1215                                        if ( ! section.expanded() ) {
     1216                                                section.expand();
    11871217                                        }
    11881218                                });
     1219
     1220                                // Focus the input if the icon is clicked.
     1221                                section.filterContainer.find( '.search-form' ).on( 'click', function( e ) {
     1222                                        if ( ! $( e.currentTarget ).hasClass( 'wp-filter-search' ) ) {
     1223                                                $( e.currentTarget ).find( '.wp-filter-search' ).focus();
     1224                                        }
     1225                                });
     1226                        } else if ( 'favorites' === section.params.action ) {
     1227                                section.container.on( 'click', '.favorites-form-submit', function() {
     1228                                        section.checkTerm( section );
     1229                                });
     1230                                section.container.on( 'keydown', '#wporg-username-input', function( e ) {
     1231                                        if ( api.utils.isKeydownButNotEnterEvent( e ) ) {
     1232                                                return;
     1233                                        }
     1234                                        section.checkTerm( section );
     1235                                });
     1236                        } else if ( 'feature_filter' === section.params.action ) {
     1237                                section.container.on( 'click', '.filter-group input', function() {
     1238                                        section.filtersChecked();
     1239                                        section.checkTerm( section );
     1240                                });
     1241
     1242                                // Toggle feature filter sections.
     1243                                section.container.on( 'click', '.filter-group legend button', function( e ) {
     1244                                        $( e.currentTarget )
     1245                                                .toggleClass( 'open' )
     1246                                                .attr('aria-expanded', function ( i, attr ) {
     1247                                                        return attr === 'true' ? 'false' : 'true';
     1248                                                })
     1249                                                .parent().next( '.filter-group-feature' ).slideToggle( 180 );
     1250                                });
     1251                        }
     1252
     1253                        // Move section controls to the themes area.
     1254                        api.bind( 'ready', function () {
     1255                                section.contentContainer = section.container.find( '.customize-themes-section' );
     1256                                section.contentContainer.appendTo( $( '.customize-themes-full-container' ) );
     1257                                section.container.add( section.filterContainer );
    11891258                        });
    11901259                },
     
    11981267                 * @param {Object}   args
    11991268                 * @param {Boolean}  args.unchanged
    1200                  * @param {Callback} args.completeCallback
     1269                 * @param {Function} args.completeCallback
    12011270                 */
    12021271                onChangeExpanded: function ( expanded, args ) {
     
    12111280
    12121281                        // Note: there is a second argument 'args' passed
    1213                         var panel = this,
    1214                                 section = panel.contentContainer,
    1215                                 overlay = section.closest( '.wp-full-overlay' ),
    1216                                 container = section.closest( '.wp-full-overlay-sidebar-content' ),
    1217                                 customizeBtn = section.find( '.customize-theme' ),
    1218                                 changeBtn = panel.headContainer.find( '.change-theme' );
    1219 
    1220                         if ( expanded && ! section.hasClass( 'current-panel' ) ) {
     1282                        var section = this,
     1283                                container = section.contentContainer.closest( '.customize-themes-full-container' );
     1284
     1285                        if ( expanded ) {
     1286
     1287                                if ( -1 !== $.inArray( section.params.action, [ 'search', 'favorites', 'feature_filter' ] ) && '' === section.term ) {
     1288                                        section.collapse(); // Note that the current section hasn't been collapsed yet, so this is all we need to do to do nothing.
     1289                                        return; // Don't expand to an empty section that can't load any themes.
     1290                                }
     1291
     1292                                // Load controls if none are loaded yet.
     1293                                if ( 0 === section.loaded ) {
     1294                                        section.loadControls();
     1295                                }
     1296
    12211297                                // Collapse any sibling sections/panels
    12221298                                api.section.each( function ( otherSection ) {
    1223                                         if ( otherSection !== panel ) {
     1299                                        if ( otherSection !== section ) {
    12241300                                                otherSection.collapse( { duration: args.duration } );
    12251301                                        }
    12261302                                });
    1227                                 api.panel.each( function ( otherPanel ) {
    1228                                         otherPanel.collapse( { duration: 0 } );
    1229                                 });
    1230 
    1231                                 panel._animateChangeExpanded( function() {
    1232                                         changeBtn.attr( 'tabindex', '-1' );
    1233                                         customizeBtn.attr( 'tabindex', '0' );
    1234 
    1235                                         customizeBtn.focus();
    1236                                         section.css( 'top', '' );
    1237                                         container.scrollTop( 0 );
    1238 
    1239                                         if ( args.completeCallback ) {
    1240                                                 args.completeCallback();
     1303
     1304                                section.contentContainer.addClass( 'current-section' );
     1305                                container.scrollTop();
     1306                                section.filterContainer.find( '.customize-themes-section-title' ).addClass( 'selected' );
     1307
     1308                                container.on( 'scroll', _.throttle( section.renderScreenshots, 300 ) );
     1309                                container.on( 'scroll', _.throttle( section.loadMore, 300 ) );
     1310
     1311                                if ( args.completeCallback ) {
     1312                                        args.completeCallback();
     1313                                }
     1314                                section.updateCount(); // Show this section's count.
     1315                        } else {
     1316                                section.contentContainer.removeClass( 'current-section' );
     1317
     1318                                // Always hide, even if they don't exist or are already hidden.
     1319                                section.filterContainer.find( '.customize-themes-section-title' ).removeClass( 'selected details-open' ).attr( 'aria-expanded', 'false' );
     1320                                section.filterContainer.find( '.filter-details' ).slideUp( 180 );
     1321
     1322                                container.off( 'scroll' );
     1323
     1324                                if ( args.completeCallback ) {
     1325                                        args.completeCallback();
     1326                                }
     1327                        }
     1328                },
     1329
     1330                /**
     1331                 * Return the section's content element without detachng from the parent.
     1332                 *
     1333                 * @since 4.7.0
     1334                 */
     1335                getContent: function() {
     1336                        return this.container.find( '.control-section-content' );
     1337                },
     1338
     1339                /**
     1340                 * Load theme data via ajax and add themes to the section as controls.
     1341                 *
     1342                 * @since 4.7.0
     1343                 */
     1344                loadControls: function() {
     1345                        var section = this, params, page, request;
     1346
     1347                        if ( section.loading ) {
     1348                                return; // We're already loading a batch of themes.
     1349                        }
     1350
     1351                        // Parameters for every API query. Additional params are set in PHP.
     1352                        page = Math.ceil( section.loaded / 100 ) + 1;
     1353                        params = {
     1354                                'switch-themes-nonce': api.settings.nonce['switch-themes'],
     1355                                'wp_customize': 'on',
     1356                                'theme_action': section.params.action,
     1357                                'customized_theme': api.settings.theme.stylesheet,
     1358                                'page': page
     1359                        };
     1360
     1361                        // Add fields for special request actions.
     1362                        if ( 'search' === section.params.action ) {
     1363                                if ( '' === section.term ) {
     1364                                        return;
     1365                                } else {
     1366                                        params.search = section.term;
     1367                                }
     1368                        } else if ( 'favorites' === section.params.action ) {
     1369                                if ( '' === section.term ) {
     1370                                        return;
     1371                                } else {
     1372                                        params.user = section.term;
     1373                                }
     1374                        } else if ( 'feature_filter' === section.params.action ) {
     1375                                if ( '' === section.term ) {
     1376                                        return;
     1377                                } else {
     1378                                        params.tags = section.term;
     1379                                }
     1380                        }
     1381
     1382                        // Load themes.
     1383                        section.headContainer.closest( '.wp-full-overlay' ).addClass( 'loading' );
     1384                        section.loading = true;
     1385                        section.container.find( '.no-themes' ).hide();
     1386                        request = wp.ajax.post( 'customize-load-themes', params );
     1387                        request.done(function( data ) {
     1388                                var themes = data.themes,
     1389                                    themeControl, newThemeControls;
     1390                                if ( 0 !== themes.length ) {
     1391                                        newThemeControls = [];
     1392                                        // Add controls for each theme.
     1393                                        _.each( themes, function ( theme ) {
     1394                                                var customizeId = section.params.action + '_theme_' + theme.id;
     1395                                                themeControl = new api.controlConstructor.theme( customizeId, {
     1396                                                        params: {
     1397                                                                type: 'theme',
     1398                                                                content: '<li id="customize-control-theme-' + section.params.action + '_' + theme.id + '" class="customize-control customize-control-theme"></li>',
     1399                                                                section: section.params.id,
     1400                                                                active: true,
     1401                                                                theme: theme,
     1402                                                                priority: section.loaded + 1
     1403                                                        },
     1404                                                        previewer: api.previewer
     1405                                                } );
     1406
     1407                                                api.control.add( customizeId, themeControl );
     1408                                                newThemeControls.push( themeControl );
     1409                                                section.loaded = section.loaded + 1;
     1410                                        });
     1411
     1412                                        if ( 1 === page ) {
     1413                                                // Pre-load the first 3 theme screenshots.
     1414                                                _.each( section.controls().slice( 0, 3 ), function ( control ) {
     1415                                                        var img, src = control.params.theme.screenshot[0];
     1416                                                        if ( src ) {
     1417                                                                img = new Image();
     1418                                                                img.src = src;
     1419                                                        }
     1420                                                });
     1421                                                if ( 'search' === section.params.action ) {
     1422                                                        wp.a11y.speak( api.settings.l10n.themeSearchResults.replace( '%d', data.info.results ) );
     1423                                                }
     1424                                        } else {
     1425                                                Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue.
    12411426                                        }
    1242                                 } );
    1243 
    1244                                 overlay.addClass( 'in-themes-panel' );
    1245                                 section.addClass( 'current-panel' );
    1246 
    1247                         } else if ( ! expanded && section.hasClass( 'current-panel' ) ) {
    1248                                 panel._animateChangeExpanded( function() {
    1249                                         changeBtn.attr( 'tabindex', '0' );
    1250                                         customizeBtn.attr( 'tabindex', '-1' );
    1251 
    1252                                         changeBtn.focus();
    1253                                         section.css( 'top', '' );
    1254 
    1255                                         if ( args.completeCallback ) {
    1256                                                 args.completeCallback();
     1427                                        _.delay( section.renderScreenshots, 100 ); // Wait for the controls to become visible.
     1428
     1429                                        if ( 'installed' === section.action || 100 > themes.length ) { // If we have less than the requested 100 themes, it's the end of the list.
     1430                                                section.fullyLoaded = true;
    12571431                                        }
    1258                                 } );
    1259 
    1260                                 overlay.removeClass( 'in-themes-panel' );
    1261                                 section.removeClass( 'current-panel' );
     1432                                } else {
     1433                                        if ( 0 === section.loaded ) {
     1434                                                section.container.find( '.no-themes' ).show();
     1435                                                wp.a11y.speak( section.container.find( '.no-themes' ).text() );
     1436                                        } else {
     1437                                                section.fullyLoaded = true;
     1438                                        }
     1439                                }
     1440                                if ( 'installed' === section.params.action ) {
     1441                                        section.updateCount();
     1442                                } else {
     1443                                        section.updateCount( data.info.results );
     1444                                }
     1445                                section.container.find( '.unexpected-error' ).hide(); // Hide error notice in case it was previously shown.
     1446
     1447                                // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case.
     1448                                section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' );
     1449                                section.loading = false;
     1450                        });
     1451                        request.fail(function( data ) {
     1452                                if ( 'undefined' === typeof data ) {
     1453                                        section.container.find( '.unexpected-error' ).show();
     1454                                        wp.a11y.speak( section.container.find( '.unexpected-error' ).text() );
     1455                                } else if ( typeof console !== 'undefined' && console.error ) {
     1456                                        console.error( data );
     1457                                }
     1458
     1459                                // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case.
     1460                                section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' );
     1461                                section.loading = false;
     1462                        });
     1463                },
     1464
     1465                /**
     1466                 * Determines whether more themes should be loaded, and loads them.
     1467                 *
     1468                 * @since 4.7.0
     1469                 */
     1470                loadMore: function() {
     1471                        var section = this, container, bottom, threshold;
     1472                        if ( ! section.fullyLoaded && ! section.loading ) {
     1473                                container = section.container.closest( '.customize-themes-full-container' );
     1474
     1475                                bottom = container.scrollTop() + container.height();
     1476                                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.
     1477
     1478                                if ( bottom > threshold ) {
     1479                                        section.loadControls();
     1480                                }
     1481                        }
     1482                },
     1483
     1484                /**
     1485                 * Event handler for search, feature filter, and favorites input that determines if the term has changed and loads new controls as needed.
     1486                 *
     1487                 * @since 4.7.0
     1488                 *
     1489                 * @param {wp.customize.ThemesSection} section The current theme section, passed through the debouncer.
     1490                 */
     1491                checkTerm: function( section ) {
     1492                        var newTerm;
     1493
     1494                        // Find term.
     1495                        if ( 'search' === section.params.action ) {
     1496                                newTerm = $( '#wp-filter-search-input' ).val();
     1497                        } else if ( 'favorites' === section.params.action ) {
     1498                                newTerm = $( '#wporg-username-input' ).val();
     1499                        } else if ( 'feature_filter' === section.params.action ) {
     1500                                newTerm = section.term; // Set separately by filtersChecked(), as they're changed.
     1501                                if ( '' === newTerm ) {
     1502                                        return;
     1503                                }
     1504                        } else {
     1505                                return;
     1506                        }
     1507
     1508                        if ( section.term === newTerm && 'feature_filter' !== section.params.action ) {
     1509                                return;
     1510                        }
     1511
     1512                        // Clear the controls in the section.
     1513                        _.each( section.controls(), function( control ) {
     1514                                control.container.remove();
     1515                                api.control.remove( control.id );
     1516                        });
     1517                        section.loaded = 0;
     1518                        section.fullyLoaded = false;
     1519                        section.screenshotQueue = null;
     1520
     1521                        if ( '' !== newTerm ) { // Empty term should not show any results.
     1522                                // Run a new query, with loadControls handling paging, etc.
     1523                                section.term = newTerm;
     1524                                section.loadControls();
     1525                                if ( ! section.expanded() ) {
     1526                                        section.expand(); // Expand the section if it isn't expanded.
     1527                                }
     1528                        }
     1529                },
     1530
     1531                /**
     1532                 * Check for filters checked in the feature filter list.
     1533                 *
     1534                 * @since 4.7.0
     1535                 */
     1536                filtersChecked: function() {
     1537                        var section = this,
     1538                            items = section.container.find( '.filter-group' ).find( ':checkbox' ),
     1539                            tags = [];
     1540
     1541                        if ( 'feature_filter' !== section.params.action ) {
     1542                                return false;
     1543                        }
     1544
     1545                        _.each( items.filter( ':checked' ), function( item ) {
     1546                                tags.push( $( item ).prop( 'value' ) );
     1547                        });
     1548
     1549                        // When no filters are checked, restore initial state and return
     1550                        if ( tags.length === 0 ) {
     1551                                section.term = '';
     1552                        } else {
     1553                                section.term = tags;
    12621554                        }
    12631555                },
     
    12711563                        var section = this;
    12721564
    1273                         // Fill queue initially.
    1274                         if ( section.screenshotQueue === null ) {
    1275                                 section.screenshotQueue = section.controls();
    1276                         }
    1277 
    1278                         // Are all screenshots rendered?
     1565                        // Fill queue initially, or check for more if empty.
     1566                        if ( section.screenshotQueue === null || 0 === section.screenshotQueue.length ) {
     1567                                // Add controls that haven't had their screenshots rendered.
     1568                                section.screenshotQueue = _.filter( section.controls(), function( control ) {
     1569                                        return ! control.screenshotRendered;
     1570                                });
     1571                        }
     1572
     1573                        // Are all screenshots rendered (for now)?
    12791574                        if ( ! section.screenshotQueue.length ) {
    12801575                                return;
     
    13121607
    13131608                /**
     1609                 * Update the number of themes in the section.
     1610                 *
     1611                 * @since 4.7.0
     1612                 */
     1613                updateCount: function ( count ) {
     1614                        if ( ! count ) {
     1615                                count = this.loaded;
     1616                        }
     1617
     1618                        var displayed = this.container.closest( '.control-panel-content' ).find( '.themes-displayed' ),
     1619                            countEl = this.container.closest( '.control-panel-content' ).find( '.theme-count' );
     1620
     1621                        if ( 0 === count ) {
     1622                                countEl.text( count );
     1623                        } else {
     1624                                // Animate the count change for emphasis.
     1625                                displayed.fadeOut( 180, function() {
     1626                                        countEl.text( count );
     1627                                        displayed.fadeIn( 180 );
     1628                                } );
     1629                                wp.a11y.speak( api.settings.l10n.announceThemeCount.replace( '%d', count ) );
     1630                        }
     1631                },
     1632
     1633                /**
    13141634                 * Advance the modal to the next theme.
    13151635                 *
     
    13311651                 */
    13321652                getNextTheme: function () {
    1333                         var control, next;
    1334                         control = api.control( 'theme_' + this.currentTheme );
     1653                        var section = this, control, next;
     1654                        control = api.control( section.params.action + '_theme_' + this.currentTheme );
    13351655                        next = control.container.next( 'li.customize-control-theme' );
    13361656                        if ( ! next.length ) {
    13371657                                return false;
    13381658                        }
    1339                         next = next[0].id.replace( 'customize-control-', '' );
     1659                        next = next[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' );
    13401660                        control = api.control( next );
    13411661
     
    13631683                 */
    13641684                getPreviousTheme: function () {
    1365                         var control, previous;
    1366                         control = api.control( 'theme_' + this.currentTheme );
     1685                        var section = this, control, previous;
     1686                        control = api.control( section.params.action + '_theme_' + this.currentTheme );
    13671687                        previous = control.container.prev( 'li.customize-control-theme' );
    13681688                        if ( ! previous.length ) {
    13691689                                return false;
    13701690                        }
    1371                         previous = previous[0].id.replace( 'customize-control-', '' );
     1691                        previous = previous[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' );
    13721692                        control = api.control( previous );
    13731693
     
    13871707                                this.overlay.find( '.left' ).addClass( 'disabled' );
    13881708                        }
    1389                 },
    1390 
    1391                 /**
    1392                  * Load theme preview.
    1393                  *
    1394                  * @since 4.7.0
    1395                  *
    1396                  * @param {string} themeId Theme ID.
    1397                  * @returns {jQuery.promise} Promise.
    1398                  */
    1399                 loadThemePreview: function( themeId ) {
    1400                         var deferred = $.Deferred(), onceProcessingComplete, overlay, urlParser;
    1401 
    1402                         urlParser = document.createElement( 'a' );
    1403                         urlParser.href = location.href;
    1404                         urlParser.search = $.param( _.extend(
    1405                                 api.utils.parseQueryString( urlParser.search.substr( 1 ) ),
    1406                                 {
    1407                                         theme: themeId,
    1408                                         changeset_uuid: api.settings.changeset.uuid
    1409                                 }
    1410                         ) );
    1411 
    1412                         overlay = $( '.wp-full-overlay' );
    1413                         overlay.addClass( 'customize-loading' );
    1414 
    1415                         onceProcessingComplete = function() {
    1416                                 var request;
    1417                                 if ( api.state( 'processing' ).get() > 0 ) {
    1418                                         return;
    1419                                 }
    1420 
    1421                                 api.state( 'processing' ).unbind( onceProcessingComplete );
    1422 
    1423                                 request = api.requestChangesetUpdate();
    1424                                 request.done( function() {
    1425                                         $( window ).off( 'beforeunload.customize-confirm' );
    1426                                         window.location.href = urlParser.href;
    1427                                 } );
    1428                                 request.fail( function() {
    1429                                         overlay.removeClass( 'customize-loading' );
    1430                                 } );
    1431                         };
    1432 
    1433                         if ( 0 === api.state( 'processing' ).get() ) {
    1434                                 onceProcessingComplete();
    1435                         } else {
    1436                                 api.state( 'processing' ).bind( onceProcessingComplete );
    1437                         }
    1438 
    1439                         return deferred.promise();
    14401709                },
    14411710
     
    14571726                        section.containFocus( section.overlay );
    14581727                        section.updateLimits();
     1728                        wp.a11y.speak( api.settings.l10n.announceThemeDetails.replace( '%s', theme.name ) );
    14591729
    14601730                        link = section.overlay.find( '.inactive-theme > a' );
    1461 
    14621731                        link.on( 'click', function( event ) {
    14631732                                event.preventDefault();
     
    14691738                                link.addClass( 'disabled' );
    14701739
    1471                                 section.loadThemePreview( theme.id ).fail( function() {
     1740                                api.panel( 'themes' ).loadThemePreview( theme.id ).fail( function() {
    14721741                                        link.removeClass( 'disabled' );
    14731742                                } );
     
    14841753                        $( 'body' ).removeClass( 'modal-open' );
    14851754                        this.overlay.fadeOut( 'fast' );
    1486                         api.control( 'theme_' + this.currentTheme ).focus();
     1755                        api.control( this.params.action + '_theme_' + this.currentTheme ).container.find( '.theme' ).focus();
    14871756                },
    14881757
     
    15641833                        if ( ! panel.contentContainer.parent().is( panel.headContainer ) ) {
    15651834                                container.append( panel.contentContainer );
    1566                                 panel.renderContent();
    1567                         }
     1835                        }
     1836                        panel.renderContent();
    15681837
    15691838                        panel.deferred.embedded.resolve();
     
    17532022                }
    17542023        });
     2024
     2025
     2026        /**
     2027         * wp.customize.ThemesPanel
     2028         *
     2029         * Custom section for themes that displays without the customize preview.
     2030         *
     2031         * @constructor
     2032         * @augments wp.customize.Panel
     2033         * @augments wp.customize.Container
     2034         */
     2035        api.ThemesPanel = api.Panel.extend({
     2036                installingThemes: [],
     2037
     2038                /**
     2039                 * @since 4.7.0
     2040                 */
     2041                attachEvents: function () {
     2042                        var panel = this;
     2043
     2044                        // Attach regular panel events.
     2045                        api.Panel.prototype.attachEvents.apply( this );
     2046
     2047                        // Collapse panel to customize the current theme.
     2048                        panel.contentContainer.on( 'click', '.customize-theme', function() {
     2049                                panel.collapse();
     2050                        });
     2051
     2052                        // Toggle between filtering and browsing themes on mobile.
     2053                        panel.contentContainer.on( 'click', '.see-themes, .filter-themes', function() {
     2054                                $( '.wp-full-overlay' ).toggleClass( 'showing-themes' );
     2055                        });
     2056
     2057                        // Install (and maybe preview) a theme.
     2058                        panel.contentContainer.on( 'click', '.theme-install', function( event ) {
     2059                                panel.installTheme( event );
     2060                        });
     2061
     2062                        // Update a theme. Theme cards have the class, the details modal has the id.
     2063                        panel.contentContainer.on( 'click', '.update-theme, #update-theme', function( event ) {
     2064                                // #update-theme is a link.
     2065                                event.preventDefault();
     2066                                event.stopPropagation();
     2067
     2068                                panel.updateTheme( event );
     2069                        });
     2070
     2071                        // Delete a theme.
     2072                        panel.contentContainer.on( 'click', '.delete-theme', function( event ) {
     2073                                panel.deleteTheme( event );
     2074                        });
     2075
     2076                        _.bindAll( this, 'installTheme', 'updateTheme' );
     2077                },
     2078
     2079                /**
     2080                 * Update UI to reflect expanded state
     2081                 *
     2082                 * @since 4.7.0
     2083                 *
     2084                 * @param {Boolean}  expanded
     2085                 * @param {Object}   args
     2086                 * @param {Boolean}  args.unchanged
     2087                 * @param {Function} args.completeCallback
     2088                 */
     2089                onChangeExpanded: function ( expanded, args ) {
     2090
     2091                        // Expand/collapse the panel normally.
     2092                        api.Panel.prototype.onChangeExpanded.apply( this, [ expanded, args ] );
     2093
     2094                        // Immediately call the complete callback if there were no changes
     2095                        if ( args.unchanged ) {
     2096                                if ( args.completeCallback ) {
     2097                                        args.completeCallback();
     2098                                }
     2099                                return;
     2100                        }
     2101
     2102                        // Note: there is a second argument 'args' passed
     2103                        var panel = this,
     2104                                overlay = panel.headContainer.closest( '.wp-full-overlay' );
     2105
     2106                        if ( expanded ) {
     2107                                overlay
     2108                                        .addClass( 'in-themes-panel' ).addClass( 'showing-themes' )
     2109                                        .delay( 200 ).find( '.customize-themes-full-container' ).addClass( 'animate' );
     2110
     2111                                // Automatically open the installed themes section.
     2112                                api.section( 'installed_themes' ).expand();
     2113                        } else {
     2114                                overlay
     2115                                        .removeClass( 'in-themes-panel' )
     2116                                        .find( '.customize-themes-full-container' ).removeClass( 'animate' );
     2117                        }
     2118                },
     2119
     2120                /**
     2121                 * Install a theme via wp.updates.
     2122                 *
     2123                 * @since 4.7.0
     2124                 */
     2125                installTheme: function( event ) {
     2126                        var panel = this, preview = false, slug = $( event.target ).data( 'slug' );
     2127
     2128                        if ( -1 !== $.inArray( this.installingThemes, slug ) ) {
     2129                                return; // Theme is already being installed.
     2130                        }
     2131
     2132                        wp.updates.maybeRequestFilesystemCredentials( event );
     2133
     2134                        $( document ).one( 'wp-theme-install-success', function( event, response ) {
     2135                                var theme = false, customizeId, themeControl;
     2136                                if ( preview ) {
     2137
     2138                                        panel.loadThemePreview( slug ).fail( function() {
     2139                                                $( '.wp-full-overlay' ).removeClass( 'customize-loading' );
     2140                                        } );
     2141
     2142                                } else {
     2143                                        api.control.each( function( control ) {
     2144                                                if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) {
     2145                                                        theme = control.params.theme; // Used below to add theme control.
     2146                                                        control.rerenderAsInstalled( true );
     2147                                                }
     2148                                        });
     2149
     2150                                        // Don't add the same theme more than once.
     2151                                        if ( ! theme || 'undefined' !== typeof api.control( 'installed_theme_' + theme.id ) ) {
     2152                                                return;
     2153                                        }
     2154
     2155                                        // Add theme control to installed section.
     2156                                        theme.type = 'installed';
     2157                                        customizeId = 'installed_theme_' + theme.id;
     2158                                        themeControl = new api.controlConstructor.theme( customizeId, {
     2159                                                params: {
     2160                                                        type: 'theme',
     2161                                                        content: $( '<li class="customize-control customize-control-theme"></li>' ).attr( 'id', 'customize-control-theme-installed_' + theme.id ).prop( 'outerHTML' ),
     2162                                                        section: 'installed_themes',
     2163                                                        active: true,
     2164                                                        theme: theme,
     2165                                                        priority: 0 // Add all newly-installed themes to the top.
     2166                                                },
     2167                                                previewer: api.previewer
     2168                                        } );
     2169
     2170                                        api.control.add( customizeId, themeControl );
     2171                                        api.control( customizeId ).container.trigger( 'render-screenshot' );
     2172
     2173                                        // Close the details modal if it's open to the installed theme.
     2174                                        api.section.each( function( section ) {
     2175                                                if ( 'themes' === section.params.type ) {
     2176                                                        if ( theme.id === section.currentTheme ) { // Don't close the modal if the user has navigated elsewhere.
     2177                                                                section.closeDetails();
     2178                                                        }
     2179                                                }
     2180                                        });
     2181                                }
     2182                        } );
     2183
     2184                        this.installingThemes.push( $( event.target ).data( 'slug' ) ); // Note: we don't remove elements from installingThemes, since they shouldn't be installed again.
     2185                        wp.updates.installTheme( {
     2186                                slug: slug
     2187                        } );
     2188
     2189                        // Also preview the theme as the event is triggered on Install & Preview.
     2190                        if ( $( event.target ).hasClass( 'preview' ) ) {
     2191                                preview = true;
     2192                                $( '.wp-full-overlay' ).addClass( 'customize-loading' );
     2193                        }
     2194                },
     2195
     2196                /**
     2197                 * Load theme preview.
     2198                 *
     2199                 * @since 4.7.0
     2200                 *
     2201                 * @param {string} themeId Theme ID.
     2202                 * @returns {jQuery.promise} Promise.
     2203                 */
     2204                loadThemePreview: function( themeId ) {
     2205                        var deferred = $.Deferred(), onceProcessingComplete, overlay, urlParser;
     2206
     2207                        urlParser = document.createElement( 'a' );
     2208                        urlParser.href = location.href;
     2209                        urlParser.search = $.param( _.extend(
     2210                                api.utils.parseQueryString( urlParser.search.substr( 1 ) ),
     2211                                {
     2212                                        theme: themeId,
     2213                                        changeset_uuid: api.settings.changeset.uuid
     2214                                }
     2215                        ) );
     2216
     2217                        overlay = $( '.wp-full-overlay' );
     2218                        overlay.addClass( 'customize-loading' );
     2219
     2220                        onceProcessingComplete = function() {
     2221                                var request;
     2222                                if ( api.state( 'processing' ).get() > 0 ) {
     2223                                        return;
     2224                                }
     2225
     2226                                api.state( 'processing' ).unbind( onceProcessingComplete );
     2227
     2228                                request = api.requestChangesetUpdate();
     2229                                request.done( function() {
     2230                                        $( window ).off( 'beforeunload.customize-confirm' );
     2231                                        window.location.href = urlParser.href;
     2232                                } );
     2233                                request.fail( function() {
     2234                                        overlay.removeClass( 'customize-loading' );
     2235                                } );
     2236                        };
     2237
     2238                        if ( 0 === api.state( 'processing' ).get() ) {
     2239                                onceProcessingComplete();
     2240                        } else {
     2241                                api.state( 'processing' ).bind( onceProcessingComplete );
     2242                        }
     2243
     2244                        return deferred.promise();
     2245                },
     2246
     2247                /**
     2248                 * Update a theme via wp.updates.
     2249                 *
     2250                 * @since 4.7.0
     2251                 */
     2252                updateTheme: function( event ) {
     2253                        wp.updates.maybeRequestFilesystemCredentials( event );
     2254
     2255                        $( document ).one( 'wp-theme-update-success', function( event, response ) {
     2256                                // Rerender the control to reflect the update.
     2257                                api.control.each( function( control ) {
     2258                                        if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) {
     2259                                                control.params.theme.hasUpdate = false;
     2260                                                control.rerenderAsInstalled( true );
     2261                                        }
     2262                                });
     2263                        } );
     2264
     2265                        wp.updates.updateTheme( {
     2266                                slug: $( event.target ).closest( '.notice' ).data( 'slug' )
     2267                        } );
     2268                },
     2269
     2270                /**
     2271                 * Delete a theme via wp.updates.
     2272                 *
     2273                 * @since 4.7.0
     2274                 */
     2275                deleteTheme: function( event ) {
     2276                        var theme, section;
     2277                        theme = $( event.target ).data( 'slug' );
     2278                        section = api.section( 'installed_themes' );
     2279
     2280                        event.preventDefault();
     2281
     2282                        // Confirmation dialog for deleting a theme.
     2283                        if ( ! window.confirm( api.settings.l10n.confirmDeleteTheme ) ) {
     2284                                return;
     2285                        }
     2286
     2287                        wp.updates.maybeRequestFilesystemCredentials( event );
     2288
     2289                        $( document ).one( 'wp-theme-delete-success', function() {
     2290                                var control = api.control( 'installed_theme_' + theme );
     2291
     2292                                // Remove theme control.
     2293                                control.container.remove();
     2294                                api.control.remove( control.id );
     2295
     2296                                // Update installed count.
     2297                                section.loaded = section.loaded - 1;
     2298                                section.updateCount();
     2299
     2300                                // Rerender any other theme controls as uninstalled.
     2301                                api.control.each( function( control ) {
     2302                                        if ( 'theme' === control.params.type && control.params.theme.id === theme ) {
     2303                                                control.rerenderAsInstalled( false );
     2304                                        }
     2305                                });
     2306                        } );
     2307
     2308                        wp.updates.deleteTheme( {
     2309                                slug: theme
     2310                        } );
     2311
     2312                        // Close modal and focus the section.
     2313                        section.closeDetails();
     2314                        section.focus();
     2315                }
     2316
     2317        });
     2318
    17552319
    17562320        /**
     
    20492613                 * @param {Object}   args
    20502614                 * @param {Number}   args.duration
    2051                  * @param {Callback} args.completeCallback
     2615                 * @param {Function} args.completeCallback
    20522616                 */
    20532617                onChangeActive: function ( active, args ) {
     
    30723636
    30733637                touchDrag: false,
    3074                 isRendered: false,
    3075 
    3076                 /**
    3077                  * Defer rendering the theme control until the section is displayed.
    3078                  *
    3079                  * @since 4.2.0
    3080                  */
    3081                 renderContent: function () {
    3082                         var control = this,
    3083                                 renderContentArgs = arguments;
    3084 
    3085                         api.section( control.section(), function( section ) {
    3086                                 if ( section.expanded() ) {
    3087                                         api.Control.prototype.renderContent.apply( control, renderContentArgs );
    3088                                         control.isRendered = true;
    3089                                 } else {
    3090                                         section.expanded.bind( function( expanded ) {
    3091                                                 if ( expanded && ! control.isRendered ) {
    3092                                                         api.Control.prototype.renderContent.apply( control, renderContentArgs );
    3093                                                         control.isRendered = true;
    3094                                                 }
    3095                                         } );
    3096                                 }
    3097                         } );
    3098                 },
     3638                screenshotRendered: false,
    30993639
    31003640                /**
     
    31203660
    31213661                                // Prevent the modal from showing when the user clicks the action button.
    3122                                 if ( $( event.target ).is( '.theme-actions .button' ) ) {
     3662                                if ( $( event.target ).is( '.theme-actions .button, .update-theme' ) ) {
    31233663                                        return;
    31243664                                }
    31253665
    3126                                 api.section( control.section() ).loadThemePreview( control.params.theme.id );
    3127                         });
    3128 
    3129                         control.container.on( 'click keydown', '.theme-actions .theme-details', function( event ) {
    3130                                 if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
    3131                                         return;
    3132                                 }
    3133 
    31343666                                event.preventDefault(); // Keep this AFTER the key filter above
    3135 
    31363667                                api.section( control.section() ).showDetails( control.params.theme );
    31373668                        });
     
    31443675                                        $screenshot.attr( 'src', source );
    31453676                                }
    3146                         });
    3147                 },
    3148 
    3149                 /**
    3150                  * Show or hide the theme based on the presence of the term in the title, description, and author.
     3677                                control.screenshotRendered = true;
     3678                        });
     3679                },
     3680
     3681                /**
     3682                 * Show or hide the theme based on the presence of the term in the title, description, tags, and author.
    31513683                 *
    31523684                 * @since 4.2.0
     
    31643696                                control.deactivate();
    31653697                        }
     3698                },
     3699
     3700                /**
     3701                 * Rerender the theme from its JS template with the installed type.
     3702                 *
     3703                 * @since 4.7.0
     3704                 */
     3705                rerenderAsInstalled: function( installed ) {
     3706                        var control = this, section;
     3707                        if ( installed ) {
     3708                                control.params.theme.type = 'installed';
     3709                        } else {
     3710                                section = api.section( control.params.section );
     3711                                control.params.theme.type = section.params.action;
     3712                        }
     3713                        control.renderContent(); // replaces existing content
     3714                        control.container.trigger( 'render-screenshot' );
    31663715                }
    31673716        });
     
    38454394                theme:         api.ThemeControl
    38464395        };
    3847         api.panelConstructor = {};
     4396        api.panelConstructor = {
     4397                themes: api.ThemesPanel
     4398        };
    38484399        api.sectionConstructor = {
    38494400                themes: api.ThemesSection
     
    39634514                // Sort the sections within each panel
    39644515                api.panel.each( function ( panel ) {
     4516                        if ( 'themes' === panel.id ) {
     4517                                return; // Don't reflow theme sections, as doing so moves them after the themes container.
     4518                        }
     4519                       
    39654520                        var sections = panel.sections(),
    39664521                                sectionHeadContainers = _.pluck( sections, 'headContainer' );
     
    45685123                        collapsedObject = expandedControls[0] || expandedSections[0] || expandedPanels[0];
    45695124                        if ( collapsedObject ) {
     5125                                if ( 'themes' === collapsedObject.params.type ) {
     5126                                        // Themes panel or section.
     5127                                        if ( $( 'body' ).hasClass( 'modal-open' ) ) {
     5128                                                collapsedObject.closeDetails();
     5129                                        } else {
     5130                                                // If we're collapsing a section, collapse the panel also.
     5131                                                wp.customize.panel( 'themes' ).collapse();
     5132                                        }
     5133                                        return;
     5134                                }
    45705135                                collapsedObject.collapse();
    45715136                                event.preventDefault();
  • trunk/src/wp-admin/js/updates.js

    r38704 r38813  
    180180                        $notice.replaceWith( $adminNotice );
    181181                } else {
    182                         $( '.wrap' ).find( '> h1' ).after( $adminNotice );
     182                        if ( 'customize' === pagenow ) {
     183                                $( '.customize-themes-notifications' ).append( $adminNotice );
     184                        } else {
     185                                $( '.wrap' ).find( '> h1' ).after( $adminNotice );
     186                        }
    183187                }
    184188
     
    908912                        $notice = $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' );
    909913
     914                } else if ( 'customize' === pagenow ) {
     915
     916                        // Update the theme details UI.
     917                        $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' );
     918
     919                        $notice.find( 'h3' ).remove();
     920
     921                        // Add the top-level UI, and update both.
     922                        $notice = $notice.add( $( '#customize-control-theme-installed_' + args.slug ).find( '.update-message' ) );
     923                        $notice = $notice.addClass( 'updating-message' ).find( 'p' );
     924
    910925                } else {
    911926                        $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' );
     
    950965                        $notice, newText;
    951966
     967                if ( 'customize' === pagenow ) {
     968                        $theme = wp.customize.control( 'installed_theme_' + response.slug ).container;
     969                }
     970
    952971                if ( 'themes-network' === pagenow ) {
    953972                        $notice = $theme.find( '.update-message' );
     
    10021021                if ( wp.updates.maybeHandleCredentialError( response, 'update-theme' ) ) {
    10031022                        return;
     1023                }
     1024
     1025                if ( 'customize' === pagenow ) {
     1026                        $theme = wp.customize.control( 'installed_theme_' + response.slug ).container;
    10041027                }
    10051028
     
    11401163                }
    11411164
    1142                 if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) {
    1143                         $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
    1144                         $card   = $( '.install-theme-info' ).prepend( $message );
     1165                if ( 'customize' === pagenow ) {
     1166                        if ( $document.find( 'body' ).hasClass( 'modal-open' ) ) {
     1167                                $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
     1168                                $card   = $( '.theme-overlay .theme-info' ).prepend( $message );
     1169                        } else {
     1170                                $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
     1171                                $card   = $button.closest( '.theme' ).addClass( 'theme-install-failed' ).append( $message );
     1172                        }
     1173                        $( '.wp-full-overlay' ).removeClass( 'customize-loading' );
    11451174                } else {
    1146                         $card   = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message );
    1147                         $button = $card.find( '.theme-install' );
     1175                        if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) {
     1176                                $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
     1177                                $card   = $( '.install-theme-info' ).prepend( $message );
     1178                        } else {
     1179                                $card   = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message );
     1180                                $button = $card.find( '.theme-install' );
     1181                        }
    11481182                }
    11491183
  • trunk/src/wp-includes/class-wp-customize-manager.php

    r38810 r38813  
    295295                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' );
    296296
     297                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-panel.php' );
    297298                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php' );
    298299                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php' );
     
    349350                add_action( 'wp_ajax_customize_save',           array( $this, 'save' ) );
    350351                add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) );
     352                add_action( 'wp_ajax_customize-load-themes',    array( $this, 'load_themes_ajax' ) );
    351353
    352354                add_action( 'customize_register',                 array( $this, 'register_controls' ) );
     
    362364                // Export the settings to JS via the _wpCustomizeSettings variable.
    363365                add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 );
     366
     367                // Add theme update notices.
     368                if ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) ) {
     369                        require_once( ABSPATH . '/wp-admin/includes/update.php' );
     370                        add_action( 'customize_controls_print_footer_scripts', 'wp_print_admin_notice_templates' );
     371                }
    364372        }
    365373
     
    25852593                        $control->enqueue();
    25862594                }
     2595                if ( ! is_multisite() && ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) || current_user_can( 'delete_themes' ) ) ) {
     2596                        wp_enqueue_script( 'updates' );
     2597                }
    25872598        }
    25882599
     
    27992810                        'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
    28002811                        'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
     2812                        'switch-themes' => wp_create_nonce( 'switch-themes' ),
    28012813                );
    28022814
     
    28722884                        'documentTitleTmpl' => $this->get_document_title_template(),
    28732885                        'previewableDevices' => $this->get_previewable_devices(),
     2886                        'l10n' => array(
     2887                                'confirmDeleteTheme' => __( 'Are you sure you want to delete this theme?' ),
     2888                                /* translators: %d is the number of theme search results, which cannot consider singular vs. plural forms */
     2889                                'themeSearchResults' => __( '%d themes found' ),
     2890                                /* translators: %d is the number of themes being displayed, which cannot consider singular vs. plural forms */
     2891                                'announceThemeCount' => __( 'Displaying %d themes' ),
     2892                                'announceThemeDetails' => __( 'Showing details for theme: %s' ),
     2893                        ),
    28742894                );
    28752895
     
    29752995                /* Panel, Section, and Control Types */
    29762996                $this->register_panel_type( 'WP_Customize_Panel' );
     2997                $this->register_panel_type( 'WP_Customize_Themes_Panel' );
    29772998                $this->register_section_type( 'WP_Customize_Section' );
    29782999                $this->register_section_type( 'WP_Customize_Sidebar_Section' );
     3000                $this->register_section_type( 'WP_Customize_Themes_Section' );
    29793001                $this->register_control_type( 'WP_Customize_Color_Control' );
    29803002                $this->register_control_type( 'WP_Customize_Media_Control' );
     
    29863008                $this->register_control_type( 'WP_Customize_Theme_Control' );
    29873009
    2988                 /* Themes */
    2989 
    2990                 $this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array(
    2991                         'title'      => $this->theme()->display( 'Name' ),
    2992                         'capability' => 'switch_themes',
    2993                         'priority'   => 0,
     3010                /* Themes (controls are loaded via ajax) */
     3011
     3012                $this->add_panel( new WP_Customize_Themes_Panel( $this, 'themes', array(
     3013                        'title'       => $this->theme()->display( 'Name' ),
     3014                        '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.' ),
     3015                        'capability'  => 'switch_themes',
     3016                        'priority'    => 0,
     3017                ) ) );
     3018
     3019                $this->add_section( new WP_Customize_Themes_Section( $this, 'installed_themes', array(
     3020                        'title'       => __( 'Installed' ),
     3021                        'text_before' => __( 'Your local site' ),
     3022                        'action'      => 'installed',
     3023                        'capability'  => 'switch_themes',
     3024                        'panel'       => 'themes',
     3025                        'priority'    => 0,
     3026                ) ) );
     3027
     3028                $this->add_section( new WP_Customize_Themes_Section( $this, 'search_themes', array(
     3029                        'title'       => __( 'Search themes&hellip;' ),
     3030                        'text_before' => __( 'Browse all WordPress.org themes' ),
     3031                        'action'      => 'search',
     3032                        'capability'  => 'install_themes',
     3033                        'panel'       => 'themes',
     3034                        'priority'    => 5,
     3035                ) ) );
     3036
     3037                $this->add_section( new WP_Customize_Themes_Section( $this, 'featured_themes', array(
     3038                        'title'      => __( 'Featured' ),
     3039                        'action'     => 'featured',
     3040                        'capability' => 'install_themes',
     3041                        'panel'      => 'themes',
     3042                        'priority'   => 10,
     3043                ) ) );
     3044
     3045                $this->add_section( new WP_Customize_Themes_Section( $this, 'popular_themes', array(
     3046                        'title'      => __( 'Popular' ),
     3047                        'action'     => 'popular',
     3048                        'capability' => 'install_themes',
     3049                        'panel'      => 'themes',
     3050                        'priority'   => 15,
     3051                ) ) );
     3052
     3053                $this->add_section( new WP_Customize_Themes_Section( $this, 'latest_themes', array(
     3054                        'title'      => __( 'Latest' ),
     3055                        'action'     => 'latest',
     3056                        'capability' => 'install_themes',
     3057                        'panel'      => 'themes',
     3058                        'priority'   => 20,
     3059                ) ) );
     3060
     3061                $this->add_section( new WP_Customize_Themes_Section( $this, 'feature_filter_themes', array(
     3062                        'title'      => __( 'Feature Filter' ),
     3063                        'action'     => 'feature_filter',
     3064                        'capability' => 'install_themes',
     3065                        'panel'      => 'themes',
     3066                        'priority'   => 25,
     3067                ) ) );
     3068
     3069                $this->add_section( new WP_Customize_Themes_Section( $this, 'favorites_themes', array(
     3070                        'title'      => __( 'Favorites' ),
     3071                        'action'     => 'favorites',
     3072                        'capability' => 'install_themes',
     3073                        'panel'      => 'themes',
     3074                        'priority'   => 30,
    29943075                ) ) );
    29953076
     
    29983079                        'capability' => 'switch_themes',
    29993080                ) ) );
    3000 
    3001                 require_once( ABSPATH . 'wp-admin/includes/theme.php' );
    3002 
    3003                 // Theme Controls.
    3004 
    3005                 // Add a control for the active/original theme.
    3006                 if ( ! $this->is_theme_active() ) {
    3007                         $themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) );
    3008                         $active_theme = current( $themes );
    3009                         $active_theme['isActiveTheme'] = true;
    3010                         $this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array(
    3011                                 'theme'    => $active_theme,
    3012                                 'section'  => 'themes',
    3013                                 'settings' => 'active_theme',
    3014                         ) ) );
    3015                 }
    3016 
    3017                 $themes = wp_prepare_themes_for_js();
    3018                 foreach ( $themes as $theme ) {
    3019                         if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) {
    3020                                 continue;
    3021                         }
    3022 
    3023                         $theme_id = 'theme_' . $theme['id'];
    3024                         $theme['isActiveTheme'] = false;
    3025                         $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(
    3026                                 'theme'    => $theme,
    3027                                 'section'  => 'themes',
    3028                                 'settings' => 'active_theme',
    3029                         ) ) );
    3030                 }
    30313081
    30323082                /* Site Identity */
     
    33573407
    33583408        /**
     3409         * Load themes into the theme browsing/installation UI.
     3410         *
     3411         * @since 4.7.0
     3412         * @access public
     3413         */
     3414        public function load_themes_ajax() {
     3415                check_ajax_referer( 'switch-themes', 'switch-themes-nonce' );
     3416
     3417                if ( ! current_user_can( 'switch_themes' ) ) {
     3418                        wp_die( -1 );
     3419                }
     3420
     3421                if ( empty( $_POST['theme_action'] ) ) {
     3422                        wp_send_json_error( 'missing_theme_action' );
     3423                }
     3424
     3425                if ( 'search' === $_POST['theme_action'] && ! array_key_exists( 'search', $_POST ) ) {
     3426                        wp_send_json_error( 'empty_search' );
     3427                } elseif ( 'favorites' === $_POST['theme_action'] && ! array_key_exists( 'user', $_POST ) ) {
     3428                        wp_send_json_error( 'empty_user' );
     3429                } elseif ( 'feature_filter' === $_POST['theme_action'] && ! array_key_exists( 'tags', $_POST ) ) {
     3430                        wp_send_json_error( 'no_features' );
     3431                }
     3432
     3433                require_once( ABSPATH . 'wp-admin/includes/theme.php' );
     3434                if ( 'installed' === $_POST['theme_action'] ) {
     3435                        $themes = array( 'themes' => wp_prepare_themes_for_js() );
     3436                        foreach ( $themes['themes'] as &$theme ) {
     3437                                $theme['type'] = 'installed';
     3438                                // Set active based on customized theme.
     3439                                if ( $_POST['customized_theme'] === $theme['id'] ) {
     3440                                        $theme['active'] = true;
     3441                                } else {
     3442                                        $theme['active'] = false;
     3443                                }
     3444                        }
     3445                } else {
     3446                        if ( ! current_user_can( 'install_themes' ) ) {
     3447                                wp_die( -1 );
     3448                        }
     3449
     3450                        // Arguments for all queries.
     3451                        $args = array(
     3452                                'per_page' => 100,
     3453                                'page' => absint( $_POST['page'] ),
     3454                                'fields' => array(
     3455                                        'slug' => true,
     3456                                        'screenshot' => true,
     3457                                        'description' => true,
     3458                                        'requires' => true,
     3459                                        'rating' => true,
     3460                                        'downloaded' => true,
     3461                                        'downloadLink' => true,
     3462                                        'last_updated' => true,
     3463                                        'homepage' => true,
     3464                                        'num_ratings' => true,
     3465                                        'tags' => true,
     3466                                ),
     3467                        );
     3468
     3469                        // Specialized handling for each query.
     3470                        switch ( $_POST['theme_action'] ) {
     3471                                case 'search':
     3472                                        $args['search'] = wp_unslash( $_POST['search'] );
     3473                                        break;
     3474                                case 'favorites':
     3475                                        $args['user'] = wp_unslash( $_POST['user'] );
     3476                                case 'featured':
     3477                                case 'popular':
     3478                                        $args['browse'] = wp_unslash( $_POST['theme_action'] );
     3479                                        break;
     3480                                case 'latest':
     3481                                        $args['browse'] = 'new';
     3482                                        break;
     3483                                case 'feature_filter':
     3484                                        $args['tag'] = wp_unslash( $_POST['tags'] );
     3485                                        break;
     3486                        }
     3487
     3488                        // Load themes from the .org API.
     3489                        $themes = themes_api( 'query_themes', $args );
     3490                        if ( is_wp_error( $themes ) ) {
     3491                                wp_send_json_error();
     3492                        }
     3493
     3494                        // This list matches the allowed tags in wp-admin/includes/theme-install.php.
     3495                        $themes_allowedtags = array('a' => array('href' => array(), 'title' => array(), 'target' => array()),
     3496                                'abbr' => array('title' => array()), 'acronym' => array('title' => array()),
     3497                                'code' => array(), 'pre' => array(), 'em' => array(), 'strong' => array(),
     3498                                'div' => array(), 'p' => array(), 'ul' => array(), 'ol' => array(), 'li' => array(),
     3499                                'h1' => array(), 'h2' => array(), 'h3' => array(), 'h4' => array(), 'h5' => array(), 'h6' => array(),
     3500                                'img' => array('src' => array(), 'class' => array(), 'alt' => array())
     3501                        );
     3502
     3503                        // Prepare a list of installed themes to check against before the loop.
     3504                        $installed_themes = array();
     3505                        $wp_themes = wp_get_themes();
     3506                        foreach ( $wp_themes as $theme ) {
     3507                                $installed_themes[] = $theme->get_stylesheet();
     3508                        }
     3509                        $update_php = network_admin_url( 'update.php?action=install-theme' );
     3510                        foreach ( $themes->themes as &$theme ) {
     3511                                $theme->install_url = add_query_arg( array(
     3512                                        'theme'    => $theme->slug,
     3513                                        '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ),
     3514                                ), $update_php );
     3515
     3516                                $theme->name        = wp_kses( $theme->name, $themes_allowedtags );
     3517                                $theme->author      = wp_kses( $theme->author, $themes_allowedtags );
     3518                                $theme->version     = wp_kses( $theme->version, $themes_allowedtags );
     3519                                $theme->description = wp_kses( $theme->description, $themes_allowedtags );
     3520                                $theme->tags        = implode( ', ', $theme->tags );
     3521                                $theme->stars       = wp_star_rating( array( 'rating' => $theme->rating, 'type' => 'percent', 'number' => $theme->num_ratings, 'echo' => false ) );
     3522                                $theme->num_ratings = number_format_i18n( $theme->num_ratings );
     3523                                $theme->preview_url = set_url_scheme( $theme->preview_url );
     3524
     3525                                // Handle themes that are already installed as installed themes.
     3526                                if ( in_array( $theme->slug, $installed_themes, true ) ) {
     3527                                        $theme->type = 'installed';
     3528                                } else {
     3529                                        $theme->type = $_POST['theme_action'];
     3530                                }
     3531
     3532                                // Set active based on customized theme.
     3533                                if ( $_POST['customized_theme'] === $theme->slug ) {
     3534                                        $theme->active = true;
     3535                                } else {
     3536                                        $theme->active = false;
     3537                                }
     3538
     3539                                // Map available theme properties to installed theme properties.
     3540                                $theme->id           = $theme->slug;
     3541                                $theme->screenshot   = array( $theme->screenshot_url );
     3542                                $theme->authorAndUri = $theme->author;
     3543                                unset( $theme->slug );
     3544                                unset( $theme->screenshot_url );
     3545                                unset( $theme->author );
     3546                        } // End foreach().
     3547                } // End if().
     3548                wp_send_json_success( $themes );
     3549        }
     3550
     3551
     3552        /**
    33593553         * Callback for validating the header_textcolor value.
    33603554         *
  • trunk/src/wp-includes/customize/class-wp-customize-theme-control.php

    r38810 r38813  
    6363         */
    6464        public function content_template() {
    65                 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
    66                 $active_url  = esc_url( remove_query_arg( 'customize_theme', $current_url ) );
    67                 $preview_url = esc_url( add_query_arg( 'customize_theme', '__THEME__', $current_url ) ); // Token because esc_url() strips curly braces.
    68                 $preview_url = str_replace( '__THEME__', '{{ data.theme.id }}', $preview_url );
     65                /* translators: %s: theme name */
     66                $details_label = sprintf( __( 'Details for theme: %s' ), '{{ data.theme.name }}' );
     67                /* translators: %s: theme name */
     68                $customize_label = sprintf( __( 'Customize theme: %s' ), '{{ data.theme.name }}' );
     69                /* translators: %s: theme name */
     70                $preview_label = sprintf( __( 'Live preview theme: %s' ), '{{ data.theme.name }}' );
     71                /* translators: %s: theme name */
     72                $install_label = sprintf( __( 'Install and preview theme: %s' ), '{{ data.theme.name }}' );
    6973                ?>
    70                 <# if ( data.theme.isActiveTheme ) { #>
    71                         <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">
     74                <# if ( data.theme.active ) { #>
     75                        <div class="theme active" tabindex="0" aria-describedby="{{ data.section }}-{{ data.theme.id }}-action {{ data.theme.id }}-name">
    7276                <# } else { #>
    73                         <div class="theme" tabindex="0" data-preview-url="<?php echo esc_attr( $preview_url ); ?>" aria-describedby="{{ data.theme.id }}-action {{ data.theme.id }}-name">
     77                        <div class="theme" tabindex="0" aria-describedby="{{ data.section }}-{{ data.theme.id }}-action {{ data.theme.id }}-name">
    7478                <# } #>
    7579
    76                         <# if ( data.theme.screenshot[0] ) { #>
     80                        <# if ( data.theme.screenshot && data.theme.screenshot[0] ) { #>
    7781                                <div class="theme-screenshot">
    7882                                        <img data-src="{{ data.theme.screenshot[0] }}" alt="" />
     
    8286                        <# } #>
    8387
    84                         <# if ( data.theme.isActiveTheme ) { #>
    85                                 <span class="more-details" id="{{ data.theme.id }}-action"><?php _e( 'Customize' ); ?></span>
    86                         <# } else { #>
    87                                 <span class="more-details" id="{{ data.theme.id }}-action"><?php _e( 'Live Preview' ); ?></span>
     88                        <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>
     89
     90
     91                        <# if ( 'installed' === data.theme.type && data.theme.hasUpdate ) { #>
     92                                <div class="update-message notice inline notice-warning notice-alt" data-slug="{{ data.theme.id }}"><p><?php printf( __( 'New version available. %s' ), '<button class="button-link update-theme" type="button">' . __( 'Update now' ) . '</button>' ); ?></p></div>
    8893                        <# } #>
    8994
    90                         <div class="theme-author"><?php printf( __( 'By %s' ), '{{ data.theme.author }}' ); ?></div>
    91 
    92                         <# if ( data.theme.isActiveTheme ) { #>
    93                                 <h3 class="theme-name" id="{{ data.theme.id }}-name">
     95                        <# if ( data.theme.active ) { #>
     96                                <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">
    9497                                        <?php
    9598                                        /* translators: %s: theme name */
    96                                         printf( __( '<span>Active:</span> %s' ), '{{{ data.theme.name }}}' );
     99                                        printf( __( '<span>Current:</span> %s' ), '{{ data.theme.name }}' );
    97100                                        ?>
    98101                                </h3>
     102                                <div class="theme-actions">
     103                                        <button type="button" class="button button-primary customize-theme" aria-label="<?php echo esc_attr( $customize_label ); ?>"><?php _e( 'Customize' ); ?></button>
     104                                </div>
     105                                <div class="notice notice-success notice-alt"><p><?php _e( 'Installed' ); ?></p></div>
     106                        <# } else if ( 'installed' === data.theme.type ) { #>
     107                                <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3>
     108                                <div class="theme-actions">
     109                                        <button type="button" class="button button-primary preview-theme" aria-label="<?php echo esc_attr( $preview_label ); ?>" data-theme-id="{{ data.theme.id }}"><?php _e( 'Live Preview' ); ?></span>
     110                                </div>
     111                                <div class="notice notice-success notice-alt"><p><?php _e( 'Installed' ); ?></p></div>
    99112                        <# } else { #>
    100                                 <h3 class="theme-name" id="{{ data.theme.id }}-name">{{{ data.theme.name }}}</h3>
     113                                <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3>
    101114                                <div class="theme-actions">
    102                                         <button type="button" class="button theme-details"><?php _e( 'Theme Details' ); ?></button>
     115                                        <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 }}" data-theme-id="{{ data.theme.id }}"><?php _e( 'Install & Preview' ); ?></button>
    103116                                </div>
    104117                        <# } #>
  • trunk/src/wp-includes/customize/class-wp-customize-themes-section.php

    r35943 r38813  
    1111 * Customize Themes Section class.
    1212 *
    13  * A UI container for theme controls, which behaves like a backwards Panel.
     13 * A UI container for theme controls, which are displayed in tabbed sections.
    1414 *
    1515 * @since 4.2.0
     
    2929
    3030        /**
    31          * Render the themes section, which behaves like a panel.
     31         * Theme section action.
    3232         *
    33          * @since 4.2.0
     33         * Defines the type of themes to load (installed, featured, latest, etc.).
     34         *
     35         * @since 4.7.0
     36         * @access public
     37         * @var string
     38         */
     39        public $action = '';
     40
     41        /**
     42         * Text before theme section heading.
     43         *
     44         * @since 4.7.0
     45         * @access public
     46         * @var string
     47         */
     48        public $text_before = '';
     49
     50        /**
     51         * Get section parameters for JS.
     52         *
     53         * @since 4.7.0
     54         * @access public
     55         * @return array Exported parameters.
     56         */
     57        public function json() {
     58                $exported = parent::json();
     59                $exported['action'] = $this->action;
     60                $exported['text_before'] = $this->text_before;
     61
     62                return $exported;
     63        }
     64
     65        /**
     66         * Render a themes section as a JS template.
     67         *
     68         * The template is only rendered by PHP once, so all actions are prepared at once on the server side.
     69         *
     70         * @since 4.7.0
    3471         * @access protected
    3572         */
    36         protected function render() {
    37                 $classes = 'accordion-section control-section control-section-' . $this->type;
     73        protected function render_template() {
    3874                ?>
    39                 <li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>">
    40                         <h3 class="accordion-section-title">
    41                                 <?php
    42                                 if ( $this->manager->is_theme_active() ) {
    43                                         echo '<span class="customize-action">' . __( 'Active theme' ) . '</span> ' . $this->title;
     75                <li id="accordion-section-{{ data.id }}" class="theme-section">
     76                        <# if ( '' !== data.text_before ) { #>
     77                                <p class="customize-themes-text-before">{{ data.text_before }}</p>
     78                        <# } #>
     79                        <# if ( 'search' === data.action ) { #>
     80                                <div class="search-form customize-themes-section-title themes-section-search_themes">
     81                                        <label class="screen-reader-text" for="wp-filter-search-input">{{ data.title }}</label>
     82                                        <input placeholder="{{ data.title }}" type="text" aria-describedby="live-search-desc" id="wp-filter-search-input" class="wp-filter-search">
     83                                        <span id="live-search-desc" class="screen-reader-text"><?php _e( 'The search results will be updated as you type.' ); ?></span>
     84                                </div>
     85                        <# } else { #>
     86                                <# if ( 'favorites' === data.action || 'feature_filter' === data.action ) {
     87                                        var attr = ' aria-expanded="false"';
    4488                                } else {
    45                                         echo '<span class="customize-action">' . __( 'Previewing theme' ) . '</span> ' . $this->title;
    46                                 }
    47                                 ?>
    48 
    49                                 <?php if ( count( $this->controls ) > 0 ) : ?>
    50                                         <button type="button" class="button change-theme" tabindex="0"><?php _ex( 'Change', 'theme' ); ?></button>
    51                                 <?php endif; ?>
    52                         </h3>
    53                         <div class="customize-themes-panel control-panel-content themes-php">
    54                                 <h3 class="accordion-section-title customize-section-title">
    55                                         <span class="customize-action"><?php _e( 'Customizing' ); ?></span>
    56                                         <?php _e( 'Themes' ); ?>
    57                                         <span class="title-count theme-count"><?php echo count( $this->controls ) + 1 /* Active theme */; ?></span>
    58                                 </h3>
    59                                 <h3 class="accordion-section-title customize-section-title">
     89                                        var attr = '';
     90                                } #>
     91                                <button type="button" class="customize-themes-section-title themes-section-{{ data.id }}"{{{ attr }}}>{{ data.title }}</button>
     92                        <# } #>
     93                        <?php if ( ! current_user_can( 'install_themes' ) || is_multisite() ) : ?>
     94                                <# if ( 'installed' === data.action ) { #>
     95                                        <p class="themes-filter-container">
     96                                                <label for="themes-filter">
     97                                                        <span class="screen-reader-text"><?php _e( 'Search installed themes&hellip;' ); ?></span>
     98                                                        <input type="text" id="themes-filter" placeholder="<?php esc_attr_e( 'Search installed themes&hellip;' ); ?>" />
     99                                                </label>
     100                                        </p>
     101                                <# } #>
     102                        <?php endif; ?>
     103                        <# if ( 'favorites' === data.action ) { #>
     104                                <div class="favorites-form filter-details">
     105                                        <p class="install-help"><?php _e( 'If you have marked themes as favorites on WordPress.org, you can browse them here.' ); ?></p>
     106                                        <p>
     107                                                <label for="wporg-username-input"><?php _e( 'Your WordPress.org username:' ); ?></label>
     108                                                <input type="search" id="wporg-username-input" value="">
     109                                                <button type="button" class="button button-secondary favorites-form-submit"><?php _e( 'Get Favorites' ); ?></button>
     110                                        </p>
     111                                </div>
     112                        <# } else if ( 'feature_filter' === data.action ) { #>
     113                                <div class="filter-drawer filter-details">
    60114                                        <?php
    61                                         if ( $this->manager->is_theme_active() ) {
    62                                                 echo '<span class="customize-action">' . __( 'Active theme' ) . '</span> ' . $this->title;
    63                                         } else {
    64                                                 echo '<span class="customize-action">' . __( 'Previewing theme' ) . '</span> ' . $this->title;
     115                                        $feature_list = get_theme_feature_list();
     116                                        foreach ( $feature_list as $feature_name => $features ) {
     117                                                echo '<fieldset class="filter-group">';
     118                                                $feature_name = esc_html( $feature_name );
     119                                                echo '<legend><button type="button" class="button-link" aria-expanded="false">' . $feature_name . '</button></legend>';
     120                                                echo '<div class="filter-group-feature">';
     121                                                foreach ( $features as $feature => $feature_name ) {
     122                                                        $feature = esc_attr( $feature );
     123                                                        echo '<input type="checkbox" id="filter-id-' . $feature . '" value="' . $feature . '" /> ';
     124                                                        echo '<label for="filter-id-' . $feature . '">' . $feature_name . '</label><br>';
     125                                                }
     126                                                echo '</div>';
     127                                                echo '</fieldset>';
    65128                                        }
    66129                                        ?>
    67                                         <button type="button" class="button customize-theme"><?php _e( 'Customize' ); ?></button>
    68                                 </h3>
    69 
     130                                </div>
     131                        <# } #>
     132                        <div class="customize-themes-section themes-section-{{ data.id }} control-section-content themes-php">
    70133                                <div class="theme-overlay" tabindex="0" role="dialog" aria-label="<?php esc_attr_e( 'Theme Details' ); ?>"></div>
    71 
    72                                 <div id="customize-container"></div>
    73                                 <?php if ( count( $this->controls ) > 4 ) : ?>
    74                                         <p><label for="themes-filter">
    75                                                 <span class="screen-reader-text"><?php _e( 'Search installed themes&hellip;' ); ?></span>
    76                                                 <input type="text" id="themes-filter" placeholder="<?php esc_attr_e( 'Search installed themes&hellip;' ); ?>" />
    77                                         </label></p>
    78                                 <?php endif; ?>
    79134                                <div class="theme-browser rendered">
    80                                         <ul class="themes accordion-section-content">
     135                                        <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&#8217;s configuration. If you continue to have problems, please try the <a href="https://wordpress.org/support/">support forums</a>.' ); ?></p></div>
     136                                        <ul class="themes">
    81137                                        </ul>
     138                                        <p class="no-themes"><?php _e( 'No themes found. Try a different search.' ); ?></p>
     139                                        <p class="spinner"></p>
    82140                                </div>
    83141                        </div>
  • trunk/tests/phpunit/tests/customize/manager.php

    r38810 r38813  
    14871487                $this->assertNotEmpty( $data );
    14881488
    1489                 $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts' ), array_keys( $data ) );
     1489                $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts', 'l10n' ), array_keys( $data ) );
    14901490                $this->assertEquals( $autofocus, $data['autofocus'] );
    14911491                $this->assertArrayHasKey( 'save', $data['nonce'] );
Note: See TracChangeset for help on using the changeset viewer.