Ticket #37661: 37661.11.diff
File 37661.11.diff, 99.4 KB (added by , 7 years ago) |
---|
-
src/wp-admin/css/customize-controls.css
diff --git a/src/wp-admin/css/customize-controls.css b/src/wp-admin/css/customize-controls.css index cc5a37f08c..2d02a3cd38 100644
a b body { 203 203 .15s border-color ease-in-out; 204 204 } 205 205 206 #customize-controls #customize-theme-controls .customize-themes-panel .accordion-section-title {207 color: #555;208 background-color: #fff;209 border-left: 4px solid #fff;210 }211 212 206 #customize-theme-controls .accordion-section-title:after { 213 207 content: "\f345"; 214 208 color: #a0a5aa; … … body { 319 313 } 320 314 321 315 #customize-theme-controls .customize-pane-child.open, 322 #customize-theme-controls .customize-pane-child.current-panel, 323 #customize-theme-controls .customize-themes-panel.customize-pane-child.current-panel { 316 #customize-theme-controls .customize-pane-child.current-panel { 324 317 -webkit-transform: none; 325 318 transform: none; 326 319 } 327 320 328 #customize-theme-controls .customize-themes-panel.customize-pane-child,329 321 .section-open #customize-theme-controls .customize-pane-parent, 330 322 .in-sub-panel #customize-theme-controls .customize-pane-parent, 331 323 .section-open #customize-info, 332 324 .in-sub-panel #customize-info, 333 .in-sub-panel.section-open #customize-theme-controls .customize-pane-child.current-panel, 334 .in-themes-panel #customize-theme-controls .customize-pane-parent, 335 .in-themes-panel #customize-info { 325 .in-sub-panel.section-open #customize-theme-controls .customize-pane-child.current-panel { 336 326 visibility: hidden; 337 327 height: 0; 338 328 overflow: hidden; … … body { 342 332 343 333 .section-open #customize-theme-controls .customize-pane-parent.busy, 344 334 .in-sub-panel #customize-theme-controls .customize-pane-parent.busy, 345 .in-themes-panel #customize-theme-controls .customize-pane-parent.busy,346 335 .section-open #customize-info.busy, 347 336 .in-sub-panel #customize-info.busy, 348 .in-themes-panel #customize-info.busy,349 337 .busy.section-open.in-sub-panel #customize-theme-controls .customize-pane-child.current-panel, 350 338 #customize-theme-controls .customize-pane-child.open, 351 339 #customize-theme-controls .customize-pane-child.current-panel, … … body { 355 343 overflow: auto; 356 344 } 357 345 358 .in-themes-panel #customize-theme-controls .customize-pane-parent,359 .in-themes-panel #customize-info {360 -webkit-transform: translateX(100%);361 transform: translateX(100%);362 }363 364 346 #customize-theme-controls .customize-pane-child.accordion-section-content, 365 347 #customize-theme-controls .customize-pane-child.accordion-sub-container { 366 348 display: block; … … p.customize-section-description { 1246 1228 100% { opacity: 1; } 1247 1229 } 1248 1230 1249 /* #customize-container is reused from customize-loader.js, hence the naming. */ 1250 .wp-customizer .customize-loading #customize-container { 1231 .wp-customizer .customize-loading #customize-themes-loading-container { 1251 1232 display: block; 1252 1233 -webkit-animation: customize-reload .75s; /* Can't use `transition` because `display` changes here. */ 1253 1234 animation: customize-reload .75s; 1254 1235 } 1255 1236 1256 #customize-theme-controls .control-section-themes .accordion-section-title:hover, /* Not a focusable element. */ 1257 #customize-theme-controls .control-section-themes .accordion-section-title { 1237 .customize-loading #customize-themes-loading-container span { 1238 clear: both; 1239 color: #555d66; 1240 font-size: 18px; 1241 font-style: normal; 1242 margin: 0; 1243 padding: 100px 0; 1244 text-align: center; 1245 width: 100%; 1246 display: block; 1247 } 1248 1249 .customize-loading #customize-themes-loading-container .customize-loading-text { 1250 display: none; 1251 } 1252 1253 #customize-theme-controls .control-panel-themes { 1254 border-bottom: none; 1255 } 1256 1257 #customize-theme-controls .control-panel-themes > .accordion-section-title:hover, /* Not a focusable element. */ 1258 #customize-theme-controls .control-panel-themes > .accordion-section-title { 1258 1259 cursor: default; 1259 1260 background: #fff; 1260 1261 color: #555d66; 1261 1262 border-top: 1px solid #ddd; 1262 1263 border-bottom: 1px solid #ddd; 1263 1264 border-left: none; 1264 margin-top: 0; 1265 } 1266 #customize-theme-controls .control-section-themes .customize-section-back { 1267 position: absolute; 1268 right: 0; 1269 top: 0; 1270 height: 80px; 1271 border-left: 1px solid #ddd; 1272 border-right: 4px solid #fff; 1273 } 1274 #customize-theme-controls .control-section-themes .customize-section-back:before { 1275 content: "\f345"; 1276 } 1277 #customize-theme-controls .control-section-themes .customize-section-back:hover, 1278 #customize-theme-controls .control-section-themes .customize-section-back:focus { 1279 border-right-color: #0073aa; 1265 border-right: none; 1266 margin: 0 0 15px 0; 1267 padding-right: 100px; /* Space for the button */ 1280 1268 } 1281 1269 1282 1270 #customize-theme-controls .control-section-themes .customize-themes-panel .accordion-section-title:first-child:hover, /* Not a focusable element. */ … … p.customize-section-description { 1284 1272 border-top: 0; 1285 1273 } 1286 1274 1287 #customize-theme-controls .control-section-themes > .accordion-section-title:hover, /* Not a focusable element. */ 1288 #customize-theme-controls .control-section-themes > .accordion-section-title { 1289 margin: 0 0 15px; 1290 } 1291 1292 #customize-controls .customize-themes-panel .accordion-section-title:hover, 1293 #customize-controls .customize-themes-panel .accordion-section-title { 1294 margin: 15px -8px; 1295 } 1296 1297 #customize-controls .control-section-themes .accordion-section-title, 1298 #customize-controls .customize-themes-panel .accordion-section-title { 1299 padding-right: 100px; /* Space for the button */ 1300 } 1301 1302 #customize-controls .control-section-themes .accordion-section-title span.customize-action, 1275 .control-panel-themes .accordion-section-title span.customize-action, 1303 1276 #customize-controls .customize-section-title span.customize-action { 1304 1277 font-size: 13px; 1305 1278 display: block; 1306 1279 font-weight: 400; 1307 1280 } 1308 1281 1309 #customize-controls .control-section-themes .accordion-section-title .change-theme, 1310 #customize-controls .customize-themes-panel .accordion-section-title .customize-theme { 1282 .control-panel-themes .accordion-section-title .change-theme { 1311 1283 position: absolute; 1312 1284 right: 10px; 1313 1285 top: 50%; … … p.customize-section-description { 1315 1287 font-weight: 400; 1316 1288 } 1317 1289 1318 #customize- controls .control-section-themes .accordion-section-title:before{1290 #customize-theme-controls .control-panel-themes > .accordion-section-title:after { 1319 1291 display: none; 1320 1292 } 1321 1293 1322 #customize-controls .customize-themes-panel { 1323 padding: 0 8px; 1324 background: #f1f1f1; 1325 box-sizing: border-box; 1294 .control-panel-themes .customize-themes-full-container { 1295 position: fixed; 1296 top: 0; 1297 left: 0; 1298 -webkit-transition: .18s left ease-in-out; 1299 transition: .18s left ease-in-out; 1300 margin: 46px 0 0 300px; 1301 padding: 25px; 1302 overflow-y: scroll; 1303 width: calc(100% - 350px); 1304 height: calc(100% - 96px); 1305 background: #eee; 1306 z-index: 20; 1326 1307 } 1327 1308 1328 #customize-controls .customize-themes-panel .accordion-section-title:first-child { 1329 margin-top: 0; 1309 /* Animations for opening the themes panel */ 1310 #customize-header-actions .save, 1311 #customize-header-actions .spinner, 1312 #customize-header-actions .customize-controls-preview-toggle { 1313 position: relative; 1314 top: 0; 1315 -webkit-transition: .18s top ease-in-out; 1316 transition: .18s top ease-in-out; 1317 } 1318 1319 #customize-footer-actions, 1320 #customize-footer-actions .collapse-sidebar { 1321 bottom: 0; 1322 -webkit-transition: .18s bottom ease-in-out; 1323 transition: .18s bottom ease-in-out; 1324 } 1325 1326 .in-themes-panel:not(.animating) #customize-header-actions .save, 1327 .in-themes-panel:not(.animating) #customize-header-actions .spinner, 1328 .in-themes-panel:not(.animating) #customize-header-actions .customize-controls-preview-toggle, 1329 .in-themes-panel:not(.animating) #customize-preview, 1330 .in-themes-panel:not(.animating) #customize-footer-actions { 1331 visibility: hidden; 1332 } 1333 1334 .wp-full-overlay.in-themes-panel { 1335 background: #eee; /* Prevents a black flash when fading in the panel */ 1330 1336 } 1331 1337 1332 #customize-controls .customize-themes-panel .accordion-section-title:nth-child(2) { 1338 .in-themes-panel #customize-header-actions .save, 1339 .in-themes-panel #customize-header-actions .spinner, 1340 .in-themes-panel #customize-header-actions .customize-controls-preview-toggle { 1341 top: -45px; 1342 } 1343 1344 .in-themes-panel #customize-footer-actions, 1345 .in-themes-panel #customize-footer-actions .collapse-sidebar { 1346 bottom: -45px; 1347 } 1348 1349 /* Don't show the theme count while the panel opens, as it's in the wrong place during the animation */ 1350 .in-themes-panel.animating .control-panel-themes .filter-themes-count { 1351 display: none; 1352 } 1353 1354 .in-themes-panel.wp-full-overlay .wp-full-overlay-sidebar-content { 1355 bottom: 0; 1356 } 1357 1358 .themes-filter-bar .feature-filter-toggle { 1359 float: right; 1360 margin: 3px 25px; 1361 } 1362 1363 .themes-filter-bar .feature-filter-toggle:before { 1364 content: "\f111"; 1365 margin: 0 5px 0 0; 1366 font: normal 16px/1 dashicons; 1367 vertical-align: text-bottom; 1368 -webkit-font-smoothing: antialiased; 1369 -moz-osx-font-smoothing: grayscale; 1370 } 1371 1372 .themes-filter-bar .feature-filter-toggle.open { 1373 background: #eee; 1374 border-color: #999; 1375 box-shadow: inset 0 2px 5px -3px rgba( 0, 0, 0, 0.5 ); 1376 -webkit-transform: translateY(1px); 1377 transform: translateY(1px); 1378 } 1379 1380 .themes-filter-bar .filter-drawer { 1381 width: 100%; 1382 position: absolute; 1383 top: 46px; 1384 border-top: 0; 1385 margin: 0 -25px; 1386 background: #eee; 1387 border-bottom: 1px solid #ddd; 1388 } 1389 1390 /* Adds a delay before fading in to avoid it "jumping" */ 1391 @-webkit-keyframes themes-fade-in { 1392 0% { 1393 opacity: 0; 1394 } 1395 50% { 1396 opacity: 0; 1397 } 1398 100% { 1399 opacity: 1; 1400 } 1401 } 1402 @keyframes themes-fade-in { 1403 0% { 1404 opacity: 0; 1405 } 1406 50% { 1407 opacity: 0; 1408 } 1409 100% { 1410 opacity: 1; 1411 } 1412 } 1413 1414 .control-panel-themes .customize-themes-full-container.animate { 1415 -webkit-animation: .6s themes-fade-in 1; 1416 animation: .6s themes-fade-in 1; 1417 } 1418 1419 .in-themes-panel:not(.animating) .control-panel-themes .filter-themes-count { 1420 -webkit-animation: .6s themes-fade-in 1; 1421 animation: .6s themes-fade-in 1; 1422 } 1423 1424 .control-panel-themes .filter-themes-count { 1425 position: relative; 1426 float: right; 1427 line-height: 34px; 1428 } 1429 1430 .control-panel-themes .filter-themes-count .themes-displayed { 1431 font-weight: 600; 1432 color: #555d66; 1433 } 1434 1435 .control-panel-themes .filter-themes-count .see-themes, 1436 .control-panel-themes .filter-themes-count .filter-themes { 1437 display: none; 1438 } 1439 1440 1441 /* Mobile - toggle between themes and filters */ 1442 @media screen and (max-width:600px) { 1443 1444 /* Show a spinner in the filters view also, reusing the main customize spinner */ 1445 .in-themes-panel.loading #customize-header-actions .spinner { 1446 position: fixed; 1447 top: 0; 1448 left: 48px; 1449 visibility: visible; 1450 } 1451 1452 .in-themes-panel.loading.showing-themes #customize-header-actions .spinner { 1453 visibility: hidden; 1454 } 1455 1456 .control-panel-themes .filter-themes-count { 1457 width: -webkit-calc(100% - 93px); 1458 width: calc(100% - 93px); 1459 } 1460 1461 .control-panel-themes .filter-themes-count .themes-displayed { 1462 display: none; 1463 } 1464 1465 .wp-full-overlay:not(.showing-themes) .control-panel-themes .filter-themes-count .see-themes { 1466 display: block; 1467 float: right; 1468 } 1469 1470 .wp-full-overlay.showing-themes .control-panel-themes .filter-themes-count .filter-themes { 1471 display: block; 1472 float: right; 1473 } 1474 1475 .in-themes-panel.showing-themes .control-panel-themes .customize-panel-back { 1476 position: fixed; 1477 top: 0; 1478 left: 0; 1479 z-index: 10; 1480 height: 45px; 1481 background: #eee; 1482 } 1483 1484 .in-themes-panel.showing-themes .control-panel-themes .customize-panel-back:before { 1485 line-height: 45px; 1486 } 1487 1488 .control-panel-themes .customize-themes-full-container { 1489 width: -webkit-calc(100% - 50px); 1490 width: calc(100% - 50px); 1491 margin: 0; 1492 top: 46px; 1493 height: -webkit-calc(100% - 96px); 1494 height: calc(100% - 96px); 1495 z-index: 1; 1496 display: none; 1497 } 1498 1499 .showing-themes .control-panel-themes .customize-themes-full-container { 1500 display: block; 1501 } 1502 } 1503 1504 .customize-themes-notifications { 1505 margin: 0; 1506 } 1507 1508 .control-panel-themes .customize-themes-notifications .notice { 1509 margin: 0 0 25px 0; 1510 } 1511 1512 .customize-themes-full-container .customize-themes-section { 1513 display: none !important; /* There is unknown JS that perpetually tries to show all theme sections when more items are added. */ 1514 overflow: hidden; 1515 } 1516 1517 .customize-themes-full-container .customize-themes-section.current-section { 1518 display: list-item !important; /* There is unknown JS that perpetually tries to show all theme sections when more items are added. */ 1519 } 1520 1521 .control-section .customize-section-text-before { 1522 padding: 0 0 8px 15px; 1523 margin: 15px 0 0 0; 1524 line-height: 16px; 1525 border-bottom: 1px solid #ddd; 1526 color: #555d66; 1527 } 1528 1529 .control-panel-themes .customize-themes-section-title { 1530 width: 100%; 1531 background: #fff; 1532 -webkit-box-shadow: none; 1533 box-shadow: none; 1534 outline: none; 1535 border-top: none; 1536 border-bottom: 1px solid #ddd; 1537 border-left: 4px solid #fff; 1538 border-right: none; 1539 cursor: pointer; 1540 padding: 10px 15px; 1541 position: relative; 1542 text-align: left; 1333 1543 font-size: 14px; 1334 1544 font-weight: 600; 1545 color: #555d66; 1546 text-shadow: none; 1335 1547 } 1336 1548 1337 #customize-controls .customize-themes-panel > h2 { 1338 padding: 15px 8px 0 8px; 1549 .control-panel-themes .theme-section { 1550 margin: 0; 1551 position: relative; 1339 1552 } 1340 1553 1341 #customize-theme-controls .customize-themes-panel .accordion-section-content { 1342 background: transparent; 1343 display: block; 1554 .control-panel-themes .customize-themes-section-title:focus, 1555 .control-panel-themes .customize-themes-section-title:hover { 1556 border-left-color: #0073aa; 1557 color: #0073aa; 1558 background: #f5f5f5; 1344 1559 } 1345 1560 1346 .customize-control.customize-control-theme { 1347 margin-bottom: 8px; 1561 .control-panel-themes .theme-section .customize-themes-section-title.selected:after { 1562 content: "\f147"; 1563 font: 16px/1 dashicons; 1564 box-sizing: border-box; 1565 width: 20px; 1566 height: 20px; 1567 padding: 3px 3px 1px 1px; /* Re-align the icon to the smaller grid */ 1568 -webkit-border-radius: 100%; 1569 border-radius: 100%; 1570 position: absolute; 1571 top: 9px; 1572 right: 15px; 1573 background: #0073aa; 1574 color: #fff; 1575 } 1576 1577 .control-panel-themes .customize-themes-section-title.selected { 1578 color: #0073aa; 1348 1579 } 1349 1580 1350 1581 #customize-theme-controls .themes.accordion-section-content { … … p.customize-section-description { 1354 1585 width: 100%; 1355 1586 } 1356 1587 1357 .wp-customizer .theme-browser .themes { 1358 padding-bottom: 8px; 1588 .loading .customize-themes-section .spinner { 1589 display: block; 1590 visibility: visible; 1591 position: relative; 1592 clear: both; 1593 width: 20px; 1594 height: 20px; 1595 left: -webkit-calc(50% - 10px); 1596 left: calc(50% - 10px); 1597 float: none; 1598 margin-top: 50px; 1359 1599 } 1360 1600 1361 .wp-customizer .theme-browser .theme { 1362 margin: 0; 1601 .customize-themes-section .no-themes { 1602 display: none; 1603 } 1604 1605 .themes-section-installed_themes .theme .notice-success { 1606 display: none; /* Hide "installed" notice on installed themes tab. */ 1607 } 1608 1609 .control-panel-themes .theme-browser .theme .theme-actions .button-primary { 1610 margin: 0 0 0 8px; 1611 } 1612 1613 .customize-control-theme .theme { 1363 1614 width: 100%; 1615 margin: 0; 1616 } 1617 1618 .customize-control.customize-control-theme { /* override most properties on .customize-control */ 1619 -webkit-box-sizing: border-box; 1620 -moz-box-sizing: border-box; 1621 box-sizing: border-box; 1622 width: 18.4%; 1623 margin: 0 2% 2% 0; 1624 padding: 0; 1625 clear: none; 1626 } 1627 1628 /* 5 columns above 2100px */ 1629 @media screen and (min-width: 2101px) { 1630 .customize-control.customize-control-theme:nth-child(5n) { 1631 margin-right: 0; 1632 } 1633 } 1634 1635 /* 4 columns up to 2100px */ 1636 @media screen and (min-width: 1601px) and (max-width: 2100px) { 1637 .customize-control.customize-control-theme { 1638 width: 23.5%; 1639 } 1640 1641 .customize-control.customize-control-theme:nth-child(4n) { 1642 margin-right: 0; 1643 } 1644 } 1645 1646 /* 3 columns up to 1600px */ 1647 @media screen and (min-width: 1201px) and (max-width: 1600px) { 1648 .customize-control.customize-control-theme { 1649 width: 32%; 1650 } 1651 1652 .customize-control.customize-control-theme:nth-child(3n) { 1653 margin-right: 0; 1654 } 1655 } 1656 1657 /* 2 columns up to 1200px */ 1658 @media screen and (min-width: 851px) and (max-width: 1200px) { 1659 .customize-control.customize-control-theme { 1660 width: 49%; 1661 } 1662 1663 .customize-control.customize-control-theme:nth-child(even) { 1664 margin-right: 0; 1665 } 1666 } 1667 1668 /* 1 column up to 850 px */ 1669 @media screen and (max-width: 850px) { 1670 .customize-control.customize-control-theme { 1671 width: 100%; 1672 margin: 0 0 3% 0; 1673 } 1674 } 1675 1676 .wp-customizer .theme-browser .themes { 1677 padding-bottom: 8px; 1364 1678 } 1365 1679 1366 1680 .wp-customizer .theme-browser .theme .theme-actions { 1367 -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";1368 1681 opacity: 1; 1369 1682 } 1370 1683 … … p.customize-section-description { 1376 1689 font-size: 32px; 1377 1690 } 1378 1691 1379 .wp-customizer #themes-filter { 1380 font-size: 16px; 1381 font-weight: 300; 1382 line-height: 1.5; 1383 width: 100%; 1692 .customize-preview-header.themes-filter-bar { 1693 position: fixed; 1694 top: 0; 1695 left: 300px; 1696 width: calc(100% - 300px); 1697 height: 46px; 1698 background: #fff; 1699 z-index: 10; 1700 padding: 6px 25px; 1701 box-sizing: border-box; 1702 border-bottom: 1px solid #ddd; 1384 1703 } 1385 1704 1386 . control-section-themes .accordion-section-title:after,1387 .customize-themes-panel .accordion-section-title:after { 1388 display: none;1705 .themes-filter-bar .themes-filter-container { 1706 margin: 0; 1707 padding: 0; 1389 1708 } 1390 1709 1391 .customize-themes-panel.control-panel-content { 1392 border-top: 1px solid #ddd; 1710 .themes-filter-bar .wp-filter-search { 1711 line-height: 25px; 1712 padding: 3px 5px; 1713 max-width: 100%; 1714 width: 40%; 1715 min-width: 300px; 1716 position: absolute; 1717 top: 6px; 1718 left: 25px; 1719 } 1720 1721 /* Unstick the filter bar on short windows/screens. This breakpoint is based on the 1722 current length of .org feature filters assuming translations do not wrap lines. */ 1723 @media screen and (max-height:540px) { 1724 .customize-preview-header.themes-filter-bar { 1725 position: relative; 1726 left: 0; 1727 width: 100%; 1728 margin: 0 0 25px 0; 1729 } 1730 .wp-customizer .theme-browser .themes { 1731 padding: 0 25px; 1732 min-height: 540px; 1733 } 1734 1735 .control-panel-themes .customize-themes-full-container { 1736 margin-top: 0; 1737 padding: 0; 1738 height: 100%; 1739 width: calc(100% - 300px); 1740 } 1393 1741 } 1394 1742 1395 1743 /* Details View */ … … p.customize-section-description { 1406 1754 z-index: 109; 1407 1755 } 1408 1756 1757 /* Avoid a z-index war by resetting elements that should be under the overlay. 1758 This is likely required because of the way that sections and panels are positioned. */ 1759 .wp-customizer.modal-open #customize-header-actions, 1760 .wp-customizer.modal-open .control-panel-themes .filter-themes-count, 1761 .wp-customizer.modal-open .control-panel-themes .customize-themes-section-title.selected:after { 1762 z-index: -1; 1763 } 1764 1409 1765 .wp-customizer .theme-overlay .theme-backdrop { 1410 1766 background: rgba( 238, 238, 238, 0.75 ); 1411 1767 position: fixed; 1412 1768 z-index: 110; 1413 1769 } 1414 1770 1771 .wp-customizer .theme-overlay .star-rating { 1772 float: left; 1773 margin-right: 8px; 1774 } 1775 1776 .wp-customizer .theme-rating .num-ratings { 1777 line-height: 20px; 1778 } 1779 1415 1780 .wp-customizer .theme-overlay .theme-wrap { 1416 1781 left: 90px; 1417 1782 right: 90px; 1418 1783 top: 45px; 1419 1784 bottom: 45px; 1420 1785 z-index: 120; 1421 max-width: 1740px; /* To ensure that theme screenshots are not displayed larger than 880px wide. */1422 1786 } 1423 1787 1424 1788 .wp-customizer .theme-overlay .theme-actions { 1425 text-align: right; /* Because there's only one action, match the pattern of media modals and right-align the action. */ 1789 text-align: right; /* Because there're only one or two actions, match the UI pattern of media modals and right-align the action. */ 1790 padding: 10px 15px; 1426 1791 } 1427 1792 1428 .ie8 .wp-customizer .theme-overlay .theme-header, 1429 .ie8 .wp-customizer .theme-overlay .theme-about, 1430 .ie8 .wp-customizer .theme-overlay .theme-actions { 1431 position: static; 1793 .wp-customizer .theme-overlay .theme-actions .theme-install.preview { 1794 margin-left: 8px; 1795 } 1796 1797 .control-panel-themes .theme-actions .delete-theme { 1798 left: 15px; /* these override themes.css on mobile */ 1799 right: auto; 1800 bottom: auto; 1801 position: absolute; 1432 1802 } 1433 1803 1804 .modal-open .in-themes-panel #customize-controls .wp-full-overlay-sidebar-content { 1805 overflow: visible; /* Prevent the top-level Customizer controls from becoming visible when elements on the right of the details modal are focused. */ 1806 } 1807 1808 1434 1809 /* Small Screens */ 1435 1810 @media (max-width:850px), (max-height:472px) { 1436 1811 .wp-customizer .theme-overlay .theme-wrap { … … body.cheatin { 1456 1831 body.cheatin h1 { 1457 1832 border-bottom: 1px solid #ddd; 1458 1833 clear: both; 1459 color: # 666;1834 color: #555d66; 1460 1835 font-size: 24px; 1461 1836 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 1462 1837 margin: 30px 0 0 0; -
src/wp-admin/css/themes.css
diff --git a/src/wp-admin/css/themes.css b/src/wp-admin/css/themes.css index 6c4ce8716a..07294bc514 100644
a b body.folded .theme-browser ~ .theme-overlay .theme-wrap { 549 549 float: left; 550 550 margin: 0 30px 0 0; 551 551 width: 55%; 552 max-width: 880px;552 max-width: 1200px; /* Recommended theme screenshot width, set here to avoid stretching */ 553 553 text-align: center; 554 554 } 555 555 … … body.full-overlay-active { 1705 1705 display: none; 1706 1706 } 1707 1707 1708 #customize-container { 1708 #customize-container, 1709 #customize-themes-loading-container { 1709 1710 display: none; 1710 1711 background: #fff; 1711 1712 z-index: 500000; … … body.full-overlay-active { 1720 1721 1721 1722 /* Make the Customizer and Theme installer overlays the only available content. */ 1722 1723 #customize-container, 1724 #customize-themes-loading-container, 1723 1725 .theme-install-overlay { 1724 1726 visibility: visible; 1725 1727 } … … body.full-overlay-active { 1824 1826 1825 1827 #customize-preview.wp-full-overlay-main:before, 1826 1828 .customize-loading #customize-container:before, 1829 .customize-loading #customize-themes-loading-container:before, 1827 1830 .theme-install-overlay .wp-full-overlay-main:before { 1828 1831 content: ""; 1829 1832 display: block; … … body.full-overlay-active { 1861 1864 1862 1865 #customize-preview.wp-full-overlay-main:before, 1863 1866 .customize-loading #customize-container:before, 1867 .customize-loading #customize-themes-loading-container:before, 1864 1868 .theme-install-overlay .wp-full-overlay-main:before { 1865 1869 background-image: url(../images/spinner-2x.gif); 1866 1870 } -
src/wp-admin/customize.php
diff --git a/src/wp-admin/customize.php b/src/wp-admin/customize.php index 81a7ae2741..3c5455720c 100644
a b 109 109 ?><title><?php echo $admin_title; ?></title> 110 110 111 111 <script type="text/javascript"> 112 var ajaxurl = <?php echo wp_json_encode( admin_url( 'admin-ajax.php', 'relative' ) ); ?>; 112 var ajaxurl = <?php echo wp_json_encode( admin_url( 'admin-ajax.php', 'relative' ) ); ?>, 113 pagenow = 'customize'; 113 114 </script> 114 115 115 116 <?php -
src/wp-admin/includes/theme.php
diff --git a/src/wp-admin/includes/theme.php b/src/wp-admin/includes/theme.php index c5de5e5c9c..632c1ac34d 100644
a b function get_theme_feature_list( $api = true ) { 234 234 // Hard-coded list is used if api not accessible. 235 235 $features = array( 236 236 237 __( 'Layout' ) => array( 238 'grid-layout' => __( 'Grid Layout' ), 239 'one-column' => __( 'One Column' ), 240 'two-columns' => __( 'Two Columns' ), 241 'three-columns' => __( 'Three Columns' ), 242 'four-columns' => __( 'Four Columns' ), 243 'left-sidebar' => __( 'Left Sidebar' ), 244 'right-sidebar' => __( 'Right Sidebar' ), 237 __( 'Subject' ) => array( 238 'blog' => __( 'Blog' ), 239 'e-commerce' => __( 'E-Commerce' ), 240 'education' => __( 'Education' ), 241 'entertainment' => __( 'Entertainment' ), 242 'food-and-drink' => __( 'Food & Drink' ), 243 'holiday' => __( 'Holiday' ), 244 'news' => __( 'News' ), 245 'photography' => __( 'Photography' ), 246 'portfolio' => __( 'Portfolio' ), 245 247 ), 246 248 247 249 __( 'Features' ) => array( 248 250 'accessibility-ready' => __( 'Accessibility Ready' ), 249 'buddypress' => __( 'BuddyPress' ),250 251 'custom-background' => __( 'Custom Background' ), 251 252 'custom-colors' => __( 'Custom Colors' ), 252 253 'custom-header' => __( 'Custom Header' ), 253 254 'custom-logo' => __( 'Custom Logo' ), 254 'custom-menu' => __( 'Custom Menu' ),255 255 'editor-style' => __( 'Editor Style' ), 256 256 'featured-image-header' => __( 'Featured Image Header' ), 257 257 'featured-images' => __( 'Featured Images' ), 258 'flexible-header' => __( 'Flexible Header' ),259 258 'footer-widgets' => __( 'Footer Widgets' ), 260 'front-page-post-form' => __( 'Front Page Posting' ),261 259 'full-width-template' => __( 'Full Width Template' ), 262 'microformats' => __( 'Microformats' ),263 260 'post-formats' => __( 'Post Formats' ), 264 'rtl-language-support' => __( 'RTL Language Support' ),265 261 'sticky-post' => __( 'Sticky Post' ), 266 262 'theme-options' => __( 'Theme Options' ), 267 'threaded-comments' => __( 'Threaded Comments' ),268 'translation-ready' => __( 'Translation Ready' ),269 263 ), 270 264 271 __( 'Subject' ) => array( 272 'blog' => __( 'Blog' ), 273 'e-commerce' => __( 'E-Commerce' ), 274 'education' => __( 'Education' ), 275 'entertainment' => __( 'Entertainment' ), 276 'food-and-drink' => __( 'Food & Drink' ), 277 'holiday' => __( 'Holiday' ), 278 'news' => __( 'News' ), 279 'photography' => __( 'Photography' ), 280 'portfolio' => __( 'Portfolio' ), 265 __( 'Layout' ) => array( 266 'grid-layout' => __( 'Grid Layout' ), 267 'one-column' => __( 'One Column' ), 268 'two-columns' => __( 'Two Columns' ), 269 'three-columns' => __( 'Three Columns' ), 270 'four-columns' => __( 'Four Columns' ), 271 'left-sidebar' => __( 'Left Sidebar' ), 272 'right-sidebar' => __( 'Right Sidebar' ), 281 273 ) 274 282 275 ); 283 276 284 277 if ( ! $api || ! current_user_can( 'install_themes' ) ) … … function wp_prepare_themes_for_js( $themes = null ) { 570 563 571 564 $parent = false; 572 565 if ( $theme->parent() ) { 573 $parent = $theme->parent()->display( 'Name' ); 574 $parents[ $slug ] = $theme->parent()->get_stylesheet(); 566 $parent = $theme->parent(); 567 $parents[ $slug ] = $parent->get_stylesheet(); 568 $parent = $parent->display( 'Name' ); 575 569 } 576 570 577 571 $customize_action = null; … … function wp_prepare_themes_for_js( $themes = null ) { 631 625 * @since 4.2.0 632 626 */ 633 627 function customize_themes_print_templates() { 634 $preview_url = esc_url( add_query_arg( 'theme', '__THEME__' ) ); // Token because esc_url() strips curly braces.635 $preview_url = str_replace( '__THEME__', '{{ data.id }}', $preview_url );636 628 ?> 637 629 <script type="text/html" id="tmpl-customize-themes-details-view"> 638 630 <div class="theme-backdrop"></div> … … function customize_themes_print_templates() { 644 636 </div> 645 637 <div class="theme-about wp-clearfix"> 646 638 <div class="theme-screenshots"> 647 <# if ( data.screenshot [0] ) { #>639 <# if ( data.screenshot && data.screenshot[0] ) { #> 648 640 <div class="screenshot"><img src="{{ data.screenshot[0] }}" alt="" /></div> 649 641 <# } else { #> 650 642 <div class="screenshot blank"></div> … … function customize_themes_print_templates() { 657 649 <# } #> 658 650 <h2 class="theme-name">{{{ data.name }}}<span class="theme-version"><?php printf( __( 'Version: %s' ), '{{ data.version }}' ); ?></span></h2> 659 651 <h3 class="theme-author"><?php printf( __( 'By %s' ), '{{{ data.authorAndUri }}}' ); ?></h3> 660 <p class="theme-description">{{{ data.description }}}</p> 652 653 <# if ( data.stars && 0 != data.num_ratings ) { #> 654 <div class="theme-rating"> 655 {{{ data.stars }}} 656 <span class="num-ratings"><?php echo sprintf( __( '(%s ratings)' ), '{{ data.num_ratings }}' ); ?></span> 657 </div> 658 <# } #> 659 660 <# if ( data.hasUpdate ) { #> 661 <div class="notice notice-warning notice-alt notice-large" data-slug="{{ data.id }}"> 662 <h3 class="notice-title"><?php _e( 'Update Available' ); ?></h3> 663 {{{ data.update }}} 664 </div> 665 <# } #> 661 666 662 667 <# if ( data.parent ) { #> 663 668 <p class="parent-theme"><?php printf( __( 'This is a child theme of %s.' ), '<strong>{{{ data.parent }}}</strong>' ); ?></p> 664 669 <# } #> 665 670 671 <p class="theme-description">{{{ data.description }}}</p> 672 666 673 <# if ( data.tags ) { #> 667 <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{ data.tags}}</p>674 <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{{ data.tags }}}</p> 668 675 <# } #> 669 676 </div> 670 677 </div> 671 678 672 <# if ( ! data.active ) { #> 673 <div class="theme-actions"> 674 <div class="inactive-theme"> 675 <?php 676 /* translators: %s: Theme name */ 677 $aria_label = sprintf( __( 'Preview %s' ), '{{ data.name }}' ); 678 ?> 679 <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> 680 </div> 681 </div> 682 <# } #> 679 <div class="theme-actions"> 680 <# if ( data.active ) { #> 681 <button type="button" class="button button-primary customize-theme"><?php _e( 'Customize' ); ?></a> 682 <# } else if ( 'installed' === data.type ) { #> 683 <?php if ( current_user_can( 'delete_themes' ) ) { ?> 684 <# if ( data.actions && data.actions['delete'] ) { #> 685 <a href="{{{ data.actions['delete'] }}}" data-slug="{{ data.id }}" class="button button-secondary delete-theme"><?php _e( 'Delete' ); ?></a> 686 <# } #> 687 <?php } ?> 688 <button type="button" class="button button-primary preview-theme" data-slug="{{ data.id }}"><?php _e( 'Live Preview' ); ?></span> 689 <# } else { #> 690 <button type="button" class="button theme-install" data-slug="{{ data.id }}"><?php _e( 'Install' ); ?></button> 691 <button type="button" class="button button-primary theme-install preview" data-slug="{{ data.id }}"><?php _e( 'Install & Preview' ); ?></button> 692 <# } #> 693 </div> 683 694 </div> 684 695 </script> 685 696 <?php -
src/wp-admin/js/customize-controls.js
diff --git a/src/wp-admin/js/customize-controls.js b/src/wp-admin/js/customize-controls.js index c80ef86e52..715407b8f4 100644
a b 1106 1106 section = this, 1107 1107 container = $( '#customize-theme-controls' ); 1108 1108 1109 // Watch for changes to the panel state 1109 // Watch for changes to the panel state. 1110 1110 inject = function ( panelId ) { 1111 1111 var parentContainer; 1112 1112 if ( panelId ) { 1113 // The panel has been supplied, so wait until the panel object is registered 1113 // The panel has been supplied, so wait until the panel object is registered. 1114 1114 api.panel( panelId, function ( panel ) { 1115 // The panel has been registered, wait for it to become ready/initialized 1115 // The panel has been registered, wait for it to become ready/initialized. 1116 1116 panel.deferred.embedded.done( function () { 1117 1117 parentContainer = panel.contentContainer; 1118 1118 if ( ! section.headContainer.parent().is( parentContainer ) ) { … … 1137 1137 } 1138 1138 }; 1139 1139 section.panel.bind( inject ); 1140 inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one 1140 inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one. 1141 1141 }, 1142 1142 1143 1143 /** … … 1310 1310 /** 1311 1311 * wp.customize.ThemesSection 1312 1312 * 1313 * Custom section for themes that functions similarly to a backwards panel,1314 * and alsohandles the theme-details view rendering and navigation.1313 * Custom section for themes that loads themes by category, and also 1314 * handles the theme-details view rendering and navigation. 1315 1315 * 1316 1316 * @constructor 1317 1317 * @augments wp.customize.Section … … 1323 1323 template: '', 1324 1324 screenshotQueue: null, 1325 1325 $window: $( window ), 1326 loaded: 0, 1327 loading: false, 1328 fullyLoaded: false, 1329 term: '', 1330 tags: '', 1331 nextTerm: '', 1332 nextTags: '', 1333 headerContainer: $(), 1326 1334 1327 1335 /** 1328 * @since 4.2.0 1336 * Embed the section in the DOM when the themes panel is ready. 1337 * 1338 * Insert the section before the themes container. Assume that a themes section is within a panel, but not necessarily the themes panel. 1339 * 1340 * @since 4.9.0 1329 1341 */ 1330 initialize: function () { 1331 this.$customizeSidebar = $( '.wp-full-overlay-sidebar-content:first' ); 1332 return api.Section.prototype.initialize.apply( this, arguments ); 1342 embed: function () { 1343 var inject, 1344 section = this, 1345 container = $( '#customize-theme-controls' ); 1346 1347 // Watch for changes to the panel state 1348 inject = function ( panelId ) { 1349 var parentContainer; 1350 api.panel( panelId, function ( panel ) { 1351 // The panel has been registered, wait for it to become ready/initialized 1352 panel.deferred.embedded.done( function () { 1353 parentContainer = panel.contentContainer; 1354 if ( ! section.headContainer.parent().is( parentContainer ) ) { 1355 parentContainer.find( '.customize-themes-full-container-container' ).before( section.headContainer ); 1356 } 1357 if ( ! section.contentContainer.parent().is( section.headContainer ) ) { 1358 container.append( section.contentContainer ); 1359 } 1360 section.deferred.embedded.resolve(); 1361 }); 1362 } ); 1363 }; 1364 section.panel.bind( inject ); 1365 inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one 1333 1366 }, 1334 1367 1335 1368 /** … … 1358 1391 1359 1392 // Pressing the escape key fires a theme:collapse event 1360 1393 if ( 27 === event.keyCode ) { 1361 section.closeDetails(); 1394 if ( $( 'body' ).hasClass( 'modal-open' ) ) { 1395 // Escape from the details modal. 1396 section.closeDetails(); 1397 } else { 1398 // Escape from the inifinite scroll list. 1399 section.headerContainer.find( '.customize-themes-section-title' ).focus(); 1400 } 1362 1401 event.stopPropagation(); // Prevent section from being collapsed. 1363 1402 } 1364 1403 }); 1365 1404 1366 _.bindAll( this, 'renderScreenshots' );1405 _.bindAll( this, 'renderScreenshots', 'loadMore', 'checkTerm', 'filtersChecked' ); 1367 1406 }, 1368 1407 1369 1408 /** 1370 1409 * Override Section.isContextuallyActive method. 1371 1410 * 1372 1411 * Ignore the active states' of the contained theme controls, and just 1373 * use the section's own active state instead. This ensures empty search1374 * results for theme s to causethe section to become inactive.1412 * use the section's own active state instead. This prevents empty search 1413 * results for theme sections from causing the section to become inactive. 1375 1414 * 1376 1415 * @since 4.2.0 1377 1416 * … … 1396 1435 section.collapse(); 1397 1436 }); 1398 1437 1399 // Expand/Collapse section/panel. 1400 section.container.find( '.change-theme, .customize-theme' ).on( 'click keydown', function( event ) { 1401 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1402 return; 1438 section.headerContainer = $( '#accordion-section-' + section.id ); 1439 1440 // Expand section/panel. Only collapse when opening another section. 1441 section.headerContainer.on( 'click', '.customize-themes-section-title', function() { 1442 1443 // Toggle accordion filters under section headers. 1444 if ( section.headerContainer.find( '.filter-details' ).length ) { 1445 section.headerContainer.find( '.customize-themes-section-title' ) 1446 .toggleClass( 'details-open' ) 1447 .attr('aria-expanded', function ( i, attr ) { 1448 return attr === 'true' ? 'false' : 'true'; 1449 }); 1450 section.headerContainer.find( '.filter-details' ).slideToggle( 180 ); 1403 1451 } 1404 event.preventDefault(); // Keep this AFTER the key filter above1405 1452 1406 if ( section.expanded() ) { 1407 section.collapse(); 1408 } else { 1453 // Open the section. 1454 if ( ! section.expanded() ) { 1409 1455 section.expand(); 1410 1456 } 1411 1457 }); 1412 1458 1413 // Theme navigation in details view. 1414 section.container.on( 'click keydown', '.left', function( event ) { 1415 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1416 return; 1417 } 1459 // Preview installed themes. 1460 section.container.on( 'click', '.theme-actions .preview-theme', function() { 1461 var themeId = $( this ).data( 'slug' ); 1418 1462 1419 event.preventDefault(); // Keep this AFTER the key filter above 1463 $( '.wp-full-overlay' ).addClass( 'customize-loading' ); 1464 api.panel( 'themes' ).loadThemePreview( themeId ).fail( function() { 1465 $( '.wp-full-overlay' ).removeClass( 'customize-loading' ); 1466 } ); 1467 }); 1420 1468 1469 // Theme navigation in details view. 1470 section.container.on( 'click', '.left', function() { 1421 1471 section.previousTheme(); 1422 1472 }); 1423 1473 1424 section.container.on( 'click keydown', '.right', function( event ) { 1425 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1426 return; 1427 } 1428 1429 event.preventDefault(); // Keep this AFTER the key filter above 1430 1474 section.container.on( 'click', '.right', function() { 1431 1475 section.nextTheme(); 1432 1476 }); 1433 1477 1434 section.container.on( 'click keydown', '.theme-backdrop, .close', function( event ) { 1435 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1436 return; 1437 } 1438 1439 event.preventDefault(); // Keep this AFTER the key filter above 1440 1478 section.container.on( 'click', '.theme-backdrop, .close', function() { 1441 1479 section.closeDetails(); 1442 1480 }); 1443 1481 1444 1482 var renderScreenshots = _.throttle( _.bind( section.renderScreenshots, this ), 100 ); 1445 section.container.on( 'input', '#themes-filter', function( event ) { 1483 1484 // Filter-search all theme objects loaded in the section. 1485 section.container.on( 'input', '.wp-filter-search-themes', function( event ) { 1446 1486 var count, 1447 1487 term = event.currentTarget.value.toLowerCase().trim().replace( '-', ' ' ), 1448 1488 controls = section.controls(); … … 1454 1494 renderScreenshots(); 1455 1495 1456 1496 // Update theme count. 1457 count = section.cont ainer.find( 'li.customize-control:visible' ).length;1458 section. container.find( '.theme-count' ).text( count );1497 count = section.contentContainer.find( 'li.customize-control:visible' ).length; 1498 section.updateCount( count ); 1459 1499 }); 1460 1500 1461 // Pre-load the first 3 theme screenshots. 1462 api.bind( 'ready', function () { 1463 _.each( section.controls().slice( 0, 3 ), function ( control ) { 1464 var img, src = control.params.theme.screenshot[0]; 1465 if ( src ) { 1466 img = new Image(); 1467 img.src = src; 1501 // Event listeners for remote wporg queries with user-entered terms. 1502 if ( 'wporg' === section.params.action ) { 1503 1504 // Search terms. 1505 var debounced = _.debounce( section.checkTerm, 500 ); // Wait until there is no input for 500 milliseconds to initiate a search. 1506 section.contentContainer.on( 'input', '#wp-filter-search-input', function() { 1507 debounced( section ); 1508 if ( ! section.expanded() ) { 1509 section.expand(); 1468 1510 } 1511 section.checkTerm( section ); 1512 }); 1513 1514 // Feature filters. 1515 section.contentContainer.on( 'click', '.filter-group input', function() { 1516 section.filtersChecked(); 1517 section.checkTerm( section ); 1469 1518 }); 1519 1520 // Toggle feature filter sections. 1521 section.contentContainer.on( 'click', '.feature-filter-toggle', function( e ) { 1522 $( e.currentTarget ) 1523 .toggleClass( 'open' ) 1524 .attr('aria-expanded', function ( i, attr ) { 1525 return attr === 'true' ? 'false' : 'true'; 1526 }) 1527 .next( '.filter-drawer' ).slideToggle( 180 ); 1528 }); 1529 } 1530 1531 // Move section controls to the themes area. 1532 api.bind( 'ready', function () { 1533 section.contentContainer = section.container.find( '.customize-themes-section' ); 1534 section.contentContainer.appendTo( $( '.customize-themes-full-container' ) ); 1535 section.container.add( section.headerContainer ); 1470 1536 }); 1471 1537 }, 1472 1538 … … 1478 1544 * @param {Boolean} expanded 1479 1545 * @param {Object} args 1480 1546 * @param {Boolean} args.unchanged 1481 * @param { Callback} args.completeCallback1547 * @param {Function} args.completeCallback 1482 1548 */ 1483 1549 onChangeExpanded: function ( expanded, args ) { 1484 1550 … … 1491 1557 } 1492 1558 1493 1559 // Note: there is a second argument 'args' passed 1494 var panel = this, 1495 section = panel.contentContainer, 1496 overlay = section.closest( '.wp-full-overlay' ), 1497 container = section.closest( '.wp-full-overlay-sidebar-content' ), 1498 customizeBtn = section.find( '.customize-theme' ), 1499 changeBtn = panel.headContainer.find( '.change-theme' ); 1560 var section = this, 1561 container = section.contentContainer.closest( '.customize-themes-full-container' ); 1562 1563 if ( expanded ) { 1564 1565 // Try to load controls if none are loaded yet. 1566 if ( 0 === section.loaded ) { 1567 section.loadControls(); 1568 } 1500 1569 1501 if ( expanded && ! section.hasClass( 'current-panel' ) ) {1502 1570 // Collapse any sibling sections/panels 1503 1571 api.section.each( function ( otherSection ) { 1504 if ( otherSection !== panel) {1572 if ( otherSection !== section ) { 1505 1573 otherSection.collapse( { duration: args.duration } ); 1506 1574 } 1507 1575 }); 1508 api.panel.each( function ( otherPanel ) {1509 otherPanel.collapse( { duration: 0 } );1510 });1511 1576 1512 panel._animateChangeExpanded( function() {1513 changeBtn.attr( 'tabindex', '-1');1514 customizeBtn.attr( 'tabindex', '0' );1577 section.contentContainer.addClass( 'current-section' ); 1578 container.scrollTop(); 1579 section.headerContainer.find( '.customize-themes-section-title' ).addClass( 'selected' ).attr( 'aria-expanded', 'true' ); 1515 1580 1516 customizeBtn.focus(); 1517 section.css( 'top', '' ); 1518 container.scrollTop( 0 ); 1581 container.on( 'scroll', _.throttle( section.renderScreenshots, 300 ) ); 1582 container.on( 'scroll', _.throttle( section.loadMore, 300 ) ); 1519 1583 1520 if ( args.completeCallback ) { 1521 args.completeCallback(); 1584 if ( args.completeCallback ) { 1585 args.completeCallback(); 1586 } 1587 section.updateCount(); // Show this section's count. 1588 } else { 1589 section.contentContainer.removeClass( 'current-section' ); 1590 1591 // Always hide, even if they don't exist or are already hidden. 1592 section.headerContainer.find( '.customize-themes-section-title' ).removeClass( 'selected details-open' ).attr( 'aria-expanded', 'false' ); 1593 section.headerContainer.find( '.filter-details' ).slideUp( 180 ); 1594 1595 container.off( 'scroll' ); 1596 1597 if ( args.completeCallback ) { 1598 args.completeCallback(); 1599 } 1600 } 1601 }, 1602 1603 /** 1604 * Return the section's content element without detachng from the parent. 1605 * 1606 * @since 4.9.0 1607 */ 1608 getContent: function() { 1609 return this.container.find( '.control-section-content' ); 1610 }, 1611 1612 /** 1613 * Load theme data via Ajax and add themes to the section as controls. 1614 * 1615 * @since 4.9.0 1616 */ 1617 loadControls: function() { 1618 var section = this, params, page, request; 1619 1620 if ( section.loading ) { 1621 return; // We're already loading a batch of themes. 1622 } 1623 1624 // Parameters for every API query. Additional params are set in PHP. 1625 page = Math.ceil( section.loaded / 100 ) + 1; 1626 params = { 1627 'switch-themes-nonce': api.settings.nonce['switch-themes'], 1628 'wp_customize': 'on', 1629 'theme_action': section.params.action, 1630 'customized_theme': api.settings.theme.stylesheet, 1631 'page': page 1632 }; 1633 1634 // Add fields for wporg actions. 1635 if ( 'wporg' === section.params.action ) { 1636 params.search = section.term; 1637 params.tags = section.tags; 1638 } 1639 1640 // Load themes. 1641 section.headContainer.closest( '.wp-full-overlay' ).addClass( 'loading' ); 1642 section.loading = true; 1643 section.container.find( '.no-themes' ).hide(); 1644 request = wp.ajax.post( 'customize-load-themes', params ); 1645 request.done(function( data ) { 1646 var themes = data.themes, 1647 themeControl, newThemeControls; 1648 1649 // Stop and try again if the term changed while loading. 1650 if ( section.nextTerm || section.nextTags ) { 1651 if ( section.nextTerm ) { 1652 section.term = section.nextTerm; 1522 1653 } 1523 } ); 1654 if ( section.nextTags ) { 1655 section.tags = section.nextTags; 1656 } 1657 section.nextTerm = ''; 1658 section.nextTags = ''; 1659 section.loading = false; 1660 section.loadControls(); 1661 return; 1662 } 1524 1663 1525 overlay.addClass( 'in-themes-panel' ); 1526 section.addClass( 'current-panel' ); 1527 _.delay( panel.renderScreenshots, 10 ); // Wait for the controls 1528 panel.$customizeSidebar.on( 'scroll.customize-themes-section', _.throttle( panel.renderScreenshots, 300 ) ); 1664 if ( 0 !== themes.length ) { 1665 newThemeControls = []; 1666 // Add controls for each theme. 1667 _.each( themes, function ( theme ) { 1668 var customizeId = section.params.action + '_theme_' + theme.id; 1669 themeControl = new api.controlConstructor.theme( customizeId, { 1670 params: { 1671 type: 'theme', 1672 content: '<li id="customize-control-theme-' + section.params.action + '_' + theme.id + '" class="customize-control customize-control-theme"></li>', 1673 section: section.params.id, 1674 active: true, 1675 theme: theme, 1676 priority: section.loaded + 1 1677 }, 1678 previewer: api.previewer 1679 } ); 1529 1680 1530 } else if ( ! expanded && section.hasClass( 'current-panel' ) ) {1531 panel._animateChangeExpanded( function() {1532 changeBtn.attr( 'tabindex', '0' );1533 customizeBtn.attr( 'tabindex', '-1');1681 api.control.add( customizeId, themeControl ); 1682 newThemeControls.push( themeControl ); 1683 section.loaded = section.loaded + 1; 1684 }); 1534 1685 1535 changeBtn.focus(); 1536 section.css( 'top', '' ); 1686 if ( 1 === page ) { 1687 // Pre-load the first 3 theme screenshots. 1688 _.each( section.controls().slice( 0, 3 ), function ( control ) { 1689 var img, src = control.params.theme.screenshot[0]; 1690 if ( src ) { 1691 img = new Image(); 1692 img.src = src; 1693 } 1694 }); 1695 if ( 'installed' !== section.params.action ) { 1696 wp.a11y.speak( api.settings.l10n.themeSearchResults.replace( '%d', data.info.results ) ); 1697 } 1698 } else { 1699 Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue. 1700 } 1701 _.delay( section.renderScreenshots, 100 ); // Wait for the controls to become visible. 1537 1702 1538 if ( args.completeCallback ) {1539 args.completeCallback();1703 if ( 'installed' === section.params.action || 100 > themes.length ) { // If we have less than the requested 100 themes, it's the end of the list. 1704 section.fullyLoaded = true; 1540 1705 } 1541 } ); 1706 } else { 1707 if ( 0 === section.loaded ) { 1708 section.container.find( '.no-themes' ).show(); 1709 wp.a11y.speak( section.container.find( '.no-themes' ).text() ); 1710 } else { 1711 section.fullyLoaded = true; 1712 } 1713 } 1714 if ( 'installed' === section.params.action ) { 1715 section.updateCount(); 1716 } else { 1717 section.updateCount( data.info.results ); 1718 } 1719 section.container.find( '.unexpected-error' ).hide(); // Hide error notice in case it was previously shown. 1720 1721 // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case. 1722 section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' ); 1723 section.loading = false; 1724 }); 1725 request.fail(function( data ) { 1726 if ( 'undefined' === typeof data ) { 1727 section.container.find( '.unexpected-error' ).show(); 1728 wp.a11y.speak( section.container.find( '.unexpected-error' ).text() ); 1729 } else if ( typeof console !== 'undefined' && console.error ) { 1730 console.error( data ); 1731 } 1732 1733 // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case. 1734 section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' ); 1735 section.loading = false; 1736 }); 1737 }, 1738 1739 /** 1740 * Determines whether more themes should be loaded, and loads them. 1741 * 1742 * @since 4.9.0 1743 */ 1744 loadMore: function() { 1745 var section = this, container, bottom, threshold; 1746 if ( ! section.fullyLoaded && ! section.loading ) { 1747 container = section.container.closest( '.customize-themes-full-container' ); 1748 1749 bottom = container.scrollTop() + container.height(); 1750 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. 1542 1751 1543 overlay.removeClass( 'in-themes-panel' ); 1544 section.removeClass( 'current-panel' ); 1545 panel.$customizeSidebar.off( 'scroll.customize-themes-section' ); 1752 if ( bottom > threshold ) { 1753 section.loadControls(); 1754 } 1755 } 1756 }, 1757 1758 /** 1759 * Event handler for search input that determines if the terms have changed and loads new controls as needed. 1760 * 1761 * @since 4.9.0 1762 * 1763 * @param {wp.customize.ThemesSection} section The current theme section, passed through the debouncer. 1764 */ 1765 checkTerm: function( section ) { 1766 var newTerm; 1767 1768 // Find term. 1769 if ( 'wporg' === section.params.action ) { 1770 newTerm = $( '#wp-filter-search-input' ).val(); 1771 } else { 1772 return; 1773 } 1774 1775 if ( section.term === newTerm ) { 1776 return; 1777 } else { 1778 section.initializeNewQuery( newTerm, section.tags ); 1779 }; 1780 1781 }, 1782 1783 /** 1784 * Check for filters checked in the feature filter list and initialize a new query. 1785 * 1786 * @since 4.9.0 1787 */ 1788 filtersChecked: function() { 1789 var section = this, 1790 items = section.container.find( '.filter-group' ).find( ':checkbox' ), 1791 tags = []; 1792 1793 _.each( items.filter( ':checked' ), function( item ) { 1794 tags.push( $( item ).prop( 'value' ) ); 1795 }); 1796 1797 // When no filters are checked, restore initial state. 1798 if ( tags.length === 0 ) { 1799 tags = ''; 1800 } 1801 1802 section.contentContainer.find( '.feature-filter-toggle .theme-filter-count' ).text( tags.length ); 1803 1804 section.initializeNewQuery( section.term, tags ) 1805 }, 1806 1807 /** 1808 * Reset the current query and load new results. 1809 * 1810 * @since 4.9.0 1811 */ 1812 initializeNewQuery: function( newTerm, newTags ) { 1813 var section = this; 1814 1815 // Clear the controls in the section. 1816 _.each( section.controls(), function( control ) { 1817 control.container.remove(); 1818 api.control.remove( control.id ); 1819 }); 1820 section.loaded = 0; 1821 section.fullyLoaded = false; 1822 section.screenshotQueue = null; 1823 1824 // Run a new query, with loadControls handling paging, etc. 1825 if ( ! section.loading ) { 1826 section.term = newTerm; 1827 section.tags = newTags; 1828 section.loadControls(); 1829 } else { 1830 section.nextTerm = newTerm; // This will reload from loadControls() with the newest term once the current batch is loaded. 1831 section.nextTags = newTags; // This will reload from loadControls() with the newest tags once the current batch is loaded. 1832 } 1833 if ( ! section.expanded() ) { 1834 section.expand(); // Expand the section if it isn't expanded. 1546 1835 } 1547 1836 }, 1548 1837 … … 1554 1843 renderScreenshots: function( ) { 1555 1844 var section = this; 1556 1845 1557 // Fill queue initially. 1558 if ( section.screenshotQueue === null ) { 1559 section.screenshotQueue = section.controls(); 1846 // Fill queue initially, or check for more if empty. 1847 if ( section.screenshotQueue === null || 0 === section.screenshotQueue.length ) { 1848 // Add controls that haven't had their screenshots rendered. 1849 section.screenshotQueue = _.filter( section.controls(), function( control ) { 1850 return ! control.screenshotRendered; 1851 }); 1560 1852 } 1561 1853 1562 // Are all screenshots rendered ?1854 // Are all screenshots rendered (for now)? 1563 1855 if ( ! section.screenshotQueue.length ) { 1564 1856 return; 1565 1857 } … … 1595 1887 }, 1596 1888 1597 1889 /** 1890 * Update the number of themes in the section. 1891 * 1892 * @since 4.9.0 1893 */ 1894 updateCount: function ( count ) { 1895 if ( ! count ) { 1896 count = this.loaded; 1897 } 1898 1899 var displayed = this.contentContainer.find( '.themes-displayed' ), 1900 countEl = this.contentContainer.find( '.theme-count' ); 1901 1902 if ( 0 === count ) { 1903 countEl.text( count ); 1904 } else { 1905 // Animate the count change for emphasis. 1906 displayed.fadeOut( 180, function() { 1907 countEl.text( count ); 1908 displayed.fadeIn( 180 ); 1909 } ); 1910 wp.a11y.speak( api.settings.l10n.announceThemeCount.replace( '%d', count ) ); 1911 } 1912 }, 1913 1914 /** 1598 1915 * Advance the modal to the next theme. 1599 1916 * 1600 1917 * @since 4.2.0 … … 1614 1931 * @since 4.2.0 1615 1932 */ 1616 1933 getNextTheme: function () { 1617 var control, next;1618 control = api.control( 'theme_' + this.currentTheme );1934 var section = this, control, next; 1935 control = api.control( section.params.action + '_theme_' + this.currentTheme ); 1619 1936 next = control.container.next( 'li.customize-control-theme' ); 1620 1937 if ( ! next.length ) { 1621 1938 return false; 1622 1939 } 1623 next = next[0].id.replace( 'customize-control- ', '' );1940 next = next[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' ); 1624 1941 control = api.control( next ); 1625 1942 1626 1943 return control.params.theme; … … 1646 1963 * @since 4.2.0 1647 1964 */ 1648 1965 getPreviousTheme: function () { 1649 var control, previous;1650 control = api.control( 'theme_' + this.currentTheme );1966 var section = this, control, previous; 1967 control = api.control( section.params.action + '_theme_' + this.currentTheme ); 1651 1968 previous = control.container.prev( 'li.customize-control-theme' ); 1652 1969 if ( ! previous.length ) { 1653 1970 return false; 1654 1971 } 1655 previous = previous[0].id.replace( 'customize-control- ', '' );1972 previous = previous[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' ); 1656 1973 control = api.control( previous ); 1657 1974 1658 1975 return control.params.theme; … … 1734 2051 * @param {Object} theme 1735 2052 */ 1736 2053 showDetails: function ( theme, callback ) { 1737 var section = this , link;2054 var section = this; 1738 2055 callback = callback || function(){}; 1739 2056 section.currentTheme = theme.id; 1740 2057 section.overlay.html( section.template( theme ) ) … … 1743 2060 $( 'body' ).addClass( 'modal-open' ); 1744 2061 section.containFocus( section.overlay ); 1745 2062 section.updateLimits(); 1746 1747 link = section.overlay.find( '.inactive-theme > a' ); 1748 1749 link.on( 'click', function( event ) { 1750 event.preventDefault(); 1751 1752 // Short-circuit if request is currently being made. 1753 if ( link.hasClass( 'disabled' ) ) { 1754 return; 1755 } 1756 link.addClass( 'disabled' ); 1757 1758 section.loadThemePreview( theme.id ).fail( function() { 1759 link.removeClass( 'disabled' ); 1760 } ); 1761 } ); 2063 wp.a11y.speak( api.settings.l10n.announceThemeDetails.replace( '%s', theme.name ) ); 1762 2064 callback(); 1763 2065 }, 1764 2066 … … 1770 2072 closeDetails: function () { 1771 2073 $( 'body' ).removeClass( 'modal-open' ); 1772 2074 this.overlay.fadeOut( 'fast' ); 1773 api.control( 'theme_' + this.currentTheme).focus();2075 api.control( this.params.action + '_theme_' + this.currentTheme ).container.find( '.theme' ).focus(); 1774 2076 }, 1775 2077 1776 2078 /** … … 1850 2152 } 1851 2153 if ( ! panel.contentContainer.parent().is( panel.headContainer ) ) { 1852 2154 container.append( panel.contentContainer ); 1853 panel.renderContent();1854 2155 } 2156 panel.renderContent(); 1855 2157 1856 2158 panel.deferred.embedded.resolve(); 1857 2159 }, … … 2061 2363 } 2062 2364 }); 2063 2365 2366 2367 /** 2368 * wp.customize.ThemesPanel 2369 * 2370 * Custom section for themes that displays without the customize preview. 2371 * 2372 * @constructor 2373 * @augments wp.customize.Panel 2374 * @augments wp.customize.Container 2375 */ 2376 api.ThemesPanel = api.Panel.extend({ 2377 installingThemes: [], 2378 2379 /** 2380 * @since 4.9.0 2381 */ 2382 attachEvents: function () { 2383 var panel = this; 2384 2385 // Attach regular panel events. 2386 api.Panel.prototype.attachEvents.apply( this ); 2387 2388 // Collapse panel to customize the current theme. 2389 panel.contentContainer.on( 'click', '.customize-theme', function() { 2390 panel.collapse(); 2391 }); 2392 2393 // Toggle between filtering and browsing themes on mobile. 2394 panel.contentContainer.on( 'click', '.see-themes, .filter-themes', function() { 2395 $( '.wp-full-overlay' ).toggleClass( 'showing-themes' ); 2396 }); 2397 2398 // Install (and maybe preview) a theme. 2399 panel.contentContainer.on( 'click', '.theme-install', function( event ) { 2400 panel.installTheme( event ); 2401 }); 2402 2403 // Update a theme. Theme cards have the class, the details modal has the id. 2404 panel.contentContainer.on( 'click', '.update-theme, #update-theme', function( event ) { 2405 // #update-theme is a link. 2406 event.preventDefault(); 2407 event.stopPropagation(); 2408 2409 panel.updateTheme( event ); 2410 }); 2411 2412 // Delete a theme. 2413 panel.contentContainer.on( 'click', '.delete-theme', function( event ) { 2414 panel.deleteTheme( event ); 2415 }); 2416 2417 _.bindAll( this, 'installTheme', 'updateTheme' ); 2418 }, 2419 2420 /** 2421 * Update UI to reflect expanded state 2422 * 2423 * @since 4.9.0 2424 * 2425 * @param {Boolean} expanded 2426 * @param {Object} args 2427 * @param {Boolean} args.unchanged 2428 * @param {Function} args.completeCallback 2429 */ 2430 onChangeExpanded: function ( expanded, args ) { 2431 2432 // Expand/collapse the panel normally. 2433 api.Panel.prototype.onChangeExpanded.apply( this, [ expanded, args ] ); 2434 2435 // Immediately call the complete callback if there were no changes 2436 if ( args.unchanged ) { 2437 if ( args.completeCallback ) { 2438 args.completeCallback(); 2439 } 2440 return; 2441 } 2442 2443 // Note: there is a second argument 'args' passed 2444 var panel = this, 2445 overlay = panel.headContainer.closest( '.wp-full-overlay' ); 2446 2447 if ( expanded ) { 2448 overlay 2449 .addClass( 'in-themes-panel' ).addClass( 'showing-themes' ) 2450 .delay( 200 ).find( '.customize-themes-full-container' ).addClass( 'animate' ); 2451 2452 // Automatically open the installed themes section. 2453 api.section( 'installed_themes' ).expand(); 2454 } else { 2455 overlay 2456 .removeClass( 'in-themes-panel' ) 2457 .find( '.customize-themes-full-container' ).removeClass( 'animate' ); 2458 } 2459 }, 2460 2461 /** 2462 * Install a theme via wp.updates. 2463 * 2464 * @since 4.9.0 2465 */ 2466 installTheme: function( event ) { 2467 var panel = this, preview = false, slug = $( event.target ).data( 'slug' ); 2468 2469 if ( -1 !== $.inArray( this.installingThemes, slug ) ) { 2470 return; // Theme is already being installed. 2471 } 2472 2473 wp.updates.maybeRequestFilesystemCredentials( event ); 2474 2475 $( document ).one( 'wp-theme-install-success', function( event, response ) { 2476 var theme = false, customizeId, themeControl; 2477 if ( preview ) { 2478 2479 // Update loading message. Everything else is handled by reloading the page. 2480 // $( '#customize-themes-loading-container span' ).hide(); 2481 // $( '#customize-themes-loading-container .customize-loading-text' ).css( 'display', 'block' ); 2482 2483 panel.loadThemePreview( slug ).fail( function() { 2484 $( '.wp-full-overlay' ).removeClass( 'customize-loading' ); 2485 } ); 2486 2487 } else { 2488 api.control.each( function( control ) { 2489 if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) { 2490 theme = control.params.theme; // Used below to add theme control. 2491 control.rerenderAsInstalled( true ); 2492 } 2493 }); 2494 2495 // Don't add the same theme more than once. 2496 if ( ! theme || 'undefined' !== typeof api.control( 'installed_theme_' + theme.id ) ) { 2497 return; 2498 } 2499 2500 // Add theme control to installed section. 2501 theme.type = 'installed'; 2502 customizeId = 'installed_theme_' + theme.id; 2503 themeControl = new api.controlConstructor.theme( customizeId, { 2504 params: { 2505 type: 'theme', 2506 content: $( '<li class="customize-control customize-control-theme"></li>' ).attr( 'id', 'customize-control-theme-installed_' + theme.id ).prop( 'outerHTML' ), 2507 section: 'installed_themes', 2508 active: true, 2509 theme: theme, 2510 priority: 0 // Add all newly-installed themes to the top. 2511 }, 2512 previewer: api.previewer 2513 } ); 2514 2515 api.control.add( customizeId, themeControl ); 2516 api.control( customizeId ).container.trigger( 'render-screenshot' ); 2517 2518 // Close the details modal if it's open to the installed theme. 2519 api.section.each( function( section ) { 2520 if ( 'themes' === section.params.type ) { 2521 if ( theme.id === section.currentTheme ) { // Don't close the modal if the user has navigated elsewhere. 2522 section.closeDetails(); 2523 } 2524 } 2525 }); 2526 } 2527 } ); 2528 2529 this.installingThemes.push( $( event.target ).data( 'slug' ) ); // Note: we don't remove elements from installingThemes, since they shouldn't be installed again. 2530 wp.updates.installTheme( { 2531 slug: slug 2532 } ); 2533 2534 // Also preview the theme as the event is triggered on Install & Preview. 2535 if ( $( event.target ).hasClass( 'preview' ) ) { 2536 preview = true; 2537 $( '.wp-full-overlay' ).addClass( 'customize-loading' ); 2538 } 2539 }, 2540 2541 /** 2542 * Load theme preview. 2543 * 2544 * @since 4.9.0 2545 * 2546 * @param {string} themeId Theme ID. 2547 * @returns {jQuery.promise} Promise. 2548 */ 2549 loadThemePreview: function( themeId ) { 2550 var deferred = $.Deferred(), onceProcessingComplete, overlay, urlParser; 2551 2552 urlParser = document.createElement( 'a' ); 2553 urlParser.href = location.href; 2554 urlParser.search = $.param( _.extend( 2555 api.utils.parseQueryString( urlParser.search.substr( 1 ) ), 2556 { 2557 theme: themeId, 2558 changeset_uuid: api.settings.changeset.uuid 2559 } 2560 ) ); 2561 2562 // Update loading message. Everything else is handled by reloading the page. 2563 $( '#customize-themes-loading-container span' ).hide(); 2564 $( '#customize-themes-loading-container .customize-loading-text' ).css( 'display', 'block' ); 2565 overlay = $( '.wp-full-overlay' ); 2566 overlay.addClass( 'customize-loading' ); 2567 2568 onceProcessingComplete = function() { 2569 var request; 2570 if ( api.state( 'processing' ).get() > 0 ) { 2571 return; 2572 } 2573 2574 api.state( 'processing' ).unbind( onceProcessingComplete ); 2575 2576 request = api.requestChangesetUpdate(); 2577 request.done( function() { 2578 $( window ).off( 'beforeunload.customize-confirm' ); 2579 window.location.href = urlParser.href; 2580 } ); 2581 request.fail( function() { 2582 overlay.removeClass( 'customize-loading' ); 2583 } ); 2584 }; 2585 2586 if ( 0 === api.state( 'processing' ).get() ) { 2587 onceProcessingComplete(); 2588 } else { 2589 api.state( 'processing' ).bind( onceProcessingComplete ); 2590 } 2591 2592 return deferred.promise(); 2593 }, 2594 2595 /** 2596 * Update a theme via wp.updates. 2597 * 2598 * @since 4.9.0 2599 */ 2600 updateTheme: function( event ) { 2601 wp.updates.maybeRequestFilesystemCredentials( event ); 2602 2603 $( document ).one( 'wp-theme-update-success', function( event, response ) { 2604 // Rerender the control to reflect the update. 2605 api.control.each( function( control ) { 2606 if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) { 2607 control.params.theme.hasUpdate = false; 2608 control.rerenderAsInstalled( true ); 2609 } 2610 }); 2611 } ); 2612 2613 wp.updates.updateTheme( { 2614 slug: $( event.target ).closest( '.notice' ).data( 'slug' ) 2615 } ); 2616 }, 2617 2618 /** 2619 * Delete a theme via wp.updates. 2620 * 2621 * @since 4.9.0 2622 */ 2623 deleteTheme: function( event ) { 2624 var theme, section; 2625 theme = $( event.target ).data( 'slug' ); 2626 section = api.section( 'installed_themes' ); 2627 2628 event.preventDefault(); 2629 2630 // Confirmation dialog for deleting a theme. 2631 if ( ! window.confirm( api.settings.l10n.confirmDeleteTheme ) ) { 2632 return; 2633 } 2634 2635 wp.updates.maybeRequestFilesystemCredentials( event ); 2636 2637 $( document ).one( 'wp-theme-delete-success', function() { 2638 var control = api.control( 'installed_theme_' + theme ); 2639 2640 // Remove theme control. 2641 control.container.remove(); 2642 api.control.remove( control.id ); 2643 2644 // Update installed count. 2645 section.loaded = section.loaded - 1; 2646 section.updateCount(); 2647 2648 // Rerender any other theme controls as uninstalled. 2649 api.control.each( function( control ) { 2650 if ( 'theme' === control.params.type && control.params.theme.id === theme ) { 2651 control.rerenderAsInstalled( false ); 2652 } 2653 }); 2654 } ); 2655 2656 wp.updates.deleteTheme( { 2657 slug: theme 2658 } ); 2659 2660 // Close modal and focus the section. 2661 section.closeDetails(); 2662 section.focus(); 2663 } 2664 2665 }); 2666 2667 2064 2668 /** 2065 2669 * A Customizer Control. 2066 2670 * … … 2410 3014 * @param {Boolean} active 2411 3015 * @param {Object} args 2412 3016 * @param {Number} args.duration 2413 * @param { Callback} args.completeCallback3017 * @param {Function} args.completeCallback 2414 3018 */ 2415 3019 onChangeActive: function ( active, args ) { 2416 3020 if ( args.unchanged ) { … … 3582 4186 api.ThemeControl = api.Control.extend({ 3583 4187 3584 4188 touchDrag: false, 3585 isRendered: false, 3586 3587 /** 3588 * Defer rendering the theme control until the section is displayed. 3589 * 3590 * @since 4.2.0 3591 */ 3592 renderContent: function () { 3593 var control = this, 3594 renderContentArgs = arguments; 3595 3596 api.section( control.section(), function( section ) { 3597 if ( section.expanded() ) { 3598 api.Control.prototype.renderContent.apply( control, renderContentArgs ); 3599 control.isRendered = true; 3600 } else { 3601 section.expanded.bind( function( expanded ) { 3602 if ( expanded && ! control.isRendered ) { 3603 api.Control.prototype.renderContent.apply( control, renderContentArgs ); 3604 control.isRendered = true; 3605 } 3606 } ); 3607 } 3608 } ); 3609 }, 4189 screenshotRendered: false, 3610 4190 3611 4191 /** 3612 4192 * @since 4.2.0 … … 3630 4210 } 3631 4211 3632 4212 // Prevent the modal from showing when the user clicks the action button. 3633 if ( $( event.target ).is( '.theme-actions .button' ) ) { 3634 return; 3635 } 3636 3637 api.section( control.section() ).loadThemePreview( control.params.theme.id ); 3638 }); 3639 3640 control.container.on( 'click keydown', '.theme-actions .theme-details', function( event ) { 3641 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 4213 if ( $( event.target ).is( '.theme-actions .button, .update-theme' ) ) { 3642 4214 return; 3643 4215 } 3644 4216 3645 4217 event.preventDefault(); // Keep this AFTER the key filter above 3646 3647 4218 api.section( control.section() ).showDetails( control.params.theme ); 3648 4219 }); 3649 4220 … … 3654 4225 if ( source ) { 3655 4226 $screenshot.attr( 'src', source ); 3656 4227 } 4228 control.screenshotRendered = true; 3657 4229 }); 3658 4230 }, 3659 4231 3660 4232 /** 3661 * Show or hide the theme based on the presence of the term in the title, description, and author.4233 * Show or hide the theme based on the presence of the term in the title, description, tags, and author. 3662 4234 * 3663 4235 * @since 4.2.0 3664 4236 */ … … 3674 4246 } else { 3675 4247 control.deactivate(); 3676 4248 } 4249 }, 4250 4251 /** 4252 * Rerender the theme from its JS template with the installed type. 4253 * 4254 * @since 4.9.0 4255 */ 4256 rerenderAsInstalled: function( installed ) { 4257 var control = this, section; 4258 if ( installed ) { 4259 control.params.theme.type = 'installed'; 4260 } else { 4261 section = api.section( control.params.section ); 4262 control.params.theme.type = section.params.action; 4263 } 4264 control.renderContent(); // replaces existing content 4265 control.container.trigger( 'render-screenshot' ); 3677 4266 } 3678 4267 }); 3679 4268 … … 4619 5208 theme: api.ThemeControl, 4620 5209 code_editor: api.CodeEditorControl 4621 5210 }; 4622 api.panelConstructor = {}; 5211 api.panelConstructor = { 5212 themes: api.ThemesPanel 5213 }; 4623 5214 api.sectionConstructor = { 4624 5215 themes: api.ThemesSection 4625 5216 }; … … 4737 5328 4738 5329 // Sort the sections within each panel 4739 5330 api.panel.each( function ( panel ) { 5331 if ( 'themes' === panel.id ) { 5332 return; // Don't reflow theme sections, as doing so moves them after the themes container. 5333 } 5334 4740 5335 var sections = panel.sections(), 4741 5336 sectionHeadContainers = _.pluck( sections, 'headContainer' ); 4742 5337 rootNodes.push( panel ); … … 5459 6054 // Collapse the most granular expanded object. 5460 6055 collapsedObject = expandedControls[0] || expandedSections[0] || expandedPanels[0]; 5461 6056 if ( collapsedObject ) { 6057 if ( 'themes' === collapsedObject.params.type ) { 6058 // Themes panel or section. 6059 if ( $( 'body' ).hasClass( 'modal-open' ) ) { 6060 collapsedObject.closeDetails(); 6061 } else { 6062 // If we're collapsing a section, collapse the panel also. 6063 wp.customize.panel( 'themes' ).collapse(); 6064 } 6065 return; 6066 } 5462 6067 collapsedObject.collapse(); 5463 6068 event.preventDefault(); 5464 6069 } -
src/wp-admin/js/updates.js
diff --git a/src/wp-admin/js/updates.js b/src/wp-admin/js/updates.js index 233714f589..195c64c748 100644
a b 183 183 if ( $notice.length ) { 184 184 $notice.replaceWith( $adminNotice ); 185 185 } else { 186 $( '.wrap' ).find( '> h1' ).after( $adminNotice ); 186 if ( 'customize' === pagenow ) { 187 $( '.customize-themes-notifications' ).append( $adminNotice ); 188 } else { 189 $( '.wrap' ).find( '> h1' ).after( $adminNotice ); 190 } 187 191 } 188 192 189 193 $document.trigger( 'wp-updates-notice-added' ); … … 930 934 if ( 'themes-network' === pagenow ) { 931 935 $notice = $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' ); 932 936 937 } else if ( 'customize' === pagenow ) { 938 939 // Update the theme details UI. 940 $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' ); 941 942 $notice.find( 'h3' ).remove(); 943 944 // Add the top-level UI, and update both. 945 $notice = $notice.add( $( '#customize-control-theme-installed_' + args.slug ).find( '.update-message' ) ); 946 $notice = $notice.addClass( 'updating-message' ).find( 'p' ); 947 933 948 } else { 934 949 $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' ); 935 950 … … 972 987 }, 973 988 $notice, newText; 974 989 990 if ( 'customize' === pagenow ) { 991 $theme = wp.customize.control( 'installed_theme_' + response.slug ).container; 992 } 993 975 994 if ( 'themes-network' === pagenow ) { 976 995 $notice = $theme.find( '.update-message' ); 977 996 … … 1026 1045 return; 1027 1046 } 1028 1047 1048 if ( 'customize' === pagenow ) { 1049 $theme = wp.customize.control( 'installed_theme_' + response.slug ).container; 1050 } 1051 1029 1052 if ( 'themes-network' === pagenow ) { 1030 1053 $notice = $theme.find( '.update-message ' ); 1031 1054 } else { … … 1162 1185 return; 1163 1186 } 1164 1187 1165 if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) { 1166 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1167 $card = $( '.install-theme-info' ).prepend( $message ); 1188 if ( 'customize' === pagenow ) { 1189 if ( $document.find( 'body' ).hasClass( 'modal-open' ) ) { 1190 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1191 $card = $( '.theme-overlay .theme-info' ).prepend( $message ); 1192 } else { 1193 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1194 $card = $button.closest( '.theme' ).addClass( 'theme-install-failed' ).append( $message ); 1195 } 1196 $( '.wp-full-overlay' ).removeClass( 'customize-loading' ); 1168 1197 } else { 1169 $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message ); 1170 $button = $card.find( '.theme-install' ); 1198 if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) { 1199 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1200 $card = $( '.install-theme-info' ).prepend( $message ); 1201 } else { 1202 $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message ); 1203 $button = $card.find( '.theme-install' ); 1204 } 1171 1205 } 1172 1206 1173 1207 $button -
src/wp-includes/class-wp-customize-manager.php
diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php index 648740df18..a957046c5c 100644
a b public function __construct( $args = array() ) { 288 288 289 289 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' ); 290 290 291 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-panel.php' ); 291 292 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php' ); 292 293 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php' ); 293 294 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-section.php' ); … … public function __construct( $args = array() ) { 343 344 344 345 add_action( 'wp_ajax_customize_save', array( $this, 'save' ) ); 345 346 add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) ); 347 add_action( 'wp_ajax_customize-load-themes', array( $this, 'load_themes_ajax' ) ); 346 348 347 349 add_action( 'customize_register', array( $this, 'register_controls' ) ); 348 350 add_action( 'customize_register', array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first … … public function __construct( $args = array() ) { 359 361 360 362 // Export the settings to JS via the _wpCustomizeSettings variable. 361 363 add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 ); 364 365 // Add theme update notices. 366 if ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) ) { 367 require_once( ABSPATH . '/wp-admin/includes/update.php' ); 368 add_action( 'customize_controls_print_footer_scripts', 'wp_print_admin_notice_templates' ); 369 } 362 370 } 363 371 364 372 /** … … public function enqueue_control_scripts() { 3323 3331 foreach ( $this->controls as $control ) { 3324 3332 $control->enqueue(); 3325 3333 } 3334 3335 if ( ! is_multisite() && ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) || current_user_can( 'delete_themes' ) ) ) { 3336 wp_enqueue_script( 'updates' ); 3337 } 3326 3338 } 3327 3339 3328 3340 /** … … public function get_nonces() { 3527 3539 $nonces = array( 3528 3540 'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ), 3529 3541 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ), 3542 'switch-themes' => wp_create_nonce( 'switch-themes' ), 3530 3543 ); 3531 3544 3532 3545 /** … … public function customize_pane_settings() { 3600 3613 'autofocus' => $this->get_autofocus(), 3601 3614 'documentTitleTmpl' => $this->get_document_title_template(), 3602 3615 'previewableDevices' => $this->get_previewable_devices(), 3616 'l10n' => array( 3617 'confirmDeleteTheme' => __( 'Are you sure you want to delete this theme?' ), 3618 /* translators: %d is the number of theme search results, which cannot currently consider singular vs. plural forms */ 3619 'themeSearchResults' => __( '%d themes found' ), 3620 /* translators: %d is the number of themes being displayed, which cannot currently consider singular vs. plural forms */ 3621 'announceThemeCount' => __( 'Displaying %d themes' ), 3622 'announceThemeDetails' => __( 'Showing details for theme: %s' ), 3623 ), 3603 3624 ); 3604 3625 3605 3626 // Prepare Customize Section objects to pass to JavaScript. … … public function register_controls() { 3702 3723 3703 3724 /* Panel, Section, and Control Types */ 3704 3725 $this->register_panel_type( 'WP_Customize_Panel' ); 3726 $this->register_panel_type( 'WP_Customize_Themes_Panel' ); 3705 3727 $this->register_section_type( 'WP_Customize_Section' ); 3706 3728 $this->register_section_type( 'WP_Customize_Sidebar_Section' ); 3729 $this->register_section_type( 'WP_Customize_Themes_Section' ); 3707 3730 $this->register_control_type( 'WP_Customize_Color_Control' ); 3708 3731 $this->register_control_type( 'WP_Customize_Media_Control' ); 3709 3732 $this->register_control_type( 'WP_Customize_Upload_Control' ); … … public function register_controls() { 3715 3738 $this->register_control_type( 'WP_Customize_Theme_Control' ); 3716 3739 $this->register_control_type( 'WP_Customize_Code_Editor_Control' ); 3717 3740 3718 /* Themes */3741 /* Themes (controls are loaded via ajax) */ 3719 3742 3720 $this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array( 3721 'title' => $this->theme()->display( 'Name' ), 3722 'capability' => 'switch_themes', 3723 'priority' => 0, 3743 $this->add_panel( new WP_Customize_Themes_Panel( $this, 'themes', array( 3744 'title' => $this->theme()->display( 'Name' ), 3745 '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.' ), 3746 'capability' => 'switch_themes', 3747 'priority' => 0, 3724 3748 ) ) ); 3725 3749 3726 // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience). 3727 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array( 3728 'capability' => 'switch_themes', 3750 $this->add_section( new WP_Customize_Themes_Section( $this, 'installed_themes', array( 3751 'title' => __( 'Installed' ), 3752 'action' => 'installed', 3753 'capability' => 'switch_themes', 3754 'panel' => 'themes', 3755 'priority' => 0, 3729 3756 ) ) ); 3730 3757 3731 require_once( ABSPATH . 'wp-admin/includes/theme.php' ); 3732 3733 // Theme Controls. 3734 3735 // Add a control for the active/original theme. 3736 if ( ! $this->is_theme_active() ) { 3737 $themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) ); 3738 $active_theme = current( $themes ); 3739 $active_theme['isActiveTheme'] = true; 3740 $this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array( 3741 'theme' => $active_theme, 3742 'section' => 'themes', 3743 'settings' => 'active_theme', 3758 if ( ! is_multisite() ) { 3759 $this->add_section( new WP_Customize_Themes_Section( $this, 'wporg_themes', array( 3760 'title' => __( 'WordPress.org' ), 3761 'action' => 'wporg', 3762 'capability' => 'install_themes', 3763 'panel' => 'themes', 3764 'priority' => 5, 3744 3765 ) ) ); 3745 3766 } 3746 3767 3747 $themes = wp_prepare_themes_for_js(); 3748 foreach ( $themes as $theme ) { 3749 if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) { 3750 continue; 3751 } 3752 3753 $theme_id = 'theme_' . $theme['id']; 3754 $theme['isActiveTheme'] = false; 3755 $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array( 3756 'theme' => $theme, 3757 'section' => 'themes', 3758 'settings' => 'active_theme', 3759 ) ) ); 3760 } 3768 // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience). 3769 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array( 3770 'capability' => 'switch_themes', 3771 ) ) ); 3761 3772 3762 3773 /* Site Identity */ 3763 3774 … … public function register_dynamic_settings() { 4259 4270 } 4260 4271 4261 4272 /** 4273 * Load themes into the theme browsing/installation UI. 4274 * 4275 * @since 4.9.0 4276 */ 4277 public function load_themes_ajax() { 4278 check_ajax_referer( 'switch-themes', 'switch-themes-nonce' ); 4279 4280 if ( ! current_user_can( 'switch_themes' ) ) { 4281 wp_die( -1 ); 4282 } 4283 4284 if ( empty( $_POST['theme_action'] ) ) { 4285 wp_send_json_error( 'missing_theme_action' ); 4286 } 4287 4288 require_once( ABSPATH . 'wp-admin/includes/theme.php' ); 4289 if ( 'installed' === $_POST['theme_action'] ) { 4290 $themes = array( 'themes' => wp_prepare_themes_for_js() ); 4291 foreach ( $themes['themes'] as &$theme ) { 4292 $theme['type'] = 'installed'; 4293 // Set active based on customized theme. 4294 if ( $_POST['customized_theme'] === $theme['id'] ) { 4295 $theme['active'] = true; 4296 } else { 4297 $theme['active'] = false; 4298 } 4299 } 4300 } elseif ( 'wporg' === $_POST['theme_action'] ) { 4301 if ( ! current_user_can( 'install_themes' ) ) { 4302 wp_die( -1 ); 4303 } 4304 4305 // Arguments for all queries. 4306 $args = array( 4307 'per_page' => 100, 4308 'page' => absint( $_POST['page'] ), 4309 'fields' => array( 4310 'screenshot_url' => true, 4311 'description' => true, 4312 'rating' => true, 4313 'downloaded' => true, 4314 'downloadlink' => true, 4315 'last_updated' => true, 4316 'homepage' => true, 4317 'num_ratings' => true, 4318 'tags' => true, 4319 'parent' => true, 4320 //'extended_author' => true, @todo: WordPress.org throws a 500 server error when this is here. 4321 ), 4322 ); 4323 4324 // Define query filters based on user input. 4325 if ( ! array_key_exists( 'search', $_POST ) ) { 4326 $args['search'] = ''; 4327 } else { 4328 $args['search'] = wp_unslash( $_POST['search'] ); 4329 } 4330 4331 if ( ! array_key_exists( 'tags', $_POST ) ) { 4332 $args['tag'] = ''; 4333 } else { 4334 $args['tag'] = wp_unslash( $_POST['tags'] ); 4335 } 4336 4337 if ( '' === $args['search'] && '' === $args['tag'] ) { 4338 $args['browse'] = 'new'; // Sort by latest themes by default. 4339 } 4340 4341 // Load themes from the .org API. 4342 $themes = themes_api( 'query_themes', $args ); 4343 if ( is_wp_error( $themes ) ) { 4344 wp_send_json_error(); 4345 } 4346 4347 // This list matches the allowed tags in wp-admin/includes/theme-install.php. 4348 $themes_allowedtags = array('a' => array('href' => array(), 'title' => array(), 'target' => array()), 4349 'abbr' => array('title' => array()), 'acronym' => array('title' => array()), 4350 'code' => array(), 'pre' => array(), 'em' => array(), 'strong' => array(), 4351 'div' => array(), 'p' => array(), 'ul' => array(), 'ol' => array(), 'li' => array(), 4352 'h1' => array(), 'h2' => array(), 'h3' => array(), 'h4' => array(), 'h5' => array(), 'h6' => array(), 4353 'img' => array('src' => array(), 'class' => array(), 'alt' => array()) 4354 ); 4355 4356 // Prepare a list of installed themes to check against before the loop. 4357 $installed_themes = array(); 4358 $wp_themes = wp_get_themes(); 4359 foreach ( $wp_themes as $theme ) { 4360 $installed_themes[] = $theme->get_stylesheet(); 4361 } 4362 $update_php = network_admin_url( 'update.php?action=install-theme' ); 4363 4364 // Set up properties for themes available on WordPress.org. 4365 foreach ( $themes->themes as &$theme ) { 4366 $theme->install_url = add_query_arg( array( 4367 'theme' => $theme->slug, 4368 '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ), 4369 ), $update_php ); 4370 4371 $theme->name = wp_kses( $theme->name, $themes_allowedtags ); 4372 $theme->author = wp_kses( $theme->author, $themes_allowedtags ); 4373 $theme->version = wp_kses( $theme->version, $themes_allowedtags ); 4374 $theme->description = wp_kses( $theme->description, $themes_allowedtags ); 4375 $theme->tags = implode( ', ', $theme->tags ); 4376 $theme->stars = wp_star_rating( array( 'rating' => $theme->rating, 'type' => 'percent', 'number' => $theme->num_ratings, 'echo' => false ) ); 4377 $theme->num_ratings = number_format_i18n( $theme->num_ratings ); 4378 $theme->preview_url = set_url_scheme( $theme->preview_url ); 4379 4380 // Handle themes that are already installed as installed themes. 4381 if ( in_array( $theme->slug, $installed_themes, true ) ) { 4382 $theme->type = 'installed'; 4383 } else { 4384 $theme->type = $_POST['theme_action']; 4385 } 4386 4387 // Set active based on customized theme. 4388 if ( $_POST['customized_theme'] === $theme->slug ) { 4389 $theme->active = true; 4390 } else { 4391 $theme->active = false; 4392 } 4393 4394 // Map available theme properties to installed theme properties. 4395 $theme->id = $theme->slug; 4396 $theme->screenshot = array( $theme->screenshot_url ); 4397 $theme->authorAndUri = $theme->author; 4398 $theme->parent = ( $theme->slug === $theme->template ) ? false: $theme->template; // The .org API does not seem to return the parent in a documneted way; however, this check should yield a similar result in most cases. 4399 unset( $theme->slug ); 4400 unset( $theme->screenshot_url ); 4401 unset( $theme->author ); 4402 } // End foreach(). 4403 } // End if(). 4404 wp_send_json_success( $themes ); 4405 } 4406 4407 4408 /** 4262 4409 * Callback for validating the header_textcolor value. 4263 4410 * 4264 4411 * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash(). -
src/wp-includes/customize/class-wp-customize-theme-control.php
diff --git a/src/wp-includes/customize/class-wp-customize-theme-control.php b/src/wp-includes/customize/class-wp-customize-theme-control.php index ffc9c878ae..556ce22698 100644
a b public function render_content() {} 57 57 * @since 4.2.0 58 58 */ 59 59 public function content_template() { 60 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); 61 $active_url = esc_url( remove_query_arg( 'customize_theme', $current_url ) ); 62 $preview_url = esc_url( add_query_arg( 'customize_theme', '__THEME__', $current_url ) ); // Token because esc_url() strips curly braces. 63 $preview_url = str_replace( '__THEME__', '{{ data.theme.id }}', $preview_url ); 60 /* translators: %s: theme name */ 61 $details_label = sprintf( __( 'Details for theme: %s' ), '{{ data.theme.name }}' ); 62 /* translators: %s: theme name */ 63 $customize_label = sprintf( __( 'Customize theme: %s' ), '{{ data.theme.name }}' ); 64 /* translators: %s: theme name */ 65 $preview_label = sprintf( __( 'Live preview theme: %s' ), '{{ data.theme.name }}' ); 66 /* translators: %s: theme name */ 67 $install_label = sprintf( __( 'Install and preview theme: %s' ), '{{ data.theme.name }}' ); 64 68 ?> 65 <# if ( data.theme. isActiveTheme ) { #>66 <div class="theme active" tabindex="0" data-preview-url="<?php echo esc_attr( $active_url ); ?>" aria-describedby="{{ data.theme.id }}-action {{ data.theme.id }}-name">69 <# if ( data.theme.active ) { #> 70 <div class="theme active" tabindex="0" aria-describedby="{{ data.section }}-{{ data.theme.id }}-action {{ data.theme.id }}-name"> 67 71 <# } else { #> 68 <div class="theme" tabindex="0" data-preview-url="<?php echo esc_attr( $preview_url ); ?>" aria-describedby="{{ data.theme.id }}-action {{ data.theme.id }}-name">72 <div class="theme" tabindex="0" aria-describedby="{{ data.section }}-{{ data.theme.id }}-action {{ data.theme.id }}-name"> 69 73 <# } #> 70 74 71 <# if ( data.theme.screenshot [0] ) { #>75 <# if ( data.theme.screenshot && data.theme.screenshot[0] ) { #> 72 76 <div class="theme-screenshot"> 73 77 <img data-src="{{ data.theme.screenshot[0] }}" alt="" /> 74 78 </div> … … public function content_template() { 76 80 <div class="theme-screenshot blank"></div> 77 81 <# } #> 78 82 79 <# if ( data.theme.isActiveTheme ) { #> 80 <span class="more-details" id="{{ data.theme.id }}-action"><?php _e( 'Customize' ); ?></span> 81 <# } else { #> 82 <span class="more-details" id="{{ data.theme.id }}-action"><?php _e( 'Live Preview' ); ?></span> 83 <# } #> 83 <span class="more-details theme-details" id="{{ data.section }}-{{ data.theme.id }}-action" aria-label="<?php echo esc_attr( $details_label ); ?>"><?php _e( 'Theme Details' ); ?></span> 84 84 85 85 <div class="theme-author"><?php 86 86 /* translators: Theme author name */ 87 87 printf( _x( 'By %s', 'theme author' ), '{{ data.theme.author }}' ); 88 88 ?></div> 89 89 90 <# if ( data.theme.isActiveTheme ) { #> 91 <h3 class="theme-name" id="{{ data.theme.id }}-name"> 90 <# if ( 'installed' === data.theme.type && data.theme.hasUpdate ) { #> 91 <div class="update-message notice inline notice-warning notice-alt" data-slug="{{ data.theme.id }}"><p><?php printf( __( 'New version available. %s' ), '<button class="button-link update-theme" type="button">' . __( 'Update now' ) . '</button>' ); ?></p></div> 92 <# } #> 93 94 <# if ( data.theme.active ) { #> 95 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name"> 92 96 <?php 93 97 /* translators: %s: theme name */ 94 printf( __( '<span> Active:</span> %s' ), '{{{ data.theme.name }}}' );98 printf( __( '<span>Current:</span> %s' ), '{{ data.theme.name }}' ); 95 99 ?> 96 100 </h3> 101 <div class="theme-actions"> 102 <button type="button" class="button button-primary customize-theme" aria-label="<?php echo esc_attr( $customize_label ); ?>"><?php _e( 'Customize' ); ?></button> 103 </div> 104 <div class="notice notice-success notice-alt"><p><?php _e( 'Installed' ); ?></p></div> 105 <# } else if ( 'installed' === data.theme.type ) { #> 106 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> 107 <div class="theme-actions"> 108 <button type="button" class="button button-primary preview-theme" aria-label="<?php echo esc_attr( $preview_label ); ?>" data-slug="{{ data.theme.id }}"><?php _e( 'Live Preview' ); ?></span> 109 </div> 110 <div class="notice notice-success notice-alt"><p><?php _e( 'Installed' ); ?></p></div> 97 111 <# } else { #> 98 <h3 class="theme-name" id="{{ data. theme.id }}-name">{{{ data.theme.name }}}</h3>112 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> 99 113 <div class="theme-actions"> 100 <button type="button" class="button theme-details"><?php _e( 'Theme Details' ); ?></button>114 <button type="button" class="button button-primary theme-install preview" aria-label="<?php echo esc_attr( $install_label ); ?>" data-slug="{{ data.theme.id }}" data-name="{{ data.theme.name }}"><?php _e( 'Install & Preview' ); ?></button> 101 115 </div> 102 116 <# } #> 103 117 </div> -
new file src/wp-includes/customize/class-wp-customize-themes-panel.php
diff --git a/src/wp-includes/customize/class-wp-customize-themes-panel.php b/src/wp-includes/customize/class-wp-customize-themes-panel.php new file mode 100644 index 0000000000..c170b34a94
- + 1 <?php 2 /** 3 * Customize API: WP_Customize_Themes_Panel class 4 * 5 * @package WordPress 6 * @subpackage Customize 7 * @since 4.9.0 8 */ 9 10 /** 11 * Customize Themes Panel Class 12 * 13 * @since 4.9.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.9.0 23 * @var string 24 */ 25 public $type = 'themes'; 26 27 /** 28 * An Underscore (JS) template for rendering this panel's container. 29 * 30 * The themes panel renders a custom panel heading with the current theme and a switch themes button. 31 * 32 * @see WP_Customize_Panel::print_template() 33 * 34 * @since 4.9.0 35 */ 36 protected function render_template() { 37 ?> 38 <li id="accordion-section-{{ data.id }}" class="accordion-section control-panel-themes"> 39 <h3 class="accordion-section-title"> 40 <?php 41 if ( $this->manager->is_theme_active() ) { 42 echo '<span class="customize-action">' . __( 'Active theme' ) . '</span> {{ data.title }}'; 43 } else { 44 echo '<span class="customize-action">' . __( 'Previewing theme' ) . '</span> {{ data.title }}'; 45 } 46 ?> 47 48 <?php 49 if ( current_user_can( 'switch_themes' ) ) : ?> 50 <button type="button" class="button change-theme" aria-label="<?php _e( 'Change theme' ); ?>"><?php _ex( 'Change', 'theme' ); ?></button> 51 <?php endif; ?> 52 </h3> 53 <ul class="accordion-sub-container control-panel-content"></ul> 54 </li> 55 <?php 56 } 57 58 /** 59 * An Underscore (JS) template for this panel's content (but not its container). 60 * 61 * Class variables for this panel class are available in the `data` JS object; 62 * export custom variables by overriding WP_Customize_Panel::json(). 63 * 64 * @since 4.9.0 65 * 66 * @see WP_Customize_Panel::print_template() 67 */ 68 protected function content_template() { 69 ?> 70 <li class="panel-meta customize-info accordion-section <# if ( ! data.description ) { #> cannot-expand<# } #>"> 71 <button class="customize-panel-back" tabindex="-1"><span class="screen-reader-text"><?php _e( 'Back' ); ?></span></button> 72 <div class="accordion-section-title"> 73 <span class="preview-notice"><?php 74 /* translators: %s: themes panel title in the Customizer */ 75 echo sprintf( __( 'You are browsing %s' ), '<strong class="panel-title">' . __( 'Themes' ) . '</strong>' ); // Separate strings for consistency with other panels. 76 ?></span> 77 <?php if ( current_user_can( 'install_themes' ) && ! is_multisite() ) : ?> 78 <# if ( data.description ) { #> 79 <button class="customize-help-toggle dashicons dashicons-editor-help" tabindex="0" aria-expanded="false"><span class="screen-reader-text"><?php _e( 'Help' ); ?></span></button> 80 <# } #> 81 <?php endif; ?> 82 </div> 83 <?php if ( current_user_can( 'install_themes' ) && ! is_multisite() ) : ?> 84 <# if ( data.description ) { #> 85 <div class="description customize-panel-description"> 86 {{{ data.description }}} 87 </div> 88 <# } #> 89 <?php endif; ?> 90 </li> 91 <li id="customize-themes-loading-container"> 92 <span class="customize-loading-text-installing-theme"><?php _e( 'Downloading your new theme…' ); ?></span> 93 <span class="customize-loading-text"><?php _e( 'Setting up your live preview. This may take a bit.' ); ?></span> 94 </li><?php // Used as a full-screen overlay transition after clicking to preview a theme. ?> 95 <li class="customize-themes-full-container-container"> 96 <ul class="customize-themes-full-container"> 97 <li class="customize-themes-notifications"></li> 98 </ul> 99 </li> 100 <?php 101 } 102 } -
src/wp-includes/customize/class-wp-customize-themes-section.php
diff --git a/src/wp-includes/customize/class-wp-customize-themes-section.php b/src/wp-includes/customize/class-wp-customize-themes-section.php index 415ef47222..6573d1e759 100644
a b 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 within sections. 14 14 * 15 15 * @since 4.2.0 16 16 * 17 17 * @see WP_Customize_Section 18 18 */ 19 19 class WP_Customize_Themes_Section extends WP_Customize_Section { 20 21 20 /** 22 * Customize section type.21 * Section type. 23 22 * 24 23 * @since 4.2.0 25 24 * @var string … … class WP_Customize_Themes_Section extends WP_Customize_Section { 27 26 public $type = 'themes'; 28 27 29 28 /** 30 * Render the themes section, which behaves like a panel.29 * Theme section action. 31 30 * 32 * @since 4.2.0 31 * Defines the type of themes to load (installed, wporg, etc.). 32 * 33 * @since 4.9.0 34 * @var string 33 35 */ 34 protected function render() { 35 $classes = 'accordion-section control-section control-section-' . $this->type; 36 ?> 37 <li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>"> 38 <h3 class="accordion-section-title"> 39 <?php 40 if ( $this->manager->is_theme_active() ) { 41 echo '<span class="customize-action">' . __( 'Active theme' ) . '</span> ' . $this->title; 42 } else { 43 echo '<span class="customize-action">' . __( 'Previewing theme' ) . '</span> ' . $this->title; 44 } 45 ?> 36 public $action = ''; 46 37 47 <?php if ( count( $this->controls ) > 0 ) : ?> 48 <button type="button" class="button change-theme" tabindex="0"><?php _ex( 'Change', 'theme' ); ?></button> 49 <?php endif; ?> 50 </h3> 51 <div class="customize-themes-panel control-panel-content themes-php"> 52 <h3 class="accordion-section-title customize-section-title"> 53 <button class="customize-section-back" tabindex="0" type="button"><span class="screen-reader-text"><?php _e( 'Back' ); ?></span></button> 54 <span class="customize-action"><?php _e( 'Customizing' ); ?></span> 55 <?php _e( 'Themes' ); ?> 56 <span class="title-count theme-count"><?php echo count( $this->controls ) + 1 /* Active theme */; ?></span> 57 </h3> 58 <h3 class="accordion-section-title customize-section-title"> 59 <?php 60 if ( $this->manager->is_theme_active() ) { 61 echo '<span class="customize-action">' . __( 'Active theme' ) . '</span> ' . $this->title; 62 } else { 63 echo '<span class="customize-action">' . __( 'Previewing theme' ) . '</span> ' . $this->title; 64 } 65 ?> 66 <button type="button" class="button customize-theme"><?php _e( 'Customize' ); ?></button> 67 </h3> 38 /** 39 * Get section parameters for JS. 40 * 41 * @since 4.9.0 42 * @return array Exported parameters. 43 */ 44 public function json() { 45 $exported = parent::json(); 46 $exported['action'] = $this->action; 68 47 69 <div class="theme-overlay" tabindex="0" role="dialog" aria-label="<?php esc_attr_e( 'Theme Details' ); ?>"></div> 48 return $exported; 49 } 70 50 71 <div id="customize-container"></div> 72 <?php if ( count( $this->controls ) > 4 ) : ?> 73 <p><label for="themes-filter"> 74 <span class="screen-reader-text"><?php _e( 'Search installed themes…' ); ?></span> 75 <input type="text" id="themes-filter" placeholder="<?php esc_attr_e( 'Search installed themes…' ); ?>" /> 76 </label></p> 77 <?php endif; ?> 51 /** 52 * Render a themes section as a JS template. 53 * 54 * The template is only rendered by PHP once, so all actions are prepared at once on the server side. 55 * 56 * @since 4.9.0 57 */ 58 protected function render_template() { 59 ?> 60 <li id="accordion-section-{{ data.id }}" class="theme-section"> 61 <button type="button" class="customize-themes-section-title themes-section-{{ data.id }}">{{ data.title }}</button> 62 <?php if ( current_user_can( 'install_themes' ) || is_multisite() ) : // @todo: upload support ?> 63 <?php endif; ?> 64 <div class="customize-themes-section themes-section-{{ data.id }} control-section-content themes-php"> 65 <div class="theme-overlay" tabindex="0" role="dialog" aria-label="<?php esc_attr_e( 'Theme Details' ); ?>"></div> 78 66 <div class="theme-browser rendered"> 79 <ul class="themes accordion-section-content"> 80 </ul> 67 <div class="customize-preview-header themes-filter-bar"> 68 <?php $this->filter_bar_content_template(); ?> 69 </div> 70 <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> 71 <ul class="themes"> 72 </ul> 73 <p class="no-themes"><?php _e( 'No themes found. Try a different search.' ); ?></p> 74 <p class="spinner"></p> 81 75 </div> 82 76 </div> 83 77 </li> 84 78 <?php } 79 80 /** 81 * Render the filter bar portion of a themes section as a JS template. 82 * 83 * The template is only rendered by PHP once, so all actions are prepared at once on the server side. 84 * The filter bar container is rendered by @see `render_template()`. 85 * 86 * @since 4.9.0 87 */ 88 protected function filter_bar_content_template() { 89 ?> 90 <# if ( 'wporg' === data.action ) { #> 91 <div class="search-form"> 92 <label class="screen-reader-text" for="wp-filter-search-input"><?php _e( 'Search themes…' ); ?></label> 93 <input placeholder="<?php _e( 'Search themes…' ); ?>" type="text" aria-describedby="live-search-desc" id="wp-filter-search-input" class="wp-filter-search"> 94 <span id="live-search-desc" class="screen-reader-text"><?php _e( 'The search results will be updated as you type.' ); ?></span> 95 </div> 96 <button type="button" class="button feature-filter-toggle"> 97 <?php /* translators: %s: number of filters selected. */ 98 printf( __( 'Filter themes (%s)' ), '<span class="theme-filter-count">0</span>' ); ?> 99 </button> 100 <div class="filter-drawer filter-details"> 101 <?php 102 $feature_list = get_theme_feature_list( false ); // @todo: Use the .org API instead of the local core feature list. The .org API is currently outdated and will be reconciled when the .org themes directory is next redesigned. 103 foreach ( $feature_list as $feature_name => $features ) { 104 echo '<fieldset class="filter-group">'; 105 $feature_name = esc_html( $feature_name ); 106 echo '<legend>' . $feature_name . '</legend>'; 107 echo '<div class="filter-group-feature">'; 108 foreach ( $features as $feature => $feature_name ) { 109 $feature = esc_attr( $feature ); 110 echo '<input type="checkbox" id="filter-id-' . $feature . '" value="' . $feature . '" /> '; 111 echo '<label for="filter-id-' . $feature . '">' . $feature_name . '</label><br>'; 112 } 113 echo '</div>'; 114 echo '</fieldset>'; 115 } 116 ?> 117 </div> 118 <# } else { #> 119 <p class="themes-filter-container"> 120 <label for="themes-filter"> 121 <span class="screen-reader-text"><?php _e( 'Search themes…' ); ?></span> 122 <input type="text" id="themes-filter" placeholder="<?php esc_attr_e( 'Search themes…' ); ?>" aria-describedby="live-search-desc" class="wp-filter-search wp-filter-search-themes" /> 123 <span id="live-search-desc" class="screen-reader-text"><?php _e( 'The search results will be updated as you type.' ); ?></span> 124 </label> 125 </p> 126 <# } #> 127 <div class="filter-themes-count"> 128 <span class="themes-displayed"><?php 129 /* translators: %s: number of themes displayed. */ 130 echo sprintf( __( '%s themes' ), '<span class="theme-count">0</span>' ); 131 ?></span> 132 <button type="button" class="button button-primary filter-themes"><?php _e( 'Filter themes' ); ?></button> 133 </div> 134 <?php } 85 135 } -
tests/phpunit/tests/customize/manager.php
diff --git a/tests/phpunit/tests/customize/manager.php b/tests/phpunit/tests/customize/manager.php index 1955a1cd74..0af267cbd5 100644
a b function test_customize_pane_settings() { 2351 2351 $data = json_decode( $json, true ); 2352 2352 $this->assertNotEmpty( $data ); 2353 2353 2354 $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts' ), array_keys( $data ) );2354 $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts', 'l10n' ), array_keys( $data ) ); 2355 2355 $this->assertEquals( $autofocus, $data['autofocus'] ); 2356 2356 $this->assertArrayHasKey( 'save', $data['nonce'] ); 2357 2357 $this->assertArrayHasKey( 'preview', $data['nonce'] );