Ticket #37661: 37661.12.diff
File 37661.12.diff, 106.9 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 6c81fa8247..32452e7df2 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 { 1256 1238 100% { opacity: 1; } 1257 1239 } 1258 1240 1259 /* #customize-container is reused from customize-loader.js, hence the naming. */ 1260 .wp-customizer .customize-loading #customize-container { 1241 .wp-customizer .customize-loading #customize-themes-loading-container { 1242 display: block; 1243 -webkit-animation: customize-reload .5s; /* Can't use `transition` because `display` changes here. */ 1244 animation: customize-reload .5s; 1245 } 1246 1247 .customize-loading #customize-themes-loading-container span { 1248 clear: both; 1249 color: #191e23; 1250 font-size: 18px; 1251 font-style: normal; 1252 margin: 0; 1253 padding: 2em 0; 1254 text-align: center; 1255 width: 100%; 1261 1256 display: block; 1262 -webkit-animation: customize-reload .75s; /* Can't use `transition` because `display` changes here. */ 1263 animation: customize-reload .75s; 1257 top: 50%; 1258 position: relative; 1259 } 1260 1261 .customize-loading #customize-themes-loading-container .customize-loading-text { 1262 display: none; 1263 } 1264 1265 #customize-theme-controls .control-panel-themes { 1266 border-bottom: none; 1264 1267 } 1265 1268 1266 #customize-theme-controls .control- section-themes.accordion-section-title:hover, /* Not a focusable element. */1267 #customize-theme-controls .control- section-themes.accordion-section-title {1269 #customize-theme-controls .control-panel-themes > .accordion-section-title:hover, /* Not a focusable element. */ 1270 #customize-theme-controls .control-panel-themes > .accordion-section-title { 1268 1271 cursor: default; 1269 1272 background: #fff; 1270 1273 color: #555d66; 1271 1274 border-top: 1px solid #ddd; 1272 1275 border-bottom: 1px solid #ddd; 1273 1276 border-left: none; 1274 margin-top: 0; 1275 } 1276 #customize-theme-controls .control-section-themes .customize-section-back { 1277 position: absolute; 1278 right: 0; 1279 top: 0; 1280 height: 80px; 1281 border-left: 1px solid #ddd; 1282 border-right: 4px solid #fff; 1283 } 1284 #customize-theme-controls .control-section-themes .customize-section-back:before { 1285 content: "\f345"; 1286 } 1287 #customize-theme-controls .control-section-themes .customize-section-back:hover, 1288 #customize-theme-controls .control-section-themes .customize-section-back:focus { 1289 border-right-color: #0073aa; 1277 border-right: none; 1278 margin: 0 0 15px 0; 1279 padding-right: 100px; /* Space for the button */ 1290 1280 } 1291 1281 1292 1282 #customize-theme-controls .control-section-themes .customize-themes-panel .accordion-section-title:first-child:hover, /* Not a focusable element. */ … … p.customize-section-description { 1294 1284 border-top: 0; 1295 1285 } 1296 1286 1297 #customize-theme-controls .control-section-themes > .accordion-section-title:hover, /* Not a focusable element. */ 1298 #customize-theme-controls .control-section-themes > .accordion-section-title { 1299 margin: 0 0 15px; 1300 } 1301 1302 #customize-controls .customize-themes-panel .accordion-section-title:hover, 1303 #customize-controls .customize-themes-panel .accordion-section-title { 1304 margin: 15px -8px; 1305 } 1306 1307 #customize-controls .control-section-themes .accordion-section-title, 1308 #customize-controls .customize-themes-panel .accordion-section-title { 1309 padding-right: 100px; /* Space for the button */ 1310 } 1311 1312 #customize-controls .control-section-themes .accordion-section-title span.customize-action, 1287 .control-panel-themes .accordion-section-title span.customize-action, 1313 1288 #customize-controls .customize-section-title span.customize-action { 1314 1289 font-size: 13px; 1315 1290 display: block; 1316 1291 font-weight: 400; 1317 1292 } 1318 1293 1319 #customize-controls .control-section-themes .accordion-section-title .change-theme, 1320 #customize-controls .customize-themes-panel .accordion-section-title .customize-theme { 1294 #customize-theme-controls .control-panel-themes .accordion-section-title .change-theme { 1321 1295 position: absolute; 1322 1296 right: 10px; 1323 1297 top: 50%; … … p.customize-section-description { 1325 1299 font-weight: 400; 1326 1300 } 1327 1301 1328 #customize- controls .control-section-themes .accordion-section-title:before{1302 #customize-theme-controls .control-panel-themes > .accordion-section-title:after { 1329 1303 display: none; 1330 1304 } 1331 1305 1332 #customize-controls .customize-themes-panel { 1333 padding: 0 8px; 1334 background: #f1f1f1; 1306 .control-panel-themes .customize-themes-full-container { 1307 position: fixed; 1308 top: 0; 1309 left: 0; 1310 -webkit-transition: .18s left ease-in-out; 1311 transition: .18s left ease-in-out; 1312 margin: 46px 0 0 300px; 1313 padding: 25px 0; 1314 overflow-y: scroll; 1315 width: calc(100% - 300px); 1316 height: calc(100% - 96px); 1317 background: #eee; 1318 z-index: 20; 1319 } 1320 1321 /* Animations for opening the themes panel */ 1322 #customize-header-actions .save, 1323 #customize-header-actions .spinner, 1324 #customize-header-actions .customize-controls-preview-toggle { 1325 position: relative; 1326 top: 0; 1327 -webkit-transition: .18s top ease-in-out; 1328 transition: .18s top ease-in-out; 1329 } 1330 1331 #customize-footer-actions, 1332 #customize-footer-actions .collapse-sidebar { 1333 bottom: 0; 1334 -webkit-transition: .18s bottom ease-in-out; 1335 transition: .18s bottom ease-in-out; 1336 } 1337 1338 .in-themes-panel:not(.animating) #customize-header-actions .save, 1339 .in-themes-panel:not(.animating) #customize-header-actions .spinner, 1340 .in-themes-panel:not(.animating) #customize-header-actions .customize-controls-preview-toggle, 1341 .in-themes-panel:not(.animating) #customize-preview, 1342 .in-themes-panel:not(.animating) #customize-footer-actions { 1343 visibility: hidden; 1344 } 1345 1346 .wp-full-overlay.in-themes-panel { 1347 background: #eee; /* Prevents a black flash when fading in the panel */ 1348 } 1349 1350 .in-themes-panel #customize-header-actions .save, 1351 .in-themes-panel #customize-header-actions .spinner, 1352 .in-themes-panel #customize-header-actions .customize-controls-preview-toggle { 1353 top: -45px; 1354 } 1355 1356 .in-themes-panel #customize-footer-actions, 1357 .in-themes-panel #customize-footer-actions .collapse-sidebar { 1358 bottom: -45px; 1359 } 1360 1361 /* Don't show the theme count while the panel opens, as it's in the wrong place during the animation */ 1362 .in-themes-panel.animating .control-panel-themes .filter-themes-count { 1363 display: none; 1364 } 1365 1366 .in-themes-panel.wp-full-overlay .wp-full-overlay-sidebar-content { 1367 bottom: 0; 1368 } 1369 1370 .themes-filter-bar .feature-filter-toggle { 1371 float: right; 1372 margin: 3px 0 3px 25px; 1373 } 1374 1375 .themes-filter-bar .feature-filter-toggle:before { 1376 content: "\f111"; 1377 margin: 0 5px 0 0; 1378 font: normal 16px/1 dashicons; 1379 vertical-align: text-bottom; 1380 -webkit-font-smoothing: antialiased; 1381 -moz-osx-font-smoothing: grayscale; 1382 } 1383 1384 .themes-filter-bar .feature-filter-toggle.open { 1385 background: #eee; 1386 border-color: #999; 1387 box-shadow: inset 0 2px 5px -3px rgba( 0, 0, 0, 0.5 ); 1388 -webkit-transform: translateY(1px); 1389 transform: translateY(1px); 1390 } 1391 1392 .themes-filter-bar .feature-filter-toggle .filter-count-filters { 1393 display: none; 1394 } 1395 1396 .themes-filter-bar .filter-drawer { 1335 1397 box-sizing: border-box; 1398 width: 100%; 1399 position: absolute; 1400 top: 46px; 1401 left: 0; 1402 padding: 25px 0 25px 25px; 1403 border-top: 0; 1404 margin: 0; 1405 background: #eee; 1406 border-bottom: 1px solid #ddd; 1336 1407 } 1337 1408 1338 #customize-controls .customize-themes-panel .accordion-section-title:first-child { 1339 margin-top: 0; 1409 .themes-filter-bar .filter-group { 1410 margin: 0 25px 0 0; 1411 width: calc( (100% - 75px) / 3); 1412 min-width: 200px; 1413 max-width: 320px; 1414 } 1415 1416 /* Adds a delay before fading in to avoid it "jumping" */ 1417 @-webkit-keyframes themes-fade-in { 1418 0% { 1419 opacity: 0; 1420 } 1421 50% { 1422 opacity: 0; 1423 } 1424 100% { 1425 opacity: 1; 1426 } 1427 } 1428 @keyframes themes-fade-in { 1429 0% { 1430 opacity: 0; 1431 } 1432 50% { 1433 opacity: 0; 1434 } 1435 100% { 1436 opacity: 1; 1437 } 1438 } 1439 1440 .control-panel-themes .customize-themes-full-container.animate { 1441 -webkit-animation: .6s themes-fade-in 1; 1442 animation: .6s themes-fade-in 1; 1443 } 1444 1445 .in-themes-panel:not(.animating) .control-panel-themes .filter-themes-count { 1446 -webkit-animation: .6s themes-fade-in 1; 1447 animation: .6s themes-fade-in 1; 1340 1448 } 1341 1449 1342 #customize-controls .customize-themes-panel .accordion-section-title:nth-child(2) { 1450 .control-panel-themes .filter-themes-count { 1451 position: relative; 1452 float: right; 1453 line-height: 34px; 1454 } 1455 1456 .control-panel-themes .filter-themes-count .themes-displayed { 1457 font-weight: 600; 1458 color: #555d66; 1459 } 1460 1461 .customize-themes-notifications { 1462 margin: 0; 1463 } 1464 1465 .control-panel-themes .customize-themes-notifications .notice { 1466 margin: 0 0 25px 0; 1467 } 1468 1469 .customize-themes-full-container .customize-themes-section { 1470 display: none !important; /* There is unknown JS that perpetually tries to show all theme sections when more items are added. */ 1471 overflow: hidden; 1472 } 1473 1474 .customize-themes-full-container .customize-themes-section.current-section { 1475 display: list-item !important; /* There is unknown JS that perpetually tries to show all theme sections when more items are added. */ 1476 } 1477 1478 .control-section .customize-section-text-before { 1479 padding: 0 0 8px 15px; 1480 margin: 15px 0 0 0; 1481 line-height: 16px; 1482 border-bottom: 1px solid #ddd; 1483 color: #555d66; 1484 } 1485 1486 .control-panel-themes .customize-themes-section-title { 1487 width: 100%; 1488 background: #fff; 1489 -webkit-box-shadow: none; 1490 box-shadow: none; 1491 outline: none; 1492 border-top: none; 1493 border-bottom: 1px solid #ddd; 1494 border-left: 4px solid #fff; 1495 border-right: none; 1496 cursor: pointer; 1497 padding: 10px 15px; 1498 position: relative; 1499 text-align: left; 1343 1500 font-size: 14px; 1344 1501 font-weight: 600; 1502 color: #555d66; 1503 text-shadow: none; 1345 1504 } 1346 1505 1347 #customize-controls .customize-themes-panel > h2{1348 padding: 15px 8px 0 8px;1506 .control-panel-themes #accordion-section-installed_themes { 1507 border-top: 1px solid #ddd; 1349 1508 } 1350 1509 1351 #customize-theme-controls .customize-themes-panel .accordion-section-content { 1352 background: transparent; 1510 .control-panel-themes .theme-section { 1511 margin: 0; 1512 position: relative; 1513 } 1514 1515 .control-panel-themes .customize-themes-section-title:focus, 1516 .control-panel-themes .customize-themes-section-title:hover { 1517 border-left-color: #0073aa; 1518 color: #0073aa; 1519 background: #f5f5f5; 1520 } 1521 1522 .customize-themes-section-title:not(.selected):after { 1523 content: ""; 1353 1524 display: block; 1525 position: absolute; 1526 top: 9px; 1527 right: 15px; 1528 width: 18px; 1529 height: 18px; 1530 border-radius: 100%; 1531 border: 1px solid #ccc; 1532 background: #fff; 1354 1533 } 1355 1534 1356 .customize-control.customize-control-theme { 1357 margin-bottom: 8px; 1535 .control-panel-themes .theme-section .customize-themes-section-title.selected:after { 1536 content: "\f147"; 1537 font: 16px/1 dashicons; 1538 box-sizing: border-box; 1539 width: 20px; 1540 height: 20px; 1541 padding: 3px 3px 1px 1px; /* Re-align the icon to the smaller grid */ 1542 -webkit-border-radius: 100%; 1543 border-radius: 100%; 1544 position: absolute; 1545 top: 9px; 1546 right: 15px; 1547 background: #0073aa; 1548 color: #fff; 1549 } 1550 1551 .control-panel-themes .customize-themes-section-title.selected { 1552 color: #0073aa; 1358 1553 } 1359 1554 1360 1555 #customize-theme-controls .themes.accordion-section-content { … … p.customize-section-description { 1364 1559 width: 100%; 1365 1560 } 1366 1561 1367 .wp-customizer .theme-browser .themes { 1368 padding-bottom: 8px; 1562 .loading .customize-themes-section .spinner { 1563 display: block; 1564 visibility: visible; 1565 position: relative; 1566 clear: both; 1567 width: 20px; 1568 height: 20px; 1569 left: -webkit-calc(50% - 10px); 1570 left: calc(50% - 10px); 1571 float: none; 1572 margin-top: 50px; 1369 1573 } 1370 1574 1371 .wp-customizer .theme-browser .theme { 1372 margin: 0; 1575 .customize-themes-section .no-themes, 1576 .customize-themes-section .no-themes-local { 1577 display: none; 1578 } 1579 1580 .themes-section-installed_themes .theme .notice-success { 1581 display: none; /* Hide "installed" notice on installed themes tab. */ 1582 } 1583 1584 .control-panel-themes .theme-browser .theme .theme-actions .button-primary { 1585 margin: 0 0 0 8px; 1586 } 1587 1588 .customize-control-theme .theme { 1373 1589 width: 100%; 1590 margin: 0; 1591 border: 1px solid #ddd; 1592 background: #fff; 1593 } 1594 1595 .customize-control-theme .theme .theme-name, .customize-control-theme .theme .theme-actions { 1596 background: #fff; 1597 border: none; 1598 } 1599 1600 .customize-control.customize-control-theme { /* override most properties on .customize-control */ 1601 -webkit-box-sizing: border-box; 1602 -moz-box-sizing: border-box; 1603 box-sizing: border-box; 1604 width: 25%; 1605 max-width: 600px; /* Max. screenshot size / 2 */ 1606 margin: 0 25px 25px 0; 1607 padding: 0; 1608 clear: none; 1609 } 1610 1611 /* 5 columns above 2100px */ 1612 @media screen and (min-width: 2101px) { 1613 .customize-control.customize-control-theme { 1614 width: calc( ( 100% - 125px ) / 5 - 1px ); /* 1px offset accounts for browser rounding, typical all grids */ 1615 } 1616 } 1617 1618 /* 4 columns up to 2100px */ 1619 @media screen and (min-width: 1601px) and (max-width: 2100px) { 1620 .customize-control.customize-control-theme { 1621 width: calc( ( 100% - 100px ) / 4 - 1px ); 1622 } 1623 } 1624 1625 /* 3 columns up to 1600px */ 1626 @media screen and (min-width: 1201px) and (max-width: 1600px) { 1627 .customize-control.customize-control-theme { 1628 width: calc( ( 100% - 75px ) / 3 - 1px ); 1629 } 1630 } 1631 1632 /* 2 columns up to 1200px */ 1633 @media screen and (min-width: 851px) and (max-width: 1200px) { 1634 .customize-control.customize-control-theme { 1635 width: calc( ( 100% - 50px ) / 2 - 1px ); 1636 1637 } 1638 } 1639 1640 /* 1 column up to 850 px */ 1641 @media screen and (max-width: 850px) { 1642 .customize-control.customize-control-theme { 1643 width: 100%; 1644 } 1645 } 1646 1647 .wp-customizer .theme-browser .themes { 1648 padding: 0 0 25px 25px; 1649 transition: .18s margin-top linear; 1374 1650 } 1375 1651 1376 1652 .wp-customizer .theme-browser .theme .theme-actions { 1377 -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";1378 1653 opacity: 1; 1379 1654 } 1380 1655 … … p.customize-section-description { 1386 1661 font-size: 32px; 1387 1662 } 1388 1663 1389 .wp-customizer #themes-filter { 1390 font-size: 16px; 1391 font-weight: 300; 1392 line-height: 1.5; 1393 width: 100%; 1664 .customize-preview-header.themes-filter-bar { 1665 position: fixed; 1666 top: 0; 1667 left: 300px; 1668 width: calc(100% - 300px); 1669 height: 46px; 1670 background: #eee; 1671 z-index: 10; 1672 padding: 6px 25px; 1673 box-sizing: border-box; 1674 border-bottom: 1px solid #ddd; 1675 } 1676 1677 .themes-filter-bar .themes-filter-container { 1678 margin: 0; 1679 padding: 0; 1680 } 1681 1682 .themes-filter-bar .wp-filter-search { 1683 line-height: 25px; 1684 padding: 3px 5px; 1685 max-width: 100%; 1686 width: 40%; 1687 min-width: 300px; 1688 position: absolute; 1689 top: 6px; 1690 left: 25px; 1394 1691 } 1395 1692 1396 .control-section-themes .accordion-section-title:after, 1397 .customize-themes-panel .accordion-section-title:after { 1693 /* Unstick the filter bar on short windows/screens. This breakpoint is based on the 1694 current length of .org feature filters assuming translations do not wrap lines. */ 1695 @media screen and (max-height:540px), screen and (max-width:1018px) { 1696 .customize-preview-header.themes-filter-bar { 1697 position: relative; 1698 left: 0; 1699 width: 100%; 1700 margin: 0 0 25px 0; 1701 } 1702 .wp-customizer .theme-browser .themes { 1703 padding: 0 0 25px 25px; 1704 overflow: hidden; 1705 } 1706 1707 .control-panel-themes .customize-themes-full-container { 1708 margin-top: 0; 1709 padding: 0; 1710 height: 100%; 1711 width: calc(100% - 300px); 1712 } 1713 } 1714 1715 @media screen and (max-width:1018px) { 1716 .themes-filter-bar .filter-group { 1717 width: calc( (100% - 50px) / 2); 1718 } 1719 } 1720 1721 @media screen and (max-width:900px) { 1722 .customize-preview-header.themes-filter-bar { 1723 height: 86px; 1724 padding-top: 46px; 1725 } 1726 1727 .themes-filter-bar .wp-filter-search { 1728 width: calc(100% - 50px); 1729 margin: 0; 1730 min-width: 200px; 1731 } 1732 1733 .themes-filter-bar .filter-drawer { 1734 top: 86px; 1735 } 1736 1737 .control-panel-themes .filter-themes-count { 1738 float: left; 1739 } 1740 } 1741 1742 @media screen and (max-width:792px) { 1743 .themes-filter-bar .filter-group { 1744 width: calc( 100% - 25px); 1745 } 1746 } 1747 1748 .control-panel-themes .customize-themes-mobile-back { 1398 1749 display: none; 1399 1750 } 1400 1751 1401 .customize-themes-panel.control-panel-content { 1402 border-top: 1px solid #ddd; 1752 /* Mobile - toggle between themes and filters */ 1753 @media screen and (max-width:600px) { 1754 1755 .wp-full-overlay.showing-themes .control-panel-themes .filter-themes-count .filter-themes { 1756 display: block; 1757 float: right; 1758 } 1759 1760 .control-panel-themes .customize-themes-full-container { 1761 width: 100%; 1762 margin: 0; 1763 top: 46px; 1764 height: calc(100% - 46px); 1765 z-index: 1; 1766 display: none; 1767 } 1768 1769 .showing-themes .control-panel-themes .customize-themes-full-container { 1770 display: block; 1771 } 1772 1773 .wp-customizer .showing-themes .control-panel-themes .customize-themes-mobile-back { 1774 display: block; 1775 position: fixed; 1776 top: 0; 1777 left: 0; 1778 background: #eee; 1779 color: #444; 1780 border-radius: 0; 1781 box-shadow: none; 1782 border: none; 1783 height: 46px; 1784 width: 100%; 1785 z-index: 10; 1786 text-align: left; 1787 text-shadow: none; 1788 border-bottom: 1px solid #ddd; 1789 border-left: 4px solid transparent; 1790 margin: 0; 1791 padding: 0; 1792 font-size: 0; 1793 overflow: hidden; 1794 } 1795 1796 .wp-customizer .showing-themes .control-panel-themes .customize-themes-mobile-back:before { 1797 left: 0; 1798 top: 0; 1799 height: 42px; 1800 width: 26px; 1801 display: block; 1802 line-height: 46px; 1803 padding: 0 8px 0 8px; 1804 border-right: 1px solid #ddd; 1805 } 1806 1807 .wp-customizer .showing-themes .control-panel-themes .customize-themes-mobile-back:hover, 1808 .wp-customizer .showing-themes .control-panel-themes .customize-themes-mobile-back:focus { 1809 color: #0073aa; 1810 background: #f3f3f5; 1811 border-left-color: #0073aa; 1812 outline: none; 1813 box-shadow: none; 1814 } 1815 1816 .showing-themes #customize-header-actions { 1817 display: none; 1818 } 1403 1819 } 1404 1820 1405 1821 /* Details View */ … … p.customize-section-description { 1416 1832 z-index: 109; 1417 1833 } 1418 1834 1835 /* Avoid a z-index war by resetting elements that should be under the overlay. 1836 This is likely required because of the way that sections and panels are positioned. */ 1837 .wp-customizer.modal-open #customize-header-actions, 1838 .wp-customizer.modal-open .control-panel-themes .filter-themes-count, 1839 .wp-customizer.modal-open .control-panel-themes .customize-themes-section-title.selected:after { 1840 z-index: -1; 1841 } 1842 1419 1843 .wp-customizer .theme-overlay .theme-backdrop { 1420 1844 background: rgba( 238, 238, 238, 0.75 ); 1421 1845 position: fixed; 1422 1846 z-index: 110; 1423 1847 } 1424 1848 1849 .wp-customizer .theme-overlay .star-rating { 1850 float: left; 1851 margin-right: 8px; 1852 } 1853 1854 .wp-customizer .theme-rating .num-ratings { 1855 line-height: 20px; 1856 } 1857 1425 1858 .wp-customizer .theme-overlay .theme-wrap { 1426 1859 left: 90px; 1427 1860 right: 90px; 1428 1861 top: 45px; 1429 1862 bottom: 45px; 1430 1863 z-index: 120; 1431 max-width: 1740px; /* To ensure that theme screenshots are not displayed larger than 880px wide. */1432 1864 } 1433 1865 1434 1866 .wp-customizer .theme-overlay .theme-actions { 1435 text-align: right; /* Because there's only one action, match the pattern of media modals and right-align the action. */ 1867 text-align: right; /* Because there're only one or two actions, match the UI pattern of media modals and right-align the action. */ 1868 padding: 10px 25px; 1869 background: #eee; 1436 1870 } 1437 1871 1438 .ie8 .wp-customizer .theme-overlay .theme-header, 1439 .ie8 .wp-customizer .theme-overlay .theme-about, 1440 .ie8 .wp-customizer .theme-overlay .theme-actions { 1441 position: static; 1872 .wp-customizer .theme-overlay .theme-actions .theme-install.preview { 1873 margin-left: 8px; 1874 } 1875 1876 .control-panel-themes .theme-actions .delete-theme { 1877 left: 15px; /* these override themes.css on mobile */ 1878 right: auto; 1879 bottom: auto; 1880 position: absolute; 1881 } 1882 1883 .modal-open .in-themes-panel #customize-controls .wp-full-overlay-sidebar-content { 1884 overflow: visible; /* Prevent the top-level Customizer controls from becoming visible when elements on the right of the details modal are focused. */ 1885 } 1886 1887 .wp-customizer .theme-header { 1888 background: #eee; 1889 } 1890 1891 .wp-customizer .theme-overlay .theme-header button, 1892 .wp-customizer .theme-overlay .theme-header .close:before { 1893 color: #444; 1894 } 1895 1896 .wp-customizer .theme-overlay .theme-header .close:focus, 1897 .wp-customizer .theme-overlay .theme-header .close:hover, 1898 .wp-customizer .theme-overlay .theme-header .right:focus, 1899 .wp-customizer .theme-overlay .theme-header .right:hover, 1900 .wp-customizer .theme-overlay .theme-header .left:focus, 1901 .wp-customizer .theme-overlay .theme-header .left:hover { 1902 background: #fff; 1903 border-bottom: 4px solid #0073aa; 1904 color: #0073aa; 1905 } 1906 1907 .wp-customizer .theme-overlay .theme-header button.disabled { 1908 border-bottom: none; 1909 background: transparent; 1910 color: #ccc; 1911 } 1912 1913 .wp-customizer .theme-overlay .theme-header .close:focus:before, 1914 .wp-customizer .theme-overlay .theme-header .close:hover:before { 1915 color: #0073aa; 1442 1916 } 1443 1917 1444 1918 /* Small Screens */ … … body.cheatin { 1466 1940 body.cheatin h1 { 1467 1941 border-bottom: 1px solid #ddd; 1468 1942 clear: both; 1469 color: # 666;1943 color: #555d66; 1470 1944 font-size: 24px; 1471 1945 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 1472 1946 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..b6522952e6 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.folded .theme-browser ~ .theme-overlay .theme-wrap { 1049 1049 text-align: center; 1050 1050 } 1051 1051 1052 p.no-themes { 1052 p.no-themes, 1053 p.no-themes-local { 1053 1054 clear: both; 1054 1055 color: #666; 1055 1056 font-size: 18px; … … body.full-overlay-active { 1705 1706 display: none; 1706 1707 } 1707 1708 1708 #customize-container { 1709 #customize-container, 1710 #customize-themes-loading-container { 1709 1711 display: none; 1710 background: # fff;1712 background: #eee; 1711 1713 z-index: 500000; 1712 1714 position: fixed; 1713 1715 overflow: visible; … … body.full-overlay-active { 1720 1722 1721 1723 /* Make the Customizer and Theme installer overlays the only available content. */ 1722 1724 #customize-container, 1725 #customize-themes-loading-container, 1723 1726 .theme-install-overlay { 1724 1727 visibility: visible; 1725 1728 } … … body.full-overlay-active { 1824 1827 1825 1828 #customize-preview.wp-full-overlay-main:before, 1826 1829 .customize-loading #customize-container:before, 1830 .customize-loading #customize-themes-loading-container:before, 1827 1831 .theme-install-overlay .wp-full-overlay-main:before { 1828 1832 content: ""; 1829 1833 display: block; … … body.full-overlay-active { 1861 1865 1862 1866 #customize-preview.wp-full-overlay-main:before, 1863 1867 .customize-loading #customize-container:before, 1868 .customize-loading #customize-themes-loading-container:before, 1864 1869 .theme-install-overlay .wp-full-overlay-main:before { 1865 1870 background-image: url(../images/spinner-2x.gif); 1866 1871 } -
src/wp-admin/customize.php
diff --git a/src/wp-admin/customize.php b/src/wp-admin/customize.php index 81a7ae2741..435397c4b2 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..ab00eebacf 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 657 /* translators: %s is the number of ratings */ 658 echo sprintf( __( '(%s ratings)' ), '{{ data.num_ratings }}' ); ?></span> 659 </div> 660 <# } #> 661 662 <# if ( data.hasUpdate ) { #> 663 <div class="notice notice-warning notice-alt notice-large" data-slug="{{ data.id }}"> 664 <h3 class="notice-title"><?php _e( 'Update Available' ); ?></h3> 665 {{{ data.update }}} 666 </div> 667 <# } #> 661 668 662 669 <# if ( data.parent ) { #> 663 670 <p class="parent-theme"><?php printf( __( 'This is a child theme of %s.' ), '<strong>{{{ data.parent }}}</strong>' ); ?></p> 664 671 <# } #> 665 672 673 <p class="theme-description">{{{ data.description }}}</p> 674 666 675 <# if ( data.tags ) { #> 667 <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{ data.tags}}</p>676 <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{{ data.tags }}}</p> 668 677 <# } #> 669 678 </div> 670 679 </div> 671 680 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 <# } #> 681 <div class="theme-actions"> 682 <# if ( data.active ) { #> 683 <button type="button" class="button button-primary customize-theme"><?php _e( 'Customize' ); ?></a> 684 <# } else if ( 'installed' === data.type ) { #> 685 <?php if ( current_user_can( 'delete_themes' ) ) { ?> 686 <# if ( data.actions && data.actions['delete'] ) { #> 687 <a href="{{{ data.actions['delete'] }}}" data-slug="{{ data.id }}" class="button button-secondary delete-theme"><?php _e( 'Delete' ); ?></a> 688 <# } #> 689 <?php } ?> 690 <button type="button" class="button button-primary preview-theme" data-slug="{{ data.id }}"><?php _e( 'Live Preview' ); ?></span> 691 <# } else { #> 692 <button type="button" class="button theme-install" data-slug="{{ data.id }}"><?php _e( 'Install' ); ?></button> 693 <button type="button" class="button button-primary theme-install preview" data-slug="{{ data.id }}"><?php _e( 'Install & Preview' ); ?></button> 694 <# } #> 695 </div> 683 696 </div> 684 697 </script> 685 698 <?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 17ce623f6c..57c1aae5cc 100644
a b 1135 1135 section = this, 1136 1136 container = $( '#customize-theme-controls' ); 1137 1137 1138 // Watch for changes to the panel state 1138 // Watch for changes to the panel state. 1139 1139 inject = function ( panelId ) { 1140 1140 var parentContainer; 1141 1141 if ( panelId ) { 1142 // The panel has been supplied, so wait until the panel object is registered 1142 // The panel has been supplied, so wait until the panel object is registered. 1143 1143 api.panel( panelId, function ( panel ) { 1144 // The panel has been registered, wait for it to become ready/initialized 1144 // The panel has been registered, wait for it to become ready/initialized. 1145 1145 panel.deferred.embedded.done( function () { 1146 1146 parentContainer = panel.contentContainer; 1147 1147 if ( ! section.headContainer.parent().is( parentContainer ) ) { … … 1166 1166 } 1167 1167 }; 1168 1168 section.panel.bind( inject ); 1169 inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one 1169 inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one. 1170 1170 }, 1171 1171 1172 1172 /** … … 1339 1339 /** 1340 1340 * wp.customize.ThemesSection 1341 1341 * 1342 * Custom section for themes that functions similarly to a backwards panel,1343 * and alsohandles the theme-details view rendering and navigation.1342 * Custom section for themes that loads themes by category, and also 1343 * handles the theme-details view rendering and navigation. 1344 1344 * 1345 1345 * @constructor 1346 1346 * @augments wp.customize.Section … … 1352 1352 template: '', 1353 1353 screenshotQueue: null, 1354 1354 $window: $( window ), 1355 loaded: 0, 1356 loading: false, 1357 fullyLoaded: false, 1358 term: '', 1359 tags: '', 1360 nextTerm: '', 1361 nextTags: '', 1362 filtersHeight: 0, 1363 headerContainer: $(), 1355 1364 1356 1365 /** 1357 * @since 4.2.0 1366 * Embed the section in the DOM when the themes panel is ready. 1367 * 1368 * Insert the section before the themes container. Assume that a themes section is within a panel, but not necessarily the themes panel. 1369 * 1370 * @since 4.9.0 1358 1371 */ 1359 initialize: function () { 1360 this.$customizeSidebar = $( '.wp-full-overlay-sidebar-content:first' ); 1361 return api.Section.prototype.initialize.apply( this, arguments ); 1372 embed: function() { 1373 var inject, 1374 section = this, 1375 container = $( '#customize-theme-controls' ); 1376 1377 // Watch for changes to the panel state 1378 inject = function( panelId ) { 1379 var parentContainer; 1380 api.panel( panelId, function( panel ) { 1381 1382 // The panel has been registered, wait for it to become ready/initialized 1383 panel.deferred.embedded.done( function() { 1384 parentContainer = panel.contentContainer; 1385 if ( ! section.headContainer.parent().is( parentContainer ) ) { 1386 parentContainer.find( '.customize-themes-full-container-container' ).before( section.headContainer ); 1387 } 1388 if ( ! section.contentContainer.parent().is( section.headContainer ) ) { 1389 container.append( section.contentContainer ); 1390 } 1391 section.deferred.embedded.resolve(); 1392 }); 1393 } ); 1394 }; 1395 section.panel.bind( inject ); 1396 inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one 1362 1397 }, 1363 1398 1364 1399 /** 1365 1400 * @since 4.2.0 1366 1401 */ 1367 ready: function 1402 ready: function() { 1368 1403 var section = this; 1369 1404 section.overlay = section.container.find( '.theme-overlay' ); 1370 1405 section.template = wp.template( 'customize-themes-details-view' ); … … 1387 1422 1388 1423 // Pressing the escape key fires a theme:collapse event 1389 1424 if ( 27 === event.keyCode ) { 1390 section.closeDetails(); 1425 if ( $( 'body' ).hasClass( 'modal-open' ) ) { 1426 1427 // Escape from the details modal. 1428 section.closeDetails(); 1429 } else { 1430 1431 // Escape from the inifinite scroll list. 1432 section.headerContainer.find( '.customize-themes-section-title' ).focus(); 1433 } 1391 1434 event.stopPropagation(); // Prevent section from being collapsed. 1392 1435 } 1393 1436 }); 1394 1437 1395 _.bindAll( this, 'renderScreenshots' );1438 _.bindAll( this, 'renderScreenshots', 'loadMore', 'checkTerm', 'filtersChecked' ); 1396 1439 }, 1397 1440 1398 1441 /** 1399 1442 * Override Section.isContextuallyActive method. 1400 1443 * 1401 1444 * Ignore the active states' of the contained theme controls, and just 1402 * use the section's own active state instead. This ensures empty search1403 * results for theme s to causethe section to become inactive.1445 * use the section's own active state instead. This prevents empty search 1446 * results for theme sections from causing the section to become inactive. 1404 1447 * 1405 1448 * @since 4.2.0 1406 1449 * … … 1414 1457 * @since 4.2.0 1415 1458 */ 1416 1459 attachEvents: function () { 1417 var section = this ;1460 var section = this, debounced; 1418 1461 1419 1462 // Expand/Collapse accordion sections on click. 1420 1463 section.container.find( '.customize-section-back' ).on( 'click keydown', function( event ) { … … 1425 1468 section.collapse(); 1426 1469 }); 1427 1470 1428 // Expand/Collapse section/panel. 1429 section.container.find( '.change-theme, .customize-theme' ).on( 'click keydown', function( event ) { 1430 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1431 return; 1471 section.headerContainer = $( '#accordion-section-' + section.id ); 1472 1473 // Expand section/panel. Only collapse when opening another section. 1474 section.headerContainer.on( 'click', '.customize-themes-section-title', function() { 1475 1476 // Toggle accordion filters under section headers. 1477 if ( section.headerContainer.find( '.filter-details' ).length ) { 1478 section.headerContainer.find( '.customize-themes-section-title' ) 1479 .toggleClass( 'details-open' ) 1480 .attr( 'aria-expanded', function( i, attr ) { 1481 return 'true' === attr ? 'false' : 'true'; 1482 }); 1483 section.headerContainer.find( '.filter-details' ).slideToggle( 180 ); 1432 1484 } 1433 event.preventDefault(); // Keep this AFTER the key filter above1434 1485 1435 if ( section.expanded() ) { 1436 section.collapse(); 1437 } else { 1486 // Open the section. 1487 if ( ! section.expanded() ) { 1438 1488 section.expand(); 1439 1489 } 1440 1490 }); 1441 1491 1442 // Theme navigation in details view. 1443 section.container.on( 'click keydown', '.left', function( event ) { 1444 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1445 return; 1446 } 1492 // Preview installed themes. 1493 section.container.on( 'click', '.theme-actions .preview-theme', function() { 1494 var themeId = $( this ).data( 'slug' ); 1447 1495 1448 event.preventDefault(); // Keep this AFTER the key filter above 1496 $( '.wp-full-overlay' ).addClass( 'customize-loading' ); 1497 api.panel( 'themes' ).loadThemePreview( themeId ).fail( function() { 1498 $( '.wp-full-overlay' ).removeClass( 'customize-loading' ); 1499 } ); 1500 }); 1449 1501 1502 // Theme navigation in details view. 1503 section.container.on( 'click', '.left', function() { 1450 1504 section.previousTheme(); 1451 1505 }); 1452 1506 1453 section.container.on( 'click keydown', '.right', function( event ) { 1454 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1455 return; 1456 } 1457 1458 event.preventDefault(); // Keep this AFTER the key filter above 1459 1507 section.container.on( 'click', '.right', function() { 1460 1508 section.nextTheme(); 1461 1509 }); 1462 1510 1463 section.container.on( 'click keydown', '.theme-backdrop, .close', function( event ) { 1464 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1465 return; 1466 } 1467 1468 event.preventDefault(); // Keep this AFTER the key filter above 1469 1511 section.container.on( 'click', '.theme-backdrop, .close', function() { 1470 1512 section.closeDetails(); 1471 1513 }); 1472 1514 1473 var renderScreenshots = _.throttle( _.bind( section.renderScreenshots, this ), 100 ); 1474 section.container.on( 'input', '#themes-filter', function( event ) { 1475 var count, 1476 term = event.currentTarget.value.toLowerCase().trim().replace( '-', ' ' ), 1477 controls = section.controls(); 1515 // Filter-search all theme objects loaded in the section. 1516 section.container.on( 'input', '.wp-filter-search-themes', function( event ) { 1517 section.filterSearch( event.currentTarget ); 1518 }); 1519 1520 // Event listeners for remote wporg queries with user-entered terms. 1521 if ( 'wporg' === section.params.action ) { 1478 1522 1479 _.each( controls, function( control ) { 1480 control.filter( term ); 1523 // Search terms. 1524 debounced = _.debounce( section.checkTerm, 500 ); // Wait until there is no input for 500 milliseconds to initiate a search. 1525 section.contentContainer.on( 'input', '#wp-filter-search-input', function() { 1526 debounced( section ); 1527 if ( ! section.expanded() ) { 1528 section.expand(); 1529 } 1530 section.checkTerm( section ); 1481 1531 }); 1482 1532 1483 renderScreenshots(); 1533 // Feature filters. 1534 section.contentContainer.on( 'click', '.filter-group input', function() { 1535 section.filtersChecked(); 1536 section.checkTerm( section ); 1537 }); 1538 1539 // Toggle feature filter sections. 1540 section.contentContainer.on( 'click', '.feature-filter-toggle', function( e ) { 1541 $( e.currentTarget ) 1542 .toggleClass( 'open' ) 1543 .attr( 'aria-expanded', function( i, attr ) { 1544 return 'true' == attr ? 'false' : 'true'; 1545 }) 1546 .next( '.filter-drawer' ).slideToggle( 180, 'linear', function() { 1547 if ( 0 === section.filtersHeight ) { 1548 section.filtersHeight = $( this ).height(); 1549 1550 // First time, so it's opened. 1551 section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 ); 1552 } 1553 }); 1554 if ( $( e.currentTarget ).hasClass( 'open' ) ) { 1555 section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + 76 ); 1556 } else { 1557 section.contentContainer.find( '.themes' ).css( 'margin-top', 0 ); 1558 } 1559 }); 1560 } 1484 1561 1485 // Update theme count.1486 count = section.container.find( 'li.customize-control:visible' ).length;1487 section.container.find( '.theme-count' ).text( count);1562 // Setup section cross-linking. 1563 section.contentContainer.on( 'click', '.no-themes-local .search-dotorg-themes', function() { 1564 api.section( 'wporg_themes' ).focus(); 1488 1565 }); 1489 1566 1490 // Pre-load the first 3 theme screenshots.1567 // Move section controls to the themes area. 1491 1568 api.bind( 'ready', function () { 1492 _.each( section.controls().slice( 0, 3 ), function ( control ) { 1493 var img, src = control.params.theme.screenshot[0]; 1494 if ( src ) { 1495 img = new Image(); 1496 img.src = src; 1497 } 1498 }); 1569 section.contentContainer = section.container.find( '.customize-themes-section' ); 1570 section.contentContainer.appendTo( $( '.customize-themes-full-container' ) ); 1571 section.container.add( section.headerContainer ); 1499 1572 }); 1500 1573 }, 1501 1574 … … 1507 1580 * @param {Boolean} expanded 1508 1581 * @param {Object} args 1509 1582 * @param {Boolean} args.unchanged 1510 * @param { Callback} args.completeCallback1583 * @param {Function} args.completeCallback 1511 1584 */ 1512 1585 onChangeExpanded: function ( expanded, args ) { 1513 1586 1587 // Note: there is a second argument 'args' passed 1588 var section = this, 1589 container = section.contentContainer.closest( '.customize-themes-full-container' ); 1590 1514 1591 // Immediately call the complete callback if there were no changes 1515 1592 if ( args.unchanged ) { 1516 1593 if ( args.completeCallback ) { … … 1519 1596 return; 1520 1597 } 1521 1598 1522 // Note: there is a second argument 'args' passed 1523 var panel = this, 1524 section = panel.contentContainer, 1525 overlay = section.closest( '.wp-full-overlay' ), 1526 container = section.closest( '.wp-full-overlay-sidebar-content' ), 1527 customizeBtn = section.find( '.customize-theme' ), 1528 changeBtn = panel.headContainer.find( '.change-theme' ); 1599 if ( expanded ) { 1600 1601 // Try to load controls if none are loaded yet. 1602 if ( 0 === section.loaded ) { 1603 section.loadControls(); 1604 } 1529 1605 1530 if ( expanded && ! section.hasClass( 'current-panel' ) ) {1531 1606 // Collapse any sibling sections/panels 1532 1607 api.section.each( function ( otherSection ) { 1533 if ( otherSection !== panel ) { 1608 var searchTerm; 1609 1610 if ( otherSection !== section ) { 1611 1612 // Try to sync the current search term to the new section. 1613 if ( 'themes' === otherSection.params.type ) { 1614 searchTerm = otherSection.contentContainer.find( '.wp-filter-search' ).val(); 1615 section.contentContainer.find( '.wp-filter-search' ).val( searchTerm ); 1616 1617 // Directly initialize an empty remote search to avoid a race condition. 1618 if ( '' === searchTerm && '' !== section.term && 'installed' !== section.params.action ) { 1619 section.term = ''; 1620 section.initializeNewQuery( section.term, section.tags ); 1621 } else { 1622 section.checkTerm( section ); 1623 } 1624 section.filterSearch( section.contentContainer.find( '.wp-filter-search' ).get( 0 ) ); 1625 } 1534 1626 otherSection.collapse( { duration: args.duration } ); 1535 1627 } 1536 1628 }); 1537 api.panel.each( function ( otherPanel ) {1538 otherPanel.collapse( { duration: 0 } );1539 });1540 1629 1541 panel._animateChangeExpanded( function() {1542 changeBtn.attr( 'tabindex', '-1');1543 customizeBtn.attr( 'tabindex', '0' );1630 section.contentContainer.addClass( 'current-section' ); 1631 container.scrollTop(); 1632 section.headerContainer.find( '.customize-themes-section-title' ).addClass( 'selected' ).attr( 'aria-expanded', 'true' ); 1544 1633 1545 customizeBtn.focus(); 1546 section.css( 'top', '' ); 1547 container.scrollTop( 0 ); 1634 container.on( 'scroll', _.throttle( section.renderScreenshots, 300 ) ); 1635 container.on( 'scroll', _.throttle( section.loadMore, 300 ) ); 1548 1636 1549 if ( args.completeCallback ) { 1550 args.completeCallback(); 1637 if ( args.completeCallback ) { 1638 args.completeCallback(); 1639 } 1640 section.updateCount(); // Show this section's count. 1641 } else { 1642 section.contentContainer.removeClass( 'current-section' ); 1643 1644 // Always hide, even if they don't exist or are already hidden. 1645 section.headerContainer.find( '.customize-themes-section-title' ).removeClass( 'selected details-open' ).attr( 'aria-expanded', 'false' ); 1646 section.headerContainer.find( '.filter-details' ).slideUp( 180 ); 1647 1648 container.off( 'scroll' ); 1649 1650 if ( args.completeCallback ) { 1651 args.completeCallback(); 1652 } 1653 } 1654 }, 1655 1656 /** 1657 * Return the section's content element without detachng from the parent. 1658 * 1659 * @since 4.9.0 1660 */ 1661 getContent: function() { 1662 return this.container.find( '.control-section-content' ); 1663 }, 1664 1665 /** 1666 * Load theme data via Ajax and add themes to the section as controls. 1667 * 1668 * @since 4.9.0 1669 */ 1670 loadControls: function() { 1671 var section = this, params, page, request; 1672 1673 if ( section.loading ) { 1674 return; // We're already loading a batch of themes. 1675 } 1676 1677 // Parameters for every API query. Additional params are set in PHP. 1678 page = Math.ceil( section.loaded / 100 ) + 1; 1679 params = { 1680 'switch-themes-nonce': api.settings.nonce['switch-themes'], 1681 'wp_customize': 'on', 1682 'theme_action': section.params.action, 1683 'customized_theme': api.settings.theme.stylesheet, 1684 'page': page 1685 }; 1686 1687 // Add fields for wporg actions. 1688 if ( 'wporg' === section.params.action ) { 1689 params.search = section.term; 1690 params.tags = section.tags; 1691 } 1692 1693 // Load themes. 1694 section.headContainer.closest( '.wp-full-overlay' ).addClass( 'loading' ); 1695 section.loading = true; 1696 section.container.find( '.no-themes' ).hide(); 1697 request = wp.ajax.post( 'customize-load-themes', params ); 1698 request.done(function( data ) { 1699 var themes = data.themes, 1700 themeControl, newThemeControls; 1701 1702 // Stop and try again if the term changed while loading. 1703 if ( section.nextTerm || section.nextTags ) { 1704 if ( section.nextTerm ) { 1705 section.term = section.nextTerm; 1551 1706 } 1552 } ); 1707 if ( section.nextTags ) { 1708 section.tags = section.nextTags; 1709 } 1710 section.nextTerm = ''; 1711 section.nextTags = ''; 1712 section.loading = false; 1713 section.loadControls(); 1714 return; 1715 } 1553 1716 1554 overlay.addClass( 'in-themes-panel' ); 1555 section.addClass( 'current-panel' ); 1556 _.delay( panel.renderScreenshots, 10 ); // Wait for the controls 1557 panel.$customizeSidebar.on( 'scroll.customize-themes-section', _.throttle( panel.renderScreenshots, 300 ) ); 1717 if ( 0 !== themes.length ) { 1718 newThemeControls = []; 1719 1720 // Add controls for each theme. 1721 _.each( themes, function( theme ) { 1722 var customizeId = section.params.action + '_theme_' + theme.id; 1723 themeControl = new api.controlConstructor.theme( customizeId, { 1724 params: { 1725 type: 'theme', 1726 content: '<li id="customize-control-theme-' + section.params.action + '_' + theme.id + '" class="customize-control customize-control-theme"></li>', 1727 section: section.params.id, 1728 active: true, 1729 theme: theme, 1730 priority: section.loaded + 1 1731 }, 1732 previewer: api.previewer 1733 } ); 1558 1734 1559 } else if ( ! expanded && section.hasClass( 'current-panel' ) ) {1560 panel._animateChangeExpanded( function() {1561 changeBtn.attr( 'tabindex', '0' );1562 customizeBtn.attr( 'tabindex', '-1');1735 api.control.add( customizeId, themeControl ); 1736 newThemeControls.push( themeControl ); 1737 section.loaded = section.loaded + 1; 1738 }); 1563 1739 1564 changeBtn.focus(); 1565 section.css( 'top', '' ); 1740 if ( 1 === page ) { 1566 1741 1567 if ( args.completeCallback ) { 1568 args.completeCallback(); 1742 // Pre-load the first 3 theme screenshots. 1743 _.each( section.controls().slice( 0, 3 ), function( control ) { 1744 var img, src = control.params.theme.screenshot[0]; 1745 if ( src ) { 1746 img = new Image(); 1747 img.src = src; 1748 } 1749 }); 1750 if ( 'installed' !== section.params.action ) { 1751 wp.a11y.speak( api.settings.l10n.themeSearchResults.replace( '%d', data.info.results ) ); 1752 } 1753 } else { 1754 Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue. 1569 1755 } 1570 } ); 1756 _.delay( section.renderScreenshots, 100 ); // Wait for the controls to become visible. 1757 1758 if ( 'installed' === section.params.action || 100 > themes.length ) { // If we have less than the requested 100 themes, it's the end of the list. 1759 section.fullyLoaded = true; 1760 } 1761 } else { 1762 if ( 0 === section.loaded ) { 1763 section.container.find( '.no-themes' ).show(); 1764 wp.a11y.speak( section.container.find( '.no-themes' ).text() ); 1765 } else { 1766 section.fullyLoaded = true; 1767 } 1768 } 1769 if ( 'installed' === section.params.action ) { 1770 section.updateCount(); // Count of visible theme controls. 1771 } else { 1772 section.updateCount( data.info.results ); // Total number of results including pages not yet loaded. 1773 } 1774 section.container.find( '.unexpected-error' ).hide(); // Hide error notice in case it was previously shown. 1775 1776 // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case. 1777 section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' ); 1778 section.loading = false; 1779 }); 1780 request.fail(function( data ) { 1781 if ( 'undefined' === typeof data ) { 1782 section.container.find( '.unexpected-error' ).show(); 1783 wp.a11y.speak( section.container.find( '.unexpected-error' ).text() ); 1784 } else if ( 'undefined' !== typeof console && console.error ) { 1785 console.error( data ); 1786 } 1787 1788 // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case. 1789 section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' ); 1790 section.loading = false; 1791 }); 1792 }, 1793 1794 /** 1795 * Determines whether more themes should be loaded, and loads them. 1796 * 1797 * @since 4.9.0 1798 */ 1799 loadMore: function() { 1800 var section = this, container, bottom, threshold; 1801 if ( ! section.fullyLoaded && ! section.loading ) { 1802 container = section.container.closest( '.customize-themes-full-container' ); 1571 1803 1572 overlay.removeClass( 'in-themes-panel' ); 1573 section.removeClass( 'current-panel' ); 1574 panel.$customizeSidebar.off( 'scroll.customize-themes-section' ); 1804 bottom = container.scrollTop() + container.height(); 1805 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. 1806 1807 if ( bottom > threshold ) { 1808 section.loadControls(); 1809 } 1810 } 1811 }, 1812 1813 /** 1814 * Event handler for search input that filters visible controls. 1815 * 1816 * @since 4.9.0 1817 * 1818 * @param Element el The search input element as a raw JS object. 1819 */ 1820 filterSearch: function( el ) { 1821 var count = 0, 1822 visible = false, 1823 section = this, 1824 noFilter = ( undefined !== api.section( 'wporg_themes' ) && 'wporg' !== section.params.action ) ? '.no-themes-local' : '.no-themes', 1825 term = el.value.toLowerCase().trim().replace( '-', ' ' ), 1826 controls = section.controls(), 1827 renderScreenshots; 1828 1829 if ( section.loading ) { 1830 return; 1831 } 1832 1833 _.each( controls, function( control ) { 1834 visible = control.filter( term ); 1835 if ( visible ) { 1836 count = count + 1; 1837 } 1838 }); 1839 1840 if ( 0 === count ) { 1841 section.container.find( noFilter ).show(); 1842 wp.a11y.speak( section.container.find( noFilter ).text() ); 1843 } else { 1844 section.container.find( noFilter ).hide(); 1845 } 1846 1847 renderScreenshots = _.throttle( _.bind( section.renderScreenshots, this ), 100 ); 1848 1849 renderScreenshots(); 1850 1851 // Update theme count. 1852 section.updateCount( count ); 1853 }, 1854 1855 /** 1856 * Event handler for search input that determines if the terms have changed and loads new controls as needed. 1857 * 1858 * @since 4.9.0 1859 * 1860 * @param {wp.customize.ThemesSection} section The current theme section, passed through the debouncer. 1861 */ 1862 checkTerm: function( section ) { 1863 var newTerm; 1864 1865 // Find term. 1866 if ( 'wporg' === section.params.action ) { 1867 newTerm = $( '#wp-filter-search-input' ).val(); 1868 } else { 1869 return; 1870 } 1871 1872 if ( section.term === newTerm ) { 1873 return; 1874 } else { 1875 section.initializeNewQuery( newTerm, section.tags ); 1876 } 1877 1878 }, 1879 1880 /** 1881 * Check for filters checked in the feature filter list and initialize a new query. 1882 * 1883 * @since 4.9.0 1884 */ 1885 filtersChecked: function() { 1886 var section = this, 1887 items = section.container.find( '.filter-group' ).find( ':checkbox' ), 1888 tags = []; 1889 1890 _.each( items.filter( ':checked' ), function( item ) { 1891 tags.push( $( item ).prop( 'value' ) ); 1892 }); 1893 1894 // When no filters are checked, restore initial state. 1895 if ( 0 === tags.length ) { 1896 tags = ''; 1897 section.contentContainer.find( '.feature-filter-toggle .filter-count-0' ).show(); 1898 section.contentContainer.find( '.feature-filter-toggle .filter-count-filters' ).hide(); 1899 } else { 1900 section.contentContainer.find( '.feature-filter-toggle .theme-filter-count' ).text( tags.length ); 1901 section.contentContainer.find( '.feature-filter-toggle .filter-count-0' ).hide(); 1902 section.contentContainer.find( '.feature-filter-toggle .filter-count-filters' ).show(); 1903 } 1904 1905 section.initializeNewQuery( section.term, tags ); 1906 }, 1907 1908 /** 1909 * Reset the current query and load new results. 1910 * 1911 * @since 4.9.0 1912 */ 1913 initializeNewQuery: function( newTerm, newTags ) { 1914 var section = this; 1915 1916 // Clear the controls in the section. 1917 _.each( section.controls(), function( control ) { 1918 control.container.remove(); 1919 api.control.remove( control.id ); 1920 }); 1921 section.loaded = 0; 1922 section.fullyLoaded = false; 1923 section.screenshotQueue = null; 1924 1925 // Run a new query, with loadControls handling paging, etc. 1926 if ( ! section.loading ) { 1927 section.term = newTerm; 1928 section.tags = newTags; 1929 section.loadControls(); 1930 } else { 1931 section.nextTerm = newTerm; // This will reload from loadControls() with the newest term once the current batch is loaded. 1932 section.nextTags = newTags; // This will reload from loadControls() with the newest tags once the current batch is loaded. 1933 } 1934 if ( ! section.expanded() ) { 1935 section.expand(); // Expand the section if it isn't expanded. 1575 1936 } 1576 1937 }, 1577 1938 … … 1580 1941 * 1581 1942 * @since 4.2.0 1582 1943 */ 1583 renderScreenshots: function( 1944 renderScreenshots: function() { 1584 1945 var section = this; 1585 1946 1586 // Fill queue initially. 1587 if ( section.screenshotQueue === null ) { 1588 section.screenshotQueue = section.controls(); 1947 // Fill queue initially, or check for more if empty. 1948 if ( null === section.screenshotQueue || 0 === section.screenshotQueue.length ) { 1949 1950 // Add controls that haven't had their screenshots rendered. 1951 section.screenshotQueue = _.filter( section.controls(), function( control ) { 1952 return ! control.screenshotRendered; 1953 }); 1589 1954 } 1590 1955 1591 // Are all screenshots rendered ?1956 // Are all screenshots rendered (for now)? 1592 1957 if ( ! section.screenshotQueue.length ) { 1593 1958 return; 1594 1959 } … … 1623 1988 } ); 1624 1989 }, 1625 1990 1991 getVisibleCount: function() { 1992 return this.contentContainer.find( 'li.customize-control:visible' ).length; 1993 }, 1994 1995 /** 1996 * Update the number of themes in the section. 1997 * 1998 * @since 4.9.0 1999 */ 2000 updateCount: function( count ) { 2001 var section = this, countEl, displayed; 2002 2003 if ( ! count && 0 !== count ) { 2004 count = section.getVisibleCount(); 2005 } 2006 2007 displayed = section.contentContainer.find( '.themes-displayed' ); 2008 countEl = section.contentContainer.find( '.theme-count' ); 2009 2010 if ( 0 === count ) { 2011 countEl.text( '0' ); 2012 } else { 2013 // Animate the count change for emphasis. 2014 displayed.fadeOut( 180, function() { 2015 countEl.text( count ); 2016 displayed.fadeIn( 180 ); 2017 } ); 2018 wp.a11y.speak( api.settings.l10n.announceThemeCount.replace( '%d', count ) ); 2019 } 2020 }, 2021 1626 2022 /** 1627 2023 * Advance the modal to the next theme. 1628 2024 * … … 1643 2039 * @since 4.2.0 1644 2040 */ 1645 2041 getNextTheme: function () { 1646 var control, next;1647 control = api.control( 'theme_' + this.currentTheme );2042 var section = this, control, next; 2043 control = api.control( section.params.action + '_theme_' + this.currentTheme ); 1648 2044 next = control.container.next( 'li.customize-control-theme' ); 1649 2045 if ( ! next.length ) { 1650 2046 return false; 1651 2047 } 1652 next = next[0].id.replace( 'customize-control- ', '' );2048 next = next[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' ); 1653 2049 control = api.control( next ); 1654 2050 1655 2051 return control.params.theme; … … 1675 2071 * @since 4.2.0 1676 2072 */ 1677 2073 getPreviousTheme: function () { 1678 var control, previous;1679 control = api.control( 'theme_' + this.currentTheme );2074 var section = this, control, previous; 2075 control = api.control( section.params.action + '_theme_' + this.currentTheme ); 1680 2076 previous = control.container.prev( 'li.customize-control-theme' ); 1681 2077 if ( ! previous.length ) { 1682 2078 return false; 1683 2079 } 1684 previous = previous[0].id.replace( 'customize-control- ', '' );2080 previous = previous[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' ); 1685 2081 control = api.control( previous ); 1686 2082 1687 2083 return control.params.theme; … … 1769 2165 * @param {Object} theme 1770 2166 */ 1771 2167 showDetails: function ( theme, callback ) { 1772 var section = this , link;2168 var section = this; 1773 2169 callback = callback || function(){}; 1774 2170 section.currentTheme = theme.id; 1775 2171 section.overlay.html( section.template( theme ) ) … … 1778 2174 $( 'body' ).addClass( 'modal-open' ); 1779 2175 section.containFocus( section.overlay ); 1780 2176 section.updateLimits(); 1781 1782 link = section.overlay.find( '.inactive-theme > a' ); 1783 1784 link.on( 'click', function( event ) { 1785 event.preventDefault(); 1786 1787 // Short-circuit if request is currently being made. 1788 if ( link.hasClass( 'disabled' ) ) { 1789 return; 1790 } 1791 link.addClass( 'disabled' ); 1792 1793 section.loadThemePreview( theme.id ).fail( function() { 1794 link.removeClass( 'disabled' ); 1795 } ); 1796 } ); 2177 wp.a11y.speak( api.settings.l10n.announceThemeDetails.replace( '%s', theme.name ) ); 1797 2178 callback(); 1798 2179 }, 1799 2180 … … 1805 2186 closeDetails: function () { 1806 2187 $( 'body' ).removeClass( 'modal-open' ); 1807 2188 this.overlay.fadeOut( 'fast' ); 1808 api.control( 'theme_' + this.currentTheme).focus();2189 api.control( this.params.action + '_theme_' + this.currentTheme ).container.find( '.theme' ).focus(); 1809 2190 }, 1810 2191 1811 2192 /** … … 1885 2266 } 1886 2267 if ( ! panel.contentContainer.parent().is( panel.headContainer ) ) { 1887 2268 container.append( panel.contentContainer ); 1888 panel.renderContent();1889 2269 } 2270 panel.renderContent(); 1890 2271 1891 2272 panel.deferred.embedded.resolve(); 1892 2273 }, … … 2091 2472 } 2092 2473 }); 2093 2474 2475 2476 /** 2477 * wp.customize.ThemesPanel 2478 * 2479 * Custom section for themes that displays without the customize preview. 2480 * 2481 * @constructor 2482 * @augments wp.customize.Panel 2483 * @augments wp.customize.Container 2484 */ 2485 api.ThemesPanel = api.Panel.extend({ 2486 installingThemes: [], 2487 2488 /** 2489 * @since 4.9.0 2490 */ 2491 attachEvents: function () { 2492 var panel = this; 2493 2494 // Attach regular panel events. 2495 api.Panel.prototype.attachEvents.apply( this ); 2496 2497 // Collapse panel to customize the current theme. 2498 panel.contentContainer.on( 'click', '.customize-theme', function() { 2499 panel.collapse(); 2500 }); 2501 2502 // Toggle between filtering and browsing themes on mobile. 2503 panel.contentContainer.on( 'click', '.customize-themes-section-title, .customize-themes-mobile-back', function() { 2504 $( '.wp-full-overlay' ).toggleClass( 'showing-themes' ); 2505 }); 2506 2507 // Install (and maybe preview) a theme. 2508 panel.contentContainer.on( 'click', '.theme-install', function( event ) { 2509 panel.installTheme( event ); 2510 }); 2511 2512 // Update a theme. Theme cards have the class, the details modal has the id. 2513 panel.contentContainer.on( 'click', '.update-theme, #update-theme', function( event ) { 2514 2515 // #update-theme is a link. 2516 event.preventDefault(); 2517 event.stopPropagation(); 2518 2519 panel.updateTheme( event ); 2520 }); 2521 2522 // Delete a theme. 2523 panel.contentContainer.on( 'click', '.delete-theme', function( event ) { 2524 panel.deleteTheme( event ); 2525 }); 2526 2527 _.bindAll( this, 'installTheme', 'updateTheme' ); 2528 }, 2529 2530 /** 2531 * Update UI to reflect expanded state 2532 * 2533 * @since 4.9.0 2534 * 2535 * @param {Boolean} expanded 2536 * @param {Object} args 2537 * @param {Boolean} args.unchanged 2538 * @param {Function} args.completeCallback 2539 */ 2540 onChangeExpanded: function ( expanded, args ) { 2541 2542 // Expand/collapse the panel normally. 2543 api.Panel.prototype.onChangeExpanded.apply( this, [ expanded, args ] ); 2544 2545 // Immediately call the complete callback if there were no changes 2546 if ( args.unchanged ) { 2547 if ( args.completeCallback ) { 2548 args.completeCallback(); 2549 } 2550 return; 2551 } 2552 2553 // Note: there is a second argument 'args' passed 2554 var panel = this, 2555 overlay = panel.headContainer.closest( '.wp-full-overlay' ); 2556 2557 if ( expanded ) { 2558 overlay 2559 .addClass( 'in-themes-panel' ) 2560 .delay( 200 ).find( '.customize-themes-full-container' ).addClass( 'animate' ); 2561 2562 // Automatically open the installed themes section (except on small screens). 2563 if ( 600 < window.innerWidth ) { 2564 api.section( 'installed_themes' ).expand(); 2565 } 2566 } else { 2567 overlay 2568 .removeClass( 'in-themes-panel' ) 2569 .find( '.customize-themes-full-container' ).removeClass( 'animate' ); 2570 } 2571 }, 2572 2573 /** 2574 * Install a theme via wp.updates. 2575 * 2576 * @since 4.9.0 2577 */ 2578 installTheme: function( event ) { 2579 var panel = this, preview = false, slug = $( event.target ).data( 'slug' ); 2580 2581 if ( -1 !== $.inArray( this.installingThemes, slug ) ) { 2582 return; // Theme is already being installed. 2583 } 2584 2585 wp.updates.maybeRequestFilesystemCredentials( event ); 2586 2587 $( document ).one( 'wp-theme-install-success', function( event, response ) { 2588 var theme = false, customizeId, themeControl; 2589 if ( preview ) { 2590 2591 panel.loadThemePreview( slug ).fail( function() { 2592 $( '.wp-full-overlay' ).removeClass( 'customize-loading' ); 2593 } ); 2594 2595 } else { 2596 api.control.each( function( control ) { 2597 if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) { 2598 theme = control.params.theme; // Used below to add theme control. 2599 control.rerenderAsInstalled( true ); 2600 } 2601 }); 2602 2603 // Don't add the same theme more than once. 2604 if ( ! theme || 'undefined' !== typeof api.control( 'installed_theme_' + theme.id ) ) { 2605 return; 2606 } 2607 2608 // Add theme control to installed section. 2609 theme.type = 'installed'; 2610 customizeId = 'installed_theme_' + theme.id; 2611 themeControl = new api.controlConstructor.theme( customizeId, { 2612 params: { 2613 type: 'theme', 2614 content: $( '<li class="customize-control customize-control-theme"></li>' ).attr( 'id', 'customize-control-theme-installed_' + theme.id ).prop( 'outerHTML' ), 2615 section: 'installed_themes', 2616 active: true, 2617 theme: theme, 2618 priority: 0 // Add all newly-installed themes to the top. 2619 }, 2620 previewer: api.previewer 2621 } ); 2622 2623 api.control.add( customizeId, themeControl ); 2624 api.control( customizeId ).container.trigger( 'render-screenshot' ); 2625 2626 // Close the details modal if it's open to the installed theme. 2627 api.section.each( function( section ) { 2628 if ( 'themes' === section.params.type ) { 2629 if ( theme.id === section.currentTheme ) { // Don't close the modal if the user has navigated elsewhere. 2630 section.closeDetails(); 2631 } 2632 } 2633 }); 2634 } 2635 } ); 2636 2637 this.installingThemes.push( $( event.target ).data( 'slug' ) ); // Note: we don't remove elements from installingThemes, since they shouldn't be installed again. 2638 wp.updates.installTheme( { 2639 slug: slug 2640 } ); 2641 2642 // Also preview the theme as the event is triggered on Install & Preview. 2643 if ( $( event.target ).hasClass( 'preview' ) ) { 2644 preview = true; 2645 $( '.wp-full-overlay' ).addClass( 'customize-loading' ); 2646 wp.a11y.speak( $( '#customize-themes-loading-container .customize-loading-text-installing-theme' ).text() ); 2647 } 2648 }, 2649 2650 /** 2651 * Load theme preview. 2652 * 2653 * @since 4.9.0 2654 * 2655 * @param {string} themeId Theme ID. 2656 * @returns {jQuery.promise} Promise. 2657 */ 2658 loadThemePreview: function( themeId ) { 2659 var deferred = $.Deferred(), onceProcessingComplete, overlay, urlParser; 2660 2661 urlParser = document.createElement( 'a' ); 2662 urlParser.href = location.href; 2663 urlParser.search = $.param( _.extend( 2664 api.utils.parseQueryString( urlParser.search.substr( 1 ) ), 2665 { 2666 theme: themeId, 2667 changeset_uuid: api.settings.changeset.uuid 2668 } 2669 ) ); 2670 2671 // Update loading message. Everything else is handled by reloading the page. 2672 $( '#customize-themes-loading-container span' ).hide(); 2673 $( '#customize-themes-loading-container .customize-loading-text' ).css( 'display', 'block' ); 2674 wp.a11y.speak( $( '#customize-themes-loading-container .customize-loading-text' ).text() ); 2675 overlay = $( '.wp-full-overlay' ); 2676 overlay.addClass( 'customize-loading' ); 2677 2678 onceProcessingComplete = function() { 2679 var request; 2680 if ( api.state( 'processing' ).get() > 0 ) { 2681 return; 2682 } 2683 2684 api.state( 'processing' ).unbind( onceProcessingComplete ); 2685 2686 request = api.requestChangesetUpdate(); 2687 request.done( function() { 2688 $( window ).off( 'beforeunload.customize-confirm' ); 2689 window.location.href = urlParser.href; 2690 } ); 2691 request.fail( function() { 2692 overlay.removeClass( 'customize-loading' ); 2693 } ); 2694 }; 2695 2696 if ( 0 === api.state( 'processing' ).get() ) { 2697 onceProcessingComplete(); 2698 } else { 2699 api.state( 'processing' ).bind( onceProcessingComplete ); 2700 } 2701 2702 return deferred.promise(); 2703 }, 2704 2705 /** 2706 * Update a theme via wp.updates. 2707 * 2708 * @since 4.9.0 2709 */ 2710 updateTheme: function( event ) { 2711 wp.updates.maybeRequestFilesystemCredentials( event ); 2712 2713 $( document ).one( 'wp-theme-update-success', function( event, response ) { 2714 2715 // Rerender the control to reflect the update. 2716 api.control.each( function( control ) { 2717 if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) { 2718 control.params.theme.hasUpdate = false; 2719 control.rerenderAsInstalled( true ); 2720 } 2721 }); 2722 } ); 2723 2724 wp.updates.updateTheme( { 2725 slug: $( event.target ).closest( '.notice' ).data( 'slug' ) 2726 } ); 2727 }, 2728 2729 /** 2730 * Delete a theme via wp.updates. 2731 * 2732 * @since 4.9.0 2733 */ 2734 deleteTheme: function( event ) { 2735 var theme, section; 2736 theme = $( event.target ).data( 'slug' ); 2737 section = api.section( 'installed_themes' ); 2738 2739 event.preventDefault(); 2740 2741 // Confirmation dialog for deleting a theme. 2742 if ( ! window.confirm( api.settings.l10n.confirmDeleteTheme ) ) { 2743 return; 2744 } 2745 2746 wp.updates.maybeRequestFilesystemCredentials( event ); 2747 2748 $( document ).one( 'wp-theme-delete-success', function() { 2749 var control = api.control( 'installed_theme_' + theme ); 2750 2751 // Remove theme control. 2752 control.container.remove(); 2753 api.control.remove( control.id ); 2754 2755 // Update installed count. 2756 section.loaded = section.loaded - 1; 2757 section.updateCount(); 2758 2759 // Rerender any other theme controls as uninstalled. 2760 api.control.each( function( control ) { 2761 if ( 'theme' === control.params.type && control.params.theme.id === theme ) { 2762 control.rerenderAsInstalled( false ); 2763 } 2764 }); 2765 } ); 2766 2767 wp.updates.deleteTheme( { 2768 slug: theme 2769 } ); 2770 2771 // Close modal and focus the section. 2772 section.closeDetails(); 2773 section.focus(); 2774 } 2775 2776 }); 2777 2094 2778 /** 2095 2779 * A Customizer Control. 2096 2780 * … … 2440 3124 * @param {Boolean} active 2441 3125 * @param {Object} args 2442 3126 * @param {Number} args.duration 2443 * @param { Callback} args.completeCallback3127 * @param {Function} args.completeCallback 2444 3128 */ 2445 3129 onChangeActive: function ( active, args ) { 2446 3130 if ( args.unchanged ) { … … 3612 4296 api.ThemeControl = api.Control.extend({ 3613 4297 3614 4298 touchDrag: false, 3615 isRendered: false, 3616 3617 /** 3618 * Defer rendering the theme control until the section is displayed. 3619 * 3620 * @since 4.2.0 3621 */ 3622 renderContent: function () { 3623 var control = this, 3624 renderContentArgs = arguments; 3625 3626 api.section( control.section(), function( section ) { 3627 if ( section.expanded() ) { 3628 api.Control.prototype.renderContent.apply( control, renderContentArgs ); 3629 control.isRendered = true; 3630 } else { 3631 section.expanded.bind( function( expanded ) { 3632 if ( expanded && ! control.isRendered ) { 3633 api.Control.prototype.renderContent.apply( control, renderContentArgs ); 3634 control.isRendered = true; 3635 } 3636 } ); 3637 } 3638 } ); 3639 }, 4299 screenshotRendered: false, 3640 4300 3641 4301 /** 3642 4302 * @since 4.2.0 … … 3660 4320 } 3661 4321 3662 4322 // Prevent the modal from showing when the user clicks the action button. 3663 if ( $( event.target ).is( '.theme-actions .button' ) ) { 3664 return; 3665 } 3666 3667 api.section( control.section() ).loadThemePreview( control.params.theme.id ); 3668 }); 3669 3670 control.container.on( 'click keydown', '.theme-actions .theme-details', function( event ) { 3671 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 4323 if ( $( event.target ).is( '.theme-actions .button, .update-theme' ) ) { 3672 4324 return; 3673 4325 } 3674 4326 3675 4327 event.preventDefault(); // Keep this AFTER the key filter above 3676 3677 4328 api.section( control.section() ).showDetails( control.params.theme ); 3678 4329 }); 3679 4330 … … 3684 4335 if ( source ) { 3685 4336 $screenshot.attr( 'src', source ); 3686 4337 } 4338 control.screenshotRendered = true; 3687 4339 }); 3688 4340 }, 3689 4341 3690 4342 /** 3691 * Show or hide the theme based on the presence of the term in the title, description, and author.4343 * Show or hide the theme based on the presence of the term in the title, description, tags, and author. 3692 4344 * 3693 4345 * @since 4.2.0 3694 4346 */ … … 3701 4353 haystack = haystack.toLowerCase().replace( '-', ' ' ); 3702 4354 if ( -1 !== haystack.search( term ) ) { 3703 4355 control.activate(); 4356 return true; 3704 4357 } else { 3705 4358 control.deactivate(); 4359 return false; 4360 } 4361 }, 4362 4363 /** 4364 * Rerender the theme from its JS template with the installed type. 4365 * 4366 * @since 4.9.0 4367 */ 4368 rerenderAsInstalled: function( installed ) { 4369 var control = this, section; 4370 if ( installed ) { 4371 control.params.theme.type = 'installed'; 4372 } else { 4373 section = api.section( control.params.section ); 4374 control.params.theme.type = section.params.action; 3706 4375 } 4376 control.renderContent(); // Replaces existing content. 4377 control.container.trigger( 'render-screenshot' ); 3707 4378 } 3708 4379 }); 3709 4380 … … 4662 5333 theme: api.ThemeControl, 4663 5334 code_editor: api.CodeEditorControl 4664 5335 }; 4665 api.panelConstructor = {}; 5336 api.panelConstructor = { 5337 themes: api.ThemesPanel 5338 }; 4666 5339 api.sectionConstructor = { 4667 5340 themes: api.ThemesSection 4668 5341 }; … … 4780 5453 4781 5454 // Sort the sections within each panel 4782 5455 api.panel.each( function ( panel ) { 5456 if ( 'themes' === panel.id ) { 5457 return; // Don't reflow theme sections, as doing so moves them after the themes container. 5458 } 5459 4783 5460 var sections = panel.sections(), 4784 5461 sectionHeadContainers = _.pluck( sections, 'headContainer' ); 4785 5462 rootNodes.push( panel ); … … 5625 6302 // Collapse the most granular expanded object. 5626 6303 collapsedObject = expandedControls[0] || expandedSections[0] || expandedPanels[0]; 5627 6304 if ( collapsedObject ) { 6305 if ( 'themes' === collapsedObject.params.type ) { 6306 6307 // Themes panel or section. 6308 if ( $( 'body' ).hasClass( 'modal-open' ) ) { 6309 collapsedObject.closeDetails(); 6310 } else { 6311 6312 // If we're collapsing a section, collapse the panel also. 6313 wp.customize.panel( 'themes' ).collapse(); 6314 } 6315 return; 6316 } 5628 6317 collapsedObject.collapse(); 5629 6318 event.preventDefault(); 5630 6319 } -
src/wp-admin/js/updates.js
diff --git a/src/wp-admin/js/updates.js b/src/wp-admin/js/updates.js index 4f1bfbbdc2..725cdbc170 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 33f423aacb..5f146bb239 100644
a b public function __construct( $args = array() ) { 320 320 321 321 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' ); 322 322 323 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-panel.php' ); 323 324 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php' ); 324 325 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php' ); 325 326 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-section.php' ); … … public function __construct( $args = array() ) { 375 376 376 377 add_action( 'wp_ajax_customize_save', array( $this, 'save' ) ); 377 378 add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) ); 379 add_action( 'wp_ajax_customize-load-themes', array( $this, 'load_themes_ajax' ) ); 378 380 add_action( 'wp_ajax_dismiss_customize_changeset_autosave', array( $this, 'handle_dismiss_changeset_autosave_request' ) ); 379 381 380 382 add_action( 'customize_register', array( $this, 'register_controls' ) ); … … public function __construct( $args = array() ) { 392 394 393 395 // Export the settings to JS via the _wpCustomizeSettings variable. 394 396 add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 ); 397 398 // Add theme update notices. 399 if ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) ) { 400 require_once ABSPATH . '/wp-admin/includes/update.php'; 401 add_action( 'customize_controls_print_footer_scripts', 'wp_print_admin_notice_templates' ); 402 } 395 403 } 396 404 397 405 /** … … public function enqueue_control_scripts() { 3622 3630 foreach ( $this->controls as $control ) { 3623 3631 $control->enqueue(); 3624 3632 } 3633 3634 if ( ! is_multisite() && ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) || current_user_can( 'delete_themes' ) ) ) { 3635 wp_enqueue_script( 'updates' ); 3636 } 3625 3637 } 3626 3638 3627 3639 /** … … public function get_nonces() { 3826 3838 $nonces = array( 3827 3839 'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ), 3828 3840 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ), 3841 'switch-themes' => wp_create_nonce( 'switch-themes' ), 3829 3842 'dismiss_autosave' => wp_create_nonce( 'dismiss_customize_changeset_autosave' ), 3830 3843 ); 3831 3844 … … public function customize_pane_settings() { 3922 3935 'autofocus' => $this->get_autofocus(), 3923 3936 'documentTitleTmpl' => $this->get_document_title_template(), 3924 3937 'previewableDevices' => $this->get_previewable_devices(), 3938 'l10n' => array( 3939 'confirmDeleteTheme' => __( 'Are you sure you want to delete this theme?' ), 3940 /* translators: %d is the number of theme search results, which cannot currently consider singular vs. plural forms */ 3941 'themeSearchResults' => __( '%d themes found' ), 3942 /* translators: %d is the number of themes being displayed, which cannot currently consider singular vs. plural forms */ 3943 'announceThemeCount' => __( 'Displaying %d themes' ), 3944 /* translators: %s is the theme name */ 3945 'announceThemeDetails' => __( 'Showing details for theme: %s' ), 3946 ), 3925 3947 ); 3926 3948 3927 3949 // Prepare Customize Section objects to pass to JavaScript. … … public function register_controls() { 4024 4046 4025 4047 /* Panel, Section, and Control Types */ 4026 4048 $this->register_panel_type( 'WP_Customize_Panel' ); 4049 $this->register_panel_type( 'WP_Customize_Themes_Panel' ); 4027 4050 $this->register_section_type( 'WP_Customize_Section' ); 4028 4051 $this->register_section_type( 'WP_Customize_Sidebar_Section' ); 4052 $this->register_section_type( 'WP_Customize_Themes_Section' ); 4029 4053 $this->register_control_type( 'WP_Customize_Color_Control' ); 4030 4054 $this->register_control_type( 'WP_Customize_Media_Control' ); 4031 4055 $this->register_control_type( 'WP_Customize_Upload_Control' ); … … public function register_controls() { 4037 4061 $this->register_control_type( 'WP_Customize_Theme_Control' ); 4038 4062 $this->register_control_type( 'WP_Customize_Code_Editor_Control' ); 4039 4063 4040 /* Themes */4064 /* Themes (controls are loaded via ajax) */ 4041 4065 4042 $this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array( 4043 'title' => $this->theme()->display( 'Name' ), 4044 'capability' => 'switch_themes', 4045 'priority' => 0, 4066 $this->add_panel( new WP_Customize_Themes_Panel( $this, 'themes', array( 4067 'title' => $this->theme()->display( 'Name' ), 4068 '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.' ), 4069 'capability' => 'switch_themes', 4070 'priority' => 0, 4046 4071 ) ) ); 4047 4072 4048 // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience). 4049 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array( 4050 'capability' => 'switch_themes', 4073 $this->add_section( new WP_Customize_Themes_Section( $this, 'installed_themes', array( 4074 'title' => __( 'Installed themes' ), 4075 'action' => 'installed', 4076 'capability' => 'switch_themes', 4077 'panel' => 'themes', 4078 'priority' => 0, 4051 4079 ) ) ); 4052 4080 4053 require_once( ABSPATH . 'wp-admin/includes/theme.php' ); 4054 4055 // Theme Controls. 4056 4057 // Add a control for the active/original theme. 4058 if ( ! $this->is_theme_active() ) { 4059 $themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) ); 4060 $active_theme = current( $themes ); 4061 $active_theme['isActiveTheme'] = true; 4062 $this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array( 4063 'theme' => $active_theme, 4064 'section' => 'themes', 4065 'settings' => 'active_theme', 4081 if ( ! is_multisite() ) { 4082 $this->add_section( new WP_Customize_Themes_Section( $this, 'wporg_themes', array( 4083 'title' => __( 'WordPress.org themes' ), 4084 'action' => 'wporg', 4085 'capability' => 'install_themes', 4086 'panel' => 'themes', 4087 'priority' => 5, 4066 4088 ) ) ); 4067 4089 } 4068 4090 4069 $themes = wp_prepare_themes_for_js(); 4070 foreach ( $themes as $theme ) { 4071 if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) { 4072 continue; 4073 } 4074 4075 $theme_id = 'theme_' . $theme['id']; 4076 $theme['isActiveTheme'] = false; 4077 $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array( 4078 'theme' => $theme, 4079 'section' => 'themes', 4080 'settings' => 'active_theme', 4081 ) ) ); 4082 } 4091 // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience). 4092 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array( 4093 'capability' => 'switch_themes', 4094 ) ) ); 4083 4095 4084 4096 /* Site Identity */ 4085 4097 … … public function register_dynamic_settings() { 4586 4598 } 4587 4599 4588 4600 /** 4601 * Load themes into the theme browsing/installation UI. 4602 * 4603 * @since 4.9.0 4604 */ 4605 public function load_themes_ajax() { 4606 check_ajax_referer( 'switch-themes', 'switch-themes-nonce' ); 4607 4608 if ( ! current_user_can( 'switch_themes' ) ) { 4609 wp_die( -1 ); 4610 } 4611 4612 if ( empty( $_POST['theme_action'] ) ) { 4613 wp_send_json_error( 'missing_theme_action' ); 4614 } 4615 4616 require_once ABSPATH . 'wp-admin/includes/theme.php'; 4617 if ( 'installed' === $_POST['theme_action'] ) { 4618 $themes = array( 'themes' => wp_prepare_themes_for_js() ); 4619 foreach ( $themes['themes'] as &$theme ) { 4620 $theme['type'] = 'installed'; 4621 // Set active based on customized theme. 4622 if ( $_POST['customized_theme'] === $theme['id'] ) { 4623 $theme['active'] = true; 4624 } else { 4625 $theme['active'] = false; 4626 } 4627 } 4628 } elseif ( 'wporg' === $_POST['theme_action'] ) { 4629 if ( ! current_user_can( 'install_themes' ) ) { 4630 wp_die( -1 ); 4631 } 4632 4633 // Arguments for all queries. 4634 $args = array( 4635 'per_page' => 100, 4636 'page' => absint( $_POST['page'] ), 4637 'fields' => array( 4638 'screenshot_url' => true, 4639 'description' => true, 4640 'rating' => true, 4641 'downloaded' => true, 4642 'downloadlink' => true, 4643 'last_updated' => true, 4644 'homepage' => true, 4645 'num_ratings' => true, 4646 'tags' => true, 4647 'parent' => true, 4648 // 'extended_author' => true, @todo: WordPress.org throws a 500 server error when this is here. 4649 ), 4650 ); 4651 4652 // Define query filters based on user input. 4653 if ( ! array_key_exists( 'search', $_POST ) ) { 4654 $args['search'] = ''; 4655 } else { 4656 $args['search'] = wp_unslash( $_POST['search'] ); 4657 } 4658 4659 if ( ! array_key_exists( 'tags', $_POST ) ) { 4660 $args['tag'] = ''; 4661 } else { 4662 $args['tag'] = wp_unslash( $_POST['tags'] ); 4663 } 4664 4665 if ( '' === $args['search'] && '' === $args['tag'] ) { 4666 $args['browse'] = 'new'; // Sort by latest themes by default. 4667 } 4668 4669 // Load themes from the .org API. 4670 $themes = themes_api( 'query_themes', $args ); 4671 if ( is_wp_error( $themes ) ) { 4672 wp_send_json_error(); 4673 } 4674 4675 // This list matches the allowed tags in wp-admin/includes/theme-install.php. 4676 $themes_allowedtags = array('a' => array('href' => array(), 'title' => array(), 'target' => array()), 4677 'abbr' => array('title' => array()), 'acronym' => array('title' => array()), 4678 'code' => array(), 'pre' => array(), 'em' => array(), 'strong' => array(), 4679 'div' => array(), 'p' => array(), 'ul' => array(), 'ol' => array(), 'li' => array(), 4680 'h1' => array(), 'h2' => array(), 'h3' => array(), 'h4' => array(), 'h5' => array(), 'h6' => array(), 4681 'img' => array('src' => array(), 'class' => array(), 'alt' => array()) 4682 ); 4683 4684 // Prepare a list of installed themes to check against before the loop. 4685 $installed_themes = array(); 4686 $wp_themes = wp_get_themes(); 4687 foreach ( $wp_themes as $theme ) { 4688 $installed_themes[] = $theme->get_stylesheet(); 4689 } 4690 $update_php = network_admin_url( 'update.php?action=install-theme' ); 4691 4692 // Set up properties for themes available on WordPress.org. 4693 foreach ( $themes->themes as &$theme ) { 4694 $theme->install_url = add_query_arg( array( 4695 'theme' => $theme->slug, 4696 '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ), 4697 ), $update_php ); 4698 4699 $theme->name = wp_kses( $theme->name, $themes_allowedtags ); 4700 $theme->author = wp_kses( $theme->author, $themes_allowedtags ); 4701 $theme->version = wp_kses( $theme->version, $themes_allowedtags ); 4702 $theme->description = wp_kses( $theme->description, $themes_allowedtags ); 4703 $theme->tags = implode( ', ', $theme->tags ); 4704 $theme->stars = wp_star_rating( array( 'rating' => $theme->rating, 'type' => 'percent', 'number' => $theme->num_ratings, 'echo' => false ) ); 4705 $theme->num_ratings = number_format_i18n( $theme->num_ratings ); 4706 $theme->preview_url = set_url_scheme( $theme->preview_url ); 4707 4708 // Handle themes that are already installed as installed themes. 4709 if ( in_array( $theme->slug, $installed_themes, true ) ) { 4710 $theme->type = 'installed'; 4711 } else { 4712 $theme->type = $_POST['theme_action']; 4713 } 4714 4715 // Set active based on customized theme. 4716 if ( $_POST['customized_theme'] === $theme->slug ) { 4717 $theme->active = true; 4718 } else { 4719 $theme->active = false; 4720 } 4721 4722 // Map available theme properties to installed theme properties. 4723 $theme->id = $theme->slug; 4724 $theme->screenshot = array( $theme->screenshot_url ); 4725 $theme->authorAndUri = $theme->author; 4726 $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. 4727 unset( $theme->slug ); 4728 unset( $theme->screenshot_url ); 4729 unset( $theme->author ); 4730 } // End foreach(). 4731 } // End if(). 4732 wp_send_json_success( $themes ); 4733 } 4734 4735 4736 /** 4589 4737 * Callback for validating the header_textcolor value. 4590 4738 * 4591 4739 * 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..e7ac63274f 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 92 /* translators: %s is the linked update now button */ 93 printf( __( 'New version available. %s' ), '<button class="button-link update-theme" type="button">' . __( 'Update now' ) . '</button>' ); ?></p></div> 94 <# } #> 95 96 <# if ( data.theme.active ) { #> 97 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name"> 92 98 <?php 93 99 /* translators: %s: theme name */ 94 printf( __( '<span> Active:</span> %s' ), '{{{ data.theme.name }}}' );100 printf( __( '<span>Current:</span> %s' ), '{{ data.theme.name }}' ); 95 101 ?> 96 102 </h3> 103 <div class="theme-actions"> 104 <button type="button" class="button button-primary customize-theme" aria-label="<?php echo esc_attr( $customize_label ); ?>"><?php _e( 'Customize' ); ?></button> 105 </div> 106 <div class="notice notice-success notice-alt"><p><?php _e( 'Installed' ); ?></p></div> 107 <# } else if ( 'installed' === data.theme.type ) { #> 108 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> 109 <div class="theme-actions"> 110 <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> 111 </div> 112 <div class="notice notice-success notice-alt"><p><?php _e( 'Installed' ); ?></p></div> 97 113 <# } else { #> 98 <h3 class="theme-name" id="{{ data. theme.id }}-name">{{{ data.theme.name }}}</h3>114 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> 99 115 <div class="theme-actions"> 100 <button type="button" class="button theme-details"><?php _e( 'Theme Details' ); ?></button>116 <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 117 </div> 102 118 <# } #> 103 119 </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..7e197307c3
- + 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 if ( current_user_can( 'switch_themes' ) ) : ?> 49 <button type="button" class="button change-theme" aria-label="<?php _e( 'Change theme' ); ?>"><?php _ex( 'Change', 'theme' ); ?></button> 50 <?php endif; ?> 51 </h3> 52 <ul class="accordion-sub-container control-panel-content"></ul> 53 </li> 54 <?php 55 } 56 57 /** 58 * An Underscore (JS) template for this panel's content (but not its container). 59 * 60 * Class variables for this panel class are available in the `data` JS object; 61 * export custom variables by overriding WP_Customize_Panel::json(). 62 * 63 * @since 4.9.0 64 * 65 * @see WP_Customize_Panel::print_template() 66 */ 67 protected function content_template() { 68 ?> 69 <li class="panel-meta customize-info accordion-section <# if ( ! data.description ) { #> cannot-expand<# } #>"> 70 <button class="customize-panel-back" tabindex="-1" type="button"><span class="screen-reader-text"><?php _e( 'Back' ); ?></span></button> 71 <div class="accordion-section-title"> 72 <span class="preview-notice"><?php 73 /* translators: %s: themes panel title in the Customizer */ 74 echo sprintf( __( 'You are browsing %s' ), '<strong class="panel-title">' . __( 'Themes' ) . '</strong>' ); // Separate strings for consistency with other panels. 75 ?></span> 76 <?php if ( current_user_can( 'install_themes' ) && ! is_multisite() ) : ?> 77 <# if ( data.description ) { #> 78 <button class="customize-help-toggle dashicons dashicons-editor-help" type="button" aria-expanded="false"><span class="screen-reader-text"><?php _e( 'Help' ); ?></span></button> 79 <# } #> 80 <?php endif; ?> 81 </div> 82 <?php if ( current_user_can( 'install_themes' ) && ! is_multisite() ) : ?> 83 <# if ( data.description ) { #> 84 <div class="description customize-panel-description"> 85 {{{ data.description }}} 86 </div> 87 <# } #> 88 <?php endif; ?> 89 </li> 90 <li id="customize-themes-loading-container"> 91 <span class="customize-loading-text-installing-theme"><?php _e( 'Downloading your new theme…' ); ?></span> 92 <span class="customize-loading-text"><?php _e( 'Setting up your live preview. This may take a bit.' ); ?></span> 93 </li><?php // Used as a full-screen overlay transition after clicking to preview a theme. ?> 94 <li class="customize-themes-full-container-container"> 95 <ul class="customize-themes-full-container"> 96 <li class="customize-themes-notifications"></li> 97 </ul> 98 </li> 99 <?php 100 } 101 } -
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..ef9bd99c7f 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"> 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"> 80 72 </ul> 73 <p class="no-themes"><?php _e( 'No themes found. Try a different search.' ); ?></p> 74 <p class="no-themes-local"><?php 75 /* translators: %s is the string, "search WordPress.org themes" */ 76 printf( __( 'No themes found. Try a different search, or %s.' ), 77 sprintf( '<button type="button" class="button-link search-dotorg-themes">%s</button>', __( 'Search WordPress.org themes' ) ) 78 ); ?></p> 79 <p class="spinner"></p> 81 80 </div> 82 81 </div> 83 82 </li> 84 83 <?php } 84 85 /** 86 * Render the filter bar portion of a themes section as a JS template. 87 * 88 * The template is only rendered by PHP once, so all actions are prepared at once on the server side. 89 * The filter bar container is rendered by @see `render_template()`. 90 * 91 * @since 4.9.0 92 */ 93 protected function filter_bar_content_template() { 94 ?> 95 <button type="button" class="button button-primary customize-section-back customize-themes-mobile-back"><?php _e( 'Back to theme sources' ); ?></button> 96 <# if ( 'wporg' === data.action ) { #> 97 <div class="search-form"> 98 <label class="screen-reader-text" for="wp-filter-search-input"><?php _e( 'Search themes…' ); ?></label> 99 <input placeholder="<?php _e( 'Search themes…' ); ?>" type="search" aria-describedby="live-search-desc" id="wp-filter-search-input" class="wp-filter-search"> 100 <span id="live-search-desc" class="screen-reader-text"><?php _e( 'The search results will be updated as you type.' ); ?></span> 101 </div> 102 <button type="button" class="button feature-filter-toggle"> 103 <span class="filter-count-0"><?php _e( 'Filter themes' ); ?></span><span class="filter-count-filters"> 104 <?php /* translators: %s: number of filters selected. */ 105 printf( __( 'Filter themes (%s)' ), '<span class="theme-filter-count">0</span>' ); ?></span> 106 </button> 107 <div class="filter-drawer filter-details"> 108 <?php 109 $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. 110 foreach ( $feature_list as $feature_name => $features ) { 111 echo '<fieldset class="filter-group">'; 112 $feature_name = esc_html( $feature_name ); 113 echo '<legend>' . $feature_name . '</legend>'; 114 echo '<div class="filter-group-feature">'; 115 foreach ( $features as $feature => $feature_name ) { 116 $feature = esc_attr( $feature ); 117 $feature_name = esc_html( $feature_name ); 118 echo '<input type="checkbox" id="filter-id-' . $feature . '" value="' . $feature . '" /> '; 119 echo '<label for="filter-id-' . $feature . '">' . $feature_name . '</label><br>'; 120 } 121 echo '</div>'; 122 echo '</fieldset>'; 123 } 124 ?> 125 </div> 126 <# } else { #> 127 <p class="themes-filter-container"> 128 <label for="themes-filter"> 129 <span class="screen-reader-text"><?php _e( 'Search themes…' ); ?></span> 130 <input type="search" id="themes-filter" placeholder="<?php esc_attr_e( 'Search themes…' ); ?>" aria-describedby="live-search-desc" class="wp-filter-search wp-filter-search-themes" /> 131 <span id="live-search-desc" class="screen-reader-text"><?php _e( 'The search results will be updated as you type.' ); ?></span> 132 </label> 133 </p> 134 <# } #> 135 <div class="filter-themes-count"> 136 <span class="themes-displayed"><?php 137 /* translators: %s: number of themes displayed. */ 138 echo sprintf( __( '%s themes' ), '<span class="theme-count">0</span>' ); 139 ?></span> 140 </div> 141 <?php } 85 142 } -
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'] );