(function() { "use strict"; const TEXT_DOMAIN = "desktop-mode"; function i18n() { return window.wp?.i18n; } function __(text, domain = TEXT_DOMAIN) { return i18n()?.__(text, domain) ?? text; } function _n(single, plural, number, domain = TEXT_DOMAIN) { return i18n()?._n(single, plural, number, domain) ?? (number === 1 ? single : plural); } function sprintf(format, ...args) { const impl = i18n()?.sprintf; if (impl) { return impl(format, ...args); } let i = 0; return format.replace(/%[sd]/g, () => String(args[i++] ?? "")); } function getWpHooks() { const hooks = window.wp?.hooks; if (!hooks) { throw new Error( "[desktop-mode] `window.wp.hooks` is not available. The plugin declares `wp-hooks` as a script dependency; if you are seeing this error, verify the enqueue order." ); } return hooks; } function addAction(hookName2, namespace, callback2, priority) { getWpHooks().addAction( hookName2, namespace, callback2, priority ); } function removeAction(hookName2, namespace) { return getWpHooks().removeAction(hookName2, namespace); } function applyFilters(hookName2, value, ...args) { return getWpHooks().applyFilters(hookName2, value, ...args); } function doAction(hookName2, ...args) { getWpHooks().doAction(hookName2, ...args); } const CANARY_TAG = "wpd-confirm-dialog"; let inflight = null; function isLoaded() { return typeof window.customElements !== "undefined" && !!window.customElements.get(CANARY_TAG); } function injectScript(scriptUrl) { return new Promise((resolve, reject) => { const existing = document.querySelector( 'script[data-desktop-mode-shell-overlays="1"]' ); const finish = () => { if (isLoaded()) { resolve(); return; } reject( new Error( "[desktop-mode] shell-overlays bundle loaded but did not register the overlay components." ) ); }; if (existing) { if (isLoaded()) { finish(); } else { existing.addEventListener("load", finish); existing.addEventListener( "error", () => reject(new Error("failed to load shell-overlays bundle")) ); } return; } const s = document.createElement("script"); s.src = scriptUrl; s.async = true; s.dataset.desktopModeShellOverlays = "1"; s.addEventListener("load", finish); s.addEventListener( "error", () => reject(new Error("failed to load shell-overlays bundle")) ); document.head.appendChild(s); }); } function ensureShellOverlaysLoaded(scriptUrl) { if (isLoaded()) { return Promise.resolve(); } if (!scriptUrl) { return Promise.resolve(); } if (!inflight) { inflight = injectScript(scriptUrl); } return inflight; } function shellOverlaysBundleUrl() { const cfg = window.desktopModeConfig; return cfg?.shellOverlaysBundleUrl ?? ""; } function openWithShellOverlays(isStillCurrent, fn) { const url = shellOverlaysBundleUrl(); if (isLoaded() || !url) { fn(); return; } void ensureShellOverlaysLoaded(url).then(() => { if (!isStillCurrent()) { return; } fn(); }).catch((err) => { if (typeof console !== "undefined") { console.warn( "[desktop-mode] shell-overlays failed to load; menu/dialog suppressed:", err ); } }); } const MENU_CLASS = "desktop-mode-icon-canvas-menu"; let activeMenu = null; let activeFlyout = null; let activeCanvas = null; let outsideHandler = null; let escHandler = null; function attachIconCanvasMenu(canvas, deps) { deps.openOnBackgroundClick !== false; const onContextMenu = (e) => { if (isInsideTile(e.target) || isInsideMenu(e.target)) { return; } e.preventDefault(); toggle(e.clientX, e.clientY); }; let toggleGen = 0; const toggle = (x, y) => { if (activeCanvas === canvas && activeMenu) { closeMenu(); return; } const items = buildItems(deps); const filtered = applyFilters( "desktop-mode.icon-canvas.menu", items, deps.scope ); const finalItems = Array.isArray(filtered) ? filtered : items; const myGen = ++toggleGen; openWithShellOverlays( () => myGen === toggleGen, () => openMenu(finalItems, { x, y }, canvas) ); }; canvas.addEventListener("contextmenu", onContextMenu); return { dispose: () => { canvas.removeEventListener("contextmenu", onContextMenu); closeMenu(); } }; } function isInsideTile(target) { if (!(target instanceof Element)) { return false; } return target.closest(".desktop-mode-file-tile") !== null; } function isInsideMenu(target) { if (!(target instanceof Element)) { return false; } return target.closest(`.${MENU_CLASS}`) !== null; } function buildItems(deps) { const sortItem = { id: "sort-by", label: __("Sort by", "desktop-mode"), icon: "dashicons-sort", sort: 10, children: [ { id: "sort-name-asc", label: __("Name (A → Z)", "desktop-mode"), sort: 10, onClick: () => deps.onSort("name-asc") }, { id: "sort-name-desc", label: __("Name (Z → A)", "desktop-mode"), sort: 20, onClick: () => deps.onSort("name-desc") }, { id: "sort-date-desc", label: __("Newest first", "desktop-mode"), sort: 30, onClick: () => deps.onSort("date-desc") }, { id: "sort-date-asc", label: __("Oldest first", "desktop-mode"), sort: 40, onClick: () => deps.onSort("date-asc") } ] }; const items = [sortItem]; if (Array.isArray(deps.extraItems)) { items.push(...deps.extraItems); } return items; } function sortItems(items) { return items.slice().sort((a, b) => { const sa = typeof a.sort === "number" ? a.sort : 100; const sb = typeof b.sort === "number" ? b.sort : 100; if (sa !== sb) { return sa - sb; } return a.label.localeCompare(b.label); }); } function openMenu(items, pos, canvas) { closeMenu(); if (items.length === 0) { return; } activeCanvas = canvas; const sorted = sortItems(items); const menu = document.createElement("wpd-context-menu"); menu.setAttribute("open", ""); menu.classList.add(MENU_CLASS); menu.style.left = `${pos.x}px`; menu.style.top = `${pos.y}px`; const itemById = /* @__PURE__ */ new Map(); for (const item of sorted) { itemById.set(item.id, item); const opt = appendOption(menu, item); if (hasChildren(item)) { opt.addEventListener("mouseenter", () => { openFlyout(item, opt); }); } } menu.addEventListener("wpd-context-menu-pick", (e) => { const detail = e.detail; const item = itemById.get(detail.id); if (!item) { return; } if (hasChildren(item)) { e.stopPropagation(); const anchor = menu.querySelector( `[data-menu-item-id="${item.id}"]` ); if (anchor) { openFlyout(item, anchor); } return; } closeMenu(); item.onClick?.(); }); document.body.appendChild(menu); activeMenu = menu; clampToViewport(menu); queueMicrotask(() => { outsideHandler = (e) => { if (isInsideMenu(e.target)) { return; } closeMenu(); }; escHandler = (e) => { if (e.key === "Escape") { closeMenu(); } }; document.addEventListener("mousedown", outsideHandler); document.addEventListener("keydown", escHandler); }); } function appendOption(host, item) { const opt = document.createElement("wpd-context-menu-option"); opt.dataset.menuItemId = item.id; opt.setAttribute("value", item.id); if (item.heading) { opt.setAttribute("heading", ""); } if (item.disabled) { opt.setAttribute("disabled", ""); } if (item.icon) { opt.setAttribute("icon", sanitizeClass$1(item.icon)); } if (hasChildren(item)) { opt.setAttribute("has-children", ""); } opt.textContent = item.label; host.appendChild(opt); return opt; } function openFlyout(parent, anchor) { closeFlyout(); if (!hasChildren(parent)) { return; } const fly = document.createElement("wpd-context-menu"); fly.setAttribute("open", ""); fly.classList.add(MENU_CLASS, `${MENU_CLASS}--flyout`); const childById = /* @__PURE__ */ new Map(); for (const child of sortItems(parent.children ?? [])) { childById.set(child.id, child); appendOption(fly, child); } fly.addEventListener("wpd-context-menu-pick", (e) => { const detail = e.detail; const child = childById.get(detail.id); if (!child) { return; } e.stopPropagation(); closeMenu(); child.onClick?.(); }); document.body.appendChild(fly); activeFlyout = fly; positionFlyout(fly, anchor); } function positionFlyout(fly, anchor) { const ar = anchor.getBoundingClientRect(); fly.style.position = "fixed"; fly.style.left = `${ar.right}px`; fly.style.top = `${ar.top}px`; const fr = fly.getBoundingClientRect(); if (fr.right > window.innerWidth) { fly.style.left = `${Math.max(0, ar.left - fr.width)}px`; } if (fr.bottom > window.innerHeight) { fly.style.top = `${Math.max(0, window.innerHeight - fr.height - 8)}px`; } } function clampToViewport(menu) { const rect = menu.getBoundingClientRect(); if (rect.right > window.innerWidth) { menu.style.left = `${Math.max(0, window.innerWidth - rect.width - 8)}px`; } if (rect.bottom > window.innerHeight) { menu.style.top = `${Math.max(0, window.innerHeight - rect.height - 8)}px`; } } function hasChildren(item) { return Array.isArray(item.children) && item.children.length > 0; } function closeFlyout() { if (activeFlyout) { activeFlyout.remove(); activeFlyout = null; } } function closeMenu() { closeFlyout(); if (activeMenu) { activeMenu.remove(); activeMenu = null; } activeCanvas = null; if (outsideHandler) { document.removeEventListener("mousedown", outsideHandler); outsideHandler = null; } if (escHandler) { document.removeEventListener("keydown", escHandler); escHandler = null; } } function sanitizeClass$1(raw) { return raw.replace(/[^a-zA-Z0-9_-]/g, ""); } const STATUS_BAR_CLASS = "desktop-mode-folder-status-bar"; const ROOT_CLASS$1 = STATUS_BAR_CLASS; function renderStatusBarSegments(bar, segments) { render$1(bar, segments); } function render$1(bar, segments) { const sort = (a, b) => { const sa = typeof a.sort === "number" ? a.sort : 100; const sb = typeof b.sort === "number" ? b.sort : 100; if (sa !== sb) { return sa - sb; } return a.label.localeCompare(b.label); }; const start = segments.filter((s) => (s.align ?? "start") === "start").sort(sort); const end = segments.filter((s) => s.align === "end").sort(sort); bar.replaceChildren(); bar.appendChild(buildCluster("start", start)); bar.appendChild(buildCluster("end", end)); } function buildCluster(align, segs) { const cluster = document.createElement("div"); cluster.className = `${ROOT_CLASS$1}__cluster ${ROOT_CLASS$1}__cluster--${align}`; for (const seg of segs) { cluster.appendChild(buildSegment(seg)); } return cluster; } function buildSegment(seg) { const interactive = typeof seg.onClick === "function"; const el = document.createElement(interactive ? "button" : "span"); el.className = `${ROOT_CLASS$1}__segment`; el.dataset.segmentId = seg.id; if (interactive) { el.type = "button"; el.addEventListener("click", (e) => seg.onClick(e)); } if (seg.icon) { const icon = document.createElement("span"); icon.className = `${ROOT_CLASS$1}__icon dashicons ${seg.icon.replace(/[^a-zA-Z0-9_-]/g, "")}`; icon.setAttribute("aria-hidden", "true"); el.appendChild(icon); } const label = document.createElement("span"); label.className = `${ROOT_CLASS$1}__label`; label.textContent = seg.label; el.appendChild(label); return el; } function html(strings, ...values) { return { __wpdHtml: true, strings, values }; } function isTemplateResult(v) { return !!v && v.__wpdHtml === true; } const MARKER_PREFIX = "$$wpd$$"; const MARKER_RE = /\$\$wpd\$\$(\d+)\$\$/g; function joinWithMarkers(strings) { let out = strings[0]; for (let i = 1; i < strings.length; i++) { out += `${MARKER_PREFIX}${i - 1}$$` + strings[i]; } return out; } const compiledCache = /* @__PURE__ */ new WeakMap(); function compile(strings) { const cached = compiledCache.get(strings); if (cached) { return cached; } const template = document.createElement("template"); template.innerHTML = joinWithMarkers(strings); const recipes = []; const walk = (node, path) => { if (node.nodeType === Node.ELEMENT_NODE) { const el = node; for (const attr of Array.from(el.attributes)) { const rawName = attr.name; const rawValue = attr.value; const prefix = rawName[0]; if (MARKER_RE.test(rawValue)) { MARKER_RE.lastIndex = 0; if (prefix === "@") { const match = MARKER_RE.exec(rawValue); MARKER_RE.lastIndex = 0; recipes.push({ path, kind: "event", name: rawName.slice(1), valueIndex: match ? Number(match[1]) : 0 }); el.removeAttribute(rawName); } else if (prefix === ".") { const match = MARKER_RE.exec(rawValue); MARKER_RE.lastIndex = 0; recipes.push({ path, kind: "prop", name: rawName.slice(1), valueIndex: match ? Number(match[1]) : 0 }); el.removeAttribute(rawName); } else if (prefix === "?") { const match = MARKER_RE.exec(rawValue); MARKER_RE.lastIndex = 0; recipes.push({ path, kind: "bool", name: rawName.slice(1), valueIndex: match ? Number(match[1]) : 0 }); el.removeAttribute(rawName); } else { const fragments = []; const indices = []; let lastEnd = 0; let m; MARKER_RE.lastIndex = 0; while ((m = MARKER_RE.exec(rawValue)) !== null) { fragments.push(rawValue.slice(lastEnd, m.index)); indices.push(Number(m[1])); lastEnd = m.index + m[0].length; } fragments.push(rawValue.slice(lastEnd)); recipes.push({ path, kind: "attr", name: rawName, template: fragments, valueIndices: indices }); el.setAttribute(rawName, ""); } } } } const children = Array.from(node.childNodes); let shift = 0; for (let i = 0; i < children.length; i++) { const child = children[i]; const liveIndex = i + shift; if (child.nodeType === Node.TEXT_NODE) { const text = child.textContent || ""; if (!MARKER_RE.test(text)) { MARKER_RE.lastIndex = 0; continue; } MARKER_RE.lastIndex = 0; const parent = child.parentNode; let lastEnd = 0; let m; const newNodes = []; const newRecipes = []; MARKER_RE.lastIndex = 0; while ((m = MARKER_RE.exec(text)) !== null) { if (m.index > lastEnd) { newNodes.push(document.createTextNode(text.slice(lastEnd, m.index))); } const placeholder = document.createTextNode(""); newNodes.push(placeholder); newRecipes.push({ path: [...path, liveIndex + newNodes.length - 1], kind: "node", valueIndex: Number(m[1]) }); lastEnd = m.index + m[0].length; } if (lastEnd < text.length) { newNodes.push(document.createTextNode(text.slice(lastEnd))); } for (const nn of newNodes) { parent.insertBefore(nn, child); } parent.removeChild(child); shift += newNodes.length - 1; recipes.push(...newRecipes); } else { walk(child, [...path, liveIndex]); } } }; walk(template.content, []); const buildParts = (fragment) => { const out = []; for (const r of recipes) { let node = fragment; for (const idx of r.path) { node = node.childNodes[idx]; } if (r.kind === "node") { out.push({ kind: "node", valueIndex: r.valueIndex, child: { anchor: node, state: null } }); } else if (r.kind === "attr") { out.push({ kind: "attr", element: node, name: r.name, template: r.template, valueIndices: r.valueIndices }); } else if (r.kind === "event") { out.push({ kind: "event", valueIndex: r.valueIndex, element: node, name: r.name }); } else if (r.kind === "prop") { out.push({ kind: "prop", valueIndex: r.valueIndex, element: node, name: r.name }); } else if (r.kind === "bool") { out.push({ kind: "bool", valueIndex: r.valueIndex, element: node, name: r.name }); } } return out; }; const entry = { template, buildParts }; compiledCache.set(strings, entry); return entry; } const mountState = /* @__PURE__ */ new WeakMap(); function render(result, container) { const existing = mountState.get(container); if (existing && existing.strings === result.strings) { applyValues(existing.parts, result.values); return; } const compiled = compile(result.strings); const fragment = compiled.template.content.cloneNode(true); const parts = compiled.buildParts(fragment); while (container.firstChild) { container.removeChild(container.firstChild); } container.appendChild(fragment); applyValues(parts, result.values); mountState.set(container, { strings: result.strings, parts }); } function applyValues(parts, values) { for (const part of parts) { if (part.kind === "node") { updateChildPart(part.child, values[part.valueIndex]); } else if (part.kind === "attr") { let composed = part.template[0]; for (let i = 0; i < part.valueIndices.length; i++) { composed += formatText(values[part.valueIndices[i]]); composed += part.template[i + 1]; } if (composed !== part.last) { part.last = composed; if (composed === "") { part.element.removeAttribute(part.name); } else { part.element.setAttribute(part.name, composed); } } } else if (part.kind === "event") { const next = values[part.valueIndex]; if (next !== part.current) { if (part.current) { part.element.removeEventListener(part.name, part.current); } if (next) { part.element.addEventListener(part.name, next); } part.current = next; } } else if (part.kind === "prop") { const next = values[part.valueIndex]; if (next !== part.last) { part.last = next; part.element[part.name] = next; } } else if (part.kind === "bool") { const next = !!values[part.valueIndex]; if (next !== part.last) { part.last = next; if (next) { part.element.setAttribute(part.name, ""); } else { part.element.removeAttribute(part.name); } } } } } function updateChildPart(child, value) { if (value === null || value === void 0 || value === false) { if (child.state) { disposeChildState(child.state); child.state = null; } return; } if (Array.isArray(value)) { updateArrayChild(child, value); return; } if (isTemplateResult(value)) { updateTemplateChild(child, value); return; } if (value instanceof Node) { updateNodeChild(child, value); return; } updateTextChild(child, formatText(value)); } function updateNodeChild(child, node) { const old = child.state; if (old?.shape === "node" && old.node === node) { return; } if (old) { disposeChildState(old); } insertBeforeAnchor(child, [node]); child.state = { shape: "node", node }; } function updateTextChild(child, text) { const old = child.state; if (old?.shape === "text") { if (old.text !== text) { old.node.textContent = text; old.text = text; } return; } if (old) { disposeChildState(old); } const node = document.createTextNode(text); insertBeforeAnchor(child, [node]); child.state = { shape: "text", node, text }; } function updateTemplateChild(child, result) { const old = child.state; if (old?.shape === "template" && old.strings === result.strings) { applyValues(old.parts, result.values); return; } if (old) { disposeChildState(old); } const compiled = compile(result.strings); const fragment = compiled.template.content.cloneNode(true); const parts = compiled.buildParts(fragment); const topNodes = Array.from(fragment.childNodes); insertBeforeAnchor(child, [fragment]); applyValues(parts, result.values); child.state = { shape: "template", strings: result.strings, parts, nodes: topNodes }; } function updateArrayChild(child, arr) { const old = child.state; if (old?.shape === "array" && old.entries.length === arr.length) { for (let i = 0; i < arr.length; i++) { updateChildPart(old.entries[i], arr[i]); } return; } if (old) { disposeChildState(old); } const entries = []; for (const v of arr) { const entryAnchor = document.createTextNode(""); insertBeforeAnchor(child, [entryAnchor]); const entry = { anchor: entryAnchor, state: null }; updateChildPart(entry, v); entries.push(entry); } child.state = { shape: "array", entries }; } function insertBeforeAnchor(child, nodes) { const parent = child.anchor.parentNode; if (!parent) { return; } for (const node of nodes) { parent.insertBefore(node, child.anchor); } } function disposeChildState(state) { if (state.shape === "text") { state.node.remove(); return; } if (state.shape === "template") { for (const node of state.nodes) { if (node.parentNode) { node.parentNode.removeChild(node); } } return; } if (state.shape === "node") { if (state.node.parentNode) { state.node.parentNode.removeChild(state.node); } return; } for (const entry of state.entries) { if (entry.state) { disposeChildState(entry.state); } entry.anchor.remove(); } } function formatText(v) { if (v === null || v === void 0 || v === false) { return ""; } return String(v); } const _Component = class _Component extends HTMLElement { constructor() { super(); this._renderScheduled = false; this._propValues = {}; const ctor = this.constructor; if (ctor.shadow) { this.attachShadow({ mode: "open" }); this._renderRoot = this.shadowRoot; } else { this._renderRoot = this; } this._installPropAccessors(); } static get observedAttributes() { return this.props.map(kebab); } connectedCallback() { this._adoptStyles(); this.requestUpdate(); } attributeChangedCallback(name, oldValue, newValue) { if (oldValue === newValue) { return; } const prop = camel(name); this._propValues[prop] = newValue; this.requestUpdate(); } /** * Declarative class-name setter. Assign an array (or a * space-separated string) and the host's `class` attribute is * rewritten to match. Intended for programmatic styling — when * a plugin has enqueued its own stylesheet and wants to apply * one of those classes to a shell component: * * ```js * element.classNames = [ 'my-plugin-brand', 'is-active' ]; * // → * ``` * * The plain HTML `class="…"` attribute works just the same and * is always preferred when writing markup by hand — this setter * exists for the JS-API case where the caller has an array of * conditional classes in hand. * * Getter returns the current `classList` as a plain array for * symmetric read/write. * * @since 0.13.0 */ get classNames() { return Array.from(this.classList); } set classNames(next) { if (next === null || next === void 0) { this.removeAttribute("class"); return; } const list = Array.isArray(next) ? next : String(next).split(/\s+/); const cleaned = list.map((s) => String(s).trim()).filter((s) => s !== ""); this.className = cleaned.join(" "); } /** * Request a re-render explicitly. Components rarely need this — * declare state via props + attribute observers and the render * loop picks up changes automatically. */ requestUpdate() { this._scheduleRender(); } /** * Dispatch a `CustomEvent` with a `detail`. Bubbles + composed * by default (matches typical WC UX — events cross shadow * boundaries, parents can listen without knowing about internal * structure). */ emit(name, detail) { return this.dispatchEvent( new CustomEvent(name, { detail, bubbles: true, composed: true }) ); } // ------------------------------------------------------------------ // Internals // ------------------------------------------------------------------ /** * Wire every `static props` entry to a matched property getter + * setter on the element. Setting the property reflects into the * attribute (so downstream observers + CSS selectors see it); * reading the property falls back to the attribute. */ _installPropAccessors() { const ctor = this.constructor; for (const prop of ctor.props) { if (Object.getOwnPropertyDescriptor(this, prop)) { continue; } const attr = kebab(prop); Object.defineProperty(this, prop, { get: () => { if (prop in this._propValues) { return this._propValues[prop]; } return this.getAttribute(attr); }, set: (value) => { let str; if (value === null || value === void 0 || value === false) { str = null; } else if (value === true) { str = ""; } else { str = String(value); } this._propValues[prop] = str; if (str === null) { this.removeAttribute(attr); } else { this.setAttribute(attr, str); } this.requestUpdate(); }, enumerable: true, configurable: true }); } } /** * Schedule a render on the next microtask. Multiple property * assignments in the same tick collapse into a single render. */ _scheduleRender() { if (this._renderScheduled || !this.isConnected) { return; } this._renderScheduled = true; queueMicrotask(() => { this._renderScheduled = false; if (!this.isConnected) { return; } render(this.render(), this._renderRoot); }); } /** * Mount adoptable stylesheets onto the shadow root (via * `adoptedStyleSheets`) or the light DOM (via one `