Ticket #37661: 37661.7.diff
File 37661.7.diff, 90.1 KB (added by , 8 years ago) |
---|
-
src/wp-admin/css/customize-controls.css
271 271 } 272 272 273 273 #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 { 276 275 -webkit-transform: none; 277 276 -ms-transform: none; 278 277 transform: none; 279 278 } 280 279 281 #customize-theme-controls .customize-themes-panel.customize-pane-child,282 280 .section-open #customize-theme-controls .customize-pane-parent, 283 281 .in-sub-panel #customize-theme-controls .customize-pane-parent, 284 282 .section-open #customize-info, 285 283 .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 { 289 285 visibility: hidden; 290 286 height: 0; 291 287 overflow: hidden; … … 296 292 297 293 .section-open #customize-theme-controls .customize-pane-parent.busy, 298 294 .in-sub-panel #customize-theme-controls .customize-pane-parent.busy, 299 .in-themes-panel #customize-theme-controls .customize-pane-parent.busy,300 295 .section-open #customize-info.busy, 301 296 .in-sub-panel #customize-info.busy, 302 .in-themes-panel #customize-info.busy,303 297 .busy.section-open.in-sub-panel #customize-theme-controls .customize-pane-child.current-panel, 304 298 #customize-theme-controls .customize-pane-child.open, 305 299 #customize-theme-controls .customize-pane-child.current-panel, … … 309 303 overflow: auto; 310 304 } 311 305 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%);317 }318 319 306 #customize-theme-controls .customize-pane-child.accordion-section-content, 320 307 #customize-theme-controls .customize-pane-child.accordion-sub-container { 321 308 display: block; … … 406 393 display: block; 407 394 float: left; 408 395 width: 48px; 409 height: 7 1px;396 height: 70px; 410 397 padding: 0 24px 0 0; 411 398 margin: 0; 412 399 background: #fff; … … 420 407 } 421 408 422 409 .customize-section-back { 423 height: 7 4px;410 height: 73px; 424 411 } 425 412 426 413 .ios .customize-panel-back { … … 996 983 animation: customize-reload .75s; 997 984 } 998 985 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 { 1001 992 cursor: default; 1002 993 background: #fff; 1003 994 color: #555; … … 1004 995 border-top: 1px solid #ddd; 1005 996 border-bottom: 1px solid #ddd; 1006 997 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 */ 1008 1001 } 1009 1002 1010 1003 #customize-theme-controls .control-section-themes .customize-themes-panel .accordion-section-title:first-child:hover, /* Not a focusable element. */ … … 1012 1005 border-top: 0; 1013 1006 } 1014 1007 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, 1030 1009 #customize-controls .customize-section-title span.customize-action { 1031 1010 font-size: 13px; 1032 1011 display: block; … … 1033 1012 font-weight: 400; 1034 1013 } 1035 1014 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 { 1038 1016 position: absolute; 1039 1017 right: 10px; 1040 1018 top: 50%; … … 1042 1020 font-weight: 400; 1043 1021 } 1044 1022 1045 #customize- controls .control-section-themes .accordion-section-title:before{1023 #customize-theme-controls .control-panel-themes > .accordion-section-title:after { 1046 1024 display: none; 1047 1025 } 1048 1026 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; 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: 10; 1055 1039 } 1056 1040 1057 #customize-controls .customize-themes-panel .accordion-section-title:first-child { 1058 margin-top: 0; 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; 1059 1048 } 1060 1049 1061 #customize-controls .customize-themes-panel .accordion-section-title:nth-child(2) { 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; 1062 1226 font-size: 14px; 1063 1227 font-weight: 600; 1228 color: #555d66; 1229 text-shadow: none; 1064 1230 } 1065 1231 1066 #customize-controls .customize-themes-panel > h2 { 1067 padding: 15px 8px 0 8px; 1232 .control-panel-themes .theme-section { 1233 margin: 0; 1234 position: relative; 1068 1235 } 1069 1236 1070 #customize-theme-controls .customize-themes-panel .accordion-section-content { 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 50px 5px 15px; 1266 width: auto; 1267 } 1268 1269 .control-panel-themes .customize-themes-section-title.themes-section-search_themes:after { 1270 content: "\f179"; 1271 font: 20px/1 dashicons; 1272 position: absolute; 1273 right: 15px; 1274 bottom: 10px; 1275 } 1276 1277 .control-panel-themes .customize-themes-section-title.themes-section-feature_filter_themes:after, 1278 .control-panel-themes .customize-themes-section-title.themes-section-favorites_themes:after { 1279 content: "\f140"; 1280 font: 20px/1 dashicons; 1281 position: absolute; 1282 right: 15px; 1283 top: 8px; 1284 } 1285 1286 .control-panel-themes .customize-themes-section-title.themes-section-search_themes .wp-filter-search { 1287 width: 100%; 1288 } 1289 1290 .control-panel-themes .customize-themes-section-title.themes-section-search_themes.selected, 1291 .control-panel-themes .customize-themes-section-title.themes-section-search_themes:hover { 1292 background: #fff; 1293 cursor: default; 1294 } 1295 1296 .control-panel-themes .customize-themes-section-title.themes-section-feature_filter_themes { 1297 margin-top: 15px; 1298 border-top: 1px solid #ddd; 1299 } 1300 1301 .control-panel-themes .filter-details { 1302 background: #f5f5f5; 1303 margin: 0; 1304 padding: 8px 15px; 1305 border-top: none; 1306 border-bottom: 1px solid #ddd; 1307 display: none; 1308 } 1309 1310 .control-panel-themes .customize-themes-section-title.selected.details-open { 1311 border-bottom-color: #f5f5f5; 1312 border-left-color: #f5f5f5; 1313 background: #f5f5f5; 1314 } 1315 1316 .control-panel-themes .favorites-form.filter-details label { 1317 padding-bottom: 6px; 1318 display: inline-block; 1319 } 1320 1321 .control-panel-themes .filter-details .filter-group { 1322 float: none; 1323 width: 100%; 1071 1324 background: transparent; 1072 display: block; 1325 border: none; 1326 padding: 0; 1327 box-shadow: none; 1073 1328 } 1074 1329 1075 .customize-control.customize-control-theme { 1076 margin-bottom: 8px; 1330 .control-panel-themes .filter-details .filter-group legend button { 1331 padding: 18px 15px 8px 10px; 1332 line-height: 14px; 1333 border-bottom: 1px solid #ddd; 1334 width: 100%; 1335 text-align: left; 1077 1336 } 1078 1337 1338 .control-panel-themes .filter-details .filter-group legend { 1339 position: relative; 1340 top: 0; 1341 width: 100%; 1342 } 1343 1344 .control-panel-themes .filter-details .filter-group legend button:after { 1345 content: "\f140"; 1346 font: 20px/1 dashicons; 1347 position: absolute; 1348 bottom: 6px; 1349 right: 5px; 1350 } 1351 1352 .control-panel-themes .filter-details .filter-group legend button:hover, 1353 .control-panel-themes .filter-details .filter-group legend button:focus { 1354 color: #0073aa; 1355 border-bottom-color: #0073aa; /* Color change for focus style should be acceptable because border-bottom is barely visible previously. */ 1356 outline: none; 1357 box-shadow: none; 1358 } 1359 1360 .control-panel-themes .filter-details .filter-group legend button.open:after { 1361 content: "\f142"; 1362 } 1363 1364 .control-panel-themes .filter-details .filter-group .filter-group-feature { 1365 display: none; 1366 margin: 0; 1367 } 1368 1369 .control-panel-themes .filter-details .filter-group-feature label { 1370 border: 1px solid #ddd; 1371 border-top: 0; 1372 background: #fff; 1373 color: #555d66; 1374 margin: 0; 1375 padding: 12px 10px 12px 34px; 1376 width: calc(100% - 46px); 1377 line-height: 16px; 1378 font-weight: 600; 1379 } 1380 1381 .control-panel-themes .filter-details .filter-group-feature input { 1382 position: absolute; 1383 margin: 12px 10px; 1384 } 1385 1386 .control-panel-themes .filter-details .filter-group-feature label:hover { 1387 color: #0073aa; 1388 } 1389 1079 1390 #customize-theme-controls .themes.accordion-section-content { 1080 1391 position: relative; 1081 1392 left: 0; … … 1083 1394 width: 100%; 1084 1395 } 1085 1396 1086 .wp-customizer .theme-browser .themes { 1087 padding-bottom: 8px; 1397 .loading .customize-themes-section .spinner { 1398 display: block; 1399 visibility: visible; 1400 position: relative; 1401 clear: both; 1402 width: 20px; 1403 height: 20px; 1404 left: calc(50% - 10px); 1405 float: none; 1406 margin-top: 50px; 1088 1407 } 1089 1408 1090 .wp-customizer .theme-browser .theme { 1409 .customize-themes-section .filter-drawer { 1410 border-top: none; 1411 display: block; 1412 background: transparent; 1413 padding-top: 5px; 1414 } 1415 1416 .customize-themes-section .clear-filters { 1417 margin-left: 8px; 1418 display: none; 1419 } 1420 1421 .customize-themes-section .no-themes { 1422 display: none; 1423 } 1424 1425 .themes-section-installed_themes .theme .notice-success { 1426 display: none; /* Hide "installed" notice on installed themes tab. */ 1427 } 1428 1429 .control-panel-themes .theme-browser .theme .theme-actions .button-primary { 1430 margin: 0 0 0 8px; 1431 } 1432 1433 .customize-control-theme .theme { 1434 width: 100%; 1091 1435 margin: 0; 1092 width: 100%;1093 1436 } 1094 1437 1438 .customize-control.customize-control-theme { /* override most properties on .customize-control */ 1439 box-sizing: border-box; 1440 width: 18.4%; 1441 margin: 0 2% 2% 0; 1442 padding: 0; 1443 clear: none; 1444 } 1445 1446 /* 5 columns above 2100px */ 1447 @media screen and (min-width: 2101px) { 1448 .customize-control.customize-control-theme:nth-child(5n) { 1449 margin-right: 0; 1450 } 1451 } 1452 1453 /* 4 columns up to 2100px */ 1454 @media screen and (min-width: 1601px) and (max-width: 2100px) { 1455 .customize-control.customize-control-theme { 1456 width: 23.5%; 1457 } 1458 1459 .customize-control.customize-control-theme:nth-child(4n) { 1460 margin-right: 0; 1461 } 1462 } 1463 1464 /* 3 columns up to 1600px */ 1465 @media screen and (min-width: 1201px) and (max-width: 1600px) { 1466 .customize-control.customize-control-theme { 1467 width: 32%; 1468 } 1469 1470 .customize-control.customize-control-theme:nth-child(3n) { 1471 margin-right: 0; 1472 } 1473 } 1474 1475 /* 2 columns up to 1200px */ 1476 @media screen and (min-width: 851px) and (max-width: 1200px) { 1477 .customize-control.customize-control-theme { 1478 width: 49%; 1479 } 1480 1481 .customize-control.customize-control-theme:nth-child(even) { 1482 margin-right: 0; 1483 } 1484 } 1485 1486 /* 1 column up to 850 px */ 1487 @media screen and (max-width: 850px) { 1488 .customize-control.customize-control-theme { 1489 width: 100%; 1490 margin: 0 0 3% 0; 1491 } 1492 } 1493 1494 .wp-customizer .theme-browser .themes { 1495 padding-bottom: 8px; 1496 } 1497 1095 1498 .wp-customizer .theme-browser .theme .theme-actions { 1096 -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";1097 1499 opacity: 1; 1098 1500 } 1099 1501 … … 1112 1514 width: 100%; 1113 1515 } 1114 1516 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;1122 }1123 1124 1517 /* Details View */ 1125 1518 .wp-customizer .theme-overlay { 1126 1519 display: none; … … 1135 1528 z-index: 109; 1136 1529 } 1137 1530 1531 /* Avoid a z-index war by resetting elements that should be under the overlay. 1532 This is likely required because of the way that sections and panels are positioned. */ 1533 .wp-customizer.modal-open #customize-header-actions, 1534 .wp-customizer.modal-open .control-panel-themes .filter-themes-count, 1535 .wp-customizer.modal-open .control-panel-themes .customize-themes-section-title.selected:after { 1536 z-index: -1; 1537 } 1538 1138 1539 .wp-customizer .theme-overlay .theme-backdrop { 1139 1540 background: rgba( 238, 238, 238, 0.75 ); 1140 1541 position: fixed; … … 1141 1542 z-index: 110; 1142 1543 } 1143 1544 1545 .wp-customizer .theme-overlay .star-rating { 1546 float: left; 1547 margin-right: 8px; 1548 } 1549 1550 .wp-customizer .theme-rating .num-ratings { 1551 line-height: 20px; 1552 } 1553 1144 1554 .wp-customizer .theme-overlay .theme-wrap { 1145 1555 left: 90px; 1146 1556 right: 90px; … … 1147 1557 top: 45px; 1148 1558 bottom: 45px; 1149 1559 z-index: 120; 1150 max-width: 1740px; /* To ensure that theme screenshots are not displayed larger than 880px wide. */1151 1560 } 1152 1561 1153 1562 .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. */ 1563 text-align: right; /* Because there're only one or two actions, match the UI pattern of media modals and right-align the action. */ 1564 padding: 10px 15px; 1155 1565 } 1156 1566 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; 1567 .wp-customizer .theme-overlay .theme-actions .theme-install.preview { 1568 margin-left: 8px; 1161 1569 } 1162 1570 1571 .control-panel-themes .theme-actions .delete-theme { 1572 left: 15px; /* these override themes.css on mobile */ 1573 right: auto; 1574 bottom: auto; 1575 position: absolute; 1576 } 1577 1578 .modal-open .in-themes-panel #customize-controls .wp-full-overlay-sidebar-content { 1579 overflow: visible; /* Prevent the top-level Customizer controls from becoming visible when elements on the right of the details modal are focused. */ 1580 } 1581 1582 1163 1583 /* Small Screens */ 1164 1584 @media (max-width:850px), (max-height:472px) { 1165 1585 .wp-customizer .theme-overlay .theme-wrap { -
src/wp-admin/css/themes.css
570 570 float: left; 571 571 margin: 0 30px 0 0; 572 572 width: 55%; 573 max-width: 880px;573 max-width: 1200px; /* Recommended theme screenshot width, set here to avoid stretching */ 574 574 text-align: center; 575 575 } 576 576 -
src/wp-admin/includes/theme.php
606 606 * @since 4.2.0 607 607 */ 608 608 function customize_themes_print_templates() { 609 $preview_url = esc_url( add_query_arg( 'theme', '__THEME__' ) ); // Token because esc_url() strips curly braces. 609 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); 610 $active_url = esc_url( remove_query_arg( 'theme', $current_url ) ); 611 $preview_url = esc_url( add_query_arg( 'theme', '__THEME__', $current_url ) ); // Token because esc_url() strips curly braces. 610 612 $preview_url = str_replace( '__THEME__', '{{ data.id }}', $preview_url ); 611 613 ?> 612 614 <script type="text/html" id="tmpl-customize-themes-details-view"> … … 619 621 </div> 620 622 <div class="theme-about wp-clearfix"> 621 623 <div class="theme-screenshots"> 622 <# if ( data.screenshot [0] ) { #>624 <# if ( data.screenshot && data.screenshot[0] ) { #> 623 625 <div class="screenshot"><img src="{{ data.screenshot[0] }}" alt="" /></div> 624 626 <# } else { #> 625 627 <div class="screenshot blank"></div> … … 632 634 <# } #> 633 635 <h2 class="theme-name">{{{ data.name }}}<span class="theme-version"><?php printf( __( 'Version: %s' ), '{{ data.version }}' ); ?></span></h2> 634 636 <h3 class="theme-author"><?php printf( __( 'By %s' ), '{{{ data.authorAndUri }}}' ); ?></h3> 637 638 <# if ( data.stars && 0 != data.num_ratings ) { #> 639 <div class="theme-rating"> 640 {{{ data.stars }}} 641 <span class="num-ratings"><?php echo sprintf( __( '(%s ratings)' ), '{{ data.num_ratings }}' ); ?></span> 642 </div> 643 <# } #> 644 645 <# if ( data.hasUpdate ) { #> 646 <div class="notice notice-warning notice-alt notice-large" data-slug="{{ data.id }}"> 647 <h3 class="notice-title"><?php _e( 'Update Available' ); ?></h3> 648 {{{ data.update }}} 649 </div> 650 <# } #> 651 635 652 <p class="theme-description">{{{ data.description }}}</p> 636 653 637 654 <# if ( data.parent ) { #> 638 655 <p class="parent-theme"><?php printf( __( 'This is a child theme of %s.' ), '<strong>{{{ data.parent }}}</strong>' ); ?></p> 639 656 <# } #> 640 641 657 <# if ( data.tags ) { #> 642 <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{ data.tags}}</p>658 <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{{ data.tags }}}</p> 643 659 <# } #> 644 660 </div> 645 661 </div> 646 662 647 <# if ( ! data.active ) { #> 648 <div class="theme-actions"> 649 <div class="inactive-theme"> 650 <?php 651 /* translators: %s: Theme name */ 652 $aria_label = sprintf( __( 'Preview %s' ), '{{ data.name }}' ); 653 ?> 654 <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> 655 </div> 656 </div> 657 <# } #> 663 <div class="theme-actions"> 664 <# if ( data.active ) { #> 665 <?php 666 /* translators: %s: Theme name */ 667 $aria_label = sprintf( __( 'Customize %s' ), '{{ data.name }}' ); 668 ?> 669 <button type="button" class="button button-primary customize-theme" aria-label="<?php echo esc_attr( $aria_label ); ?>"><?php _e( 'Customize' ); ?></a> 670 <# } else if ( 'installed' === data.type ) { #> 671 <?php if ( current_user_can( 'delete_themes' ) ) { ?> 672 <# if ( data.actions && data.actions['delete'] ) { #> 673 <a href="{{{ data.actions['delete'] }}}" data-slug="{{ data.id }}" class="button button-secondary delete-theme"><?php _e( 'Delete' ); ?></a> 674 <# } #> 675 <?php } ?> 676 <button type="button" class="button button-primary preview-theme" data-preview-url="<?php echo esc_attr( $preview_url ); ?>" aria-label="<?php echo esc_attr( $aria_label ); ?>"><?php _e( 'Live Preview' ); ?></span> 677 <# } else { #> 678 <button type="button" class="button theme-install" data-slug="{{ data.id }}"><?php _e( 'Install' ); ?></button> 679 <button type="button" class="button button-primary theme-install preview" data-slug="{{ data.id }}" data-previewurl="<?php echo esc_attr( $preview_url ); ?>"><?php _e( 'Install & Preview' ); ?></button> 680 <# } #> 681 </div> 658 682 </div> 659 683 </script> 660 684 <?php -
src/wp-admin/js/customize-controls.js
2 2 (function( exports, $ ){ 3 3 var Container, focus, normalizedTransitionendEventName, api = wp.customize; 4 4 5 window.pagenow = 'customize'; // Needed for updates.js to function properly. 6 5 7 /** 6 8 * A Customizer Setting. 7 9 * … … 866 868 /** 867 869 * wp.customize.ThemesSection 868 870 * 869 * Custom section for themes that functions similarly to a backwards panel,870 * and alsohandles the theme-details view rendering and navigation.871 * Custom section for themes that loads themes by category, and also 872 * handles the theme-details view rendering and navigation. 871 873 * 872 874 * @constructor 873 875 * @augments wp.customize.Section … … 879 881 template: '', 880 882 screenshotQueue: null, 881 883 $window: $( window ), 884 loaded: 0, 885 loading: false, 886 fullyLoaded: false, 887 term: '', 888 filterContainer: $(), 882 889 883 890 /** 884 * @since 4.2.0 891 * Embed the section in the DOM when the themes panel is ready. 892 * 893 * Insert the section before the themes container. Assume that a themes section is within a panel, but not necessarily the themes panel. 894 * 895 * @since 4.7.0 885 896 */ 886 initialize: function () { 887 this.$customizeSidebar = $( '.wp-full-overlay-sidebar-content:first' ); 888 return api.Section.prototype.initialize.apply( this, arguments ); 897 embed: function () { 898 var inject, 899 section = this, 900 container = $( '#customize-theme-controls' ); 901 902 // Watch for changes to the panel state 903 inject = function ( panelId ) { 904 var parentContainer; 905 api.panel( panelId, function ( panel ) { 906 // The panel has been registered, wait for it to become ready/initialized 907 panel.deferred.embedded.done( function () { 908 parentContainer = panel.contentContainer; 909 if ( ! section.headContainer.parent().is( parentContainer ) ) { 910 parentContainer.find( '.customize-themes-full-container-container' ).before( section.headContainer ); 911 } 912 if ( ! section.contentContainer.parent().is( section.headContainer ) ) { 913 container.append( section.contentContainer ); 914 } 915 section.deferred.embedded.resolve(); 916 }); 917 } ); 918 }; 919 section.panel.bind( inject ); 920 inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one 889 921 }, 890 922 891 923 /** … … 918 950 } 919 951 }); 920 952 921 _.bindAll( this, 'renderScreenshots' );953 _.bindAll( this, 'renderScreenshots', 'loadMore', 'checkTerm', 'filtersChecked' ); 922 954 }, 923 955 924 956 /** … … 925 957 * Override Section.isContextuallyActive method. 926 958 * 927 959 * Ignore the active states' of the contained theme controls, and just 928 * use the section's own active state instead. This ensures empty search929 * results for theme s to causethe section to become inactive.960 * use the section's own active state instead. This prevents empty search 961 * results for theme sections from causing the section to become inactive. 930 962 * 931 963 * @since 4.2.0 932 964 * … … 942 974 attachEvents: function () { 943 975 var section = this; 944 976 945 // Expand/Collapse section/panel. 946 section.container.find( '.change-theme, .customize-theme' ).on( 'click keydown', function( event ) { 947 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 948 return; 949 } 950 event.preventDefault(); // Keep this AFTER the key filter above 977 section.filterContainer = $( '#accordion-section-' + section.id ); 951 978 952 if ( section.expanded() ) { 953 section.collapse(); 954 } else { 979 // Expand section/panel. Only collapse when opening another section. 980 section.filterContainer.on( 'click', '.customize-themes-section-title', function( event ) { 981 // Open the section. 982 if ( ! section.expanded() ) { 955 983 section.expand(); 956 984 } 957 });958 985 959 // Theme navigation in details view. 960 section.container.on( 'click keydown', '.left', function( event ) { 961 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 962 return; 986 // Toggle filters. 987 if ( section.filterContainer.find( '.filter-details' ).length ) { 988 section.filterContainer.find( '.customize-themes-section-title' ).toggleClass( 'details-open' ) 989 .attr('aria-expanded', function ( i, attr ) { 990 return attr === 'true' ? 'false' : 'true'; 991 }); 992 section.filterContainer.find( '.filter-details' ).slideToggle( 180 ); 963 993 } 994 }); 964 995 965 event.preventDefault(); // Keep this AFTER the key filter above 996 // Preview installed themes. 997 section.container.on( 'click', '.theme-actions .preview-theme', function() { 998 var previewUrl = $( this ).data( 'previewUrl' ); 966 999 1000 $( '.wp-full-overlay' ).addClass( 'customize-loading' ); 1001 1002 window.parent.location = previewUrl; 1003 }); 1004 1005 // Theme navigation in details view. 1006 section.container.on( 'click', '.left', function( event ) { 967 1007 section.previousTheme(); 968 1008 }); 969 1009 970 section.container.on( 'click keydown', '.right', function( event ) { 971 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 972 return; 973 } 974 975 event.preventDefault(); // Keep this AFTER the key filter above 976 1010 section.container.on( 'click', '.right', function( event ) { 977 1011 section.nextTheme(); 978 1012 }); 979 1013 980 section.container.on( 'click keydown', '.theme-backdrop, .close', function( event ) { 981 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 982 return; 983 } 984 985 event.preventDefault(); // Keep this AFTER the key filter above 986 1014 section.container.on( 'click', '.theme-backdrop, .close', function( event ) { 987 1015 section.closeDetails(); 988 1016 }); 989 1017 990 1018 var renderScreenshots = _.throttle( _.bind( section.renderScreenshots, this ), 100 ); 991 section.container.on( 'input', '#themes-filter', function( event ) { 1019 1020 // Only used when there is only one section - installed themes. 1021 $( '.control-panel-themes' ).on( 'input', '#themes-filter', function( event ) { 992 1022 var count, 993 1023 term = event.currentTarget.value.toLowerCase().trim().replace( '-', ' ' ), 994 1024 controls = section.controls(); … … 1000 1030 renderScreenshots(); 1001 1031 1002 1032 // Update theme count. 1003 count = section.cont ainer.find( 'li.customize-control:visible' ).length;1004 section.container.find( '.theme-count' ).text( count );1033 count = section.contentContainer.find( 'li.customize-control:visible' ).length; 1034 $( '.control-panel-themes' ).find( '.theme-count' ).text( count ); 1005 1035 }); 1006 1036 1007 // Pre-load the first 3 theme screenshots.1008 api.bind( 'ready', function () {1009 _.each( section.controls().slice( 0, 3 ), function ( control ) {1010 var img, src = control.params.theme.screenshot[0];1011 if ( src ) {1012 img = new Image();1013 img.src = src;1037 // Event listeners for queries with user-entered terms. 1038 if ( 'search' === section.params.action ) { 1039 var debounced = _.debounce( section.checkTerm, 500 ); // Wait until there is no input for 500 miliseconds to initiate a search. 1040 $( '.control-panel-themes' ).on( 'input', '#wp-filter-search-input', function() { 1041 debounced( section ); 1042 if ( ! section.expanded() ) { 1043 section.expand(); 1014 1044 } 1015 1045 }); 1046 1047 // Focus the input if the icon is clicked. 1048 section.filterContainer.find( '.search-form' ).on( 'click', function( e ) { 1049 if ( ! $( e.currentTarget ).hasClass( 'wp-filter-search' ) ) { 1050 $( e.currentTarget ).find( '.wp-filter-search' ).focus(); 1051 } 1052 }); 1053 } else if ( 'favorites' === section.params.action ) { 1054 section.container.on( 'click', '.favorites-form-submit', function() { 1055 section.checkTerm( section ); 1056 }); 1057 section.container.on( 'keydown', '#wporg-username-input', function( e ) { 1058 if ( api.utils.isKeydownButNotEnterEvent( e ) ) { 1059 return; 1060 } 1061 section.checkTerm( section ); 1062 }); 1063 } else if ( 'feature_filter' === section.params.action ) { 1064 section.container.on( 'click', '.filter-group input', function() { 1065 section.filtersChecked(); 1066 section.checkTerm( section ); 1067 }); 1068 1069 // Toggle feature filter sections. 1070 section.container.on( 'click', '.filter-group legend button', function( e ) { 1071 $( e.currentTarget ).toggleClass( 'open' ).attr('aria-expanded', function ( i, attr ) { 1072 return attr === 'true' ? 'false' : 'true'; 1073 }) 1074 .parent().next( '.filter-group-feature' ).slideToggle( 180 ); 1075 }); 1076 } 1077 1078 // Move section controls to the themes area. 1079 api.bind( 'ready', function () { 1080 section.contentContainer = section.container.find( '.customize-themes-section' ); 1081 section.contentContainer.appendTo( $( '.customize-themes-full-container' ) ); 1082 section.container.add( section.filterContainer ); 1016 1083 }); 1017 1084 }, 1018 1085 … … 1037 1104 } 1038 1105 1039 1106 // Note: there is a second argument 'args' passed 1040 var panel = this, 1041 section = panel.contentContainer, 1042 overlay = section.closest( '.wp-full-overlay' ), 1043 container = section.closest( '.wp-full-overlay-sidebar-content' ), 1044 customizeBtn = section.find( '.customize-theme' ), 1045 changeBtn = panel.headContainer.find( '.change-theme' ); 1107 var section = this, 1108 container = section.contentContainer.closest( '.customize-themes-full-container' ); 1046 1109 1047 if ( expanded && ! section.hasClass( 'current-panel' ) ) { 1110 if ( expanded ) { 1111 1112 if ( -1 !== $.inArray( section.params.action, [ 'search', 'favorites', 'feature_filter' ] ) && '' === section.term ) { 1113 section.collapse(); // Note that the current section hasn't been collapsed yet, so this is all we need to do to do nothing. 1114 return; // Don't expand to an empty section that can't load any themes. 1115 } 1116 1117 // Load controls if none are loaded yet. 1118 if ( 0 === section.loaded ) { 1119 section.loadControls(); 1120 } 1121 1048 1122 // Collapse any sibling sections/panels 1049 1123 api.section.each( function ( otherSection ) { 1050 if ( otherSection !== panel) {1124 if ( otherSection !== section ) { 1051 1125 otherSection.collapse( { duration: args.duration } ); 1052 1126 } 1053 1127 }); 1054 api.panel.each( function ( otherPanel ) {1055 otherPanel.collapse( { duration: 0 } );1056 });1057 1128 1058 panel._animateChangeExpanded( function() {1059 changeBtn.attr( 'tabindex', '-1');1060 customizeBtn.attr( 'tabindex', '0' );1129 section.contentContainer.addClass( 'current-section' ); 1130 container.scrollTop(); 1131 section.filterContainer.find( '.customize-themes-section-title' ).addClass( 'selected' ); 1061 1132 1062 customizeBtn.focus(); 1063 section.css( 'top', '' ); 1064 container.scrollTop( 0 ); 1133 container.on( 'scroll', _.throttle( section.renderScreenshots, 300 ) ); 1134 container.on( 'scroll', _.throttle( section.loadMore, 300 ) ); 1065 1135 1066 if ( args.completeCallback ) { 1067 args.completeCallback(); 1068 } 1069 } ); 1136 if ( args.completeCallback ) { 1137 args.completeCallback(); 1138 } 1139 section.updateCount(); // Show this section's count. 1140 } else { 1141 section.contentContainer.removeClass( 'current-section' ); 1070 1142 1071 overlay.addClass( 'in-themes-panel' ); 1072 section.addClass( 'current-panel' ); 1143 // Always hide, even if they don't exist or are already hidden. 1144 section.filterContainer.find( '.customize-themes-section-title' ).removeClass( 'selected details-open' ).attr( 'aria-expanded', 'false' ); 1145 section.filterContainer.find( '.filter-details' ).slideUp( 180 ); 1073 1146 1074 } else if ( ! expanded && section.hasClass( 'current-panel' ) ) { 1075 panel._animateChangeExpanded( function() { 1076 changeBtn.attr( 'tabindex', '0' ); 1077 customizeBtn.attr( 'tabindex', '-1' ); 1147 container.off( 'scroll' ); 1078 1148 1079 changeBtn.focus(); 1080 section.css( 'top', '' ); 1149 if ( args.completeCallback ) { 1150 args.completeCallback(); 1151 } 1152 } 1153 }, 1081 1154 1082 if ( args.completeCallback ) { 1083 args.completeCallback(); 1155 /** 1156 * Return the section's content element without detachng from the parent. 1157 * 1158 * @since 4.7.0 1159 */ 1160 getContent: function() { 1161 return this.container.find( '.control-section-content' ); 1162 }, 1163 1164 /** 1165 * Load theme data via ajax and add themes to the section as controls. 1166 * 1167 * @since 4.7.0 1168 */ 1169 loadControls: function() { 1170 var section = this, params, page, request, search; 1171 1172 if ( section.loading ) { 1173 return; // We're already loading a batch of themes. 1174 } 1175 1176 // Parameters for every API query. Additional params are set in PHP. 1177 page = Math.ceil( section.loaded / 100 ) + 1; 1178 params = { 1179 'customize-themes-nonce': api.settings.nonce['customize-themes'], 1180 'wp_customize': 'on', 1181 'theme_action': section.params.action, 1182 'customized_theme': api.settings.theme.stylesheet, 1183 'page': page 1184 } 1185 1186 // Add fields for special request actions. 1187 if ( 'search' === section.params.action ) { 1188 if ( '' === section.term ) { 1189 return; 1190 } else { 1191 params.search = section.term; 1192 } 1193 } else if ( 'favorites' === section.params.action ) { 1194 if ( '' === section.term ) { 1195 return; 1196 } else { 1197 params.user = section.term; 1198 } 1199 } else if ( 'feature_filter' === section.params.action ) { 1200 if ( '' === section.term ) { 1201 return; 1202 } else { 1203 params.tags = section.term; 1204 } 1205 } 1206 1207 // Load themes. 1208 section.headContainer.closest( '.wp-full-overlay' ).addClass( 'loading' ); 1209 section.loading = true; 1210 section.container.find( '.no-themes' ).hide(); 1211 request = wp.ajax.post( 'customize-load-themes', params ); 1212 request.done(function( data ) { 1213 var themes = data.themes, 1214 themeControl, newThemeControls; 1215 if ( 0 !== themes.length ) { 1216 newThemeControls = new Array(); 1217 // Add controls for each theme. 1218 _.each( themes, function ( theme ) { 1219 customizeId = section.params.action + '_theme_' + theme.id; 1220 themeControl = new api.controlConstructor.theme( customizeId, { 1221 params: { 1222 type: 'theme', 1223 content: '<li id="customize-control-theme-' + section.params.action + '_' + theme.id + '" class="customize-control customize-control-theme"></li>', 1224 section: section.params.id, 1225 active: true, 1226 theme: theme, 1227 priority: section.loaded + 1 1228 }, 1229 previewer: api.previewer 1230 } ); 1231 1232 api.control.add( customizeId, themeControl ); 1233 newThemeControls.push( themeControl ); 1234 section.loaded = section.loaded + 1; 1235 return; 1236 }); 1237 1238 if ( 1 === page ) { 1239 // Pre-load the first 3 theme screenshots. 1240 _.each( section.controls().slice( 0, 3 ), function ( control ) { 1241 var img, src = control.params.theme.screenshot[0]; 1242 if ( src ) { 1243 img = new Image(); 1244 img.src = src; 1245 } 1246 }); 1247 if ( 'search' === section.params.action ) { 1248 wp.a11y.speak( api.settings.l10n.themeSearchResults.replace( '%d', data.info.results ) ); 1249 } 1250 } else { 1251 Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue. 1084 1252 } 1085 } );1253 _.delay( section.renderScreenshots, 100 ); // Wait for the controls to become visible. 1086 1254 1087 overlay.removeClass( 'in-themes-panel' ); 1088 section.removeClass( 'current-panel' ); 1255 if ( 'installed' === section.action || 100 > themes.length ) { // If we have less than the requested 100 themes, it's the end of the list. 1256 section.fullyLoaded = true; 1257 } 1258 } else { 1259 if ( 0 === section.loaded ) { 1260 section.container.find( '.no-themes' ).show(); 1261 wp.a11y.speak( section.container.find( '.no-themes' ).text() ); 1262 } else { 1263 section.fullyLoaded = true; 1264 } 1265 } 1266 if ( 'installed' === section.params.action ) { 1267 section.updateCount(); 1268 } else { 1269 section.updateCount( data.info.results ); 1270 } 1271 section.container.find( '.unexpected-error' ).hide(); // Hide error notice in case it was previously shown. 1272 1273 // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case. 1274 section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' ); 1275 section.loading = false; 1276 }); 1277 request.fail(function( data ) { 1278 if ( 'undefined' === typeof data ) { 1279 section.container.find( '.unexpected-error' ).show(); 1280 wp.a11y.speak( section.container.find( '.unexpected-error' ).text() ); 1281 } else if ( typeof console !== 'undefined' && console.error ) { 1282 console.error( data ); 1283 } 1284 1285 // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case. 1286 section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' ); 1287 section.loading = false; 1288 }); 1289 }, 1290 1291 /** 1292 * Determines whether more themes should be loaded, and loads them. 1293 * 1294 * @since 4.7.0 1295 */ 1296 loadMore: function() { 1297 var section = this, container, bottom, threshold, page; 1298 if ( ! section.fullyLoaded && ! section.loading ) { 1299 container = section.container.closest( '.customize-themes-full-container' ); 1300 1301 bottom = container.scrollTop() + container.height(); 1302 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. 1303 1304 if ( bottom > threshold ) { 1305 section.loadControls(); 1306 } 1089 1307 } 1090 1308 }, 1091 1309 1092 1310 /** 1311 * Event handler for search, feature filter, and favorites input that determines if the term has changed and loads new controls as needed. 1312 * 1313 * @since 4.7.0 1314 * 1315 * @param api.ThemesSection section The current theme section, passed through the debouncer. 1316 */ 1317 checkTerm: function( section ) { 1318 var newTerm, filteringBy; 1319 1320 // Find term. 1321 if ( 'search' === section.params.action ) { 1322 newTerm = $( '#wp-filter-search-input' ).val(); 1323 } else if ( 'favorites' === section.params.action ) { 1324 newTerm = $( '#wporg-username-input' ).val(); 1325 } else if ( 'feature_filter' === section.params.action ) { 1326 newTerm = section.term; // Set separately by filtersChecked(), as they're changed. 1327 if ( '' === newTerm ) { 1328 return; 1329 } 1330 } else { 1331 return; 1332 } 1333 1334 if ( section.term === newTerm && 'feature_filter' !== section.params.action ) { 1335 return; 1336 } 1337 // Clear the controls in the section. 1338 _.each( section.controls(), function( control ) { 1339 control.container.remove(); 1340 api.control.remove( control.id ); 1341 }); 1342 section.loaded = 0; 1343 section.fullyLoaded = false; 1344 section.screenshotQueue = null; 1345 1346 if ( '' !== newTerm ) { // Empty term should not show any results. 1347 // Run a new query, with loadControls handling paging, etc. 1348 section.term = newTerm; 1349 section.loadControls(); 1350 if ( ! section.expanded() ) { 1351 section.expand(); // Expand the section if it isn't expanded. 1352 } 1353 } 1354 }, 1355 1356 /** 1357 * Check for filters checked in the feature filter list. 1358 * 1359 * @since 4.7.0 1360 */ 1361 filtersChecked: function() { 1362 var section = this, 1363 items = section.container.find( '.filter-group' ).find( ':checkbox' ), 1364 tags = []; 1365 1366 if ( 'feature_filter' !== section.params.action ) { 1367 return false; 1368 } 1369 1370 _.each( items.filter( ':checked' ), function( item ) { 1371 tags.push( $( item ).prop( 'value' ) ); 1372 }); 1373 1374 // When no filters are checked, restore initial state and return 1375 if ( tags.length === 0 ) { 1376 section.term = ''; 1377 } else { 1378 section.term = tags; 1379 } 1380 }, 1381 1382 /** 1093 1383 * Render control's screenshot if the control comes into view. 1094 1384 * 1095 1385 * @since 4.2.0 … … 1097 1387 renderScreenshots: function( ) { 1098 1388 var section = this; 1099 1389 1100 // Fill queue initially. 1101 if ( section.screenshotQueue === null ) { 1102 section.screenshotQueue = section.controls(); 1390 // Fill queue initially, or check for more if empty. 1391 if ( section.screenshotQueue === null || 0 === section.screenshotQueue.length ) { 1392 // Add controls that haven't had their screenshots rendered. 1393 section.screenshotQueue = _.filter( section.controls(), function( control ) { 1394 return ! control.screenshotRendered; 1395 }); 1103 1396 } 1104 1397 1105 // Are all screenshots rendered ?1398 // Are all screenshots rendered (for now)? 1106 1399 if ( ! section.screenshotQueue.length ) { 1107 1400 return; 1108 1401 } … … 1138 1431 }, 1139 1432 1140 1433 /** 1434 * Update the number of themes in the section. 1435 * 1436 * @since 4.7.0 1437 */ 1438 updateCount: function ( count ) { 1439 if ( ! count ) { 1440 count = this.loaded; 1441 } 1442 1443 var displayed = this.container.closest( '.control-panel-content' ).find( '.themes-displayed' ), 1444 countEl = this.container.closest( '.control-panel-content' ).find( '.theme-count' ); 1445 1446 if ( 0 === count ) { 1447 countEl.text( count ); 1448 } else { 1449 // Animate the count change for emphasis. 1450 displayed.fadeOut( 180, function() { 1451 countEl.text( count ); 1452 displayed.fadeIn( 180 ); 1453 } ); 1454 } 1455 }, 1456 1457 /** 1141 1458 * Advance the modal to the next theme. 1142 1459 * 1143 1460 * @since 4.2.0 … … 1157 1474 * @since 4.2.0 1158 1475 */ 1159 1476 getNextTheme: function () { 1160 var control, next;1161 control = api.control( 'theme_' + this.currentTheme );1477 var section = this, control, next; 1478 control = api.control( section.params.action + '_theme_' + this.currentTheme ); 1162 1479 next = control.container.next( 'li.customize-control-theme' ); 1163 1480 if ( ! next.length ) { 1164 1481 return false; 1165 1482 } 1166 next = next[0].id.replace( 'customize-control- ', '' );1483 next = next[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' ); 1167 1484 control = api.control( next ); 1168 1485 1169 1486 return control.params.theme; … … 1189 1506 * @since 4.2.0 1190 1507 */ 1191 1508 getPreviousTheme: function () { 1192 var control, previous;1193 control = api.control( 'theme_' + this.currentTheme );1509 var section = this, control, previous; 1510 control = api.control( section.params.action + '_theme_' + this.currentTheme ); 1194 1511 previous = control.container.prev( 'li.customize-control-theme' ); 1195 1512 if ( ! previous.length ) { 1196 1513 return false; 1197 1514 } 1198 previous = previous[0].id.replace( 'customize-control- ', '' );1515 previous = previous[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' ); 1199 1516 control = api.control( previous ); 1200 1517 1201 1518 return control.params.theme; … … 1243 1560 closeDetails: function () { 1244 1561 $( 'body' ).removeClass( 'modal-open' ); 1245 1562 this.overlay.fadeOut( 'fast' ); 1246 api.control( 'theme_' + this.currentTheme).focus();1563 api.control( this.params.action + '_theme_' + this.currentTheme ).container.find( '.theme' ).focus(); 1247 1564 }, 1248 1565 1249 1566 /** … … 1323 1640 } 1324 1641 if ( ! panel.contentContainer.parent().is( panel.headContainer ) ) { 1325 1642 container.append( panel.contentContainer ); 1326 panel.renderContent();1327 1643 } 1644 panel.renderContent(); 1328 1645 1329 1646 panel.deferred.embedded.resolve(); 1330 1647 }, … … 1513 1830 } 1514 1831 }); 1515 1832 1833 1516 1834 /** 1835 * wp.customize.ThemesPanel 1836 * 1837 * Custom section for themes that displays without the customize preview. 1838 * 1839 * @constructor 1840 * @augments wp.customize.Panel 1841 * @augments wp.customize.Container 1842 */ 1843 api.ThemesPanel = api.Panel.extend({ 1844 installingThemes: [], 1845 1846 /** 1847 * @since 4.7.0 1848 */ 1849 attachEvents: function () { 1850 var panel = this; 1851 1852 // Attach regular panel events. 1853 api.Panel.prototype.attachEvents.apply( this ); 1854 1855 // Collapse panel to customize the current theme. 1856 panel.contentContainer.on( 'click', '.customize-theme', function( event ) { 1857 panel.collapse(); 1858 }); 1859 1860 // Toggle between filtering and browsing themes on mobile. 1861 panel.contentContainer.on( 'click', '.see-themes, .filter-themes', function( event ) { 1862 $( '.wp-full-overlay' ).toggleClass( 'showing-themes' ); 1863 }); 1864 1865 // Hide unsaved changes notice on save. 1866 api.bind( 'saved', function() { 1867 panel.container.find( '.customize-themes-unsaved-changes' ).hide(); 1868 }); 1869 1870 // Save & publish customized settings. 1871 panel.contentContainer.on( 'click', '#customize-themes-save', function() { 1872 $( '#save' ).click(); // Trigger customizer save. 1873 panel.container.find( '.customize-themes-unsaved-changes' ).hide(); 1874 api.section( 'installed_themes' ).focus(); 1875 }); 1876 1877 // Install (and maybe preview) a theme. 1878 panel.contentContainer.on( 'click', '.theme-install', function( event ) { 1879 panel.installTheme( event ); 1880 }); 1881 1882 // Update a theme. Theme cards have the class, the details modal has the id. 1883 panel.contentContainer.on( 'click', '.update-theme, #update-theme', function( event ) { 1884 // #update-theme is a link. 1885 event.preventDefault(); 1886 event.stopPropagation(); 1887 1888 panel.updateTheme( event ); 1889 }); 1890 1891 // Delete a theme. 1892 panel.contentContainer.on( 'click', '.delete-theme', function( event ) { 1893 panel.deleteTheme( event ); 1894 }); 1895 1896 _.bindAll( this, 'installTheme', 'updateTheme' ); 1897 }, 1898 1899 /** 1900 * Update UI to reflect expanded state 1901 * 1902 * @since 4.7.0 1903 * 1904 * @param {Boolean} expanded 1905 * @param {Object} args 1906 * @param {Boolean} args.unchanged 1907 * @param {Callback} args.completeCallback 1908 */ 1909 onChangeExpanded: function ( expanded, args ) { 1910 1911 // Expand/collapse the panel normally. 1912 api.Panel.prototype.onChangeExpanded.apply( this, [ expanded, args ] ); 1913 1914 // Immediately call the complete callback if there were no changes 1915 if ( args.unchanged ) { 1916 if ( args.completeCallback ) { 1917 args.completeCallback(); 1918 } 1919 return; 1920 } 1921 1922 // Note: there is a second argument 'args' passed 1923 var panel = this, 1924 overlay = panel.headContainer.closest( '.wp-full-overlay' ); 1925 1926 if ( expanded ) { 1927 overlay.addClass( 'in-themes-panel' ).addClass( 'showing-themes' ) 1928 .delay( 200 ).find( '.customize-themes-full-container' ).addClass( 'animate' ); 1929 1930 if ( false === api.state( 'saved' ).get() ) { 1931 panel.container.find( '.customize-themes-unsaved-changes' ).show(); 1932 } 1933 1934 // Automatically open the installed themes section. 1935 api.section( 'installed_themes' ).expand(); 1936 } else { 1937 overlay.removeClass( 'in-themes-panel' ) 1938 .find( '.customize-themes-full-container' ).removeClass( 'animate' ); 1939 } 1940 }, 1941 1942 /** 1943 * Install a theme via wp.updates. 1944 * 1945 * @since 4.7.0 1946 */ 1947 installTheme: function( event ) { 1948 var preview = false, previewUrl, 1949 slug = $( event.target ).data( 'slug' ); 1950 1951 if ( -1 !== $.inArray( this.installingThemes, slug ) ) { 1952 return; // Theme is already being installed. 1953 } 1954 1955 wp.updates.maybeRequestFilesystemCredentials( event ); 1956 1957 $( document ).on( 'wp-theme-install-success', function( event, response ) { 1958 var theme = false, customizeId, themeControl; 1959 if ( preview ) { 1960 window.parent.location = previewUrl; 1961 } else { 1962 api.control.each( function( control ) { 1963 if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) { 1964 theme = control.params.theme; // Used below to add theme control. 1965 control.rerenderAsInstalled( true ); 1966 } 1967 }); 1968 1969 // Don't add the same theme more than once. 1970 if ( ! theme || 'undefined' !== typeof api.control( 'installed_theme_' + theme.id ) ) { 1971 return; 1972 } 1973 1974 // Add theme control to installed section. 1975 theme.type = 'installed'; 1976 customizeId = 'installed_theme_' + theme.id; 1977 themeControl = new api.controlConstructor.theme( customizeId, { 1978 params: { 1979 type: 'theme', 1980 content: '<li id="customize-control-theme-installed_' + theme.id + '" class="customize-control customize-control-theme"></li>', 1981 section: 'installed_themes', 1982 active: true, 1983 theme: theme, 1984 priority: 0 // Add all newly-installed themes to the top. 1985 }, 1986 previewer: api.previewer 1987 } ); 1988 1989 api.control.add( customizeId, themeControl ); 1990 api.control( customizeId ).container.trigger( 'render-screenshot' ); 1991 1992 // Close the details modal if it's open to the installed theme. 1993 api.section.each( function( section ) { 1994 if ( 'themes' === section.params.type ) { 1995 if ( theme.id === section.currentTheme ) { // Don't close the modal if the user has navigated elsewhere. 1996 section.closeDetails(); 1997 } 1998 } 1999 }); 2000 } 2001 } ); 2002 2003 $( document ).on( 'wp-theme-install-failure', function( event, response ) { 2004 if ( preview ) { 2005 $( '.wp-full-overlay' ).removeClass( 'customize-loading' ); 2006 } 2007 }); 2008 2009 this.installingThemes.push( $( event.target ).data( 'slug' ) ); // Note: we don't remove elements from installingThemes, since they shouldn't be installed again. 2010 wp.updates.installTheme( { 2011 slug: $( event.target ).data( 'slug' ) 2012 } ); 2013 2014 // Also preview the theme as the event is triggered on Install & Preview. 2015 if ( $( event.target ).hasClass( 'preview' ) ) { 2016 preview = true; 2017 $( '.wp-full-overlay' ).addClass( 'customize-loading' ); 2018 previewUrl = $( event.target ).data( 'previewurl' ); 2019 } 2020 }, 2021 2022 /** 2023 * Update a theme via wp.updates. 2024 * 2025 * @since 4.7.0 2026 */ 2027 updateTheme: function( event ) { 2028 wp.updates.maybeRequestFilesystemCredentials( event ); 2029 2030 $( document ).on( 'wp-theme-update-success', function( event, response ) { 2031 // Rerender the control to reflect the update. 2032 api.control.each( function( control ) { 2033 if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) { 2034 control.params.theme.hasUpdate = false; 2035 control.rerenderAsInstalled( true ); 2036 } 2037 }); 2038 } ); 2039 2040 wp.updates.updateTheme( { 2041 slug: $( event.target ).closest( '.notice' ).data( 'slug' ) 2042 } ); 2043 }, 2044 2045 /** 2046 * Delete a theme via wp.updates. 2047 * 2048 * @since 4.7.0 2049 */ 2050 deleteTheme: function( event ) { 2051 var panel = this, 2052 theme = $( event.target ).data( 'slug' ), 2053 section = api.section( 'installed_themes' ); 2054 2055 event.preventDefault(); 2056 2057 // Confirmation dialog for deleting a theme. 2058 if ( ! window.confirm( api.settings.l10n.confirmDeleteTheme ) ) { 2059 return; 2060 } 2061 2062 wp.updates.maybeRequestFilesystemCredentials( event ); 2063 2064 $( document ).one( 'wp-theme-delete-success', function( event, response ) { 2065 var control = api.control( 'installed_theme_' + theme ); 2066 2067 // Remove theme control. 2068 control.container.remove(); 2069 api.control.remove( control.id ); 2070 2071 // Update installed count. 2072 section.loaded = section.loaded - 1; 2073 section.updateCount(); 2074 2075 // Rerender any other theme controls as uninstalled. 2076 api.control.each( function( control ) { 2077 if ( 'theme' === control.params.type && control.params.theme.id === theme ) { 2078 control.rerenderAsInstalled( false ); 2079 } 2080 }); 2081 } ); 2082 2083 wp.updates.deleteTheme( { 2084 slug: theme 2085 } ); 2086 2087 // Close modal and focus the section. 2088 section.closeDetails(); 2089 section.focus(); 2090 } 2091 2092 }); 2093 2094 2095 /** 1517 2096 * A Customizer Control. 1518 2097 * 1519 2098 * A control provides a UI element that allows a user to modify a Customizer Setting. … … 2831 3410 api.ThemeControl = api.Control.extend({ 2832 3411 2833 3412 touchDrag: false, 2834 isRendered: false,3413 screenshotRendered: false, 2835 3414 2836 3415 /** 2837 * Defer rendering the theme control until the section is displayed.2838 *2839 3416 * @since 4.2.0 2840 3417 */ 2841 renderContent: function () {2842 var control = this,2843 renderContentArgs = arguments;2844 2845 api.section( control.section(), function( section ) {2846 if ( section.expanded() ) {2847 api.Control.prototype.renderContent.apply( control, renderContentArgs );2848 control.isRendered = true;2849 } else {2850 section.expanded.bind( function( expanded ) {2851 if ( expanded && ! control.isRendered ) {2852 api.Control.prototype.renderContent.apply( control, renderContentArgs );2853 control.isRendered = true;2854 }2855 } );2856 }2857 } );2858 },2859 2860 /**2861 * @since 4.2.02862 */2863 3418 ready: function() { 2864 3419 var control = this; 2865 3420 … … 2879 3434 } 2880 3435 2881 3436 // Prevent the modal from showing when the user clicks the action button. 2882 if ( $( event.target ).is( '.theme-actions .button ' ) ) {3437 if ( $( event.target ).is( '.theme-actions .button, .update-theme' ) ) { 2883 3438 return; 2884 3439 } 2885 3440 2886 var previewUrl = $( this ).data( 'previewUrl' );2887 2888 $( '.wp-full-overlay' ).addClass( 'customize-loading' );2889 2890 window.parent.location = previewUrl;2891 });2892 2893 control.container.on( 'click keydown', '.theme-actions .theme-details', function( event ) {2894 if ( api.utils.isKeydownButNotEnterEvent( event ) ) {2895 return;2896 }2897 2898 event.preventDefault(); // Keep this AFTER the key filter above2899 2900 3441 api.section( control.section() ).showDetails( control.params.theme ); 2901 3442 }); 2902 3443 … … 2907 3448 if ( source ) { 2908 3449 $screenshot.attr( 'src', source ); 2909 3450 } 3451 control.screenshotRendered = true; 2910 3452 }); 2911 3453 }, 2912 3454 2913 3455 /** 2914 * Show or hide the theme based on the presence of the term in the title, description, and author.3456 * Show or hide the theme based on the presence of the term in the title, description, tags, and author. 2915 3457 * 2916 3458 * @since 4.2.0 2917 3459 */ … … 2927 3469 } else { 2928 3470 control.deactivate(); 2929 3471 } 3472 }, 3473 3474 /** 3475 * Rerender the theme from its JS template with the installed type. 3476 * 3477 * @since 4.7.0 3478 */ 3479 rerenderAsInstalled: function( installed ) { 3480 var control = this, section; 3481 if ( installed ) { 3482 control.params.theme.type = 'installed'; 3483 } else { 3484 section = api.section( control.params.section ); 3485 control.params.theme.type = section.params.action; 3486 } 3487 control.renderContent(); // replaces existing content 3488 control.container.trigger( 'render-screenshot' ); 2930 3489 } 2931 3490 }); 2932 3491 … … 3491 4050 background: api.BackgroundControl, 3492 4051 theme: api.ThemeControl 3493 4052 }; 3494 api.panelConstructor = {}; 4053 api.panelConstructor = { 4054 themes: api.ThemesPanel 4055 }; 3495 4056 api.sectionConstructor = { 3496 4057 themes: api.ThemesSection 3497 4058 }; … … 3609 4170 3610 4171 // Sort the sections within each panel 3611 4172 api.panel.each( function ( panel ) { 4173 if ( 'themes' === panel.id ) { 4174 return; // Don't reflow theme sections, as doing so moves them after the themes container. 4175 } 4176 3612 4177 var sections = panel.sections(), 3613 4178 sectionHeadContainers = _.pluck( sections, 'headContainer' ); 3614 4179 rootNodes.push( panel ); … … 4086 4651 // Collapse the most granular expanded object. 4087 4652 collapsedObject = expandedControls[0] || expandedSections[0] || expandedPanels[0]; 4088 4653 if ( collapsedObject ) { 4654 if ( 'themes' === collapsedObject.params.type ) { 4655 // Themes panel or section. 4656 if ( $( 'body' ).hasClass( 'modal-open' ) ) { 4657 collapsedObject.closeDetails(); 4658 } else { 4659 // If we're collapsing a section, collapse the panel also. 4660 wp.customize.panel( 'themes' ).collapse(); 4661 } 4662 return; 4663 } 4089 4664 collapsedObject.collapse(); 4090 4665 event.preventDefault(); 4091 4666 } -
src/wp-admin/js/updates.js
179 179 if ( $notice.length ) { 180 180 $notice.replaceWith( $adminNotice ); 181 181 } 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 } 183 187 } 184 188 185 189 $document.trigger( 'wp-updates-notice-added' ); … … 907 911 if ( 'themes-network' === pagenow ) { 908 912 $notice = $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' ); 909 913 914 } else if ( 'customize' === pagenow ) { 915 // Update the theme details UI. 916 $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' ); 917 918 $notice.find( 'h3' ).remove(); 919 920 // Add the top-level UI, and update both. 921 $notice = $notice.add( $( '#customize-control-theme-installed_' + args.slug ).find( '.update-message' ) ); 922 $notice = $notice.addClass( 'updating-message' ).find( 'p' ); 923 910 924 } else { 911 925 $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' ); 912 926 … … 949 963 }, 950 964 $notice, newText; 951 965 966 if ( 'customize' === pagenow ) { 967 $theme = wp.customize.control( 'installed_theme_' + response.slug ).container; 968 } 969 952 970 if ( 'themes-network' === pagenow ) { 953 971 $notice = $theme.find( '.update-message' ); 954 972 … … 1003 1021 return; 1004 1022 } 1005 1023 1024 if ( 'customize' === pagenow ) { 1025 $theme = wp.customize.control( 'installed_theme_' + response.slug ).container; 1026 } 1027 1006 1028 if ( 'themes-network' === pagenow ) { 1007 1029 $notice = $theme.find( '.update-message ' ); 1008 1030 } else { … … 1139 1161 return; 1140 1162 } 1141 1163 1142 if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) { 1143 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1144 $card = $( '.install-theme-info' ).prepend( $message ); 1164 if ( 'customize' === pagenow ) { 1165 if ( $document.find( 'body' ).hasClass( 'modal-open' ) ) { 1166 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1167 $card = $( '.theme-overlay .theme-info' ).prepend( $message ); 1168 } else { 1169 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1170 $card = $button.closest( '.theme' ).addClass( 'theme-install-failed' ).append( $message ); 1171 } 1145 1172 } else { 1146 $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message ); 1147 $button = $card.find( '.theme-install' ); 1173 if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) { 1174 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1175 $card = $( '.install-theme-info' ).prepend( $message ); 1176 } else { 1177 $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message ); 1178 $button = $card.find( '.theme-install' ); 1179 } 1148 1180 } 1149 1181 1150 1182 $button -
src/wp-includes/class-wp-customize-manager.php
230 230 231 231 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' ); 232 232 233 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-panel.php' ); 233 234 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php' ); 234 235 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php' ); 235 236 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-section.php' ); … … 289 290 290 291 add_action( 'wp_ajax_customize_save', array( $this, 'save' ) ); 291 292 add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) ); 293 add_action( 'wp_ajax_customize-load-themes', array( $this, 'load_themes_ajax' ) ); 292 294 293 295 add_action( 'customize_register', array( $this, 'register_controls' ) ); 294 296 add_action( 'customize_register', array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first … … 302 304 303 305 // Export the settings to JS via the _wpCustomizeSettings variable. 304 306 add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 ); 307 308 // Add theme update notices. 309 if ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) ) { 310 require_once( ABSPATH . '/wp-admin/includes/update.php' ); 311 add_action( 'customize_controls_print_footer_scripts', 'wp_print_admin_notice_templates' ); 312 } 305 313 } 306 314 307 315 /** … … 1617 1625 foreach ( $this->controls as $control ) { 1618 1626 $control->enqueue(); 1619 1627 } 1628 if ( ! is_multisite() && ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) || current_user_can( 'delete_themes' ) ) ) { 1629 wp_enqueue_script( 'updates' ); 1630 } 1620 1631 } 1621 1632 1622 1633 /** … … 1770 1781 $nonces = array( 1771 1782 'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ), 1772 1783 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ), 1784 'customize-themes' => wp_create_nonce( 'customize-themes' ), 1773 1785 ); 1774 1786 1775 1787 /** … … 1850 1862 'autofocus' => $this->get_autofocus(), 1851 1863 'documentTitleTmpl' => $this->get_document_title_template(), 1852 1864 'previewableDevices' => $this->get_previewable_devices(), 1865 'l10n' => array( 1866 'confirmDeleteTheme' => __( 'Are you sure you want to delete this theme?' ), 1867 /* translators: %d is the number of theme search results, which cannot consider singular vs. plural forms */ 1868 'themeSearchResults' => __( '%d themes found' ), 1869 ), 1853 1870 ); 1854 1871 1855 1872 // Prepare Customize Section objects to pass to JavaScript. … … 1953 1970 1954 1971 /* Panel, Section, and Control Types */ 1955 1972 $this->register_panel_type( 'WP_Customize_Panel' ); 1973 $this->register_panel_type( 'WP_Customize_Themes_Panel' ); 1956 1974 $this->register_section_type( 'WP_Customize_Section' ); 1957 1975 $this->register_section_type( 'WP_Customize_Sidebar_Section' ); 1976 $this->register_section_type( 'WP_Customize_Themes_Section' ); 1958 1977 $this->register_control_type( 'WP_Customize_Color_Control' ); 1959 1978 $this->register_control_type( 'WP_Customize_Media_Control' ); 1960 1979 $this->register_control_type( 'WP_Customize_Upload_Control' ); … … 1964 1983 $this->register_control_type( 'WP_Customize_Site_Icon_Control' ); 1965 1984 $this->register_control_type( 'WP_Customize_Theme_Control' ); 1966 1985 1967 /* Themes */1986 /* Themes (controls are loaded via ajax) */ 1968 1987 1969 $this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array( 1970 'title' => $this->theme()->display( 'Name' ), 1971 'capability' => 'switch_themes', 1972 'priority' => 0, 1988 $this->add_panel( new WP_Customize_Themes_Panel( $this, 'themes', array( 1989 'title' => $this->theme()->display( 'Name' ), 1990 '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.' ), 1991 'capability' => 'switch_themes', 1992 'priority' => 0, 1973 1993 ) ) ); 1974 1994 1975 // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience). 1976 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array( 1977 'capability' => 'switch_themes', 1995 $this->add_section( new WP_Customize_Themes_Section( $this, 'installed_themes', array( 1996 'title' => __( 'Installed' ), 1997 'text_before' => __( 'Your local site' ), 1998 'action' => 'installed', 1999 'capability' => 'switch_themes', 2000 'panel' => 'themes', 2001 'priority' => 0, 1978 2002 ) ) ); 1979 2003 1980 require_once( ABSPATH . 'wp-admin/includes/theme.php' ); 2004 $this->add_section( new WP_Customize_Themes_Section( $this, 'search_themes', array( 2005 'title' => __( 'Search themes…' ), 2006 'text_before' => __( 'Browse all WordPress.org themes' ), 2007 'action' => 'search', 2008 'capability' => 'install_themes', 2009 'panel' => 'themes', 2010 'priority' => 5, 2011 ) ) ); 1981 2012 1982 // Theme Controls. 2013 $this->add_section( new WP_Customize_Themes_Section( $this, 'featured_themes', array( 2014 'title' => __( 'Featured' ), 2015 'action' => 'featured', 2016 'capability' => 'install_themes', 2017 'panel' => 'themes', 2018 'priority' => 10, 2019 ) ) ); 1983 2020 1984 // Add a control for the active/original theme. 1985 if ( ! $this->is_theme_active() ) { 1986 $themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) ); 1987 $active_theme = current( $themes ); 1988 $active_theme['isActiveTheme'] = true; 1989 $this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array( 1990 'theme' => $active_theme, 1991 'section' => 'themes', 1992 'settings' => 'active_theme', 1993 ) ) ); 1994 } 2021 $this->add_section( new WP_Customize_Themes_Section( $this, 'popular_themes', array( 2022 'title' => __( 'Popular' ), 2023 'action' => 'popular', 2024 'capability' => 'install_themes', 2025 'panel' => 'themes', 2026 'priority' => 15, 2027 ) ) ); 1995 2028 1996 $themes = wp_prepare_themes_for_js(); 1997 foreach ( $themes as $theme ) { 1998 if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) { 1999 continue; 2000 } 2029 $this->add_section( new WP_Customize_Themes_Section( $this, 'latest_themes', array( 2030 'title' => __( 'Latest' ), 2031 'action' => 'latest', 2032 'capability' => 'install_themes', 2033 'panel' => 'themes', 2034 'priority' => 20, 2035 ) ) ); 2001 2036 2002 $theme_id = 'theme_' . $theme['id']; 2003 $theme['isActiveTheme'] = false; 2004 $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array( 2005 'theme' => $theme, 2006 'section' => 'themes', 2007 'settings' => 'active_theme', 2008 ) ) ); 2009 } 2037 $this->add_section( new WP_Customize_Themes_Section( $this, 'feature_filter_themes', array( 2038 'title' => __( 'Feature Filter' ), 2039 'action' => 'feature_filter', 2040 'capability' => 'install_themes', 2041 'panel' => 'themes', 2042 'priority' => 25, 2043 ) ) ); 2010 2044 2045 $this->add_section( new WP_Customize_Themes_Section( $this, 'favorites_themes', array( 2046 'title' => __( 'Favorites' ), 2047 'action' => 'favorites', 2048 'capability' => 'install_themes', 2049 'panel' => 'themes', 2050 'priority' => 30, 2051 ) ) ); 2052 2053 // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience). 2054 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array( 2055 'capability' => 'switch_themes', 2056 ) ) ); 2057 2058 2011 2059 /* Site Identity */ 2012 2060 2013 2061 $this->add_section( 'title_tagline', array( … … 2334 2382 } 2335 2383 2336 2384 /** 2385 * Load themes into the theme browsing/installation UI. 2386 * 2387 * @since 4.7.0 2388 * @access public 2389 */ 2390 public function load_themes_ajax() { 2391 check_ajax_referer( 'customize-themes', 'customize-themes-nonce' ); 2392 2393 if ( ! current_user_can( 'switch_themes' ) ) { 2394 wp_die( -1 ); 2395 } 2396 2397 if ( empty( $_POST['theme_action'] ) ) { 2398 wp_send_json_error( 'missing_theme_action' ); 2399 } 2400 2401 if ( 'search' === $_POST['theme_action'] && ! array_key_exists( 'search', $_POST ) ) { 2402 wp_send_json_error( 'empty_search' ); 2403 } elseif ( 'favorites' === $_POST['theme_action'] && ! array_key_exists( 'user', $_POST ) ) { 2404 wp_send_json_error( 'empty_user' ); 2405 } elseif ( 'feature_filter' === $_POST['theme_action'] && ! array_key_exists( 'tags', $_POST ) ) { 2406 wp_send_json_error( 'no_features' ); 2407 } 2408 2409 require_once( ABSPATH . 'wp-admin/includes/theme.php' ); 2410 if ( 'installed' === $_POST['theme_action'] ) { 2411 $themes = array( 'themes' => wp_prepare_themes_for_js() ); 2412 foreach ( $themes['themes'] as &$theme ) { 2413 $theme['type'] = 'installed'; 2414 // Set active based on customized theme. 2415 if ( $_POST['customized_theme'] === $theme['id'] ) { 2416 $theme['active'] = true; 2417 } else { 2418 $theme['active'] = false; 2419 } 2420 } 2421 } else { 2422 if ( ! current_user_can( 'install_themes' ) ) { 2423 wp_die( -1 ); 2424 } 2425 2426 // Arguments for all queries. 2427 $args = array( 2428 'per_page' => 100, 2429 'page' => absint( $_POST['page'] ), 2430 'fields' => array( 2431 'slug' => true, 2432 'screenshot' => true, 2433 'description' => true, 2434 'requires' => true, 2435 'rating' => true, 2436 'downloaded' => true, 2437 'downloadLink' => true, 2438 'last_updated' => true, 2439 'homepage' => true, 2440 'num_ratings' => true, 2441 'tags' => true, 2442 ) 2443 ); 2444 2445 // Specialized handling for each query. 2446 switch ( $_POST['theme_action'] ) { 2447 case 'search': 2448 $args['search'] = wp_unslash( $_POST['search'] ); 2449 break; 2450 case 'favorites': 2451 $args['user'] = wp_unslash( $_POST['user'] ); 2452 case 'featured': 2453 case 'popular': 2454 $args['browse'] = $_POST['theme_action']; 2455 break; 2456 case 'latest': 2457 $args['browse'] = 'new'; 2458 break; 2459 case 'feature_filter': 2460 $args['tag'] = wp_unslash( $_POST['tags'] ); 2461 break; 2462 } 2463 2464 // Load themes from the .org API. 2465 $themes = themes_api( 'query_themes', $args ); 2466 if ( is_wp_error( $themes ) ) { 2467 wp_send_json_error(); 2468 } 2469 2470 // Prepare a list of installed themes to check against before the loop. 2471 $installed_themes = array(); 2472 $wp_themes = wp_get_themes(); 2473 foreach ( $wp_themes as $theme ) { 2474 $installed_themes[] = $theme->get_stylesheet(); 2475 } 2476 $update_php = network_admin_url( 'update.php?action=install-theme' ); 2477 foreach ( $themes->themes as &$theme ) { 2478 $theme->install_url = add_query_arg( array( 2479 'theme' => $theme->slug, 2480 '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ) 2481 ), $update_php ); 2482 2483 $theme->name = wp_kses( $theme->name, $themes_allowedtags ); 2484 $theme->author = wp_kses( $theme->author, $themes_allowedtags ); 2485 $theme->version = wp_kses( $theme->version, $themes_allowedtags ); 2486 $theme->description = wp_kses( $theme->description, $themes_allowedtags ); 2487 $theme->tags = implode( ', ', $theme->tags ); 2488 $theme->stars = wp_star_rating( array( 'rating' => $theme->rating, 'type' => 'percent', 'number' => $theme->num_ratings, 'echo' => false ) ); 2489 $theme->num_ratings = number_format_i18n( $theme->num_ratings ); 2490 $theme->preview_url = set_url_scheme( $theme->preview_url ); 2491 2492 // Handle themes that are already installed as installed themes. 2493 if ( in_array( $theme->slug, $installed_themes ) ) { 2494 $theme->type = 'installed'; 2495 } else { 2496 $theme->type = $_POST['theme_action']; 2497 } 2498 2499 // Set active based on customized theme. 2500 if ( $_POST['customized_theme'] === $theme->slug ) { 2501 $theme->active = true; 2502 } else { 2503 $theme->active = false; 2504 } 2505 2506 // Map available theme properties to installed theme properties. 2507 $theme->id = $theme->slug; 2508 $theme->screenshot = array( $theme->screenshot_url ); 2509 $theme->authorAndUri = $theme->author; 2510 unset( $theme->slug ); 2511 unset( $theme->screenshot_url ); 2512 unset( $theme->author ); 2513 } 2514 } 2515 wp_send_json_success( $themes ); 2516 } 2517 2518 2519 /** 2337 2520 * Callback for validating the header_textcolor value. 2338 2521 * 2339 2522 * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash(). -
src/wp-includes/customize/class-wp-customize-theme-control.php
67 67 $preview_url = esc_url( add_query_arg( 'theme', '__THEME__', $current_url ) ); // Token because esc_url() strips curly braces. 68 68 $preview_url = str_replace( '__THEME__', '{{ data.theme.id }}', $preview_url ); 69 69 ?> 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">70 <# if ( data.theme.active ) { #> 71 <div class="theme active" tabindex="0" aria-describedby="{{ data.section }}-{{ data.theme.id }}-action {{ data.theme.id }}-name"> 72 72 <# } 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">73 <div class="theme" tabindex="0" aria-describedby="{{ data.section }}-{{ data.theme.id }}-action {{ data.theme.id }}-name"> 74 74 <# } #> 75 75 76 <# if ( data.theme.screenshot [0] ) { #>76 <# if ( data.theme.screenshot && data.theme.screenshot[0] ) { #> 77 77 <div class="theme-screenshot"> 78 78 <img data-src="{{ data.theme.screenshot[0] }}" alt="" /> 79 79 </div> … … 81 81 <div class="theme-screenshot blank"></div> 82 82 <# } #> 83 83 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> 84 <span class="more-details theme-details" id="{{ data.section }}-{{ data.theme.id }}-action"><?php _e( 'Theme Details' ); ?></span> 85 86 87 <# if ( 'installed' === data.theme.type && data.theme.hasUpdate ) { #> 88 <div class="update-message notice inline notice-warning notice-alt" data-slug="{{ data.theme.id }}"><p><?php _e( 'New version available. <button class="button-link update-theme" type="button">Update now</button>' ); ?></p></div> 88 89 <# } #> 89 90 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"> 91 <# if ( data.theme.active ) { #> 92 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name"> 94 93 <?php 95 94 /* translators: %s: theme name */ 96 printf( __( '<span> Active:</span> %s' ), '{{{ data.theme.name }}}' );95 printf( __( '<span>Current:</span> %s' ), '{{ data.theme.name }}' ); 97 96 ?> 98 97 </h3> 98 <div class="theme-actions"> 99 <button type="button" class="button button-primary customize-theme"><?php _e( 'Customize' ); ?></button> 100 </div> 101 <div class="notice notice-success notice-alt"><p><?php _e( 'Installed' ); ?></p></div> 102 <# } else if ( 'installed' === data.theme.type ) { #> 103 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> 104 <div class="theme-actions"> 105 <button type="button" class="button button-primary preview-theme" data-preview-url="<?php echo esc_attr( $preview_url ); ?>"><?php _e( 'Live Preview' ); ?></span> 106 </div> 107 <div class="notice notice-success notice-alt"><p><?php _e( 'Installed' ); ?></p></div> 99 108 <# } else { #> 100 <h3 class="theme-name" id="{{ data. theme.id }}-name">{{{ data.theme.name }}}</h3>109 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> 101 110 <div class="theme-actions"> 102 <button type="button" class="button theme-details"><?php _e( 'Theme Details' ); ?></button> 111 <button type="button" class="button theme-install" data-slug="{{ data.theme.id }}"><?php _e( 'Install' ); ?></button> 112 <button type="button" class="button button-primary theme-install preview" data-slug="{{ data.theme.id }}" data-previewurl="<?php echo esc_attr( $preview_url ); ?>"><?php _e( 'Install & Preview' ); ?></button> 103 113 </div> 104 <# } #> 114 <# } #> 105 115 </div> 106 116 <?php 107 117 } -
src/wp-includes/customize/class-wp-customize-themes-panel.php
1 <?php 2 /** 3 * Customize API: WP_Customize_Themes_Panel class 4 * 5 * @package WordPress 6 * @subpackage Customize 7 * @since 4.7.0 8 */ 9 10 /** 11 * Customize Themes Panel Class 12 * 13 * @since 4.7.0 14 * 15 * @see WP_Customize_Panel 16 */ 17 class WP_Customize_Themes_Panel extends WP_Customize_Panel { 18 19 /** 20 * Panel type. 21 * 22 * @since 4.7.0 23 * @access public 24 * @var string 25 */ 26 public $type = 'themes'; 27 28 /** 29 * An Underscore (JS) template for rendering this panel's container. 30 * 31 * The themes panel renders a custom panel heading with the current theme and a switch themes button. 32 * 33 * @see WP_Customize_Panel::print_template() 34 * 35 * @since 4.7.0 36 * @access protected 37 */ 38 protected function render_template() { 39 ?> 40 <li id="accordion-section-{{ data.id }}" class="accordion-section control-panel-themes"> 41 <h3 class="accordion-section-title"> 42 <?php 43 if ( $this->manager->is_theme_active() ) { 44 echo '<span class="customize-action">' . __( 'Active theme' ) . '</span> {{ data.title }}'; 45 } else { 46 echo '<span class="customize-action">' . __( 'Previewing theme' ) . '</span> {{ data.title }}'; 47 } 48 ?> 49 50 <?php 51 if ( current_user_can( 'switch_themes' ) ) : ?> 52 <button type="button" class="button change-theme"><?php _ex( 'Change', 'theme' ); ?></button> 53 <?php endif; ?> 54 </h3> 55 <ul class="accordion-sub-container control-panel-content"></ul> 56 </li> 57 <?php 58 } 59 60 /** 61 * An Underscore (JS) template for this panel's content (but not its container). 62 * 63 * Class variables for this panel class are available in the `data` JS object; 64 * export custom variables by overriding WP_Customize_Panel::json(). 65 * 66 * @since 4.7.0 67 * @access protected 68 * 69 * @see WP_Customize_Panel::print_template() 70 */ 71 protected function content_template() { 72 ?> 73 <li class="filter-themes-count"> 74 <span class="themes-displayed"><?php 75 /* translators: %s: number of themes displayed; plural forms cannot be accomodated here so assume plurality or translate as "Themes: %s" */ 76 echo sprintf( __( 'Displaying %s themes' ), '<span class="theme-count">0</span>' ); 77 ?></span> 78 <button type="button" class="button button-primary see-themes"><?php 79 /* translators: %s: number of themes displayed; plural forms cannot be accomodated here so assume plurality or omit the count and translate as "Show themes" */ 80 echo sprintf( __( 'Show %s themes' ), '<span class="theme-count">0</span>' ); 81 ?></button> 82 <button type="button" class="button button-primary filter-themes"><?php _e( 'Filter themes' ); ?></button> 83 </li> 84 <li class="panel-meta customize-info accordion-section <# if ( ! data.description ) { #> cannot-expand<# } #>"> 85 <button class="customize-panel-back" tabindex="-1"><span class="screen-reader-text"><?php _e( 'Back' ); ?></span></button> 86 <div class="accordion-section-title"> 87 <span class="preview-notice"><?php 88 /* translators: %s: themes panel title in the Customizer */ 89 echo sprintf( __( 'You are browsing %s' ), '<strong class="panel-title">' . __( 'Themes' ) . '</strong>' ); // Separate strings for consistency with other panels. 90 ?></span> 91 <?php if ( current_user_can( 'install_themes' ) && ! is_multisite() ) : ?> 92 <# if ( data.description ) { #> 93 <button class="customize-help-toggle dashicons dashicons-editor-help" tabindex="0" aria-expanded="false"><span class="screen-reader-text"><?php _e( 'Help' ); ?></span></button> 94 <# } #> 95 <?php endif; ?> 96 </div> 97 <?php if ( current_user_can( 'install_themes' ) && ! is_multisite() ) : ?> 98 <# if ( data.description ) { #> 99 <div class="description customize-panel-description"> 100 {{{ data.description }}} 101 </div> 102 <# } #> 103 <?php endif; ?> 104 </li> 105 <li id="customize-container"></li><?php // Used as a full-screen overlay transition after clicking to preview a theme. ?> 106 <li class="customize-themes-full-container-container"> 107 <ul class="customize-themes-full-container"> 108 <li class="customize-themes-notifications"> 109 <div class="notice notice-warning customize-themes-unsaved-changes" style="display: none;"><p> 110 <?php _e( 'You have unsaved changes that will be lost if you preview a new theme.' ); ?> 111 <button type="button" id="customize-themes-save" class="button" ><?php _e( 'Save & Publish' ); ?></button> 112 </p></div> 113 </li> 114 </ul> 115 </li> 116 <?php 117 } 118 } -
src/wp-includes/customize/class-wp-customize-themes-section.php
10 10 /** 11 11 * Customize Themes Section class. 12 12 * 13 * A UI container for theme controls, which behaves like a backwards Panel.13 * A UI container for theme controls, which are displayed in tabbed sections. 14 14 * 15 15 * @since 4.2.0 16 16 * … … 28 28 public $type = 'themes'; 29 29 30 30 /** 31 * Render the themes section, which behaves like a panel.31 * Theme section action. 32 32 * 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 34 71 * @access protected 35 72 */ 36 protected function render() { 37 $classes = 'accordion-section control-section control-section-' . $this->type; 73 protected function render_template() { 38 74 ?> 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="search" 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"'; 44 88 } 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…' ); ?></span> 98 <input type="text" id="themes-filter" placeholder="<?php esc_attr_e( 'Search installed themes…' ); ?>" /> 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"> 60 114 <?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>'; 65 128 } 66 129 ?> 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"> 70 133 <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…' ); ?></span>76 <input type="text" id="themes-filter" placeholder="<?php esc_attr_e( 'Search installed themes…' ); ?>" />77 </label></p>78 <?php endif; ?>79 134 <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’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"> 81 137 </ul> 138 <p class="no-themes"><?php _e( 'No themes found. Try a different search.' ); ?></p> 139 <p class="spinner"></p> 82 140 </div> 83 141 </div> 84 142 </li>