| @@ -1470,331 +1470,9 @@ | ||
| 1470 | 1470 | save.notify(err.message || i18n.genericError, "error"); |
| 1471 | 1471 | }); |
| 1472 | 1472 | }); |
| 1473 | 1473 | |
| 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 | 1474 | /* ------------------------------------------------------------------ * |
| 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 | 1475 | * Products tab: client-side search + pagination over the product list. |
| 1798 | 1476 | * ------------------------------------------------------------------ */ |
| 1799 | 1477 | |
| 1800 | 1478 | /** |
| @@ -1925,13 +1603,8 @@ | ||
| 1925 | 1603 | } |
| 1926 | 1604 | perPage = parseInt(e.detail && e.detail.value, 10) || perPage; |
| 1927 | 1605 | page = 1; |
| 1928 | 1606 | 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 | 1607 | }); |
| 1935 | 1608 | |
| 1936 | 1609 | pager.addEventListener("click", function (e) { |
| 1937 | 1610 | var btn = e.target.closest("[data-subscrpt-page]"); |