PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.0.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.0.0
2.4.0 2.3.0 2.2.0 2.1.0 2.0.0 1.11.2 1.11.1 1.11.0 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.6 1.9.5 trunk 1.3.0 1.3.1 1.3.2 1.4.0 All 65 releases
← All changes | assets/js/admin/plans.js +1 -547 trunk → 2.0.0 View file →
@@ -579,168 +579,8 @@
579 579 return host ? host.getAttribute("data-plan-id") || "" : "";
580 580 }
581 581
582 582 /* ------------------------------------------------------------------ *
583 - * Products tab: bulk-set one price across products x durations.
584 - * ------------------------------------------------------------------ */
585 -
586 - /**
587 - * Reflect a variable product's variations in its parent control checkbox:
588 - * on when all are ticked, indeterminate when some are.
589 - *
590 - * @param {HTMLElement} group The [data-subscrpt-bulk-group] wrapper.
591 - */
592 - function syncBulkParent(group) {
593 - var parent = group && group.querySelector("[data-subscrpt-bulk-parent]");
594 - if (!parent) {
595 - return;
596 - }
597 - var kids = group.querySelectorAll("[data-subscrpt-bulk-product]");
598 - var checked = 0;
599 - kids.forEach(function (cb) {
600 - if (cb.checked) {
601 - checked += 1;
602 - }
603 - });
604 - parent.checked = kids.length > 0 && checked === kids.length;
605 - parent.indeterminate = checked > 0 && checked < kids.length;
606 - }
607 -
608 - /**
609 - * Update the "N selected" tally beside each column header.
610 - *
611 - * @param {HTMLElement} modal The bulk-price modal.
612 - */
613 - function syncBulkCounts(modal) {
614 - [
615 - ["term", "[data-subscrpt-bulk-term]"],
616 - ["product", "[data-subscrpt-bulk-product]"],
617 - ].forEach(function (pair) {
618 - var out = modal.querySelector('[data-subscrpt-bulk-count="' + pair[0] + '"]');
619 - if (!out) {
620 - return;
621 - }
622 - var n = modal.querySelectorAll(pair[1] + ":checked").length;
623 - out.textContent = n ? (i18n.bulkSelected || "%d selected").replace("%d", n) : "";
624 - });
625 - }
626 -
627 - // Any change inside the modal refreshes the per-column tallies.
628 - document.addEventListener("change", function (e) {
629 - var modal = e.target.closest("[data-subscrpt-bulk-price]");
630 - if (modal) {
631 - syncBulkCounts(modal);
632 - }
633 - });
634 -
635 - // A column's "All" checkbox ticks/unticks every row in that column.
636 - document.addEventListener("change", function (e) {
637 - var all = e.target.closest("[data-subscrpt-bulk-toggle-all]");
638 - var modal = all && all.closest("[data-subscrpt-bulk-price]");
639 - if (!modal) {
640 - return;
641 - }
642 - var col = all.getAttribute("data-subscrpt-bulk-toggle-all");
643 - var sel = "product" === col ? "[data-subscrpt-bulk-product]" : "[data-subscrpt-bulk-term]";
644 - modal.querySelectorAll(sel).forEach(function (cb) {
645 - if (!cb.disabled) {
646 - cb.checked = all.checked;
647 - }
648 - });
649 - if ("product" === col) {
650 - modal.querySelectorAll("[data-subscrpt-bulk-parent]").forEach(function (parent) {
651 - parent.checked = all.checked;
652 - parent.indeterminate = false;
653 - });
654 - }
655 - });
656 -
657 - // A variable product's parent control ticks/unticks all its variations.
658 - document.addEventListener("change", function (e) {
659 - var parent = e.target.closest("[data-subscrpt-bulk-parent]");
660 - var group = parent && parent.closest("[data-subscrpt-bulk-group]");
661 - if (!group || !parent.closest("[data-subscrpt-bulk-price]")) {
662 - return;
663 - }
664 - group.querySelectorAll("[data-subscrpt-bulk-product]").forEach(function (cb) {
665 - if (!cb.disabled) {
666 - cb.checked = parent.checked;
667 - }
668 - });
669 - parent.indeterminate = false;
670 - });
671 -
672 - // A variation tick keeps its parent control in sync.
673 - document.addEventListener("change", function (e) {
674 - var child = e.target.closest("[data-subscrpt-bulk-product]");
675 - var group = child && child.closest("[data-subscrpt-bulk-group]");
676 - if (!group || !child.closest("[data-subscrpt-bulk-price]")) {
677 - return;
678 - }
679 - syncBulkParent(group);
680 - });
681 -
682 - // Apply the entered price to every selected product on every selected duration.
683 - document.addEventListener("click", function (e) {
684 - var btn = e.target.closest("[data-subscrpt-bulk-apply]");
685 - var modal = btn && btn.closest("[data-subscrpt-bulk-price]");
686 - if (!modal) {
687 - return;
688 - }
689 -
690 - var regInput = modal.querySelector("[data-subscrpt-bulk-regular]");
691 - var saleInput = modal.querySelector("[data-subscrpt-bulk-sale]");
692 - var regular = regInput ? regInput.value.trim() : "";
693 - if ("" === regular) {
694 - save.notify(i18n.bulkNoPrice || i18n.genericError, "error");
695 - return;
696 - }
697 -
698 - var products = Array.prototype.map.call(
699 - modal.querySelectorAll("[data-subscrpt-bulk-product]:checked"),
700 - function (cb) {
701 - return { oid: cb.getAttribute("data-oid"), vid: cb.getAttribute("data-vid") || 0 };
702 - },
703 - );
704 - if (!products.length) {
705 - save.notify(i18n.bulkNoProducts || i18n.genericError, "error");
706 - return;
707 - }
708 -
709 - var planIds = Array.prototype.map.call(modal.querySelectorAll("[data-subscrpt-bulk-term]:checked"), function (cb) {
710 - return cb.value;
711 - });
712 - if (!planIds.length) {
713 - save.notify(i18n.bulkNoTerms || i18n.genericError, "error");
714 - return;
715 - }
716 -
717 - setLoading(btn, true);
718 - api("POST", "/bulk-price", {
719 - plan_ids: planIds,
720 - products: products,
721 - regular_price: regular,
722 - sale_price: saleInput ? saleInput.value.trim() : "",
723 - })
724 - .then(function () {
725 - // Close before the panel refresh replaces (and orphans) the modal —
726 - // otherwise its body-scroll lock is never released.
727 - if (window.WPSubsModal && window.WPSubsModal.close) {
728 - window.WPSubsModal.close("subscrpt-bulk-price");
729 - }
730 - return refreshProducts(currentGroupId());
731 - })
732 - .then(function () {
733 - setLoading(btn, false);
734 - save.notify(i18n.bulkApplied || i18n.pricesSaved);
735 - })
736 - .catch(function (err) {
737 - setLoading(btn, false);
738 - save.notify(err.message || i18n.genericError, "error");
739 - });
740 - });
741 -
742 - /* ------------------------------------------------------------------ *
743 583 * Products tab (Pro): bulk-add products to the plan group.
744 584 * ------------------------------------------------------------------ */
745 585
746 586 /**
@@ -944,9 +784,8 @@
944 784 if (list.lastElementChild) {
945 785 list.lastElementChild.style.borderBottom = "none";
946 786 }
947 787 syncPickerCount(modal);
948 - syncSelectAll(modal);
949 788 })
950 789 .catch(function () {
951 790 list.innerHTML =
952 791 '<li style="padding:10px 4px;color:var(--wpsubs-text-subtle);font-size:13px;">' +
@@ -976,75 +815,17 @@
976 815 });
977 816 out.textContent = n ? (i18n.picked || "%d on this plan").replace("%d", n) : i18n.pickedNone || "";
978 817 }
979 818
980 - /**
981 - * The picker's attachable, non-locked checkboxes — what select-all acts on.
982 - * Rows locked to another plan are disabled and left out.
983 - *
984 - * @param {HTMLElement} modal The add-product modal.
985 - * @return {HTMLElement[]} Enabled row checkboxes.
986 - */
987 - function selectableBoxes(modal) {
988 - var boxes = modal.querySelectorAll("[data-subscrpt-product-list] input[data-oid]");
989 - return Array.prototype.filter.call(boxes, function (box) {
990 - return !box.disabled;
991 - });
992 - }
993 -
994 - /**
995 - * Reflect the rows in the select-all checkbox: on when all are ticked,
996 - * indeterminate when some are, off when none (disabled when nothing to pick).
997 - *
998 - * @param {HTMLElement} modal The add-product modal.
999 - */
1000 - function syncSelectAll(modal) {
1001 - var toggle = modal && modal.querySelector("[data-subscrpt-picker-all]");
1002 - if (!toggle) {
1003 - return;
1004 - }
1005 - var boxes = selectableBoxes(modal);
1006 - var checked = boxes.filter(function (box) {
1007 - return box.checked;
1008 - }).length;
1009 - toggle.disabled = 0 === boxes.length;
1010 - toggle.checked = boxes.length > 0 && checked === boxes.length;
1011 - toggle.indeterminate = checked > 0 && checked < boxes.length;
1012 - }
1013 -
1014 - // Any tick in the picker updates the tally and the select-all state.
819 + // Any tick in the picker updates the tally.
1015 820 document.addEventListener("change", function (e) {
1016 821 var box = e.target.closest("[data-subscrpt-product-list] input[type=checkbox]");
1017 822 var modal = box && box.closest("[data-subscrpt-add-product]");
1018 823 if (modal) {
1019 824 syncPickerCount(modal);
1020 - syncSelectAll(modal);
1021 825 }
1022 826 });
1023 827
1024 - // Select-all ticks/unticks every enabled row. Select-all makes the picker
1025 - // uniform, so parent controls and children all take the same state directly —
1026 - // no synthetic change events (which would re-enter syncSelectAll mid-loop).
1027 - document.addEventListener("change", function (e) {
1028 - var toggle = e.target.closest("[data-subscrpt-picker-all]");
1029 - var modal = toggle && toggle.closest("[data-subscrpt-add-product]");
1030 - if (!modal) {
1031 - return;
1032 - }
1033 - var checked = toggle.checked;
1034 - var boxes = modal.querySelectorAll("[data-subscrpt-product-list] input[type=checkbox]");
1035 - Array.prototype.forEach.call(boxes, function (box) {
1036 - if (box.disabled) {
1037 - return;
1038 - }
1039 - box.checked = checked;
1040 - box.indeterminate = false;
1041 - });
1042 - toggle.checked = checked;
1043 - toggle.indeterminate = false;
1044 - syncPickerCount(modal);
1045 - });
1046 -
1047 828 // Load the picker when the modal opens (pre-checking attached products).
1048 829 document.addEventListener("wpsubs:modal:open", function (e) {
1049 830 var modal = e.target;
1050 831 if (modal && modal.id === "subscrpt-add-product") {
@@ -1470,331 +1251,9 @@
1470 1251 save.notify(err.message || i18n.genericError, "error");
1471 1252 });
1472 1253 });
1473 1254
1474 - // "Allow User Cancellation" saves itself too; on failure the switch goes back
1475 - // to what is stored.
1476 - document.addEventListener("change", function (e) {
1477 - var toggle = e.target.closest("[data-subscrpt-user-cancel-card] [data-subscrpt-user-cancel-enable]");
1478 - if (!toggle) {
1479 - return;
1480 - }
1481 - var card = toggle.closest("[data-subscrpt-user-cancel-card]");
1482 -
1483 - toggle.disabled = true;
1484 - api("PUT", "/product-user-cancel/" + card.getAttribute("data-product-id"), { enabled: toggle.checked })
1485 - .then(function () {
1486 - toggle.disabled = false;
1487 - save.notify(toggle.checked ? i18n.userCancelOn : i18n.userCancelOff);
1488 - })
1489 - .catch(function (err) {
1490 - toggle.disabled = false;
1491 - toggle.checked = !toggle.checked;
1492 - save.notify(err.message || i18n.genericError, "error");
1493 - });
1494 - });
1495 -
1496 1255 /* ------------------------------------------------------------------ *
1497 - * Drag-and-drop reorder: durations + products (vertical only).
1498 - *
1499 - * A drag starts on [data-subscrpt-sort-handle] only, moves its
1500 - * [data-subscrpt-sort-item] within the [data-subscrpt-sortable] list, and
1501 - * saves the list's full id order (hidden/paged-out items included) to
1502 - * /groups/{id}/order. Siblings slide aside with a FLIP transition. Arrow
1503 - * keys on a focused handle move it one step. Everything is delegated,
1504 - * because the Products panel is re-rendered in place.
1505 - * ------------------------------------------------------------------ */
1506 -
1507 - var SORT_ANIM_MS = 200;
1508 - var SORT_EDGE_PX = 60;
1509 - var sortDrag = null;
1510 - var sortPending = {};
1511 -
1512 - /**
1513 - * Items of a list the merchant can currently see (search/pager aware).
1514 - *
1515 - * @param {HTMLElement} list Sortable list.
1516 - * @return {HTMLElement[]}
1517 - */
1518 - function sortVisible(list) {
1519 - return Array.prototype.filter.call(list.children, function (el) {
1520 - return el.hasAttribute("data-subscrpt-sort-item") && "none" !== el.style.display;
1521 - });
1522 - }
1523 -
1524 - /**
1525 - * Every item id of a list, in DOM order.
1526 - *
1527 - * @param {HTMLElement} list Sortable list.
1528 - * @return {number[]}
1529 - */
1530 - function sortIds(list) {
1531 - return Array.prototype.map.call(list.querySelectorAll(":scope > [data-subscrpt-sort-item]"), function (el) {
1532 - return parseInt(el.getAttribute("data-subscrpt-sort-item"), 10);
1533 - });
1534 - }
1535 -
1536 - /**
1537 - * Move `el` in the DOM (before `ref`, or to the end) and slide every other
1538 - * visible item from where it was to where it now is.
1539 - *
1540 - * @param {HTMLElement} list Sortable list.
1541 - * @param {HTMLElement} el Item to move.
1542 - * @param {HTMLElement|null} ref Insert before this item.
1543 - * @param {boolean} self Animate `el` too (keyboard moves).
1544 - */
1545 - function sortMove(list, el, ref, self) {
1546 - var others = sortVisible(list).filter(function (it) {
1547 - return self || it !== el;
1548 - });
1549 - var before = others.map(function (it) {
1550 - return it.offsetTop;
1551 - });
1552 -
1553 - list.insertBefore(el, ref);
1554 -
1555 - others.forEach(function (it, i) {
1556 - var delta = before[i] - it.offsetTop;
1557 - if (!delta) {
1558 - return;
1559 - }
1560 - it.style.transition = "none";
1561 - it.style.transform = "translateY(" + delta + "px)";
1562 - void it.offsetHeight; // Commit the start frame.
1563 - it.style.transition = "transform " + SORT_ANIM_MS + "ms cubic-bezier(0.2, 0, 0, 1)";
1564 - it.style.transform = "";
1565 - window.clearTimeout(it._subscrptSortTimer);
1566 - it._subscrptSortTimer = window.setTimeout(function () {
1567 - it.style.transition = "";
1568 - }, SORT_ANIM_MS);
1569 - });
1570 - }
1571 -
1572 - /**
1573 - * Put a list back into a given id order (a failed save).
1574 - *
1575 - * @param {HTMLElement} list Sortable list.
1576 - * @param {number[]} ids Order to restore.
1577 - */
1578 - function sortRestore(list, ids) {
1579 - ids.forEach(function (id) {
1580 - var el = list.querySelector(':scope > [data-subscrpt-sort-item="' + id + '"]');
1581 - if (el) {
1582 - list.appendChild(el);
1583 - }
1584 - });
1585 - sortSynced(list);
1586 - }
1587 -
1588 - /**
1589 - * Tell the list's search/pager the DOM order changed.
1590 - *
1591 - * @param {HTMLElement} list Sortable list.
1592 - */
1593 - function sortSynced(list) {
1594 - var root = list.closest("[data-subscrpt-browse]");
1595 - if (root) {
1596 - root.dispatchEvent(new CustomEvent("subscrpt:sorted"));
1597 - }
1598 - }
1599 -
1600 - /**
1601 - * Save a list's order. Debounced so a run of arrow-key moves is one write;
1602 - * on failure the list goes back to the order it had before the run.
1603 - *
1604 - * @param {HTMLElement} list Sortable list.
1605 - * @param {number[]} before Order before this change.
1606 - */
1607 - function sortSave(list, before) {
1608 - var key = list.getAttribute("data-subscrpt-sortable");
1609 - var groupId = currentGroupId();
1610 - if (!key || !groupId) {
1611 - return;
1612 - }
1613 -
1614 - var pending = sortPending[key] || { before: before };
1615 - sortPending[key] = pending;
1616 - window.clearTimeout(pending.timer);
1617 - pending.timer = window.setTimeout(function () {
1618 - delete sortPending[key];
1619 - var ids = sortIds(list);
1620 - if (ids.join() === pending.before.join()) {
1621 - return;
1622 - }
1623 - var body = {};
1624 - body[key] = ids;
1625 - api("PUT", "/groups/" + groupId + "/order", body)
1626 - .then(function () {
1627 - save.notify(i18n.orderSaved);
1628 - })
1629 - .catch(function (err) {
1630 - sortRestore(list, pending.before);
1631 - save.notify(err.message || i18n.genericError, "error");
1632 - });
1633 - }, 300);
1634 - }
1635 -
1636 - /**
1637 - * Follow the pointer: position the lifted item, swap it past any sibling
1638 - * whose midpoint it crossed, and keep it inside the list.
1639 - *
1640 - * @param {number} clientY Pointer Y in the viewport.
1641 - */
1642 - function sortUpdate(clientY) {
1643 - var d = sortDrag;
1644 - var el = d.item;
1645 - var items = sortVisible(d.list);
1646 - var first = items[0];
1647 - var last = items[items.length - 1];
1648 -
1649 - // Page coordinates, so auto-scroll does not throw the item off the pointer.
1650 - var dy = clientY + window.scrollY - d.startY;
1651 - var top = d.startTop + dy;
1652 - top = Math.max(first.offsetTop, Math.min(top, last.offsetTop + last.offsetHeight - el.offsetHeight));
1653 - // Leading edge, not centre: an expanded (tall) product must still be able
1654 - // to pass a short one inside the clamped range.
1655 - var bottom = top + el.offsetHeight;
1656 -
1657 - var i = items.indexOf(el);
1658 - var next = items[i + 1];
1659 - var prev = items[i - 1];
1660 - var movedDown = false;
1661 - while (next && bottom > next.offsetTop + next.offsetHeight / 2) {
1662 - sortMove(d.list, el, next.nextSibling, false);
1663 - items = sortVisible(d.list);
1664 - next = items[items.indexOf(el) + 1];
1665 - movedDown = true;
1666 - }
1667 - while (!movedDown && prev && top < prev.offsetTop + prev.offsetHeight / 2) {
1668 - sortMove(d.list, el, prev, false);
1669 - items = sortVisible(d.list);
1670 - prev = items[items.indexOf(el) - 1];
1671 - }
1672 -
1673 - el.style.transform = "translateY(" + (top - el.offsetTop) + "px) scale(1.01)";
1674 - }
1675 -
1676 - /**
1677 - * Scroll the page while the pointer rests near the top/bottom edge.
1678 - */
1679 - function sortAutoScroll() {
1680 - if (!sortDrag) {
1681 - return;
1682 - }
1683 - var y = sortDrag.clientY;
1684 - var h = window.innerHeight;
1685 - var step = 0;
1686 - if (y < SORT_EDGE_PX) {
1687 - step = -Math.ceil((SORT_EDGE_PX - y) / 4);
1688 - } else if (y > h - SORT_EDGE_PX) {
1689 - step = Math.ceil((y - (h - SORT_EDGE_PX)) / 4);
1690 - }
1691 - if (step) {
1692 - window.scrollBy(0, step);
1693 - sortUpdate(y);
1694 - }
1695 - sortDrag.raf = window.requestAnimationFrame(sortAutoScroll);
1696 - }
1697 -
1698 - function sortOnMove(e) {
1699 - if (!sortDrag || e.pointerId !== sortDrag.pointerId) {
1700 - return;
1701 - }
1702 - e.preventDefault();
1703 - sortDrag.clientY = e.clientY;
1704 - sortUpdate(e.clientY);
1705 - }
1706 -
1707 - function sortOnEnd(e) {
1708 - if (!sortDrag || e.pointerId !== sortDrag.pointerId) {
1709 - return;
1710 - }
1711 - var d = sortDrag;
1712 - var el = d.item;
1713 - sortDrag = null;
1714 -
1715 - window.cancelAnimationFrame(d.raf);
1716 - window.removeEventListener("pointermove", sortOnMove);
1717 - window.removeEventListener("pointerup", sortOnEnd);
1718 - window.removeEventListener("pointercancel", sortOnEnd);
1719 - document.body.classList.remove("wpsubs-sorting");
1720 -
1721 - // Glide into the slot, then drop the lift.
1722 - el.classList.remove("wpsubs-sort-item--dragging");
1723 - el.classList.add("wpsubs-sort-item--settling");
1724 - el.style.transform = "";
1725 - window.setTimeout(function () {
1726 - el.classList.remove("wpsubs-sort-item--settling");
1727 - }, 240);
1728 -
1729 - if (sortIds(d.list).join() !== d.before.join()) {
1730 - sortSynced(d.list);
1731 - sortSave(d.list, d.before);
1732 - }
1733 - }
1734 -
1735 - document.addEventListener("pointerdown", function (e) {
1736 - var handle = e.target.closest("[data-subscrpt-sort-handle]");
1737 - if (!handle || sortDrag || (e.pointerType === "mouse" && 0 !== e.button)) {
1738 - return;
1739 - }
1740 - var item = handle.closest("[data-subscrpt-sort-item]");
1741 - var list = item && item.parentElement;
1742 - if (!list || !list.hasAttribute("data-subscrpt-sortable")) {
1743 - return;
1744 - }
1745 - e.preventDefault();
1746 - handle.focus({ preventScroll: true });
1747 -
1748 - sortDrag = {
1749 - item: item,
1750 - list: list,
1751 - pointerId: e.pointerId,
1752 - startY: e.clientY + window.scrollY,
1753 - startTop: item.offsetTop,
1754 - clientY: e.clientY,
1755 - before: sortIds(list),
1756 - };
1757 -
1758 - item.style.transition = "";
1759 - item.classList.remove("wpsubs-sort-item--settling");
1760 - item.classList.add("wpsubs-sort-item--dragging");
1761 - item.style.transform = "scale(1.01)";
1762 - document.body.classList.add("wpsubs-sorting");
1763 -
1764 - window.addEventListener("pointermove", sortOnMove, { passive: false });
1765 - window.addEventListener("pointerup", sortOnEnd);
1766 - window.addEventListener("pointercancel", sortOnEnd);
1767 - sortDrag.raf = window.requestAnimationFrame(sortAutoScroll);
1768 - });
1769 -
1770 - // Keyboard: ArrowUp / ArrowDown on a focused handle moves one step.
1771 - document.addEventListener("keydown", function (e) {
1772 - var handle = e.target.closest ? e.target.closest("[data-subscrpt-sort-handle]") : null;
1773 - if (!handle || sortDrag || ("ArrowUp" !== e.key && "ArrowDown" !== e.key)) {
1774 - return;
1775 - }
1776 - var item = handle.closest("[data-subscrpt-sort-item]");
1777 - var list = item && item.parentElement;
1778 - if (!list || !list.hasAttribute("data-subscrpt-sortable")) {
1779 - return;
1780 - }
1781 - e.preventDefault();
1782 -
1783 - var items = sortVisible(list);
1784 - var i = items.indexOf(item);
1785 - var target = "ArrowUp" === e.key ? items[i - 1] : items[i + 1];
1786 - if (!target) {
1787 - return;
1788 - }
1789 - var before = sortIds(list);
1790 - sortMove(list, item, "ArrowUp" === e.key ? target : target.nextSibling, true);
1791 - handle.focus({ preventScroll: true });
1792 - sortSynced(list);
1793 - sortSave(list, before);
1794 - });
1795 -
1796 - /* ------------------------------------------------------------------ *
1797 1256 * Products tab: client-side search + pagination over the product list.
1798 1257 * ------------------------------------------------------------------ */
1799 1258
1800 1259 /**
@@ -1925,13 +1384,8 @@
1925 1384 }
1926 1385 perPage = parseInt(e.detail && e.detail.value, 10) || perPage;
1927 1386 page = 1;
1928 1387 render();
1929 - });
1930 -
1931 - // A drag-and-drop reorder moved items in the DOM: page in the new order.
1932 - root.addEventListener("subscrpt:sorted", function () {
1933 - items = Array.prototype.slice.call(root.querySelectorAll("[data-subscrpt-browse-item]"));
1934 1388 });
1935 1389
1936 1390 pager.addEventListener("click", function (e) {
1937 1391 var btn = e.target.closest("[data-subscrpt-page]");