WordPress.org

Make WordPress Core

Changeset 38813


Ignore:
Timestamp:
10/19/2016 03:19:13 AM (20 months 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.