Ticket #37661: 37661.10.1.diff
File 37661.10.1.diff, 101.0 KB (added by , 7 years ago) |
---|
-
src/wp-admin/css/customize-controls.css
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; … … 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; … … 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, … … 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; … … 1238 1220 100% { opacity: 1; } 1239 1221 } 1240 1222 1241 /* #customize-container is reused from customize-loader.js, hence the naming. */ 1242 .wp-customizer .customize-loading #customize-container { 1223 .wp-customizer .customize-loading #customize-themes-loading-container { 1243 1224 display: block; 1244 1225 -webkit-animation: customize-reload .75s; /* Can't use `transition` because `display` changes here. */ 1245 1226 animation: customize-reload .75s; 1246 1227 } 1247 1228 1248 #customize-theme-controls .control-section-themes .accordion-section-title:hover, /* Not a focusable element. */ 1249 #customize-theme-controls .control-section-themes .accordion-section-title { 1229 .customize-loading #customize-themes-loading-container span { 1230 clear: both; 1231 color: #555d66; 1232 font-size: 18px; 1233 font-style: normal; 1234 margin: 0; 1235 padding: 100px 0; 1236 text-align: center; 1237 width: 100%; 1238 display: block; 1239 } 1240 1241 .customize-loading #customize-themes-loading-container .customize-loading-text { 1242 display: none; 1243 } 1244 1245 #customize-theme-controls .control-panel-themes { 1246 border-bottom: none; 1247 } 1248 1249 #customize-theme-controls .control-panel-themes > .accordion-section-title:hover, /* Not a focusable element. */ 1250 #customize-theme-controls .control-panel-themes > .accordion-section-title { 1250 1251 cursor: default; 1251 1252 background: #fff; 1252 1253 color: #555d66; … … 1253 1254 border-top: 1px solid #ddd; 1254 1255 border-bottom: 1px solid #ddd; 1255 1256 border-left: none; 1256 margin-top: 0; 1257 border-right: none; 1258 margin: 0 0 15px 0; 1259 padding-right: 100px; /* Space for the button */ 1257 1260 } 1258 #customize-theme-controls .control-section-themes .customize-section-back {1259 position: absolute;1260 right: 0;1261 top: 0;1262 height: 80px;1263 border-left: 1px solid #ddd;1264 border-right: 4px solid #fff;1265 }1266 #customize-theme-controls .control-section-themes .customize-section-back:before {1267 content: "\f345";1268 }1269 #customize-theme-controls .control-section-themes .customize-section-back:hover,1270 #customize-theme-controls .control-section-themes .customize-section-back:focus {1271 border-right-color: #0073aa;1272 }1273 1261 1274 1262 #customize-theme-controls .control-section-themes .customize-themes-panel .accordion-section-title:first-child:hover, /* Not a focusable element. */ 1275 1263 #customize-theme-controls .control-section-themes .customize-themes-panel .accordion-section-title:first-child { … … 1276 1264 border-top: 0; 1277 1265 } 1278 1266 1279 #customize-theme-controls .control-section-themes > .accordion-section-title:hover, /* Not a focusable element. */ 1280 #customize-theme-controls .control-section-themes > .accordion-section-title { 1281 margin: 0 0 15px; 1282 } 1283 1284 #customize-controls .customize-themes-panel .accordion-section-title:hover, 1285 #customize-controls .customize-themes-panel .accordion-section-title { 1286 margin: 15px -8px; 1287 } 1288 1289 #customize-controls .control-section-themes .accordion-section-title, 1290 #customize-controls .customize-themes-panel .accordion-section-title { 1291 padding-right: 100px; /* Space for the button */ 1292 } 1293 1294 #customize-controls .control-section-themes .accordion-section-title span.customize-action, 1267 .control-panel-themes .accordion-section-title span.customize-action, 1295 1268 #customize-controls .customize-section-title span.customize-action { 1296 1269 font-size: 13px; 1297 1270 display: block; … … 1298 1271 font-weight: 400; 1299 1272 } 1300 1273 1301 #customize-controls .control-section-themes .accordion-section-title .change-theme, 1302 #customize-controls .customize-themes-panel .accordion-section-title .customize-theme { 1274 .control-panel-themes .accordion-section-title .change-theme { 1303 1275 position: absolute; 1304 1276 right: 10px; 1305 1277 top: 50%; … … 1307 1279 font-weight: 400; 1308 1280 } 1309 1281 1310 #customize- controls .control-section-themes .accordion-section-title:before{1282 #customize-theme-controls .control-panel-themes > .accordion-section-title:after { 1311 1283 display: none; 1312 1284 } 1313 1285 1314 #customize-controls .customize-themes-panel { 1315 padding: 0 8px; 1316 background: #f1f1f1; 1317 box-sizing: border-box; 1286 .control-panel-themes .customize-themes-full-container { 1287 position: fixed; 1288 top: 0; 1289 left: 0; 1290 -webkit-transition: .18s left ease-in-out; 1291 transition: .18s left ease-in-out; 1292 margin: 0 0 0 300px; 1293 padding:25px; 1294 overflow-y: scroll; 1295 width: -webkit-calc(100% - 350px); 1296 width: calc(100% - 350px); 1297 height: -webkit-calc(100% - 50px); 1298 height: calc(100% - 50px); 1299 background: #eee; 1300 z-index: 20; 1318 1301 } 1319 1302 1320 #customize-controls .customize-themes-panel .accordion-section-title:first-child { 1321 margin-top: 0; 1303 /* Animations for opening the themes panel */ 1304 #customize-header-actions .save, 1305 #customize-header-actions .spinner, 1306 #customize-header-actions .customize-controls-preview-toggle { 1307 position: relative; 1308 top: 0; 1309 -webkit-transition: .18s top ease-in-out; 1310 transition: .18s top ease-in-out; 1322 1311 } 1323 1312 1324 #customize-controls .customize-themes-panel .accordion-section-title:nth-child(2) { 1313 #customize-footer-actions, 1314 #customize-footer-actions .collapse-sidebar { 1315 bottom: 0; 1316 -webkit-transition: .18s bottom ease-in-out; 1317 transition: .18s bottom ease-in-out; 1318 } 1319 1320 .in-themes-panel:not(.animating) #customize-header-actions .save, 1321 .in-themes-panel:not(.animating) #customize-header-actions .spinner, 1322 .in-themes-panel:not(.animating) #customize-header-actions .customize-controls-preview-toggle, 1323 .in-themes-panel:not(.animating) #customize-preview, 1324 .in-themes-panel:not(.animating) #customize-footer-actions { 1325 visibility: hidden; 1326 } 1327 1328 .wp-full-overlay.in-themes-panel { 1329 background: #eee; /* Prevents a black flash when fading in the panel */ 1330 } 1331 1332 .in-themes-panel #customize-header-actions .save, 1333 .in-themes-panel #customize-header-actions .spinner, 1334 .in-themes-panel #customize-header-actions .customize-controls-preview-toggle { 1335 top: -45px; 1336 } 1337 1338 .in-themes-panel #customize-footer-actions, 1339 .in-themes-panel #customize-footer-actions .collapse-sidebar { 1340 bottom: -45px; 1341 } 1342 1343 /* Don't show the theme count while the panel opens, as it's in the wrong place during the animation */ 1344 .in-themes-panel.animating .control-panel-themes .filter-themes-count { 1345 display: none; 1346 } 1347 1348 .in-themes-panel.wp-full-overlay .wp-full-overlay-sidebar-content { 1349 bottom: 0; 1350 } 1351 1352 /* Adds a delay before fading in to avoid it "jumping" */ 1353 @-webkit-keyframes themes-fade-in { 1354 0% { 1355 opacity: 0; 1356 } 1357 50% { 1358 opacity: 0; 1359 } 1360 100% { 1361 opacity: 1; 1362 } 1363 } 1364 @keyframes themes-fade-in { 1365 0% { 1366 opacity: 0; 1367 } 1368 50% { 1369 opacity: 0; 1370 } 1371 100% { 1372 opacity: 1; 1373 } 1374 } 1375 1376 .control-panel-themes .customize-themes-full-container.animate { 1377 -webkit-animation: .6s themes-fade-in 1; 1378 animation: .6s themes-fade-in 1; 1379 } 1380 1381 .in-themes-panel:not(.animating) .control-panel-themes .filter-themes-count { 1382 -webkit-animation: .6s themes-fade-in 1; 1383 animation: .6s themes-fade-in 1; 1384 } 1385 1386 .control-panel-themes .filter-themes-count { 1387 position: fixed; 1388 top: 0; 1389 left: 48px; 1390 width: 222px; 1391 padding: 6px 15px; 1392 margin: 0; 1393 line-height: 32px; 1394 text-align: right; 1395 z-index: 10; 1396 } 1397 1398 .control-panel-themes .filter-themes-count .themes-displayed { 1399 font-weight: 600; 1400 color: #555d66; 1401 } 1402 1403 .control-panel-themes .filter-themes-count .see-themes, 1404 .control-panel-themes .filter-themes-count .filter-themes { 1405 display: none; 1406 } 1407 1408 1409 /* Mobile - toggle between themes and filters */ 1410 @media screen and (max-width:600px) { 1411 1412 /* Show a spinner in the filters view also, reusing the main customize spinner */ 1413 .in-themes-panel.loading #customize-header-actions .spinner { 1414 position: fixed; 1415 top: 0; 1416 left: 48px; 1417 visibility: visible; 1418 } 1419 1420 .in-themes-panel.loading.showing-themes #customize-header-actions .spinner { 1421 visibility: hidden; 1422 } 1423 1424 .control-panel-themes .filter-themes-count { 1425 width: -webkit-calc(100% - 93px); 1426 width: calc(100% - 93px); 1427 } 1428 1429 .control-panel-themes .filter-themes-count .themes-displayed { 1430 display: none; 1431 } 1432 1433 .wp-full-overlay:not(.showing-themes) .control-panel-themes .filter-themes-count .see-themes { 1434 display: block; 1435 float: right; 1436 } 1437 1438 .wp-full-overlay.showing-themes .control-panel-themes .filter-themes-count .filter-themes { 1439 display: block; 1440 float: right; 1441 } 1442 1443 .in-themes-panel.showing-themes .control-panel-themes .customize-panel-back { 1444 position: fixed; 1445 top: 0; 1446 left: 0; 1447 z-index: 10; 1448 height: 45px; 1449 background: #eee; 1450 } 1451 1452 .in-themes-panel.showing-themes .control-panel-themes .customize-panel-back:before { 1453 line-height: 45px; 1454 } 1455 1456 .control-panel-themes .customize-themes-full-container { 1457 width: -webkit-calc(100% - 50px); 1458 width: calc(100% - 50px); 1459 margin: 0; 1460 top: 46px; 1461 height: -webkit-calc(100% - 96px); 1462 height: calc(100% - 96px); 1463 z-index: 1; 1464 display: none; 1465 } 1466 1467 .showing-themes .control-panel-themes .customize-themes-full-container { 1468 display: block; 1469 } 1470 } 1471 1472 .control-panel-themes .customize-themes-notifications .notice { 1473 margin: 0 0 25px 0; 1474 } 1475 1476 .customize-themes-full-container .customize-themes-section { 1477 display: none !important; /* There is unknown JS that perpetually tries to show all theme sections when more items are added. */ 1478 overflow: hidden; 1479 } 1480 1481 .customize-themes-full-container .customize-themes-section.current-section { 1482 display: list-item !important; /* There is unknown JS that perpetually tries to show all theme sections when more items are added. */ 1483 } 1484 1485 .theme-section .customize-themes-text-before { 1486 padding: 0 0 8px 15px; 1487 margin: 15px 0 0 0; 1488 line-height: 16px; 1489 border-bottom: 1px solid #ddd; 1490 color: #555d66; 1491 } 1492 1493 .control-panel-themes .customize-themes-section-title { 1494 width: 100%; 1495 background: #fff; 1496 -webkit-box-shadow: none; 1497 box-shadow: none; 1498 outline: none; 1499 border-top: none; 1500 border-bottom: 1px solid #ddd; 1501 border-left: 4px solid #fff; 1502 border-right: none; 1503 cursor: pointer; 1504 padding: 10px 15px; 1505 position: relative; 1506 text-align: left; 1325 1507 font-size: 14px; 1326 1508 font-weight: 600; 1509 color: #555d66; 1510 text-shadow: none; 1327 1511 } 1328 1512 1329 #customize-controls .customize-themes-panel > h2 { 1330 padding: 15px 8px 0 8px; 1513 .control-panel-themes .theme-section { 1514 margin: 0; 1515 position: relative; 1331 1516 } 1332 1517 1333 #customize-theme-controls .customize-themes-panel .accordion-section-content { 1518 .control-panel-themes .customize-themes-section-title:focus, 1519 .control-panel-themes .customize-themes-section-title:hover { 1520 border-left-color: #0073aa; 1521 color: #0073aa; 1522 background: #f5f5f5; 1523 } 1524 1525 .control-panel-themes .theme-section .customize-themes-section-title.selected:after { 1526 content: "\f147"; 1527 font: 16px/1 dashicons; 1528 box-sizing: border-box; 1529 width: 20px; 1530 height: 20px; 1531 padding: 3px 3px 1px 1px; /* Re-align the icon to the smaller grid */ 1532 -webkit-border-radius: 100%; 1533 border-radius: 100%; 1534 position: absolute; 1535 top: 9px; 1536 right: 15px; 1537 background: #0073aa; 1538 color: #fff; 1539 } 1540 1541 .control-panel-themes .customize-themes-section-title.selected { 1542 color: #0073aa; 1543 } 1544 1545 .control-panel-themes .customize-themes-section-title.themes-section-search_themes { 1546 border-left: none; 1547 padding: 5px 10px 5px 15px; 1548 width: auto; 1549 } 1550 1551 .control-panel-themes .customize-themes-section-title.themes-section-feature_filter_themes:after, 1552 .control-panel-themes .customize-themes-section-title.themes-section-favorites_themes:after { 1553 content: "\f140"; 1554 font: 20px/1 dashicons; 1555 position: absolute; 1556 right: 15px; 1557 top: 8px; 1558 } 1559 1560 .control-panel-themes .customize-themes-section-title.themes-section-search_themes .wp-filter-search { 1561 width: 100%; 1562 } 1563 1564 .control-panel-themes .customize-themes-section-title.themes-section-search_themes.selected, 1565 .control-panel-themes .customize-themes-section-title.themes-section-search_themes:hover { 1566 background: #fff; 1567 cursor: default; 1568 } 1569 1570 .control-panel-themes .customize-themes-section-title.themes-section-feature_filter_themes { 1571 margin-top: 15px; 1572 border-top: 1px solid #ddd; 1573 } 1574 1575 .control-panel-themes .filter-details { 1576 background: #f5f5f5; 1577 margin: 0; 1578 padding: 8px 15px; 1579 border-top: none; 1580 border-bottom: 1px solid #ddd; 1581 display: none; 1582 } 1583 1584 .control-panel-themes .customize-themes-section-title.selected.details-open { 1585 border-bottom-color: #f5f5f5; 1586 border-left-color: #f5f5f5; 1587 background: #f5f5f5; 1588 } 1589 1590 .control-panel-themes .favorites-form.filter-details label { 1591 padding-bottom: 6px; 1592 display: inline-block; 1593 } 1594 1595 .control-panel-themes .filter-details .filter-group { 1596 float: none; 1597 width: 100%; 1334 1598 background: transparent; 1335 display: block; 1599 border: none; 1600 padding: 0; 1601 -webkit-box-shadow: none; 1602 box-shadow: none; 1336 1603 } 1337 1604 1338 .customize-control.customize-control-theme { 1339 margin-bottom: 8px; 1605 .control-panel-themes .filter-details .filter-group legend button { 1606 padding: 18px 15px 8px 10px; 1607 line-height: 14px; 1608 border-bottom: 1px solid #ddd; 1609 width: 100%; 1610 text-align: left; 1611 text-decoration: none; 1340 1612 } 1341 1613 1614 .control-panel-themes .filter-details .filter-group legend { 1615 position: relative; 1616 top: 0; 1617 width: 100%; 1618 } 1619 1620 .control-panel-themes .filter-details .filter-group legend button:after { 1621 content: "\f140"; 1622 font: 20px/1 dashicons; 1623 position: absolute; 1624 bottom: 6px; 1625 right: 5px; 1626 } 1627 1628 .control-panel-themes .filter-details .filter-group legend button:hover, 1629 .control-panel-themes .filter-details .filter-group legend button:focus { 1630 color: #0073aa; 1631 border-bottom-color: #0073aa; /* Color change for focus style should be acceptable because border-bottom is barely visible previously. */ 1632 outline: none; 1633 -webkit-box-shadow: none; 1634 box-shadow: none; 1635 } 1636 1637 .control-panel-themes .filter-details .filter-group legend button.open:after { 1638 content: "\f142"; 1639 } 1640 1641 .control-panel-themes .filter-details .filter-group .filter-group-feature { 1642 display: none; 1643 margin: 0; 1644 } 1645 1646 .control-panel-themes .filter-details .filter-group-feature label { 1647 border: 1px solid #ddd; 1648 border-top: 0; 1649 background: #fff; 1650 color: #555d66; 1651 margin: 0; 1652 padding: 12px 10px 12px 34px; 1653 width: -webkit-calc(100% - 46px); 1654 width: calc(100% - 46px); 1655 line-height: 16px; 1656 font-weight: 600; 1657 } 1658 1659 .control-panel-themes .filter-details .filter-group-feature input { 1660 position: absolute; 1661 margin: 12px 10px; 1662 } 1663 1664 .control-panel-themes .filter-details .filter-group-feature label:hover { 1665 color: #0073aa; 1666 } 1667 1342 1668 #customize-theme-controls .themes.accordion-section-content { 1343 1669 position: relative; 1344 1670 left: 0; … … 1346 1672 width: 100%; 1347 1673 } 1348 1674 1349 .wp-customizer .theme-browser .themes { 1350 padding-bottom: 8px; 1675 .loading .customize-themes-section .spinner { 1676 display: block; 1677 visibility: visible; 1678 position: relative; 1679 clear: both; 1680 width: 20px; 1681 height: 20px; 1682 left: -webkit-calc(50% - 10px); 1683 left: calc(50% - 10px); 1684 float: none; 1685 margin-top: 50px; 1351 1686 } 1352 1687 1353 .wp-customizer .theme-browser .theme { 1688 .customize-themes-section .filter-drawer { 1689 border-top: none; 1690 display: block; 1691 background: transparent; 1692 padding-top: 5px; 1693 } 1694 1695 .customize-themes-section .clear-filters { 1696 margin-left: 8px; 1697 display: none; 1698 } 1699 1700 .customize-themes-section .no-themes { 1701 display: none; 1702 } 1703 1704 .themes-section-installed_themes .theme .notice-success { 1705 display: none; /* Hide "installed" notice on installed themes tab. */ 1706 } 1707 1708 .control-panel-themes .theme-browser .theme .theme-actions .button-primary { 1709 margin: 0 0 0 8px; 1710 } 1711 1712 .customize-control-theme .theme { 1713 width: 100%; 1354 1714 margin: 0; 1355 width: 100%;1356 1715 } 1357 1716 1717 .customize-control.customize-control-theme { /* override most properties on .customize-control */ 1718 -webkit-box-sizing: border-box; 1719 -moz-box-sizing: border-box; 1720 box-sizing: border-box; 1721 width: 18.4%; 1722 margin: 0 2% 2% 0; 1723 padding: 0; 1724 clear: none; 1725 } 1726 1727 /* 5 columns above 2100px */ 1728 @media screen and (min-width: 2101px) { 1729 .customize-control.customize-control-theme:nth-child(5n) { 1730 margin-right: 0; 1731 } 1732 } 1733 1734 /* 4 columns up to 2100px */ 1735 @media screen and (min-width: 1601px) and (max-width: 2100px) { 1736 .customize-control.customize-control-theme { 1737 width: 23.5%; 1738 } 1739 1740 .customize-control.customize-control-theme:nth-child(4n) { 1741 margin-right: 0; 1742 } 1743 } 1744 1745 /* 3 columns up to 1600px */ 1746 @media screen and (min-width: 1201px) and (max-width: 1600px) { 1747 .customize-control.customize-control-theme { 1748 width: 32%; 1749 } 1750 1751 .customize-control.customize-control-theme:nth-child(3n) { 1752 margin-right: 0; 1753 } 1754 } 1755 1756 /* 2 columns up to 1200px */ 1757 @media screen and (min-width: 851px) and (max-width: 1200px) { 1758 .customize-control.customize-control-theme { 1759 width: 49%; 1760 } 1761 1762 .customize-control.customize-control-theme:nth-child(even) { 1763 margin-right: 0; 1764 } 1765 } 1766 1767 /* 1 column up to 850 px */ 1768 @media screen and (max-width: 850px) { 1769 .customize-control.customize-control-theme { 1770 width: 100%; 1771 margin: 0 0 3% 0; 1772 } 1773 } 1774 1775 .wp-customizer .theme-browser .themes { 1776 padding-bottom: 8px; 1777 } 1778 1358 1779 .wp-customizer .theme-browser .theme .theme-actions { 1359 -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";1360 1780 opacity: 1; 1361 1781 } 1362 1782 … … 1375 1795 width: 100%; 1376 1796 } 1377 1797 1378 .control-section-themes .accordion-section-title:after,1379 .customize-themes-panel .accordion-section-title:after {1380 display: none;1381 }1382 1383 .customize-themes-panel.control-panel-content {1384 border-top: 1px solid #ddd;1385 }1386 1387 1798 /* Details View */ 1388 1799 .wp-customizer .theme-overlay { 1389 1800 display: none; … … 1398 1809 z-index: 109; 1399 1810 } 1400 1811 1812 /* Avoid a z-index war by resetting elements that should be under the overlay. 1813 This is likely required because of the way that sections and panels are positioned. */ 1814 .wp-customizer.modal-open #customize-header-actions, 1815 .wp-customizer.modal-open .control-panel-themes .filter-themes-count, 1816 .wp-customizer.modal-open .control-panel-themes .customize-themes-section-title.selected:after { 1817 z-index: -1; 1818 } 1819 1401 1820 .wp-customizer .theme-overlay .theme-backdrop { 1402 1821 background: rgba( 238, 238, 238, 0.75 ); 1403 1822 position: fixed; … … 1404 1823 z-index: 110; 1405 1824 } 1406 1825 1826 .wp-customizer .theme-overlay .star-rating { 1827 float: left; 1828 margin-right: 8px; 1829 } 1830 1831 .wp-customizer .theme-rating .num-ratings { 1832 line-height: 20px; 1833 } 1834 1407 1835 .wp-customizer .theme-overlay .theme-wrap { 1408 1836 left: 90px; 1409 1837 right: 90px; … … 1410 1838 top: 45px; 1411 1839 bottom: 45px; 1412 1840 z-index: 120; 1413 max-width: 1740px; /* To ensure that theme screenshots are not displayed larger than 880px wide. */1414 1841 } 1415 1842 1416 1843 .wp-customizer .theme-overlay .theme-actions { 1417 text-align: right; /* Because there's only one action, match the pattern of media modals and right-align the action. */ 1844 text-align: right; /* Because there're only one or two actions, match the UI pattern of media modals and right-align the action. */ 1845 padding: 10px 15px; 1418 1846 } 1419 1847 1420 .ie8 .wp-customizer .theme-overlay .theme-header, 1421 .ie8 .wp-customizer .theme-overlay .theme-about, 1422 .ie8 .wp-customizer .theme-overlay .theme-actions { 1423 position: static; 1848 .wp-customizer .theme-overlay .theme-actions .theme-install.preview { 1849 margin-left: 8px; 1424 1850 } 1425 1851 1852 .control-panel-themes .theme-actions .delete-theme { 1853 left: 15px; /* these override themes.css on mobile */ 1854 right: auto; 1855 bottom: auto; 1856 position: absolute; 1857 } 1858 1859 .modal-open .in-themes-panel #customize-controls .wp-full-overlay-sidebar-content { 1860 overflow: visible; /* Prevent the top-level Customizer controls from becoming visible when elements on the right of the details modal are focused. */ 1861 } 1862 1863 1426 1864 /* Small Screens */ 1427 1865 @media (max-width:850px), (max-height:472px) { 1428 1866 .wp-customizer .theme-overlay .theme-wrap { … … 1448 1886 body.cheatin h1 { 1449 1887 border-bottom: 1px solid #ddd; 1450 1888 clear: both; 1451 color: # 666;1889 color: #555d66; 1452 1890 font-size: 24px; 1453 1891 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 1454 1892 margin: 30px 0 0 0; -
src/wp-admin/css/themes.css
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 … … 1705 1705 display: none; 1706 1706 } 1707 1707 1708 #customize-container { 1708 #customize-container, 1709 #customize-themes-loading-container { 1709 1710 display: none; 1710 1711 background: #fff; 1711 1712 z-index: 500000; … … 1720 1721 1721 1722 /* Make the Customizer and Theme installer overlays the only available content. */ 1722 1723 #customize-container, 1724 #customize-themes-loading-container, 1723 1725 .theme-install-overlay { 1724 1726 visibility: visible; 1725 1727 } … … 1824 1826 1825 1827 #customize-preview.wp-full-overlay-main:before, 1826 1828 .customize-loading #customize-container:before, 1829 .customize-loading #customize-themes-loading-container:before, 1827 1830 .theme-install-overlay .wp-full-overlay-main:before { 1828 1831 content: ""; 1829 1832 display: block; … … 1861 1864 1862 1865 #customize-preview.wp-full-overlay-main:before, 1863 1866 .customize-loading #customize-container:before, 1867 .customize-loading #customize-themes-loading-container:before, 1864 1868 .theme-install-overlay .wp-full-overlay-main:before { 1865 1869 background-image: url(../images/spinner-2x.gif); 1866 1870 } -
src/wp-admin/customize.php
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
570 570 571 571 $parent = false; 572 572 if ( $theme->parent() ) { 573 $parent = $theme->parent()->display( 'Name' ); 574 $parents[ $slug ] = $theme->parent()->get_stylesheet(); 573 $parent = $theme->parent(); 574 $parents[ $slug ] = $parent->get_stylesheet(); 575 $parent = $parent->display( 'Name' ); 575 576 } 576 577 577 578 $customize_action = null; … … 631 632 * @since 4.2.0 632 633 */ 633 634 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 635 ?> 637 636 <script type="text/html" id="tmpl-customize-themes-details-view"> 638 637 <div class="theme-backdrop"></div> … … 644 643 </div> 645 644 <div class="theme-about wp-clearfix"> 646 645 <div class="theme-screenshots"> 647 <# if ( data.screenshot [0] ) { #>646 <# if ( data.screenshot && data.screenshot[0] ) { #> 648 647 <div class="screenshot"><img src="{{ data.screenshot[0] }}" alt="" /></div> 649 648 <# } else { #> 650 649 <div class="screenshot blank"></div> … … 657 656 <# } #> 658 657 <h2 class="theme-name">{{{ data.name }}}<span class="theme-version"><?php printf( __( 'Version: %s' ), '{{ data.version }}' ); ?></span></h2> 659 658 <h3 class="theme-author"><?php printf( __( 'By %s' ), '{{{ data.authorAndUri }}}' ); ?></h3> 660 <p class="theme-description">{{{ data.description }}}</p>661 659 660 <# if ( data.stars && 0 != data.num_ratings ) { #> 661 <div class="theme-rating"> 662 {{{ data.stars }}} 663 <span class="num-ratings"><?php echo sprintf( __( '(%s ratings)' ), '{{ data.num_ratings }}' ); ?></span> 664 </div> 665 <# } #> 666 667 <# if ( data.hasUpdate ) { #> 668 <div class="notice notice-warning notice-alt notice-large" data-slug="{{ data.id }}"> 669 <h3 class="notice-title"><?php _e( 'Update Available' ); ?></h3> 670 {{{ data.update }}} 671 </div> 672 <# } #> 673 662 674 <# if ( data.parent ) { #> 663 675 <p class="parent-theme"><?php printf( __( 'This is a child theme of %s.' ), '<strong>{{{ data.parent }}}</strong>' ); ?></p> 664 676 <# } #> 665 677 678 <p class="theme-description">{{{ data.description }}}</p> 679 666 680 <# if ( data.tags ) { #> 667 <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{ data.tags}}</p>681 <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{{ data.tags }}}</p> 668 682 <# } #> 669 683 </div> 670 684 </div> 671 685 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 <# } #> 686 <div class="theme-actions"> 687 <# if ( data.active ) { #> 688 <button type="button" class="button button-primary customize-theme"><?php _e( 'Customize' ); ?></a> 689 <# } else if ( 'installed' === data.type ) { #> 690 <?php if ( current_user_can( 'delete_themes' ) ) { ?> 691 <# if ( data.actions && data.actions['delete'] ) { #> 692 <a href="{{{ data.actions['delete'] }}}" data-slug="{{ data.id }}" class="button button-secondary delete-theme"><?php _e( 'Delete' ); ?></a> 693 <# } #> 694 <?php } ?> 695 <button type="button" class="button button-primary preview-theme" data-slug="{{ data.id }}"><?php _e( 'Live Preview' ); ?></span> 696 <# } else { #> 697 <button type="button" class="button theme-install" data-slug="{{ data.id }}"><?php _e( 'Install' ); ?></button> 698 <button type="button" class="button button-primary theme-install preview" data-slug="{{ data.id }}"><?php _e( 'Install & Preview' ); ?></button> 699 <# } #> 700 </div> 683 701 </div> 684 702 </script> 685 703 <?php -
src/wp-admin/js/customize-controls.js
1106 1106 section = this, 1107 1107 container = $( '#customize-theme-controls' ); 1108 1108 1109 // Watch for changes to the panel state 1109 // Watch for changes to the panel state. 1110 1110 inject = function ( panelId ) { 1111 1111 var parentContainer; 1112 1112 if ( panelId ) { 1113 // The panel has been supplied, so wait until the panel object is registered 1113 // The panel has been supplied, so wait until the panel object is registered. 1114 1114 api.panel( panelId, function ( panel ) { 1115 // The panel has been registered, wait for it to become ready/initialized 1115 // The panel has been registered, wait for it to become ready/initialized. 1116 1116 panel.deferred.embedded.done( function () { 1117 1117 parentContainer = panel.contentContainer; 1118 1118 if ( ! section.headContainer.parent().is( parentContainer ) ) { … … 1137 1137 } 1138 1138 }; 1139 1139 section.panel.bind( inject ); 1140 inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one 1140 inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one. 1141 1141 }, 1142 1142 1143 1143 /** … … 1310 1310 /** 1311 1311 * wp.customize.ThemesSection 1312 1312 * 1313 * Custom section for themes that functions similarly to a backwards panel,1314 * and alsohandles the theme-details view rendering and navigation.1313 * Custom section for themes that loads themes by category, and also 1314 * handles the theme-details view rendering and navigation. 1315 1315 * 1316 1316 * @constructor 1317 1317 * @augments wp.customize.Section … … 1323 1323 template: '', 1324 1324 screenshotQueue: null, 1325 1325 $window: $( window ), 1326 loaded: 0, 1327 loading: false, 1328 fullyLoaded: false, 1329 term: '', 1330 nextTerm: '', 1331 filterContainer: $(), 1326 1332 1327 1333 /** 1328 * @since 4.2.0 1334 * Embed the section in the DOM when the themes panel is ready. 1335 * 1336 * Insert the section before the themes container. Assume that a themes section is within a panel, but not necessarily the themes panel. 1337 * 1338 * @since 4.9.0 1329 1339 */ 1330 initialize: function () { 1331 this.$customizeSidebar = $( '.wp-full-overlay-sidebar-content:first' ); 1332 return api.Section.prototype.initialize.apply( this, arguments ); 1340 embed: function () { 1341 var inject, 1342 section = this, 1343 container = $( '#customize-theme-controls' ); 1344 1345 // Watch for changes to the panel state 1346 inject = function ( panelId ) { 1347 var parentContainer; 1348 api.panel( panelId, function ( panel ) { 1349 // The panel has been registered, wait for it to become ready/initialized 1350 panel.deferred.embedded.done( function () { 1351 parentContainer = panel.contentContainer; 1352 if ( ! section.headContainer.parent().is( parentContainer ) ) { 1353 parentContainer.find( '.customize-themes-full-container-container' ).before( section.headContainer ); 1354 } 1355 if ( ! section.contentContainer.parent().is( section.headContainer ) ) { 1356 container.append( section.contentContainer ); 1357 } 1358 section.deferred.embedded.resolve(); 1359 }); 1360 } ); 1361 }; 1362 section.panel.bind( inject ); 1363 inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one 1333 1364 }, 1334 1365 1335 1366 /** … … 1363 1394 } 1364 1395 }); 1365 1396 1366 _.bindAll( this, 'renderScreenshots' );1397 _.bindAll( this, 'renderScreenshots', 'loadMore', 'checkTerm', 'filtersChecked' ); 1367 1398 }, 1368 1399 1369 1400 /** … … 1370 1401 * Override Section.isContextuallyActive method. 1371 1402 * 1372 1403 * Ignore the active states' of the contained theme controls, and just 1373 * use the section's own active state instead. This ensures empty search1374 * results for theme s to causethe section to become inactive.1404 * use the section's own active state instead. This prevents empty search 1405 * results for theme sections from causing the section to become inactive. 1375 1406 * 1376 1407 * @since 4.2.0 1377 1408 * … … 1396 1427 section.collapse(); 1397 1428 }); 1398 1429 1399 // Expand/Collapse section/panel. 1400 section.container.find( '.change-theme, .customize-theme' ).on( 'click keydown', function( event ) { 1401 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1402 return; 1430 section.filterContainer = $( '#accordion-section-' + section.id ); 1431 1432 // Expand section/panel. Only collapse when opening another section. 1433 section.filterContainer.on( 'click', '.customize-themes-section-title', function() { 1434 1435 // Toggle filters. 1436 if ( section.filterContainer.find( '.filter-details' ).length ) { 1437 section.filterContainer.find( '.customize-themes-section-title' ) 1438 .toggleClass( 'details-open' ) 1439 .attr('aria-expanded', function ( i, attr ) { 1440 return attr === 'true' ? 'false' : 'true'; 1441 }); 1442 section.filterContainer.find( '.filter-details' ).slideToggle( 180 ); 1403 1443 } 1404 event.preventDefault(); // Keep this AFTER the key filter above1405 1444 1406 if ( section.expanded() ) { 1407 section.collapse(); 1408 } else { 1409 section.expand(); 1445 // Open the section. 1446 if ( ! section.expanded() ) { 1447 1448 // Don't expand if there's nothing to show. 1449 if ( -1 !== $.inArray( section.params.action, [ 'search', 'favorites', 'feature_filter' ] ) && '' === section.term ) { 1450 return; 1451 } else { 1452 section.expand(); 1453 } 1410 1454 } 1411 1455 }); 1412 1456 1413 // Theme navigation in details view. 1414 section.container.on( 'click keydown', '.left', function( event ) { 1415 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1416 return; 1417 } 1457 // Preview installed themes. 1458 section.container.on( 'click', '.theme-actions .preview-theme', function() { 1459 var themeId = $( this ).data( 'slug' ); 1418 1460 1419 event.preventDefault(); // Keep this AFTER the key filter above 1461 $( '.wp-full-overlay' ).addClass( 'customize-loading' ); 1462 api.panel( 'themes' ).loadThemePreview( themeId ).fail( function() { 1463 $( '.wp-full-overlay' ).removeClass( 'customize-loading' ); 1464 } ); 1465 }); 1420 1466 1467 // Theme navigation in details view. 1468 section.container.on( 'click', '.left', function() { 1421 1469 section.previousTheme(); 1422 1470 }); 1423 1471 1424 section.container.on( 'click keydown', '.right', function( event ) { 1425 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1426 return; 1427 } 1428 1429 event.preventDefault(); // Keep this AFTER the key filter above 1430 1472 section.container.on( 'click', '.right', function() { 1431 1473 section.nextTheme(); 1432 1474 }); 1433 1475 1434 section.container.on( 'click keydown', '.theme-backdrop, .close', function( event ) { 1435 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 1436 return; 1437 } 1438 1439 event.preventDefault(); // Keep this AFTER the key filter above 1440 1476 section.container.on( 'click', '.theme-backdrop, .close', function() { 1441 1477 section.closeDetails(); 1442 1478 }); 1443 1479 1444 1480 var renderScreenshots = _.throttle( _.bind( section.renderScreenshots, this ), 100 ); 1445 section.container.on( 'input', '#themes-filter', function( event ) { 1481 1482 // Only used when there is only one section - installed themes. 1483 $( '.control-panel-themes' ).on( 'input', '#themes-filter', function( event ) { 1446 1484 var count, 1447 1485 term = event.currentTarget.value.toLowerCase().trim().replace( '-', ' ' ), 1448 1486 controls = section.controls(); … … 1454 1492 renderScreenshots(); 1455 1493 1456 1494 // Update theme count. 1457 count = section.cont ainer.find( 'li.customize-control:visible' ).length;1458 section.container.find( '.theme-count' ).text( count );1495 count = section.contentContainer.find( 'li.customize-control:visible' ).length; 1496 $( '.control-panel-themes' ).find( '.theme-count' ).text( count ); 1459 1497 }); 1460 1498 1461 // Pre-load the first 3 theme screenshots.1462 api.bind( 'ready', function () {1463 _.each( section.controls().slice( 0, 3 ), function ( control ) {1464 var img, src = control.params.theme.screenshot[0];1465 if ( src ) {1466 img = new Image();1467 img.src = src;1499 // Event listeners for queries with user-entered terms. 1500 if ( 'search' === section.params.action ) { 1501 var debounced = _.debounce( section.checkTerm, 500 ); // Wait until there is no input for 500 milliseconds to initiate a search. 1502 $( '.control-panel-themes' ).on( 'input', '#wp-filter-search-input', function() { 1503 debounced( section ); 1504 if ( ! section.expanded() ) { 1505 section.expand(); 1468 1506 } 1469 1507 }); 1508 1509 // Focus the input if the icon is clicked. 1510 section.filterContainer.find( '.search-form' ).on( 'click', function( e ) { 1511 if ( ! $( e.currentTarget ).hasClass( 'wp-filter-search' ) ) { 1512 $( e.currentTarget ).find( '.wp-filter-search' ).focus(); 1513 } 1514 }); 1515 } else if ( 'favorites' === section.params.action ) { 1516 section.checkTerm( section ); // Expand the section if there's already a term. 1517 section.container.on( 'click', '.favorites-form-submit', function() { 1518 section.checkTerm( section ); 1519 }); 1520 section.container.on( 'keydown', '#wporg-username-input', function( e ) { 1521 if ( api.utils.isKeydownButNotEnterEvent( e ) ) { 1522 return; 1523 } 1524 section.checkTerm( section ); 1525 }); 1526 } else if ( 'feature_filter' === section.params.action ) { 1527 section.checkTerm( section ); // Expand the section if there's already a term. 1528 section.container.on( 'click', '.filter-group input', function() { 1529 section.filtersChecked(); 1530 section.checkTerm( section ); 1531 }); 1532 1533 // Toggle feature filter sections. 1534 section.container.on( 'click', '.filter-group legend button', function( e ) { 1535 $( e.currentTarget ) 1536 .toggleClass( 'open' ) 1537 .attr('aria-expanded', function ( i, attr ) { 1538 return attr === 'true' ? 'false' : 'true'; 1539 }) 1540 .parent().next( '.filter-group-feature' ).slideToggle( 180 ); 1541 }); 1542 } 1543 1544 // Move section controls to the themes area. 1545 api.bind( 'ready', function () { 1546 section.contentContainer = section.container.find( '.customize-themes-section' ); 1547 section.contentContainer.appendTo( $( '.customize-themes-full-container' ) ); 1548 section.container.add( section.filterContainer ); 1470 1549 }); 1471 1550 }, 1472 1551 … … 1478 1557 * @param {Boolean} expanded 1479 1558 * @param {Object} args 1480 1559 * @param {Boolean} args.unchanged 1481 * @param { Callback} args.completeCallback1560 * @param {Function} args.completeCallback 1482 1561 */ 1483 1562 onChangeExpanded: function ( expanded, args ) { 1484 1563 … … 1491 1570 } 1492 1571 1493 1572 // Note: there is a second argument 'args' passed 1494 var panel = this, 1495 section = panel.contentContainer, 1496 overlay = section.closest( '.wp-full-overlay' ), 1497 container = section.closest( '.wp-full-overlay-sidebar-content' ), 1498 customizeBtn = section.find( '.customize-theme' ), 1499 changeBtn = panel.headContainer.find( '.change-theme' ); 1573 var section = this, 1574 container = section.contentContainer.closest( '.customize-themes-full-container' ); 1500 1575 1501 if ( expanded && ! section.hasClass( 'current-panel' ) ) { 1576 if ( expanded ) { 1577 1578 if ( -1 !== $.inArray( section.params.action, [ 'search', 'favorites', 'feature_filter' ] ) && '' === section.term ) { 1579 section.collapse(); // Note that the current section hasn't been collapsed yet, so this is all we need to do to do nothing. 1580 return; // Don't expand to an empty section that can't load any themes. 1581 } 1582 1583 // Load controls if none are loaded yet. 1584 if ( 0 === section.loaded ) { 1585 section.loadControls(); 1586 } 1587 1502 1588 // Collapse any sibling sections/panels 1503 1589 api.section.each( function ( otherSection ) { 1504 if ( otherSection !== panel) {1590 if ( otherSection !== section ) { 1505 1591 otherSection.collapse( { duration: args.duration } ); 1506 1592 } 1507 1593 }); 1508 api.panel.each( function ( otherPanel ) {1509 otherPanel.collapse( { duration: 0 } );1510 });1511 1594 1512 panel._animateChangeExpanded( function() {1513 changeBtn.attr( 'tabindex', '-1');1514 customizeBtn.attr( 'tabindex', '0' );1595 section.contentContainer.addClass( 'current-section' ); 1596 container.scrollTop(); 1597 section.filterContainer.find( '.customize-themes-section-title' ).addClass( 'selected' ); 1515 1598 1516 customizeBtn.focus(); 1517 section.css( 'top', '' ); 1518 container.scrollTop( 0 ); 1599 container.on( 'scroll', _.throttle( section.renderScreenshots, 300 ) ); 1600 container.on( 'scroll', _.throttle( section.loadMore, 300 ) ); 1519 1601 1520 if ( args.completeCallback ) { 1521 args.completeCallback(); 1522 } 1523 } ); 1602 if ( args.completeCallback ) { 1603 args.completeCallback(); 1604 } 1605 section.updateCount(); // Show this section's count. 1606 } else { 1607 section.contentContainer.removeClass( 'current-section' ); 1524 1608 1525 overlay.addClass( 'in-themes-panel' ); 1526 section.addClass( 'current-panel' ); 1527 _.delay( panel.renderScreenshots, 10 ); // Wait for the controls 1528 panel.$customizeSidebar.on( 'scroll.customize-themes-section', _.throttle( panel.renderScreenshots, 300 ) ); 1609 // Always hide, even if they don't exist or are already hidden. 1610 section.filterContainer.find( '.customize-themes-section-title' ).removeClass( 'selected details-open' ).attr( 'aria-expanded', 'false' ); 1611 section.filterContainer.find( '.filter-details' ).slideUp( 180 ); 1529 1612 1530 } else if ( ! expanded && section.hasClass( 'current-panel' ) ) { 1531 panel._animateChangeExpanded( function() { 1532 changeBtn.attr( 'tabindex', '0' ); 1533 customizeBtn.attr( 'tabindex', '-1' ); 1613 container.off( 'scroll' ); 1534 1614 1535 changeBtn.focus(); 1536 section.css( 'top', '' ); 1615 if ( args.completeCallback ) { 1616 args.completeCallback(); 1617 } 1618 } 1619 }, 1537 1620 1538 if ( args.completeCallback ) { 1539 args.completeCallback(); 1621 /** 1622 * Return the section's content element without detachng from the parent. 1623 * 1624 * @since 4.9.0 1625 */ 1626 getContent: function() { 1627 return this.container.find( '.control-section-content' ); 1628 }, 1629 1630 /** 1631 * Load theme data via ajax and add themes to the section as controls. 1632 * 1633 * @since 4.9.0 1634 */ 1635 loadControls: function() { 1636 var section = this, params, page, request; 1637 1638 if ( section.loading ) { 1639 return; // We're already loading a batch of themes. 1640 } 1641 1642 // Parameters for every API query. Additional params are set in PHP. 1643 page = Math.ceil( section.loaded / 100 ) + 1; 1644 params = { 1645 'switch-themes-nonce': api.settings.nonce['switch-themes'], 1646 'wp_customize': 'on', 1647 'theme_action': section.params.action, 1648 'customized_theme': api.settings.theme.stylesheet, 1649 'page': page 1650 }; 1651 1652 // Add fields for special request actions. 1653 if ( 'search' === section.params.action ) { 1654 if ( '' === section.term ) { 1655 return; 1656 } else { 1657 params.search = section.term; 1658 } 1659 } else if ( 'favorites' === section.params.action ) { 1660 if ( '' === section.term ) { 1661 return; 1662 } else { 1663 params.user = section.term; 1664 } 1665 } else if ( 'feature_filter' === section.params.action ) { 1666 if ( '' === section.term ) { 1667 return; 1668 } else { 1669 params.tags = section.term; 1670 } 1671 } 1672 1673 // Load themes. 1674 section.headContainer.closest( '.wp-full-overlay' ).addClass( 'loading' ); 1675 section.loading = true; 1676 section.container.find( '.no-themes' ).hide(); 1677 request = wp.ajax.post( 'customize-load-themes', params ); 1678 request.done(function( data ) { 1679 var themes = data.themes, 1680 themeControl, newThemeControls; 1681 1682 // Stop and try again if the term changed. 1683 if ( section.nextTerm ) { 1684 section.term = section.nextTerm; 1685 section.nextTerm = ''; 1686 section.loading = false; 1687 section.loadControls(); 1688 return; 1689 } 1690 1691 if ( 0 !== themes.length ) { 1692 newThemeControls = []; 1693 // Add controls for each theme. 1694 _.each( themes, function ( theme ) { 1695 var customizeId = section.params.action + '_theme_' + theme.id; 1696 themeControl = new api.controlConstructor.theme( customizeId, { 1697 params: { 1698 type: 'theme', 1699 content: '<li id="customize-control-theme-' + section.params.action + '_' + theme.id + '" class="customize-control customize-control-theme"></li>', 1700 section: section.params.id, 1701 active: true, 1702 theme: theme, 1703 priority: section.loaded + 1 1704 }, 1705 previewer: api.previewer 1706 } ); 1707 1708 api.control.add( customizeId, themeControl ); 1709 newThemeControls.push( themeControl ); 1710 section.loaded = section.loaded + 1; 1711 }); 1712 1713 if ( 1 === page ) { 1714 // Pre-load the first 3 theme screenshots. 1715 _.each( section.controls().slice( 0, 3 ), function ( control ) { 1716 var img, src = control.params.theme.screenshot[0]; 1717 if ( src ) { 1718 img = new Image(); 1719 img.src = src; 1720 } 1721 }); 1722 if ( 'search' === section.params.action ) { 1723 wp.a11y.speak( api.settings.l10n.themeSearchResults.replace( '%d', data.info.results ) ); 1724 } 1725 } else { 1726 Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue. 1540 1727 } 1541 } );1728 _.delay( section.renderScreenshots, 100 ); // Wait for the controls to become visible. 1542 1729 1543 overlay.removeClass( 'in-themes-panel' ); 1544 section.removeClass( 'current-panel' ); 1545 panel.$customizeSidebar.off( 'scroll.customize-themes-section' ); 1730 if ( 'installed' === section.action || 100 > themes.length ) { // If we have less than the requested 100 themes, it's the end of the list. 1731 section.fullyLoaded = true; 1732 } 1733 } else { 1734 if ( 0 === section.loaded ) { 1735 section.container.find( '.no-themes' ).show(); 1736 wp.a11y.speak( section.container.find( '.no-themes' ).text() ); 1737 } else { 1738 section.fullyLoaded = true; 1739 } 1740 } 1741 if ( 'installed' === section.params.action ) { 1742 section.updateCount(); 1743 } else { 1744 section.updateCount( data.info.results ); 1745 } 1746 section.container.find( '.unexpected-error' ).hide(); // Hide error notice in case it was previously shown. 1747 1748 // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case. 1749 section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' ); 1750 section.loading = false; 1751 }); 1752 request.fail(function( data ) { 1753 if ( 'undefined' === typeof data ) { 1754 section.container.find( '.unexpected-error' ).show(); 1755 wp.a11y.speak( section.container.find( '.unexpected-error' ).text() ); 1756 } else if ( typeof console !== 'undefined' && console.error ) { 1757 console.error( data ); 1758 } 1759 1760 // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case. 1761 section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' ); 1762 section.loading = false; 1763 }); 1764 }, 1765 1766 /** 1767 * Determines whether more themes should be loaded, and loads them. 1768 * 1769 * @since 4.9.0 1770 */ 1771 loadMore: function() { 1772 var section = this, container, bottom, threshold; 1773 if ( ! section.fullyLoaded && ! section.loading ) { 1774 container = section.container.closest( '.customize-themes-full-container' ); 1775 1776 bottom = container.scrollTop() + container.height(); 1777 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. 1778 1779 if ( bottom > threshold ) { 1780 section.loadControls(); 1781 } 1546 1782 } 1547 1783 }, 1548 1784 1549 1785 /** 1786 * Event handler for search, feature filter, and favorites input that determines if the term has changed and loads new controls as needed. 1787 * 1788 * @since 4.9.0 1789 * 1790 * @param {wp.customize.ThemesSection} section The current theme section, passed through the debouncer. 1791 */ 1792 checkTerm: function( section ) { 1793 var newTerm; 1794 1795 // Find term. 1796 if ( 'search' === section.params.action ) { 1797 newTerm = $( '#wp-filter-search-input' ).val(); 1798 } else if ( 'favorites' === section.params.action ) { 1799 newTerm = $( '#wporg-username-input' ).val(); 1800 } else if ( 'feature_filter' === section.params.action ) { 1801 newTerm = section.term; // Set separately by filtersChecked(), as they're changed. 1802 if ( '' === newTerm ) { 1803 return; 1804 } 1805 } else { 1806 return; 1807 } 1808 1809 if ( section.term === newTerm && 'feature_filter' !== section.params.action ) { 1810 return; 1811 } 1812 1813 // Clear the controls in the section. 1814 _.each( section.controls(), function( control ) { 1815 control.container.remove(); 1816 api.control.remove( control.id ); 1817 }); 1818 section.loaded = 0; 1819 section.fullyLoaded = false; 1820 section.screenshotQueue = null; 1821 1822 if ( '' !== newTerm ) { // Empty term should not show any results. 1823 // Run a new query, with loadControls handling paging, etc. 1824 section.term = newTerm; 1825 if ( ! section.loading ) { 1826 section.loadControls(); 1827 } else { 1828 section.nextTerm = newTerm; // This will reload with the newest term once the current batch is loaded. 1829 } 1830 if ( ! section.expanded() ) { 1831 section.expand(); // Expand the section if it isn't expanded. 1832 } 1833 } 1834 }, 1835 1836 /** 1837 * Check for filters checked in the feature filter list. 1838 * 1839 * @since 4.9.0 1840 */ 1841 filtersChecked: function() { 1842 var section = this, 1843 items = section.container.find( '.filter-group' ).find( ':checkbox' ), 1844 tags = []; 1845 1846 if ( 'feature_filter' !== section.params.action ) { 1847 return false; 1848 } 1849 1850 _.each( items.filter( ':checked' ), function( item ) { 1851 tags.push( $( item ).prop( 'value' ) ); 1852 }); 1853 1854 // When no filters are checked, restore initial state and return 1855 if ( tags.length === 0 ) { 1856 section.term = ''; 1857 } else { 1858 section.term = tags; 1859 } 1860 }, 1861 1862 /** 1550 1863 * Render control's screenshot if the control comes into view. 1551 1864 * 1552 1865 * @since 4.2.0 … … 1554 1867 renderScreenshots: function( ) { 1555 1868 var section = this; 1556 1869 1557 // Fill queue initially. 1558 if ( section.screenshotQueue === null ) { 1559 section.screenshotQueue = section.controls(); 1870 // Fill queue initially, or check for more if empty. 1871 if ( section.screenshotQueue === null || 0 === section.screenshotQueue.length ) { 1872 // Add controls that haven't had their screenshots rendered. 1873 section.screenshotQueue = _.filter( section.controls(), function( control ) { 1874 return ! control.screenshotRendered; 1875 }); 1560 1876 } 1561 1877 1562 // Are all screenshots rendered ?1878 // Are all screenshots rendered (for now)? 1563 1879 if ( ! section.screenshotQueue.length ) { 1564 1880 return; 1565 1881 } … … 1595 1911 }, 1596 1912 1597 1913 /** 1914 * Update the number of themes in the section. 1915 * 1916 * @since 4.9.0 1917 */ 1918 updateCount: function ( count ) { 1919 if ( ! count ) { 1920 count = this.loaded; 1921 } 1922 1923 var displayed = this.container.closest( '.control-panel-content' ).find( '.themes-displayed' ), 1924 countEl = this.container.closest( '.control-panel-content' ).find( '.theme-count' ); 1925 1926 if ( 0 === count ) { 1927 countEl.text( count ); 1928 } else { 1929 // Animate the count change for emphasis. 1930 displayed.fadeOut( 180, function() { 1931 countEl.text( count ); 1932 displayed.fadeIn( 180 ); 1933 } ); 1934 wp.a11y.speak( api.settings.l10n.announceThemeCount.replace( '%d', count ) ); 1935 } 1936 }, 1937 1938 /** 1598 1939 * Advance the modal to the next theme. 1599 1940 * 1600 1941 * @since 4.2.0 … … 1614 1955 * @since 4.2.0 1615 1956 */ 1616 1957 getNextTheme: function () { 1617 var control, next;1618 control = api.control( 'theme_' + this.currentTheme );1958 var section = this, control, next; 1959 control = api.control( section.params.action + '_theme_' + this.currentTheme ); 1619 1960 next = control.container.next( 'li.customize-control-theme' ); 1620 1961 if ( ! next.length ) { 1621 1962 return false; 1622 1963 } 1623 next = next[0].id.replace( 'customize-control- ', '' );1964 next = next[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' ); 1624 1965 control = api.control( next ); 1625 1966 1626 1967 return control.params.theme; … … 1646 1987 * @since 4.2.0 1647 1988 */ 1648 1989 getPreviousTheme: function () { 1649 var control, previous;1650 control = api.control( 'theme_' + this.currentTheme );1990 var section = this, control, previous; 1991 control = api.control( section.params.action + '_theme_' + this.currentTheme ); 1651 1992 previous = control.container.prev( 'li.customize-control-theme' ); 1652 1993 if ( ! previous.length ) { 1653 1994 return false; 1654 1995 } 1655 previous = previous[0].id.replace( 'customize-control- ', '' );1996 previous = previous[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' ); 1656 1997 control = api.control( previous ); 1657 1998 1658 1999 return control.params.theme; … … 1734 2075 * @param {Object} theme 1735 2076 */ 1736 2077 showDetails: function ( theme, callback ) { 1737 var section = this , link;2078 var section = this; 1738 2079 callback = callback || function(){}; 1739 2080 section.currentTheme = theme.id; 1740 2081 section.overlay.html( section.template( theme ) ) … … 1743 2084 $( 'body' ).addClass( 'modal-open' ); 1744 2085 section.containFocus( section.overlay ); 1745 2086 section.updateLimits(); 1746 1747 link = section.overlay.find( '.inactive-theme > a' ); 1748 1749 link.on( 'click', function( event ) { 1750 event.preventDefault(); 1751 1752 // Short-circuit if request is currently being made. 1753 if ( link.hasClass( 'disabled' ) ) { 1754 return; 1755 } 1756 link.addClass( 'disabled' ); 1757 1758 section.loadThemePreview( theme.id ).fail( function() { 1759 link.removeClass( 'disabled' ); 1760 } ); 1761 } ); 2087 wp.a11y.speak( api.settings.l10n.announceThemeDetails.replace( '%s', theme.name ) ); 1762 2088 callback(); 1763 2089 }, 1764 2090 … … 1770 2096 closeDetails: function () { 1771 2097 $( 'body' ).removeClass( 'modal-open' ); 1772 2098 this.overlay.fadeOut( 'fast' ); 1773 api.control( 'theme_' + this.currentTheme).focus();2099 api.control( this.params.action + '_theme_' + this.currentTheme ).container.find( '.theme' ).focus(); 1774 2100 }, 1775 2101 1776 2102 /** … … 1850 2176 } 1851 2177 if ( ! panel.contentContainer.parent().is( panel.headContainer ) ) { 1852 2178 container.append( panel.contentContainer ); 1853 panel.renderContent();1854 2179 } 2180 panel.renderContent(); 1855 2181 1856 2182 panel.deferred.embedded.resolve(); 1857 2183 }, … … 2061 2387 } 2062 2388 }); 2063 2389 2390 2064 2391 /** 2392 * wp.customize.ThemesPanel 2393 * 2394 * Custom section for themes that displays without the customize preview. 2395 * 2396 * @constructor 2397 * @augments wp.customize.Panel 2398 * @augments wp.customize.Container 2399 */ 2400 api.ThemesPanel = api.Panel.extend({ 2401 installingThemes: [], 2402 2403 /** 2404 * @since 4.9.0 2405 */ 2406 attachEvents: function () { 2407 var panel = this; 2408 2409 // Attach regular panel events. 2410 api.Panel.prototype.attachEvents.apply( this ); 2411 2412 // Collapse panel to customize the current theme. 2413 panel.contentContainer.on( 'click', '.customize-theme', function() { 2414 panel.collapse(); 2415 }); 2416 2417 // Toggle between filtering and browsing themes on mobile. 2418 panel.contentContainer.on( 'click', '.see-themes, .filter-themes', function() { 2419 $( '.wp-full-overlay' ).toggleClass( 'showing-themes' ); 2420 }); 2421 2422 // Install (and maybe preview) a theme. 2423 panel.contentContainer.on( 'click', '.theme-install', function( event ) { 2424 panel.installTheme( event ); 2425 }); 2426 2427 // Update a theme. Theme cards have the class, the details modal has the id. 2428 panel.contentContainer.on( 'click', '.update-theme, #update-theme', function( event ) { 2429 // #update-theme is a link. 2430 event.preventDefault(); 2431 event.stopPropagation(); 2432 2433 panel.updateTheme( event ); 2434 }); 2435 2436 // Delete a theme. 2437 panel.contentContainer.on( 'click', '.delete-theme', function( event ) { 2438 panel.deleteTheme( event ); 2439 }); 2440 2441 _.bindAll( this, 'installTheme', 'updateTheme' ); 2442 }, 2443 2444 /** 2445 * Update UI to reflect expanded state 2446 * 2447 * @since 4.9.0 2448 * 2449 * @param {Boolean} expanded 2450 * @param {Object} args 2451 * @param {Boolean} args.unchanged 2452 * @param {Function} args.completeCallback 2453 */ 2454 onChangeExpanded: function ( expanded, args ) { 2455 2456 // Expand/collapse the panel normally. 2457 api.Panel.prototype.onChangeExpanded.apply( this, [ expanded, args ] ); 2458 2459 // Immediately call the complete callback if there were no changes 2460 if ( args.unchanged ) { 2461 if ( args.completeCallback ) { 2462 args.completeCallback(); 2463 } 2464 return; 2465 } 2466 2467 // Note: there is a second argument 'args' passed 2468 var panel = this, 2469 overlay = panel.headContainer.closest( '.wp-full-overlay' ); 2470 2471 if ( expanded ) { 2472 overlay 2473 .addClass( 'in-themes-panel' ).addClass( 'showing-themes' ) 2474 .delay( 200 ).find( '.customize-themes-full-container' ).addClass( 'animate' ); 2475 2476 // Automatically open the installed themes section. 2477 api.section( 'installed_themes' ).expand(); 2478 } else { 2479 overlay 2480 .removeClass( 'in-themes-panel' ) 2481 .find( '.customize-themes-full-container' ).removeClass( 'animate' ); 2482 } 2483 }, 2484 2485 /** 2486 * Install a theme via wp.updates. 2487 * 2488 * @since 4.9.0 2489 */ 2490 installTheme: function( event ) { 2491 var panel = this, preview = false, slug = $( event.target ).data( 'slug' ); 2492 2493 if ( -1 !== $.inArray( this.installingThemes, slug ) ) { 2494 return; // Theme is already being installed. 2495 } 2496 2497 wp.updates.maybeRequestFilesystemCredentials( event ); 2498 2499 $( document ).one( 'wp-theme-install-success', function( event, response ) { 2500 var theme = false, customizeId, themeControl; 2501 if ( preview ) { 2502 2503 // Update loading message. Everything else is handled by reloading the page. 2504 // $( '#customize-themes-loading-container span' ).hide(); 2505 // $( '#customize-themes-loading-container .customize-loading-text' ).css( 'display', 'block' ); 2506 2507 panel.loadThemePreview( slug ).fail( function() { 2508 $( '.wp-full-overlay' ).removeClass( 'customize-loading' ); 2509 } ); 2510 2511 } else { 2512 api.control.each( function( control ) { 2513 if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) { 2514 theme = control.params.theme; // Used below to add theme control. 2515 control.rerenderAsInstalled( true ); 2516 } 2517 }); 2518 2519 // Don't add the same theme more than once. 2520 if ( ! theme || 'undefined' !== typeof api.control( 'installed_theme_' + theme.id ) ) { 2521 return; 2522 } 2523 2524 // Add theme control to installed section. 2525 theme.type = 'installed'; 2526 customizeId = 'installed_theme_' + theme.id; 2527 themeControl = new api.controlConstructor.theme( customizeId, { 2528 params: { 2529 type: 'theme', 2530 content: $( '<li class="customize-control customize-control-theme"></li>' ).attr( 'id', 'customize-control-theme-installed_' + theme.id ).prop( 'outerHTML' ), 2531 section: 'installed_themes', 2532 active: true, 2533 theme: theme, 2534 priority: 0 // Add all newly-installed themes to the top. 2535 }, 2536 previewer: api.previewer 2537 } ); 2538 2539 api.control.add( customizeId, themeControl ); 2540 api.control( customizeId ).container.trigger( 'render-screenshot' ); 2541 2542 // Close the details modal if it's open to the installed theme. 2543 api.section.each( function( section ) { 2544 if ( 'themes' === section.params.type ) { 2545 if ( theme.id === section.currentTheme ) { // Don't close the modal if the user has navigated elsewhere. 2546 section.closeDetails(); 2547 } 2548 } 2549 }); 2550 } 2551 } ); 2552 2553 this.installingThemes.push( $( event.target ).data( 'slug' ) ); // Note: we don't remove elements from installingThemes, since they shouldn't be installed again. 2554 wp.updates.installTheme( { 2555 slug: slug 2556 } ); 2557 2558 // Also preview the theme as the event is triggered on Install & Preview. 2559 if ( $( event.target ).hasClass( 'preview' ) ) { 2560 preview = true; 2561 $( '.wp-full-overlay' ).addClass( 'customize-loading' ); 2562 } 2563 }, 2564 2565 /** 2566 * Load theme preview. 2567 * 2568 * @since 4.9.0 2569 * 2570 * @param {string} themeId Theme ID. 2571 * @returns {jQuery.promise} Promise. 2572 */ 2573 loadThemePreview: function( themeId ) { 2574 var deferred = $.Deferred(), onceProcessingComplete, overlay, urlParser; 2575 2576 urlParser = document.createElement( 'a' ); 2577 urlParser.href = location.href; 2578 urlParser.search = $.param( _.extend( 2579 api.utils.parseQueryString( urlParser.search.substr( 1 ) ), 2580 { 2581 theme: themeId, 2582 changeset_uuid: api.settings.changeset.uuid 2583 } 2584 ) ); 2585 2586 // Update loading message. Everything else is handled by reloading the page. 2587 $( '#customize-themes-loading-container span' ).hide(); 2588 $( '#customize-themes-loading-container .customize-loading-text' ).css( 'display', 'block' ); 2589 overlay = $( '.wp-full-overlay' ); 2590 overlay.addClass( 'customize-loading' ); 2591 2592 onceProcessingComplete = function() { 2593 var request; 2594 if ( api.state( 'processing' ).get() > 0 ) { 2595 return; 2596 } 2597 2598 api.state( 'processing' ).unbind( onceProcessingComplete ); 2599 2600 request = api.requestChangesetUpdate(); 2601 request.done( function() { 2602 $( window ).off( 'beforeunload.customize-confirm' ); 2603 window.location.href = urlParser.href; 2604 } ); 2605 request.fail( function() { 2606 overlay.removeClass( 'customize-loading' ); 2607 } ); 2608 }; 2609 2610 if ( 0 === api.state( 'processing' ).get() ) { 2611 onceProcessingComplete(); 2612 } else { 2613 api.state( 'processing' ).bind( onceProcessingComplete ); 2614 } 2615 2616 return deferred.promise(); 2617 }, 2618 2619 /** 2620 * Update a theme via wp.updates. 2621 * 2622 * @since 4.9.0 2623 */ 2624 updateTheme: function( event ) { 2625 wp.updates.maybeRequestFilesystemCredentials( event ); 2626 2627 $( document ).one( 'wp-theme-update-success', function( event, response ) { 2628 // Rerender the control to reflect the update. 2629 api.control.each( function( control ) { 2630 if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) { 2631 control.params.theme.hasUpdate = false; 2632 control.rerenderAsInstalled( true ); 2633 } 2634 }); 2635 } ); 2636 2637 wp.updates.updateTheme( { 2638 slug: $( event.target ).closest( '.notice' ).data( 'slug' ) 2639 } ); 2640 }, 2641 2642 /** 2643 * Delete a theme via wp.updates. 2644 * 2645 * @since 4.9.0 2646 */ 2647 deleteTheme: function( event ) { 2648 var theme, section; 2649 theme = $( event.target ).data( 'slug' ); 2650 section = api.section( 'installed_themes' ); 2651 2652 event.preventDefault(); 2653 2654 // Confirmation dialog for deleting a theme. 2655 if ( ! window.confirm( api.settings.l10n.confirmDeleteTheme ) ) { 2656 return; 2657 } 2658 2659 wp.updates.maybeRequestFilesystemCredentials( event ); 2660 2661 $( document ).one( 'wp-theme-delete-success', function() { 2662 var control = api.control( 'installed_theme_' + theme ); 2663 2664 // Remove theme control. 2665 control.container.remove(); 2666 api.control.remove( control.id ); 2667 2668 // Update installed count. 2669 section.loaded = section.loaded - 1; 2670 section.updateCount(); 2671 2672 // Rerender any other theme controls as uninstalled. 2673 api.control.each( function( control ) { 2674 if ( 'theme' === control.params.type && control.params.theme.id === theme ) { 2675 control.rerenderAsInstalled( false ); 2676 } 2677 }); 2678 } ); 2679 2680 wp.updates.deleteTheme( { 2681 slug: theme 2682 } ); 2683 2684 // Close modal and focus the section. 2685 section.closeDetails(); 2686 section.focus(); 2687 } 2688 2689 }); 2690 2691 2692 /** 2065 2693 * A Customizer Control. 2066 2694 * 2067 2695 * A control provides a UI element that allows a user to modify a Customizer Setting. … … 2410 3038 * @param {Boolean} active 2411 3039 * @param {Object} args 2412 3040 * @param {Number} args.duration 2413 * @param { Callback} args.completeCallback3041 * @param {Function} args.completeCallback 2414 3042 */ 2415 3043 onChangeActive: function ( active, args ) { 2416 3044 if ( args.unchanged ) { … … 3582 4210 api.ThemeControl = api.Control.extend({ 3583 4211 3584 4212 touchDrag: false, 3585 isRendered: false,4213 screenshotRendered: false, 3586 4214 3587 4215 /** 3588 * Defer rendering the theme control until the section is displayed.3589 *3590 4216 * @since 4.2.0 3591 4217 */ 3592 renderContent: function () {3593 var control = this,3594 renderContentArgs = arguments;3595 3596 api.section( control.section(), function( section ) {3597 if ( section.expanded() ) {3598 api.Control.prototype.renderContent.apply( control, renderContentArgs );3599 control.isRendered = true;3600 } else {3601 section.expanded.bind( function( expanded ) {3602 if ( expanded && ! control.isRendered ) {3603 api.Control.prototype.renderContent.apply( control, renderContentArgs );3604 control.isRendered = true;3605 }3606 } );3607 }3608 } );3609 },3610 3611 /**3612 * @since 4.2.03613 */3614 4218 ready: function() { 3615 4219 var control = this; 3616 4220 … … 3630 4234 } 3631 4235 3632 4236 // Prevent the modal from showing when the user clicks the action button. 3633 if ( $( event.target ).is( '.theme-actions .button ' ) ) {4237 if ( $( event.target ).is( '.theme-actions .button, .update-theme' ) ) { 3634 4238 return; 3635 4239 } 3636 4240 3637 api.section( control.section() ).loadThemePreview( control.params.theme.id );3638 });3639 3640 control.container.on( 'click keydown', '.theme-actions .theme-details', function( event ) {3641 if ( api.utils.isKeydownButNotEnterEvent( event ) ) {3642 return;3643 }3644 3645 4241 event.preventDefault(); // Keep this AFTER the key filter above 3646 3647 4242 api.section( control.section() ).showDetails( control.params.theme ); 3648 4243 }); 3649 4244 … … 3654 4249 if ( source ) { 3655 4250 $screenshot.attr( 'src', source ); 3656 4251 } 4252 control.screenshotRendered = true; 3657 4253 }); 3658 4254 }, 3659 4255 3660 4256 /** 3661 * Show or hide the theme based on the presence of the term in the title, description, and author.4257 * Show or hide the theme based on the presence of the term in the title, description, tags, and author. 3662 4258 * 3663 4259 * @since 4.2.0 3664 4260 */ … … 3674 4270 } else { 3675 4271 control.deactivate(); 3676 4272 } 4273 }, 4274 4275 /** 4276 * Rerender the theme from its JS template with the installed type. 4277 * 4278 * @since 4.9.0 4279 */ 4280 rerenderAsInstalled: function( installed ) { 4281 var control = this, section; 4282 if ( installed ) { 4283 control.params.theme.type = 'installed'; 4284 } else { 4285 section = api.section( control.params.section ); 4286 control.params.theme.type = section.params.action; 4287 } 4288 control.renderContent(); // replaces existing content 4289 control.container.trigger( 'render-screenshot' ); 3677 4290 } 3678 4291 }); 3679 4292 … … 4374 4987 background_position: api.BackgroundPositionControl, 4375 4988 theme: api.ThemeControl 4376 4989 }; 4377 api.panelConstructor = {}; 4990 api.panelConstructor = { 4991 themes: api.ThemesPanel 4992 }; 4378 4993 api.sectionConstructor = { 4379 4994 themes: api.ThemesSection 4380 4995 }; … … 4492 5107 4493 5108 // Sort the sections within each panel 4494 5109 api.panel.each( function ( panel ) { 5110 if ( 'themes' === panel.id ) { 5111 return; // Don't reflow theme sections, as doing so moves them after the themes container. 5112 } 5113 4495 5114 var sections = panel.sections(), 4496 5115 sectionHeadContainers = _.pluck( sections, 'headContainer' ); 4497 5116 rootNodes.push( panel ); … … 5214 5833 // Collapse the most granular expanded object. 5215 5834 collapsedObject = expandedControls[0] || expandedSections[0] || expandedPanels[0]; 5216 5835 if ( collapsedObject ) { 5836 if ( 'themes' === collapsedObject.params.type ) { 5837 // Themes panel or section. 5838 if ( $( 'body' ).hasClass( 'modal-open' ) ) { 5839 collapsedObject.closeDetails(); 5840 } else { 5841 // If we're collapsing a section, collapse the panel also. 5842 wp.customize.panel( 'themes' ).collapse(); 5843 } 5844 return; 5845 } 5217 5846 collapsedObject.collapse(); 5218 5847 event.preventDefault(); 5219 5848 } -
src/wp-admin/js/updates.js
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
302 302 303 303 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' ); 304 304 305 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-panel.php' ); 305 306 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php' ); 306 307 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php' ); 307 308 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-section.php' ); … … 357 358 358 359 add_action( 'wp_ajax_customize_save', array( $this, 'save' ) ); 359 360 add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) ); 361 add_action( 'wp_ajax_customize-load-themes', array( $this, 'load_themes_ajax' ) ); 360 362 361 363 add_action( 'customize_register', array( $this, 'register_controls' ) ); 362 364 add_action( 'customize_register', array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first … … 373 375 374 376 // Export the settings to JS via the _wpCustomizeSettings variable. 375 377 add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 ); 378 379 // Add theme update notices. 380 if ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) ) { 381 require_once( ABSPATH . '/wp-admin/includes/update.php' ); 382 add_action( 'customize_controls_print_footer_scripts', 'wp_print_admin_notice_templates' ); 383 } 376 384 } 377 385 378 386 /** … … 3343 3351 'type' => 'text/css', 3344 3352 ) ); 3345 3353 } 3354 3355 if ( ! is_multisite() && ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) || current_user_can( 'delete_themes' ) ) ) { 3356 wp_enqueue_script( 'updates' ); 3357 } 3346 3358 } 3347 3359 3348 3360 /** … … 3547 3559 $nonces = array( 3548 3560 'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ), 3549 3561 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ), 3562 'switch-themes' => wp_create_nonce( 'switch-themes' ), 3550 3563 ); 3551 3564 3552 3565 /** … … 3623 3636 'autofocus' => $this->get_autofocus(), 3624 3637 'documentTitleTmpl' => $this->get_document_title_template(), 3625 3638 'previewableDevices' => $this->get_previewable_devices(), 3639 'l10n' => array( 3640 'confirmDeleteTheme' => __( 'Are you sure you want to delete this theme?' ), 3641 /* translators: %d is the number of theme search results, which cannot currently consider singular vs. plural forms */ 3642 'themeSearchResults' => __( '%d themes found' ), 3643 /* translators: %d is the number of themes being displayed, which cannot currently consider singular vs. plural forms */ 3644 'announceThemeCount' => __( 'Displaying %d themes' ), 3645 'announceThemeDetails' => __( 'Showing details for theme: %s' ), 3646 ), 3626 3647 ); 3627 3648 3628 3649 // Prepare Customize Section objects to pass to JavaScript. … … 3725 3746 3726 3747 /* Panel, Section, and Control Types */ 3727 3748 $this->register_panel_type( 'WP_Customize_Panel' ); 3749 $this->register_panel_type( 'WP_Customize_Themes_Panel' ); 3728 3750 $this->register_section_type( 'WP_Customize_Section' ); 3729 3751 $this->register_section_type( 'WP_Customize_Sidebar_Section' ); 3752 $this->register_section_type( 'WP_Customize_Themes_Section' ); 3730 3753 $this->register_control_type( 'WP_Customize_Color_Control' ); 3731 3754 $this->register_control_type( 'WP_Customize_Media_Control' ); 3732 3755 $this->register_control_type( 'WP_Customize_Upload_Control' ); … … 3737 3760 $this->register_control_type( 'WP_Customize_Site_Icon_Control' ); 3738 3761 $this->register_control_type( 'WP_Customize_Theme_Control' ); 3739 3762 3740 /* Themes */3763 /* Themes (controls are loaded via ajax) */ 3741 3764 3742 $this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array( 3743 'title' => $this->theme()->display( 'Name' ), 3744 'capability' => 'switch_themes', 3745 'priority' => 0, 3765 $this->add_panel( new WP_Customize_Themes_Panel( $this, 'themes', array( 3766 'title' => $this->theme()->display( 'Name' ), 3767 '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.' ), 3768 'capability' => 'switch_themes', 3769 'priority' => 0, 3746 3770 ) ) ); 3747 3771 3748 // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience). 3749 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array( 3750 'capability' => 'switch_themes', 3772 $this->add_section( new WP_Customize_Themes_Section( $this, 'installed_themes', array( 3773 'title' => __( 'Installed' ), 3774 'text_before' => __( 'Your local site' ), 3775 'action' => 'installed', 3776 'capability' => 'switch_themes', 3777 'panel' => 'themes', 3778 'priority' => 0, 3751 3779 ) ) ); 3752 3780 3753 require_once( ABSPATH . 'wp-admin/includes/theme.php' ); 3781 if ( ! is_multisite() ) { 3782 $this->add_section( new WP_Customize_Themes_Section( $this, 'search_themes', array( 3783 'title' => __( 'Search themes…' ), 3784 'text_before' => __( 'Browse all WordPress.org themes' ), 3785 'action' => 'search', 3786 'capability' => 'install_themes', 3787 'panel' => 'themes', 3788 'priority' => 5, 3789 ) ) ); 3754 3790 3755 // Theme Controls. 3791 $this->add_section( new WP_Customize_Themes_Section( $this, 'featured_themes', array( 3792 'title' => __( 'Featured' ), 3793 'action' => 'featured', 3794 'capability' => 'install_themes', 3795 'panel' => 'themes', 3796 'priority' => 10, 3797 ) ) ); 3756 3798 3757 // Add a control for the active/original theme. 3758 if ( ! $this->is_theme_active() ) { 3759 $themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) ); 3760 $active_theme = current( $themes ); 3761 $active_theme['isActiveTheme'] = true; 3762 $this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array( 3763 'theme' => $active_theme, 3764 'section' => 'themes', 3765 'settings' => 'active_theme', 3799 $this->add_section( new WP_Customize_Themes_Section( $this, 'popular_themes', array( 3800 'title' => __( 'Popular' ), 3801 'action' => 'popular', 3802 'capability' => 'install_themes', 3803 'panel' => 'themes', 3804 'priority' => 15, 3766 3805 ) ) ); 3767 }3768 3806 3769 $themes = wp_prepare_themes_for_js(); 3770 foreach ( $themes as $theme ) { 3771 if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) { 3772 continue; 3773 } 3807 $this->add_section( new WP_Customize_Themes_Section( $this, 'latest_themes', array( 3808 'title' => __( 'Latest' ), 3809 'action' => 'latest', 3810 'capability' => 'install_themes', 3811 'panel' => 'themes', 3812 'priority' => 20, 3813 ) ) ); 3774 3814 3775 $th eme_id = 'theme_' . $theme['id'];3776 $theme['isActiveTheme'] = false;3777 $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(3778 ' theme' => $theme,3779 ' section'=> 'themes',3780 ' settings' => 'active_theme',3815 $this->add_section( new WP_Customize_Themes_Section( $this, 'feature_filter_themes', array( 3816 'title' => __( 'Feature Filter' ), 3817 'action' => 'feature_filter', 3818 'capability' => 'install_themes', 3819 'panel' => 'themes', 3820 'priority' => 25, 3781 3821 ) ) ); 3822 3823 $this->add_section( new WP_Customize_Themes_Section( $this, 'favorites_themes', array( 3824 'title' => __( 'Favorites' ), 3825 'action' => 'favorites', 3826 'capability' => 'install_themes', 3827 'panel' => 'themes', 3828 'priority' => 30, 3829 ) ) ); 3782 3830 } 3783 3831 3832 // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience). 3833 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array( 3834 'capability' => 'switch_themes', 3835 ) ) ); 3836 3784 3837 /* Site Identity */ 3785 3838 3786 3839 $this->add_section( 'title_tagline', array( … … 4292 4345 } 4293 4346 4294 4347 /** 4348 * Load themes into the theme browsing/installation UI. 4349 * 4350 * @since 4.9.0 4351 */ 4352 public function load_themes_ajax() { 4353 check_ajax_referer( 'switch-themes', 'switch-themes-nonce' ); 4354 4355 if ( ! current_user_can( 'switch_themes' ) ) { 4356 wp_die( -1 ); 4357 } 4358 4359 if ( empty( $_POST['theme_action'] ) ) { 4360 wp_send_json_error( 'missing_theme_action' ); 4361 } 4362 4363 if ( 'search' === $_POST['theme_action'] && ! array_key_exists( 'search', $_POST ) ) { 4364 wp_send_json_error( 'empty_search' ); 4365 } elseif ( 'favorites' === $_POST['theme_action'] && ! array_key_exists( 'user', $_POST ) ) { 4366 wp_send_json_error( 'empty_user' ); 4367 } elseif ( 'feature_filter' === $_POST['theme_action'] && ! array_key_exists( 'tags', $_POST ) ) { 4368 wp_send_json_error( 'no_features' ); 4369 } 4370 4371 require_once( ABSPATH . 'wp-admin/includes/theme.php' ); 4372 if ( 'installed' === $_POST['theme_action'] ) { 4373 $themes = array( 'themes' => wp_prepare_themes_for_js() ); 4374 foreach ( $themes['themes'] as &$theme ) { 4375 $theme['type'] = 'installed'; 4376 // Set active based on customized theme. 4377 if ( $_POST['customized_theme'] === $theme['id'] ) { 4378 $theme['active'] = true; 4379 } else { 4380 $theme['active'] = false; 4381 } 4382 } 4383 } else { 4384 if ( ! current_user_can( 'install_themes' ) ) { 4385 wp_die( -1 ); 4386 } 4387 4388 // Arguments for all queries. 4389 $args = array( 4390 'per_page' => 100, 4391 'page' => absint( $_POST['page'] ), 4392 'fields' => array( 4393 'screenshot_url' => true, 4394 'description' => true, 4395 'rating' => true, 4396 'downloaded' => true, 4397 'downloadlink' => true, 4398 'last_updated' => true, 4399 'homepage' => true, 4400 'num_ratings' => true, 4401 'tags' => true, 4402 'parent' => true, 4403 //'extended_author' => true, @todo: WordPress.org throws a 500 server error when this is here. 4404 ), 4405 ); 4406 4407 // Specialized handling for each query. 4408 switch ( $_POST['theme_action'] ) { 4409 case 'search': 4410 $args['search'] = wp_unslash( $_POST['search'] ); 4411 break; 4412 case 'favorites': 4413 $args['user'] = wp_unslash( $_POST['user'] ); 4414 case 'featured': 4415 case 'popular': 4416 $args['browse'] = wp_unslash( $_POST['theme_action'] ); 4417 break; 4418 case 'latest': 4419 $args['browse'] = 'new'; 4420 break; 4421 case 'feature_filter': 4422 $args['tag'] = wp_unslash( $_POST['tags'] ); 4423 break; 4424 } 4425 4426 // Load themes from the .org API. 4427 $themes = themes_api( 'query_themes', $args ); 4428 if ( is_wp_error( $themes ) ) { 4429 wp_send_json_error(); 4430 } 4431 4432 // This list matches the allowed tags in wp-admin/includes/theme-install.php. 4433 $themes_allowedtags = array('a' => array('href' => array(), 'title' => array(), 'target' => array()), 4434 'abbr' => array('title' => array()), 'acronym' => array('title' => array()), 4435 'code' => array(), 'pre' => array(), 'em' => array(), 'strong' => array(), 4436 'div' => array(), 'p' => array(), 'ul' => array(), 'ol' => array(), 'li' => array(), 4437 'h1' => array(), 'h2' => array(), 'h3' => array(), 'h4' => array(), 'h5' => array(), 'h6' => array(), 4438 'img' => array('src' => array(), 'class' => array(), 'alt' => array()) 4439 ); 4440 4441 // Prepare a list of installed themes to check against before the loop. 4442 $installed_themes = array(); 4443 $wp_themes = wp_get_themes(); 4444 foreach ( $wp_themes as $theme ) { 4445 $installed_themes[] = $theme->get_stylesheet(); 4446 } 4447 $update_php = network_admin_url( 'update.php?action=install-theme' ); 4448 4449 // Set up properties for themes available on WordPress.org. 4450 foreach ( $themes->themes as &$theme ) { 4451 $theme->install_url = add_query_arg( array( 4452 'theme' => $theme->slug, 4453 '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ), 4454 ), $update_php ); 4455 4456 $theme->name = wp_kses( $theme->name, $themes_allowedtags ); 4457 $theme->author = wp_kses( $theme->author, $themes_allowedtags ); 4458 $theme->version = wp_kses( $theme->version, $themes_allowedtags ); 4459 $theme->description = wp_kses( $theme->description, $themes_allowedtags ); 4460 $theme->tags = implode( ', ', $theme->tags ); 4461 $theme->stars = wp_star_rating( array( 'rating' => $theme->rating, 'type' => 'percent', 'number' => $theme->num_ratings, 'echo' => false ) ); 4462 $theme->num_ratings = number_format_i18n( $theme->num_ratings ); 4463 $theme->preview_url = set_url_scheme( $theme->preview_url ); 4464 4465 // Handle themes that are already installed as installed themes. 4466 if ( in_array( $theme->slug, $installed_themes, true ) ) { 4467 $theme->type = 'installed'; 4468 } else { 4469 $theme->type = $_POST['theme_action']; 4470 } 4471 4472 // Set active based on customized theme. 4473 if ( $_POST['customized_theme'] === $theme->slug ) { 4474 $theme->active = true; 4475 } else { 4476 $theme->active = false; 4477 } 4478 4479 // Map available theme properties to installed theme properties. 4480 $theme->id = $theme->slug; 4481 $theme->screenshot = array( $theme->screenshot_url ); 4482 $theme->authorAndUri = $theme->author; 4483 $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. 4484 unset( $theme->slug ); 4485 unset( $theme->screenshot_url ); 4486 unset( $theme->author ); 4487 } // End foreach(). 4488 } // End if(). 4489 wp_send_json_success( $themes ); 4490 } 4491 4492 4493 /** 4295 4494 * Callback for validating the header_textcolor value. 4296 4495 * 4297 4496 * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash(). -
src/wp-includes/customize/class-wp-customize-theme-control.php
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> … … 76 80 <div class="theme-screenshot blank"></div> 77 81 <# } #> 78 82 79 <# if ( data.theme.isActiveTheme ) { #> 80 <span class="more-details" id="{{ data.theme.id }}-action"><?php _e( 'Customize' ); ?></span> 81 <# } else { #> 82 <span class="more-details" id="{{ data.theme.id }}-action"><?php _e( 'Live Preview' ); ?></span> 83 <# } #> 83 <span class="more-details theme-details" id="{{ data.section }}-{{ data.theme.id }}-action" aria-label="<?php echo esc_attr( $details_label ); ?>"><?php _e( 'Theme Details' ); ?></span> 84 84 85 85 <div class="theme-author"><?php 86 86 /* translators: Theme author name */ … … 87 87 printf( _x( 'By %s', 'theme author' ), '{{ data.theme.author }}' ); 88 88 ?></div> 89 89 90 <# if ( data.theme.isActiveTheme ) { #> 91 <h3 class="theme-name" id="{{ data.theme.id }}-name"> 90 <# if ( 'installed' === data.theme.type && data.theme.hasUpdate ) { #> 91 <div class="update-message notice inline notice-warning notice-alt" data-slug="{{ data.theme.id }}"><p><?php printf( __( 'New version available. %s' ), '<button class="button-link update-theme" type="button">' . __( 'Update now' ) . '</button>' ); ?></p></div> 92 <# } #> 93 94 <# if ( data.theme.active ) { #> 95 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name"> 92 96 <?php 93 97 /* translators: %s: theme name */ 94 printf( __( '<span> Active:</span> %s' ), '{{{ data.theme.name }}}' );98 printf( __( '<span>Current:</span> %s' ), '{{ data.theme.name }}' ); 95 99 ?> 96 100 </h3> 101 <div class="theme-actions"> 102 <button type="button" class="button button-primary customize-theme" aria-label="<?php echo esc_attr( $customize_label ); ?>"><?php _e( 'Customize' ); ?></button> 103 </div> 104 <div class="notice notice-success notice-alt"><p><?php _e( 'Installed' ); ?></p></div> 105 <# } else if ( 'installed' === data.theme.type ) { #> 106 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> 107 <div class="theme-actions"> 108 <button type="button" class="button button-primary preview-theme" aria-label="<?php echo esc_attr( $preview_label ); ?>" data-slug="{{ data.theme.id }}"><?php _e( 'Live Preview' ); ?></span> 109 </div> 110 <div class="notice notice-success notice-alt"><p><?php _e( 'Installed' ); ?></p></div> 97 111 <# } else { #> 98 <h3 class="theme-name" id="{{ data. theme.id }}-name">{{{ data.theme.name }}}</h3>112 <h3 class="theme-name" id="{{ data.section }}-{{ data.theme.id }}-name">{{ data.theme.name }}</h3> 99 113 <div class="theme-actions"> 100 <button type="button" class="button theme-details"><?php _e( 'Theme Details' ); ?></button>114 <button type="button" class="button button-primary theme-install preview" aria-label="<?php echo esc_attr( $install_label ); ?>" data-slug="{{ data.theme.id }}" data-name="{{ data.theme.name }}"><?php _e( 'Install & Preview' ); ?></button> 101 115 </div> 102 116 <# } #> 103 117 </div> -
src/wp-includes/customize/class-wp-customize-themes-panel.php
1 <?php 2 /** 3 * Customize API: WP_Customize_Themes_Panel class 4 * 5 * @package WordPress 6 * @subpackage Customize 7 * @since 4.9.0 8 */ 9 10 /** 11 * Customize Themes Panel Class 12 * 13 * @since 4.9.0 14 * 15 * @see WP_Customize_Panel 16 */ 17 class WP_Customize_Themes_Panel extends WP_Customize_Panel { 18 19 /** 20 * Panel type. 21 * 22 * @since 4.9.0 23 * @var string 24 */ 25 public $type = 'themes'; 26 27 /** 28 * An Underscore (JS) template for rendering this panel's container. 29 * 30 * The themes panel renders a custom panel heading with the current theme and a switch themes button. 31 * 32 * @see WP_Customize_Panel::print_template() 33 * 34 * @since 4.9.0 35 */ 36 protected function render_template() { 37 ?> 38 <li id="accordion-section-{{ data.id }}" class="accordion-section control-panel-themes"> 39 <h3 class="accordion-section-title"> 40 <?php 41 if ( $this->manager->is_theme_active() ) { 42 echo '<span class="customize-action">' . __( 'Active theme' ) . '</span> {{ data.title }}'; 43 } else { 44 echo '<span class="customize-action">' . __( 'Previewing theme' ) . '</span> {{ data.title }}'; 45 } 46 ?> 47 48 <?php 49 if ( current_user_can( 'switch_themes' ) ) : ?> 50 <button type="button" class="button change-theme" aria-label="<?php _e( 'Change theme' ); ?>"><?php _ex( 'Change', 'theme' ); ?></button> 51 <?php endif; ?> 52 </h3> 53 <ul class="accordion-sub-container control-panel-content"></ul> 54 </li> 55 <?php 56 } 57 58 /** 59 * An Underscore (JS) template for this panel's content (but not its container). 60 * 61 * Class variables for this panel class are available in the `data` JS object; 62 * export custom variables by overriding WP_Customize_Panel::json(). 63 * 64 * @since 4.9.0 65 * 66 * @see WP_Customize_Panel::print_template() 67 */ 68 protected function content_template() { 69 ?> 70 <li class="filter-themes-count"> 71 <span class="themes-displayed"><?php 72 /* translators: %s: number of themes displayed; plural forms cannot be accommodated here so assume plurality or translate as "Themes: %s" */ 73 echo sprintf( __( 'Displaying %s themes' ), '<span class="theme-count">0</span>' ); 74 ?></span> 75 <button type="button" class="button button-primary see-themes"><?php 76 /* translators: %s: number of themes displayed; plural forms cannot be accommodated here so assume plurality or omit the count and translate as "Show themes" */ 77 echo sprintf( __( 'Show %s themes' ), '<span class="theme-count">0</span>' ); 78 ?></button> 79 <button type="button" class="button button-primary filter-themes"><?php _e( 'Filter themes' ); ?></button> 80 </li> 81 <li class="panel-meta customize-info accordion-section <# if ( ! data.description ) { #> cannot-expand<# } #>"> 82 <button class="customize-panel-back" tabindex="-1"><span class="screen-reader-text"><?php _e( 'Back' ); ?></span></button> 83 <div class="accordion-section-title"> 84 <span class="preview-notice"><?php 85 /* translators: %s: themes panel title in the Customizer */ 86 echo sprintf( __( 'You are browsing %s' ), '<strong class="panel-title">' . __( 'Themes' ) . '</strong>' ); // Separate strings for consistency with other panels. 87 ?></span> 88 <?php if ( current_user_can( 'install_themes' ) && ! is_multisite() ) : ?> 89 <# if ( data.description ) { #> 90 <button class="customize-help-toggle dashicons dashicons-editor-help" tabindex="0" aria-expanded="false"><span class="screen-reader-text"><?php _e( 'Help' ); ?></span></button> 91 <# } #> 92 <?php endif; ?> 93 </div> 94 <?php if ( current_user_can( 'install_themes' ) && ! is_multisite() ) : ?> 95 <# if ( data.description ) { #> 96 <div class="description customize-panel-description"> 97 {{{ data.description }}} 98 </div> 99 <# } #> 100 <?php endif; ?> 101 </li> 102 <li id="customize-themes-loading-container"> 103 <span class="customize-loading-text-installing-theme"><?php _e( 'Downloading your new theme…' ); ?></span> 104 <span class="customize-loading-text"><?php _e( 'Setting up your live preview. This may take a bit.' ); ?></span> 105 </li><?php // Used as a full-screen overlay transition after clicking to preview a theme. ?> 106 <li class="customize-themes-full-container-container"> 107 <ul class="customize-themes-full-container"> 108 <li class="customize-themes-notifications"></li> 109 </ul> 110 </li> 111 <?php 112 } 113 } -
src/wp-includes/customize/class-wp-customize-themes-section.php
10 10 /** 11 11 * Customize Themes Section class. 12 12 * 13 * A UI container for theme controls, which behaves like a backwards Panel.13 * A UI container for theme controls, which are displayed 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 * @since 4. 2.023 * @since 4.9.0 25 24 * @var string 26 25 */ 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, latest, search, 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 public $action = ''; 37 38 /** 39 * Optional text to display before the theme section heading. 40 * 41 * @since 4.9.0 42 * @var string 43 */ 44 public $text_before = ''; 45 46 /** 47 * Get section parameters for JS. 48 * 49 * @since 4.9.0 50 * @return array Exported parameters. 51 */ 52 public function json() { 53 $exported = parent::json(); 54 $exported['action'] = $this->action; 55 $exported['text_before'] = $this->text_before; 56 57 return $exported; 58 } 59 60 /** 61 * Render a themes section as a JS template. 62 * 63 * The template is only rendered by PHP once, so all actions are prepared at once on the server side. 64 * 65 * @since 4.9.0 66 */ 67 protected function render_template() { 36 68 ?> 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; 69 <li id="accordion-section-{{ data.id }}" class="theme-section"> 70 <# if ( '' !== data.text_before ) { #> 71 <p class="customize-themes-text-before">{{ data.text_before }}</p> 72 <# } #> 73 <# if ( 'search' === data.action ) { #> 74 <div class="search-form customize-themes-section-title themes-section-search_themes"> 75 <label class="screen-reader-text" for="wp-filter-search-input">{{ data.title }}</label> 76 <input placeholder="{{ data.title }}" type="text" aria-describedby="live-search-desc" id="wp-filter-search-input" class="wp-filter-search"> 77 <span id="live-search-desc" class="screen-reader-text"><?php _e( 'The search results will be updated as you type.' ); ?></span> 78 </div> 79 <# } else { #> 80 <# if ( 'favorites' === data.action || 'feature_filter' === data.action ) { 81 var attr = ' aria-expanded="false"'; 42 82 } else { 43 echo '<span class="customize-action">' . __( 'Previewing theme' ) . '</span> ' . $this->title; 44 } 45 ?> 46 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"> 83 var attr = ''; 84 } #> 85 <button type="button" class="customize-themes-section-title themes-section-{{ data.id }}"{{{ attr }}}>{{ data.title }}</button> 86 <# } #> 87 <?php if ( ! current_user_can( 'install_themes' ) || is_multisite() ) : ?> 88 <# if ( 'installed' === data.action ) { #> 89 <p class="themes-filter-container"> 90 <label for="themes-filter"> 91 <span class="screen-reader-text"><?php _e( 'Search installed themes…' ); ?></span> 92 <input type="text" id="themes-filter" placeholder="<?php esc_attr_e( 'Search installed themes…' ); ?>" /> 93 </label> 94 </p> 95 <# } #> 96 <?php endif; ?> 97 <# if ( 'favorites' === data.action ) { #> 98 <div class="favorites-form filter-details"> 99 <p class="install-help"><?php _e( 'If you have marked themes as favorites on WordPress.org, you can browse them here.' ); ?></p> 100 <p> 101 <label for="wporg-username-input"><?php _e( 'Your WordPress.org username:' ); ?></label> 102 <input type="search" id="wporg-username-input" value=""> 103 <button type="button" class="button button-secondary favorites-form-submit"><?php _e( 'Get Favorites' ); ?></button> 104 </p> 105 </div> 106 <# } else if ( 'feature_filter' === data.action ) { #> 107 <div class="filter-drawer filter-details"> 59 108 <?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; 109 $feature_list = get_theme_feature_list(); 110 foreach ( $feature_list as $feature_name => $features ) { 111 echo '<fieldset class="filter-group">'; 112 $feature_name = esc_html( $feature_name ); 113 echo '<legend><button type="button" class="button-link" aria-expanded="false">' . $feature_name . '</button></legend>'; 114 echo '<div class="filter-group-feature">'; 115 foreach ( $features as $feature => $feature_name ) { 116 $feature = esc_attr( $feature ); 117 echo '<input type="checkbox" id="filter-id-' . $feature . '" value="' . $feature . '" /> '; 118 echo '<label for="filter-id-' . $feature . '">' . $feature_name . '</label><br>'; 119 } 120 echo '</div>'; 121 echo '</fieldset>'; 64 122 } 65 123 ?> 66 <button type="button" class="button customize-theme"><?php _e( 'Customize' ); ?></button>67 </h3>68 124 </div> 125 <# } #> 126 <div class="customize-themes-section themes-section-{{ data.id }} control-section-content themes-php"> 69 127 <div class="theme-overlay" tabindex="0" role="dialog" aria-label="<?php esc_attr_e( 'Theme Details' ); ?>"></div> 70 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; ?>78 128 <div class="theme-browser rendered"> 79 <ul class="themes accordion-section-content"> 80 </ul> 129 <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> 130 <ul class="themes"> 131 </ul> 132 <p class="no-themes"><?php _e( 'No themes found. Try a different search.' ); ?></p> 133 <p class="spinner"></p> 81 134 </div> 82 135 </div> 83 136 </li> -
tests/phpunit/tests/customize/manager.php
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', 'customCss', 'changeset', 'timeouts' ), array_keys( $data ) );2354 $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'customCss', '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'] );