| 1 |
(function() { |
| 2 |
"use strict"; |
| 3 |
const TEXT_DOMAIN = "desktop-mode"; |
| 4 |
function i18n() { |
| 5 |
return window.wp?.i18n; |
| 6 |
} |
| 7 |
function __(text, domain = TEXT_DOMAIN) { |
| 8 |
return i18n()?.__(text, domain) ?? text; |
| 9 |
} |
| 10 |
function _n(single, plural, number, domain = TEXT_DOMAIN) { |
| 11 |
return i18n()?._n(single, plural, number, domain) ?? (number === 1 ? single : plural); |
| 12 |
} |
| 13 |
function sprintf(format, ...args) { |
| 14 |
const impl = i18n()?.sprintf; |
| 15 |
if (impl) { |
| 16 |
return impl(format, ...args); |
| 17 |
} |
| 18 |
let i = 0; |
| 19 |
return format.replace(/%[sd]/g, () => String(args[i++] ?? "")); |
| 20 |
} |
| 21 |
function getWpHooks() { |
| 22 |
const hooks = window.wp?.hooks; |
| 23 |
if (!hooks) { |
| 24 |
throw new Error( |
| 25 |
"[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." |
| 26 |
); |
| 27 |
} |
| 28 |
return hooks; |
| 29 |
} |
| 30 |
function addAction(hookName2, namespace, callback2, priority) { |
| 31 |
getWpHooks().addAction( |
| 32 |
hookName2, |
| 33 |
namespace, |
| 34 |
callback2, |
| 35 |
priority |
| 36 |
); |
| 37 |
} |
| 38 |
function removeAction(hookName2, namespace) { |
| 39 |
return getWpHooks().removeAction(hookName2, namespace); |
| 40 |
} |
| 41 |
function applyFilters(hookName2, value, ...args) { |
| 42 |
return getWpHooks().applyFilters(hookName2, value, ...args); |
| 43 |
} |
| 44 |
function doAction(hookName2, ...args) { |
| 45 |
getWpHooks().doAction(hookName2, ...args); |
| 46 |
} |
| 47 |
const CANARY_TAG = "wpd-confirm-dialog"; |
| 48 |
let inflight = null; |
| 49 |
function isLoaded() { |
| 50 |
return typeof window.customElements !== "undefined" && !!window.customElements.get(CANARY_TAG); |
| 51 |
} |
| 52 |
function injectScript(scriptUrl) { |
| 53 |
return new Promise((resolve, reject) => { |
| 54 |
const existing = document.querySelector( |
| 55 |
'script[data-desktop-mode-shell-overlays="1"]' |
| 56 |
); |
| 57 |
const finish = () => { |
| 58 |
if (isLoaded()) { |
| 59 |
resolve(); |
| 60 |
return; |
| 61 |
} |
| 62 |
reject( |
| 63 |
new Error( |
| 64 |
"[desktop-mode] shell-overlays bundle loaded but did not register the overlay components." |
| 65 |
) |
| 66 |
); |
| 67 |
}; |
| 68 |
if (existing) { |
| 69 |
if (isLoaded()) { |
| 70 |
finish(); |
| 71 |
} else { |
| 72 |
existing.addEventListener("load", finish); |
| 73 |
existing.addEventListener( |
| 74 |
"error", |
| 75 |
() => reject(new Error("failed to load shell-overlays bundle")) |
| 76 |
); |
| 77 |
} |
| 78 |
return; |
| 79 |
} |
| 80 |
const s = document.createElement("script"); |
| 81 |
s.src = scriptUrl; |
| 82 |
s.async = true; |
| 83 |
s.dataset.desktopModeShellOverlays = "1"; |
| 84 |
s.addEventListener("load", finish); |
| 85 |
s.addEventListener( |
| 86 |
"error", |
| 87 |
() => reject(new Error("failed to load shell-overlays bundle")) |
| 88 |
); |
| 89 |
document.head.appendChild(s); |
| 90 |
}); |
| 91 |
} |
| 92 |
function ensureShellOverlaysLoaded(scriptUrl) { |
| 93 |
if (isLoaded()) { |
| 94 |
return Promise.resolve(); |
| 95 |
} |
| 96 |
if (!scriptUrl) { |
| 97 |
return Promise.resolve(); |
| 98 |
} |
| 99 |
if (!inflight) { |
| 100 |
inflight = injectScript(scriptUrl); |
| 101 |
} |
| 102 |
return inflight; |
| 103 |
} |
| 104 |
function shellOverlaysBundleUrl() { |
| 105 |
const cfg = window.desktopModeConfig; |
| 106 |
return cfg?.shellOverlaysBundleUrl ?? ""; |
| 107 |
} |
| 108 |
function openWithShellOverlays(isStillCurrent, fn) { |
| 109 |
const url = shellOverlaysBundleUrl(); |
| 110 |
if (isLoaded() || !url) { |
| 111 |
fn(); |
| 112 |
return; |
| 113 |
} |
| 114 |
void ensureShellOverlaysLoaded(url).then(() => { |
| 115 |
if (!isStillCurrent()) { |
| 116 |
return; |
| 117 |
} |
| 118 |
fn(); |
| 119 |
}).catch((err) => { |
| 120 |
if (typeof console !== "undefined") { |
| 121 |
console.warn( |
| 122 |
"[desktop-mode] shell-overlays failed to load; menu/dialog suppressed:", |
| 123 |
err |
| 124 |
); |
| 125 |
} |
| 126 |
}); |
| 127 |
} |
| 128 |
const MENU_CLASS = "desktop-mode-icon-canvas-menu"; |
| 129 |
let activeMenu = null; |
| 130 |
let activeFlyout = null; |
| 131 |
let activeCanvas = null; |
| 132 |
let outsideHandler = null; |
| 133 |
let escHandler = null; |
| 134 |
function attachIconCanvasMenu(canvas, deps) { |
| 135 |
deps.openOnBackgroundClick !== false; |
| 136 |
const onContextMenu = (e) => { |
| 137 |
if (isInsideTile(e.target) || isInsideMenu(e.target)) { |
| 138 |
return; |
| 139 |
} |
| 140 |
e.preventDefault(); |
| 141 |
toggle(e.clientX, e.clientY); |
| 142 |
}; |
| 143 |
let toggleGen = 0; |
| 144 |
const toggle = (x, y) => { |
| 145 |
if (activeCanvas === canvas && activeMenu) { |
| 146 |
closeMenu(); |
| 147 |
return; |
| 148 |
} |
| 149 |
const items = buildItems(deps); |
| 150 |
const filtered = applyFilters( |
| 151 |
"desktop-mode.icon-canvas.menu", |
| 152 |
items, |
| 153 |
deps.scope |
| 154 |
); |
| 155 |
const finalItems = Array.isArray(filtered) ? filtered : items; |
| 156 |
const myGen = ++toggleGen; |
| 157 |
openWithShellOverlays( |
| 158 |
() => myGen === toggleGen, |
| 159 |
() => openMenu(finalItems, { x, y }, canvas) |
| 160 |
); |
| 161 |
}; |
| 162 |
canvas.addEventListener("contextmenu", onContextMenu); |
| 163 |
return { |
| 164 |
dispose: () => { |
| 165 |
canvas.removeEventListener("contextmenu", onContextMenu); |
| 166 |
closeMenu(); |
| 167 |
} |
| 168 |
}; |
| 169 |
} |
| 170 |
function isInsideTile(target) { |
| 171 |
if (!(target instanceof Element)) { |
| 172 |
return false; |
| 173 |
} |
| 174 |
return target.closest(".desktop-mode-file-tile") !== null; |
| 175 |
} |
| 176 |
function isInsideMenu(target) { |
| 177 |
if (!(target instanceof Element)) { |
| 178 |
return false; |
| 179 |
} |
| 180 |
return target.closest(`.${MENU_CLASS}`) !== null; |
| 181 |
} |
| 182 |
function buildItems(deps) { |
| 183 |
const sortItem = { |
| 184 |
id: "sort-by", |
| 185 |
label: __("Sort by", "desktop-mode"), |
| 186 |
icon: "dashicons-sort", |
| 187 |
sort: 10, |
| 188 |
children: [ |
| 189 |
{ |
| 190 |
id: "sort-name-asc", |
| 191 |
label: __("Name (A → Z)", "desktop-mode"), |
| 192 |
sort: 10, |
| 193 |
onClick: () => deps.onSort("name-asc") |
| 194 |
}, |
| 195 |
{ |
| 196 |
id: "sort-name-desc", |
| 197 |
label: __("Name (Z → A)", "desktop-mode"), |
| 198 |
sort: 20, |
| 199 |
onClick: () => deps.onSort("name-desc") |
| 200 |
}, |
| 201 |
{ |
| 202 |
id: "sort-date-desc", |
| 203 |
label: __("Newest first", "desktop-mode"), |
| 204 |
sort: 30, |
| 205 |
onClick: () => deps.onSort("date-desc") |
| 206 |
}, |
| 207 |
{ |
| 208 |
id: "sort-date-asc", |
| 209 |
label: __("Oldest first", "desktop-mode"), |
| 210 |
sort: 40, |
| 211 |
onClick: () => deps.onSort("date-asc") |
| 212 |
} |
| 213 |
] |
| 214 |
}; |
| 215 |
const items = [sortItem]; |
| 216 |
if (Array.isArray(deps.extraItems)) { |
| 217 |
items.push(...deps.extraItems); |
| 218 |
} |
| 219 |
return items; |
| 220 |
} |
| 221 |
function sortItems(items) { |
| 222 |
return items.slice().sort((a, b) => { |
| 223 |
const sa = typeof a.sort === "number" ? a.sort : 100; |
| 224 |
const sb = typeof b.sort === "number" ? b.sort : 100; |
| 225 |
if (sa !== sb) { |
| 226 |
return sa - sb; |
| 227 |
} |
| 228 |
return a.label.localeCompare(b.label); |
| 229 |
}); |
| 230 |
} |
| 231 |
function openMenu(items, pos, canvas) { |
| 232 |
closeMenu(); |
| 233 |
if (items.length === 0) { |
| 234 |
return; |
| 235 |
} |
| 236 |
activeCanvas = canvas; |
| 237 |
const sorted = sortItems(items); |
| 238 |
const menu = document.createElement("wpd-context-menu"); |
| 239 |
menu.setAttribute("open", ""); |
| 240 |
menu.classList.add(MENU_CLASS); |
| 241 |
menu.style.left = `${pos.x}px`; |
| 242 |
menu.style.top = `${pos.y}px`; |
| 243 |
const itemById = /* @__PURE__ */ new Map(); |
| 244 |
for (const item of sorted) { |
| 245 |
itemById.set(item.id, item); |
| 246 |
const opt = appendOption(menu, item); |
| 247 |
if (hasChildren(item)) { |
| 248 |
opt.addEventListener("mouseenter", () => { |
| 249 |
openFlyout(item, opt); |
| 250 |
}); |
| 251 |
} |
| 252 |
} |
| 253 |
menu.addEventListener("wpd-context-menu-pick", (e) => { |
| 254 |
const detail = e.detail; |
| 255 |
const item = itemById.get(detail.id); |
| 256 |
if (!item) { |
| 257 |
return; |
| 258 |
} |
| 259 |
if (hasChildren(item)) { |
| 260 |
e.stopPropagation(); |
| 261 |
const anchor = menu.querySelector( |
| 262 |
`[data-menu-item-id="${item.id}"]` |
| 263 |
); |
| 264 |
if (anchor) { |
| 265 |
openFlyout(item, anchor); |
| 266 |
} |
| 267 |
return; |
| 268 |
} |
| 269 |
closeMenu(); |
| 270 |
item.onClick?.(); |
| 271 |
}); |
| 272 |
document.body.appendChild(menu); |
| 273 |
activeMenu = menu; |
| 274 |
clampToViewport(menu); |
| 275 |
queueMicrotask(() => { |
| 276 |
outsideHandler = (e) => { |
| 277 |
if (isInsideMenu(e.target)) { |
| 278 |
return; |
| 279 |
} |
| 280 |
closeMenu(); |
| 281 |
}; |
| 282 |
escHandler = (e) => { |
| 283 |
if (e.key === "Escape") { |
| 284 |
closeMenu(); |
| 285 |
} |
| 286 |
}; |
| 287 |
document.addEventListener("mousedown", outsideHandler); |
| 288 |
document.addEventListener("keydown", escHandler); |
| 289 |
}); |
| 290 |
} |
| 291 |
function appendOption(host, item) { |
| 292 |
const opt = document.createElement("wpd-context-menu-option"); |
| 293 |
opt.dataset.menuItemId = item.id; |
| 294 |
opt.setAttribute("value", item.id); |
| 295 |
if (item.heading) { |
| 296 |
opt.setAttribute("heading", ""); |
| 297 |
} |
| 298 |
if (item.disabled) { |
| 299 |
opt.setAttribute("disabled", ""); |
| 300 |
} |
| 301 |
if (item.icon) { |
| 302 |
opt.setAttribute("icon", sanitizeClass$1(item.icon)); |
| 303 |
} |
| 304 |
if (hasChildren(item)) { |
| 305 |
opt.setAttribute("has-children", ""); |
| 306 |
} |
| 307 |
opt.textContent = item.label; |
| 308 |
host.appendChild(opt); |
| 309 |
return opt; |
| 310 |
} |
| 311 |
function openFlyout(parent, anchor) { |
| 312 |
closeFlyout(); |
| 313 |
if (!hasChildren(parent)) { |
| 314 |
return; |
| 315 |
} |
| 316 |
const fly = document.createElement("wpd-context-menu"); |
| 317 |
fly.setAttribute("open", ""); |
| 318 |
fly.classList.add(MENU_CLASS, `${MENU_CLASS}--flyout`); |
| 319 |
const childById = /* @__PURE__ */ new Map(); |
| 320 |
for (const child of sortItems(parent.children ?? [])) { |
| 321 |
childById.set(child.id, child); |
| 322 |
appendOption(fly, child); |
| 323 |
} |
| 324 |
fly.addEventListener("wpd-context-menu-pick", (e) => { |
| 325 |
const detail = e.detail; |
| 326 |
const child = childById.get(detail.id); |
| 327 |
if (!child) { |
| 328 |
return; |
| 329 |
} |
| 330 |
e.stopPropagation(); |
| 331 |
closeMenu(); |
| 332 |
child.onClick?.(); |
| 333 |
}); |
| 334 |
document.body.appendChild(fly); |
| 335 |
activeFlyout = fly; |
| 336 |
positionFlyout(fly, anchor); |
| 337 |
} |
| 338 |
function positionFlyout(fly, anchor) { |
| 339 |
const ar = anchor.getBoundingClientRect(); |
| 340 |
fly.style.position = "fixed"; |
| 341 |
fly.style.left = `${ar.right}px`; |
| 342 |
fly.style.top = `${ar.top}px`; |
| 343 |
const fr = fly.getBoundingClientRect(); |
| 344 |
if (fr.right > window.innerWidth) { |
| 345 |
fly.style.left = `${Math.max(0, ar.left - fr.width)}px`; |
| 346 |
} |
| 347 |
if (fr.bottom > window.innerHeight) { |
| 348 |
fly.style.top = `${Math.max(0, window.innerHeight - fr.height - 8)}px`; |
| 349 |
} |
| 350 |
} |
| 351 |
function clampToViewport(menu) { |
| 352 |
const rect = menu.getBoundingClientRect(); |
| 353 |
if (rect.right > window.innerWidth) { |
| 354 |
menu.style.left = `${Math.max(0, window.innerWidth - rect.width - 8)}px`; |
| 355 |
} |
| 356 |
if (rect.bottom > window.innerHeight) { |
| 357 |
menu.style.top = `${Math.max(0, window.innerHeight - rect.height - 8)}px`; |
| 358 |
} |
| 359 |
} |
| 360 |
function hasChildren(item) { |
| 361 |
return Array.isArray(item.children) && item.children.length > 0; |
| 362 |
} |
| 363 |
function closeFlyout() { |
| 364 |
if (activeFlyout) { |
| 365 |
activeFlyout.remove(); |
| 366 |
activeFlyout = null; |
| 367 |
} |
| 368 |
} |
| 369 |
function closeMenu() { |
| 370 |
closeFlyout(); |
| 371 |
if (activeMenu) { |
| 372 |
activeMenu.remove(); |
| 373 |
activeMenu = null; |
| 374 |
} |
| 375 |
activeCanvas = null; |
| 376 |
if (outsideHandler) { |
| 377 |
document.removeEventListener("mousedown", outsideHandler); |
| 378 |
outsideHandler = null; |
| 379 |
} |
| 380 |
if (escHandler) { |
| 381 |
document.removeEventListener("keydown", escHandler); |
| 382 |
escHandler = null; |
| 383 |
} |
| 384 |
} |
| 385 |
function sanitizeClass$1(raw) { |
| 386 |
return raw.replace(/[^a-zA-Z0-9_-]/g, ""); |
| 387 |
} |
| 388 |
const STATUS_BAR_CLASS = "desktop-mode-folder-status-bar"; |
| 389 |
const ROOT_CLASS$1 = STATUS_BAR_CLASS; |
| 390 |
function renderStatusBarSegments(bar, segments) { |
| 391 |
render$1(bar, segments); |
| 392 |
} |
| 393 |
function render$1(bar, segments) { |
| 394 |
const sort = (a, b) => { |
| 395 |
const sa = typeof a.sort === "number" ? a.sort : 100; |
| 396 |
const sb = typeof b.sort === "number" ? b.sort : 100; |
| 397 |
if (sa !== sb) { |
| 398 |
return sa - sb; |
| 399 |
} |
| 400 |
return a.label.localeCompare(b.label); |
| 401 |
}; |
| 402 |
const start = segments.filter((s) => (s.align ?? "start") === "start").sort(sort); |
| 403 |
const end = segments.filter((s) => s.align === "end").sort(sort); |
| 404 |
bar.replaceChildren(); |
| 405 |
bar.appendChild(buildCluster("start", start)); |
| 406 |
bar.appendChild(buildCluster("end", end)); |
| 407 |
} |
| 408 |
function buildCluster(align, segs) { |
| 409 |
const cluster = document.createElement("div"); |
| 410 |
cluster.className = `${ROOT_CLASS$1}__cluster ${ROOT_CLASS$1}__cluster--${align}`; |
| 411 |
for (const seg of segs) { |
| 412 |
cluster.appendChild(buildSegment(seg)); |
| 413 |
} |
| 414 |
return cluster; |
| 415 |
} |
| 416 |
function buildSegment(seg) { |
| 417 |
const interactive = typeof seg.onClick === "function"; |
| 418 |
const el = document.createElement(interactive ? "button" : "span"); |
| 419 |
el.className = `${ROOT_CLASS$1}__segment`; |
| 420 |
el.dataset.segmentId = seg.id; |
| 421 |
if (interactive) { |
| 422 |
el.type = "button"; |
| 423 |
el.addEventListener("click", (e) => seg.onClick(e)); |
| 424 |
} |
| 425 |
if (seg.icon) { |
| 426 |
const icon = document.createElement("span"); |
| 427 |
icon.className = `${ROOT_CLASS$1}__icon dashicons ${seg.icon.replace(/[^a-zA-Z0-9_-]/g, "")}`; |
| 428 |
icon.setAttribute("aria-hidden", "true"); |
| 429 |
el.appendChild(icon); |
| 430 |
} |
| 431 |
const label = document.createElement("span"); |
| 432 |
label.className = `${ROOT_CLASS$1}__label`; |
| 433 |
label.textContent = seg.label; |
| 434 |
el.appendChild(label); |
| 435 |
return el; |
| 436 |
} |
| 437 |
function html(strings, ...values) { |
| 438 |
return { __wpdHtml: true, strings, values }; |
| 439 |
} |
| 440 |
function isTemplateResult(v) { |
| 441 |
return !!v && v.__wpdHtml === true; |
| 442 |
} |
| 443 |
const MARKER_PREFIX = "$$wpd$$"; |
| 444 |
const MARKER_RE = /\$\$wpd\$\$(\d+)\$\$/g; |
| 445 |
function joinWithMarkers(strings) { |
| 446 |
let out = strings[0]; |
| 447 |
for (let i = 1; i < strings.length; i++) { |
| 448 |
out += `${MARKER_PREFIX}${i - 1}$$` + strings[i]; |
| 449 |
} |
| 450 |
return out; |
| 451 |
} |
| 452 |
const compiledCache = /* @__PURE__ */ new WeakMap(); |
| 453 |
function compile(strings) { |
| 454 |
const cached = compiledCache.get(strings); |
| 455 |
if (cached) { |
| 456 |
return cached; |
| 457 |
} |
| 458 |
const template = document.createElement("template"); |
| 459 |
template.innerHTML = joinWithMarkers(strings); |
| 460 |
const recipes = []; |
| 461 |
const walk = (node, path) => { |
| 462 |
if (node.nodeType === Node.ELEMENT_NODE) { |
| 463 |
const el = node; |
| 464 |
for (const attr of Array.from(el.attributes)) { |
| 465 |
const rawName = attr.name; |
| 466 |
const rawValue = attr.value; |
| 467 |
const prefix = rawName[0]; |
| 468 |
if (MARKER_RE.test(rawValue)) { |
| 469 |
MARKER_RE.lastIndex = 0; |
| 470 |
if (prefix === "@") { |
| 471 |
const match = MARKER_RE.exec(rawValue); |
| 472 |
MARKER_RE.lastIndex = 0; |
| 473 |
recipes.push({ |
| 474 |
path, |
| 475 |
kind: "event", |
| 476 |
name: rawName.slice(1), |
| 477 |
valueIndex: match ? Number(match[1]) : 0 |
| 478 |
}); |
| 479 |
el.removeAttribute(rawName); |
| 480 |
} else if (prefix === ".") { |
| 481 |
const match = MARKER_RE.exec(rawValue); |
| 482 |
MARKER_RE.lastIndex = 0; |
| 483 |
recipes.push({ |
| 484 |
path, |
| 485 |
kind: "prop", |
| 486 |
name: rawName.slice(1), |
| 487 |
valueIndex: match ? Number(match[1]) : 0 |
| 488 |
}); |
| 489 |
el.removeAttribute(rawName); |
| 490 |
} else if (prefix === "?") { |
| 491 |
const match = MARKER_RE.exec(rawValue); |
| 492 |
MARKER_RE.lastIndex = 0; |
| 493 |
recipes.push({ |
| 494 |
path, |
| 495 |
kind: "bool", |
| 496 |
name: rawName.slice(1), |
| 497 |
valueIndex: match ? Number(match[1]) : 0 |
| 498 |
}); |
| 499 |
el.removeAttribute(rawName); |
| 500 |
} else { |
| 501 |
const fragments = []; |
| 502 |
const indices = []; |
| 503 |
let lastEnd = 0; |
| 504 |
let m; |
| 505 |
MARKER_RE.lastIndex = 0; |
| 506 |
while ((m = MARKER_RE.exec(rawValue)) !== null) { |
| 507 |
fragments.push(rawValue.slice(lastEnd, m.index)); |
| 508 |
indices.push(Number(m[1])); |
| 509 |
lastEnd = m.index + m[0].length; |
| 510 |
} |
| 511 |
fragments.push(rawValue.slice(lastEnd)); |
| 512 |
recipes.push({ |
| 513 |
path, |
| 514 |
kind: "attr", |
| 515 |
name: rawName, |
| 516 |
template: fragments, |
| 517 |
valueIndices: indices |
| 518 |
}); |
| 519 |
el.setAttribute(rawName, ""); |
| 520 |
} |
| 521 |
} |
| 522 |
} |
| 523 |
} |
| 524 |
const children = Array.from(node.childNodes); |
| 525 |
let shift = 0; |
| 526 |
for (let i = 0; i < children.length; i++) { |
| 527 |
const child = children[i]; |
| 528 |
const liveIndex = i + shift; |
| 529 |
if (child.nodeType === Node.TEXT_NODE) { |
| 530 |
const text = child.textContent || ""; |
| 531 |
if (!MARKER_RE.test(text)) { |
| 532 |
MARKER_RE.lastIndex = 0; |
| 533 |
continue; |
| 534 |
} |
| 535 |
MARKER_RE.lastIndex = 0; |
| 536 |
const parent = child.parentNode; |
| 537 |
let lastEnd = 0; |
| 538 |
let m; |
| 539 |
const newNodes = []; |
| 540 |
const newRecipes = []; |
| 541 |
MARKER_RE.lastIndex = 0; |
| 542 |
while ((m = MARKER_RE.exec(text)) !== null) { |
| 543 |
if (m.index > lastEnd) { |
| 544 |
newNodes.push(document.createTextNode(text.slice(lastEnd, m.index))); |
| 545 |
} |
| 546 |
const placeholder = document.createTextNode(""); |
| 547 |
newNodes.push(placeholder); |
| 548 |
newRecipes.push({ |
| 549 |
path: [...path, liveIndex + newNodes.length - 1], |
| 550 |
kind: "node", |
| 551 |
valueIndex: Number(m[1]) |
| 552 |
}); |
| 553 |
lastEnd = m.index + m[0].length; |
| 554 |
} |
| 555 |
if (lastEnd < text.length) { |
| 556 |
newNodes.push(document.createTextNode(text.slice(lastEnd))); |
| 557 |
} |
| 558 |
for (const nn of newNodes) { |
| 559 |
parent.insertBefore(nn, child); |
| 560 |
} |
| 561 |
parent.removeChild(child); |
| 562 |
shift += newNodes.length - 1; |
| 563 |
recipes.push(...newRecipes); |
| 564 |
} else { |
| 565 |
walk(child, [...path, liveIndex]); |
| 566 |
} |
| 567 |
} |
| 568 |
}; |
| 569 |
walk(template.content, []); |
| 570 |
const buildParts = (fragment) => { |
| 571 |
const out = []; |
| 572 |
for (const r of recipes) { |
| 573 |
let node = fragment; |
| 574 |
for (const idx of r.path) { |
| 575 |
node = node.childNodes[idx]; |
| 576 |
} |
| 577 |
if (r.kind === "node") { |
| 578 |
out.push({ |
| 579 |
kind: "node", |
| 580 |
valueIndex: r.valueIndex, |
| 581 |
child: { |
| 582 |
anchor: node, |
| 583 |
state: null |
| 584 |
} |
| 585 |
}); |
| 586 |
} else if (r.kind === "attr") { |
| 587 |
out.push({ |
| 588 |
kind: "attr", |
| 589 |
element: node, |
| 590 |
name: r.name, |
| 591 |
template: r.template, |
| 592 |
valueIndices: r.valueIndices |
| 593 |
}); |
| 594 |
} else if (r.kind === "event") { |
| 595 |
out.push({ |
| 596 |
kind: "event", |
| 597 |
valueIndex: r.valueIndex, |
| 598 |
element: node, |
| 599 |
name: r.name |
| 600 |
}); |
| 601 |
} else if (r.kind === "prop") { |
| 602 |
out.push({ |
| 603 |
kind: "prop", |
| 604 |
valueIndex: r.valueIndex, |
| 605 |
element: node, |
| 606 |
name: r.name |
| 607 |
}); |
| 608 |
} else if (r.kind === "bool") { |
| 609 |
out.push({ |
| 610 |
kind: "bool", |
| 611 |
valueIndex: r.valueIndex, |
| 612 |
element: node, |
| 613 |
name: r.name |
| 614 |
}); |
| 615 |
} |
| 616 |
} |
| 617 |
return out; |
| 618 |
}; |
| 619 |
const entry = { template, buildParts }; |
| 620 |
compiledCache.set(strings, entry); |
| 621 |
return entry; |
| 622 |
} |
| 623 |
const mountState = /* @__PURE__ */ new WeakMap(); |
| 624 |
function render(result, container) { |
| 625 |
const existing = mountState.get(container); |
| 626 |
if (existing && existing.strings === result.strings) { |
| 627 |
applyValues(existing.parts, result.values); |
| 628 |
return; |
| 629 |
} |
| 630 |
const compiled = compile(result.strings); |
| 631 |
const fragment = compiled.template.content.cloneNode(true); |
| 632 |
const parts = compiled.buildParts(fragment); |
| 633 |
while (container.firstChild) { |
| 634 |
container.removeChild(container.firstChild); |
| 635 |
} |
| 636 |
container.appendChild(fragment); |
| 637 |
applyValues(parts, result.values); |
| 638 |
mountState.set(container, { strings: result.strings, parts }); |
| 639 |
} |
| 640 |
function applyValues(parts, values) { |
| 641 |
for (const part of parts) { |
| 642 |
if (part.kind === "node") { |
| 643 |
updateChildPart(part.child, values[part.valueIndex]); |
| 644 |
} else if (part.kind === "attr") { |
| 645 |
let composed = part.template[0]; |
| 646 |
for (let i = 0; i < part.valueIndices.length; i++) { |
| 647 |
composed += formatText(values[part.valueIndices[i]]); |
| 648 |
composed += part.template[i + 1]; |
| 649 |
} |
| 650 |
if (composed !== part.last) { |
| 651 |
part.last = composed; |
| 652 |
if (composed === "") { |
| 653 |
part.element.removeAttribute(part.name); |
| 654 |
} else { |
| 655 |
part.element.setAttribute(part.name, composed); |
| 656 |
} |
| 657 |
} |
| 658 |
} else if (part.kind === "event") { |
| 659 |
const next = values[part.valueIndex]; |
| 660 |
if (next !== part.current) { |
| 661 |
if (part.current) { |
| 662 |
part.element.removeEventListener(part.name, part.current); |
| 663 |
} |
| 664 |
if (next) { |
| 665 |
part.element.addEventListener(part.name, next); |
| 666 |
} |
| 667 |
part.current = next; |
| 668 |
} |
| 669 |
} else if (part.kind === "prop") { |
| 670 |
const next = values[part.valueIndex]; |
| 671 |
if (next !== part.last) { |
| 672 |
part.last = next; |
| 673 |
part.element[part.name] = next; |
| 674 |
} |
| 675 |
} else if (part.kind === "bool") { |
| 676 |
const next = !!values[part.valueIndex]; |
| 677 |
if (next !== part.last) { |
| 678 |
part.last = next; |
| 679 |
if (next) { |
| 680 |
part.element.setAttribute(part.name, ""); |
| 681 |
} else { |
| 682 |
part.element.removeAttribute(part.name); |
| 683 |
} |
| 684 |
} |
| 685 |
} |
| 686 |
} |
| 687 |
} |
| 688 |
function updateChildPart(child, value) { |
| 689 |
if (value === null || value === void 0 || value === false) { |
| 690 |
if (child.state) { |
| 691 |
disposeChildState(child.state); |
| 692 |
child.state = null; |
| 693 |
} |
| 694 |
return; |
| 695 |
} |
| 696 |
if (Array.isArray(value)) { |
| 697 |
updateArrayChild(child, value); |
| 698 |
return; |
| 699 |
} |
| 700 |
if (isTemplateResult(value)) { |
| 701 |
updateTemplateChild(child, value); |
| 702 |
return; |
| 703 |
} |
| 704 |
if (value instanceof Node) { |
| 705 |
updateNodeChild(child, value); |
| 706 |
return; |
| 707 |
} |
| 708 |
updateTextChild(child, formatText(value)); |
| 709 |
} |
| 710 |
function updateNodeChild(child, node) { |
| 711 |
const old = child.state; |
| 712 |
if (old?.shape === "node" && old.node === node) { |
| 713 |
return; |
| 714 |
} |
| 715 |
if (old) { |
| 716 |
disposeChildState(old); |
| 717 |
} |
| 718 |
insertBeforeAnchor(child, [node]); |
| 719 |
child.state = { shape: "node", node }; |
| 720 |
} |
| 721 |
function updateTextChild(child, text) { |
| 722 |
const old = child.state; |
| 723 |
if (old?.shape === "text") { |
| 724 |
if (old.text !== text) { |
| 725 |
old.node.textContent = text; |
| 726 |
old.text = text; |
| 727 |
} |
| 728 |
return; |
| 729 |
} |
| 730 |
if (old) { |
| 731 |
disposeChildState(old); |
| 732 |
} |
| 733 |
const node = document.createTextNode(text); |
| 734 |
insertBeforeAnchor(child, [node]); |
| 735 |
child.state = { shape: "text", node, text }; |
| 736 |
} |
| 737 |
function updateTemplateChild(child, result) { |
| 738 |
const old = child.state; |
| 739 |
if (old?.shape === "template" && old.strings === result.strings) { |
| 740 |
applyValues(old.parts, result.values); |
| 741 |
return; |
| 742 |
} |
| 743 |
if (old) { |
| 744 |
disposeChildState(old); |
| 745 |
} |
| 746 |
const compiled = compile(result.strings); |
| 747 |
const fragment = compiled.template.content.cloneNode(true); |
| 748 |
const parts = compiled.buildParts(fragment); |
| 749 |
const topNodes = Array.from(fragment.childNodes); |
| 750 |
insertBeforeAnchor(child, [fragment]); |
| 751 |
applyValues(parts, result.values); |
| 752 |
child.state = { |
| 753 |
shape: "template", |
| 754 |
strings: result.strings, |
| 755 |
parts, |
| 756 |
nodes: topNodes |
| 757 |
}; |
| 758 |
} |
| 759 |
function updateArrayChild(child, arr) { |
| 760 |
const old = child.state; |
| 761 |
if (old?.shape === "array" && old.entries.length === arr.length) { |
| 762 |
for (let i = 0; i < arr.length; i++) { |
| 763 |
updateChildPart(old.entries[i], arr[i]); |
| 764 |
} |
| 765 |
return; |
| 766 |
} |
| 767 |
if (old) { |
| 768 |
disposeChildState(old); |
| 769 |
} |
| 770 |
const entries = []; |
| 771 |
for (const v of arr) { |
| 772 |
const entryAnchor = document.createTextNode(""); |
| 773 |
insertBeforeAnchor(child, [entryAnchor]); |
| 774 |
const entry = { anchor: entryAnchor, state: null }; |
| 775 |
updateChildPart(entry, v); |
| 776 |
entries.push(entry); |
| 777 |
} |
| 778 |
child.state = { shape: "array", entries }; |
| 779 |
} |
| 780 |
function insertBeforeAnchor(child, nodes) { |
| 781 |
const parent = child.anchor.parentNode; |
| 782 |
if (!parent) { |
| 783 |
return; |
| 784 |
} |
| 785 |
for (const node of nodes) { |
| 786 |
parent.insertBefore(node, child.anchor); |
| 787 |
} |
| 788 |
} |
| 789 |
function disposeChildState(state) { |
| 790 |
if (state.shape === "text") { |
| 791 |
state.node.remove(); |
| 792 |
return; |
| 793 |
} |
| 794 |
if (state.shape === "template") { |
| 795 |
for (const node of state.nodes) { |
| 796 |
if (node.parentNode) { |
| 797 |
node.parentNode.removeChild(node); |
| 798 |
} |
| 799 |
} |
| 800 |
return; |
| 801 |
} |
| 802 |
if (state.shape === "node") { |
| 803 |
if (state.node.parentNode) { |
| 804 |
state.node.parentNode.removeChild(state.node); |
| 805 |
} |
| 806 |
return; |
| 807 |
} |
| 808 |
for (const entry of state.entries) { |
| 809 |
if (entry.state) { |
| 810 |
disposeChildState(entry.state); |
| 811 |
} |
| 812 |
entry.anchor.remove(); |
| 813 |
} |
| 814 |
} |
| 815 |
function formatText(v) { |
| 816 |
if (v === null || v === void 0 || v === false) { |
| 817 |
return ""; |
| 818 |
} |
| 819 |
return String(v); |
| 820 |
} |
| 821 |
const _Component = class _Component extends HTMLElement { |
| 822 |
constructor() { |
| 823 |
super(); |
| 824 |
this._renderScheduled = false; |
| 825 |
this._propValues = {}; |
| 826 |
const ctor = this.constructor; |
| 827 |
if (ctor.shadow) { |
| 828 |
this.attachShadow({ mode: "open" }); |
| 829 |
this._renderRoot = this.shadowRoot; |
| 830 |
} else { |
| 831 |
this._renderRoot = this; |
| 832 |
} |
| 833 |
this._installPropAccessors(); |
| 834 |
} |
| 835 |
static get observedAttributes() { |
| 836 |
return this.props.map(kebab); |
| 837 |
} |
| 838 |
connectedCallback() { |
| 839 |
this._adoptStyles(); |
| 840 |
this.requestUpdate(); |
| 841 |
} |
| 842 |
attributeChangedCallback(name, oldValue, newValue) { |
| 843 |
if (oldValue === newValue) { |
| 844 |
return; |
| 845 |
} |
| 846 |
const prop = camel(name); |
| 847 |
this._propValues[prop] = newValue; |
| 848 |
this.requestUpdate(); |
| 849 |
} |
| 850 |
/** |
| 851 |
* Declarative class-name setter. Assign an array (or a |
| 852 |
* space-separated string) and the host's `class` attribute is |
| 853 |
* rewritten to match. Intended for programmatic styling — when |
| 854 |
* a plugin has enqueued its own stylesheet and wants to apply |
| 855 |
* one of those classes to a shell component: |
| 856 |
* |
| 857 |
* ```js |
| 858 |
* element.classNames = [ 'my-plugin-brand', 'is-active' ]; |
| 859 |
* // → <wpd-select class="my-plugin-brand is-active"> |
| 860 |
* ``` |
| 861 |
* |
| 862 |
* The plain HTML `class="…"` attribute works just the same and |
| 863 |
* is always preferred when writing markup by hand — this setter |
| 864 |
* exists for the JS-API case where the caller has an array of |
| 865 |
* conditional classes in hand. |
| 866 |
* |
| 867 |
* Getter returns the current `classList` as a plain array for |
| 868 |
* symmetric read/write. |
| 869 |
* |
| 870 |
* @since 0.13.0 |
| 871 |
*/ |
| 872 |
get classNames() { |
| 873 |
return Array.from(this.classList); |
| 874 |
} |
| 875 |
set classNames(next) { |
| 876 |
if (next === null || next === void 0) { |
| 877 |
this.removeAttribute("class"); |
| 878 |
return; |
| 879 |
} |
| 880 |
const list = Array.isArray(next) ? next : String(next).split(/\s+/); |
| 881 |
const cleaned = list.map((s) => String(s).trim()).filter((s) => s !== ""); |
| 882 |
this.className = cleaned.join(" "); |
| 883 |
} |
| 884 |
/** |
| 885 |
* Request a re-render explicitly. Components rarely need this — |
| 886 |
* declare state via props + attribute observers and the render |
| 887 |
* loop picks up changes automatically. |
| 888 |
*/ |
| 889 |
requestUpdate() { |
| 890 |
this._scheduleRender(); |
| 891 |
} |
| 892 |
/** |
| 893 |
* Dispatch a `CustomEvent` with a `detail`. Bubbles + composed |
| 894 |
* by default (matches typical WC UX — events cross shadow |
| 895 |
* boundaries, parents can listen without knowing about internal |
| 896 |
* structure). |
| 897 |
*/ |
| 898 |
emit(name, detail) { |
| 899 |
return this.dispatchEvent( |
| 900 |
new CustomEvent(name, { |
| 901 |
detail, |
| 902 |
bubbles: true, |
| 903 |
composed: true |
| 904 |
}) |
| 905 |
); |
| 906 |
} |
| 907 |
// ------------------------------------------------------------------ |
| 908 |
// Internals |
| 909 |
// ------------------------------------------------------------------ |
| 910 |
/** |
| 911 |
* Wire every `static props` entry to a matched property getter + |
| 912 |
* setter on the element. Setting the property reflects into the |
| 913 |
* attribute (so downstream observers + CSS selectors see it); |
| 914 |
* reading the property falls back to the attribute. |
| 915 |
*/ |
| 916 |
_installPropAccessors() { |
| 917 |
const ctor = this.constructor; |
| 918 |
for (const prop of ctor.props) { |
| 919 |
if (Object.getOwnPropertyDescriptor(this, prop)) { |
| 920 |
continue; |
| 921 |
} |
| 922 |
const attr = kebab(prop); |
| 923 |
Object.defineProperty(this, prop, { |
| 924 |
get: () => { |
| 925 |
if (prop in this._propValues) { |
| 926 |
return this._propValues[prop]; |
| 927 |
} |
| 928 |
return this.getAttribute(attr); |
| 929 |
}, |
| 930 |
set: (value) => { |
| 931 |
let str; |
| 932 |
if (value === null || value === void 0 || value === false) { |
| 933 |
str = null; |
| 934 |
} else if (value === true) { |
| 935 |
str = ""; |
| 936 |
} else { |
| 937 |
str = String(value); |
| 938 |
} |
| 939 |
this._propValues[prop] = str; |
| 940 |
if (str === null) { |
| 941 |
this.removeAttribute(attr); |
| 942 |
} else { |
| 943 |
this.setAttribute(attr, str); |
| 944 |
} |
| 945 |
this.requestUpdate(); |
| 946 |
}, |
| 947 |
enumerable: true, |
| 948 |
configurable: true |
| 949 |
}); |
| 950 |
} |
| 951 |
} |
| 952 |
/** |
| 953 |
* Schedule a render on the next microtask. Multiple property |
| 954 |
* assignments in the same tick collapse into a single render. |
| 955 |
*/ |
| 956 |
_scheduleRender() { |
| 957 |
if (this._renderScheduled || !this.isConnected) { |
| 958 |
return; |
| 959 |
} |
| 960 |
this._renderScheduled = true; |
| 961 |
queueMicrotask(() => { |
| 962 |
this._renderScheduled = false; |
| 963 |
if (!this.isConnected) { |
| 964 |
return; |
| 965 |
} |
| 966 |
render(this.render(), this._renderRoot); |
| 967 |
}); |
| 968 |
} |
| 969 |
/** |
| 970 |
* Mount adoptable stylesheets onto the shadow root (via |
| 971 |
* `adoptedStyleSheets`) or the light DOM (via one `<style>` |
| 972 |
* tag per def). No-op if `static styles` is empty. |
| 973 |
*/ |
| 974 |
_adoptStyles() { |
| 975 |
const ctor = this.constructor; |
| 976 |
if (ctor.styles.length === 0) { |
| 977 |
return; |
| 978 |
} |
| 979 |
if (ctor.shadow && this.shadowRoot) { |
| 980 |
const sheets = ctor.styles.map((s) => s.sheet).filter((s) => s !== null); |
| 981 |
this.shadowRoot.adoptedStyleSheets = sheets; |
| 982 |
if (sheets.length !== ctor.styles.length) { |
| 983 |
for (const s of ctor.styles) { |
| 984 |
if (!s.sheet) { |
| 985 |
const tag = document.createElement("style"); |
| 986 |
tag.textContent = s.cssText; |
| 987 |
this.shadowRoot.appendChild(tag); |
| 988 |
} |
| 989 |
} |
| 990 |
} |
| 991 |
} else { |
| 992 |
this._adoptLightStyles(ctor); |
| 993 |
} |
| 994 |
} |
| 995 |
_adoptLightStyles(ctor) { |
| 996 |
if (_Component._lightStylesAdopted.has(ctor)) { |
| 997 |
return; |
| 998 |
} |
| 999 |
_Component._lightStylesAdopted.add(ctor); |
| 1000 |
for (const s of ctor.styles) { |
| 1001 |
const tag = document.createElement("style"); |
| 1002 |
tag.dataset.wpdUi = this.tagName.toLowerCase(); |
| 1003 |
tag.textContent = s.cssText; |
| 1004 |
document.head.appendChild(tag); |
| 1005 |
} |
| 1006 |
} |
| 1007 |
}; |
| 1008 |
_Component.props = []; |
| 1009 |
_Component.styles = []; |
| 1010 |
_Component.shadow = true; |
| 1011 |
_Component._lightStylesAdopted = /* @__PURE__ */ new WeakSet(); |
| 1012 |
let Component = _Component; |
| 1013 |
function defineComponent(tag, ctor) { |
| 1014 |
if (customElements.get(tag)) { |
| 1015 |
return; |
| 1016 |
} |
| 1017 |
customElements.define(tag, ctor); |
| 1018 |
} |
| 1019 |
function kebab(s) { |
| 1020 |
return s.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase()); |
| 1021 |
} |
| 1022 |
function camel(s) { |
| 1023 |
return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); |
| 1024 |
} |
| 1025 |
const SUPPORTS_CONSTRUCTABLE_SHEETS = (() => { |
| 1026 |
try { |
| 1027 |
const s = new CSSStyleSheet(); |
| 1028 |
return typeof s.replaceSync === "function"; |
| 1029 |
} catch { |
| 1030 |
return false; |
| 1031 |
} |
| 1032 |
})(); |
| 1033 |
function css(strings, ...values) { |
| 1034 |
let text = strings[0]; |
| 1035 |
for (let i = 1; i < strings.length; i++) { |
| 1036 |
const v = values[i - 1]; |
| 1037 |
if (typeof v === "string" || typeof v === "number") { |
| 1038 |
text += String(v); |
| 1039 |
} else if (v && v.__wpdCss) { |
| 1040 |
text += v.cssText; |
| 1041 |
} else { |
| 1042 |
throw new TypeError( |
| 1043 |
"[wpd-ui] css`` interpolations must be strings, numbers, or other css`` results. Got: " + typeof v |
| 1044 |
); |
| 1045 |
} |
| 1046 |
text += strings[i]; |
| 1047 |
} |
| 1048 |
if (SUPPORTS_CONSTRUCTABLE_SHEETS) { |
| 1049 |
const sheet = new CSSStyleSheet(); |
| 1050 |
sheet.replaceSync(text); |
| 1051 |
return { __wpdCss: true, sheet, cssText: text }; |
| 1052 |
} |
| 1053 |
return { __wpdCss: true, sheet: null, cssText: text }; |
| 1054 |
} |
| 1055 |
function hashTitleToHue(input) { |
| 1056 |
if (!input) { |
| 1057 |
return 214; |
| 1058 |
} |
| 1059 |
let hash = 5381; |
| 1060 |
for (let i = 0; i < input.length; i++) { |
| 1061 |
hash = Math.imul(hash, 33) + input.charCodeAt(i); |
| 1062 |
} |
| 1063 |
return (hash % 360 + 360) % 360; |
| 1064 |
} |
| 1065 |
function renderIcon(icon, opts) { |
| 1066 |
const className = opts.className ?? ""; |
| 1067 |
const title = opts.title ?? ""; |
| 1068 |
if (typeof icon === "string" && icon.startsWith("dashicons-")) { |
| 1069 |
const el = document.createElement("span"); |
| 1070 |
el.className = `dashicons ${icon} ${className}`.trim(); |
| 1071 |
el.setAttribute("aria-hidden", "true"); |
| 1072 |
return el; |
| 1073 |
} |
| 1074 |
if (typeof icon === "string" && icon.startsWith("data:image/svg+xml;base64,")) { |
| 1075 |
const base64Part = icon.slice("data:image/svg+xml;base64,".length); |
| 1076 |
if (/^[A-Za-z0-9+/=]+$/.test(base64Part)) { |
| 1077 |
const el = document.createElement("span"); |
| 1078 |
el.className = className; |
| 1079 |
el.setAttribute("aria-hidden", "true"); |
| 1080 |
el.style.backgroundImage = `url("${icon}")`; |
| 1081 |
el.style.backgroundRepeat = "no-repeat"; |
| 1082 |
el.style.backgroundPosition = "center"; |
| 1083 |
el.style.backgroundSize = "contain"; |
| 1084 |
el.style.display = "inline-block"; |
| 1085 |
return el; |
| 1086 |
} |
| 1087 |
} |
| 1088 |
if (typeof icon === "string" && /^data:image\/(png|jpeg|jpg|gif|webp|x-icon|vnd\.microsoft\.icon);base64,/i.test(icon)) { |
| 1089 |
const commaIdx = icon.indexOf(","); |
| 1090 |
const payload = commaIdx >= 0 ? icon.slice(commaIdx + 1) : ""; |
| 1091 |
if (/^[A-Za-z0-9+/=]+$/.test(payload)) { |
| 1092 |
return makeImgIcon(icon, className); |
| 1093 |
} |
| 1094 |
} |
| 1095 |
if (typeof icon === "string" && (icon.startsWith("http://") || icon.startsWith("https://"))) { |
| 1096 |
return makeImgIcon(icon, className); |
| 1097 |
} |
| 1098 |
const span = document.createElement("span"); |
| 1099 |
span.className = `${className} desktop-mode-icon-letter`.trim(); |
| 1100 |
span.setAttribute("aria-hidden", "true"); |
| 1101 |
const letters = letterFromTitle(title); |
| 1102 |
span.textContent = letters; |
| 1103 |
const hue = hashTitleToHue(title); |
| 1104 |
span.style.backgroundColor = `hsl( ${hue}, 60%, 45% )`; |
| 1105 |
span.style.color = "#fff"; |
| 1106 |
span.style.display = "inline-flex"; |
| 1107 |
span.style.alignItems = "center"; |
| 1108 |
span.style.justifyContent = "center"; |
| 1109 |
span.style.fontWeight = "600"; |
| 1110 |
span.style.borderRadius = "4px"; |
| 1111 |
return span; |
| 1112 |
} |
| 1113 |
function makeImgIcon(src, className) { |
| 1114 |
const img = document.createElement("img"); |
| 1115 |
img.className = className; |
| 1116 |
img.src = src; |
| 1117 |
img.alt = ""; |
| 1118 |
img.setAttribute("aria-hidden", "true"); |
| 1119 |
img.draggable = false; |
| 1120 |
return img; |
| 1121 |
} |
| 1122 |
function letterFromTitle(title) { |
| 1123 |
const trimmed = (title ?? "").trim(); |
| 1124 |
if (trimmed === "") { |
| 1125 |
return "?"; |
| 1126 |
} |
| 1127 |
const words = trimmed.split(/\s+/); |
| 1128 |
if (words.length >= 2) { |
| 1129 |
return (words[0][0] + words[1][0]).toUpperCase(); |
| 1130 |
} |
| 1131 |
const first = words[0]; |
| 1132 |
if (first.length >= 2) { |
| 1133 |
return first.slice(0, 2).toUpperCase(); |
| 1134 |
} |
| 1135 |
return first.toUpperCase(); |
| 1136 |
} |
| 1137 |
function applyTileEntryStagger(tile) { |
| 1138 |
tile.style.setProperty( |
| 1139 |
"--desktop-mode-file-tile-enter-delay", |
| 1140 |
`${(Math.random() * 0.25).toFixed(3)}s` |
| 1141 |
); |
| 1142 |
tile.style.setProperty( |
| 1143 |
"--desktop-mode-file-tile-enter-duration", |
| 1144 |
`${(0.3 + Math.random() * 0.25).toFixed(3)}s` |
| 1145 |
); |
| 1146 |
} |
| 1147 |
const styles$3 = css`:host{display:inline-block}`; |
| 1148 |
const styles$2 = css`:host{position:absolute;width:var( --wpd-ribbon-size,90px );height:var( --wpd-ribbon-size,90px );overflow:hidden;pointer-events:none;z-index:var( --wpd-ribbon-z,2 )}:host( [ hidden ] ){display:none}.banner{position:absolute;display:block;width:var( --wpd-ribbon-banner-width,140px );padding:var( --wpd-ribbon-padding,4px 0 );text-align:center;font:var( --wpd-ribbon-font,700 10px/1.4 var( --desktop-mode-font,system-ui ) );letter-spacing:var( --wpd-ribbon-tracking,0.06em );text-transform:uppercase;color:var( --wpd-ribbon-fg,#fff );background:var( --wpd-ribbon-bg,var( --wp-admin-theme-color,#2271b1 ) );box-shadow:var( --wpd-ribbon-shadow,0 2px 4px rgba( 0,0,0,0.2 ) )}:host(:not( [ placement ] ) ),:host( [ placement='top-end' ] ){inset-block-start:0;inset-inline-end:0}:host(:not( [ placement ] ) ) .banner,:host( [ placement='top-end' ] ) .banner{inset-block-start:var( --wpd-ribbon-banner-offset,20px );inset-inline-end:var( --wpd-ribbon-banner-pull,-36px );transform:rotate( 45deg )}:host( [ placement='top-start' ] ){inset-block-start:0;inset-inline-start:0}:host( [ placement='top-start' ] ) .banner{inset-block-start:var( --wpd-ribbon-banner-offset,20px );inset-inline-start:var( --wpd-ribbon-banner-pull,-36px );transform:rotate( -45deg )}:host( [ placement='bottom-end' ] ){inset-block-end:0;inset-inline-end:0}:host( [ placement='bottom-end' ] ) .banner{inset-block-end:var( --wpd-ribbon-banner-offset,20px );inset-inline-end:var( --wpd-ribbon-banner-pull,-36px );transform:rotate( -45deg )}:host( [ placement='bottom-start' ] ){inset-block-end:0;inset-inline-start:0}:host( [ placement='bottom-start' ] ) .banner{inset-block-end:var( --wpd-ribbon-banner-offset,20px );inset-inline-start:var( --wpd-ribbon-banner-pull,-36px );transform:rotate( 45deg )}:host-context( [ dir='rtl' ] ):host(:not( [ placement ] ) ) .banner,:host-context( [ dir='rtl' ] ):host( [ placement='top-end' ] ) .banner{transform:rotate( -45deg )}:host-context( [ dir='rtl' ] ):host( [ placement='top-start' ] ) .banner{transform:rotate( 45deg )}:host-context( [ dir='rtl' ] ):host( [ placement='bottom-end' ] ) .banner{transform:rotate( 45deg )}:host-context( [ dir='rtl' ] ):host( [ placement='bottom-start' ] ) .banner{transform:rotate( -45deg )}:host( [ tone='success' ] ) .banner{background:var( --wpd-ribbon-success,#1a7f37 )}:host( [ tone='warning' ] ) .banner{background:var( --wpd-ribbon-warning,#9a6700 )}:host( [ tone='danger' ] ) .banner{background:var( --wpd-ribbon-danger,#cf222e )}:host( [ tone='info' ] ) .banner{background:var( --wpd-ribbon-info,#0969da )}:host( [ tone='neutral' ] ) .banner{background:var( --wpd-ribbon-neutral,#57606a )}`; |
| 1149 |
const _WpdRibbon = class _WpdRibbon extends Component { |
| 1150 |
render() { |
| 1151 |
return html`<span class="banner" part="banner"><slot></slot></span>`; |
| 1152 |
} |
| 1153 |
}; |
| 1154 |
_WpdRibbon.props = ["placement", "tone"]; |
| 1155 |
_WpdRibbon.styles = [styles$2]; |
| 1156 |
_WpdRibbon.help = { |
| 1157 |
title: "Ribbon", |
| 1158 |
summary: "45° corner ribbon. Wraps the top-end (default), top-start, bottom-end, or bottom-start corner of its positioned parent. The host owns clipping + rotation; consumers only set position-relative on the parent and drop a label inside.", |
| 1159 |
status: "experimental", |
| 1160 |
since: "0.20.0", |
| 1161 |
props: [ |
| 1162 |
{ |
| 1163 |
name: "placement", |
| 1164 |
type: '"top-end" | "top-start" | "bottom-end" | "bottom-start"', |
| 1165 |
description: "Which corner of the parent the ribbon hugs. Defaults to `top-end` (logical right in LTR, left in RTL)." |
| 1166 |
}, |
| 1167 |
{ |
| 1168 |
name: "tone", |
| 1169 |
type: '"primary" | "success" | "warning" | "danger" | "info" | "neutral"', |
| 1170 |
description: "Background color tone. Defaults to `primary` (the admin theme accent)." |
| 1171 |
} |
| 1172 |
], |
| 1173 |
slots: [{ name: "(default)", description: "Ribbon label text. Keep short." }], |
| 1174 |
cssProps: [ |
| 1175 |
{ name: "--wpd-ribbon-size", default: "90px", description: "Square clipping window edge." }, |
| 1176 |
{ name: "--wpd-ribbon-banner-width", default: "140px", description: "Width of the rotated strip." }, |
| 1177 |
{ name: "--wpd-ribbon-banner-offset", default: "20px", description: "Distance from corner to strip center." }, |
| 1178 |
{ name: "--wpd-ribbon-banner-pull", default: "-36px", description: "How far the strip overhangs the clip edge." }, |
| 1179 |
{ name: "--wpd-ribbon-bg", default: "var(--wp-admin-theme-color, #2271b1)" }, |
| 1180 |
{ name: "--wpd-ribbon-fg", default: "#fff" }, |
| 1181 |
{ name: "--wpd-ribbon-shadow", default: "0 2px 4px rgba(0,0,0,0.2)" }, |
| 1182 |
{ name: "--wpd-ribbon-padding", default: "4px 0" }, |
| 1183 |
{ name: "--wpd-ribbon-font", default: "700 10px/1.4 system-ui" }, |
| 1184 |
{ name: "--wpd-ribbon-tracking", default: "0.06em" }, |
| 1185 |
{ name: "--wpd-ribbon-z", default: "2" } |
| 1186 |
], |
| 1187 |
example: html` |
| 1188 |
<div |
| 1189 |
style="position: relative; width: 240px; height: 120px; |
| 1190 |
border: 1px solid #ccc; border-radius: 8px; |
| 1191 |
padding: 16px; box-sizing: border-box;" |
| 1192 |
> |
| 1193 |
<wpd-ribbon>Featured</wpd-ribbon> |
| 1194 |
Card body… |
| 1195 |
</div> |
| 1196 |
` |
| 1197 |
}; |
| 1198 |
let WpdRibbon = _WpdRibbon; |
| 1199 |
defineComponent("wpd-ribbon", WpdRibbon); |
| 1200 |
const TILE_CLASS = "desktop-mode-file-tile"; |
| 1201 |
const STATUS_LABEL = { |
| 1202 |
draft: "Draft", |
| 1203 |
pending: "Pending", |
| 1204 |
private: "Private", |
| 1205 |
future: "Scheduled" |
| 1206 |
}; |
| 1207 |
function statusRibbonsEnabled() { |
| 1208 |
const get = window.wp?.desktop?.getOsSettings; |
| 1209 |
if (typeof get !== "function") { |
| 1210 |
return true; |
| 1211 |
} |
| 1212 |
try { |
| 1213 |
return get()?.showPostStatusRibbons !== false; |
| 1214 |
} catch { |
| 1215 |
return true; |
| 1216 |
} |
| 1217 |
} |
| 1218 |
function getDragManager$1() { |
| 1219 |
const api = window.wp?.desktop?.dragManager; |
| 1220 |
return api ?? null; |
| 1221 |
} |
| 1222 |
const REACTIVE_PROPS = [ |
| 1223 |
"type", |
| 1224 |
"ref", |
| 1225 |
"label", |
| 1226 |
"icon", |
| 1227 |
"thumbnail", |
| 1228 |
"kind", |
| 1229 |
"status", |
| 1230 |
"selected", |
| 1231 |
"missing", |
| 1232 |
"access-gated", |
| 1233 |
"drag-kind", |
| 1234 |
"drag-title", |
| 1235 |
"drag-icon" |
| 1236 |
]; |
| 1237 |
const _WpdTile = class _WpdTile extends Component { |
| 1238 |
constructor() { |
| 1239 |
super(...arguments); |
| 1240 |
this._pointerdownHandler = null; |
| 1241 |
this._keydownHandler = null; |
| 1242 |
} |
| 1243 |
connectedCallback() { |
| 1244 |
super.connectedCallback(); |
| 1245 |
if (!this._keydownHandler) { |
| 1246 |
this._keydownHandler = (e) => { |
| 1247 |
if (e.key === "Enter" || e.key === " ") { |
| 1248 |
e.preventDefault(); |
| 1249 |
this.click(); |
| 1250 |
} |
| 1251 |
}; |
| 1252 |
this.addEventListener("keydown", this._keydownHandler); |
| 1253 |
} |
| 1254 |
this._paint(); |
| 1255 |
} |
| 1256 |
disconnectedCallback() { |
| 1257 |
if (this._pointerdownHandler) { |
| 1258 |
this.removeEventListener( |
| 1259 |
"pointerdown", |
| 1260 |
this._pointerdownHandler |
| 1261 |
); |
| 1262 |
this._pointerdownHandler = null; |
| 1263 |
} |
| 1264 |
if (this._keydownHandler) { |
| 1265 |
this.removeEventListener( |
| 1266 |
"keydown", |
| 1267 |
this._keydownHandler |
| 1268 |
); |
| 1269 |
this._keydownHandler = null; |
| 1270 |
} |
| 1271 |
} |
| 1272 |
/** |
| 1273 |
* Bypass the templated render loop. Lit-html's `render(template, |
| 1274 |
* root)` would wipe the host's light-DOM children every tick — |
| 1275 |
* including the visual / label / ribbon `_paint()` just |
| 1276 |
* inserted. We override `requestUpdate` directly so attribute |
| 1277 |
* changes call `_paint` (idempotent) without lit-html getting |
| 1278 |
* involved. |
| 1279 |
*/ |
| 1280 |
requestUpdate() { |
| 1281 |
if (!this.isConnected) { |
| 1282 |
return; |
| 1283 |
} |
| 1284 |
this._paint(); |
| 1285 |
} |
| 1286 |
render() { |
| 1287 |
return html``; |
| 1288 |
} |
| 1289 |
_paint() { |
| 1290 |
const type = this.getAttribute("type") ?? ""; |
| 1291 |
const ref = this.getAttribute("ref") ?? ""; |
| 1292 |
const label = this.getAttribute("label") ?? ""; |
| 1293 |
const icon = this.getAttribute("icon") ?? ""; |
| 1294 |
const thumbnail = this.getAttribute("thumbnail") ?? ""; |
| 1295 |
const kind = this.getAttribute("kind") ?? "entry"; |
| 1296 |
const status = this.getAttribute("status") ?? ""; |
| 1297 |
const selected = this.hasAttribute("selected"); |
| 1298 |
const missing = this.hasAttribute("missing"); |
| 1299 |
const accessGated = this.hasAttribute("access-gated"); |
| 1300 |
const ownedClasses = [ |
| 1301 |
TILE_CLASS, |
| 1302 |
`${TILE_CLASS}--folder`, |
| 1303 |
`${TILE_CLASS}--missing`, |
| 1304 |
`${TILE_CLASS}--access-gated`, |
| 1305 |
`${TILE_CLASS}--selected` |
| 1306 |
]; |
| 1307 |
for (const c of ownedClasses) { |
| 1308 |
this.classList.remove(c); |
| 1309 |
} |
| 1310 |
this.classList.add(TILE_CLASS); |
| 1311 |
if (kind === "folder") { |
| 1312 |
this.classList.add(`${TILE_CLASS}--folder`); |
| 1313 |
} |
| 1314 |
if (missing) { |
| 1315 |
this.classList.add(`${TILE_CLASS}--missing`); |
| 1316 |
} |
| 1317 |
if (accessGated) { |
| 1318 |
this.classList.add(`${TILE_CLASS}--access-gated`); |
| 1319 |
} |
| 1320 |
if (selected) { |
| 1321 |
this.classList.add(`${TILE_CLASS}--selected`); |
| 1322 |
} |
| 1323 |
this.dataset.fileType = type; |
| 1324 |
this.dataset.fileRef = ref; |
| 1325 |
if (kind) { |
| 1326 |
this.dataset.role = kind; |
| 1327 |
} |
| 1328 |
this.setAttribute("role", "listitem"); |
| 1329 |
this.setAttribute("aria-label", label); |
| 1330 |
if (!this.hasAttribute("tabindex")) { |
| 1331 |
this.setAttribute("tabindex", "0"); |
| 1332 |
} |
| 1333 |
const accessGatedTitle = "You don’t have permission to open this — ask the folder owner for access."; |
| 1334 |
if (accessGated) { |
| 1335 |
this.title = accessGatedTitle; |
| 1336 |
this.setAttribute("aria-disabled", "true"); |
| 1337 |
} else { |
| 1338 |
this.removeAttribute("aria-disabled"); |
| 1339 |
if (this.title === accessGatedTitle) { |
| 1340 |
this.removeAttribute("title"); |
| 1341 |
} |
| 1342 |
} |
| 1343 |
const SLOTS = [ |
| 1344 |
`${TILE_CLASS}__visual`, |
| 1345 |
`${TILE_CLASS}__label`, |
| 1346 |
`${TILE_CLASS}__lock` |
| 1347 |
]; |
| 1348 |
for (const cls of SLOTS) { |
| 1349 |
this.querySelectorAll(`:scope > .${cls}`).forEach( |
| 1350 |
(n) => n.remove() |
| 1351 |
); |
| 1352 |
} |
| 1353 |
this.querySelectorAll(":scope > wpd-ribbon").forEach( |
| 1354 |
(n) => n.remove() |
| 1355 |
); |
| 1356 |
const visual = document.createElement("span"); |
| 1357 |
visual.className = `${TILE_CLASS}__visual`; |
| 1358 |
if (thumbnail) { |
| 1359 |
const img = document.createElement("img"); |
| 1360 |
img.src = thumbnail; |
| 1361 |
img.alt = ""; |
| 1362 |
img.loading = "lazy"; |
| 1363 |
img.decoding = "async"; |
| 1364 |
img.className = `${TILE_CLASS}__preview`; |
| 1365 |
img.draggable = false; |
| 1366 |
visual.appendChild(img); |
| 1367 |
} else if (icon) { |
| 1368 |
const iconNode = renderIcon(icon, { |
| 1369 |
title: label, |
| 1370 |
className: `${TILE_CLASS}__icon` |
| 1371 |
}); |
| 1372 |
visual.appendChild(iconNode); |
| 1373 |
} |
| 1374 |
this.appendChild(visual); |
| 1375 |
const labelNode = document.createElement("span"); |
| 1376 |
labelNode.className = `${TILE_CLASS}__label`; |
| 1377 |
labelNode.textContent = label; |
| 1378 |
this.appendChild(labelNode); |
| 1379 |
if (accessGated) { |
| 1380 |
const lock = document.createElement("span"); |
| 1381 |
lock.className = `${TILE_CLASS}__lock dashicons dashicons-lock`; |
| 1382 |
lock.setAttribute("aria-hidden", "true"); |
| 1383 |
this.appendChild(lock); |
| 1384 |
} |
| 1385 |
if (status && status !== "publish" && STATUS_LABEL[status] && statusRibbonsEnabled()) { |
| 1386 |
const ribbon = document.createElement("wpd-ribbon"); |
| 1387 |
ribbon.setAttribute("placement", "top-end"); |
| 1388 |
ribbon.setAttribute("tone", ribbonToneFor(status)); |
| 1389 |
ribbon.textContent = STATUS_LABEL[status]; |
| 1390 |
this.appendChild(ribbon); |
| 1391 |
} |
| 1392 |
applyTileEntryStagger(this); |
| 1393 |
doAction("desktop-mode.tile.rendered", { tile: this }); |
| 1394 |
this._wireDragOut(); |
| 1395 |
} |
| 1396 |
_wireDragOut() { |
| 1397 |
if (this._pointerdownHandler) { |
| 1398 |
this.removeEventListener( |
| 1399 |
"pointerdown", |
| 1400 |
this._pointerdownHandler |
| 1401 |
); |
| 1402 |
this._pointerdownHandler = null; |
| 1403 |
} |
| 1404 |
const dragKind = this.getAttribute("drag-kind"); |
| 1405 |
if (!dragKind) { |
| 1406 |
return; |
| 1407 |
} |
| 1408 |
const handler = (e) => { |
| 1409 |
if (e.button !== 0) { |
| 1410 |
return; |
| 1411 |
} |
| 1412 |
const dragManager = getDragManager$1(); |
| 1413 |
if (!dragManager) { |
| 1414 |
return; |
| 1415 |
} |
| 1416 |
const ref = this.getAttribute("ref") ?? ""; |
| 1417 |
const title = this.getAttribute("drag-title") ?? this.getAttribute("label") ?? void 0; |
| 1418 |
const icon = this.getAttribute("drag-icon") ?? this.getAttribute("icon") ?? void 0; |
| 1419 |
const rect = this.getBoundingClientRect(); |
| 1420 |
dragManager.start({ |
| 1421 |
payload: { |
| 1422 |
type: "shortcut", |
| 1423 |
source: this, |
| 1424 |
data: { |
| 1425 |
kind: dragKind, |
| 1426 |
ref, |
| 1427 |
title, |
| 1428 |
icon |
| 1429 |
}, |
| 1430 |
ghost: { |
| 1431 |
offsetX: e.clientX - rect.left, |
| 1432 |
offsetY: e.clientY - rect.top |
| 1433 |
} |
| 1434 |
}, |
| 1435 |
origin: e |
| 1436 |
}); |
| 1437 |
}; |
| 1438 |
this._pointerdownHandler = handler; |
| 1439 |
this.addEventListener("pointerdown", handler); |
| 1440 |
} |
| 1441 |
}; |
| 1442 |
_WpdTile.shadow = false; |
| 1443 |
_WpdTile.props = REACTIVE_PROPS; |
| 1444 |
_WpdTile.styles = [styles$3]; |
| 1445 |
_WpdTile.help = { |
| 1446 |
title: "Tile", |
| 1447 |
summary: "Canonical file/entity tile. Used across the wallpaper, folder windows, every My WordPress section, and plugin surfaces. Renders the standard `.desktop-mode-file-tile` chrome + optional status ribbon and wires the shared drag-out helper.", |
| 1448 |
status: "experimental", |
| 1449 |
since: "0.21.0", |
| 1450 |
props: [ |
| 1451 |
{ name: "type", type: "string" }, |
| 1452 |
{ name: "ref", type: "string" }, |
| 1453 |
{ name: "label", type: "string" }, |
| 1454 |
{ name: "icon", type: "string", description: "Dashicon class / URL / data URI. Ignored when `thumbnail` is set." }, |
| 1455 |
{ name: "thumbnail", type: "string", description: "Preview image URL. Renders as `<img>` and wins over `icon`." }, |
| 1456 |
{ name: "kind", type: "`entry` | `folder`" }, |
| 1457 |
{ name: "status", type: "`draft` | `pending` | `private` | `future` | `publish`" }, |
| 1458 |
{ name: "selected", type: "boolean" }, |
| 1459 |
{ name: "missing", type: "boolean" }, |
| 1460 |
{ name: "access-gated", type: "boolean" }, |
| 1461 |
{ name: "drag-kind", type: "string", description: "When set, the component wires pointerdown → DragManager." }, |
| 1462 |
{ name: "drag-title", type: "string" }, |
| 1463 |
{ name: "drag-icon", type: "string" } |
| 1464 |
] |
| 1465 |
}; |
| 1466 |
let WpdTile = _WpdTile; |
| 1467 |
function ribbonToneFor(status) { |
| 1468 |
switch (status) { |
| 1469 |
case "draft": |
| 1470 |
return "warning"; |
| 1471 |
case "pending": |
| 1472 |
return "info"; |
| 1473 |
case "private": |
| 1474 |
return "danger"; |
| 1475 |
case "future": |
| 1476 |
return "primary"; |
| 1477 |
default: |
| 1478 |
return "primary"; |
| 1479 |
} |
| 1480 |
} |
| 1481 |
defineComponent("wpd-tile", WpdTile); |
| 1482 |
function buildTileFromSpec(spec) { |
| 1483 |
const tile = document.createElement("wpd-tile"); |
| 1484 |
tile.setAttribute("type", spec.type); |
| 1485 |
tile.setAttribute("ref", spec.ref); |
| 1486 |
tile.setAttribute("label", spec.label); |
| 1487 |
if (spec.icon) { |
| 1488 |
tile.setAttribute("icon", spec.icon); |
| 1489 |
} |
| 1490 |
if (spec.thumbnail) { |
| 1491 |
tile.setAttribute("thumbnail", spec.thumbnail); |
| 1492 |
} |
| 1493 |
if (spec.role) { |
| 1494 |
tile.setAttribute("kind", spec.role); |
| 1495 |
} |
| 1496 |
if (spec.status) { |
| 1497 |
tile.setAttribute("status", spec.status); |
| 1498 |
} |
| 1499 |
if (spec.missing) { |
| 1500 |
tile.setAttribute("missing", ""); |
| 1501 |
} |
| 1502 |
if (spec.accessGated) { |
| 1503 |
tile.setAttribute("access-gated", ""); |
| 1504 |
} |
| 1505 |
if (spec.dataset) { |
| 1506 |
for (const [key, raw] of Object.entries(spec.dataset)) { |
| 1507 |
if (raw === void 0 || raw === null) { |
| 1508 |
continue; |
| 1509 |
} |
| 1510 |
tile.dataset[key] = String(raw); |
| 1511 |
} |
| 1512 |
} |
| 1513 |
if (Array.isArray(spec.extraClasses)) { |
| 1514 |
for (const c of spec.extraClasses) { |
| 1515 |
if (c) { |
| 1516 |
tile.classList.add(c); |
| 1517 |
} |
| 1518 |
} |
| 1519 |
} |
| 1520 |
const classFiltered = applyFilters( |
| 1521 |
"desktop-mode.tile.class", |
| 1522 |
tile.className, |
| 1523 |
spec |
| 1524 |
); |
| 1525 |
if (classFiltered && classFiltered !== tile.className) { |
| 1526 |
tile.className = classFiltered; |
| 1527 |
} |
| 1528 |
if (typeof spec.x === "number" && typeof spec.y === "number") { |
| 1529 |
tile.style.position = "absolute"; |
| 1530 |
tile.style.left = `${spec.x}px`; |
| 1531 |
tile.style.top = `${spec.y}px`; |
| 1532 |
} |
| 1533 |
return tile; |
| 1534 |
} |
| 1535 |
function attachTileDragOut(tile, payload, onClick) { |
| 1536 |
tile.addEventListener("pointerdown", (e) => { |
| 1537 |
if (e.button !== 0) { |
| 1538 |
return; |
| 1539 |
} |
| 1540 |
const dragManager = getDragManager$1(); |
| 1541 |
if (!dragManager) { |
| 1542 |
return; |
| 1543 |
} |
| 1544 |
const rect = tile.getBoundingClientRect(); |
| 1545 |
dragManager.start({ |
| 1546 |
payload: { |
| 1547 |
type: "shortcut", |
| 1548 |
source: tile, |
| 1549 |
data: { |
| 1550 |
kind: payload.kind, |
| 1551 |
ref: payload.ref, |
| 1552 |
title: payload.title, |
| 1553 |
icon: payload.icon, |
| 1554 |
bridgePayload: payload.bridgePayload |
| 1555 |
}, |
| 1556 |
ghost: { |
| 1557 |
offsetX: e.clientX - rect.left, |
| 1558 |
offsetY: e.clientY - rect.top |
| 1559 |
} |
| 1560 |
}, |
| 1561 |
origin: e, |
| 1562 |
onClickOnly: onClick |
| 1563 |
}); |
| 1564 |
}); |
| 1565 |
} |
| 1566 |
function getDragManager() { |
| 1567 |
const api = window.wp?.desktop?.dragManager; |
| 1568 |
return api ?? null; |
| 1569 |
} |
| 1570 |
function stripTags(html2) { |
| 1571 |
const div = document.createElement("div"); |
| 1572 |
div.innerHTML = html2; |
| 1573 |
return (div.textContent ?? "").trim(); |
| 1574 |
} |
| 1575 |
const renderers = /* @__PURE__ */ new Map(); |
| 1576 |
function registerEntityKind(kind, renderer) { |
| 1577 |
if (typeof kind !== "string" || kind === "") { |
| 1578 |
throw new TypeError( |
| 1579 |
"[my-wordpress] registerEntityKind: kind must be a non-empty string." |
| 1580 |
); |
| 1581 |
} |
| 1582 |
if (typeof renderer !== "function") { |
| 1583 |
throw new TypeError( |
| 1584 |
"[my-wordpress] registerEntityKind: renderer must be a function." |
| 1585 |
); |
| 1586 |
} |
| 1587 |
renderers.set(kind, renderer); |
| 1588 |
return () => { |
| 1589 |
if (renderers.get(kind) === renderer) { |
| 1590 |
renderers.delete(kind); |
| 1591 |
} |
| 1592 |
}; |
| 1593 |
} |
| 1594 |
function getEntityRenderer(kind) { |
| 1595 |
if (!kind) { |
| 1596 |
return renderers.get("post"); |
| 1597 |
} |
| 1598 |
return renderers.get(kind); |
| 1599 |
} |
| 1600 |
const DEBOUNCE_MS = 300; |
| 1601 |
function renderListToolbar(options) { |
| 1602 |
const host = document.createElement("div"); |
| 1603 |
host.className = "desktop-mode-my-wordpress__list-toolbar"; |
| 1604 |
const search = document.createElement("div"); |
| 1605 |
search.className = "desktop-mode-my-wordpress__list-toolbar-search"; |
| 1606 |
const input = document.createElement("input"); |
| 1607 |
input.type = "search"; |
| 1608 |
input.className = "desktop-mode-my-wordpress__list-toolbar-search-input"; |
| 1609 |
input.placeholder = options.placeholder ?? __("Search…", "desktop-mode"); |
| 1610 |
input.setAttribute( |
| 1611 |
"aria-label", |
| 1612 |
options.ariaLabel ?? options.placeholder ?? __("Search", "desktop-mode") |
| 1613 |
); |
| 1614 |
input.autocomplete = "off"; |
| 1615 |
input.spellcheck = false; |
| 1616 |
if (options.initialValue) { |
| 1617 |
input.value = options.initialValue; |
| 1618 |
} |
| 1619 |
search.appendChild(input); |
| 1620 |
host.appendChild(search); |
| 1621 |
let debounceId = null; |
| 1622 |
let lastEmitted = options.initialValue ?? ""; |
| 1623 |
const emit = (raw) => { |
| 1624 |
const normalized = raw.trim(); |
| 1625 |
if (normalized === lastEmitted) { |
| 1626 |
return; |
| 1627 |
} |
| 1628 |
lastEmitted = normalized; |
| 1629 |
options.onSearchChange(normalized); |
| 1630 |
}; |
| 1631 |
const onInput = () => { |
| 1632 |
if (debounceId !== null) { |
| 1633 |
clearTimeout(debounceId); |
| 1634 |
} |
| 1635 |
debounceId = setTimeout(() => { |
| 1636 |
debounceId = null; |
| 1637 |
emit(input.value); |
| 1638 |
}, DEBOUNCE_MS); |
| 1639 |
}; |
| 1640 |
input.addEventListener("input", onInput); |
| 1641 |
const onSearchEvent = () => { |
| 1642 |
if (input.value === "") { |
| 1643 |
if (debounceId !== null) { |
| 1644 |
clearTimeout(debounceId); |
| 1645 |
debounceId = null; |
| 1646 |
} |
| 1647 |
emit(""); |
| 1648 |
} |
| 1649 |
}; |
| 1650 |
input.addEventListener("search", onSearchEvent); |
| 1651 |
const onKeydown = (ev) => { |
| 1652 |
if (ev.key === "Enter") { |
| 1653 |
ev.preventDefault(); |
| 1654 |
if (debounceId !== null) { |
| 1655 |
clearTimeout(debounceId); |
| 1656 |
debounceId = null; |
| 1657 |
} |
| 1658 |
emit(input.value); |
| 1659 |
} |
| 1660 |
}; |
| 1661 |
input.addEventListener("keydown", onKeydown); |
| 1662 |
return { |
| 1663 |
host, |
| 1664 |
getQuery: () => lastEmitted, |
| 1665 |
destroy: () => { |
| 1666 |
if (debounceId !== null) { |
| 1667 |
clearTimeout(debounceId); |
| 1668 |
debounceId = null; |
| 1669 |
} |
| 1670 |
input.removeEventListener("input", onInput); |
| 1671 |
input.removeEventListener("search", onSearchEvent); |
| 1672 |
input.removeEventListener("keydown", onKeydown); |
| 1673 |
} |
| 1674 |
}; |
| 1675 |
} |
| 1676 |
const FILE_DROP_HOOKS = { |
| 1677 |
/** |
| 1678 |
* Action — fires after a successful upload. Payload: |
| 1679 |
* `{ file: File, result: DropUploadResult, fields: |
| 1680 |
* DropDialogFields, context: DropContext }`. |
| 1681 |
* |
| 1682 |
* The `file` field carries the same `File` reference that |
| 1683 |
* `UPLOAD_STARTED` / `UPLOAD_PROGRESS` exposed (i.e. the |
| 1684 |
* payload returned by the `BEFORE_UPLOAD` filter, in case a |
| 1685 |
* plugin swapped the file). Subscribers tracking per-file |
| 1686 |
* state — progress HUDs, sequence counters — should match on |
| 1687 |
* this identity rather than the filename: two drops of |
| 1688 |
* `photo.jpg` from different folders would otherwise route |
| 1689 |
* each other's success event to the wrong row. |
| 1690 |
* |
| 1691 |
* @since 0.31.0 the `file` field was added; pre-0.31.0 code |
| 1692 |
* that destructured `{ result, fields, context }` keeps working. |
| 1693 |
*/ |
| 1694 |
AFTER_UPLOAD: "desktop-mode.drop.after-upload" |
| 1695 |
}; |
| 1696 |
const dialogStyles = css`:host{display:none;position:fixed;inset:0;align-items:center;justify-content:center;background:rgba( 0,0,0,0.45 );backdrop-filter:blur( 2px );z-index:10000}:host( [ open ] ){display:flex}.dialog{width:min( 420px,92vw );background:var( --wpd-confirm-dialog-bg,var( --desktop-mode-bg,#1d2327 ) );color:var( --wpd-confirm-dialog-fg,var( --desktop-mode-fg,#fff ) );border:1px solid rgba( 255,255,255,0.08 );border-radius:10px;box-shadow:0 20px 50px rgba( 0,0,0,0.6 );padding:20px 22px 18px;display:flex;flex-direction:column;gap:10px;position:relative}.close{position:absolute;top:8px;right:10px;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:0;border-radius:6px;color:var( --wpd-confirm-dialog-fg-muted,rgba( 255,255,255,0.7 ) );cursor:pointer;font-size:22px;line-height:1;padding:0}.close:hover{background:rgba( 255,255,255,0.08 );color:inherit}.title{margin:0 0 4px;font-size:16px;font-weight:600}.message{margin:0;color:var( --wpd-confirm-dialog-fg-muted,rgba( 255,255,255,0.7 ) );line-height:1.45;white-space:pre-line}.actions{display:flex;justify-content:flex-end;gap:8px;margin-top:6px}.btn{border:0;border-radius:6px;padding:8px 14px;font-size:13px;cursor:pointer;font-weight:500}.btn--secondary{background:rgba( 255,255,255,0.08 );color:inherit}.btn--secondary:hover{background:rgba( 255,255,255,0.14 )}.btn--primary{background:var( --wp-admin-theme-color,#2271b1 );color:#fff}.btn--primary:hover{filter:brightness( 1.08 )}.btn--danger{background:#d63638;color:#fff}.btn--danger:hover{filter:brightness( 1.08 )}`; |
| 1697 |
const _WpdConfirmDialog = class _WpdConfirmDialog extends Component { |
| 1698 |
constructor() { |
| 1699 |
super(...arguments); |
| 1700 |
this._onKey = (e) => { |
| 1701 |
if (e.key === "Escape") { |
| 1702 |
e.preventDefault(); |
| 1703 |
this._cancel(); |
| 1704 |
} |
| 1705 |
if (e.key === "Enter" && !e.isComposing) { |
| 1706 |
e.preventDefault(); |
| 1707 |
this._confirm(); |
| 1708 |
} |
| 1709 |
}; |
| 1710 |
this._onBackdrop = (e) => { |
| 1711 |
const path = e.composedPath(); |
| 1712 |
const original = path.length > 0 ? path[0] : e.target; |
| 1713 |
if (original === this) { |
| 1714 |
this._cancel(); |
| 1715 |
} |
| 1716 |
}; |
| 1717 |
this._confirm = () => { |
| 1718 |
this.emit("wpd-confirm", { confirmed: true }); |
| 1719 |
this.removeAttribute("open"); |
| 1720 |
}; |
| 1721 |
this._cancel = () => { |
| 1722 |
this.emit("wpd-cancel", { confirmed: false }); |
| 1723 |
this.removeAttribute("open"); |
| 1724 |
}; |
| 1725 |
} |
| 1726 |
connectedCallback() { |
| 1727 |
super.connectedCallback(); |
| 1728 |
this.setAttribute("role", "dialog"); |
| 1729 |
this.setAttribute("aria-modal", "true"); |
| 1730 |
this.addEventListener("keydown", this._onKey); |
| 1731 |
this.addEventListener("click", this._onBackdrop); |
| 1732 |
} |
| 1733 |
disconnectedCallback() { |
| 1734 |
this.removeEventListener("keydown", this._onKey); |
| 1735 |
this.removeEventListener("click", this._onBackdrop); |
| 1736 |
} |
| 1737 |
render() { |
| 1738 |
const title = this.title ?? ""; |
| 1739 |
const message = this.message ?? ""; |
| 1740 |
const confirmLabel = this["confirm-label"] || "Confirm"; |
| 1741 |
const cancelLabel = this["cancel-label"] || "Cancel"; |
| 1742 |
const isDanger = this.hasAttribute("danger"); |
| 1743 |
const hideCancel = this.hasAttribute("hide-cancel"); |
| 1744 |
const isDismissable = this.hasAttribute("dismissable"); |
| 1745 |
return html` |
| 1746 |
<div class="dialog" tabindex="-1"> |
| 1747 |
${isDismissable ? html`<button |
| 1748 |
type="button" |
| 1749 |
class="close" |
| 1750 |
aria-label="Close" |
| 1751 |
@click=${() => this._cancel()} |
| 1752 |
>×</button>` : html``} |
| 1753 |
${title ? html`<h2 class="title">${title}</h2>` : html``} |
| 1754 |
${message ? html`<p class="message">${message}</p>` : html``} |
| 1755 |
<div class="actions"> |
| 1756 |
${hideCancel ? html`` : html`<button |
| 1757 |
type="button" |
| 1758 |
class="btn btn--secondary" |
| 1759 |
@click=${() => this._cancel()} |
| 1760 |
> |
| 1761 |
${cancelLabel} |
| 1762 |
</button>`} |
| 1763 |
<button |
| 1764 |
type="button" |
| 1765 |
class="btn ${isDanger ? "btn--danger" : "btn--primary"}" |
| 1766 |
@click=${() => this._confirm()} |
| 1767 |
> |
| 1768 |
${confirmLabel} |
| 1769 |
</button> |
| 1770 |
</div> |
| 1771 |
</div> |
| 1772 |
`; |
| 1773 |
} |
| 1774 |
}; |
| 1775 |
_WpdConfirmDialog.props = [ |
| 1776 |
"open", |
| 1777 |
"title", |
| 1778 |
"message", |
| 1779 |
"confirm-label", |
| 1780 |
"cancel-label", |
| 1781 |
"danger", |
| 1782 |
"hide-cancel", |
| 1783 |
"dismissable" |
| 1784 |
]; |
| 1785 |
_WpdConfirmDialog.styles = [dialogStyles]; |
| 1786 |
_WpdConfirmDialog.help = { |
| 1787 |
title: "Confirm dialog", |
| 1788 |
summary: "Modal Yes/No replacement for window.confirm(). Two consumption paths: declarative element with `open` + `wpd-confirm` event, or the imperative Promise-returning `wpdConfirm()` helper.", |
| 1789 |
status: "experimental", |
| 1790 |
since: "0.9.0", |
| 1791 |
props: [ |
| 1792 |
{ name: "open", type: "boolean attribute", description: "Mounts the dialog visible." }, |
| 1793 |
{ name: "title", type: "string", description: "Heading shown at the top." }, |
| 1794 |
{ name: "message", type: "string", description: "Body copy. Newlines preserved." }, |
| 1795 |
{ name: "confirm-label", type: "string", default: "Confirm", description: "Confirm-button label." }, |
| 1796 |
{ name: "cancel-label", type: "string", default: "Cancel", description: "Cancel-button label." }, |
| 1797 |
{ name: "danger", type: "boolean attribute", description: "Renders the confirm button red." }, |
| 1798 |
{ name: "hide-cancel", type: "boolean attribute", description: "Hides the cancel button entirely. Useful when there is no alternative action — pair with `dismissable` so the user still has an explicit way to close." }, |
| 1799 |
{ name: "dismissable", type: "boolean attribute", description: "Renders an X close button in the top-right corner. Click emits `wpd-cancel`." } |
| 1800 |
], |
| 1801 |
events: [ |
| 1802 |
{ |
| 1803 |
name: "wpd-confirm", |
| 1804 |
description: "Fires on confirm. Detail: `{ confirmed: true }`." |
| 1805 |
}, |
| 1806 |
{ |
| 1807 |
name: "wpd-cancel", |
| 1808 |
description: "Fires on cancel (Cancel button, Escape, backdrop click). Detail: `{ confirmed: false }`." |
| 1809 |
} |
| 1810 |
] |
| 1811 |
}; |
| 1812 |
let WpdConfirmDialog = _WpdConfirmDialog; |
| 1813 |
defineComponent("wpd-confirm-dialog", WpdConfirmDialog); |
| 1814 |
function wpdConfirm(options) { |
| 1815 |
return new Promise((resolve) => { |
| 1816 |
const dialog = document.createElement("wpd-confirm-dialog"); |
| 1817 |
dialog.setAttribute("open", ""); |
| 1818 |
if (options.title) { |
| 1819 |
dialog.setAttribute("title", options.title); |
| 1820 |
} |
| 1821 |
dialog.setAttribute("message", options.message); |
| 1822 |
if (options.confirmLabel) { |
| 1823 |
dialog.setAttribute("confirm-label", options.confirmLabel); |
| 1824 |
} |
| 1825 |
if (options.cancelLabel) { |
| 1826 |
dialog.setAttribute("cancel-label", options.cancelLabel); |
| 1827 |
} |
| 1828 |
{ |
| 1829 |
dialog.setAttribute("danger", ""); |
| 1830 |
} |
| 1831 |
if (options.hideCancel) { |
| 1832 |
dialog.setAttribute("hide-cancel", ""); |
| 1833 |
} |
| 1834 |
if (options.dismissable) { |
| 1835 |
dialog.setAttribute("dismissable", ""); |
| 1836 |
} |
| 1837 |
const cleanup = (ok) => { |
| 1838 |
dialog.remove(); |
| 1839 |
resolve(ok); |
| 1840 |
}; |
| 1841 |
dialog.addEventListener("wpd-confirm", () => cleanup(true)); |
| 1842 |
dialog.addEventListener("wpd-cancel", () => cleanup(false)); |
| 1843 |
document.body.appendChild(dialog); |
| 1844 |
const inner = dialog.shadowRoot?.querySelector(".dialog"); |
| 1845 |
(inner ?? dialog).focus?.(); |
| 1846 |
}); |
| 1847 |
} |
| 1848 |
const HOOK_PREFIX = "desktop-mode.activity."; |
| 1849 |
function hookName(channel) { |
| 1850 |
return `${HOOK_PREFIX}${String(channel)}`; |
| 1851 |
} |
| 1852 |
let subscribeSeq = 0; |
| 1853 |
const activity = { |
| 1854 |
publish(channel, payload) { |
| 1855 |
doAction(hookName(channel), payload); |
| 1856 |
}, |
| 1857 |
subscribe(channel, cb) { |
| 1858 |
const ns = `desktop-mode/activity-sub/${++subscribeSeq}`; |
| 1859 |
const hook = hookName(channel); |
| 1860 |
addAction( |
| 1861 |
hook, |
| 1862 |
ns, |
| 1863 |
(payload) => cb(payload) |
| 1864 |
); |
| 1865 |
let removed = false; |
| 1866 |
return () => { |
| 1867 |
if (removed) { |
| 1868 |
return; |
| 1869 |
} |
| 1870 |
removed = true; |
| 1871 |
removeAction(hook, ns); |
| 1872 |
}; |
| 1873 |
}, |
| 1874 |
filter(channel, value, ...args) { |
| 1875 |
return applyFilters(hookName(channel), value, ...args); |
| 1876 |
} |
| 1877 |
}; |
| 1878 |
const DEFAULT_DURATION_MS = 4e3; |
| 1879 |
const FADE_OUT_MS = 200; |
| 1880 |
function showToast$1(options) { |
| 1881 |
const intent = activity.filter( |
| 1882 |
"desktop-mode/toast-requested", |
| 1883 |
{ ...options } |
| 1884 |
); |
| 1885 |
if (!intent || intent.cancel === true) { |
| 1886 |
return () => void 0; |
| 1887 |
} |
| 1888 |
let dismissRequested = false; |
| 1889 |
let realDismiss = null; |
| 1890 |
openWithShellOverlays( |
| 1891 |
() => !dismissRequested, |
| 1892 |
() => { |
| 1893 |
realDismiss = renderToast(intent); |
| 1894 |
} |
| 1895 |
); |
| 1896 |
return () => { |
| 1897 |
dismissRequested = true; |
| 1898 |
if (realDismiss) { |
| 1899 |
realDismiss(); |
| 1900 |
} |
| 1901 |
}; |
| 1902 |
} |
| 1903 |
function renderToast(intent) { |
| 1904 |
const container = ensureContainer(); |
| 1905 |
const toast = document.createElement("wpd-toast"); |
| 1906 |
toast.textContent = intent.message; |
| 1907 |
if (intent.action) { |
| 1908 |
toast.setAttribute("action", intent.action.label); |
| 1909 |
toast.addEventListener("wpd-toast-action", () => { |
| 1910 |
intent.action?.onClick(); |
| 1911 |
dismiss(); |
| 1912 |
}); |
| 1913 |
} |
| 1914 |
container.appendChild(toast); |
| 1915 |
let dismissed = false; |
| 1916 |
let dismissTimer = null; |
| 1917 |
const dismiss = () => { |
| 1918 |
if (dismissed) { |
| 1919 |
return; |
| 1920 |
} |
| 1921 |
dismissed = true; |
| 1922 |
if (dismissTimer !== null) { |
| 1923 |
window.clearTimeout(dismissTimer); |
| 1924 |
dismissTimer = null; |
| 1925 |
} |
| 1926 |
toast.setAttribute("state", "out"); |
| 1927 |
window.setTimeout(() => { |
| 1928 |
toast.remove(); |
| 1929 |
}, FADE_OUT_MS); |
| 1930 |
}; |
| 1931 |
requestAnimationFrame(() => { |
| 1932 |
toast.setAttribute("state", "in"); |
| 1933 |
}); |
| 1934 |
dismissTimer = window.setTimeout( |
| 1935 |
dismiss, |
| 1936 |
intent.duration ?? DEFAULT_DURATION_MS |
| 1937 |
); |
| 1938 |
activity.publish("desktop-mode/toast-shown", { ...intent }); |
| 1939 |
return dismiss; |
| 1940 |
} |
| 1941 |
function ensureContainer() { |
| 1942 |
const existing = document.querySelector( |
| 1943 |
"wpd-toast-container" |
| 1944 |
); |
| 1945 |
if (existing) { |
| 1946 |
return existing; |
| 1947 |
} |
| 1948 |
const el = document.createElement("wpd-toast-container"); |
| 1949 |
document.body.appendChild(el); |
| 1950 |
return el; |
| 1951 |
} |
| 1952 |
const FALLBACK_BASE = "http://localhost/"; |
| 1953 |
function joinRestUrl(restRoot, path) { |
| 1954 |
const base = typeof window !== "undefined" && window.location ? window.location.href : FALLBACK_BASE; |
| 1955 |
const url = new URL(restRoot, base); |
| 1956 |
const trimmed = path.replace(/^\/+/, ""); |
| 1957 |
const queryAt = trimmed.indexOf("?"); |
| 1958 |
const route = queryAt === -1 ? trimmed : trimmed.slice(0, queryAt); |
| 1959 |
const extraQuery = queryAt === -1 ? "" : trimmed.slice(queryAt + 1); |
| 1960 |
if (url.searchParams.has("rest_route")) { |
| 1961 |
const existing = url.searchParams.get("rest_route") ?? "/"; |
| 1962 |
const prefix = existing.endsWith("/") ? existing : existing + "/"; |
| 1963 |
url.searchParams.set("rest_route", prefix + route); |
| 1964 |
} else { |
| 1965 |
const pathname = url.pathname.endsWith("/") ? url.pathname : url.pathname + "/"; |
| 1966 |
url.pathname = pathname + route; |
| 1967 |
} |
| 1968 |
if (extraQuery) { |
| 1969 |
const extras = new URLSearchParams(extraQuery); |
| 1970 |
extras.forEach((value, key) => { |
| 1971 |
url.searchParams.append(key, value); |
| 1972 |
}); |
| 1973 |
} |
| 1974 |
return url.toString(); |
| 1975 |
} |
| 1976 |
const NONCE_HEADER = "X-WP-Nonce"; |
| 1977 |
function injectRestNonce(input, init) { |
| 1978 |
const nonce = readRestNonce(); |
| 1979 |
if (!nonce) { |
| 1980 |
return init; |
| 1981 |
} |
| 1982 |
const url = resolveUrl(input); |
| 1983 |
if (!url || !isSameOriginRestUrl(url)) { |
| 1984 |
return init; |
| 1985 |
} |
| 1986 |
const baseHeaders = init?.headers ?? (typeof Request !== "undefined" && input instanceof Request ? input.headers : void 0); |
| 1987 |
const headers = new Headers(baseHeaders ?? {}); |
| 1988 |
if (headers.has(NONCE_HEADER)) { |
| 1989 |
return init; |
| 1990 |
} |
| 1991 |
headers.set(NONCE_HEADER, nonce); |
| 1992 |
return { ...init ?? {}, headers }; |
| 1993 |
} |
| 1994 |
function readRestNonce() { |
| 1995 |
if (typeof window === "undefined") { |
| 1996 |
return void 0; |
| 1997 |
} |
| 1998 |
const cfg = window.desktopModeConfig; |
| 1999 |
const value = cfg?.restNonce; |
| 2000 |
return typeof value === "string" && value.length > 0 ? value : void 0; |
| 2001 |
} |
| 2002 |
function resolveUrl(input) { |
| 2003 |
try { |
| 2004 |
const base = typeof window !== "undefined" && window.location ? window.location.href : void 0; |
| 2005 |
if (typeof input === "string") { |
| 2006 |
return new URL(input, base); |
| 2007 |
} |
| 2008 |
if (input instanceof URL) { |
| 2009 |
return input; |
| 2010 |
} |
| 2011 |
if (typeof Request !== "undefined" && input instanceof Request) { |
| 2012 |
return new URL(input.url, base); |
| 2013 |
} |
| 2014 |
return null; |
| 2015 |
} catch { |
| 2016 |
return null; |
| 2017 |
} |
| 2018 |
} |
| 2019 |
function isSameOriginRestUrl(url) { |
| 2020 |
if (typeof window === "undefined" || !window.location || url.origin !== window.location.origin) { |
| 2021 |
return false; |
| 2022 |
} |
| 2023 |
if (url.pathname.includes("/wp-json/")) { |
| 2024 |
return true; |
| 2025 |
} |
| 2026 |
if (url.searchParams.has("rest_route")) { |
| 2027 |
return true; |
| 2028 |
} |
| 2029 |
return false; |
| 2030 |
} |
| 2031 |
function trackedFetch(input, init, opts = {}) { |
| 2032 |
const fn = window.wp?.desktop?.fetch; |
| 2033 |
if (typeof fn === "function") { |
| 2034 |
return fn(input, init, opts); |
| 2035 |
} |
| 2036 |
const finalInit = injectRestNonce(input, init); |
| 2037 |
return fetch(input, finalInit); |
| 2038 |
} |
| 2039 |
const WINDOW_ID$2 = "desktop-mode-my-wordpress"; |
| 2040 |
function getConfig() { |
| 2041 |
const store = window.desktopModeWindowConfig; |
| 2042 |
const cfg = store ? store[WINDOW_ID$2] : void 0; |
| 2043 |
if (!cfg) { |
| 2044 |
throw new Error( |
| 2045 |
"[desktop-mode-my-wordpress] config blob missing — was the window opened without registration?" |
| 2046 |
); |
| 2047 |
} |
| 2048 |
return cfg; |
| 2049 |
} |
| 2050 |
function getEntity(id) { |
| 2051 |
return getConfig().entities.find((e) => e.id === id); |
| 2052 |
} |
| 2053 |
function buildUrl$1(path) { |
| 2054 |
return joinRestUrl(getConfig().restRoot, path); |
| 2055 |
} |
| 2056 |
async function shellFetch$1(input, init) { |
| 2057 |
return trackedFetch(input, init, { |
| 2058 |
windowId: WINDOW_ID$2, |
| 2059 |
source: "desktop-mode/my-wordpress" |
| 2060 |
}); |
| 2061 |
} |
| 2062 |
async function fetchEntityList(entity, params) { |
| 2063 |
const cfg = getConfig(); |
| 2064 |
const url = new URL(buildUrl$1(entity.restPath)); |
| 2065 |
url.searchParams.set("page", String(params.page)); |
| 2066 |
url.searchParams.set("per_page", String(params.perPage)); |
| 2067 |
url.searchParams.set( |
| 2068 |
"_fields", |
| 2069 |
"id,title,excerpt,date,status,featured_media,link,desktop_mode_lock,_links,_embedded" |
| 2070 |
); |
| 2071 |
url.searchParams.set("_embed", "wp:featuredmedia"); |
| 2072 |
url.searchParams.set("status", "publish,future,draft,pending,private"); |
| 2073 |
if (params.search) { |
| 2074 |
url.searchParams.set("search", params.search); |
| 2075 |
} |
| 2076 |
const response = await shellFetch$1(url.toString(), { |
| 2077 |
method: "GET", |
| 2078 |
credentials: "same-origin", |
| 2079 |
headers: { |
| 2080 |
"X-WP-Nonce": cfg.restNonce, |
| 2081 |
Accept: "application/json" |
| 2082 |
}, |
| 2083 |
signal: params.signal |
| 2084 |
}); |
| 2085 |
if (!response.ok) { |
| 2086 |
throw new Error( |
| 2087 |
await readErrorMessage$1(response, "Failed to load list") |
| 2088 |
); |
| 2089 |
} |
| 2090 |
const items = await response.json(); |
| 2091 |
const total = Number(response.headers.get("X-WP-Total") ?? items.length); |
| 2092 |
const totalPages = Number( |
| 2093 |
response.headers.get("X-WP-TotalPages") ?? 1 |
| 2094 |
); |
| 2095 |
return { items, total, totalPages }; |
| 2096 |
} |
| 2097 |
async function fetchEntityDetail(entity, id) { |
| 2098 |
const cfg = getConfig(); |
| 2099 |
const url = new URL(buildUrl$1(`${entity.restPath}/${id}`)); |
| 2100 |
url.searchParams.set( |
| 2101 |
"_fields", |
| 2102 |
"id,title,content,excerpt,date,modified,status,link,author,featured_media,categories,tags,comment_status,desktop_mode_contributors,desktop_mode_attached_media,_links,_embedded" |
| 2103 |
); |
| 2104 |
url.searchParams.set("_embed", "author,wp:term,wp:featuredmedia,replies"); |
| 2105 |
const response = await shellFetch$1(url.toString(), { |
| 2106 |
method: "GET", |
| 2107 |
credentials: "same-origin", |
| 2108 |
headers: { |
| 2109 |
"X-WP-Nonce": cfg.restNonce, |
| 2110 |
Accept: "application/json" |
| 2111 |
} |
| 2112 |
}); |
| 2113 |
if (!response.ok) { |
| 2114 |
throw new Error( |
| 2115 |
await readErrorMessage$1(response, "Failed to load entry") |
| 2116 |
); |
| 2117 |
} |
| 2118 |
return await response.json(); |
| 2119 |
} |
| 2120 |
async function trashEntity(entity, id) { |
| 2121 |
const cfg = getConfig(); |
| 2122 |
const url = buildUrl$1(`${entity.restPath}/${id}`); |
| 2123 |
const response = await shellFetch$1(url, { |
| 2124 |
method: "DELETE", |
| 2125 |
credentials: "same-origin", |
| 2126 |
headers: { |
| 2127 |
"X-WP-Nonce": cfg.restNonce, |
| 2128 |
Accept: "application/json" |
| 2129 |
} |
| 2130 |
}); |
| 2131 |
if (!response.ok) { |
| 2132 |
throw new Error( |
| 2133 |
await readErrorMessage$1(response, "Failed to move to trash") |
| 2134 |
); |
| 2135 |
} |
| 2136 |
} |
| 2137 |
async function readErrorMessage$1(response, fallback) { |
| 2138 |
let message = `${response.status} ${response.statusText || fallback}`; |
| 2139 |
try { |
| 2140 |
const json = await response.json(); |
| 2141 |
if (json && typeof json.message === "string") { |
| 2142 |
message = json.message; |
| 2143 |
} |
| 2144 |
} catch { |
| 2145 |
} |
| 2146 |
return message; |
| 2147 |
} |
| 2148 |
async function fetchEntityTotal(entity) { |
| 2149 |
const cfg = getConfig(); |
| 2150 |
const buildRequestUrl = (withWho) => { |
| 2151 |
const url = new URL(buildUrl$1(entity.restPath)); |
| 2152 |
url.searchParams.set("page", "1"); |
| 2153 |
url.searchParams.set("per_page", "1"); |
| 2154 |
url.searchParams.set("_fields", "id"); |
| 2155 |
if (entity.kind === "user") { |
| 2156 |
if (withWho) { |
| 2157 |
url.searchParams.set("who", "authors"); |
| 2158 |
} |
| 2159 |
} else if (entity.kind === "media") { |
| 2160 |
url.searchParams.set("status", "inherit"); |
| 2161 |
} else { |
| 2162 |
url.searchParams.set("status", "publish,future,draft,pending,private"); |
| 2163 |
} |
| 2164 |
return url.toString(); |
| 2165 |
}; |
| 2166 |
const send = (target) => shellFetch$1(target, { |
| 2167 |
method: "GET", |
| 2168 |
credentials: "same-origin", |
| 2169 |
headers: { |
| 2170 |
"X-WP-Nonce": cfg.restNonce, |
| 2171 |
Accept: "application/json" |
| 2172 |
} |
| 2173 |
}); |
| 2174 |
let response = await send(buildRequestUrl(false)); |
| 2175 |
if (response.status === 403 && entity.kind === "user") { |
| 2176 |
response = await send(buildRequestUrl(true)); |
| 2177 |
} |
| 2178 |
if (!response.ok) { |
| 2179 |
throw new Error(await readErrorMessage$1(response, "Failed to count")); |
| 2180 |
} |
| 2181 |
await response.json().catch(() => null); |
| 2182 |
const raw = response.headers.get("X-WP-Total"); |
| 2183 |
const n = raw ? Number(raw) : NaN; |
| 2184 |
return Number.isFinite(n) ? n : 0; |
| 2185 |
} |
| 2186 |
async function fetchUserList(entity, params) { |
| 2187 |
const cfg = getConfig(); |
| 2188 |
const buildRequestUrl = (mode) => { |
| 2189 |
const url = new URL(buildUrl$1(entity.restPath)); |
| 2190 |
url.searchParams.set("page", String(params.page)); |
| 2191 |
url.searchParams.set("per_page", String(params.perPage)); |
| 2192 |
url.searchParams.set( |
| 2193 |
"_fields", |
| 2194 |
"id,name,slug,description,link,avatar_urls,desktop_mode_summary" |
| 2195 |
); |
| 2196 |
url.searchParams.set("orderby", "name"); |
| 2197 |
url.searchParams.set("order", "asc"); |
| 2198 |
if (mode === "edit") { |
| 2199 |
url.searchParams.set("context", "edit"); |
| 2200 |
} else { |
| 2201 |
url.searchParams.set("who", "authors"); |
| 2202 |
} |
| 2203 |
if (params.search) { |
| 2204 |
url.searchParams.set("search", params.search); |
| 2205 |
} |
| 2206 |
return url.toString(); |
| 2207 |
}; |
| 2208 |
const send = (target) => shellFetch$1(target, { |
| 2209 |
method: "GET", |
| 2210 |
credentials: "same-origin", |
| 2211 |
headers: { |
| 2212 |
"X-WP-Nonce": cfg.restNonce, |
| 2213 |
Accept: "application/json" |
| 2214 |
}, |
| 2215 |
signal: params.signal |
| 2216 |
}); |
| 2217 |
let response = await send(buildRequestUrl("edit")); |
| 2218 |
if (response.status === 403) { |
| 2219 |
response = await send(buildRequestUrl("authors")); |
| 2220 |
} |
| 2221 |
if (!response.ok) { |
| 2222 |
throw new Error(await readErrorMessage$1(response, "Failed to load users")); |
| 2223 |
} |
| 2224 |
const items = await response.json(); |
| 2225 |
const total = Number(response.headers.get("X-WP-Total") ?? items.length); |
| 2226 |
const totalPages = Number( |
| 2227 |
response.headers.get("X-WP-TotalPages") ?? 1 |
| 2228 |
); |
| 2229 |
return { items, total, totalPages }; |
| 2230 |
} |
| 2231 |
function fetchUserFootprint(userId) { |
| 2232 |
return getJson( |
| 2233 |
buildUrl$1(`desktop-mode/v1/user-footprint/${userId}`) |
| 2234 |
); |
| 2235 |
} |
| 2236 |
function buildEditUserUrl(id) { |
| 2237 |
const cfg = getConfig(); |
| 2238 |
const base = cfg.editUserUrlBase || cfg.editPostUrlBase; |
| 2239 |
const sep = base.includes("?") ? "&" : "?"; |
| 2240 |
return `${base}${sep}user_id=${encodeURIComponent(String(id))}`; |
| 2241 |
} |
| 2242 |
function buildEditUrl(id) { |
| 2243 |
const cfg = getConfig(); |
| 2244 |
const base = cfg.editPostUrlBase; |
| 2245 |
const sep = base.includes("?") ? "&" : "?"; |
| 2246 |
return `${base}${sep}post=${encodeURIComponent(String(id))}&action=edit`; |
| 2247 |
} |
| 2248 |
async function getJson(url) { |
| 2249 |
const cfg = getConfig(); |
| 2250 |
const response = await shellFetch$1(url, { |
| 2251 |
method: "GET", |
| 2252 |
credentials: "same-origin", |
| 2253 |
headers: { |
| 2254 |
"X-WP-Nonce": cfg.restNonce, |
| 2255 |
Accept: "application/json" |
| 2256 |
} |
| 2257 |
}); |
| 2258 |
if (!response.ok) { |
| 2259 |
throw new Error(await readErrorMessage$1(response, "Failed to load")); |
| 2260 |
} |
| 2261 |
return await response.json(); |
| 2262 |
} |
| 2263 |
function fetchUserStats(id) { |
| 2264 |
return getJson(buildUrl$1(`desktop-mode/v1/user-stats/${id}`)); |
| 2265 |
} |
| 2266 |
function fetchTermStats(taxonomy, id) { |
| 2267 |
const slug = taxonomy.replace(/[^a-zA-Z0-9_-]/g, ""); |
| 2268 |
return getJson( |
| 2269 |
buildUrl$1(`desktop-mode/v1/term-stats/${slug}/${id}`) |
| 2270 |
); |
| 2271 |
} |
| 2272 |
function fetchCommentStats(id) { |
| 2273 |
return getJson( |
| 2274 |
buildUrl$1(`desktop-mode/v1/comment-stats/${id}`) |
| 2275 |
); |
| 2276 |
} |
| 2277 |
function fetchUser(id) { |
| 2278 |
return getJson( |
| 2279 |
buildUrl$1(`wp/v2/users/${id}?context=edit&_fields=id,name,slug,description,avatar_urls,link`) |
| 2280 |
); |
| 2281 |
} |
| 2282 |
function fetchComments(postId) { |
| 2283 |
return getJson( |
| 2284 |
buildUrl$1( |
| 2285 |
`wp/v2/comments?post=${postId}&per_page=100&_fields=id,post,author,author_name,author_avatar_urls,date,content,status,parent` |
| 2286 |
) |
| 2287 |
); |
| 2288 |
} |
| 2289 |
function fetchTerms(taxonomy, ids) { |
| 2290 |
if (ids.length === 0) { |
| 2291 |
return Promise.resolve([]); |
| 2292 |
} |
| 2293 |
return getJson( |
| 2294 |
buildUrl$1( |
| 2295 |
`wp/v2/${taxonomy}?include=${ids.join(",")}&per_page=100&_fields=id,name,slug,taxonomy,description,count,link` |
| 2296 |
) |
| 2297 |
); |
| 2298 |
} |
| 2299 |
function fetchAttachedMedia(postId) { |
| 2300 |
return getJson( |
| 2301 |
buildUrl$1( |
| 2302 |
`wp/v2/media?parent=${postId}&per_page=100&_fields=id,title,source_url,mime_type,alt_text,date,media_details` |
| 2303 |
) |
| 2304 |
); |
| 2305 |
} |
| 2306 |
function fetchMediaByIds(ids) { |
| 2307 |
const unique = Array.from(new Set(ids.filter((id) => id > 0))); |
| 2308 |
if (unique.length === 0) { |
| 2309 |
return Promise.resolve([]); |
| 2310 |
} |
| 2311 |
return getJson( |
| 2312 |
buildUrl$1( |
| 2313 |
`wp/v2/media?include=${unique.join(",")}&per_page=${unique.length}&_fields=id,title,source_url,mime_type,alt_text,date,media_details` |
| 2314 |
) |
| 2315 |
); |
| 2316 |
} |
| 2317 |
function fetchRevisions(entity, postId) { |
| 2318 |
return getJson( |
| 2319 |
buildUrl$1( |
| 2320 |
`${entity.restPath}/${postId}/revisions?_fields=id,date,modified,author,title` |
| 2321 |
) |
| 2322 |
); |
| 2323 |
} |
| 2324 |
function fetchRevision(entity, postId, revisionId) { |
| 2325 |
return getJson( |
| 2326 |
buildUrl$1( |
| 2327 |
`${entity.restPath}/${postId}/revisions/${revisionId}?_fields=id,date,modified,author,title,content,excerpt` |
| 2328 |
) |
| 2329 |
); |
| 2330 |
} |
| 2331 |
const WINDOW_ID$1 = "desktop-mode-my-wordpress"; |
| 2332 |
function shellFetch(input, init) { |
| 2333 |
return trackedFetch(input, init, { |
| 2334 |
windowId: WINDOW_ID$1, |
| 2335 |
source: "desktop-mode/my-wordpress" |
| 2336 |
}); |
| 2337 |
} |
| 2338 |
function buildUrl(path) { |
| 2339 |
return joinRestUrl(getConfig().restRoot, path); |
| 2340 |
} |
| 2341 |
async function readErrorMessage(response, fallback) { |
| 2342 |
let message = `${response.status} ${response.statusText || fallback}`; |
| 2343 |
try { |
| 2344 |
const json = await response.json(); |
| 2345 |
if (json && typeof json.message === "string") { |
| 2346 |
message = json.message; |
| 2347 |
} |
| 2348 |
} catch { |
| 2349 |
} |
| 2350 |
return message; |
| 2351 |
} |
| 2352 |
async function fetchMediaPage(entity, params) { |
| 2353 |
const cfg = getConfig(); |
| 2354 |
const url = new URL(buildUrl(entity.restPath)); |
| 2355 |
url.searchParams.set("page", String(params.page)); |
| 2356 |
url.searchParams.set("per_page", String(params.perPage)); |
| 2357 |
url.searchParams.set( |
| 2358 |
"_fields", |
| 2359 |
"id,title,date,mime_type,source_url,alt_text,caption,description,author,media_details,_embedded" |
| 2360 |
); |
| 2361 |
url.searchParams.set("_embed", "author"); |
| 2362 |
url.searchParams.set("orderby", "date"); |
| 2363 |
url.searchParams.set("order", "desc"); |
| 2364 |
url.searchParams.set("status", "inherit"); |
| 2365 |
if (params.search) { |
| 2366 |
url.searchParams.set("search", params.search); |
| 2367 |
} |
| 2368 |
const response = await shellFetch(url.toString(), { |
| 2369 |
method: "GET", |
| 2370 |
credentials: "same-origin", |
| 2371 |
headers: { |
| 2372 |
"X-WP-Nonce": cfg.restNonce, |
| 2373 |
Accept: "application/json" |
| 2374 |
}, |
| 2375 |
signal: params.signal |
| 2376 |
}); |
| 2377 |
if (!response.ok) { |
| 2378 |
throw new Error( |
| 2379 |
await readErrorMessage(response, "Failed to load media") |
| 2380 |
); |
| 2381 |
} |
| 2382 |
const items = await response.json(); |
| 2383 |
const total = Number(response.headers.get("X-WP-Total") ?? items.length); |
| 2384 |
const totalPages = Number( |
| 2385 |
response.headers.get("X-WP-TotalPages") ?? 1 |
| 2386 |
); |
| 2387 |
return { items, total, totalPages }; |
| 2388 |
} |
| 2389 |
async function fetchMediaItem(mediaId) { |
| 2390 |
const cfg = getConfig(); |
| 2391 |
const url = new URL(buildUrl(`wp/v2/media/${mediaId}`)); |
| 2392 |
url.searchParams.set( |
| 2393 |
"_fields", |
| 2394 |
"id,title,date,mime_type,source_url,alt_text,caption,description,author,media_details,_embedded" |
| 2395 |
); |
| 2396 |
url.searchParams.set("_embed", "author"); |
| 2397 |
const response = await shellFetch(url.toString(), { |
| 2398 |
method: "GET", |
| 2399 |
credentials: "same-origin", |
| 2400 |
headers: { |
| 2401 |
"X-WP-Nonce": cfg.restNonce, |
| 2402 |
Accept: "application/json" |
| 2403 |
} |
| 2404 |
}); |
| 2405 |
if (!response.ok) { |
| 2406 |
throw new Error( |
| 2407 |
await readErrorMessage(response, "Failed to load media item") |
| 2408 |
); |
| 2409 |
} |
| 2410 |
return await response.json(); |
| 2411 |
} |
| 2412 |
async function deleteMediaItem(mediaId) { |
| 2413 |
const cfg = getConfig(); |
| 2414 |
const url = new URL(buildUrl(`wp/v2/media/${mediaId}`)); |
| 2415 |
url.searchParams.set("force", "true"); |
| 2416 |
const response = await shellFetch(url.toString(), { |
| 2417 |
method: "DELETE", |
| 2418 |
credentials: "same-origin", |
| 2419 |
headers: { |
| 2420 |
"X-WP-Nonce": cfg.restNonce, |
| 2421 |
Accept: "application/json" |
| 2422 |
} |
| 2423 |
}); |
| 2424 |
if (!response.ok) { |
| 2425 |
throw new Error( |
| 2426 |
await readErrorMessage(response, "Failed to delete media item") |
| 2427 |
); |
| 2428 |
} |
| 2429 |
} |
| 2430 |
async function fetchMediaUsage(mediaId) { |
| 2431 |
const cfg = getConfig(); |
| 2432 |
const response = await shellFetch( |
| 2433 |
buildUrl(`desktop-mode/v1/media-usage/${mediaId}`), |
| 2434 |
{ |
| 2435 |
method: "GET", |
| 2436 |
credentials: "same-origin", |
| 2437 |
headers: { |
| 2438 |
"X-WP-Nonce": cfg.restNonce, |
| 2439 |
Accept: "application/json" |
| 2440 |
} |
| 2441 |
} |
| 2442 |
); |
| 2443 |
if (!response.ok) { |
| 2444 |
throw new Error( |
| 2445 |
await readErrorMessage(response, "Failed to load media usage") |
| 2446 |
); |
| 2447 |
} |
| 2448 |
return await response.json(); |
| 2449 |
} |
| 2450 |
const MIME_DASHICON_FALLBACK = "dashicons-media-default"; |
| 2451 |
const MIME_DASHICON_MAP = [ |
| 2452 |
{ test: /^image\//, icon: "dashicons-format-image" }, |
| 2453 |
{ test: /^video\//, icon: "dashicons-format-video" }, |
| 2454 |
{ test: /^audio\//, icon: "dashicons-format-audio" }, |
| 2455 |
{ test: /pdf$/, icon: "dashicons-media-document" }, |
| 2456 |
{ test: /^application\/(zip|x-tar|x-rar|x-7z)/, icon: "dashicons-media-archive" }, |
| 2457 |
{ test: /spreadsheet|excel/, icon: "dashicons-media-spreadsheet" }, |
| 2458 |
{ test: /word|document/, icon: "dashicons-media-document" }, |
| 2459 |
{ test: /^text\//, icon: "dashicons-media-text" } |
| 2460 |
]; |
| 2461 |
function dashiconForMime(mime) { |
| 2462 |
for (const entry of MIME_DASHICON_MAP) { |
| 2463 |
if (entry.test.test(mime)) { |
| 2464 |
return entry.icon; |
| 2465 |
} |
| 2466 |
} |
| 2467 |
return MIME_DASHICON_FALLBACK; |
| 2468 |
} |
| 2469 |
function mimeGroup(mime) { |
| 2470 |
if (mime.startsWith("image/")) { |
| 2471 |
return "image"; |
| 2472 |
} |
| 2473 |
if (mime.startsWith("video/")) { |
| 2474 |
return "video"; |
| 2475 |
} |
| 2476 |
if (mime.startsWith("audio/")) { |
| 2477 |
return "audio"; |
| 2478 |
} |
| 2479 |
return "doc"; |
| 2480 |
} |
| 2481 |
function formatBytes(bytes) { |
| 2482 |
if (!bytes || !Number.isFinite(bytes)) { |
| 2483 |
return ""; |
| 2484 |
} |
| 2485 |
if (bytes < 1024) { |
| 2486 |
return `${bytes} B`; |
| 2487 |
} |
| 2488 |
const units = ["KB", "MB", "GB", "TB"]; |
| 2489 |
let value = bytes / 1024; |
| 2490 |
let unit = 0; |
| 2491 |
while (value >= 1024 && unit < units.length - 1) { |
| 2492 |
value /= 1024; |
| 2493 |
unit += 1; |
| 2494 |
} |
| 2495 |
return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unit]}`; |
| 2496 |
} |
| 2497 |
function formatDate$1(iso) { |
| 2498 |
if (!iso) { |
| 2499 |
return ""; |
| 2500 |
} |
| 2501 |
const d = new Date(iso); |
| 2502 |
if (Number.isNaN(d.valueOf())) { |
| 2503 |
return iso; |
| 2504 |
} |
| 2505 |
return d.toLocaleDateString(void 0, { |
| 2506 |
year: "numeric", |
| 2507 |
month: "short", |
| 2508 |
day: "numeric" |
| 2509 |
}); |
| 2510 |
} |
| 2511 |
function buildMediaVisual(media) { |
| 2512 |
const wrap = document.createElement("div"); |
| 2513 |
wrap.className = "desktop-mode-my-wordpress__media-visual"; |
| 2514 |
const group = mimeGroup(media.mime_type); |
| 2515 |
if (group === "image") { |
| 2516 |
const img = document.createElement("img"); |
| 2517 |
const sizes = media.media_details?.sizes; |
| 2518 |
img.src = sizes?.large?.source_url ?? sizes?.medium_large?.source_url ?? sizes?.medium?.source_url ?? media.source_url; |
| 2519 |
img.alt = media.alt_text ?? stripTags(media.title.rendered); |
| 2520 |
img.loading = "lazy"; |
| 2521 |
img.decoding = "async"; |
| 2522 |
img.className = "desktop-mode-my-wordpress__media-image"; |
| 2523 |
wrap.appendChild(img); |
| 2524 |
return wrap; |
| 2525 |
} |
| 2526 |
if (group === "video") { |
| 2527 |
const video = document.createElement("video"); |
| 2528 |
video.controls = true; |
| 2529 |
video.preload = "metadata"; |
| 2530 |
video.src = media.source_url; |
| 2531 |
const sizes = media.media_details?.sizes; |
| 2532 |
const poster = sizes?.large?.source_url ?? sizes?.medium?.source_url ?? ""; |
| 2533 |
if (poster) { |
| 2534 |
video.poster = poster; |
| 2535 |
} |
| 2536 |
video.className = "desktop-mode-my-wordpress__media-video"; |
| 2537 |
wrap.appendChild(video); |
| 2538 |
return wrap; |
| 2539 |
} |
| 2540 |
if (group === "audio") { |
| 2541 |
const stack = document.createElement("div"); |
| 2542 |
stack.className = "desktop-mode-my-wordpress__media-audio-stack"; |
| 2543 |
const icon2 = document.createElement("span"); |
| 2544 |
icon2.className = "desktop-mode-my-wordpress__media-fallback-icon dashicons " + dashiconForMime(media.mime_type); |
| 2545 |
icon2.setAttribute("aria-hidden", "true"); |
| 2546 |
stack.appendChild(icon2); |
| 2547 |
const audio = document.createElement("audio"); |
| 2548 |
audio.controls = true; |
| 2549 |
audio.preload = "metadata"; |
| 2550 |
audio.src = media.source_url; |
| 2551 |
audio.className = "desktop-mode-my-wordpress__media-audio"; |
| 2552 |
stack.appendChild(audio); |
| 2553 |
wrap.appendChild(stack); |
| 2554 |
return wrap; |
| 2555 |
} |
| 2556 |
const icon = document.createElement("span"); |
| 2557 |
icon.className = "desktop-mode-my-wordpress__media-fallback-icon dashicons " + dashiconForMime(media.mime_type); |
| 2558 |
icon.setAttribute("aria-hidden", "true"); |
| 2559 |
wrap.appendChild(icon); |
| 2560 |
const link = document.createElement("a"); |
| 2561 |
link.href = media.source_url; |
| 2562 |
link.target = "_blank"; |
| 2563 |
link.rel = "noopener noreferrer"; |
| 2564 |
link.className = "desktop-mode-my-wordpress__media-doc-link"; |
| 2565 |
link.textContent = __("Open file", "desktop-mode"); |
| 2566 |
wrap.appendChild(link); |
| 2567 |
return wrap; |
| 2568 |
} |
| 2569 |
function buildMetaRow(label, value) { |
| 2570 |
if (typeof value === "string" && value.trim() === "") { |
| 2571 |
return null; |
| 2572 |
} |
| 2573 |
const dt = document.createElement("dt"); |
| 2574 |
dt.className = "desktop-mode-my-wordpress__media-meta-term"; |
| 2575 |
dt.textContent = label; |
| 2576 |
const dd = document.createElement("dd"); |
| 2577 |
dd.className = "desktop-mode-my-wordpress__media-meta-value"; |
| 2578 |
if (typeof value === "string") { |
| 2579 |
dd.textContent = value; |
| 2580 |
} else { |
| 2581 |
dd.appendChild(value); |
| 2582 |
} |
| 2583 |
return [dt, dd]; |
| 2584 |
} |
| 2585 |
function buildMetadataGrid(media) { |
| 2586 |
const grid = document.createElement("dl"); |
| 2587 |
grid.className = "desktop-mode-my-wordpress__media-meta"; |
| 2588 |
const filename = media.media_details?.file ? media.media_details.file.split("/").pop() ?? "" : media.source_url.split("/").pop() ?? ""; |
| 2589 |
const dims = media.media_details?.width && media.media_details?.height ? `${media.media_details.width} × ${media.media_details.height}` : ""; |
| 2590 |
const filesize = formatBytes(media.media_details?.filesize); |
| 2591 |
const uploaded = formatDate$1(media.date); |
| 2592 |
const uploader = media._embedded?.author?.[0]?.name ?? ""; |
| 2593 |
const alt = (media.alt_text ?? "").trim(); |
| 2594 |
const caption = stripTags(media.caption?.rendered ?? ""); |
| 2595 |
const description = stripTags(media.description?.rendered ?? ""); |
| 2596 |
const rows = [ |
| 2597 |
buildMetaRow(__("Filename", "desktop-mode"), filename), |
| 2598 |
buildMetaRow(__("Type", "desktop-mode"), media.mime_type), |
| 2599 |
buildMetaRow(__("Dimensions", "desktop-mode"), dims), |
| 2600 |
buildMetaRow(__("File size", "desktop-mode"), filesize), |
| 2601 |
buildMetaRow(__("Uploaded", "desktop-mode"), uploaded), |
| 2602 |
buildMetaRow(__("Uploader", "desktop-mode"), uploader), |
| 2603 |
buildMetaRow(__("Alt text", "desktop-mode"), alt), |
| 2604 |
buildMetaRow(__("Caption", "desktop-mode"), caption), |
| 2605 |
buildMetaRow(__("Description", "desktop-mode"), description) |
| 2606 |
]; |
| 2607 |
for (const pair of rows) { |
| 2608 |
if (pair) { |
| 2609 |
grid.append(...pair); |
| 2610 |
} |
| 2611 |
} |
| 2612 |
return grid; |
| 2613 |
} |
| 2614 |
function fireSlot(host, slot, entityId, kind, item) { |
| 2615 |
doAction( |
| 2616 |
"desktop-mode.my-wordpress.preview-extras", |
| 2617 |
{ |
| 2618 |
slot, |
| 2619 |
container: host, |
| 2620 |
entityId, |
| 2621 |
kind, |
| 2622 |
item |
| 2623 |
} |
| 2624 |
); |
| 2625 |
} |
| 2626 |
function resolvePreviewActions(descriptors, ctx) { |
| 2627 |
const scoped = descriptors.filter((a) => { |
| 2628 |
if (a.sections && a.sections.length > 0) { |
| 2629 |
if (!a.sections.includes(ctx.entityId) && !a.sections.includes("*")) { |
| 2630 |
return false; |
| 2631 |
} |
| 2632 |
} |
| 2633 |
if (a.mime) { |
| 2634 |
if (!ctx.mime) { |
| 2635 |
return false; |
| 2636 |
} |
| 2637 |
try { |
| 2638 |
const re = new RegExp(a.mime); |
| 2639 |
if (!re.test(ctx.mime)) { |
| 2640 |
return false; |
| 2641 |
} |
| 2642 |
} catch { |
| 2643 |
return false; |
| 2644 |
} |
| 2645 |
} |
| 2646 |
return true; |
| 2647 |
}); |
| 2648 |
const merged = applyFilters("desktop-mode.my-wordpress.preview-actions", scoped, ctx); |
| 2649 |
return Array.isArray(merged) ? merged : scoped; |
| 2650 |
} |
| 2651 |
function buildActionRow(actions, ctx) { |
| 2652 |
const visible = actions.filter( |
| 2653 |
(a) => typeof a.isVisible === "function" ? a.isVisible(ctx) : true |
| 2654 |
); |
| 2655 |
if (visible.length === 0) { |
| 2656 |
return null; |
| 2657 |
} |
| 2658 |
const row = document.createElement("div"); |
| 2659 |
row.className = "desktop-mode-my-wordpress__media-actions"; |
| 2660 |
row.setAttribute("role", "toolbar"); |
| 2661 |
for (const action of visible) { |
| 2662 |
const btn = document.createElement("wpd-button"); |
| 2663 |
btn.setAttribute("variant", "secondary"); |
| 2664 |
btn.dataset.actionId = action.id; |
| 2665 |
if (action.icon) { |
| 2666 |
btn.setAttribute("icon", action.icon); |
| 2667 |
} |
| 2668 |
btn.textContent = action.label; |
| 2669 |
btn.addEventListener("click", () => { |
| 2670 |
if (typeof action.onSelect === "function") { |
| 2671 |
try { |
| 2672 |
void action.onSelect(ctx); |
| 2673 |
} catch { |
| 2674 |
console.error( |
| 2675 |
`[my-wordpress] preview action ${action.id} threw.` |
| 2676 |
); |
| 2677 |
} |
| 2678 |
} |
| 2679 |
}); |
| 2680 |
row.appendChild(btn); |
| 2681 |
} |
| 2682 |
return row; |
| 2683 |
} |
| 2684 |
function renderMediaPreview(host, media, opts) { |
| 2685 |
host.replaceChildren(); |
| 2686 |
const pane = document.createElement("div"); |
| 2687 |
pane.className = "desktop-mode-my-wordpress__media-pane"; |
| 2688 |
const header = document.createElement("header"); |
| 2689 |
header.className = "desktop-mode-my-wordpress__media-header"; |
| 2690 |
const heading = document.createElement("h2"); |
| 2691 |
heading.className = "desktop-mode-my-wordpress__media-title"; |
| 2692 |
heading.textContent = stripTags(media.title.rendered) || __("(no title)", "desktop-mode"); |
| 2693 |
header.appendChild(heading); |
| 2694 |
pane.appendChild(header); |
| 2695 |
const item = media; |
| 2696 |
const ctx = { |
| 2697 |
entityId: opts.entityId, |
| 2698 |
kind: "media", |
| 2699 |
mime: media.mime_type, |
| 2700 |
item |
| 2701 |
}; |
| 2702 |
fireSlot(header, "header", opts.entityId, "media", item); |
| 2703 |
pane.appendChild(buildMediaVisual(media)); |
| 2704 |
const meta = buildMetadataGrid(media); |
| 2705 |
pane.appendChild(meta); |
| 2706 |
fireSlot(meta, "meta", opts.entityId, "media", item); |
| 2707 |
const resolved = resolvePreviewActions(opts.previewActions, ctx); |
| 2708 |
const actionRow = buildActionRow(resolved, ctx); |
| 2709 |
if (actionRow) { |
| 2710 |
pane.appendChild(actionRow); |
| 2711 |
} |
| 2712 |
if (opts.onOpenDetail) { |
| 2713 |
const footer = document.createElement("footer"); |
| 2714 |
footer.className = "desktop-mode-my-wordpress__article-footer"; |
| 2715 |
const drillBtn = document.createElement("wpd-button"); |
| 2716 |
drillBtn.setAttribute("variant", "primary"); |
| 2717 |
drillBtn.textContent = __("See where this is used", "desktop-mode"); |
| 2718 |
drillBtn.title = __( |
| 2719 |
"Show the posts, pages, and custom-post-type entries that reference this file.", |
| 2720 |
"desktop-mode" |
| 2721 |
); |
| 2722 |
drillBtn.addEventListener("click", () => opts.onOpenDetail?.()); |
| 2723 |
footer.appendChild(drillBtn); |
| 2724 |
pane.appendChild(footer); |
| 2725 |
fireSlot(footer, "footer", opts.entityId, "media", item); |
| 2726 |
} else { |
| 2727 |
const footer = document.createElement("div"); |
| 2728 |
footer.className = "desktop-mode-my-wordpress__media-footer"; |
| 2729 |
pane.appendChild(footer); |
| 2730 |
fireSlot(footer, "footer", opts.entityId, "media", item); |
| 2731 |
} |
| 2732 |
host.appendChild(pane); |
| 2733 |
} |
| 2734 |
const lastQueryByMediaEntity = /* @__PURE__ */ new Map(); |
| 2735 |
function describeCount(ctx) { |
| 2736 |
if (ctx.total === 0 && ctx.loaded === 0) { |
| 2737 |
return __("No media yet.", "desktop-mode"); |
| 2738 |
} |
| 2739 |
if (ctx.total > ctx.loaded && ctx.loaded > 0) { |
| 2740 |
return sprintf( |
| 2741 |
// translators: 1: visible item count, 2: total item count. |
| 2742 |
__("%1$d of %2$d items", "desktop-mode"), |
| 2743 |
ctx.loaded, |
| 2744 |
ctx.total |
| 2745 |
); |
| 2746 |
} |
| 2747 |
const n = Math.max(ctx.total, ctx.loaded); |
| 2748 |
return sprintf( |
| 2749 |
// translators: %d is a count of media items. |
| 2750 |
_n("%d item", "%d items", n), |
| 2751 |
n |
| 2752 |
); |
| 2753 |
} |
| 2754 |
function paintStatus$2(ctx) { |
| 2755 |
const segments = [ |
| 2756 |
{ |
| 2757 |
id: "count", |
| 2758 |
label: describeCount(ctx), |
| 2759 |
align: "start", |
| 2760 |
sort: 10 |
| 2761 |
} |
| 2762 |
]; |
| 2763 |
if (ctx.totalPages > 1) { |
| 2764 |
segments.push({ |
| 2765 |
id: "page", |
| 2766 |
label: sprintf( |
| 2767 |
// translators: 1: current page, 2: total pages. |
| 2768 |
__("Page %1$d of %2$d", "desktop-mode"), |
| 2769 |
Math.max(ctx.page, 1), |
| 2770 |
ctx.totalPages |
| 2771 |
), |
| 2772 |
align: "end", |
| 2773 |
sort: 10 |
| 2774 |
}); |
| 2775 |
} |
| 2776 |
const filtered = applyFilters( |
| 2777 |
"desktop-mode.my-wordpress.status-bar", |
| 2778 |
segments, |
| 2779 |
{ view: "list", entityId: ctx.entity.id } |
| 2780 |
); |
| 2781 |
renderStatusBarSegments( |
| 2782 |
ctx.statusBar, |
| 2783 |
Array.isArray(filtered) ? filtered : segments |
| 2784 |
); |
| 2785 |
} |
| 2786 |
function buildMediaTile(ctx, media) { |
| 2787 |
const titleText = stripTags(media.title.rendered) || __("(no title)", "desktop-mode"); |
| 2788 |
const sizes = media.media_details?.sizes; |
| 2789 |
const thumbUrl = media.mime_type.startsWith("image/") ? sizes?.thumbnail?.source_url ?? sizes?.medium?.source_url ?? media.source_url : ""; |
| 2790 |
const tile = buildTileFromSpec({ |
| 2791 |
type: "attachment", |
| 2792 |
ref: String(media.id), |
| 2793 |
label: titleText, |
| 2794 |
thumbnail: thumbUrl || void 0, |
| 2795 |
icon: thumbUrl ? void 0 : dashiconForMime(media.mime_type), |
| 2796 |
role: "entry", |
| 2797 |
dataset: { mediaId: media.id, mime: media.mime_type }, |
| 2798 |
extraClasses: [ |
| 2799 |
"desktop-mode-my-wordpress__media-tile", |
| 2800 |
"desktop-mode-my-wordpress__tile", |
| 2801 |
"desktop-mode-my-wordpress__tile--media" |
| 2802 |
] |
| 2803 |
}); |
| 2804 |
attachTileDragOut(tile, { |
| 2805 |
kind: "attachment", |
| 2806 |
ref: String(media.id), |
| 2807 |
title: titleText, |
| 2808 |
icon: dashiconForMime(media.mime_type), |
| 2809 |
// Cross-frame bridge payload — lets the Gutenberg drop- |
| 2810 |
// receiver build a `core/image` / `core/video` / `core/audio` |
| 2811 |
// / `core/file` block when this tile is dropped on an open |
| 2812 |
// editor iframe. The full-size source URL is the right block |
| 2813 |
// attribute regardless of mime; the receiver picks the |
| 2814 |
// concrete block from the MIME prefix. |
| 2815 |
bridgePayload: { |
| 2816 |
kind: "attachment", |
| 2817 |
id: media.id, |
| 2818 |
url: media.source_url, |
| 2819 |
title: titleText, |
| 2820 |
alt: stripTags(media.alt_text ?? ""), |
| 2821 |
mime: media.mime_type, |
| 2822 |
thumbnailUrl: thumbUrl || void 0, |
| 2823 |
sizes: media.media_details?.sizes |
| 2824 |
} |
| 2825 |
}); |
| 2826 |
tile.addEventListener("click", () => selectTile$1(ctx, tile, media)); |
| 2827 |
tile.addEventListener("dblclick", (e) => { |
| 2828 |
e.preventDefault(); |
| 2829 |
ctx.host.navigate({ |
| 2830 |
kind: "media-detail", |
| 2831 |
entityId: ctx.entity.id, |
| 2832 |
mediaId: media.id, |
| 2833 |
mediaTitle: titleText |
| 2834 |
}); |
| 2835 |
}); |
| 2836 |
tile.addEventListener("contextmenu", (e) => { |
| 2837 |
e.preventDefault(); |
| 2838 |
openMediaTileMenu(ctx, tile, media, titleText, { |
| 2839 |
x: e.clientX, |
| 2840 |
y: e.clientY |
| 2841 |
}); |
| 2842 |
}); |
| 2843 |
return tile; |
| 2844 |
} |
| 2845 |
function openMediaTileMenu(ctx, tile, media, titleText, pos) { |
| 2846 |
closeAnyMediaTileMenu(); |
| 2847 |
selectTile$1(ctx, tile, media); |
| 2848 |
const menu = document.createElement("wpd-context-menu"); |
| 2849 |
menu.setAttribute("open", ""); |
| 2850 |
menu.classList.add("desktop-mode-my-wordpress__menu"); |
| 2851 |
menu.style.left = `${pos.x}px`; |
| 2852 |
menu.style.top = `${pos.y}px`; |
| 2853 |
const base = [ |
| 2854 |
{ |
| 2855 |
id: "navigate-into", |
| 2856 |
label: __("Navigate into", "desktop-mode"), |
| 2857 |
icon: "dashicons-category" |
| 2858 |
}, |
| 2859 |
{ |
| 2860 |
id: "open-source", |
| 2861 |
label: __("Open file in new tab", "desktop-mode"), |
| 2862 |
icon: "dashicons-external" |
| 2863 |
}, |
| 2864 |
{ |
| 2865 |
id: "delete", |
| 2866 |
label: __("Delete permanently", "desktop-mode"), |
| 2867 |
icon: "dashicons-trash", |
| 2868 |
danger: true |
| 2869 |
} |
| 2870 |
]; |
| 2871 |
const filterCtx = { |
| 2872 |
entityId: ctx.entity.id, |
| 2873 |
kind: "attachment", |
| 2874 |
item: media |
| 2875 |
}; |
| 2876 |
const options = applyFilters( |
| 2877 |
"desktop-mode.my-wordpress.tile-context-menu", |
| 2878 |
base, |
| 2879 |
filterCtx |
| 2880 |
); |
| 2881 |
const finalOptions = Array.isArray(options) ? options : base; |
| 2882 |
for (const o of finalOptions) { |
| 2883 |
const opt = document.createElement("wpd-context-menu-option"); |
| 2884 |
opt.dataset.menuItemId = o.id; |
| 2885 |
opt.setAttribute("value", o.id); |
| 2886 |
opt.setAttribute("icon", o.icon); |
| 2887 |
if (o.danger) { |
| 2888 |
opt.setAttribute("danger", ""); |
| 2889 |
} |
| 2890 |
opt.textContent = o.label; |
| 2891 |
menu.appendChild(opt); |
| 2892 |
} |
| 2893 |
menu.addEventListener("wpd-context-menu-pick", (e) => { |
| 2894 |
const detail = e.detail; |
| 2895 |
closeAnyMediaTileMenu(); |
| 2896 |
if (detail.id === "navigate-into") { |
| 2897 |
ctx.host.navigate({ |
| 2898 |
kind: "media-detail", |
| 2899 |
entityId: ctx.entity.id, |
| 2900 |
mediaId: media.id, |
| 2901 |
mediaTitle: titleText |
| 2902 |
}); |
| 2903 |
return; |
| 2904 |
} |
| 2905 |
if (detail.id === "open-source") { |
| 2906 |
window.open(media.source_url, "_blank", "noopener,noreferrer"); |
| 2907 |
return; |
| 2908 |
} |
| 2909 |
if (detail.id === "delete") { |
| 2910 |
void confirmDeleteMedia(ctx, tile, media, titleText); |
| 2911 |
return; |
| 2912 |
} |
| 2913 |
const match = finalOptions.find((o) => o.id === detail.id); |
| 2914 |
if (match && typeof match.onSelect === "function") { |
| 2915 |
try { |
| 2916 |
match.onSelect(); |
| 2917 |
} catch (err) { |
| 2918 |
console.error( |
| 2919 |
`[my-wordpress/media] tile-context-menu '${detail.id}' onSelect threw:`, |
| 2920 |
err |
| 2921 |
); |
| 2922 |
} |
| 2923 |
} |
| 2924 |
}); |
| 2925 |
document.body.appendChild(menu); |
| 2926 |
const rect = menu.getBoundingClientRect(); |
| 2927 |
if (rect.right > window.innerWidth) { |
| 2928 |
menu.style.left = `${Math.max( |
| 2929 |
0, |
| 2930 |
window.innerWidth - rect.width - 8 |
| 2931 |
)}px`; |
| 2932 |
} |
| 2933 |
if (rect.bottom > window.innerHeight) { |
| 2934 |
menu.style.top = `${Math.max( |
| 2935 |
0, |
| 2936 |
window.innerHeight - rect.height - 8 |
| 2937 |
)}px`; |
| 2938 |
} |
| 2939 |
queueMicrotask(() => { |
| 2940 |
const onDocPointerDown = (ev) => { |
| 2941 |
if (ev.target instanceof Node && menu.contains(ev.target)) { |
| 2942 |
return; |
| 2943 |
} |
| 2944 |
closeAnyMediaTileMenu(); |
| 2945 |
}; |
| 2946 |
const onDocKey = (ev) => { |
| 2947 |
if (ev.key === "Escape") { |
| 2948 |
closeAnyMediaTileMenu(); |
| 2949 |
} |
| 2950 |
}; |
| 2951 |
document.addEventListener("pointerdown", onDocPointerDown, true); |
| 2952 |
document.addEventListener("keydown", onDocKey); |
| 2953 |
menu.addEventListener("tile-menu-closed", () => { |
| 2954 |
document.removeEventListener( |
| 2955 |
"pointerdown", |
| 2956 |
onDocPointerDown, |
| 2957 |
true |
| 2958 |
); |
| 2959 |
document.removeEventListener("keydown", onDocKey); |
| 2960 |
}); |
| 2961 |
}); |
| 2962 |
} |
| 2963 |
function closeAnyMediaTileMenu() { |
| 2964 |
document.querySelectorAll("wpd-context-menu.desktop-mode-my-wordpress__menu").forEach((n) => { |
| 2965 |
n.dispatchEvent(new CustomEvent("tile-menu-closed")); |
| 2966 |
n.remove(); |
| 2967 |
}); |
| 2968 |
} |
| 2969 |
async function confirmDeleteMedia(ctx, tile, media, titleText) { |
| 2970 |
const ok = await wpdConfirm({ |
| 2971 |
title: __("Delete media?", "desktop-mode"), |
| 2972 |
message: sprintf( |
| 2973 |
// translators: %s is a media item title. |
| 2974 |
__("“%s” will be permanently deleted. This cannot be undone.", "desktop-mode"), |
| 2975 |
titleText |
| 2976 |
), |
| 2977 |
confirmLabel: __("Delete", "desktop-mode"), |
| 2978 |
cancelLabel: __("Cancel", "desktop-mode") |
| 2979 |
}); |
| 2980 |
if (!ok) { |
| 2981 |
return; |
| 2982 |
} |
| 2983 |
try { |
| 2984 |
await deleteMediaItem(media.id); |
| 2985 |
removeMediaFromList(ctx, tile, media.id); |
| 2986 |
showToast$1({ message: __("Media deleted.", "desktop-mode") }); |
| 2987 |
} catch (err) { |
| 2988 |
const message = err instanceof Error ? err.message : __("Couldn’t delete that file.", "desktop-mode"); |
| 2989 |
showToast$1({ message }); |
| 2990 |
} |
| 2991 |
} |
| 2992 |
function removeMediaFromList(ctx, tile, mediaId) { |
| 2993 |
tile.remove(); |
| 2994 |
if (ctx.selectedId === mediaId) { |
| 2995 |
ctx.selectedId = null; |
| 2996 |
ctx.selectedTile = null; |
| 2997 |
ctx.preview.replaceChildren(); |
| 2998 |
const placeholder = document.createElement("div"); |
| 2999 |
placeholder.className = "desktop-mode-my-wordpress__preview-empty"; |
| 3000 |
placeholder.textContent = __( |
| 3001 |
"Select a media item to preview it here.", |
| 3002 |
"desktop-mode" |
| 3003 |
); |
| 3004 |
ctx.preview.appendChild(placeholder); |
| 3005 |
} |
| 3006 |
ctx.loaded = Math.max(0, ctx.loaded - 1); |
| 3007 |
ctx.total = Math.max(0, ctx.total - 1); |
| 3008 |
paintStatus$2(ctx); |
| 3009 |
} |
| 3010 |
function selectTile$1(ctx, tile, media) { |
| 3011 |
if (ctx.selectedTile) { |
| 3012 |
ctx.selectedTile.removeAttribute("selected"); |
| 3013 |
} |
| 3014 |
tile.setAttribute("selected", ""); |
| 3015 |
ctx.selectedTile = tile; |
| 3016 |
ctx.selectedId = media.id; |
| 3017 |
const titleText = stripTags(media.title.rendered) || __("(no title)", "desktop-mode"); |
| 3018 |
renderMediaPreview(ctx.preview, media, { |
| 3019 |
entityId: ctx.entity.id, |
| 3020 |
previewActions: ctx.previewActions, |
| 3021 |
onOpenDetail: () => { |
| 3022 |
ctx.host.navigate({ |
| 3023 |
kind: "media-detail", |
| 3024 |
entityId: ctx.entity.id, |
| 3025 |
mediaId: media.id, |
| 3026 |
mediaTitle: titleText |
| 3027 |
}); |
| 3028 |
} |
| 3029 |
}); |
| 3030 |
} |
| 3031 |
function renderEmpty(host, message) { |
| 3032 |
const empty = document.createElement("div"); |
| 3033 |
empty.className = "desktop-mode-my-wordpress__empty"; |
| 3034 |
empty.textContent = message; |
| 3035 |
host.appendChild(empty); |
| 3036 |
} |
| 3037 |
function renderMediaList(host, entity) { |
| 3038 |
const cfg = getConfig(); |
| 3039 |
const initialQuery = lastQueryByMediaEntity.get(entity.id) ?? ""; |
| 3040 |
const toolbar = renderListToolbar({ |
| 3041 |
placeholder: __("Search media…", "desktop-mode"), |
| 3042 |
ariaLabel: __("Search media", "desktop-mode"), |
| 3043 |
initialValue: initialQuery, |
| 3044 |
onSearchChange: (q) => { |
| 3045 |
lastQueryByMediaEntity.set(entity.id, q); |
| 3046 |
void resetForSearch(q); |
| 3047 |
} |
| 3048 |
}); |
| 3049 |
host.body.appendChild(toolbar.host); |
| 3050 |
host.addTeardown(() => toolbar.destroy()); |
| 3051 |
const split = document.createElement("div"); |
| 3052 |
split.className = "desktop-mode-my-wordpress__split desktop-mode-my-wordpress__split--media"; |
| 3053 |
const left = document.createElement("div"); |
| 3054 |
left.className = "desktop-mode-my-wordpress__list"; |
| 3055 |
const tiles = document.createElement("div"); |
| 3056 |
tiles.className = "desktop-mode-my-wordpress__media-grid"; |
| 3057 |
tiles.setAttribute("role", "list"); |
| 3058 |
left.appendChild(tiles); |
| 3059 |
const sentinel = document.createElement("div"); |
| 3060 |
sentinel.className = "desktop-mode-my-wordpress__sentinel"; |
| 3061 |
sentinel.setAttribute("aria-hidden", "true"); |
| 3062 |
left.appendChild(sentinel); |
| 3063 |
const right = document.createElement("div"); |
| 3064 |
right.className = "desktop-mode-my-wordpress__preview"; |
| 3065 |
const previewEmpty = document.createElement("div"); |
| 3066 |
previewEmpty.className = "desktop-mode-my-wordpress__preview-empty"; |
| 3067 |
previewEmpty.textContent = __( |
| 3068 |
"Select a media item to preview it here.", |
| 3069 |
"desktop-mode" |
| 3070 |
); |
| 3071 |
right.appendChild(previewEmpty); |
| 3072 |
split.append(left, right); |
| 3073 |
host.body.appendChild(split); |
| 3074 |
const statusBar = host.body.closest("[data-desktop-mode-my-wordpress-root]")?.querySelector( |
| 3075 |
"[data-desktop-mode-my-wordpress-status]" |
| 3076 |
) ?? document.createElement("div"); |
| 3077 |
const ctx = { |
| 3078 |
page: 0, |
| 3079 |
totalPages: 1, |
| 3080 |
total: 0, |
| 3081 |
loaded: 0, |
| 3082 |
loading: false, |
| 3083 |
done: false, |
| 3084 |
tiles, |
| 3085 |
sentinel, |
| 3086 |
preview: right, |
| 3087 |
selectedId: null, |
| 3088 |
selectedTile: null, |
| 3089 |
statusBar, |
| 3090 |
entity, |
| 3091 |
host, |
| 3092 |
previewActions: cfg.previewActions ?? [], |
| 3093 |
query: initialQuery, |
| 3094 |
abort: null |
| 3095 |
}; |
| 3096 |
host.addTeardown(() => ctx.abort?.abort()); |
| 3097 |
const sentinelIsVisible = () => { |
| 3098 |
const sr = sentinel.getBoundingClientRect(); |
| 3099 |
const rr = left.getBoundingClientRect(); |
| 3100 |
const slack = 200; |
| 3101 |
return sr.top < rr.bottom + slack && sr.bottom > rr.top - slack; |
| 3102 |
}; |
| 3103 |
const loadMore = async () => { |
| 3104 |
if (ctx.loading || ctx.done) { |
| 3105 |
return; |
| 3106 |
} |
| 3107 |
ctx.loading = true; |
| 3108 |
const nextPage = ctx.page + 1; |
| 3109 |
const perPage = cfg.mediaPerPage ?? 48; |
| 3110 |
const queryAtFetchTime = ctx.query; |
| 3111 |
const controller = new AbortController(); |
| 3112 |
ctx.abort = controller; |
| 3113 |
try { |
| 3114 |
const result = await fetchMediaPage(entity, { |
| 3115 |
page: nextPage, |
| 3116 |
perPage, |
| 3117 |
search: queryAtFetchTime || void 0, |
| 3118 |
signal: controller.signal |
| 3119 |
}); |
| 3120 |
if (ctx.query !== queryAtFetchTime) { |
| 3121 |
return; |
| 3122 |
} |
| 3123 |
ctx.page = nextPage; |
| 3124 |
ctx.totalPages = result.totalPages; |
| 3125 |
ctx.total = result.total; |
| 3126 |
if (result.items.length === 0 && nextPage === 1) { |
| 3127 |
renderEmpty( |
| 3128 |
tiles, |
| 3129 |
queryAtFetchTime ? sprintf( |
| 3130 |
// translators: %s is the user-entered search query. |
| 3131 |
__('No media match "%s".', "desktop-mode"), |
| 3132 |
queryAtFetchTime |
| 3133 |
) : __("No media yet.", "desktop-mode") |
| 3134 |
); |
| 3135 |
ctx.done = true; |
| 3136 |
paintStatus$2(ctx); |
| 3137 |
return; |
| 3138 |
} |
| 3139 |
for (const item of result.items) { |
| 3140 |
tiles.appendChild(buildMediaTile(ctx, item)); |
| 3141 |
ctx.loaded += 1; |
| 3142 |
} |
| 3143 |
if (ctx.page >= ctx.totalPages) { |
| 3144 |
ctx.done = true; |
| 3145 |
} |
| 3146 |
paintStatus$2(ctx); |
| 3147 |
} catch (err) { |
| 3148 |
if (err instanceof DOMException && err.name === "AbortError") { |
| 3149 |
return; |
| 3150 |
} |
| 3151 |
const message = err instanceof Error ? err.message : __("Unknown error.", "desktop-mode"); |
| 3152 |
renderEmpty(tiles, message); |
| 3153 |
ctx.done = true; |
| 3154 |
} finally { |
| 3155 |
ctx.loading = false; |
| 3156 |
if (ctx.abort === controller) { |
| 3157 |
ctx.abort = null; |
| 3158 |
} |
| 3159 |
} |
| 3160 |
if (!ctx.done) { |
| 3161 |
requestAnimationFrame(() => { |
| 3162 |
if (sentinelIsVisible()) { |
| 3163 |
void loadMore(); |
| 3164 |
} |
| 3165 |
}); |
| 3166 |
} |
| 3167 |
}; |
| 3168 |
const resetForSearch = async (q) => { |
| 3169 |
ctx.abort?.abort(); |
| 3170 |
ctx.abort = null; |
| 3171 |
ctx.query = q; |
| 3172 |
tiles.classList.add( |
| 3173 |
"desktop-mode-my-wordpress__media-grid--searching" |
| 3174 |
); |
| 3175 |
const controller = new AbortController(); |
| 3176 |
ctx.abort = controller; |
| 3177 |
ctx.loading = true; |
| 3178 |
const perPage = cfg.mediaPerPage ?? 48; |
| 3179 |
try { |
| 3180 |
const result = await fetchMediaPage(entity, { |
| 3181 |
page: 1, |
| 3182 |
perPage, |
| 3183 |
search: q || void 0, |
| 3184 |
signal: controller.signal |
| 3185 |
}); |
| 3186 |
if (ctx.query !== q) { |
| 3187 |
return; |
| 3188 |
} |
| 3189 |
tiles.replaceChildren(); |
| 3190 |
tiles.classList.remove( |
| 3191 |
"desktop-mode-my-wordpress__media-grid--searching" |
| 3192 |
); |
| 3193 |
ctx.page = 1; |
| 3194 |
ctx.totalPages = result.totalPages; |
| 3195 |
ctx.total = result.total; |
| 3196 |
ctx.loaded = 0; |
| 3197 |
ctx.done = ctx.page >= ctx.totalPages; |
| 3198 |
ctx.selectedId = null; |
| 3199 |
ctx.selectedTile = null; |
| 3200 |
ctx.preview.replaceChildren(); |
| 3201 |
const emptyPreview = document.createElement("div"); |
| 3202 |
emptyPreview.className = "desktop-mode-my-wordpress__preview-empty"; |
| 3203 |
emptyPreview.textContent = __( |
| 3204 |
"Select a media item to preview it here.", |
| 3205 |
"desktop-mode" |
| 3206 |
); |
| 3207 |
ctx.preview.appendChild(emptyPreview); |
| 3208 |
if (result.items.length === 0) { |
| 3209 |
renderEmpty( |
| 3210 |
tiles, |
| 3211 |
q ? sprintf( |
| 3212 |
// translators: %s is the user-entered search query. |
| 3213 |
__('No media match "%s".', "desktop-mode"), |
| 3214 |
q |
| 3215 |
) : __("No media yet.", "desktop-mode") |
| 3216 |
); |
| 3217 |
ctx.done = true; |
| 3218 |
} else { |
| 3219 |
for (const item of result.items) { |
| 3220 |
tiles.appendChild(buildMediaTile(ctx, item)); |
| 3221 |
ctx.loaded += 1; |
| 3222 |
} |
| 3223 |
} |
| 3224 |
paintStatus$2(ctx); |
| 3225 |
} catch (err) { |
| 3226 |
if (err instanceof DOMException && err.name === "AbortError") { |
| 3227 |
return; |
| 3228 |
} |
| 3229 |
tiles.classList.remove( |
| 3230 |
"desktop-mode-my-wordpress__media-grid--searching" |
| 3231 |
); |
| 3232 |
tiles.replaceChildren(); |
| 3233 |
const message = err instanceof Error ? err.message : __("Unknown error.", "desktop-mode"); |
| 3234 |
renderEmpty(tiles, message); |
| 3235 |
ctx.done = true; |
| 3236 |
} finally { |
| 3237 |
ctx.loading = false; |
| 3238 |
if (ctx.abort === controller) { |
| 3239 |
ctx.abort = null; |
| 3240 |
} |
| 3241 |
} |
| 3242 |
if (!ctx.done) { |
| 3243 |
requestAnimationFrame(() => { |
| 3244 |
if (sentinelIsVisible()) { |
| 3245 |
void loadMore(); |
| 3246 |
} |
| 3247 |
}); |
| 3248 |
} |
| 3249 |
}; |
| 3250 |
if (typeof IntersectionObserver !== "undefined") { |
| 3251 |
const observer = new IntersectionObserver( |
| 3252 |
(entries) => { |
| 3253 |
for (const e of entries) { |
| 3254 |
if (e.isIntersecting) { |
| 3255 |
void loadMore(); |
| 3256 |
} |
| 3257 |
} |
| 3258 |
}, |
| 3259 |
{ root: left, rootMargin: "200px 0px" } |
| 3260 |
); |
| 3261 |
observer.observe(sentinel); |
| 3262 |
host.addTeardown(() => observer.disconnect()); |
| 3263 |
} else { |
| 3264 |
const onScroll = () => { |
| 3265 |
if (sentinelIsVisible()) { |
| 3266 |
void loadMore(); |
| 3267 |
} |
| 3268 |
}; |
| 3269 |
left.addEventListener("scroll", onScroll, { passive: true }); |
| 3270 |
host.addTeardown(() => left.removeEventListener("scroll", onScroll)); |
| 3271 |
} |
| 3272 |
const liveNs = `desktop-mode/my-wordpress-media-live-${Math.random().toString(36).slice(2, 8)}`; |
| 3273 |
addAction(FILE_DROP_HOOKS.AFTER_UPLOAD, liveNs, (payload) => { |
| 3274 |
void spliceNewMedia(ctx, payload.result.id); |
| 3275 |
}); |
| 3276 |
host.addTeardown( |
| 3277 |
() => removeAction(FILE_DROP_HOOKS.AFTER_UPLOAD, liveNs) |
| 3278 |
); |
| 3279 |
host.addTeardown(() => closeAnyMediaTileMenu()); |
| 3280 |
paintStatus$2(ctx); |
| 3281 |
void loadMore(); |
| 3282 |
} |
| 3283 |
async function spliceNewMedia(ctx, mediaId) { |
| 3284 |
if (!ctx.tiles.isConnected) { |
| 3285 |
return; |
| 3286 |
} |
| 3287 |
if (ctx.tiles.querySelector( |
| 3288 |
`wpd-tile[data-media-id="${mediaId}"]` |
| 3289 |
)) { |
| 3290 |
return; |
| 3291 |
} |
| 3292 |
try { |
| 3293 |
const item = await fetchMediaItem(mediaId); |
| 3294 |
ctx.tiles.insertBefore(buildMediaTile(ctx, item), ctx.tiles.firstChild); |
| 3295 |
ctx.loaded += 1; |
| 3296 |
ctx.total += 1; |
| 3297 |
paintStatus$2(ctx); |
| 3298 |
} catch (err) { |
| 3299 |
console.warn( |
| 3300 |
"[my-wordpress/media] live-refresh fetch failed:", |
| 3301 |
err |
| 3302 |
); |
| 3303 |
} |
| 3304 |
} |
| 3305 |
function entityForPostType(postType) { |
| 3306 |
const suffix = "/" + postType; |
| 3307 |
return getConfig().entities.find((e) => e.restPath.endsWith(suffix)); |
| 3308 |
} |
| 3309 |
function entityIdForPostType(postType) { |
| 3310 |
const match = entityForPostType(postType); |
| 3311 |
if (match) { |
| 3312 |
return match.id; |
| 3313 |
} |
| 3314 |
if (postType === "page") { |
| 3315 |
return "pages"; |
| 3316 |
} |
| 3317 |
return "posts"; |
| 3318 |
} |
| 3319 |
function entityIconForPostType(postType) { |
| 3320 |
const match = entityForPostType(postType); |
| 3321 |
if (match) { |
| 3322 |
return match.icon; |
| 3323 |
} |
| 3324 |
if (postType === "page") { |
| 3325 |
return "dashicons-admin-page"; |
| 3326 |
} |
| 3327 |
return "dashicons-admin-post"; |
| 3328 |
} |
| 3329 |
function openDetailInWindow(payload) { |
| 3330 |
const myWp = window.wp?.desktop?.myWordpress; |
| 3331 |
myWp?.openDetail?.(payload); |
| 3332 |
} |
| 3333 |
function buildUsageTile(row) { |
| 3334 |
const titleText = row.title || `#${row.postId}`; |
| 3335 |
const tile = buildTileFromSpec({ |
| 3336 |
type: "post", |
| 3337 |
ref: String(row.postId), |
| 3338 |
label: titleText, |
| 3339 |
icon: entityIconForPostType(row.postType), |
| 3340 |
role: "entry", |
| 3341 |
status: row.status, |
| 3342 |
dataset: { postId: row.postId, postType: row.postType }, |
| 3343 |
extraClasses: [ |
| 3344 |
"desktop-mode-my-wordpress__tile", |
| 3345 |
"desktop-mode-my-wordpress__tile--entry", |
| 3346 |
"desktop-mode-my-wordpress__media-tile", |
| 3347 |
"desktop-mode-my-wordpress__tile--usage" |
| 3348 |
] |
| 3349 |
}); |
| 3350 |
attachTileDragOut(tile, { |
| 3351 |
kind: "post", |
| 3352 |
ref: String(row.postId), |
| 3353 |
title: titleText, |
| 3354 |
icon: entityIconForPostType(row.postType) |
| 3355 |
}); |
| 3356 |
return tile; |
| 3357 |
} |
| 3358 |
let openContextMenu = null; |
| 3359 |
function closeContextMenu() { |
| 3360 |
if (openContextMenu && openContextMenu.isConnected) { |
| 3361 |
openContextMenu.remove(); |
| 3362 |
} |
| 3363 |
openContextMenu = null; |
| 3364 |
} |
| 3365 |
function openUsageTileMenu(row, pos) { |
| 3366 |
closeContextMenu(); |
| 3367 |
const menu = document.createElement("wpd-context-menu"); |
| 3368 |
menu.setAttribute("open", ""); |
| 3369 |
menu.classList.add("desktop-mode-my-wordpress__menu"); |
| 3370 |
menu.style.left = `${pos.x}px`; |
| 3371 |
menu.style.top = `${pos.y}px`; |
| 3372 |
const addOption = (id, label, icon) => { |
| 3373 |
const opt = document.createElement("wpd-context-menu-option"); |
| 3374 |
opt.dataset.menuItemId = id; |
| 3375 |
opt.setAttribute("value", id); |
| 3376 |
opt.setAttribute("icon", icon); |
| 3377 |
opt.textContent = label; |
| 3378 |
menu.appendChild(opt); |
| 3379 |
}; |
| 3380 |
addOption("navigate-into", __("Open in My WordPress", "desktop-mode"), "dashicons-category"); |
| 3381 |
if (row.editLink) { |
| 3382 |
addOption("open-editor", __("Open in editor", "desktop-mode"), "dashicons-edit"); |
| 3383 |
} |
| 3384 |
if (row.link) { |
| 3385 |
addOption("open-front", __("View on site", "desktop-mode"), "dashicons-external"); |
| 3386 |
} |
| 3387 |
menu.addEventListener("wpd-context-menu-pick", (e) => { |
| 3388 |
const detail = e.detail; |
| 3389 |
closeContextMenu(); |
| 3390 |
if (detail.id === "navigate-into") { |
| 3391 |
openDetailInWindow({ |
| 3392 |
entityId: entityIdForPostType(row.postType), |
| 3393 |
postId: row.postId, |
| 3394 |
postTitle: row.title |
| 3395 |
}); |
| 3396 |
return; |
| 3397 |
} |
| 3398 |
if (detail.id === "open-editor" && row.editLink) { |
| 3399 |
window.open(row.editLink, "_blank", "noopener,noreferrer"); |
| 3400 |
return; |
| 3401 |
} |
| 3402 |
if (detail.id === "open-front" && row.link) { |
| 3403 |
window.open(row.link, "_blank", "noopener,noreferrer"); |
| 3404 |
} |
| 3405 |
}); |
| 3406 |
document.body.appendChild(menu); |
| 3407 |
openContextMenu = menu; |
| 3408 |
const rect = menu.getBoundingClientRect(); |
| 3409 |
if (rect.right > window.innerWidth) { |
| 3410 |
menu.style.left = `${Math.max( |
| 3411 |
0, |
| 3412 |
window.innerWidth - rect.width - 8 |
| 3413 |
)}px`; |
| 3414 |
} |
| 3415 |
if (rect.bottom > window.innerHeight) { |
| 3416 |
menu.style.top = `${Math.max( |
| 3417 |
0, |
| 3418 |
window.innerHeight - rect.height - 8 |
| 3419 |
)}px`; |
| 3420 |
} |
| 3421 |
queueMicrotask(() => { |
| 3422 |
const onDoc = (ev) => { |
| 3423 |
const target = ev.target; |
| 3424 |
if (target instanceof Node && menu.contains(target)) { |
| 3425 |
return; |
| 3426 |
} |
| 3427 |
closeContextMenu(); |
| 3428 |
document.removeEventListener("pointerdown", onDoc, true); |
| 3429 |
document.removeEventListener("keydown", onKey); |
| 3430 |
}; |
| 3431 |
const onKey = (ev) => { |
| 3432 |
if (ev.key === "Escape") { |
| 3433 |
closeContextMenu(); |
| 3434 |
document.removeEventListener("pointerdown", onDoc, true); |
| 3435 |
document.removeEventListener("keydown", onKey); |
| 3436 |
} |
| 3437 |
}; |
| 3438 |
document.addEventListener("pointerdown", onDoc, true); |
| 3439 |
document.addEventListener("keydown", onKey); |
| 3440 |
}); |
| 3441 |
} |
| 3442 |
function paintStatus$1(statusBar, count, entityId) { |
| 3443 |
const segments = [ |
| 3444 |
{ |
| 3445 |
id: "count", |
| 3446 |
label: sprintf( |
| 3447 |
// translators: %d is the count of posts that reference an attachment. |
| 3448 |
_n("%d reference", "%d references", count), |
| 3449 |
count |
| 3450 |
), |
| 3451 |
align: "start", |
| 3452 |
sort: 10 |
| 3453 |
} |
| 3454 |
]; |
| 3455 |
const filtered = applyFilters( |
| 3456 |
"desktop-mode.my-wordpress.status-bar", |
| 3457 |
segments, |
| 3458 |
{ view: "media-detail", entityId } |
| 3459 |
); |
| 3460 |
renderStatusBarSegments( |
| 3461 |
statusBar, |
| 3462 |
Array.isArray(filtered) ? filtered : segments |
| 3463 |
); |
| 3464 |
} |
| 3465 |
async function renderMediaDetail(host, mediaId) { |
| 3466 |
const wrap = document.createElement("div"); |
| 3467 |
wrap.className = "desktop-mode-my-wordpress__split desktop-mode-my-wordpress__split--media-detail"; |
| 3468 |
const left = document.createElement("div"); |
| 3469 |
left.className = "desktop-mode-my-wordpress__list desktop-mode-my-wordpress__usage-list"; |
| 3470 |
const loading = document.createElement("div"); |
| 3471 |
loading.className = "desktop-mode-my-wordpress__preview-loading"; |
| 3472 |
const spinner = document.createElement("wpd-spinner"); |
| 3473 |
loading.appendChild(spinner); |
| 3474 |
left.appendChild(loading); |
| 3475 |
const right = document.createElement("div"); |
| 3476 |
right.className = "desktop-mode-my-wordpress__preview"; |
| 3477 |
wrap.append(left, right); |
| 3478 |
host.body.appendChild(wrap); |
| 3479 |
const statusBar = host.body.closest("[data-desktop-mode-my-wordpress-root]")?.querySelector( |
| 3480 |
"[data-desktop-mode-my-wordpress-status]" |
| 3481 |
) ?? document.createElement("div"); |
| 3482 |
let usage; |
| 3483 |
try { |
| 3484 |
usage = await fetchMediaUsage(mediaId); |
| 3485 |
} catch (err) { |
| 3486 |
if (!wrap.isConnected) { |
| 3487 |
return; |
| 3488 |
} |
| 3489 |
left.replaceChildren(); |
| 3490 |
const errBox = document.createElement("div"); |
| 3491 |
errBox.className = "desktop-mode-my-wordpress__error"; |
| 3492 |
errBox.textContent = err instanceof Error ? err.message : __("Failed to load usage data.", "desktop-mode"); |
| 3493 |
left.appendChild(errBox); |
| 3494 |
return; |
| 3495 |
} |
| 3496 |
if (!wrap.isConnected) { |
| 3497 |
return; |
| 3498 |
} |
| 3499 |
if (host.route.kind !== "media-detail") { |
| 3500 |
throw new Error( |
| 3501 |
"[my-wordpress] renderMediaDetail invoked outside a media-detail route." |
| 3502 |
); |
| 3503 |
} |
| 3504 |
const entityId = host.route.entityId; |
| 3505 |
const summary = document.createElement("div"); |
| 3506 |
summary.className = "desktop-mode-my-wordpress__media-detail-summary-bar"; |
| 3507 |
const summaryText = document.createElement("p"); |
| 3508 |
summaryText.className = "desktop-mode-my-wordpress__media-detail-summary"; |
| 3509 |
summaryText.textContent = sprintf( |
| 3510 |
// translators: %d is the count of posts/pages referencing this file. |
| 3511 |
_n( |
| 3512 |
"%d entry references this file.", |
| 3513 |
"%d entries reference this file.", |
| 3514 |
usage.usedIn.length |
| 3515 |
), |
| 3516 |
usage.usedIn.length |
| 3517 |
); |
| 3518 |
summary.appendChild(summaryText); |
| 3519 |
right.replaceChildren(summary); |
| 3520 |
const mediaItem = { |
| 3521 |
id: usage.media.id, |
| 3522 |
title: { rendered: usage.media.title }, |
| 3523 |
date: usage.media.date, |
| 3524 |
mime_type: usage.media.mime, |
| 3525 |
source_url: usage.media.sourceUrl, |
| 3526 |
media_details: { |
| 3527 |
file: usage.media.filename |
| 3528 |
}, |
| 3529 |
_embedded: usage.media.author.name ? { author: [{ id: usage.media.author.id, name: usage.media.author.name }] } : void 0 |
| 3530 |
}; |
| 3531 |
const previewHost = document.createElement("div"); |
| 3532 |
previewHost.className = "desktop-mode-my-wordpress__media-detail-preview"; |
| 3533 |
renderMediaPreview(previewHost, mediaItem, { |
| 3534 |
entityId, |
| 3535 |
previewActions: getConfig().previewActions ?? [] |
| 3536 |
}); |
| 3537 |
right.appendChild(previewHost); |
| 3538 |
left.replaceChildren(); |
| 3539 |
if (usage.usedIn.length === 0) { |
| 3540 |
const empty = document.createElement("div"); |
| 3541 |
empty.className = "desktop-mode-my-wordpress__empty"; |
| 3542 |
empty.textContent = __( |
| 3543 |
"No posts or pages reference this file.", |
| 3544 |
"desktop-mode" |
| 3545 |
); |
| 3546 |
left.appendChild(empty); |
| 3547 |
paintStatus$1(statusBar, 0, entityId); |
| 3548 |
return; |
| 3549 |
} |
| 3550 |
const grid = document.createElement("div"); |
| 3551 |
grid.className = "desktop-mode-my-wordpress__media-grid desktop-mode-my-wordpress__usage-grid"; |
| 3552 |
grid.setAttribute("role", "list"); |
| 3553 |
for (const row of usage.usedIn) { |
| 3554 |
const tile = buildUsageTile(row); |
| 3555 |
tile.addEventListener("dblclick", (e) => { |
| 3556 |
e.preventDefault(); |
| 3557 |
openDetailInWindow({ |
| 3558 |
entityId: entityIdForPostType(row.postType), |
| 3559 |
postId: row.postId, |
| 3560 |
postTitle: row.title |
| 3561 |
}); |
| 3562 |
}); |
| 3563 |
tile.addEventListener("contextmenu", (e) => { |
| 3564 |
e.preventDefault(); |
| 3565 |
openUsageTileMenu(row, { x: e.clientX, y: e.clientY }); |
| 3566 |
}); |
| 3567 |
grid.appendChild(tile); |
| 3568 |
} |
| 3569 |
left.appendChild(grid); |
| 3570 |
host.addTeardown(closeContextMenu); |
| 3571 |
paintStatus$1(statusBar, usage.usedIn.length, entityId); |
| 3572 |
} |
| 3573 |
const ROOT_CLASS = "desktop-mode-breadcrumbs"; |
| 3574 |
function renderBreadcrumbs(host, segments, opts = {}) { |
| 3575 |
host.replaceChildren(); |
| 3576 |
host.classList.add(ROOT_CLASS); |
| 3577 |
if (opts.onBack) { |
| 3578 |
const back = document.createElement("button"); |
| 3579 |
back.type = "button"; |
| 3580 |
back.className = `${ROOT_CLASS}__back`; |
| 3581 |
back.setAttribute("aria-label", __("Back", "desktop-mode")); |
| 3582 |
back.title = __("Back", "desktop-mode"); |
| 3583 |
const arrow = document.createElement("span"); |
| 3584 |
arrow.className = "dashicons dashicons-arrow-left-alt2"; |
| 3585 |
arrow.setAttribute("aria-hidden", "true"); |
| 3586 |
back.appendChild(arrow); |
| 3587 |
if (opts.backDisabled) { |
| 3588 |
back.disabled = true; |
| 3589 |
} |
| 3590 |
const onBack = opts.onBack; |
| 3591 |
back.addEventListener("click", () => { |
| 3592 |
if (back.disabled) { |
| 3593 |
return; |
| 3594 |
} |
| 3595 |
onBack(); |
| 3596 |
}); |
| 3597 |
host.appendChild(back); |
| 3598 |
} |
| 3599 |
const nav = document.createElement("nav"); |
| 3600 |
nav.className = `${ROOT_CLASS}__crumbs`; |
| 3601 |
nav.setAttribute("aria-label", __("Breadcrumb", "desktop-mode")); |
| 3602 |
segments.forEach((seg, idx) => { |
| 3603 |
if (idx > 0) { |
| 3604 |
const sep = document.createElement("span"); |
| 3605 |
sep.className = `${ROOT_CLASS}__sep`; |
| 3606 |
sep.setAttribute("aria-hidden", "true"); |
| 3607 |
sep.textContent = "›"; |
| 3608 |
nav.appendChild(sep); |
| 3609 |
} |
| 3610 |
if (!seg.onClick) { |
| 3611 |
const here = document.createElement("span"); |
| 3612 |
here.className = `${ROOT_CLASS}__crumb ${ROOT_CLASS}__crumb--current`; |
| 3613 |
here.setAttribute("aria-current", "page"); |
| 3614 |
here.textContent = seg.label; |
| 3615 |
nav.appendChild(here); |
| 3616 |
return; |
| 3617 |
} |
| 3618 |
const btn = document.createElement("button"); |
| 3619 |
btn.type = "button"; |
| 3620 |
btn.className = `${ROOT_CLASS}__crumb`; |
| 3621 |
btn.textContent = seg.label; |
| 3622 |
const onClick = seg.onClick; |
| 3623 |
btn.addEventListener("click", () => { |
| 3624 |
onClick(); |
| 3625 |
}); |
| 3626 |
nav.appendChild(btn); |
| 3627 |
}); |
| 3628 |
host.appendChild(nav); |
| 3629 |
} |
| 3630 |
const styles$1 = css`:host{display:inline-flex}:host( [ fill-cell ] ){display:flex;width:100%}button{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:var( --wpd-button-padding,6px 12px );border-radius:var( --wpd-button-border-radius,6px );font:inherit;font-weight:500;cursor:pointer;transition:background-color 0.12s ease,color 0.12s ease,border-color 0.12s ease;background:var( --wpd-button-bg,transparent );color:var( --wpd-button-fg,var( --desktop-mode-text,#1d2327 ) );border:var( --wpd-button-border,1px solid var( --desktop-mode-border,#c3c4c7 ) )}:host( [ fill-cell ] ) button{width:100%;min-height:var( --wpd-button-min-height,44px )}button:disabled{opacity:0.5;cursor:not-allowed}button:hover:not(:disabled ){background:rgba( 0,0,0,0.04 )}:host( [ variant='primary' ] ) button{background:var( --wpd-button-bg,var( --wp-admin-theme-color,#2271b1 ) );color:var( --wpd-button-fg,#fff );border:var( --wpd-button-border,1px solid transparent )}:host( [ variant='primary' ] ) button:hover:not(:disabled ){filter:brightness( 1.06 );background:var( --wpd-button-bg,var( --wp-admin-theme-color,#2271b1 ) )}:host( [ variant='secondary' ] ) button{background:var( --wpd-button-bg,rgba( 0,0,0,0.06 ) );color:var( --wpd-button-fg,var( --desktop-mode-text,#1d2327 ) );border:var( --wpd-button-border,1px solid transparent )}:host( [ variant='secondary' ] ) button:hover:not(:disabled ){background:var( --wpd-button-bg-hover,rgba( 0,0,0,0.1 ) )}:host( [ variant='danger' ] ) button{background:var( --wpd-button-bg,transparent );color:var( --wpd-button-fg,#d63638 );border:var( --wpd-button-border,1px solid currentColor )}:host( [ variant='danger' ] ) button:hover:not(:disabled ){background:#d63638;color:#fff}:host( [ variant='link' ] ) button{background:transparent;color:var( --wpd-button-fg,var( --wp-admin-theme-color,#2271b1 ) );border:0;padding:0;text-decoration:underline}:host( [ busy ] ) button{pointer-events:none;opacity:0.75}`; |
| 3631 |
const _WpdButton = class _WpdButton extends Component { |
| 3632 |
render() { |
| 3633 |
const disabled = this.disabled !== null; |
| 3634 |
const type = this.type || "button"; |
| 3635 |
return html` |
| 3636 |
<button part="button" type=${type} ?disabled=${disabled}> |
| 3637 |
<slot></slot> |
| 3638 |
</button> |
| 3639 |
`; |
| 3640 |
} |
| 3641 |
}; |
| 3642 |
_WpdButton.props = ["variant", "disabled", "type", "busy", "fill-cell"]; |
| 3643 |
_WpdButton.styles = [styles$1]; |
| 3644 |
_WpdButton.help = { |
| 3645 |
title: "Button", |
| 3646 |
summary: "Thin wrapper around <button> with consistent variant styling and a slot for the label.", |
| 3647 |
status: "stable", |
| 3648 |
since: "0.9.0", |
| 3649 |
props: [ |
| 3650 |
{ |
| 3651 |
name: "variant", |
| 3652 |
type: "'primary' | 'secondary' | 'ghost' | 'danger' | 'link'", |
| 3653 |
default: "ghost", |
| 3654 |
description: "Visual weight of the button. Use primary for the single attention-grabbing action per surface." |
| 3655 |
}, |
| 3656 |
{ |
| 3657 |
name: "disabled", |
| 3658 |
type: "boolean attribute", |
| 3659 |
description: "Disable pointer + keyboard interaction and dim the chrome." |
| 3660 |
}, |
| 3661 |
{ |
| 3662 |
name: "type", |
| 3663 |
type: "'button' | 'submit' | 'reset'", |
| 3664 |
default: "button", |
| 3665 |
description: "Forwarded to the underlying native <button>." |
| 3666 |
}, |
| 3667 |
{ |
| 3668 |
name: "busy", |
| 3669 |
type: "boolean attribute", |
| 3670 |
description: "Marks the button as in-progress (e.g., awaiting a fetch)." |
| 3671 |
}, |
| 3672 |
{ |
| 3673 |
name: "fill-cell", |
| 3674 |
type: "boolean attribute", |
| 3675 |
description: "Grow to fill the parent flex/grid cell. Useful for tiled keypads." |
| 3676 |
} |
| 3677 |
], |
| 3678 |
slots: [{ name: "(default)", description: "Button label." }], |
| 3679 |
parts: [{ name: "button", description: "Underlying <button> element." }], |
| 3680 |
cssProps: [ |
| 3681 |
{ name: "--wpd-button-bg", description: "Background color." }, |
| 3682 |
{ name: "--wpd-button-fg", description: "Text color." }, |
| 3683 |
{ name: "--wpd-button-border", description: "Border shorthand." }, |
| 3684 |
{ name: "--wpd-button-border-radius", default: "6px" }, |
| 3685 |
{ name: "--wpd-button-padding", default: "6px 12px" }, |
| 3686 |
{ |
| 3687 |
name: "--wpd-button-min-height", |
| 3688 |
description: "Minimum height when fill-cell is set." |
| 3689 |
} |
| 3690 |
], |
| 3691 |
example: html` |
| 3692 |
<wpd-cluster gap="8"> |
| 3693 |
<wpd-button variant="primary">Primary</wpd-button> |
| 3694 |
<wpd-button variant="secondary">Secondary</wpd-button> |
| 3695 |
<wpd-button variant="ghost">Ghost</wpd-button> |
| 3696 |
<wpd-button variant="danger">Danger</wpd-button> |
| 3697 |
<wpd-button variant="link">Link</wpd-button> |
| 3698 |
</wpd-cluster> |
| 3699 |
` |
| 3700 |
}; |
| 3701 |
let WpdButton = _WpdButton; |
| 3702 |
defineComponent("wpd-button", WpdButton); |
| 3703 |
const menuStyles = css`:host{display:none;position:fixed;min-width:180px;background:var( --wpd-context-menu-bg,var( --desktop-mode-bg,#1d2327 ) );color:var( --wpd-context-menu-fg,var( --desktop-mode-fg,#fff ) );border:1px solid rgba( 255,255,255,0.08 );border-radius:8px;box-shadow:0 8px 24px rgba( 0,0,0,0.45 );padding:4px;font-size:13px;line-height:1.3;z-index:9999}:host( [ open ] ){display:block}`; |
| 3704 |
const optionStyles = css`:host{display:flex;align-items:center;gap:10px;width:100%;padding:8px 10px;border:0;background:transparent;color:inherit;text-align:start;cursor:pointer;border-radius:4px;box-sizing:border-box;user-select:none}:host(:hover ),:host( [ active ] ){background:rgba( 255,255,255,0.1 );outline:none}:host( [ disabled ] ){opacity:0.45;cursor:not-allowed}:host( [ danger ] ){color:#ff8a8a}:host( [ danger ]:hover ){background:rgba( 255,90,90,0.18 )}:host( [ heading ] ){padding:8px 10px 4px;font-size:10px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;color:var( --wpd-context-menu-fg-muted,rgba( 255,255,255,0.5 ) );pointer-events:none}.icon{display:inline-flex;align-items:center;justify-content:center;font-size:18px;width:20px;height:20px}.label{flex:1}.chevron{margin-inline-start:auto;padding-inline-start:8px;font-size:16px;line-height:1;opacity:0.7}.check{display:inline-flex;align-items:center;justify-content:center;width:14px;font-size:13px;line-height:1;opacity:0.95}`; |
| 3705 |
const _WpdContextMenu = class _WpdContextMenu extends Component { |
| 3706 |
render() { |
| 3707 |
return html` |
| 3708 |
<slot></slot> |
| 3709 |
`; |
| 3710 |
} |
| 3711 |
connectedCallback() { |
| 3712 |
super.connectedCallback(); |
| 3713 |
this.setAttribute("role", "menu"); |
| 3714 |
} |
| 3715 |
}; |
| 3716 |
_WpdContextMenu.props = ["open"]; |
| 3717 |
_WpdContextMenu.styles = [menuStyles]; |
| 3718 |
_WpdContextMenu.help = { |
| 3719 |
title: "Context menu", |
| 3720 |
summary: "Floating popup menu primitive. Pair with <wpd-context-menu-option> children. Toggle via the `open` boolean attribute. Listen for `wpd-context-menu-pick` to handle activation.", |
| 3721 |
status: "experimental", |
| 3722 |
since: "0.9.0", |
| 3723 |
props: [ |
| 3724 |
{ |
| 3725 |
name: "open", |
| 3726 |
type: "boolean attribute", |
| 3727 |
description: "Mounts the menu in its open / visible state." |
| 3728 |
} |
| 3729 |
], |
| 3730 |
slots: [ |
| 3731 |
{ name: "(default)", description: "List of <wpd-context-menu-option> items." } |
| 3732 |
], |
| 3733 |
events: [ |
| 3734 |
{ |
| 3735 |
name: "wpd-context-menu-pick", |
| 3736 |
description: "Bubbled from a non-disabled, non-heading option on activation. Detail: `{ id, value }`." |
| 3737 |
} |
| 3738 |
] |
| 3739 |
}; |
| 3740 |
let WpdContextMenu = _WpdContextMenu; |
| 3741 |
defineComponent("wpd-context-menu", WpdContextMenu); |
| 3742 |
const _WpdContextMenuOption = class _WpdContextMenuOption extends Component { |
| 3743 |
constructor() { |
| 3744 |
super(...arguments); |
| 3745 |
this._onActivate = (e) => { |
| 3746 |
if (this.hasAttribute("disabled") || this.hasAttribute("heading")) { |
| 3747 |
return; |
| 3748 |
} |
| 3749 |
const target = e.target; |
| 3750 |
if (target && target !== this && target.closest("wpd-context-menu-option") !== this) { |
| 3751 |
return; |
| 3752 |
} |
| 3753 |
this.emit("wpd-context-menu-pick", { |
| 3754 |
id: this.dataset.menuItemId ?? this.id ?? "", |
| 3755 |
value: this.getAttribute("value") ?? "" |
| 3756 |
}); |
| 3757 |
}; |
| 3758 |
this._onKey = (e) => { |
| 3759 |
if (e.key === "Enter" || e.key === " ") { |
| 3760 |
e.preventDefault(); |
| 3761 |
this._onActivate(e); |
| 3762 |
} |
| 3763 |
}; |
| 3764 |
} |
| 3765 |
connectedCallback() { |
| 3766 |
super.connectedCallback(); |
| 3767 |
const isHeading = this.hasAttribute("heading"); |
| 3768 |
this.setAttribute("role", isHeading ? "presentation" : "menuitem"); |
| 3769 |
if (!isHeading) { |
| 3770 |
this.setAttribute("tabindex", "0"); |
| 3771 |
} |
| 3772 |
this.addEventListener("click", this._onActivate); |
| 3773 |
this.addEventListener("keydown", this._onKey); |
| 3774 |
} |
| 3775 |
disconnectedCallback() { |
| 3776 |
this.removeEventListener("click", this._onActivate); |
| 3777 |
this.removeEventListener("keydown", this._onKey); |
| 3778 |
} |
| 3779 |
render() { |
| 3780 |
const icon = this.getAttribute("icon"); |
| 3781 |
const hasChildren2 = this.hasAttribute("has-children"); |
| 3782 |
const checked = this.hasAttribute("checked"); |
| 3783 |
return html` |
| 3784 |
${checked ? html`<span class="check" aria-hidden="true">✓</span>` : html``} |
| 3785 |
${icon ? html`<span class="icon dashicons ${icon}" aria-hidden="true"></span>` : html``} |
| 3786 |
<span class="label"><slot></slot></span> |
| 3787 |
${hasChildren2 ? html`<span class="chevron" aria-hidden="true">›</span>` : html``} |
| 3788 |
`; |
| 3789 |
} |
| 3790 |
}; |
| 3791 |
_WpdContextMenuOption.props = [ |
| 3792 |
"value", |
| 3793 |
"icon", |
| 3794 |
"disabled", |
| 3795 |
"danger", |
| 3796 |
"heading", |
| 3797 |
"has-children", |
| 3798 |
"checked" |
| 3799 |
]; |
| 3800 |
_WpdContextMenuOption.styles = [optionStyles]; |
| 3801 |
_WpdContextMenuOption.help = { |
| 3802 |
title: "Context menu option", |
| 3803 |
summary: "Single row inside <wpd-context-menu>. Use `icon` for a leading dashicon, `danger` for destructive items, `heading` for a non-interactive section header, `has-children` to render a trailing chevron.", |
| 3804 |
status: "experimental", |
| 3805 |
since: "0.9.0", |
| 3806 |
props: [ |
| 3807 |
{ |
| 3808 |
name: "value", |
| 3809 |
type: "string", |
| 3810 |
description: "Forwarded as `detail.value` on activation." |
| 3811 |
}, |
| 3812 |
{ |
| 3813 |
name: "icon", |
| 3814 |
type: "string", |
| 3815 |
description: "Dashicon class (e.g. `dashicons-trash`)." |
| 3816 |
}, |
| 3817 |
{ |
| 3818 |
name: "disabled", |
| 3819 |
type: "boolean attribute", |
| 3820 |
description: "Renders the option dimmed; clicks are ignored." |
| 3821 |
}, |
| 3822 |
{ |
| 3823 |
name: "danger", |
| 3824 |
type: "boolean attribute", |
| 3825 |
description: "Destructive styling — red text, red hover." |
| 3826 |
}, |
| 3827 |
{ |
| 3828 |
name: "heading", |
| 3829 |
type: "boolean attribute", |
| 3830 |
description: "Non-interactive section header. Ignores clicks." |
| 3831 |
}, |
| 3832 |
{ |
| 3833 |
name: "has-children", |
| 3834 |
type: "boolean attribute", |
| 3835 |
description: "Renders a trailing chevron to suggest a submenu." |
| 3836 |
}, |
| 3837 |
{ |
| 3838 |
name: "checked", |
| 3839 |
type: "boolean attribute", |
| 3840 |
description: "Renders a leading check mark — for radio-style picks inside a submenu (e.g. the active Sort By order)." |
| 3841 |
} |
| 3842 |
], |
| 3843 |
slots: [ |
| 3844 |
{ name: "(default)", description: "Visible label + optional nested <wpd-context-menu>." } |
| 3845 |
], |
| 3846 |
events: [ |
| 3847 |
{ |
| 3848 |
name: "wpd-context-menu-pick", |
| 3849 |
description: "Bubbled on click / Enter for non-heading non-disabled options. Detail: `{ id, value }`." |
| 3850 |
} |
| 3851 |
] |
| 3852 |
}; |
| 3853 |
let WpdContextMenuOption = _WpdContextMenuOption; |
| 3854 |
defineComponent("wpd-context-menu-option", WpdContextMenuOption); |
| 3855 |
const styles = css`:host{display:inline-block;--wpd-spinner-color:var( --wp-admin-theme-color,#21759b );--wpd-spinner-accent:#fff;--wpd-spinner-size:48px;width:var( --wpd-spinner-size );height:var( --wpd-spinner-size );color:var( --wpd-spinner-color );vertical-align:middle;line-height:0}:host( [ hidden ] ){display:none}.root,.root svg{display:block;width:100%;height:100%}.root svg .mark{fill:var( --wpd-spinner-accent,#fff )}@keyframes wpd-spinner-spin{to{transform:rotate( 360deg )}}@keyframes wpd-spinner-scale{0%,100%{transform:scale( 1 )}50%{transform:scale( 1.045 )}}@keyframes wpd-spinner-opacity{0%,100%{opacity:1}50%{opacity:0.7}}@media ( prefers-reduced-motion:reduce ){.root svg [ style*='animation' ]{animation:none !important}}`; |
| 3856 |
const WPD_SPINNER_PRESETS = Object.freeze({ |
| 3857 |
classic: { |
| 3858 |
sp1: 12, |
| 3859 |
sp2: 24, |
| 3860 |
sp3: 40, |
| 3861 |
a1: 28, |
| 3862 |
a2: 15, |
| 3863 |
a3: 8, |
| 3864 |
gap: 4, |
| 3865 |
dir2: 1, |
| 3866 |
dir3: -1, |
| 3867 |
pulse: "none", |
| 3868 |
dots: 0 |
| 3869 |
}, |
| 3870 |
comet: { |
| 3871 |
sp1: 8, |
| 3872 |
sp2: 14, |
| 3873 |
sp3: 26, |
| 3874 |
a1: 50, |
| 3875 |
a2: 28, |
| 3876 |
a3: 12, |
| 3877 |
gap: 3, |
| 3878 |
dir2: 1, |
| 3879 |
dir3: 1, |
| 3880 |
pulse: "none", |
| 3881 |
dots: 5 |
| 3882 |
}, |
| 3883 |
orbit: { |
| 3884 |
sp1: 10, |
| 3885 |
sp2: 10, |
| 3886 |
sp3: 32, |
| 3887 |
a1: 50, |
| 3888 |
a2: 50, |
| 3889 |
a3: 8, |
| 3890 |
gap: 5, |
| 3891 |
dir2: -1, |
| 3892 |
dir3: -1, |
| 3893 |
pulse: "opacity", |
| 3894 |
dots: 3 |
| 3895 |
}, |
| 3896 |
pulse: { |
| 3897 |
sp1: 6, |
| 3898 |
sp2: 18, |
| 3899 |
sp3: 30, |
| 3900 |
a1: 20, |
| 3901 |
a2: 12, |
| 3902 |
a3: 6, |
| 3903 |
gap: 4, |
| 3904 |
dir2: 1, |
| 3905 |
dir3: -1, |
| 3906 |
pulse: "both", |
| 3907 |
dots: 8 |
| 3908 |
} |
| 3909 |
}); |
| 3910 |
const CX = 61.26; |
| 3911 |
const CY = 61.26; |
| 3912 |
const DISC_R = 58.453; |
| 3913 |
const W_PATHS = '<path d="m8.708 61.26c0 20.802 12.089 38.779 29.619 47.298l-25.069-68.686c-2.916 6.536-4.55 13.769-4.55 21.388z"/><path d="m96.74 58.608c0-6.495-2.333-10.993-4.334-14.494-2.664-4.329-5.161-7.995-5.161-12.324 0-4.831 3.664-9.328 8.825-9.328.233 0 .454.029.681.042-9.35-8.566-21.807-13.796-35.489-13.796-18.36 0-34.513 9.42-43.91 23.688 1.233.037 2.395.063 3.382.063 5.497 0 14.006-.667 14.006-.667 2.833-.167 3.167 3.994.337 4.329 0 0-2.847.335-6.015.501l19.138 56.925 11.501-34.493-8.188-22.434c-2.83-.166-5.511-.501-5.511-.501-2.832-.166-2.5-4.496.332-4.329 0 0 8.679.667 13.843.667 5.496 0 14.006-.667 14.006-.667 2.835-.167 3.168 3.994.337 4.329 0 0-2.853.335-6.015.501l18.992 56.494 5.242-17.517c2.272-7.269 4.001-12.49 4.001-16.989z"/><path d="m62.184 65.857-15.768 45.819c4.708 1.384 9.687 2.141 14.846 2.141 6.12 0 11.989-1.058 17.452-2.979-.141-.225-.269-.464-.374-.724z"/><path d="m107.376 36.046c.226 1.674.354 3.471.354 5.404 0 5.333-.996 11.328-3.996 18.824l-16.053 46.413c15.624-9.111 26.133-26.038 26.133-45.426.001-9.137-2.333-17.729-6.438-25.215z"/>'; |
| 3914 |
const _WpdSpinner = class _WpdSpinner extends Component { |
| 3915 |
constructor() { |
| 3916 |
super(...arguments); |
| 3917 |
this._paintScheduled = false; |
| 3918 |
} |
| 3919 |
connectedCallback() { |
| 3920 |
super.connectedCallback(); |
| 3921 |
this._schedulePaint(); |
| 3922 |
} |
| 3923 |
render() { |
| 3924 |
return html`<div class="root" part="root"></div>`; |
| 3925 |
} |
| 3926 |
requestUpdate() { |
| 3927 |
super.requestUpdate(); |
| 3928 |
this._schedulePaint(); |
| 3929 |
} |
| 3930 |
_schedulePaint() { |
| 3931 |
if (this._paintScheduled || !this.isConnected) { |
| 3932 |
return; |
| 3933 |
} |
| 3934 |
this._paintScheduled = true; |
| 3935 |
queueMicrotask(() => { |
| 3936 |
this._paintScheduled = false; |
| 3937 |
if (!this.isConnected) { |
| 3938 |
return; |
| 3939 |
} |
| 3940 |
this._paint(); |
| 3941 |
}); |
| 3942 |
} |
| 3943 |
_paint() { |
| 3944 |
this._syncCssVars(); |
| 3945 |
const root = this.shadowRoot?.querySelector( |
| 3946 |
".root" |
| 3947 |
); |
| 3948 |
if (!root) { |
| 3949 |
return; |
| 3950 |
} |
| 3951 |
root.innerHTML = this._buildSvg(); |
| 3952 |
} |
| 3953 |
/** |
| 3954 |
* Reflect the color / accent / size attributes onto CSS custom |
| 3955 |
* properties on the host. Removing the attribute clears the var |
| 3956 |
* so the default cascades back in. |
| 3957 |
*/ |
| 3958 |
_syncCssVars() { |
| 3959 |
const sync = (attr, varName, transform) => { |
| 3960 |
const v = this.getAttribute(attr); |
| 3961 |
if (v === null) { |
| 3962 |
this.style.removeProperty(varName); |
| 3963 |
} else { |
| 3964 |
this.style.setProperty( |
| 3965 |
varName, |
| 3966 |
transform ? transform(v) : v |
| 3967 |
); |
| 3968 |
} |
| 3969 |
}; |
| 3970 |
sync("color", "--wpd-spinner-color"); |
| 3971 |
sync("accent", "--wpd-spinner-accent"); |
| 3972 |
sync( |
| 3973 |
"size", |
| 3974 |
"--wpd-spinner-size", |
| 3975 |
(v) => /^-?\d+(\.\d+)?$/.test(v.trim()) ? `${v}px` : v |
| 3976 |
); |
| 3977 |
} |
| 3978 |
_effectiveConfig() { |
| 3979 |
const presetName = this.getAttribute("preset") ?? "classic"; |
| 3980 |
const preset = WPD_SPINNER_PRESETS[presetName] ?? WPD_SPINNER_PRESETS.classic; |
| 3981 |
const num = (attr, fallback) => { |
| 3982 |
const v = this.getAttribute(attr); |
| 3983 |
if (v === null) { |
| 3984 |
return fallback; |
| 3985 |
} |
| 3986 |
const n = parseFloat(v); |
| 3987 |
return Number.isFinite(n) ? n : fallback; |
| 3988 |
}; |
| 3989 |
const dir = (attr, fallback) => { |
| 3990 |
const v = this.getAttribute(attr); |
| 3991 |
if (v === null) { |
| 3992 |
return fallback; |
| 3993 |
} |
| 3994 |
const lc = v.toLowerCase(); |
| 3995 |
if (lc === "-1" || lc === "ccw" || lc === "reverse") { |
| 3996 |
return -1; |
| 3997 |
} |
| 3998 |
return 1; |
| 3999 |
}; |
| 4000 |
const pulse = () => { |
| 4001 |
const v = this.getAttribute("pulse"); |
| 4002 |
if (v === "scale" || v === "opacity" || v === "both" || v === "none") { |
| 4003 |
return v; |
| 4004 |
} |
| 4005 |
return preset.pulse; |
| 4006 |
}; |
| 4007 |
return { |
| 4008 |
sp1: num("sp1", preset.sp1), |
| 4009 |
sp2: num("sp2", preset.sp2), |
| 4010 |
sp3: num("sp3", preset.sp3), |
| 4011 |
a1: num("a1", preset.a1), |
| 4012 |
a2: num("a2", preset.a2), |
| 4013 |
a3: num("a3", preset.a3), |
| 4014 |
gap: num("gap", preset.gap), |
| 4015 |
dir2: dir("dir2", preset.dir2), |
| 4016 |
dir3: dir("dir3", preset.dir3), |
| 4017 |
pulse: pulse(), |
| 4018 |
dots: Math.max(0, Math.floor(num("dots", preset.dots))) |
| 4019 |
}; |
| 4020 |
} |
| 4021 |
_buildSvg() { |
| 4022 |
const cfg = this._effectiveConfig(); |
| 4023 |
const label = escAttr(this.getAttribute("label") ?? "Loading"); |
| 4024 |
const pad = cfg.gap * 3 + 14; |
| 4025 |
const vbMin = -pad; |
| 4026 |
const vbSize = 122.52 + pad * 2; |
| 4027 |
const r1 = DISC_R + cfg.gap + 2; |
| 4028 |
const r2 = r1 + cfg.gap + 2; |
| 4029 |
const r3 = r2 + cfg.gap + 1.5; |
| 4030 |
const ring1Anim = `animation: wpd-spinner-spin ${(cfg.sp1 / 10).toFixed(2)}s linear infinite`; |
| 4031 |
const ring2Anim = `animation: wpd-spinner-spin ${(cfg.sp2 / 10).toFixed(2)}s linear infinite${cfg.dir2 < 0 ? " reverse" : ""}`; |
| 4032 |
const ring3Anim = `animation: wpd-spinner-spin ${(cfg.sp3 / 10).toFixed(2)}s linear infinite${cfg.dir3 < 0 ? " reverse" : ""}`; |
| 4033 |
const pspd = (cfg.sp1 * 1.8 / 10).toFixed(1); |
| 4034 |
const ospd = (cfg.sp1 * 2.3 / 10).toFixed(1); |
| 4035 |
let pulseStyle = ""; |
| 4036 |
if (cfg.pulse === "scale") { |
| 4037 |
pulseStyle = `animation: wpd-spinner-scale ${pspd}s ease-in-out infinite`; |
| 4038 |
} else if (cfg.pulse === "opacity") { |
| 4039 |
pulseStyle = `animation: wpd-spinner-opacity ${ospd}s ease-in-out infinite`; |
| 4040 |
} else if (cfg.pulse === "both") { |
| 4041 |
pulseStyle = `animation: wpd-spinner-scale ${pspd}s ease-in-out infinite, wpd-spinner-opacity ${ospd}s ease-in-out infinite`; |
| 4042 |
} |
| 4043 |
let dotEls = ""; |
| 4044 |
if (cfg.dots > 0) { |
| 4045 |
const dr = r3 + cfg.gap + 1; |
| 4046 |
const dc2 = 2 * Math.PI * dr; |
| 4047 |
const dsz = 1.6; |
| 4048 |
const dotDur = (cfg.sp1 * 0.65 / 10).toFixed(2); |
| 4049 |
for (let i = 0; i < cfg.dots; i++) { |
| 4050 |
const offset = -(i / cfg.dots) * dc2; |
| 4051 |
dotEls += `<circle cx="${CX}" cy="${CY}" r="${dr.toFixed(2)}" fill="none" stroke="currentColor" stroke-width="${dsz}" stroke-dasharray="${dsz.toFixed(2)} ${(dc2 - dsz).toFixed(2)}" stroke-dashoffset="${offset.toFixed(2)}" stroke-linecap="round" stroke-opacity="0.65" style="transform-origin:${CX}px ${CY}px;animation: wpd-spinner-spin ${dotDur}s linear infinite"/>`; |
| 4052 |
} |
| 4053 |
} |
| 4054 |
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${vbMin} ${vbMin} ${vbSize} ${vbSize}" role="img" aria-label="${label}"><g style="transform-origin:${CX}px ${CY}px${pulseStyle ? ";" + pulseStyle : ""}"><circle cx="${CX}" cy="${CY}" r="${DISC_R}" fill="currentColor"/><g class="mark">${W_PATHS}</g></g><circle cx="${CX}" cy="${CY}" r="${r1.toFixed(2)}" fill="none" stroke="currentColor" stroke-width="0.6" stroke-opacity="0.2"/><circle cx="${CX}" cy="${CY}" r="${r1.toFixed(2)}" fill="none" stroke="currentColor" stroke-width="2.2" stroke-dasharray="${dasharray(r1, cfg.a1)}" stroke-linecap="round" style="transform-origin:${CX}px ${CY}px;${ring1Anim}"/><circle cx="${CX}" cy="${CY}" r="${r2.toFixed(2)}" fill="none" stroke="currentColor" stroke-width="0.5" stroke-opacity="0.15"/><circle cx="${CX}" cy="${CY}" r="${r2.toFixed(2)}" fill="none" stroke="currentColor" stroke-width="1.6" stroke-opacity="0.8" stroke-dasharray="${dasharray(r2, cfg.a2)}" stroke-linecap="round" style="transform-origin:${CX}px ${CY}px;${ring2Anim}"/><circle cx="${CX}" cy="${CY}" r="${r3.toFixed(2)}" fill="none" stroke="currentColor" stroke-width="0.4" stroke-opacity="0.12"/><circle cx="${CX}" cy="${CY}" r="${r3.toFixed(2)}" fill="none" stroke="currentColor" stroke-width="1.0" stroke-opacity="0.6" stroke-dasharray="${dasharray(r3, cfg.a3)}" stroke-linecap="round" style="transform-origin:${CX}px ${CY}px;${ring3Anim}"/>` + dotEls + `</svg>`; |
| 4055 |
} |
| 4056 |
}; |
| 4057 |
_WpdSpinner.props = [ |
| 4058 |
"preset", |
| 4059 |
"size", |
| 4060 |
"color", |
| 4061 |
"accent", |
| 4062 |
"sp1", |
| 4063 |
"sp2", |
| 4064 |
"sp3", |
| 4065 |
"a1", |
| 4066 |
"a2", |
| 4067 |
"a3", |
| 4068 |
"gap", |
| 4069 |
"dir2", |
| 4070 |
"dir3", |
| 4071 |
"pulse", |
| 4072 |
"dots", |
| 4073 |
"label" |
| 4074 |
]; |
| 4075 |
_WpdSpinner.styles = [styles]; |
| 4076 |
_WpdSpinner.help = { |
| 4077 |
title: "Spinner", |
| 4078 |
summary: "Animated WordPress-mark loading indicator with four curated presets and full per-attribute overrides. CSS variables drive disc + accent colors and size; reduced-motion preferences are respected.", |
| 4079 |
status: "experimental", |
| 4080 |
since: "0.18.0", |
| 4081 |
props: [ |
| 4082 |
{ |
| 4083 |
name: "preset", |
| 4084 |
type: '"classic" | "comet" | "orbit" | "pulse"', |
| 4085 |
default: "classic", |
| 4086 |
description: "Visual personality. Every other attribute defaults to the preset's value and can be overridden individually." |
| 4087 |
}, |
| 4088 |
{ |
| 4089 |
name: "size", |
| 4090 |
type: "integer (px) or CSS length", |
| 4091 |
default: "48", |
| 4092 |
description: "Sets `--wpd-spinner-size`. Bare numbers are treated as px; pass a CSS length (e.g. `2em`) to opt into ems / rems." |
| 4093 |
}, |
| 4094 |
{ |
| 4095 |
name: "color", |
| 4096 |
type: "CSS color", |
| 4097 |
description: "Disc + ring + dot color. Sets `--wpd-spinner-color`. Default inherits the WP admin theme color." |
| 4098 |
}, |
| 4099 |
{ |
| 4100 |
name: "accent", |
| 4101 |
type: "CSS color", |
| 4102 |
default: "#fff", |
| 4103 |
description: "Color of the W mark inside the disc. Sets `--wpd-spinner-accent`. Default white — change for dark-on-light or themed marks." |
| 4104 |
}, |
| 4105 |
{ |
| 4106 |
name: "sp1, sp2, sp3", |
| 4107 |
type: "integer (deciseconds)", |
| 4108 |
description: "Per-ring rotation duration in tenths-of-a-second (12 → 1.2s). Higher = slower." |
| 4109 |
}, |
| 4110 |
{ |
| 4111 |
name: "a1, a2, a3", |
| 4112 |
type: "integer (0-100)", |
| 4113 |
description: "Per-ring arc length as a percentage of the ring circumference." |
| 4114 |
}, |
| 4115 |
{ |
| 4116 |
name: "gap", |
| 4117 |
type: "integer", |
| 4118 |
description: "Gap between concentric rings (units approximate to px at 120-viewport)." |
| 4119 |
}, |
| 4120 |
{ |
| 4121 |
name: "dir2, dir3", |
| 4122 |
type: '"1" | "-1" | "cw" | "ccw"', |
| 4123 |
description: "Per-ring direction; ring 1 is always clockwise." |
| 4124 |
}, |
| 4125 |
{ |
| 4126 |
name: "pulse", |
| 4127 |
type: '"none" | "scale" | "opacity" | "both"', |
| 4128 |
description: "Pulse animation applied to the disc + W mark." |
| 4129 |
}, |
| 4130 |
{ |
| 4131 |
name: "dots", |
| 4132 |
type: "integer", |
| 4133 |
description: "Outer trailing dot count. Sensible values: 0, 3, 5, 8." |
| 4134 |
}, |
| 4135 |
{ |
| 4136 |
name: "label", |
| 4137 |
type: "string", |
| 4138 |
default: "Loading", |
| 4139 |
description: 'Accessible name for the SVG (`role="img"` + `aria-label`).' |
| 4140 |
} |
| 4141 |
], |
| 4142 |
cssProps: [ |
| 4143 |
{ name: "--wpd-spinner-color", default: "var(--wp-admin-theme-color, #21759b)" }, |
| 4144 |
{ name: "--wpd-spinner-accent", default: "#fff" }, |
| 4145 |
{ name: "--wpd-spinner-size", default: "48px" } |
| 4146 |
], |
| 4147 |
example: html`<wpd-spinner preset="comet" size="80"></wpd-spinner>` |
| 4148 |
}; |
| 4149 |
let WpdSpinner = _WpdSpinner; |
| 4150 |
function dasharray(r, pct) { |
| 4151 |
const c = 2 * Math.PI * r; |
| 4152 |
const visible = pct / 100 * c; |
| 4153 |
const gap = c - visible; |
| 4154 |
return `${visible.toFixed(2)} ${gap.toFixed(2)}`; |
| 4155 |
} |
| 4156 |
function escAttr(s) { |
| 4157 |
return String(s).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">"); |
| 4158 |
} |
| 4159 |
defineComponent("wpd-spinner", WpdSpinner); |
| 4160 |
const WINDOW_ID = "desktop-mode-my-wordpress"; |
| 4161 |
const ROOT_SEL = "[data-desktop-mode-my-wordpress-root]"; |
| 4162 |
const BREADCRUMBS_SEL = "[data-desktop-mode-my-wordpress-breadcrumbs]"; |
| 4163 |
const BODY_SEL = "[data-desktop-mode-my-wordpress-body]"; |
| 4164 |
const STATUS_SEL = "[data-desktop-mode-my-wordpress-status]"; |
| 4165 |
function wpdConfirmGlobal(options) { |
| 4166 |
const fn = window.wp?.desktop?.confirm; |
| 4167 |
if (typeof fn !== "function") { |
| 4168 |
return Promise.resolve(false); |
| 4169 |
} |
| 4170 |
return fn(options); |
| 4171 |
} |
| 4172 |
function openIframeWindow(opts) { |
| 4173 |
const manager = window.wp?.desktop?.windowManager; |
| 4174 |
if (!manager || typeof manager.open !== "function") { |
| 4175 |
return; |
| 4176 |
} |
| 4177 |
manager.open({ |
| 4178 |
id: opts.id, |
| 4179 |
url: opts.url, |
| 4180 |
title: opts.title, |
| 4181 |
icon: opts.icon |
| 4182 |
}); |
| 4183 |
} |
| 4184 |
function getThumbnail(item) { |
| 4185 |
const media = item._embedded?.["wp:featuredmedia"]?.[0]; |
| 4186 |
if (!media) { |
| 4187 |
return ""; |
| 4188 |
} |
| 4189 |
const sizes = media.media_details?.sizes; |
| 4190 |
const preferred = sizes?.medium?.source_url ?? sizes?.thumbnail?.source_url ?? sizes?.large?.source_url ?? media.source_url; |
| 4191 |
return preferred ?? ""; |
| 4192 |
} |
| 4193 |
function paintStatus(state, baseSegments, ctx) { |
| 4194 |
const filtered = applyFilters( |
| 4195 |
"desktop-mode.my-wordpress.status-bar", |
| 4196 |
baseSegments, |
| 4197 |
ctx |
| 4198 |
); |
| 4199 |
renderStatusBarSegments( |
| 4200 |
state.statusBar, |
| 4201 |
Array.isArray(filtered) ? filtered : baseSegments |
| 4202 |
); |
| 4203 |
} |
| 4204 |
function pluralLabel(n, singular, plural) { |
| 4205 |
return `${n.toLocaleString()} ${n === 1 ? singular : plural}`; |
| 4206 |
} |
| 4207 |
function navigate(state, route, opts = {}) { |
| 4208 |
const sameRoute = routesEqual(state.route, route); |
| 4209 |
if (!opts.fromBack && !sameRoute) { |
| 4210 |
state.history.push(state.route); |
| 4211 |
} |
| 4212 |
clearTeardown(state); |
| 4213 |
state.route = route; |
| 4214 |
updateBreadcrumbs(state); |
| 4215 |
state.body.replaceChildren(); |
| 4216 |
if (route.kind === "root") { |
| 4217 |
renderRoot(state); |
| 4218 |
return; |
| 4219 |
} |
| 4220 |
const entity = getEntity(route.entityId); |
| 4221 |
if (!entity) { |
| 4222 |
renderError( |
| 4223 |
state, |
| 4224 |
__("Unknown entity type.", "desktop-mode") |
| 4225 |
); |
| 4226 |
return; |
| 4227 |
} |
| 4228 |
if (route.kind === "list") { |
| 4229 |
const renderer = getEntityRenderer(entity.kind); |
| 4230 |
if (renderer) { |
| 4231 |
const host = makeRenderHost(state); |
| 4232 |
renderer(host, entity); |
| 4233 |
return; |
| 4234 |
} |
| 4235 |
renderEntityList(state, entity); |
| 4236 |
return; |
| 4237 |
} |
| 4238 |
if (route.kind === "detail") { |
| 4239 |
renderDetail(state, entity, route.postId, route.postTitle); |
| 4240 |
return; |
| 4241 |
} |
| 4242 |
if (route.kind === "sub-list") { |
| 4243 |
renderSubList( |
| 4244 |
state, |
| 4245 |
entity, |
| 4246 |
route.postId, |
| 4247 |
route.postTitle, |
| 4248 |
route.relation |
| 4249 |
); |
| 4250 |
return; |
| 4251 |
} |
| 4252 |
if (route.kind === "user-footprint") { |
| 4253 |
renderUserFootprint(state, entity, route.userId, route.userName); |
| 4254 |
return; |
| 4255 |
} |
| 4256 |
if (route.kind === "media-detail") { |
| 4257 |
void renderMediaDetail(makeRenderHost(state), route.mediaId); |
| 4258 |
return; |
| 4259 |
} |
| 4260 |
} |
| 4261 |
function makeRenderHost(state) { |
| 4262 |
return { |
| 4263 |
body: state.body, |
| 4264 |
route: state.route, |
| 4265 |
navigate: (route) => navigate(state, route), |
| 4266 |
addTeardown: (fn) => state.teardown.push(fn) |
| 4267 |
}; |
| 4268 |
} |
| 4269 |
function routesEqual(a, b) { |
| 4270 |
if (a.kind !== b.kind) { |
| 4271 |
return false; |
| 4272 |
} |
| 4273 |
switch (a.kind) { |
| 4274 |
case "root": |
| 4275 |
return true; |
| 4276 |
case "list": |
| 4277 |
return a.entityId === b.entityId; |
| 4278 |
case "detail": { |
| 4279 |
const o = b; |
| 4280 |
return a.entityId === o.entityId && a.postId === o.postId; |
| 4281 |
} |
| 4282 |
case "sub-list": { |
| 4283 |
const o = b; |
| 4284 |
return a.entityId === o.entityId && a.postId === o.postId && a.relation === o.relation; |
| 4285 |
} |
| 4286 |
case "user-footprint": { |
| 4287 |
const o = b; |
| 4288 |
return a.entityId === o.entityId && a.userId === o.userId; |
| 4289 |
} |
| 4290 |
case "media-detail": { |
| 4291 |
const o = b; |
| 4292 |
return a.entityId === o.entityId && a.mediaId === o.mediaId; |
| 4293 |
} |
| 4294 |
default: |
| 4295 |
return false; |
| 4296 |
} |
| 4297 |
} |
| 4298 |
function parentRoute(route) { |
| 4299 |
switch (route.kind) { |
| 4300 |
case "root": |
| 4301 |
return route; |
| 4302 |
case "list": |
| 4303 |
return { kind: "root" }; |
| 4304 |
case "detail": |
| 4305 |
return { kind: "list", entityId: route.entityId }; |
| 4306 |
case "sub-list": |
| 4307 |
return { |
| 4308 |
kind: "detail", |
| 4309 |
entityId: route.entityId, |
| 4310 |
postId: route.postId, |
| 4311 |
postTitle: route.postTitle |
| 4312 |
}; |
| 4313 |
case "user-footprint": |
| 4314 |
return { kind: "list", entityId: route.entityId }; |
| 4315 |
case "media-detail": |
| 4316 |
return { kind: "list", entityId: route.entityId }; |
| 4317 |
default: |
| 4318 |
return { kind: "root" }; |
| 4319 |
} |
| 4320 |
} |
| 4321 |
function clearTeardown(state) { |
| 4322 |
for (const fn of state.teardown) { |
| 4323 |
try { |
| 4324 |
fn(); |
| 4325 |
} catch { |
| 4326 |
} |
| 4327 |
} |
| 4328 |
state.teardown = []; |
| 4329 |
} |
| 4330 |
function updateBreadcrumbs(state) { |
| 4331 |
const { route } = state; |
| 4332 |
const segments = []; |
| 4333 |
const isRoot = route.kind === "root"; |
| 4334 |
segments.push( |
| 4335 |
isRoot ? { label: __("My WordPress", "desktop-mode") } : { |
| 4336 |
label: __("My WordPress", "desktop-mode"), |
| 4337 |
onClick: () => navigate(state, { kind: "root" }) |
| 4338 |
} |
| 4339 |
); |
| 4340 |
if (route.kind !== "root") { |
| 4341 |
const entity = getEntity(route.entityId); |
| 4342 |
const label = entity ? entity.label : route.entityId; |
| 4343 |
segments.push( |
| 4344 |
route.kind === "list" ? { label } : { |
| 4345 |
label, |
| 4346 |
onClick: () => navigate(state, { |
| 4347 |
kind: "list", |
| 4348 |
entityId: route.entityId |
| 4349 |
}) |
| 4350 |
} |
| 4351 |
); |
| 4352 |
} |
| 4353 |
if (route.kind === "detail" || route.kind === "sub-list") { |
| 4354 |
const postTitle = route.postTitle; |
| 4355 |
const entityId = route.entityId; |
| 4356 |
const postId = route.postId; |
| 4357 |
segments.push( |
| 4358 |
route.kind === "detail" ? { label: postTitle } : { |
| 4359 |
label: postTitle, |
| 4360 |
onClick: () => navigate(state, { |
| 4361 |
kind: "detail", |
| 4362 |
entityId, |
| 4363 |
postId, |
| 4364 |
postTitle |
| 4365 |
}) |
| 4366 |
} |
| 4367 |
); |
| 4368 |
} |
| 4369 |
if (route.kind === "sub-list") { |
| 4370 |
segments.push({ label: subRelationLabel(route.relation) }); |
| 4371 |
} |
| 4372 |
if (route.kind === "user-footprint") { |
| 4373 |
segments.push({ |
| 4374 |
label: sprintf( |
| 4375 |
// translators: %s is a user display name. |
| 4376 |
__("%s — activity footprint", "desktop-mode"), |
| 4377 |
route.userName |
| 4378 |
) |
| 4379 |
}); |
| 4380 |
} |
| 4381 |
if (route.kind === "media-detail") { |
| 4382 |
segments.push({ label: route.mediaTitle }); |
| 4383 |
} |
| 4384 |
renderBreadcrumbs(state.breadcrumbs, segments, { |
| 4385 |
onBack: () => { |
| 4386 |
const previous = state.history.pop(); |
| 4387 |
if (previous) { |
| 4388 |
navigate(state, previous, { fromBack: true }); |
| 4389 |
return; |
| 4390 |
} |
| 4391 |
navigate(state, parentRoute(state.route), { fromBack: true }); |
| 4392 |
}, |
| 4393 |
backDisabled: isRoot && state.history.length === 0 |
| 4394 |
}); |
| 4395 |
} |
| 4396 |
function subRelationLabel(relation) { |
| 4397 |
switch (relation) { |
| 4398 |
case "author": |
| 4399 |
return __("Author", "desktop-mode"); |
| 4400 |
case "contributors": |
| 4401 |
return __("Contributors", "desktop-mode"); |
| 4402 |
case "comments": |
| 4403 |
return __("Comments", "desktop-mode"); |
| 4404 |
case "categories": |
| 4405 |
return __("Categories", "desktop-mode"); |
| 4406 |
case "tags": |
| 4407 |
return __("Tags", "desktop-mode"); |
| 4408 |
case "media": |
| 4409 |
return __("Attached media", "desktop-mode"); |
| 4410 |
case "revisions": |
| 4411 |
return __("Revisions", "desktop-mode"); |
| 4412 |
default: |
| 4413 |
return relation; |
| 4414 |
} |
| 4415 |
} |
| 4416 |
function renderRoot(state) { |
| 4417 |
const cfg = getConfig(); |
| 4418 |
const grid = document.createElement("div"); |
| 4419 |
grid.className = "desktop-mode-my-wordpress__grid desktop-mode-my-wordpress__canvas"; |
| 4420 |
grid.setAttribute("role", "list"); |
| 4421 |
const layout = createTileLayout(grid, "root"); |
| 4422 |
const select = createTileSelector(); |
| 4423 |
const tilesByEntity = /* @__PURE__ */ new Map(); |
| 4424 |
cfg.entities.forEach((entity, idx) => { |
| 4425 |
const tile = buildIconTile({ |
| 4426 |
role: "folder", |
| 4427 |
icon: entity.icon, |
| 4428 |
label: entity.label |
| 4429 |
}); |
| 4430 |
tile.dataset.entityId = entity.id; |
| 4431 |
tilesByEntity.set(entity.id, tile); |
| 4432 |
const tileKey = `entity:${entity.id}`; |
| 4433 |
const synthDate = new Date(2020, 0, 1 + idx).toISOString(); |
| 4434 |
layout.place(tile, tileKey, { |
| 4435 |
name: entity.label, |
| 4436 |
date: synthDate |
| 4437 |
}); |
| 4438 |
tile.addEventListener("click", () => select(tile)); |
| 4439 |
tile.addEventListener("dblclick", (e) => { |
| 4440 |
e.preventDefault(); |
| 4441 |
navigate(state, { kind: "list", entityId: entity.id }); |
| 4442 |
}); |
| 4443 |
grid.appendChild(tile); |
| 4444 |
}); |
| 4445 |
cfg.entities.forEach((entity) => { |
| 4446 |
void fetchEntityTotal(entity).then((total) => { |
| 4447 |
if (state.route.kind !== "root") { |
| 4448 |
return; |
| 4449 |
} |
| 4450 |
const tile = tilesByEntity.get(entity.id); |
| 4451 |
if (!tile) { |
| 4452 |
return; |
| 4453 |
} |
| 4454 |
const label = tile.querySelector( |
| 4455 |
".desktop-mode-file-tile__label" |
| 4456 |
); |
| 4457 |
if (label) { |
| 4458 |
label.textContent = `${entity.label} · ${total.toLocaleString()}`; |
| 4459 |
} |
| 4460 |
}).catch(() => { |
| 4461 |
}); |
| 4462 |
}); |
| 4463 |
state.body.appendChild(grid); |
| 4464 |
const menu = attachIconCanvasMenu(grid, { |
| 4465 |
scope: "my-wordpress:root", |
| 4466 |
onSort: (mode) => layout.sort(mode) |
| 4467 |
}); |
| 4468 |
state.teardown.push(() => menu.dispose()); |
| 4469 |
state.teardown.push(() => layout.dispose()); |
| 4470 |
paintStatus( |
| 4471 |
state, |
| 4472 |
[ |
| 4473 |
{ |
| 4474 |
id: "count", |
| 4475 |
label: pluralLabel(cfg.entities.length, "folder", "folders"), |
| 4476 |
align: "start", |
| 4477 |
sort: 10 |
| 4478 |
} |
| 4479 |
], |
| 4480 |
{ view: "root" } |
| 4481 |
); |
| 4482 |
} |
| 4483 |
function buildIconTile(spec) { |
| 4484 |
return buildTileFromSpec({ |
| 4485 |
type: spec.role === "folder" ? "folder" : "__my-wordpress-entry", |
| 4486 |
ref: spec.label, |
| 4487 |
label: spec.label, |
| 4488 |
icon: sanitizeClass(spec.icon), |
| 4489 |
role: spec.role, |
| 4490 |
extraClasses: [ |
| 4491 |
"desktop-mode-my-wordpress__tile", |
| 4492 |
spec.role === "folder" ? "desktop-mode-my-wordpress__tile--folder" : "desktop-mode-my-wordpress__tile--entry" |
| 4493 |
] |
| 4494 |
}); |
| 4495 |
} |
| 4496 |
function renderError(state, message) { |
| 4497 |
const empty = document.createElement("div"); |
| 4498 |
empty.className = "desktop-mode-my-wordpress__empty"; |
| 4499 |
empty.textContent = message; |
| 4500 |
state.body.appendChild(empty); |
| 4501 |
} |
| 4502 |
const lastQueryByEntity = /* @__PURE__ */ new Map(); |
| 4503 |
function renderEntityList(state, entity) { |
| 4504 |
const cfg = getConfig(); |
| 4505 |
const initialQuery = lastQueryByEntity.get(entity.id) ?? ""; |
| 4506 |
const toolbar = renderListToolbar({ |
| 4507 |
placeholder: sprintf( |
| 4508 |
// translators: %s is a lowercased entity-type label (e.g. "posts", "pages"). |
| 4509 |
__("Search %s…", "desktop-mode"), |
| 4510 |
entity.label.toLowerCase() |
| 4511 |
), |
| 4512 |
ariaLabel: sprintf( |
| 4513 |
// translators: %s is an entity-type label (e.g. "Posts", "Pages"). |
| 4514 |
__("Search %s", "desktop-mode"), |
| 4515 |
entity.label |
| 4516 |
), |
| 4517 |
initialValue: initialQuery, |
| 4518 |
onSearchChange: (q) => { |
| 4519 |
lastQueryByEntity.set(entity.id, q); |
| 4520 |
void resetForSearch(q); |
| 4521 |
} |
| 4522 |
}); |
| 4523 |
state.body.appendChild(toolbar.host); |
| 4524 |
state.teardown.push(() => toolbar.destroy()); |
| 4525 |
const split = document.createElement("div"); |
| 4526 |
split.className = "desktop-mode-my-wordpress__split"; |
| 4527 |
const left = document.createElement("div"); |
| 4528 |
left.className = "desktop-mode-my-wordpress__list"; |
| 4529 |
const tiles = document.createElement("div"); |
| 4530 |
tiles.className = "desktop-mode-my-wordpress__tiles desktop-mode-my-wordpress__canvas"; |
| 4531 |
tiles.setAttribute("role", "list"); |
| 4532 |
left.appendChild(tiles); |
| 4533 |
const sentinel = document.createElement("div"); |
| 4534 |
sentinel.className = "desktop-mode-my-wordpress__sentinel"; |
| 4535 |
sentinel.setAttribute("aria-hidden", "true"); |
| 4536 |
left.appendChild(sentinel); |
| 4537 |
const right = document.createElement("div"); |
| 4538 |
right.className = "desktop-mode-my-wordpress__preview"; |
| 4539 |
const previewEmpty = document.createElement("div"); |
| 4540 |
previewEmpty.className = "desktop-mode-my-wordpress__preview-empty"; |
| 4541 |
previewEmpty.textContent = __( |
| 4542 |
"Select an entry to preview it here.", |
| 4543 |
"desktop-mode" |
| 4544 |
); |
| 4545 |
right.appendChild(previewEmpty); |
| 4546 |
split.appendChild(left); |
| 4547 |
split.appendChild(right); |
| 4548 |
state.body.appendChild(split); |
| 4549 |
const tileLayout = createTileLayout(tiles, `entity:${entity.id}`); |
| 4550 |
const menu = attachIconCanvasMenu(tiles, { |
| 4551 |
scope: `my-wordpress:${entity.id}`, |
| 4552 |
onSort: (mode) => tileLayout.sort(mode) |
| 4553 |
}); |
| 4554 |
state.teardown.push(() => menu.dispose()); |
| 4555 |
const ctx = { |
| 4556 |
page: 0, |
| 4557 |
totalPages: 1, |
| 4558 |
total: 0, |
| 4559 |
loaded: 0, |
| 4560 |
loading: false, |
| 4561 |
done: false, |
| 4562 |
tiles, |
| 4563 |
sentinel, |
| 4564 |
preview: right, |
| 4565 |
selectedId: null, |
| 4566 |
selectedTile: null, |
| 4567 |
observer: null, |
| 4568 |
layout: tileLayout, |
| 4569 |
query: initialQuery, |
| 4570 |
abort: null |
| 4571 |
}; |
| 4572 |
state.teardown.push(() => tileLayout.dispose()); |
| 4573 |
state.teardown.push(() => ctx.abort?.abort()); |
| 4574 |
const repaintListStatus = () => { |
| 4575 |
let itemLabel; |
| 4576 |
if (ctx.total === 0 && ctx.loaded === 0) { |
| 4577 |
itemLabel = pluralLabel(0, "item", "items"); |
| 4578 |
} else if (ctx.total > ctx.loaded && ctx.loaded > 0) { |
| 4579 |
itemLabel = sprintf( |
| 4580 |
// translators: 1: visible item count, 2: total item count. |
| 4581 |
__("%1$d of %2$d items", "desktop-mode"), |
| 4582 |
ctx.loaded, |
| 4583 |
ctx.total |
| 4584 |
); |
| 4585 |
} else { |
| 4586 |
itemLabel = pluralLabel( |
| 4587 |
Math.max(ctx.total, ctx.loaded), |
| 4588 |
"item", |
| 4589 |
"items" |
| 4590 |
); |
| 4591 |
} |
| 4592 |
const segments = [ |
| 4593 |
{ id: "count", label: itemLabel, align: "start", sort: 10 } |
| 4594 |
]; |
| 4595 |
if (ctx.totalPages > 1) { |
| 4596 |
segments.push({ |
| 4597 |
id: "page", |
| 4598 |
label: sprintf( |
| 4599 |
// translators: 1: current page, 2: total pages. |
| 4600 |
__("Page %1$d of %2$d", "desktop-mode"), |
| 4601 |
Math.max(ctx.page, 1), |
| 4602 |
ctx.totalPages |
| 4603 |
), |
| 4604 |
align: "end", |
| 4605 |
sort: 10 |
| 4606 |
}); |
| 4607 |
} |
| 4608 |
paintStatus(state, segments, { |
| 4609 |
view: "list", |
| 4610 |
entityId: entity.id |
| 4611 |
}); |
| 4612 |
}; |
| 4613 |
repaintListStatus(); |
| 4614 |
const sentinelIsVisible = () => { |
| 4615 |
const sr = sentinel.getBoundingClientRect(); |
| 4616 |
const rr = left.getBoundingClientRect(); |
| 4617 |
const slack = 200; |
| 4618 |
return sr.top < rr.bottom + slack && sr.bottom > rr.top - slack; |
| 4619 |
}; |
| 4620 |
const loadMore = async () => { |
| 4621 |
if (ctx.loading || ctx.done) { |
| 4622 |
return; |
| 4623 |
} |
| 4624 |
ctx.loading = true; |
| 4625 |
const nextPage = ctx.page + 1; |
| 4626 |
const isFirst = nextPage === 1; |
| 4627 |
const queryAtFetchTime = ctx.query; |
| 4628 |
showLoadingSkeleton(tiles, ctx.layout, isFirst); |
| 4629 |
const controller = new AbortController(); |
| 4630 |
ctx.abort = controller; |
| 4631 |
try { |
| 4632 |
const result = await fetchEntityList(entity, { |
| 4633 |
page: nextPage, |
| 4634 |
perPage: cfg.perPage, |
| 4635 |
search: queryAtFetchTime || void 0, |
| 4636 |
signal: controller.signal |
| 4637 |
}); |
| 4638 |
if (ctx.query !== queryAtFetchTime) { |
| 4639 |
return; |
| 4640 |
} |
| 4641 |
ctx.page = nextPage; |
| 4642 |
ctx.totalPages = result.totalPages; |
| 4643 |
ctx.total = result.total; |
| 4644 |
hideLoadingSkeleton(tiles); |
| 4645 |
if (result.items.length === 0 && isFirst) { |
| 4646 |
renderListEmpty(tiles, entity, queryAtFetchTime); |
| 4647 |
ctx.done = true; |
| 4648 |
repaintListStatus(); |
| 4649 |
return; |
| 4650 |
} |
| 4651 |
for (const item of result.items) { |
| 4652 |
tiles.appendChild(buildEntityTile(state, ctx, entity, item)); |
| 4653 |
ctx.loaded += 1; |
| 4654 |
} |
| 4655 |
if (ctx.page >= ctx.totalPages) { |
| 4656 |
ctx.done = true; |
| 4657 |
} |
| 4658 |
repaintListStatus(); |
| 4659 |
} catch (err) { |
| 4660 |
if (isAbortError(err)) { |
| 4661 |
return; |
| 4662 |
} |
| 4663 |
hideLoadingSkeleton(tiles); |
| 4664 |
const msg = err instanceof Error ? err.message : __("Unknown error.", "desktop-mode"); |
| 4665 |
renderListError(tiles, msg); |
| 4666 |
ctx.done = true; |
| 4667 |
} finally { |
| 4668 |
ctx.loading = false; |
| 4669 |
if (ctx.abort === controller) { |
| 4670 |
ctx.abort = null; |
| 4671 |
} |
| 4672 |
} |
| 4673 |
if (!ctx.done) { |
| 4674 |
requestAnimationFrame(() => { |
| 4675 |
if (sentinelIsVisible()) { |
| 4676 |
void loadMore(); |
| 4677 |
} |
| 4678 |
}); |
| 4679 |
} |
| 4680 |
}; |
| 4681 |
const resetForSearch = async (q) => { |
| 4682 |
ctx.abort?.abort(); |
| 4683 |
ctx.abort = null; |
| 4684 |
ctx.query = q; |
| 4685 |
tiles.classList.add( |
| 4686 |
"desktop-mode-my-wordpress__tiles--searching" |
| 4687 |
); |
| 4688 |
hideLoadingSkeleton(tiles); |
| 4689 |
const controller = new AbortController(); |
| 4690 |
ctx.abort = controller; |
| 4691 |
ctx.loading = true; |
| 4692 |
try { |
| 4693 |
const result = await fetchEntityList(entity, { |
| 4694 |
page: 1, |
| 4695 |
perPage: cfg.perPage, |
| 4696 |
search: q || void 0, |
| 4697 |
signal: controller.signal |
| 4698 |
}); |
| 4699 |
if (ctx.query !== q) { |
| 4700 |
return; |
| 4701 |
} |
| 4702 |
tiles.replaceChildren(); |
| 4703 |
ctx.layout.clear(); |
| 4704 |
tiles.classList.remove( |
| 4705 |
"desktop-mode-my-wordpress__tiles--searching" |
| 4706 |
); |
| 4707 |
ctx.page = 1; |
| 4708 |
ctx.totalPages = result.totalPages; |
| 4709 |
ctx.total = result.total; |
| 4710 |
ctx.loaded = 0; |
| 4711 |
ctx.done = ctx.page >= ctx.totalPages; |
| 4712 |
ctx.selectedId = null; |
| 4713 |
ctx.selectedTile = null; |
| 4714 |
ctx.preview.replaceChildren(); |
| 4715 |
const emptyPreview = document.createElement("div"); |
| 4716 |
emptyPreview.className = "desktop-mode-my-wordpress__preview-empty"; |
| 4717 |
emptyPreview.textContent = __( |
| 4718 |
"Select an entry to preview it here.", |
| 4719 |
"desktop-mode" |
| 4720 |
); |
| 4721 |
ctx.preview.appendChild(emptyPreview); |
| 4722 |
if (result.items.length === 0) { |
| 4723 |
renderListEmpty(tiles, entity, q); |
| 4724 |
ctx.done = true; |
| 4725 |
} else { |
| 4726 |
for (const item of result.items) { |
| 4727 |
tiles.appendChild( |
| 4728 |
buildEntityTile(state, ctx, entity, item) |
| 4729 |
); |
| 4730 |
ctx.loaded += 1; |
| 4731 |
} |
| 4732 |
} |
| 4733 |
repaintListStatus(); |
| 4734 |
} catch (err) { |
| 4735 |
if (isAbortError(err)) { |
| 4736 |
return; |
| 4737 |
} |
| 4738 |
tiles.classList.remove( |
| 4739 |
"desktop-mode-my-wordpress__tiles--searching" |
| 4740 |
); |
| 4741 |
tiles.replaceChildren(); |
| 4742 |
ctx.layout.clear(); |
| 4743 |
const msg = err instanceof Error ? err.message : __("Unknown error.", "desktop-mode"); |
| 4744 |
renderListError(tiles, msg); |
| 4745 |
ctx.done = true; |
| 4746 |
} finally { |
| 4747 |
ctx.loading = false; |
| 4748 |
if (ctx.abort === controller) { |
| 4749 |
ctx.abort = null; |
| 4750 |
} |
| 4751 |
} |
| 4752 |
if (!ctx.done) { |
| 4753 |
requestAnimationFrame(() => { |
| 4754 |
if (sentinelIsVisible()) { |
| 4755 |
void loadMore(); |
| 4756 |
} |
| 4757 |
}); |
| 4758 |
} |
| 4759 |
}; |
| 4760 |
if (typeof IntersectionObserver !== "undefined") { |
| 4761 |
ctx.observer = new IntersectionObserver( |
| 4762 |
(entries) => { |
| 4763 |
for (const e of entries) { |
| 4764 |
if (e.isIntersecting) { |
| 4765 |
void loadMore(); |
| 4766 |
} |
| 4767 |
} |
| 4768 |
}, |
| 4769 |
{ root: left, rootMargin: "200px 0px" } |
| 4770 |
); |
| 4771 |
ctx.observer.observe(sentinel); |
| 4772 |
state.teardown.push(() => ctx.observer?.disconnect()); |
| 4773 |
} |
| 4774 |
void loadMore(); |
| 4775 |
} |
| 4776 |
function isAbortError(err) { |
| 4777 |
return err instanceof DOMException && err.name === "AbortError"; |
| 4778 |
} |
| 4779 |
function renderListEmpty(host, entity, query) { |
| 4780 |
const empty = document.createElement("div"); |
| 4781 |
empty.className = "desktop-mode-my-wordpress__empty"; |
| 4782 |
if (query) { |
| 4783 |
empty.textContent = sprintf( |
| 4784 |
// translators: 1: search query, 2: lowercased entity-type label. |
| 4785 |
__('No %2$s match "%1$s".', "desktop-mode"), |
| 4786 |
query, |
| 4787 |
entity.label.toLowerCase() |
| 4788 |
); |
| 4789 |
} else { |
| 4790 |
empty.textContent = sprintf( |
| 4791 |
// translators: %s is an entity-type label (e.g. "Posts", "Pages"). |
| 4792 |
__("No %s yet.", "desktop-mode"), |
| 4793 |
entity.label.toLowerCase() |
| 4794 |
); |
| 4795 |
} |
| 4796 |
host.appendChild(empty); |
| 4797 |
} |
| 4798 |
function renderListError(host, message) { |
| 4799 |
const err = document.createElement("div"); |
| 4800 |
err.className = "desktop-mode-my-wordpress__error"; |
| 4801 |
err.textContent = message; |
| 4802 |
host.appendChild(err); |
| 4803 |
} |
| 4804 |
function buildSkeletonTile(variant) { |
| 4805 |
const tile = document.createElement("div"); |
| 4806 |
tile.className = "desktop-mode-my-wordpress__skeleton-tile"; |
| 4807 |
tile.dataset.loadingSkeleton = variant; |
| 4808 |
tile.setAttribute("aria-hidden", "true"); |
| 4809 |
const icon = document.createElement("div"); |
| 4810 |
icon.className = "desktop-mode-my-wordpress__skeleton-icon"; |
| 4811 |
tile.appendChild(icon); |
| 4812 |
const label = document.createElement("div"); |
| 4813 |
label.className = "desktop-mode-my-wordpress__skeleton-label"; |
| 4814 |
tile.appendChild(label); |
| 4815 |
return tile; |
| 4816 |
} |
| 4817 |
const SKELETON_LABEL_WIDTHS = [72, 60, 82, 48, 70]; |
| 4818 |
const SKELETON_DELAY_STEPS = [0, 0.18, 0.36, 0.54, 0.12]; |
| 4819 |
function showLoadingSkeleton(host, layout, isFirst) { |
| 4820 |
const variant = isFirst ? "first" : "more"; |
| 4821 |
if (host.querySelector(`[data-loading-skeleton="${variant}"]`)) { |
| 4822 |
return; |
| 4823 |
} |
| 4824 |
const count = isFirst ? 8 : 4; |
| 4825 |
const cells = layout.peekNextCells(count); |
| 4826 |
let maxBottom = parseFloat(host.style.minHeight || "0"); |
| 4827 |
cells.forEach((cell, i) => { |
| 4828 |
const tile = buildSkeletonTile(variant); |
| 4829 |
tile.style.left = `${cell.x}px`; |
| 4830 |
tile.style.top = `${cell.y}px`; |
| 4831 |
tile.style.setProperty( |
| 4832 |
"--desktop-mode-skeleton-delay", |
| 4833 |
`${SKELETON_DELAY_STEPS[i % SKELETON_DELAY_STEPS.length]}s` |
| 4834 |
); |
| 4835 |
const label = tile.querySelector( |
| 4836 |
".desktop-mode-my-wordpress__skeleton-label" |
| 4837 |
); |
| 4838 |
if (label) { |
| 4839 |
label.style.width = `${SKELETON_LABEL_WIDTHS[i % SKELETON_LABEL_WIDTHS.length]}%`; |
| 4840 |
} |
| 4841 |
host.appendChild(tile); |
| 4842 |
maxBottom = Math.max(maxBottom, cell.y + TILE_H); |
| 4843 |
}); |
| 4844 |
host.style.minHeight = `${maxBottom + TILE_PAD}px`; |
| 4845 |
} |
| 4846 |
function hideLoadingSkeleton(host) { |
| 4847 |
host.querySelectorAll("[data-loading-skeleton]").forEach( |
| 4848 |
(n) => n.remove() |
| 4849 |
); |
| 4850 |
} |
| 4851 |
function buildEntityTile(state, ctx, entity, item) { |
| 4852 |
const titleText = stripTags(item.title.rendered) || __("(no title)", "desktop-mode"); |
| 4853 |
const tile = buildIconTile({ |
| 4854 |
role: "entry", |
| 4855 |
icon: entity.icon, |
| 4856 |
label: titleText |
| 4857 |
}); |
| 4858 |
tile.dataset.entryId = String(item.id); |
| 4859 |
if (item.status) { |
| 4860 |
tile.setAttribute("status", item.status); |
| 4861 |
} |
| 4862 |
attachTileDragOut( |
| 4863 |
tile, |
| 4864 |
{ |
| 4865 |
kind: "post", |
| 4866 |
ref: String(item.id), |
| 4867 |
title: titleText, |
| 4868 |
icon: entity.icon, |
| 4869 |
// Cross-frame bridge payload — the Gutenberg drop-receiver |
| 4870 |
// turns this into a `core/paragraph` with an `<a href>` to |
| 4871 |
// the permalink. Tiles without a `link` (very old REST |
| 4872 |
// shapes / private posts) still drag-out for placement |
| 4873 |
// purposes; the receiver no-ops on an empty url. |
| 4874 |
bridgePayload: { |
| 4875 |
kind: "post", |
| 4876 |
id: item.id, |
| 4877 |
postType: entity.id, |
| 4878 |
url: item.link ?? "", |
| 4879 |
title: titleText |
| 4880 |
} |
| 4881 |
}, |
| 4882 |
() => hideTooltip() |
| 4883 |
); |
| 4884 |
const lock = item.desktop_mode_lock ?? null; |
| 4885 |
if (lock) { |
| 4886 |
tile.classList.add("desktop-mode-my-wordpress__tile--locked"); |
| 4887 |
const badge = document.createElement("span"); |
| 4888 |
badge.className = "desktop-mode-my-wordpress__tile-lock dashicons dashicons-lock"; |
| 4889 |
badge.setAttribute("aria-hidden", "true"); |
| 4890 |
tile.appendChild(badge); |
| 4891 |
const lockedAriaLabel = __( |
| 4892 |
"%1$s — currently being edited by %2$s", |
| 4893 |
"desktop-mode" |
| 4894 |
); |
| 4895 |
tile.setAttribute( |
| 4896 |
"aria-label", |
| 4897 |
sprintf(lockedAriaLabel, titleText, lock.userName) |
| 4898 |
); |
| 4899 |
} |
| 4900 |
let tooltip = null; |
| 4901 |
const showTooltip = (ev) => { |
| 4902 |
if (!tooltip) { |
| 4903 |
tooltip = buildTooltip(titleText, item); |
| 4904 |
} |
| 4905 |
document.body.appendChild(tooltip); |
| 4906 |
positionTooltip(tooltip, ev); |
| 4907 |
}; |
| 4908 |
const moveTooltip = (ev) => { |
| 4909 |
if (tooltip && tooltip.isConnected) { |
| 4910 |
positionTooltip(tooltip, ev); |
| 4911 |
} |
| 4912 |
}; |
| 4913 |
const hideTooltip = () => { |
| 4914 |
if (tooltip && tooltip.isConnected) { |
| 4915 |
tooltip.remove(); |
| 4916 |
} |
| 4917 |
}; |
| 4918 |
tile.addEventListener("mouseenter", showTooltip); |
| 4919 |
tile.addEventListener("mousemove", moveTooltip); |
| 4920 |
tile.addEventListener("mouseleave", hideTooltip); |
| 4921 |
state.teardown.push(hideTooltip); |
| 4922 |
const tileKey = `entry:${item.id}`; |
| 4923 |
ctx.layout.place(tile, tileKey, { |
| 4924 |
name: titleText, |
| 4925 |
date: item.date || (/* @__PURE__ */ new Date(0)).toISOString() |
| 4926 |
}); |
| 4927 |
tile.addEventListener("click", () => { |
| 4928 |
selectTile(state, ctx, tile, entity, item.id); |
| 4929 |
}); |
| 4930 |
tile.addEventListener("dblclick", (e) => { |
| 4931 |
e.preventDefault(); |
| 4932 |
hideTooltip(); |
| 4933 |
openEditor(entity, item.id, titleText); |
| 4934 |
}); |
| 4935 |
tile.addEventListener("contextmenu", (e) => { |
| 4936 |
e.preventDefault(); |
| 4937 |
hideTooltip(); |
| 4938 |
openTileMenu(state, ctx, entity, item, titleText, { |
| 4939 |
x: e.clientX, |
| 4940 |
y: e.clientY |
| 4941 |
}); |
| 4942 |
}); |
| 4943 |
return tile; |
| 4944 |
} |
| 4945 |
function buildTooltip(title, item) { |
| 4946 |
const tip = document.createElement("div"); |
| 4947 |
tip.className = "desktop-mode-my-wordpress__tooltip"; |
| 4948 |
tip.setAttribute("role", "tooltip"); |
| 4949 |
const heading = document.createElement("div"); |
| 4950 |
heading.className = "desktop-mode-my-wordpress__tooltip-title"; |
| 4951 |
heading.textContent = title; |
| 4952 |
tip.appendChild(heading); |
| 4953 |
const lock = item.desktop_mode_lock ?? null; |
| 4954 |
if (lock) { |
| 4955 |
const banner = document.createElement("div"); |
| 4956 |
banner.className = "desktop-mode-my-wordpress__tooltip-lock"; |
| 4957 |
const icon = document.createElement("span"); |
| 4958 |
icon.className = "dashicons dashicons-lock"; |
| 4959 |
icon.setAttribute("aria-hidden", "true"); |
| 4960 |
banner.appendChild(icon); |
| 4961 |
const text = document.createElement("span"); |
| 4962 |
text.textContent = sprintf( |
| 4963 |
// translators: %s is the user name currently editing the post. |
| 4964 |
__("%s is currently editing", "desktop-mode"), |
| 4965 |
lock.userName |
| 4966 |
); |
| 4967 |
banner.appendChild(text); |
| 4968 |
tip.appendChild(banner); |
| 4969 |
} |
| 4970 |
const thumb = getThumbnail(item); |
| 4971 |
if (thumb) { |
| 4972 |
const img = document.createElement("img"); |
| 4973 |
img.className = "desktop-mode-my-wordpress__tooltip-thumb"; |
| 4974 |
img.src = thumb; |
| 4975 |
img.alt = ""; |
| 4976 |
tip.appendChild(img); |
| 4977 |
} |
| 4978 |
const excerpt = stripTags(item.excerpt?.rendered ?? ""); |
| 4979 |
if (excerpt) { |
| 4980 |
const p = document.createElement("p"); |
| 4981 |
p.className = "desktop-mode-my-wordpress__tooltip-excerpt"; |
| 4982 |
p.textContent = excerpt.length > 240 ? excerpt.slice(0, 237) + "…" : excerpt; |
| 4983 |
tip.appendChild(p); |
| 4984 |
} |
| 4985 |
return tip; |
| 4986 |
} |
| 4987 |
function positionTooltip(tip, ev) { |
| 4988 |
const offset = 16; |
| 4989 |
let x = ev.clientX + offset; |
| 4990 |
let y = ev.clientY + offset; |
| 4991 |
const rect = tip.getBoundingClientRect(); |
| 4992 |
if (x + rect.width > window.innerWidth - 8) { |
| 4993 |
x = Math.max(8, ev.clientX - rect.width - offset); |
| 4994 |
} |
| 4995 |
if (y + rect.height > window.innerHeight - 8) { |
| 4996 |
y = Math.max(8, ev.clientY - rect.height - offset); |
| 4997 |
} |
| 4998 |
tip.style.left = `${x}px`; |
| 4999 |
tip.style.top = `${y}px`; |
| 5000 |
} |
| 5001 |
function selectTile(state, ctx, tile, entity, id) { |
| 5002 |
if (ctx.selectedTile) { |
| 5003 |
ctx.selectedTile.classList.remove( |
| 5004 |
"desktop-mode-file-tile--selected" |
| 5005 |
); |
| 5006 |
} |
| 5007 |
tile.classList.add("desktop-mode-file-tile--selected"); |
| 5008 |
ctx.selectedTile = tile; |
| 5009 |
ctx.selectedId = id; |
| 5010 |
void renderPreview(state, ctx, entity, id); |
| 5011 |
} |
| 5012 |
async function renderPreview(state, ctx, entity, id) { |
| 5013 |
showPreviewLoading(ctx.preview); |
| 5014 |
let detail; |
| 5015 |
try { |
| 5016 |
detail = await fetchEntityDetail(entity, id); |
| 5017 |
} catch (err) { |
| 5018 |
ctx.preview.replaceChildren(); |
| 5019 |
if (ctx.selectedId !== id) { |
| 5020 |
return; |
| 5021 |
} |
| 5022 |
showPreviewError(ctx.preview, err); |
| 5023 |
return; |
| 5024 |
} |
| 5025 |
if (ctx.selectedId !== id) { |
| 5026 |
return; |
| 5027 |
} |
| 5028 |
appendPostArticle(ctx.preview, detail, entity, { |
| 5029 |
onExplore: () => { |
| 5030 |
navigate(state, { |
| 5031 |
kind: "detail", |
| 5032 |
entityId: entity.id, |
| 5033 |
postId: detail.id, |
| 5034 |
postTitle: stripTags(detail.title.rendered) |
| 5035 |
}); |
| 5036 |
} |
| 5037 |
}); |
| 5038 |
} |
| 5039 |
function showPreviewLoading(host) { |
| 5040 |
host.replaceChildren(); |
| 5041 |
const loading = document.createElement("div"); |
| 5042 |
loading.className = "desktop-mode-my-wordpress__preview-loading"; |
| 5043 |
const spinner = document.createElement("wpd-spinner"); |
| 5044 |
loading.appendChild(spinner); |
| 5045 |
host.appendChild(loading); |
| 5046 |
} |
| 5047 |
function showPreviewError(host, err) { |
| 5048 |
host.replaceChildren(); |
| 5049 |
const box = document.createElement("div"); |
| 5050 |
box.className = "desktop-mode-my-wordpress__error"; |
| 5051 |
box.textContent = err instanceof Error ? err.message : __("Unknown error.", "desktop-mode"); |
| 5052 |
host.appendChild(box); |
| 5053 |
} |
| 5054 |
function appendPostArticle(host, detail, entity, opts = {}) { |
| 5055 |
host.replaceChildren(); |
| 5056 |
const article = document.createElement("article"); |
| 5057 |
article.className = "desktop-mode-my-wordpress__article"; |
| 5058 |
const heading = document.createElement("h2"); |
| 5059 |
heading.className = "desktop-mode-my-wordpress__article-title"; |
| 5060 |
heading.textContent = stripTags(detail.title.rendered); |
| 5061 |
article.appendChild(heading); |
| 5062 |
const meta = buildPostMetaLine(detail); |
| 5063 |
if (meta) { |
| 5064 |
article.appendChild(meta); |
| 5065 |
} |
| 5066 |
const thumb = getThumbnail(detail); |
| 5067 |
if (thumb) { |
| 5068 |
const img = document.createElement("img"); |
| 5069 |
img.className = "desktop-mode-my-wordpress__article-hero"; |
| 5070 |
img.src = thumb; |
| 5071 |
img.alt = ""; |
| 5072 |
article.appendChild(img); |
| 5073 |
} |
| 5074 |
const content = document.createElement("div"); |
| 5075 |
content.className = "desktop-mode-my-wordpress__article-content"; |
| 5076 |
content.innerHTML = detail.content.rendered; |
| 5077 |
article.appendChild(content); |
| 5078 |
const footer = document.createElement("footer"); |
| 5079 |
footer.className = "desktop-mode-my-wordpress__article-footer"; |
| 5080 |
if (opts.onExplore) { |
| 5081 |
const exploreBtn = document.createElement("wpd-button"); |
| 5082 |
exploreBtn.setAttribute("variant", "secondary"); |
| 5083 |
exploreBtn.textContent = __("Explore details", "desktop-mode"); |
| 5084 |
exploreBtn.title = __( |
| 5085 |
"See author, comments, categories, tags, attached media, and revisions for this entry.", |
| 5086 |
"desktop-mode" |
| 5087 |
); |
| 5088 |
exploreBtn.addEventListener("click", () => { |
| 5089 |
opts.onExplore?.(); |
| 5090 |
}); |
| 5091 |
footer.appendChild(exploreBtn); |
| 5092 |
} |
| 5093 |
const editBtn = document.createElement("wpd-button"); |
| 5094 |
editBtn.setAttribute("variant", "primary"); |
| 5095 |
editBtn.textContent = __("Open in editor", "desktop-mode"); |
| 5096 |
editBtn.addEventListener("click", () => { |
| 5097 |
openEditor(entity, detail.id, stripTags(detail.title.rendered)); |
| 5098 |
}); |
| 5099 |
footer.appendChild(editBtn); |
| 5100 |
article.appendChild(footer); |
| 5101 |
host.appendChild(article); |
| 5102 |
} |
| 5103 |
function buildPostMetaLine(detail) { |
| 5104 |
const parts = []; |
| 5105 |
const author = detail._embedded?.author?.[0]; |
| 5106 |
if (author?.name) { |
| 5107 |
parts.push(author.name); |
| 5108 |
} |
| 5109 |
if (detail.date) { |
| 5110 |
try { |
| 5111 |
parts.push( |
| 5112 |
new Date(detail.date).toLocaleDateString(void 0, { |
| 5113 |
year: "numeric", |
| 5114 |
month: "long", |
| 5115 |
day: "numeric" |
| 5116 |
}) |
| 5117 |
); |
| 5118 |
} catch { |
| 5119 |
parts.push(detail.date); |
| 5120 |
} |
| 5121 |
} |
| 5122 |
if (detail.status && detail.status !== "publish") { |
| 5123 |
parts.push(detail.status); |
| 5124 |
} |
| 5125 |
if (parts.length === 0) { |
| 5126 |
return null; |
| 5127 |
} |
| 5128 |
const line = document.createElement("p"); |
| 5129 |
line.className = "desktop-mode-my-wordpress__article-meta"; |
| 5130 |
line.textContent = parts.join(" · "); |
| 5131 |
return line; |
| 5132 |
} |
| 5133 |
function renderDetail(state, entity, postId, postTitle) { |
| 5134 |
const split = document.createElement("div"); |
| 5135 |
split.className = "desktop-mode-my-wordpress__split"; |
| 5136 |
const left = document.createElement("div"); |
| 5137 |
left.className = "desktop-mode-my-wordpress__list"; |
| 5138 |
const tiles = document.createElement("div"); |
| 5139 |
tiles.className = "desktop-mode-my-wordpress__tiles desktop-mode-my-wordpress__canvas"; |
| 5140 |
tiles.setAttribute("role", "list"); |
| 5141 |
left.appendChild(tiles); |
| 5142 |
const right = document.createElement("div"); |
| 5143 |
right.className = "desktop-mode-my-wordpress__preview"; |
| 5144 |
showPreviewLoading(right); |
| 5145 |
split.appendChild(left); |
| 5146 |
split.appendChild(right); |
| 5147 |
state.body.appendChild(split); |
| 5148 |
const layout = createTileLayout( |
| 5149 |
tiles, |
| 5150 |
`detail:${entity.id}:${postId}` |
| 5151 |
); |
| 5152 |
const menu = attachIconCanvasMenu(tiles, { |
| 5153 |
scope: `my-wordpress:${entity.id}:detail:${postId}`, |
| 5154 |
onSort: (mode) => layout.sort(mode) |
| 5155 |
}); |
| 5156 |
state.teardown.push(() => menu.dispose()); |
| 5157 |
state.teardown.push(() => layout.dispose()); |
| 5158 |
showLoadingSkeleton(tiles, layout, true); |
| 5159 |
void (async () => { |
| 5160 |
let detail; |
| 5161 |
try { |
| 5162 |
detail = await fetchEntityDetail(entity, postId); |
| 5163 |
} catch (err) { |
| 5164 |
hideLoadingSkeleton(tiles); |
| 5165 |
renderListError( |
| 5166 |
tiles, |
| 5167 |
err instanceof Error ? err.message : __("Unknown error.", "desktop-mode") |
| 5168 |
); |
| 5169 |
showPreviewError(right, err); |
| 5170 |
return; |
| 5171 |
} |
| 5172 |
if (state.route.kind !== "detail" || state.route.postId !== postId) { |
| 5173 |
return; |
| 5174 |
} |
| 5175 |
hideLoadingSkeleton(tiles); |
| 5176 |
const select = createTileSelector(); |
| 5177 |
const subFolders = []; |
| 5178 |
let dateCounter = 0; |
| 5179 |
const nextDate = () => new Date(2020, 0, 1 + dateCounter++).toISOString(); |
| 5180 |
const author = detail._embedded?.author?.[0]; |
| 5181 |
subFolders.push({ |
| 5182 |
relation: "author", |
| 5183 |
label: author?.name ? sprintf( |
| 5184 |
// translators: %s is an author display name. |
| 5185 |
__("Author · %s", "desktop-mode"), |
| 5186 |
author.name |
| 5187 |
) : __("Author", "desktop-mode"), |
| 5188 |
icon: "dashicons-admin-users", |
| 5189 |
count: 1, |
| 5190 |
disabled: !detail.author, |
| 5191 |
synthDate: nextDate() |
| 5192 |
}); |
| 5193 |
const contributors = detail.desktop_mode_contributors ?? []; |
| 5194 |
if (contributors.length > 0) { |
| 5195 |
subFolders.push({ |
| 5196 |
relation: "contributors", |
| 5197 |
label: sprintf( |
| 5198 |
// translators: %d is a count of additional contributor users. |
| 5199 |
_n( |
| 5200 |
"Contributors · %d", |
| 5201 |
"Contributors · %d", |
| 5202 |
contributors.length |
| 5203 |
), |
| 5204 |
contributors.length |
| 5205 |
), |
| 5206 |
icon: "dashicons-groups", |
| 5207 |
count: contributors.length, |
| 5208 |
synthDate: nextDate() |
| 5209 |
}); |
| 5210 |
} |
| 5211 |
const commentsHref = (detail._links?.replies ?? [])[0]; |
| 5212 |
const commentCountFromLink = typeof commentsHref?.count === "number" ? commentsHref.count : null; |
| 5213 |
const repliesEmbed = detail._embedded?.replies?.[0] ?? []; |
| 5214 |
const commentCount = commentCountFromLink ?? repliesEmbed.length; |
| 5215 |
subFolders.push({ |
| 5216 |
relation: "comments", |
| 5217 |
label: sprintf( |
| 5218 |
// translators: %d is a comment count. |
| 5219 |
_n("Comments · %d", "Comments · %d", commentCount), |
| 5220 |
commentCount |
| 5221 |
), |
| 5222 |
icon: "dashicons-admin-comments", |
| 5223 |
count: commentCount, |
| 5224 |
disabled: detail.comment_status === "closed" && commentCount === 0, |
| 5225 |
synthDate: nextDate() |
| 5226 |
}); |
| 5227 |
const categoryIds = detail.categories ?? []; |
| 5228 |
if (categoryIds.length > 0) { |
| 5229 |
subFolders.push({ |
| 5230 |
relation: "categories", |
| 5231 |
label: sprintf( |
| 5232 |
// translators: %d is a category count. |
| 5233 |
_n("Categories · %d", "Categories · %d", categoryIds.length), |
| 5234 |
categoryIds.length |
| 5235 |
), |
| 5236 |
icon: "dashicons-category", |
| 5237 |
count: categoryIds.length, |
| 5238 |
synthDate: nextDate() |
| 5239 |
}); |
| 5240 |
} |
| 5241 |
const tagIds = detail.tags ?? []; |
| 5242 |
if (tagIds.length > 0) { |
| 5243 |
subFolders.push({ |
| 5244 |
relation: "tags", |
| 5245 |
label: sprintf( |
| 5246 |
// translators: %d is a tag count. |
| 5247 |
_n("Tags · %d", "Tags · %d", tagIds.length), |
| 5248 |
tagIds.length |
| 5249 |
), |
| 5250 |
icon: "dashicons-tag", |
| 5251 |
count: tagIds.length, |
| 5252 |
synthDate: nextDate() |
| 5253 |
}); |
| 5254 |
} |
| 5255 |
if (detail.featured_media && detail.featured_media > 0) { |
| 5256 |
subFolders.push({ |
| 5257 |
relation: "media", |
| 5258 |
label: __("Attached media", "desktop-mode"), |
| 5259 |
icon: "dashicons-format-image", |
| 5260 |
count: 1, |
| 5261 |
synthDate: nextDate() |
| 5262 |
}); |
| 5263 |
} else { |
| 5264 |
subFolders.push({ |
| 5265 |
relation: "media", |
| 5266 |
label: __("Attached media", "desktop-mode"), |
| 5267 |
icon: "dashicons-admin-media", |
| 5268 |
count: 0, |
| 5269 |
synthDate: nextDate() |
| 5270 |
}); |
| 5271 |
} |
| 5272 |
subFolders.push({ |
| 5273 |
relation: "revisions", |
| 5274 |
label: __("Revisions", "desktop-mode"), |
| 5275 |
icon: "dashicons-backup", |
| 5276 |
count: 0, |
| 5277 |
synthDate: nextDate() |
| 5278 |
}); |
| 5279 |
for (const sub of subFolders) { |
| 5280 |
const tile = buildIconTile({ |
| 5281 |
role: "folder", |
| 5282 |
icon: sub.icon, |
| 5283 |
label: sub.label |
| 5284 |
}); |
| 5285 |
tile.dataset.relation = sub.relation; |
| 5286 |
if (sub.disabled) { |
| 5287 |
tile.setAttribute("aria-disabled", "true"); |
| 5288 |
} |
| 5289 |
const tileKey = `relation:${sub.relation}`; |
| 5290 |
layout.place(tile, tileKey, { |
| 5291 |
name: sub.label, |
| 5292 |
date: sub.synthDate |
| 5293 |
}); |
| 5294 |
tile.addEventListener("click", () => select(tile)); |
| 5295 |
if (!sub.disabled) { |
| 5296 |
tile.addEventListener("dblclick", (e) => { |
| 5297 |
e.preventDefault(); |
| 5298 |
navigate(state, { |
| 5299 |
kind: "sub-list", |
| 5300 |
entityId: entity.id, |
| 5301 |
postId, |
| 5302 |
postTitle, |
| 5303 |
relation: sub.relation |
| 5304 |
}); |
| 5305 |
}); |
| 5306 |
} |
| 5307 |
tiles.appendChild(tile); |
| 5308 |
} |
| 5309 |
appendPostArticle(right, detail, entity); |
| 5310 |
const segments = [ |
| 5311 |
{ |
| 5312 |
id: "count", |
| 5313 |
label: pluralLabel( |
| 5314 |
subFolders.length, |
| 5315 |
"folder", |
| 5316 |
"folders" |
| 5317 |
), |
| 5318 |
align: "start", |
| 5319 |
sort: 10 |
| 5320 |
} |
| 5321 |
]; |
| 5322 |
if (detail.status) { |
| 5323 |
segments.push({ |
| 5324 |
id: "status", |
| 5325 |
label: detail.status, |
| 5326 |
align: "end", |
| 5327 |
sort: 10 |
| 5328 |
}); |
| 5329 |
} |
| 5330 |
paintStatus(state, segments, { |
| 5331 |
view: "detail", |
| 5332 |
entityId: entity.id, |
| 5333 |
postId |
| 5334 |
}); |
| 5335 |
})(); |
| 5336 |
paintStatus( |
| 5337 |
state, |
| 5338 |
[ |
| 5339 |
{ |
| 5340 |
id: "loading", |
| 5341 |
label: __("Loading…", "desktop-mode"), |
| 5342 |
align: "start", |
| 5343 |
sort: 10 |
| 5344 |
} |
| 5345 |
], |
| 5346 |
{ view: "detail", entityId: entity.id, postId } |
| 5347 |
); |
| 5348 |
} |
| 5349 |
function renderSubList(state, entity, postId, postTitle, relation) { |
| 5350 |
const split = document.createElement("div"); |
| 5351 |
split.className = "desktop-mode-my-wordpress__split"; |
| 5352 |
const left = document.createElement("div"); |
| 5353 |
left.className = "desktop-mode-my-wordpress__list"; |
| 5354 |
const tiles = document.createElement("div"); |
| 5355 |
tiles.className = "desktop-mode-my-wordpress__tiles desktop-mode-my-wordpress__canvas"; |
| 5356 |
tiles.setAttribute("role", "list"); |
| 5357 |
left.appendChild(tiles); |
| 5358 |
const right = document.createElement("div"); |
| 5359 |
right.className = "desktop-mode-my-wordpress__preview"; |
| 5360 |
const previewEmpty = document.createElement("div"); |
| 5361 |
previewEmpty.className = "desktop-mode-my-wordpress__preview-empty"; |
| 5362 |
previewEmpty.textContent = __( |
| 5363 |
"Select an item to preview it here.", |
| 5364 |
"desktop-mode" |
| 5365 |
); |
| 5366 |
right.appendChild(previewEmpty); |
| 5367 |
split.appendChild(left); |
| 5368 |
split.appendChild(right); |
| 5369 |
state.body.appendChild(split); |
| 5370 |
const layout = createTileLayout( |
| 5371 |
tiles, |
| 5372 |
`sub-list:${entity.id}:${postId}:${relation}` |
| 5373 |
); |
| 5374 |
const menu = attachIconCanvasMenu(tiles, { |
| 5375 |
scope: `my-wordpress:${entity.id}:${relation}:${postId}`, |
| 5376 |
onSort: (mode) => layout.sort(mode) |
| 5377 |
}); |
| 5378 |
state.teardown.push(() => menu.dispose()); |
| 5379 |
state.teardown.push(() => layout.dispose()); |
| 5380 |
showLoadingSkeleton(tiles, layout, true); |
| 5381 |
paintStatus( |
| 5382 |
state, |
| 5383 |
[ |
| 5384 |
{ |
| 5385 |
id: "loading", |
| 5386 |
label: __("Loading…", "desktop-mode"), |
| 5387 |
align: "start", |
| 5388 |
sort: 10 |
| 5389 |
} |
| 5390 |
], |
| 5391 |
{ view: "sub-list", entityId: entity.id, postId, relation } |
| 5392 |
); |
| 5393 |
void (async () => { |
| 5394 |
let items; |
| 5395 |
try { |
| 5396 |
items = await loadSubItems(entity, postId, relation); |
| 5397 |
} catch (err) { |
| 5398 |
hideLoadingSkeleton(tiles); |
| 5399 |
renderListError( |
| 5400 |
tiles, |
| 5401 |
err instanceof Error ? err.message : __("Unknown error.", "desktop-mode") |
| 5402 |
); |
| 5403 |
return; |
| 5404 |
} |
| 5405 |
if (state.route.kind !== "sub-list" || state.route.postId !== postId || state.route.relation !== relation) { |
| 5406 |
return; |
| 5407 |
} |
| 5408 |
hideLoadingSkeleton(tiles); |
| 5409 |
paintStatus( |
| 5410 |
state, |
| 5411 |
[ |
| 5412 |
{ |
| 5413 |
id: "count", |
| 5414 |
label: pluralLabel(items.length, "item", "items"), |
| 5415 |
align: "start", |
| 5416 |
sort: 10 |
| 5417 |
} |
| 5418 |
], |
| 5419 |
{ |
| 5420 |
view: "sub-list", |
| 5421 |
entityId: entity.id, |
| 5422 |
postId, |
| 5423 |
relation |
| 5424 |
} |
| 5425 |
); |
| 5426 |
if (items.length === 0) { |
| 5427 |
renderListEmptyMessage( |
| 5428 |
tiles, |
| 5429 |
emptySubListMessage(relation) |
| 5430 |
); |
| 5431 |
return; |
| 5432 |
} |
| 5433 |
let selectedKey = null; |
| 5434 |
let selectedTile = null; |
| 5435 |
for (const item of items) { |
| 5436 |
const tile = buildIconTile({ |
| 5437 |
role: "entry", |
| 5438 |
icon: item.icon, |
| 5439 |
label: item.label |
| 5440 |
}); |
| 5441 |
tile.dataset.subItemId = item.id; |
| 5442 |
const tileKey = `sub:${item.id}`; |
| 5443 |
layout.place(tile, tileKey, { |
| 5444 |
name: item.label, |
| 5445 |
date: item.date |
| 5446 |
}); |
| 5447 |
tile.addEventListener("click", () => { |
| 5448 |
if (selectedTile) { |
| 5449 |
selectedTile.classList.remove( |
| 5450 |
"desktop-mode-file-tile--selected" |
| 5451 |
); |
| 5452 |
} |
| 5453 |
tile.classList.add( |
| 5454 |
"desktop-mode-file-tile--selected" |
| 5455 |
); |
| 5456 |
selectedTile = tile; |
| 5457 |
selectedKey = tileKey; |
| 5458 |
showPreviewLoading(right); |
| 5459 |
Promise.resolve(item.preview()).then((node) => { |
| 5460 |
if (selectedKey !== tileKey) { |
| 5461 |
return; |
| 5462 |
} |
| 5463 |
right.replaceChildren(node); |
| 5464 |
}).catch((err) => { |
| 5465 |
if (selectedKey !== tileKey) { |
| 5466 |
return; |
| 5467 |
} |
| 5468 |
showPreviewError(right, err); |
| 5469 |
}); |
| 5470 |
}); |
| 5471 |
tiles.appendChild(tile); |
| 5472 |
} |
| 5473 |
})(); |
| 5474 |
} |
| 5475 |
function renderListEmptyMessage(host, message) { |
| 5476 |
const empty = document.createElement("div"); |
| 5477 |
empty.className = "desktop-mode-my-wordpress__empty"; |
| 5478 |
empty.textContent = message; |
| 5479 |
host.appendChild(empty); |
| 5480 |
} |
| 5481 |
function emptySubListMessage(relation) { |
| 5482 |
switch (relation) { |
| 5483 |
case "comments": |
| 5484 |
return __("No comments on this post yet.", "desktop-mode"); |
| 5485 |
case "categories": |
| 5486 |
return __("No categories assigned.", "desktop-mode"); |
| 5487 |
case "tags": |
| 5488 |
return __("No tags assigned.", "desktop-mode"); |
| 5489 |
case "media": |
| 5490 |
return __("No media attached to this post.", "desktop-mode"); |
| 5491 |
case "revisions": |
| 5492 |
return __("No revisions yet.", "desktop-mode"); |
| 5493 |
case "author": |
| 5494 |
return __("No author available.", "desktop-mode"); |
| 5495 |
case "contributors": |
| 5496 |
return __("No additional contributors.", "desktop-mode"); |
| 5497 |
default: |
| 5498 |
return __("Nothing to show.", "desktop-mode"); |
| 5499 |
} |
| 5500 |
} |
| 5501 |
async function loadSubItems(entity, postId, relation) { |
| 5502 |
if (relation === "comments") { |
| 5503 |
const comments = await fetchComments(postId); |
| 5504 |
return comments.map(commentToView); |
| 5505 |
} |
| 5506 |
if (relation === "media") { |
| 5507 |
const detail = await fetchEntityDetail(entity, postId); |
| 5508 |
const ids = /* @__PURE__ */ new Set(); |
| 5509 |
if (detail.featured_media && detail.featured_media > 0) { |
| 5510 |
ids.add(detail.featured_media); |
| 5511 |
} |
| 5512 |
const serverList = detail.desktop_mode_attached_media; |
| 5513 |
if (Array.isArray(serverList) && serverList.length > 0) { |
| 5514 |
for (const id of serverList) { |
| 5515 |
if (typeof id === "number" && id > 0) { |
| 5516 |
ids.add(id); |
| 5517 |
} |
| 5518 |
} |
| 5519 |
} else { |
| 5520 |
extractContentMediaIds(detail.content?.rendered ?? "").forEach( |
| 5521 |
(id) => ids.add(id) |
| 5522 |
); |
| 5523 |
} |
| 5524 |
const [batched, parentAttached] = await Promise.all([ |
| 5525 |
fetchMediaByIds(Array.from(ids)).catch(() => []), |
| 5526 |
fetchAttachedMedia(postId).catch(() => []) |
| 5527 |
]); |
| 5528 |
const seen = /* @__PURE__ */ new Set(); |
| 5529 |
const merged = []; |
| 5530 |
const featuredId = detail.featured_media ?? 0; |
| 5531 |
const orderedFromBatch = batched.slice().sort((a, b) => { |
| 5532 |
if (a.id === featuredId && b.id !== featuredId) { |
| 5533 |
return -1; |
| 5534 |
} |
| 5535 |
if (b.id === featuredId && a.id !== featuredId) { |
| 5536 |
return 1; |
| 5537 |
} |
| 5538 |
return 0; |
| 5539 |
}); |
| 5540 |
for (const m of [...orderedFromBatch, ...parentAttached]) { |
| 5541 |
if (seen.has(m.id)) { |
| 5542 |
continue; |
| 5543 |
} |
| 5544 |
seen.add(m.id); |
| 5545 |
merged.push(m); |
| 5546 |
} |
| 5547 |
return merged.map(mediaToView); |
| 5548 |
} |
| 5549 |
if (relation === "categories" || relation === "tags") { |
| 5550 |
const detail = await fetchEntityDetail(entity, postId); |
| 5551 |
const ids = relation === "categories" ? detail.categories ?? [] : detail.tags ?? []; |
| 5552 |
const terms = await fetchTerms( |
| 5553 |
relation === "categories" ? "categories" : "tags", |
| 5554 |
ids |
| 5555 |
); |
| 5556 |
return terms.map(termToView); |
| 5557 |
} |
| 5558 |
if (relation === "author") { |
| 5559 |
const detail = await fetchEntityDetail(entity, postId); |
| 5560 |
if (!detail.author) { |
| 5561 |
return []; |
| 5562 |
} |
| 5563 |
const user = await fetchUser(detail.author); |
| 5564 |
return [userToView(user)]; |
| 5565 |
} |
| 5566 |
if (relation === "contributors") { |
| 5567 |
const detail = await fetchEntityDetail(entity, postId); |
| 5568 |
const contribs = detail.desktop_mode_contributors ?? []; |
| 5569 |
return contribs.map(contributorToView); |
| 5570 |
} |
| 5571 |
if (relation === "revisions") { |
| 5572 |
const revs = await fetchRevisions(entity, postId); |
| 5573 |
const ordered = revs.slice().sort((a, b) => { |
| 5574 |
const ta = Date.parse(a.modified || a.date || ""); |
| 5575 |
const tb = Date.parse(b.modified || b.date || ""); |
| 5576 |
return tb - ta; |
| 5577 |
}); |
| 5578 |
return ordered.map((r) => revisionToView(r, entity, postId)); |
| 5579 |
} |
| 5580 |
return []; |
| 5581 |
} |
| 5582 |
function commentToView(c) { |
| 5583 |
const author = c.author_name || __("Anonymous", "desktop-mode"); |
| 5584 |
return { |
| 5585 |
id: `comment:${c.id}`, |
| 5586 |
icon: "dashicons-admin-comments", |
| 5587 |
label: author, |
| 5588 |
date: c.date, |
| 5589 |
preview: async () => renderCommentDossier(c) |
| 5590 |
}; |
| 5591 |
} |
| 5592 |
async function renderCommentDossier(c) { |
| 5593 |
let stats = null; |
| 5594 |
try { |
| 5595 |
stats = await fetchCommentStats(c.id); |
| 5596 |
} catch { |
| 5597 |
stats = null; |
| 5598 |
} |
| 5599 |
const wrap = document.createElement("div"); |
| 5600 |
wrap.className = "desktop-mode-my-wordpress__article desktop-mode-my-wordpress__comment"; |
| 5601 |
if (!stats) { |
| 5602 |
appendCommentHeader(wrap, { |
| 5603 |
authorName: c.author_name || __("Anonymous", "desktop-mode"), |
| 5604 |
avatarUrl: c.author_avatar_urls ? pickAvatar(c.author_avatar_urls) ?? "" : "", |
| 5605 |
authorLink: "", |
| 5606 |
authorWebsite: "", |
| 5607 |
status: c.status || "approved", |
| 5608 |
date: c.date, |
| 5609 |
editLink: "", |
| 5610 |
totalApproved: 0 |
| 5611 |
}); |
| 5612 |
const body2 = document.createElement("div"); |
| 5613 |
body2.className = "desktop-mode-my-wordpress__article-content desktop-mode-my-wordpress__comment-body"; |
| 5614 |
body2.innerHTML = c.content.rendered; |
| 5615 |
wrap.appendChild(body2); |
| 5616 |
return wrap; |
| 5617 |
} |
| 5618 |
const { author, comment, post, parent, replies } = stats; |
| 5619 |
appendCommentHeader(wrap, { |
| 5620 |
authorName: author.displayName || author.name || __("Anonymous", "desktop-mode"), |
| 5621 |
avatarUrl: author.avatarUrl, |
| 5622 |
authorLink: author.profileLink ?? "", |
| 5623 |
authorWebsite: author.url ?? "", |
| 5624 |
status: comment.status, |
| 5625 |
date: comment.date, |
| 5626 |
editLink: comment.editLink, |
| 5627 |
totalApproved: author.totalApprovedComments |
| 5628 |
}); |
| 5629 |
if (parent) { |
| 5630 |
const quote = document.createElement("blockquote"); |
| 5631 |
quote.className = "desktop-mode-my-wordpress__comment-quote"; |
| 5632 |
const lead = document.createElement("div"); |
| 5633 |
lead.className = "desktop-mode-my-wordpress__comment-quote-lead"; |
| 5634 |
lead.textContent = sprintf( |
| 5635 |
// translators: %s is the parent comment's author name. |
| 5636 |
__("In reply to %s", "desktop-mode"), |
| 5637 |
parent.authorName |
| 5638 |
); |
| 5639 |
quote.appendChild(lead); |
| 5640 |
const excerpt = document.createElement("p"); |
| 5641 |
excerpt.textContent = parent.excerpt || ""; |
| 5642 |
quote.appendChild(excerpt); |
| 5643 |
wrap.appendChild(quote); |
| 5644 |
} |
| 5645 |
const body = document.createElement("div"); |
| 5646 |
body.className = "desktop-mode-my-wordpress__article-content desktop-mode-my-wordpress__comment-body"; |
| 5647 |
body.innerHTML = comment.rendered; |
| 5648 |
wrap.appendChild(body); |
| 5649 |
if (post) { |
| 5650 |
const section = document.createElement("section"); |
| 5651 |
section.className = "desktop-mode-my-wordpress__user-section"; |
| 5652 |
const h = document.createElement("h3"); |
| 5653 |
h.textContent = __("On post", "desktop-mode"); |
| 5654 |
section.appendChild(h); |
| 5655 |
const card = document.createElement("div"); |
| 5656 |
card.className = "desktop-mode-my-wordpress__comment-post"; |
| 5657 |
const titleEl = document.createElement("a"); |
| 5658 |
titleEl.className = "desktop-mode-my-wordpress__comment-post-title"; |
| 5659 |
titleEl.href = post.link; |
| 5660 |
titleEl.target = "_blank"; |
| 5661 |
titleEl.rel = "noopener noreferrer"; |
| 5662 |
titleEl.textContent = post.title || `#${post.id}`; |
| 5663 |
card.appendChild(titleEl); |
| 5664 |
const meta = document.createElement("div"); |
| 5665 |
meta.className = "desktop-mode-my-wordpress__comment-post-meta"; |
| 5666 |
const parts = []; |
| 5667 |
parts.push(formatDate(post.date)); |
| 5668 |
if (post.author?.name) { |
| 5669 |
parts.push(post.author.name); |
| 5670 |
} |
| 5671 |
if (post.status && post.status !== "publish") { |
| 5672 |
parts.push(post.status); |
| 5673 |
} |
| 5674 |
meta.textContent = parts.join(" · "); |
| 5675 |
card.appendChild(meta); |
| 5676 |
section.appendChild(card); |
| 5677 |
wrap.appendChild(section); |
| 5678 |
} |
| 5679 |
if (replies.length > 0) { |
| 5680 |
const section = document.createElement("section"); |
| 5681 |
section.className = "desktop-mode-my-wordpress__user-section"; |
| 5682 |
const h = document.createElement("h3"); |
| 5683 |
h.textContent = sprintf( |
| 5684 |
// translators: %d is the number of direct replies to a comment. |
| 5685 |
_n("Reply (%d)", "Replies (%d)", replies.length), |
| 5686 |
replies.length |
| 5687 |
); |
| 5688 |
section.appendChild(h); |
| 5689 |
const list = document.createElement("ul"); |
| 5690 |
list.className = "desktop-mode-my-wordpress__comment-replies"; |
| 5691 |
for (const r of replies) { |
| 5692 |
const li = document.createElement("li"); |
| 5693 |
li.className = "desktop-mode-my-wordpress__comment-reply"; |
| 5694 |
if (r.avatarUrl) { |
| 5695 |
const img = document.createElement("img"); |
| 5696 |
img.src = r.avatarUrl; |
| 5697 |
img.alt = ""; |
| 5698 |
img.className = "desktop-mode-my-wordpress__comment-reply-avatar"; |
| 5699 |
li.appendChild(img); |
| 5700 |
} |
| 5701 |
const txt = document.createElement("div"); |
| 5702 |
txt.className = "desktop-mode-my-wordpress__comment-reply-text"; |
| 5703 |
const head = document.createElement("div"); |
| 5704 |
head.className = "desktop-mode-my-wordpress__comment-reply-head"; |
| 5705 |
const who = document.createElement("span"); |
| 5706 |
who.className = "desktop-mode-my-wordpress__comment-reply-name"; |
| 5707 |
who.textContent = r.authorName || __("Anonymous", "desktop-mode"); |
| 5708 |
head.appendChild(who); |
| 5709 |
const when = document.createElement("span"); |
| 5710 |
when.className = "desktop-mode-my-wordpress__comment-reply-when"; |
| 5711 |
when.textContent = formatDate(r.date); |
| 5712 |
head.appendChild(when); |
| 5713 |
txt.appendChild(head); |
| 5714 |
const ex = document.createElement("p"); |
| 5715 |
ex.className = "desktop-mode-my-wordpress__comment-reply-excerpt"; |
| 5716 |
ex.textContent = r.excerpt || ""; |
| 5717 |
txt.appendChild(ex); |
| 5718 |
li.appendChild(txt); |
| 5719 |
list.appendChild(li); |
| 5720 |
} |
| 5721 |
section.appendChild(list); |
| 5722 |
wrap.appendChild(section); |
| 5723 |
} |
| 5724 |
if (comment.ip || comment.userAgent) { |
| 5725 |
const dl = document.createElement("dl"); |
| 5726 |
dl.className = "desktop-mode-my-wordpress__user-milestones"; |
| 5727 |
if (comment.ip) { |
| 5728 |
const dt = document.createElement("dt"); |
| 5729 |
dt.textContent = __("IP", "desktop-mode"); |
| 5730 |
dl.appendChild(dt); |
| 5731 |
const dd = document.createElement("dd"); |
| 5732 |
dd.textContent = comment.ip; |
| 5733 |
dl.appendChild(dd); |
| 5734 |
} |
| 5735 |
if (comment.userAgent) { |
| 5736 |
const dt = document.createElement("dt"); |
| 5737 |
dt.textContent = __("User agent", "desktop-mode"); |
| 5738 |
dl.appendChild(dt); |
| 5739 |
const dd = document.createElement("dd"); |
| 5740 |
dd.textContent = comment.userAgent; |
| 5741 |
dl.appendChild(dd); |
| 5742 |
} |
| 5743 |
wrap.appendChild(dl); |
| 5744 |
} |
| 5745 |
return wrap; |
| 5746 |
} |
| 5747 |
function appendCommentHeader(host, header) { |
| 5748 |
const wrap = document.createElement("header"); |
| 5749 |
wrap.className = "desktop-mode-my-wordpress__user-header"; |
| 5750 |
if (header.avatarUrl) { |
| 5751 |
const img = document.createElement("img"); |
| 5752 |
img.src = header.avatarUrl; |
| 5753 |
img.alt = ""; |
| 5754 |
img.className = "desktop-mode-my-wordpress__user-avatar"; |
| 5755 |
wrap.appendChild(img); |
| 5756 |
} |
| 5757 |
const right = document.createElement("div"); |
| 5758 |
right.className = "desktop-mode-my-wordpress__user-headline"; |
| 5759 |
const h = document.createElement("h2"); |
| 5760 |
h.className = "desktop-mode-my-wordpress__article-title"; |
| 5761 |
h.textContent = header.authorName; |
| 5762 |
right.appendChild(h); |
| 5763 |
const badges = document.createElement("div"); |
| 5764 |
badges.className = "desktop-mode-my-wordpress__user-roles"; |
| 5765 |
const status = document.createElement("span"); |
| 5766 |
status.className = "desktop-mode-my-wordpress__user-role desktop-mode-my-wordpress__comment-status--" + (header.status || "approved"); |
| 5767 |
status.textContent = header.status || "approved"; |
| 5768 |
badges.appendChild(status); |
| 5769 |
const dateBadge = document.createElement("span"); |
| 5770 |
dateBadge.className = "desktop-mode-my-wordpress__user-role desktop-mode-my-wordpress__comment-date-badge"; |
| 5771 |
dateBadge.textContent = formatDate(header.date); |
| 5772 |
badges.appendChild(dateBadge); |
| 5773 |
if (header.totalApproved > 1) { |
| 5774 |
const totalBadge = document.createElement("span"); |
| 5775 |
totalBadge.className = "desktop-mode-my-wordpress__user-role"; |
| 5776 |
totalBadge.textContent = sprintf( |
| 5777 |
// translators: %d is a comment count for a particular author. |
| 5778 |
_n( |
| 5779 |
"%d comment site-wide", |
| 5780 |
"%d comments site-wide", |
| 5781 |
header.totalApproved |
| 5782 |
), |
| 5783 |
header.totalApproved |
| 5784 |
); |
| 5785 |
badges.appendChild(totalBadge); |
| 5786 |
} |
| 5787 |
right.appendChild(badges); |
| 5788 |
const links = document.createElement("div"); |
| 5789 |
links.className = "desktop-mode-my-wordpress__user-links"; |
| 5790 |
if (header.authorLink) { |
| 5791 |
const a = document.createElement("a"); |
| 5792 |
a.href = header.authorLink; |
| 5793 |
a.target = "_blank"; |
| 5794 |
a.rel = "noopener noreferrer"; |
| 5795 |
a.textContent = __("Author archive", "desktop-mode"); |
| 5796 |
links.appendChild(a); |
| 5797 |
} |
| 5798 |
if (header.authorWebsite) { |
| 5799 |
const a = document.createElement("a"); |
| 5800 |
a.href = header.authorWebsite; |
| 5801 |
a.target = "_blank"; |
| 5802 |
a.rel = "noopener noreferrer"; |
| 5803 |
a.textContent = __("Website", "desktop-mode"); |
| 5804 |
links.appendChild(a); |
| 5805 |
} |
| 5806 |
if (header.editLink) { |
| 5807 |
const a = document.createElement("a"); |
| 5808 |
a.href = header.editLink; |
| 5809 |
a.target = "_blank"; |
| 5810 |
a.rel = "noopener noreferrer"; |
| 5811 |
a.textContent = __("Moderate", "desktop-mode"); |
| 5812 |
links.appendChild(a); |
| 5813 |
} |
| 5814 |
if (links.childElementCount > 0) { |
| 5815 |
right.appendChild(links); |
| 5816 |
} |
| 5817 |
wrap.appendChild(right); |
| 5818 |
host.appendChild(wrap); |
| 5819 |
} |
| 5820 |
function userToView(u) { |
| 5821 |
return { |
| 5822 |
id: `user:${u.id}`, |
| 5823 |
icon: "dashicons-admin-users", |
| 5824 |
label: u.name || u.slug || `#${u.id}`, |
| 5825 |
date: (/* @__PURE__ */ new Date(0)).toISOString(), |
| 5826 |
preview: async () => { |
| 5827 |
const fallbackName = u.name || u.slug || `#${u.id}`; |
| 5828 |
const fallbackAvatar = pickAvatar(u.avatar_urls) ?? ""; |
| 5829 |
return renderUserDossier({ |
| 5830 |
userId: u.id, |
| 5831 |
fallbackName, |
| 5832 |
fallbackAvatar, |
| 5833 |
fallbackDescription: u.description ?? "" |
| 5834 |
}); |
| 5835 |
} |
| 5836 |
}; |
| 5837 |
} |
| 5838 |
function contributorToView(c) { |
| 5839 |
return { |
| 5840 |
id: `contributor:${c.userId}`, |
| 5841 |
icon: "dashicons-admin-users", |
| 5842 |
label: c.userName || `#${c.userId}`, |
| 5843 |
date: (/* @__PURE__ */ new Date(0)).toISOString(), |
| 5844 |
preview: async () => renderUserDossier({ |
| 5845 |
userId: c.userId, |
| 5846 |
fallbackName: c.userName, |
| 5847 |
fallbackAvatar: c.userAvatarUrl, |
| 5848 |
fallbackDescription: "" |
| 5849 |
}) |
| 5850 |
}; |
| 5851 |
} |
| 5852 |
async function renderUserDossier(opts) { |
| 5853 |
let stats = null; |
| 5854 |
try { |
| 5855 |
stats = await fetchUserStats(opts.userId); |
| 5856 |
} catch { |
| 5857 |
stats = null; |
| 5858 |
} |
| 5859 |
const wrap = document.createElement("div"); |
| 5860 |
wrap.className = "desktop-mode-my-wordpress__article desktop-mode-my-wordpress__user"; |
| 5861 |
if (!stats) { |
| 5862 |
let basic = null; |
| 5863 |
try { |
| 5864 |
basic = await fetchUser(opts.userId); |
| 5865 |
} catch { |
| 5866 |
basic = null; |
| 5867 |
} |
| 5868 |
appendUserHeader(wrap, { |
| 5869 |
name: basic?.name ?? opts.fallbackName, |
| 5870 |
avatarUrl: basic && pickAvatar(basic.avatar_urls) || opts.fallbackAvatar, |
| 5871 |
roles: [], |
| 5872 |
website: "", |
| 5873 |
link: basic?.link ?? "" |
| 5874 |
}); |
| 5875 |
const desc = basic?.description ?? opts.fallbackDescription; |
| 5876 |
if (desc) { |
| 5877 |
const bio = document.createElement("div"); |
| 5878 |
bio.className = "desktop-mode-my-wordpress__user-bio"; |
| 5879 |
bio.textContent = desc; |
| 5880 |
wrap.appendChild(bio); |
| 5881 |
} |
| 5882 |
return wrap; |
| 5883 |
} |
| 5884 |
const { profile, counts, recent, topTerms, milestones, activity: activity2 } = stats; |
| 5885 |
appendUserHeader(wrap, { |
| 5886 |
name: profile.name || opts.fallbackName, |
| 5887 |
avatarUrl: profile.avatarUrl || opts.fallbackAvatar, |
| 5888 |
roles: profile.roleLabels ?? [], |
| 5889 |
website: profile.website, |
| 5890 |
link: profile.link |
| 5891 |
}); |
| 5892 |
if (profile.description) { |
| 5893 |
const bio = document.createElement("div"); |
| 5894 |
bio.className = "desktop-mode-my-wordpress__user-bio"; |
| 5895 |
bio.textContent = profile.description; |
| 5896 |
wrap.appendChild(bio); |
| 5897 |
} |
| 5898 |
const cards = document.createElement("div"); |
| 5899 |
cards.className = "desktop-mode-my-wordpress__user-stats"; |
| 5900 |
cards.appendChild( |
| 5901 |
buildStatCard( |
| 5902 |
counts.posts.total.toLocaleString(), |
| 5903 |
__("Posts", "desktop-mode"), |
| 5904 |
counts.posts.publish > 0 ? sprintf( |
| 5905 |
// translators: %d is a published-post count. |
| 5906 |
__("%d published", "desktop-mode"), |
| 5907 |
counts.posts.publish |
| 5908 |
) : "" |
| 5909 |
) |
| 5910 |
); |
| 5911 |
cards.appendChild( |
| 5912 |
buildStatCard( |
| 5913 |
counts.pages.total.toLocaleString(), |
| 5914 |
__("Pages", "desktop-mode"), |
| 5915 |
counts.pages.publish > 0 ? sprintf( |
| 5916 |
// translators: %d is a published-page count. |
| 5917 |
__("%d published", "desktop-mode"), |
| 5918 |
counts.pages.publish |
| 5919 |
) : "" |
| 5920 |
) |
| 5921 |
); |
| 5922 |
cards.appendChild( |
| 5923 |
buildStatCard( |
| 5924 |
counts.commentsReceived.toLocaleString(), |
| 5925 |
__("Comments received", "desktop-mode"), |
| 5926 |
"" |
| 5927 |
) |
| 5928 |
); |
| 5929 |
cards.appendChild( |
| 5930 |
buildStatCard( |
| 5931 |
counts.commentsLeft.toLocaleString(), |
| 5932 |
__("Comments left", "desktop-mode"), |
| 5933 |
"" |
| 5934 |
) |
| 5935 |
); |
| 5936 |
wrap.appendChild(cards); |
| 5937 |
const spark = buildActivitySparkline(activity2); |
| 5938 |
if (spark) { |
| 5939 |
wrap.appendChild(spark); |
| 5940 |
} |
| 5941 |
const milestoneRow = buildMilestonesRow(profile, milestones); |
| 5942 |
if (milestoneRow) { |
| 5943 |
wrap.appendChild(milestoneRow); |
| 5944 |
} |
| 5945 |
if (recent.length > 0) { |
| 5946 |
const section = document.createElement("section"); |
| 5947 |
section.className = "desktop-mode-my-wordpress__user-section"; |
| 5948 |
const h = document.createElement("h3"); |
| 5949 |
h.textContent = __("Recent posts", "desktop-mode"); |
| 5950 |
section.appendChild(h); |
| 5951 |
const ul = document.createElement("ul"); |
| 5952 |
ul.className = "desktop-mode-my-wordpress__user-recent"; |
| 5953 |
for (const r of recent) { |
| 5954 |
const li = document.createElement("li"); |
| 5955 |
const a = document.createElement("a"); |
| 5956 |
a.href = r.link; |
| 5957 |
a.target = "_blank"; |
| 5958 |
a.rel = "noopener noreferrer"; |
| 5959 |
a.textContent = r.title || `#${r.id}`; |
| 5960 |
li.appendChild(a); |
| 5961 |
const meta = document.createElement("span"); |
| 5962 |
meta.className = "desktop-mode-my-wordpress__user-recent-meta"; |
| 5963 |
meta.textContent = `${formatDate(r.date)} · ${r.status}`; |
| 5964 |
li.appendChild(meta); |
| 5965 |
ul.appendChild(li); |
| 5966 |
} |
| 5967 |
section.appendChild(ul); |
| 5968 |
wrap.appendChild(section); |
| 5969 |
} |
| 5970 |
if (topTerms.length > 0) { |
| 5971 |
const section = document.createElement("section"); |
| 5972 |
section.className = "desktop-mode-my-wordpress__user-section"; |
| 5973 |
const h = document.createElement("h3"); |
| 5974 |
h.textContent = __("Top categories & tags", "desktop-mode"); |
| 5975 |
section.appendChild(h); |
| 5976 |
const chips = document.createElement("div"); |
| 5977 |
chips.className = "desktop-mode-my-wordpress__user-chips"; |
| 5978 |
for (const t of topTerms) { |
| 5979 |
const chip = document.createElement("span"); |
| 5980 |
chip.className = "desktop-mode-my-wordpress__user-chip " + (t.taxonomy === "post_tag" ? "desktop-mode-my-wordpress__user-chip--tag" : "desktop-mode-my-wordpress__user-chip--category"); |
| 5981 |
const name = document.createElement("span"); |
| 5982 |
name.textContent = t.name; |
| 5983 |
chip.appendChild(name); |
| 5984 |
const count = document.createElement("span"); |
| 5985 |
count.className = "desktop-mode-my-wordpress__user-chip-count"; |
| 5986 |
count.textContent = String(t.count); |
| 5987 |
chip.appendChild(count); |
| 5988 |
chips.appendChild(chip); |
| 5989 |
} |
| 5990 |
section.appendChild(chips); |
| 5991 |
wrap.appendChild(section); |
| 5992 |
} |
| 5993 |
return wrap; |
| 5994 |
} |
| 5995 |
function appendUserHeader(host, header) { |
| 5996 |
const wrap = document.createElement("header"); |
| 5997 |
wrap.className = "desktop-mode-my-wordpress__user-header"; |
| 5998 |
if (header.avatarUrl) { |
| 5999 |
const img = document.createElement("img"); |
| 6000 |
img.src = header.avatarUrl; |
| 6001 |
img.alt = ""; |
| 6002 |
img.className = "desktop-mode-my-wordpress__user-avatar"; |
| 6003 |
wrap.appendChild(img); |
| 6004 |
} |
| 6005 |
const right = document.createElement("div"); |
| 6006 |
right.className = "desktop-mode-my-wordpress__user-headline"; |
| 6007 |
const h = document.createElement("h2"); |
| 6008 |
h.className = "desktop-mode-my-wordpress__article-title"; |
| 6009 |
h.textContent = header.name; |
| 6010 |
right.appendChild(h); |
| 6011 |
if (header.roles.length > 0) { |
| 6012 |
const rolesRow = document.createElement("div"); |
| 6013 |
rolesRow.className = "desktop-mode-my-wordpress__user-roles"; |
| 6014 |
for (const r of header.roles) { |
| 6015 |
const badge = document.createElement("span"); |
| 6016 |
badge.className = "desktop-mode-my-wordpress__user-role"; |
| 6017 |
badge.textContent = r; |
| 6018 |
rolesRow.appendChild(badge); |
| 6019 |
} |
| 6020 |
right.appendChild(rolesRow); |
| 6021 |
} |
| 6022 |
const links = document.createElement("div"); |
| 6023 |
links.className = "desktop-mode-my-wordpress__user-links"; |
| 6024 |
if (header.link) { |
| 6025 |
const a = document.createElement("a"); |
| 6026 |
a.href = header.link; |
| 6027 |
a.target = "_blank"; |
| 6028 |
a.rel = "noopener noreferrer"; |
| 6029 |
a.textContent = __("Author archive", "desktop-mode"); |
| 6030 |
links.appendChild(a); |
| 6031 |
} |
| 6032 |
if (header.website) { |
| 6033 |
const a = document.createElement("a"); |
| 6034 |
a.href = header.website; |
| 6035 |
a.target = "_blank"; |
| 6036 |
a.rel = "noopener noreferrer"; |
| 6037 |
a.textContent = __("Website", "desktop-mode"); |
| 6038 |
links.appendChild(a); |
| 6039 |
} |
| 6040 |
if (links.childElementCount > 0) { |
| 6041 |
right.appendChild(links); |
| 6042 |
} |
| 6043 |
wrap.appendChild(right); |
| 6044 |
host.appendChild(wrap); |
| 6045 |
} |
| 6046 |
function buildStatCard(value, label, caption) { |
| 6047 |
const card = document.createElement("div"); |
| 6048 |
card.className = "desktop-mode-my-wordpress__user-stat"; |
| 6049 |
const v = document.createElement("span"); |
| 6050 |
v.className = "desktop-mode-my-wordpress__user-stat-value"; |
| 6051 |
v.textContent = value; |
| 6052 |
card.appendChild(v); |
| 6053 |
const l = document.createElement("span"); |
| 6054 |
l.className = "desktop-mode-my-wordpress__user-stat-label"; |
| 6055 |
l.textContent = label; |
| 6056 |
card.appendChild(l); |
| 6057 |
if (caption) { |
| 6058 |
const c = document.createElement("span"); |
| 6059 |
c.className = "desktop-mode-my-wordpress__user-stat-caption"; |
| 6060 |
c.textContent = caption; |
| 6061 |
card.appendChild(c); |
| 6062 |
} |
| 6063 |
return card; |
| 6064 |
} |
| 6065 |
function buildActivitySparkline(activity2) { |
| 6066 |
if (activity2.length === 0) { |
| 6067 |
return null; |
| 6068 |
} |
| 6069 |
const now = /* @__PURE__ */ new Date(); |
| 6070 |
const months = []; |
| 6071 |
for (let i = 11; i >= 0; i -= 1) { |
| 6072 |
const d = new Date(now.getFullYear(), now.getMonth() - i, 1); |
| 6073 |
const ym = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`; |
| 6074 |
const found = activity2.find((a) => a.ym === ym); |
| 6075 |
months.push({ |
| 6076 |
ym, |
| 6077 |
count: found?.count ?? 0, |
| 6078 |
label: d.toLocaleString(void 0, { month: "short" }) |
| 6079 |
}); |
| 6080 |
} |
| 6081 |
const max = Math.max(1, ...months.map((m) => m.count)); |
| 6082 |
const wrap = document.createElement("section"); |
| 6083 |
wrap.className = "desktop-mode-my-wordpress__user-section desktop-mode-my-wordpress__user-spark"; |
| 6084 |
const h = document.createElement("h3"); |
| 6085 |
h.textContent = __("Activity (last 12 months)", "desktop-mode"); |
| 6086 |
wrap.appendChild(h); |
| 6087 |
const chart = document.createElement("div"); |
| 6088 |
chart.className = "desktop-mode-my-wordpress__user-spark-chart"; |
| 6089 |
for (const m of months) { |
| 6090 |
const col = document.createElement("div"); |
| 6091 |
col.className = "desktop-mode-my-wordpress__user-spark-col"; |
| 6092 |
const bar = document.createElement("div"); |
| 6093 |
bar.className = "desktop-mode-my-wordpress__user-spark-bar"; |
| 6094 |
bar.style.height = `${Math.round(m.count / max * 100)}%`; |
| 6095 |
bar.title = sprintf( |
| 6096 |
// translators: 1: month label, 2: post count. |
| 6097 |
__("%1$s · %2$d posts", "desktop-mode"), |
| 6098 |
m.label, |
| 6099 |
m.count |
| 6100 |
); |
| 6101 |
if (m.count === 0) { |
| 6102 |
bar.classList.add("desktop-mode-my-wordpress__user-spark-bar--empty"); |
| 6103 |
} |
| 6104 |
col.appendChild(bar); |
| 6105 |
const lbl = document.createElement("span"); |
| 6106 |
lbl.className = "desktop-mode-my-wordpress__user-spark-label"; |
| 6107 |
lbl.textContent = m.label; |
| 6108 |
col.appendChild(lbl); |
| 6109 |
chart.appendChild(col); |
| 6110 |
} |
| 6111 |
wrap.appendChild(chart); |
| 6112 |
return wrap; |
| 6113 |
} |
| 6114 |
function buildMilestonesRow(profile, milestones) { |
| 6115 |
const items = []; |
| 6116 |
if (profile.registered) { |
| 6117 |
items.push({ |
| 6118 |
label: __("Member since", "desktop-mode"), |
| 6119 |
value: formatYearMonth(profile.registered) |
| 6120 |
}); |
| 6121 |
} |
| 6122 |
if (milestones.firstPublished) { |
| 6123 |
items.push({ |
| 6124 |
label: __("First published", "desktop-mode"), |
| 6125 |
value: formatYearMonth(milestones.firstPublished) |
| 6126 |
}); |
| 6127 |
} |
| 6128 |
if (milestones.lastPublished) { |
| 6129 |
items.push({ |
| 6130 |
label: __("Last published", "desktop-mode"), |
| 6131 |
value: formatYearMonth(milestones.lastPublished) |
| 6132 |
}); |
| 6133 |
} |
| 6134 |
if (items.length === 0) { |
| 6135 |
return null; |
| 6136 |
} |
| 6137 |
const dl = document.createElement("dl"); |
| 6138 |
dl.className = "desktop-mode-my-wordpress__user-milestones"; |
| 6139 |
for (const item of items) { |
| 6140 |
const dt = document.createElement("dt"); |
| 6141 |
dt.textContent = item.label; |
| 6142 |
dl.appendChild(dt); |
| 6143 |
const dd = document.createElement("dd"); |
| 6144 |
dd.textContent = item.value; |
| 6145 |
dl.appendChild(dd); |
| 6146 |
} |
| 6147 |
return dl; |
| 6148 |
} |
| 6149 |
function formatYearMonth(iso) { |
| 6150 |
if (!iso) { |
| 6151 |
return ""; |
| 6152 |
} |
| 6153 |
try { |
| 6154 |
return new Date(iso).toLocaleString(void 0, { |
| 6155 |
year: "numeric", |
| 6156 |
month: "long" |
| 6157 |
}); |
| 6158 |
} catch { |
| 6159 |
return iso; |
| 6160 |
} |
| 6161 |
} |
| 6162 |
function pickAvatar(avatars) { |
| 6163 |
if (!avatars) { |
| 6164 |
return null; |
| 6165 |
} |
| 6166 |
return avatars["96"] ?? avatars["48"] ?? avatars["24"] ?? Object.values(avatars)[0] ?? null; |
| 6167 |
} |
| 6168 |
function termToView(t) { |
| 6169 |
return { |
| 6170 |
id: `term:${t.id}`, |
| 6171 |
icon: t.taxonomy === "post_tag" ? "dashicons-tag" : "dashicons-category", |
| 6172 |
label: t.name, |
| 6173 |
date: (/* @__PURE__ */ new Date(0)).toISOString(), |
| 6174 |
preview: async () => renderTermDossier(t) |
| 6175 |
}; |
| 6176 |
} |
| 6177 |
async function renderTermDossier(t) { |
| 6178 |
let stats = null; |
| 6179 |
try { |
| 6180 |
stats = await fetchTermStats(t.taxonomy, t.id); |
| 6181 |
} catch { |
| 6182 |
stats = null; |
| 6183 |
} |
| 6184 |
const wrap = document.createElement("div"); |
| 6185 |
wrap.className = "desktop-mode-my-wordpress__article desktop-mode-my-wordpress__term"; |
| 6186 |
if (!stats) { |
| 6187 |
appendTermHeader(wrap, { |
| 6188 |
name: t.name, |
| 6189 |
taxonomyLabel: t.taxonomy, |
| 6190 |
isTag: t.taxonomy === "post_tag", |
| 6191 |
count: t.count ?? 0, |
| 6192 |
link: t.link ?? "", |
| 6193 |
parentName: "" |
| 6194 |
}); |
| 6195 |
if (t.description) { |
| 6196 |
const body = document.createElement("div"); |
| 6197 |
body.className = "desktop-mode-my-wordpress__user-bio"; |
| 6198 |
body.innerHTML = t.description; |
| 6199 |
wrap.appendChild(body); |
| 6200 |
} |
| 6201 |
return wrap; |
| 6202 |
} |
| 6203 |
const { profile, counts, recent, topAuthors, coTerms, milestones, activity: activity2 } = stats; |
| 6204 |
appendTermHeader(wrap, { |
| 6205 |
name: profile.name, |
| 6206 |
taxonomyLabel: profile.taxonomyLabel || profile.taxonomy, |
| 6207 |
isTag: profile.taxonomy === "post_tag", |
| 6208 |
count: profile.storedCount, |
| 6209 |
link: profile.link, |
| 6210 |
parentName: profile.parentName ?? "" |
| 6211 |
}); |
| 6212 |
if (profile.description) { |
| 6213 |
const bio = document.createElement("div"); |
| 6214 |
bio.className = "desktop-mode-my-wordpress__user-bio"; |
| 6215 |
bio.innerHTML = profile.description; |
| 6216 |
wrap.appendChild(bio); |
| 6217 |
} |
| 6218 |
const cards = document.createElement("div"); |
| 6219 |
cards.className = "desktop-mode-my-wordpress__user-stats"; |
| 6220 |
cards.appendChild( |
| 6221 |
buildStatCard( |
| 6222 |
counts.posts.total.toLocaleString(), |
| 6223 |
__("Posts", "desktop-mode"), |
| 6224 |
counts.posts.publish > 0 ? sprintf( |
| 6225 |
// translators: %d is a published-post count. |
| 6226 |
__("%d published", "desktop-mode"), |
| 6227 |
counts.posts.publish |
| 6228 |
) : "" |
| 6229 |
) |
| 6230 |
); |
| 6231 |
cards.appendChild( |
| 6232 |
buildStatCard( |
| 6233 |
counts.commentsReceived.toLocaleString(), |
| 6234 |
__("Comments", "desktop-mode"), |
| 6235 |
"" |
| 6236 |
) |
| 6237 |
); |
| 6238 |
cards.appendChild( |
| 6239 |
buildStatCard( |
| 6240 |
counts.distinctAuthors.toLocaleString(), |
| 6241 |
__("Authors", "desktop-mode"), |
| 6242 |
counts.distinctAuthors === 1 ? __("one contributor", "desktop-mode") : "" |
| 6243 |
) |
| 6244 |
); |
| 6245 |
wrap.appendChild(cards); |
| 6246 |
const spark = buildActivitySparkline(activity2); |
| 6247 |
if (spark) { |
| 6248 |
wrap.appendChild(spark); |
| 6249 |
} |
| 6250 |
const milestoneRow = buildTermMilestonesRow(milestones); |
| 6251 |
if (milestoneRow) { |
| 6252 |
wrap.appendChild(milestoneRow); |
| 6253 |
} |
| 6254 |
if (topAuthors.length > 0) { |
| 6255 |
const section = document.createElement("section"); |
| 6256 |
section.className = "desktop-mode-my-wordpress__user-section"; |
| 6257 |
const h = document.createElement("h3"); |
| 6258 |
h.textContent = __("Top contributors", "desktop-mode"); |
| 6259 |
section.appendChild(h); |
| 6260 |
const grid = document.createElement("div"); |
| 6261 |
grid.className = "desktop-mode-my-wordpress__term-authors"; |
| 6262 |
for (const a of topAuthors) { |
| 6263 |
const card = document.createElement("div"); |
| 6264 |
card.className = "desktop-mode-my-wordpress__term-author"; |
| 6265 |
if (a.userAvatarUrl) { |
| 6266 |
const img = document.createElement("img"); |
| 6267 |
img.src = a.userAvatarUrl; |
| 6268 |
img.alt = ""; |
| 6269 |
img.className = "desktop-mode-my-wordpress__term-author-avatar"; |
| 6270 |
card.appendChild(img); |
| 6271 |
} |
| 6272 |
const text = document.createElement("div"); |
| 6273 |
text.className = "desktop-mode-my-wordpress__term-author-text"; |
| 6274 |
const name = document.createElement("span"); |
| 6275 |
name.className = "desktop-mode-my-wordpress__term-author-name"; |
| 6276 |
name.textContent = a.userName; |
| 6277 |
text.appendChild(name); |
| 6278 |
const count = document.createElement("span"); |
| 6279 |
count.className = "desktop-mode-my-wordpress__term-author-count"; |
| 6280 |
count.textContent = sprintf( |
| 6281 |
// translators: %d is a post count. |
| 6282 |
_n("%d post", "%d posts", a.count), |
| 6283 |
a.count |
| 6284 |
); |
| 6285 |
text.appendChild(count); |
| 6286 |
card.appendChild(text); |
| 6287 |
grid.appendChild(card); |
| 6288 |
} |
| 6289 |
section.appendChild(grid); |
| 6290 |
wrap.appendChild(section); |
| 6291 |
} |
| 6292 |
if (recent.length > 0) { |
| 6293 |
const section = document.createElement("section"); |
| 6294 |
section.className = "desktop-mode-my-wordpress__user-section"; |
| 6295 |
const h = document.createElement("h3"); |
| 6296 |
h.textContent = __("Recent posts", "desktop-mode"); |
| 6297 |
section.appendChild(h); |
| 6298 |
const ul = document.createElement("ul"); |
| 6299 |
ul.className = "desktop-mode-my-wordpress__user-recent"; |
| 6300 |
for (const r of recent) { |
| 6301 |
const li = document.createElement("li"); |
| 6302 |
const a = document.createElement("a"); |
| 6303 |
a.href = r.link; |
| 6304 |
a.target = "_blank"; |
| 6305 |
a.rel = "noopener noreferrer"; |
| 6306 |
a.textContent = r.title || `#${r.id}`; |
| 6307 |
li.appendChild(a); |
| 6308 |
const meta = document.createElement("span"); |
| 6309 |
meta.className = "desktop-mode-my-wordpress__user-recent-meta"; |
| 6310 |
meta.textContent = `${formatDate(r.date)} · ${r.status}${r.author?.name ? " · " + r.author.name : ""}`; |
| 6311 |
li.appendChild(meta); |
| 6312 |
ul.appendChild(li); |
| 6313 |
} |
| 6314 |
section.appendChild(ul); |
| 6315 |
wrap.appendChild(section); |
| 6316 |
} |
| 6317 |
if (coTerms.length > 0) { |
| 6318 |
const section = document.createElement("section"); |
| 6319 |
section.className = "desktop-mode-my-wordpress__user-section"; |
| 6320 |
const h = document.createElement("h3"); |
| 6321 |
h.textContent = profile.taxonomy === "post_tag" ? __("Often paired tags", "desktop-mode") : __("Often paired categories", "desktop-mode"); |
| 6322 |
section.appendChild(h); |
| 6323 |
const chips = document.createElement("div"); |
| 6324 |
chips.className = "desktop-mode-my-wordpress__user-chips"; |
| 6325 |
for (const co of coTerms) { |
| 6326 |
const chip = document.createElement("span"); |
| 6327 |
chip.className = "desktop-mode-my-wordpress__user-chip " + (profile.taxonomy === "post_tag" ? "desktop-mode-my-wordpress__user-chip--tag" : "desktop-mode-my-wordpress__user-chip--category"); |
| 6328 |
const name = document.createElement("span"); |
| 6329 |
name.textContent = co.name; |
| 6330 |
chip.appendChild(name); |
| 6331 |
const count = document.createElement("span"); |
| 6332 |
count.className = "desktop-mode-my-wordpress__user-chip-count"; |
| 6333 |
count.textContent = String(co.count); |
| 6334 |
chip.appendChild(count); |
| 6335 |
chips.appendChild(chip); |
| 6336 |
} |
| 6337 |
section.appendChild(chips); |
| 6338 |
wrap.appendChild(section); |
| 6339 |
} |
| 6340 |
return wrap; |
| 6341 |
} |
| 6342 |
function appendTermHeader(host, header) { |
| 6343 |
const wrap = document.createElement("header"); |
| 6344 |
wrap.className = "desktop-mode-my-wordpress__term-header"; |
| 6345 |
const iconHost = document.createElement("span"); |
| 6346 |
iconHost.className = "desktop-mode-my-wordpress__term-icon " + (header.isTag ? "desktop-mode-my-wordpress__term-icon--tag" : "desktop-mode-my-wordpress__term-icon--category"); |
| 6347 |
const iconGlyph = document.createElement("span"); |
| 6348 |
iconGlyph.style.cssText = "font-family:dashicons;font-size:32px;line-height:1;display:inline-block;"; |
| 6349 |
iconGlyph.textContent = header.isTag ? "" : ""; |
| 6350 |
iconHost.appendChild(iconGlyph); |
| 6351 |
wrap.appendChild(iconHost); |
| 6352 |
const right = document.createElement("div"); |
| 6353 |
right.className = "desktop-mode-my-wordpress__user-headline"; |
| 6354 |
const h = document.createElement("h2"); |
| 6355 |
h.className = "desktop-mode-my-wordpress__article-title"; |
| 6356 |
h.textContent = header.name; |
| 6357 |
right.appendChild(h); |
| 6358 |
const meta = document.createElement("div"); |
| 6359 |
meta.className = "desktop-mode-my-wordpress__user-roles"; |
| 6360 |
const taxBadge = document.createElement("span"); |
| 6361 |
taxBadge.className = "desktop-mode-my-wordpress__user-role " + (header.isTag ? "desktop-mode-my-wordpress__user-role--tag" : "desktop-mode-my-wordpress__user-role--category"); |
| 6362 |
taxBadge.textContent = header.taxonomyLabel; |
| 6363 |
meta.appendChild(taxBadge); |
| 6364 |
if (header.parentName) { |
| 6365 |
const parent = document.createElement("span"); |
| 6366 |
parent.className = "desktop-mode-my-wordpress__user-role"; |
| 6367 |
parent.textContent = sprintf( |
| 6368 |
// translators: %s is the name of the parent category. |
| 6369 |
__("in %s", "desktop-mode"), |
| 6370 |
header.parentName |
| 6371 |
); |
| 6372 |
meta.appendChild(parent); |
| 6373 |
} |
| 6374 |
right.appendChild(meta); |
| 6375 |
if (header.link) { |
| 6376 |
const links = document.createElement("div"); |
| 6377 |
links.className = "desktop-mode-my-wordpress__user-links"; |
| 6378 |
const a = document.createElement("a"); |
| 6379 |
a.href = header.link; |
| 6380 |
a.target = "_blank"; |
| 6381 |
a.rel = "noopener noreferrer"; |
| 6382 |
a.textContent = __("View archive", "desktop-mode"); |
| 6383 |
links.appendChild(a); |
| 6384 |
right.appendChild(links); |
| 6385 |
} |
| 6386 |
wrap.appendChild(right); |
| 6387 |
host.appendChild(wrap); |
| 6388 |
} |
| 6389 |
function buildTermMilestonesRow(milestones) { |
| 6390 |
const items = []; |
| 6391 |
if (milestones.firstPosted) { |
| 6392 |
items.push({ |
| 6393 |
label: __("First post", "desktop-mode"), |
| 6394 |
value: formatYearMonth(milestones.firstPosted) |
| 6395 |
}); |
| 6396 |
} |
| 6397 |
if (milestones.lastPosted) { |
| 6398 |
items.push({ |
| 6399 |
label: __("Last post", "desktop-mode"), |
| 6400 |
value: formatYearMonth(milestones.lastPosted) |
| 6401 |
}); |
| 6402 |
} |
| 6403 |
if (items.length === 0) { |
| 6404 |
return null; |
| 6405 |
} |
| 6406 |
const dl = document.createElement("dl"); |
| 6407 |
dl.className = "desktop-mode-my-wordpress__user-milestones"; |
| 6408 |
for (const item of items) { |
| 6409 |
const dt = document.createElement("dt"); |
| 6410 |
dt.textContent = item.label; |
| 6411 |
dl.appendChild(dt); |
| 6412 |
const dd = document.createElement("dd"); |
| 6413 |
dd.textContent = item.value; |
| 6414 |
dl.appendChild(dd); |
| 6415 |
} |
| 6416 |
return dl; |
| 6417 |
} |
| 6418 |
function mediaToView(m) { |
| 6419 |
const isImage = m.mime_type.startsWith("image/"); |
| 6420 |
return { |
| 6421 |
id: `media:${m.id}`, |
| 6422 |
icon: isImage ? "dashicons-format-image" : "dashicons-media-default", |
| 6423 |
label: stripTags(m.title.rendered) || `#${m.id}`, |
| 6424 |
date: m.date, |
| 6425 |
preview: () => { |
| 6426 |
const wrap = document.createElement("div"); |
| 6427 |
wrap.className = "desktop-mode-my-wordpress__article"; |
| 6428 |
const h = document.createElement("h2"); |
| 6429 |
h.className = "desktop-mode-my-wordpress__article-title"; |
| 6430 |
h.textContent = stripTags(m.title.rendered) || `#${m.id}`; |
| 6431 |
wrap.appendChild(h); |
| 6432 |
const meta = document.createElement("p"); |
| 6433 |
meta.className = "desktop-mode-my-wordpress__article-meta"; |
| 6434 |
meta.textContent = `${m.mime_type} · ${formatDate(m.date)}`; |
| 6435 |
wrap.appendChild(meta); |
| 6436 |
if (isImage) { |
| 6437 |
const img = document.createElement("img"); |
| 6438 |
img.className = "desktop-mode-my-wordpress__article-hero"; |
| 6439 |
const sizes = m.media_details?.sizes; |
| 6440 |
img.src = sizes?.large?.source_url ?? sizes?.medium?.source_url ?? m.source_url; |
| 6441 |
img.alt = m.alt_text ?? ""; |
| 6442 |
wrap.appendChild(img); |
| 6443 |
} else { |
| 6444 |
const link = document.createElement("p"); |
| 6445 |
const a = document.createElement("a"); |
| 6446 |
a.href = m.source_url; |
| 6447 |
a.textContent = m.source_url; |
| 6448 |
a.target = "_blank"; |
| 6449 |
a.rel = "noopener noreferrer"; |
| 6450 |
link.appendChild(a); |
| 6451 |
wrap.appendChild(link); |
| 6452 |
} |
| 6453 |
return wrap; |
| 6454 |
} |
| 6455 |
}; |
| 6456 |
} |
| 6457 |
function revisionToView(r, entity, postId) { |
| 6458 |
const label = stripTags(r.title?.rendered ?? "") || formatDate(r.date); |
| 6459 |
return { |
| 6460 |
id: `revision:${r.id}`, |
| 6461 |
icon: "dashicons-backup", |
| 6462 |
label, |
| 6463 |
date: r.modified || r.date, |
| 6464 |
preview: async () => { |
| 6465 |
let detail = null; |
| 6466 |
try { |
| 6467 |
detail = await fetchRevision(entity, postId, r.id); |
| 6468 |
} catch { |
| 6469 |
detail = null; |
| 6470 |
} |
| 6471 |
const wrap = document.createElement("article"); |
| 6472 |
wrap.className = "desktop-mode-my-wordpress__article"; |
| 6473 |
const h = document.createElement("h2"); |
| 6474 |
h.className = "desktop-mode-my-wordpress__article-title"; |
| 6475 |
h.textContent = stripTags(detail?.title?.rendered ?? r.title?.rendered ?? "") || label; |
| 6476 |
wrap.appendChild(h); |
| 6477 |
const meta = document.createElement("p"); |
| 6478 |
meta.className = "desktop-mode-my-wordpress__article-meta"; |
| 6479 |
meta.textContent = sprintf( |
| 6480 |
// translators: %s is a formatted date. |
| 6481 |
__("Saved %s", "desktop-mode"), |
| 6482 |
formatDate(detail?.modified || detail?.date || r.modified || r.date) |
| 6483 |
); |
| 6484 |
wrap.appendChild(meta); |
| 6485 |
const html2 = detail?.content?.rendered ?? ""; |
| 6486 |
if (html2) { |
| 6487 |
const content = document.createElement("div"); |
| 6488 |
content.className = "desktop-mode-my-wordpress__article-content"; |
| 6489 |
content.innerHTML = html2; |
| 6490 |
wrap.appendChild(content); |
| 6491 |
} else { |
| 6492 |
const empty = document.createElement("p"); |
| 6493 |
empty.className = "desktop-mode-my-wordpress__article-meta"; |
| 6494 |
empty.textContent = detail ? __("This revision has no rendered content.", "desktop-mode") : __( |
| 6495 |
"Couldn’t load the revision content. You may not have permission to view it.", |
| 6496 |
"desktop-mode" |
| 6497 |
); |
| 6498 |
wrap.appendChild(empty); |
| 6499 |
} |
| 6500 |
return wrap; |
| 6501 |
} |
| 6502 |
}; |
| 6503 |
} |
| 6504 |
function formatDate(iso) { |
| 6505 |
if (!iso) { |
| 6506 |
return ""; |
| 6507 |
} |
| 6508 |
try { |
| 6509 |
return new Date(iso).toLocaleString(); |
| 6510 |
} catch { |
| 6511 |
return iso; |
| 6512 |
} |
| 6513 |
} |
| 6514 |
function openEditor(entity, id, title) { |
| 6515 |
const url = buildEditUrl(id); |
| 6516 |
openIframeWindow({ |
| 6517 |
id: `${entity.id}-edit-${id}`, |
| 6518 |
url, |
| 6519 |
title, |
| 6520 |
icon: entity.icon |
| 6521 |
}); |
| 6522 |
} |
| 6523 |
function openTileMenu(state, ctx, entity, item, title, pos) { |
| 6524 |
closeAnyTileMenu(); |
| 6525 |
const menu = document.createElement("wpd-context-menu"); |
| 6526 |
menu.setAttribute("open", ""); |
| 6527 |
menu.classList.add("desktop-mode-my-wordpress__menu"); |
| 6528 |
menu.style.left = `${pos.x}px`; |
| 6529 |
menu.style.top = `${pos.y}px`; |
| 6530 |
const addOption = (id, label, icon, danger = false) => { |
| 6531 |
const opt = document.createElement("wpd-context-menu-option"); |
| 6532 |
opt.dataset.menuItemId = id; |
| 6533 |
opt.setAttribute("value", id); |
| 6534 |
opt.setAttribute("icon", sanitizeClass(icon)); |
| 6535 |
if (danger) { |
| 6536 |
opt.setAttribute("danger", ""); |
| 6537 |
} |
| 6538 |
opt.textContent = label; |
| 6539 |
menu.appendChild(opt); |
| 6540 |
}; |
| 6541 |
const baseOptions = [ |
| 6542 |
{ |
| 6543 |
id: "open", |
| 6544 |
label: __("Open in editor", "desktop-mode"), |
| 6545 |
icon: "dashicons-edit" |
| 6546 |
}, |
| 6547 |
{ |
| 6548 |
id: "navigate-into", |
| 6549 |
label: __("Navigate into", "desktop-mode"), |
| 6550 |
icon: "dashicons-category" |
| 6551 |
}, |
| 6552 |
{ |
| 6553 |
id: "trash", |
| 6554 |
label: __("Move to Trash", "desktop-mode"), |
| 6555 |
icon: "dashicons-trash", |
| 6556 |
danger: true |
| 6557 |
} |
| 6558 |
]; |
| 6559 |
const ctxFilter = { |
| 6560 |
entityId: entity.id, |
| 6561 |
kind: entity.kind ?? "post", |
| 6562 |
item |
| 6563 |
}; |
| 6564 |
const options = applyFilters( |
| 6565 |
"desktop-mode.my-wordpress.tile-context-menu", |
| 6566 |
baseOptions, |
| 6567 |
ctxFilter |
| 6568 |
); |
| 6569 |
const finalOptions = Array.isArray(options) ? options : baseOptions; |
| 6570 |
for (const o of finalOptions) { |
| 6571 |
addOption(o.id, o.label, o.icon, o.danger); |
| 6572 |
} |
| 6573 |
menu.addEventListener("wpd-context-menu-pick", (e) => { |
| 6574 |
const detail = e.detail; |
| 6575 |
closeAnyTileMenu(); |
| 6576 |
if (detail.id === "open") { |
| 6577 |
openEditor(entity, item.id, title); |
| 6578 |
return; |
| 6579 |
} |
| 6580 |
if (detail.id === "navigate-into") { |
| 6581 |
navigate(state, { |
| 6582 |
kind: "detail", |
| 6583 |
entityId: entity.id, |
| 6584 |
postId: item.id, |
| 6585 |
postTitle: title |
| 6586 |
}); |
| 6587 |
return; |
| 6588 |
} |
| 6589 |
if (detail.id === "trash") { |
| 6590 |
void confirmTrash(state, ctx, entity, item.id, title); |
| 6591 |
return; |
| 6592 |
} |
| 6593 |
const match = finalOptions.find((o) => o.id === detail.id); |
| 6594 |
if (match && typeof match.onSelect === "function") { |
| 6595 |
try { |
| 6596 |
match.onSelect(); |
| 6597 |
} catch (err) { |
| 6598 |
console.error( |
| 6599 |
`[my-wordpress] tile-context-menu '${detail.id}' onSelect threw:`, |
| 6600 |
err |
| 6601 |
); |
| 6602 |
} |
| 6603 |
} |
| 6604 |
}); |
| 6605 |
document.body.appendChild(menu); |
| 6606 |
const rect = menu.getBoundingClientRect(); |
| 6607 |
if (rect.right > window.innerWidth) { |
| 6608 |
menu.style.left = `${Math.max( |
| 6609 |
0, |
| 6610 |
window.innerWidth - rect.width - 8 |
| 6611 |
)}px`; |
| 6612 |
} |
| 6613 |
if (rect.bottom > window.innerHeight) { |
| 6614 |
menu.style.top = `${Math.max( |
| 6615 |
0, |
| 6616 |
window.innerHeight - rect.height - 8 |
| 6617 |
)}px`; |
| 6618 |
} |
| 6619 |
queueMicrotask(() => { |
| 6620 |
const onDocPointerDown = (ev) => { |
| 6621 |
const target = ev.target; |
| 6622 |
if (target instanceof Node && menu.contains(target)) { |
| 6623 |
return; |
| 6624 |
} |
| 6625 |
closeAnyTileMenu(); |
| 6626 |
}; |
| 6627 |
const onDocKey = (ev) => { |
| 6628 |
if (ev.key === "Escape") { |
| 6629 |
closeAnyTileMenu(); |
| 6630 |
} |
| 6631 |
}; |
| 6632 |
document.addEventListener("pointerdown", onDocPointerDown, true); |
| 6633 |
document.addEventListener("keydown", onDocKey); |
| 6634 |
menu.addEventListener("tile-menu-closed", () => { |
| 6635 |
document.removeEventListener( |
| 6636 |
"pointerdown", |
| 6637 |
onDocPointerDown, |
| 6638 |
true |
| 6639 |
); |
| 6640 |
document.removeEventListener("keydown", onDocKey); |
| 6641 |
}); |
| 6642 |
}); |
| 6643 |
} |
| 6644 |
function closeAnyTileMenu() { |
| 6645 |
document.querySelectorAll("wpd-context-menu.desktop-mode-my-wordpress__menu").forEach((n) => { |
| 6646 |
n.dispatchEvent(new CustomEvent("tile-menu-closed")); |
| 6647 |
n.remove(); |
| 6648 |
}); |
| 6649 |
} |
| 6650 |
async function confirmTrash(state, ctx, entity, id, title) { |
| 6651 |
const ok = await wpdConfirmGlobal({ |
| 6652 |
title: __("Move to Trash", "desktop-mode"), |
| 6653 |
message: sprintf( |
| 6654 |
// translators: %s is the entry title. |
| 6655 |
__('Move "%s" to Trash?', "desktop-mode"), |
| 6656 |
title |
| 6657 |
), |
| 6658 |
confirmLabel: __("Move to Trash", "desktop-mode"), |
| 6659 |
cancelLabel: __("Cancel", "desktop-mode"), |
| 6660 |
danger: true |
| 6661 |
}); |
| 6662 |
if (!ok) { |
| 6663 |
return; |
| 6664 |
} |
| 6665 |
try { |
| 6666 |
await trashEntity(entity, id); |
| 6667 |
} catch (err) { |
| 6668 |
const msg = err instanceof Error ? err.message : __("Unknown error.", "desktop-mode"); |
| 6669 |
showToast(msg); |
| 6670 |
return; |
| 6671 |
} |
| 6672 |
const tile = ctx.tiles.querySelector( |
| 6673 |
`[data-entry-id="${id}"]` |
| 6674 |
); |
| 6675 |
tile?.remove(); |
| 6676 |
if (ctx.selectedId === id) { |
| 6677 |
ctx.selectedId = null; |
| 6678 |
ctx.selectedTile = null; |
| 6679 |
ctx.preview.replaceChildren(); |
| 6680 |
const empty = document.createElement("div"); |
| 6681 |
empty.className = "desktop-mode-my-wordpress__preview-empty"; |
| 6682 |
empty.textContent = __( |
| 6683 |
"Select an entry to preview it here.", |
| 6684 |
"desktop-mode" |
| 6685 |
); |
| 6686 |
ctx.preview.appendChild(empty); |
| 6687 |
} |
| 6688 |
} |
| 6689 |
function showToast(message) { |
| 6690 |
const toast = window.wp?.desktop?.toast; |
| 6691 |
if (typeof toast === "function") { |
| 6692 |
toast({ message }); |
| 6693 |
return; |
| 6694 |
} |
| 6695 |
console.info("[my-wordpress]", message); |
| 6696 |
} |
| 6697 |
function renderUserEntityList(state, entity) { |
| 6698 |
const cfg = getConfig(); |
| 6699 |
const initialQuery = lastQueryByEntity.get(entity.id) ?? ""; |
| 6700 |
const toolbar = renderListToolbar({ |
| 6701 |
placeholder: __("Search users…", "desktop-mode"), |
| 6702 |
ariaLabel: __("Search users", "desktop-mode"), |
| 6703 |
initialValue: initialQuery, |
| 6704 |
onSearchChange: (q) => { |
| 6705 |
lastQueryByEntity.set(entity.id, q); |
| 6706 |
void resetForSearch(q); |
| 6707 |
} |
| 6708 |
}); |
| 6709 |
state.body.appendChild(toolbar.host); |
| 6710 |
state.teardown.push(() => toolbar.destroy()); |
| 6711 |
const split = document.createElement("div"); |
| 6712 |
split.className = "desktop-mode-my-wordpress__split"; |
| 6713 |
const left = document.createElement("div"); |
| 6714 |
left.className = "desktop-mode-my-wordpress__list"; |
| 6715 |
const tiles = document.createElement("div"); |
| 6716 |
tiles.className = "desktop-mode-my-wordpress__tiles desktop-mode-my-wordpress__canvas desktop-mode-my-wordpress__canvas--users"; |
| 6717 |
tiles.setAttribute("role", "list"); |
| 6718 |
left.appendChild(tiles); |
| 6719 |
const sentinel = document.createElement("div"); |
| 6720 |
sentinel.className = "desktop-mode-my-wordpress__sentinel"; |
| 6721 |
sentinel.setAttribute("aria-hidden", "true"); |
| 6722 |
left.appendChild(sentinel); |
| 6723 |
const right = document.createElement("div"); |
| 6724 |
right.className = "desktop-mode-my-wordpress__preview"; |
| 6725 |
const previewEmpty = document.createElement("div"); |
| 6726 |
previewEmpty.className = "desktop-mode-my-wordpress__preview-empty"; |
| 6727 |
previewEmpty.textContent = __( |
| 6728 |
"Select a user to see their profile here.", |
| 6729 |
"desktop-mode" |
| 6730 |
); |
| 6731 |
right.appendChild(previewEmpty); |
| 6732 |
split.appendChild(left); |
| 6733 |
split.appendChild(right); |
| 6734 |
state.body.appendChild(split); |
| 6735 |
const tileLayout = createTileLayout(tiles, `entity:${entity.id}`); |
| 6736 |
const menu = attachIconCanvasMenu(tiles, { |
| 6737 |
scope: `my-wordpress:${entity.id}`, |
| 6738 |
onSort: (mode) => tileLayout.sort(mode) |
| 6739 |
}); |
| 6740 |
state.teardown.push(() => menu.dispose()); |
| 6741 |
const ctx = { |
| 6742 |
page: 0, |
| 6743 |
totalPages: 1, |
| 6744 |
total: 0, |
| 6745 |
loaded: 0, |
| 6746 |
loading: false, |
| 6747 |
done: false, |
| 6748 |
tiles, |
| 6749 |
sentinel, |
| 6750 |
preview: right, |
| 6751 |
selectedId: null, |
| 6752 |
selectedTile: null, |
| 6753 |
observer: null, |
| 6754 |
layout: tileLayout, |
| 6755 |
query: initialQuery, |
| 6756 |
abort: null |
| 6757 |
}; |
| 6758 |
state.teardown.push(() => tileLayout.dispose()); |
| 6759 |
state.teardown.push(() => ctx.abort?.abort()); |
| 6760 |
const repaintListStatus = () => { |
| 6761 |
let itemLabel; |
| 6762 |
if (ctx.total === 0 && ctx.loaded === 0) { |
| 6763 |
itemLabel = pluralLabel(0, "user", "users"); |
| 6764 |
} else if (ctx.total > ctx.loaded && ctx.loaded > 0) { |
| 6765 |
itemLabel = sprintf( |
| 6766 |
// translators: 1: visible user count, 2: total user count. |
| 6767 |
__("%1$d of %2$d users", "desktop-mode"), |
| 6768 |
ctx.loaded, |
| 6769 |
ctx.total |
| 6770 |
); |
| 6771 |
} else { |
| 6772 |
itemLabel = pluralLabel( |
| 6773 |
Math.max(ctx.total, ctx.loaded), |
| 6774 |
"user", |
| 6775 |
"users" |
| 6776 |
); |
| 6777 |
} |
| 6778 |
const segments = [ |
| 6779 |
{ id: "count", label: itemLabel, align: "start", sort: 10 } |
| 6780 |
]; |
| 6781 |
if (ctx.totalPages > 1) { |
| 6782 |
segments.push({ |
| 6783 |
id: "page", |
| 6784 |
label: sprintf( |
| 6785 |
// translators: 1: current page, 2: total pages. |
| 6786 |
__("Page %1$d of %2$d", "desktop-mode"), |
| 6787 |
Math.max(ctx.page, 1), |
| 6788 |
ctx.totalPages |
| 6789 |
), |
| 6790 |
align: "end", |
| 6791 |
sort: 10 |
| 6792 |
}); |
| 6793 |
} |
| 6794 |
paintStatus(state, segments, { |
| 6795 |
view: "list", |
| 6796 |
entityId: entity.id |
| 6797 |
}); |
| 6798 |
}; |
| 6799 |
repaintListStatus(); |
| 6800 |
const sentinelIsVisible = () => { |
| 6801 |
const sr = sentinel.getBoundingClientRect(); |
| 6802 |
const rr = left.getBoundingClientRect(); |
| 6803 |
const slack = 200; |
| 6804 |
return sr.top < rr.bottom + slack && sr.bottom > rr.top - slack; |
| 6805 |
}; |
| 6806 |
const loadMore = async () => { |
| 6807 |
if (ctx.loading || ctx.done) { |
| 6808 |
return; |
| 6809 |
} |
| 6810 |
ctx.loading = true; |
| 6811 |
const nextPage = ctx.page + 1; |
| 6812 |
const isFirst = nextPage === 1; |
| 6813 |
const queryAtFetchTime = ctx.query; |
| 6814 |
showLoadingSkeleton(tiles, ctx.layout, isFirst); |
| 6815 |
const controller = new AbortController(); |
| 6816 |
ctx.abort = controller; |
| 6817 |
try { |
| 6818 |
const result = await fetchUserList(entity, { |
| 6819 |
page: nextPage, |
| 6820 |
perPage: cfg.perPage, |
| 6821 |
search: queryAtFetchTime || void 0, |
| 6822 |
signal: controller.signal |
| 6823 |
}); |
| 6824 |
if (ctx.query !== queryAtFetchTime) { |
| 6825 |
return; |
| 6826 |
} |
| 6827 |
ctx.page = nextPage; |
| 6828 |
ctx.totalPages = result.totalPages; |
| 6829 |
ctx.total = result.total; |
| 6830 |
hideLoadingSkeleton(tiles); |
| 6831 |
if (result.items.length === 0 && isFirst) { |
| 6832 |
renderListEmptyMessage( |
| 6833 |
tiles, |
| 6834 |
queryAtFetchTime ? sprintf( |
| 6835 |
// translators: %s is the user-entered search query. |
| 6836 |
__('No users match "%s".', "desktop-mode"), |
| 6837 |
queryAtFetchTime |
| 6838 |
) : __("No users to show.", "desktop-mode") |
| 6839 |
); |
| 6840 |
ctx.done = true; |
| 6841 |
repaintListStatus(); |
| 6842 |
return; |
| 6843 |
} |
| 6844 |
for (const item of result.items) { |
| 6845 |
tiles.appendChild( |
| 6846 |
buildUserTile(state, ctx, entity, item) |
| 6847 |
); |
| 6848 |
ctx.loaded += 1; |
| 6849 |
} |
| 6850 |
if (ctx.page >= ctx.totalPages) { |
| 6851 |
ctx.done = true; |
| 6852 |
} |
| 6853 |
repaintListStatus(); |
| 6854 |
} catch (err) { |
| 6855 |
if (isAbortError(err)) { |
| 6856 |
return; |
| 6857 |
} |
| 6858 |
hideLoadingSkeleton(tiles); |
| 6859 |
const msg = err instanceof Error ? err.message : __("Unknown error.", "desktop-mode"); |
| 6860 |
renderListError(tiles, msg); |
| 6861 |
ctx.done = true; |
| 6862 |
} finally { |
| 6863 |
ctx.loading = false; |
| 6864 |
if (ctx.abort === controller) { |
| 6865 |
ctx.abort = null; |
| 6866 |
} |
| 6867 |
} |
| 6868 |
if (!ctx.done) { |
| 6869 |
requestAnimationFrame(() => { |
| 6870 |
if (sentinelIsVisible()) { |
| 6871 |
void loadMore(); |
| 6872 |
} |
| 6873 |
}); |
| 6874 |
} |
| 6875 |
}; |
| 6876 |
const resetForSearch = async (q) => { |
| 6877 |
ctx.abort?.abort(); |
| 6878 |
ctx.abort = null; |
| 6879 |
ctx.query = q; |
| 6880 |
tiles.classList.add( |
| 6881 |
"desktop-mode-my-wordpress__tiles--searching" |
| 6882 |
); |
| 6883 |
hideLoadingSkeleton(tiles); |
| 6884 |
const controller = new AbortController(); |
| 6885 |
ctx.abort = controller; |
| 6886 |
ctx.loading = true; |
| 6887 |
try { |
| 6888 |
const result = await fetchUserList(entity, { |
| 6889 |
page: 1, |
| 6890 |
perPage: cfg.perPage, |
| 6891 |
search: q || void 0, |
| 6892 |
signal: controller.signal |
| 6893 |
}); |
| 6894 |
if (ctx.query !== q) { |
| 6895 |
return; |
| 6896 |
} |
| 6897 |
tiles.replaceChildren(); |
| 6898 |
ctx.layout.clear(); |
| 6899 |
tiles.classList.remove( |
| 6900 |
"desktop-mode-my-wordpress__tiles--searching" |
| 6901 |
); |
| 6902 |
ctx.page = 1; |
| 6903 |
ctx.totalPages = result.totalPages; |
| 6904 |
ctx.total = result.total; |
| 6905 |
ctx.loaded = 0; |
| 6906 |
ctx.done = ctx.page >= ctx.totalPages; |
| 6907 |
ctx.selectedId = null; |
| 6908 |
ctx.selectedTile = null; |
| 6909 |
ctx.preview.replaceChildren(); |
| 6910 |
const emptyPreview = document.createElement("div"); |
| 6911 |
emptyPreview.className = "desktop-mode-my-wordpress__preview-empty"; |
| 6912 |
emptyPreview.textContent = __( |
| 6913 |
"Select a user to see their profile here.", |
| 6914 |
"desktop-mode" |
| 6915 |
); |
| 6916 |
ctx.preview.appendChild(emptyPreview); |
| 6917 |
if (result.items.length === 0) { |
| 6918 |
renderListEmptyMessage( |
| 6919 |
tiles, |
| 6920 |
q ? sprintf( |
| 6921 |
// translators: %s is the user-entered search query. |
| 6922 |
__('No users match "%s".', "desktop-mode"), |
| 6923 |
q |
| 6924 |
) : __("No users to show.", "desktop-mode") |
| 6925 |
); |
| 6926 |
ctx.done = true; |
| 6927 |
} else { |
| 6928 |
for (const item of result.items) { |
| 6929 |
tiles.appendChild( |
| 6930 |
buildUserTile(state, ctx, entity, item) |
| 6931 |
); |
| 6932 |
ctx.loaded += 1; |
| 6933 |
} |
| 6934 |
} |
| 6935 |
repaintListStatus(); |
| 6936 |
} catch (err) { |
| 6937 |
if (isAbortError(err)) { |
| 6938 |
return; |
| 6939 |
} |
| 6940 |
tiles.classList.remove( |
| 6941 |
"desktop-mode-my-wordpress__tiles--searching" |
| 6942 |
); |
| 6943 |
tiles.replaceChildren(); |
| 6944 |
ctx.layout.clear(); |
| 6945 |
const msg = err instanceof Error ? err.message : __("Unknown error.", "desktop-mode"); |
| 6946 |
renderListError(tiles, msg); |
| 6947 |
ctx.done = true; |
| 6948 |
} finally { |
| 6949 |
ctx.loading = false; |
| 6950 |
if (ctx.abort === controller) { |
| 6951 |
ctx.abort = null; |
| 6952 |
} |
| 6953 |
} |
| 6954 |
if (!ctx.done) { |
| 6955 |
requestAnimationFrame(() => { |
| 6956 |
if (sentinelIsVisible()) { |
| 6957 |
void loadMore(); |
| 6958 |
} |
| 6959 |
}); |
| 6960 |
} |
| 6961 |
}; |
| 6962 |
if (typeof IntersectionObserver !== "undefined") { |
| 6963 |
ctx.observer = new IntersectionObserver( |
| 6964 |
(entries) => { |
| 6965 |
for (const e of entries) { |
| 6966 |
if (e.isIntersecting) { |
| 6967 |
void loadMore(); |
| 6968 |
} |
| 6969 |
} |
| 6970 |
}, |
| 6971 |
{ root: left, rootMargin: "200px 0px" } |
| 6972 |
); |
| 6973 |
ctx.observer.observe(sentinel); |
| 6974 |
state.teardown.push(() => ctx.observer?.disconnect()); |
| 6975 |
} |
| 6976 |
void loadMore(); |
| 6977 |
} |
| 6978 |
function buildUserTile(state, ctx, entity, item) { |
| 6979 |
const displayName = item.name || item.slug || `#${item.id}`; |
| 6980 |
const avatarUrl = pickAvatar(item.avatar_urls) ?? ""; |
| 6981 |
const tile = buildTileFromSpec({ |
| 6982 |
type: "user", |
| 6983 |
ref: String(item.id), |
| 6984 |
label: displayName, |
| 6985 |
thumbnail: avatarUrl || void 0, |
| 6986 |
// No avatar: fall back to a generic users dashicon so the |
| 6987 |
// tile still has a visual. The initials block below |
| 6988 |
// replaces that icon as a richer fallback. |
| 6989 |
icon: avatarUrl ? void 0 : "dashicons-admin-users", |
| 6990 |
role: "entry", |
| 6991 |
dataset: { userId: item.id, role: "user" }, |
| 6992 |
extraClasses: [ |
| 6993 |
"desktop-mode-my-wordpress__tile", |
| 6994 |
"desktop-mode-my-wordpress__tile--user" |
| 6995 |
] |
| 6996 |
}); |
| 6997 |
if (!avatarUrl) { |
| 6998 |
const iconHost = tile.querySelector( |
| 6999 |
".desktop-mode-file-tile__visual" |
| 7000 |
); |
| 7001 |
if (iconHost) { |
| 7002 |
iconHost.replaceChildren(); |
| 7003 |
const initials = document.createElement("span"); |
| 7004 |
initials.className = "desktop-mode-my-wordpress__user-tile-initials"; |
| 7005 |
initials.textContent = initialsOf(displayName); |
| 7006 |
iconHost.appendChild(initials); |
| 7007 |
} |
| 7008 |
} |
| 7009 |
const summary = item.desktop_mode_summary; |
| 7010 |
const postCount = summary?.postCount ?? 0; |
| 7011 |
const roleLabel = (summary?.roleLabels ?? [])[0] ?? ""; |
| 7012 |
if (roleLabel || postCount > 0) { |
| 7013 |
const sub = document.createElement("span"); |
| 7014 |
sub.className = "desktop-mode-my-wordpress__user-tile-sub"; |
| 7015 |
const parts = []; |
| 7016 |
if (roleLabel) { |
| 7017 |
parts.push(roleLabel); |
| 7018 |
} |
| 7019 |
if (postCount > 0) { |
| 7020 |
parts.push( |
| 7021 |
sprintf( |
| 7022 |
// translators: %d is a count of posts authored. |
| 7023 |
_n("%d post", "%d posts", postCount), |
| 7024 |
postCount |
| 7025 |
) |
| 7026 |
); |
| 7027 |
} |
| 7028 |
sub.textContent = parts.join(" · "); |
| 7029 |
tile.appendChild(sub); |
| 7030 |
} |
| 7031 |
const tooltip = buildUserTooltip(displayName, item); |
| 7032 |
let tooltipNode = null; |
| 7033 |
const showTooltip = (ev) => { |
| 7034 |
if (!tooltipNode) { |
| 7035 |
tooltipNode = tooltip; |
| 7036 |
} |
| 7037 |
document.body.appendChild(tooltipNode); |
| 7038 |
positionTooltip(tooltipNode, ev); |
| 7039 |
}; |
| 7040 |
const moveTooltip = (ev) => { |
| 7041 |
if (tooltipNode && tooltipNode.isConnected) { |
| 7042 |
positionTooltip(tooltipNode, ev); |
| 7043 |
} |
| 7044 |
}; |
| 7045 |
const hideTooltip = () => { |
| 7046 |
if (tooltipNode && tooltipNode.isConnected) { |
| 7047 |
tooltipNode.remove(); |
| 7048 |
} |
| 7049 |
}; |
| 7050 |
tile.addEventListener("mouseenter", showTooltip); |
| 7051 |
tile.addEventListener("mousemove", moveTooltip); |
| 7052 |
tile.addEventListener("mouseleave", hideTooltip); |
| 7053 |
state.teardown.push(hideTooltip); |
| 7054 |
attachTileDragOut( |
| 7055 |
tile, |
| 7056 |
{ |
| 7057 |
kind: "user", |
| 7058 |
ref: String(item.id), |
| 7059 |
title: displayName, |
| 7060 |
icon: "dashicons-admin-users", |
| 7061 |
// Cross-frame bridge payload — receiver inserts a |
| 7062 |
// `core/paragraph` with `<a href>` pointing at the |
| 7063 |
// author archive (`item.link`). Falls back to empty |
| 7064 |
// string when the REST shape omitted the link; the |
| 7065 |
// receiver gates on a truthy URL. |
| 7066 |
bridgePayload: { |
| 7067 |
kind: "user", |
| 7068 |
id: item.id, |
| 7069 |
url: item.link ?? "", |
| 7070 |
title: displayName |
| 7071 |
} |
| 7072 |
}, |
| 7073 |
() => hideTooltip() |
| 7074 |
); |
| 7075 |
const tileKey = `entry:${item.id}`; |
| 7076 |
ctx.layout.place(tile, tileKey, { |
| 7077 |
name: displayName, |
| 7078 |
// Order users by post count by default — the most active |
| 7079 |
// surface first. Authoring date isn't available per-user, |
| 7080 |
// so we synthesize a date that ranks more-prolific users |
| 7081 |
// earlier when the canvas sort-by-date is selected. |
| 7082 |
date: postCount > 0 ? new Date(2100, 0, 1 - postCount).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString() |
| 7083 |
}); |
| 7084 |
tile.addEventListener("click", () => { |
| 7085 |
selectUserTile(state, ctx, tile, item); |
| 7086 |
}); |
| 7087 |
tile.addEventListener("dblclick", (e) => { |
| 7088 |
e.preventDefault(); |
| 7089 |
hideTooltip(); |
| 7090 |
navigate(state, { |
| 7091 |
kind: "user-footprint", |
| 7092 |
entityId: entity.id, |
| 7093 |
userId: item.id, |
| 7094 |
userName: displayName |
| 7095 |
}); |
| 7096 |
}); |
| 7097 |
tile.addEventListener("contextmenu", (e) => { |
| 7098 |
e.preventDefault(); |
| 7099 |
hideTooltip(); |
| 7100 |
openUserTileMenu(state, entity, item, displayName, { |
| 7101 |
x: e.clientX, |
| 7102 |
y: e.clientY |
| 7103 |
}); |
| 7104 |
}); |
| 7105 |
return tile; |
| 7106 |
} |
| 7107 |
function buildUserTooltip(name, item) { |
| 7108 |
const tip = document.createElement("div"); |
| 7109 |
tip.className = "desktop-mode-my-wordpress__tooltip"; |
| 7110 |
tip.setAttribute("role", "tooltip"); |
| 7111 |
const heading = document.createElement("div"); |
| 7112 |
heading.className = "desktop-mode-my-wordpress__tooltip-title"; |
| 7113 |
heading.textContent = name; |
| 7114 |
tip.appendChild(heading); |
| 7115 |
const summary = item.desktop_mode_summary; |
| 7116 |
const roleLabel = (summary?.roleLabels ?? [])[0]; |
| 7117 |
const postCount = summary?.postCount ?? 0; |
| 7118 |
const lastActive = summary?.lastActive ?? ""; |
| 7119 |
const lines = []; |
| 7120 |
if (roleLabel) { |
| 7121 |
lines.push(roleLabel); |
| 7122 |
} |
| 7123 |
if (postCount > 0) { |
| 7124 |
lines.push( |
| 7125 |
sprintf( |
| 7126 |
// translators: %d is a count of posts authored by a user. |
| 7127 |
_n("%d post", "%d posts", postCount), |
| 7128 |
postCount |
| 7129 |
) |
| 7130 |
); |
| 7131 |
} |
| 7132 |
if (lastActive) { |
| 7133 |
lines.push( |
| 7134 |
sprintf( |
| 7135 |
// translators: %s is a relative or absolute date. |
| 7136 |
__("Last published %s", "desktop-mode"), |
| 7137 |
formatDate(lastActive) |
| 7138 |
) |
| 7139 |
); |
| 7140 |
} |
| 7141 |
for (const ln of lines) { |
| 7142 |
const p = document.createElement("p"); |
| 7143 |
p.className = "desktop-mode-my-wordpress__tooltip-excerpt"; |
| 7144 |
p.textContent = ln; |
| 7145 |
tip.appendChild(p); |
| 7146 |
} |
| 7147 |
const bio = (item.description ?? "").trim(); |
| 7148 |
if (bio) { |
| 7149 |
const p = document.createElement("p"); |
| 7150 |
p.className = "desktop-mode-my-wordpress__tooltip-excerpt"; |
| 7151 |
p.textContent = bio.length > 200 ? bio.slice(0, 197) + "…" : bio; |
| 7152 |
tip.appendChild(p); |
| 7153 |
} |
| 7154 |
return tip; |
| 7155 |
} |
| 7156 |
function selectUserTile(state, ctx, tile, item) { |
| 7157 |
if (ctx.selectedTile) { |
| 7158 |
ctx.selectedTile.classList.remove( |
| 7159 |
"desktop-mode-file-tile--selected" |
| 7160 |
); |
| 7161 |
} |
| 7162 |
tile.classList.add("desktop-mode-file-tile--selected"); |
| 7163 |
ctx.selectedTile = tile; |
| 7164 |
ctx.selectedId = item.id; |
| 7165 |
void renderUserPreviewPane(state, ctx, item); |
| 7166 |
} |
| 7167 |
async function renderUserPreviewPane(state, ctx, item) { |
| 7168 |
const fallbackName = item.name || item.slug || `#${item.id}`; |
| 7169 |
const fallbackAvatar = pickAvatar(item.avatar_urls) ?? ""; |
| 7170 |
const userId = item.id; |
| 7171 |
showPreviewLoading(ctx.preview); |
| 7172 |
let node; |
| 7173 |
try { |
| 7174 |
node = await renderUserDossier({ |
| 7175 |
userId, |
| 7176 |
fallbackName, |
| 7177 |
fallbackAvatar, |
| 7178 |
fallbackDescription: item.description ?? "" |
| 7179 |
}); |
| 7180 |
} catch (err) { |
| 7181 |
if (ctx.selectedId !== userId) { |
| 7182 |
return; |
| 7183 |
} |
| 7184 |
showPreviewError(ctx.preview, err); |
| 7185 |
return; |
| 7186 |
} |
| 7187 |
if (ctx.selectedId !== userId) { |
| 7188 |
return; |
| 7189 |
} |
| 7190 |
const footer = document.createElement("footer"); |
| 7191 |
footer.className = "desktop-mode-my-wordpress__article-footer"; |
| 7192 |
const footprintBtn = document.createElement("wpd-button"); |
| 7193 |
footprintBtn.setAttribute("variant", "primary"); |
| 7194 |
footprintBtn.textContent = __("View activity footprint", "desktop-mode"); |
| 7195 |
footprintBtn.title = __( |
| 7196 |
"Open the full activity footprint surface for this user.", |
| 7197 |
"desktop-mode" |
| 7198 |
); |
| 7199 |
footprintBtn.addEventListener("click", () => { |
| 7200 |
navigate(state, { |
| 7201 |
kind: "user-footprint", |
| 7202 |
entityId: "users", |
| 7203 |
userId, |
| 7204 |
userName: fallbackName |
| 7205 |
}); |
| 7206 |
}); |
| 7207 |
footer.appendChild(footprintBtn); |
| 7208 |
const editBtn = document.createElement("wpd-button"); |
| 7209 |
editBtn.setAttribute("variant", "secondary"); |
| 7210 |
editBtn.textContent = __("Show profile", "desktop-mode"); |
| 7211 |
editBtn.title = __( |
| 7212 |
"Open this user’s profile editor in a new window.", |
| 7213 |
"desktop-mode" |
| 7214 |
); |
| 7215 |
editBtn.addEventListener("click", () => { |
| 7216 |
openUserEditWindow(userId); |
| 7217 |
}); |
| 7218 |
footer.appendChild(editBtn); |
| 7219 |
node.appendChild(footer); |
| 7220 |
ctx.preview.replaceChildren(node); |
| 7221 |
} |
| 7222 |
function openUserTileMenu(state, entity, item, name, pos) { |
| 7223 |
closeAnyTileMenu(); |
| 7224 |
const menu = document.createElement("wpd-context-menu"); |
| 7225 |
menu.setAttribute("open", ""); |
| 7226 |
menu.classList.add("desktop-mode-my-wordpress__menu"); |
| 7227 |
menu.style.left = `${pos.x}px`; |
| 7228 |
menu.style.top = `${pos.y}px`; |
| 7229 |
const addOption = (id, label, icon) => { |
| 7230 |
const opt = document.createElement("wpd-context-menu-option"); |
| 7231 |
opt.dataset.menuItemId = id; |
| 7232 |
opt.setAttribute("value", id); |
| 7233 |
opt.setAttribute("icon", sanitizeClass(icon)); |
| 7234 |
opt.textContent = label; |
| 7235 |
menu.appendChild(opt); |
| 7236 |
}; |
| 7237 |
addOption( |
| 7238 |
"footprint", |
| 7239 |
__("View activity footprint", "desktop-mode"), |
| 7240 |
"dashicons-chart-area" |
| 7241 |
); |
| 7242 |
addOption( |
| 7243 |
"open-profile", |
| 7244 |
__("Show profile", "desktop-mode"), |
| 7245 |
"dashicons-id-alt" |
| 7246 |
); |
| 7247 |
if (item.link) { |
| 7248 |
addOption( |
| 7249 |
"author-archive", |
| 7250 |
__("View author archive", "desktop-mode"), |
| 7251 |
"dashicons-external" |
| 7252 |
); |
| 7253 |
} |
| 7254 |
menu.addEventListener("wpd-context-menu-pick", (e) => { |
| 7255 |
const detail = e.detail; |
| 7256 |
closeAnyTileMenu(); |
| 7257 |
if (detail.id === "footprint") { |
| 7258 |
navigate(state, { |
| 7259 |
kind: "user-footprint", |
| 7260 |
entityId: entity.id, |
| 7261 |
userId: item.id, |
| 7262 |
userName: name |
| 7263 |
}); |
| 7264 |
return; |
| 7265 |
} |
| 7266 |
if (detail.id === "open-profile") { |
| 7267 |
openUserEditWindow(item.id); |
| 7268 |
return; |
| 7269 |
} |
| 7270 |
if (detail.id === "author-archive" && item.link) { |
| 7271 |
window.open(item.link, "_blank", "noopener,noreferrer"); |
| 7272 |
} |
| 7273 |
}); |
| 7274 |
document.body.appendChild(menu); |
| 7275 |
const rect = menu.getBoundingClientRect(); |
| 7276 |
if (rect.right > window.innerWidth) { |
| 7277 |
menu.style.left = `${Math.max( |
| 7278 |
0, |
| 7279 |
window.innerWidth - rect.width - 8 |
| 7280 |
)}px`; |
| 7281 |
} |
| 7282 |
if (rect.bottom > window.innerHeight) { |
| 7283 |
menu.style.top = `${Math.max( |
| 7284 |
0, |
| 7285 |
window.innerHeight - rect.height - 8 |
| 7286 |
)}px`; |
| 7287 |
} |
| 7288 |
queueMicrotask(() => { |
| 7289 |
const onDocPointerDown = (ev) => { |
| 7290 |
const target = ev.target; |
| 7291 |
if (target instanceof Node && menu.contains(target)) { |
| 7292 |
return; |
| 7293 |
} |
| 7294 |
closeAnyTileMenu(); |
| 7295 |
}; |
| 7296 |
const onDocKey = (ev) => { |
| 7297 |
if (ev.key === "Escape") { |
| 7298 |
closeAnyTileMenu(); |
| 7299 |
} |
| 7300 |
}; |
| 7301 |
document.addEventListener("pointerdown", onDocPointerDown, true); |
| 7302 |
document.addEventListener("keydown", onDocKey); |
| 7303 |
menu.addEventListener("tile-menu-closed", () => { |
| 7304 |
document.removeEventListener( |
| 7305 |
"pointerdown", |
| 7306 |
onDocPointerDown, |
| 7307 |
true |
| 7308 |
); |
| 7309 |
document.removeEventListener("keydown", onDocKey); |
| 7310 |
}); |
| 7311 |
}); |
| 7312 |
} |
| 7313 |
function openUserEditWindow(userId) { |
| 7314 |
if (!Number.isFinite(userId) || userId <= 0) { |
| 7315 |
return; |
| 7316 |
} |
| 7317 |
const desktop = window.wp?.desktop; |
| 7318 |
const createSharedStore = desktop?.createSharedStore; |
| 7319 |
if (typeof createSharedStore === "function") { |
| 7320 |
const store = createSharedStore( |
| 7321 |
"desktop-mode/user-edit/target", |
| 7322 |
() => ({ userId: null, requestedAt: 0, tabRequested: false }) |
| 7323 |
); |
| 7324 |
store.state.userId = userId; |
| 7325 |
store.state.requestedAt = Date.now(); |
| 7326 |
store.state.tabRequested = true; |
| 7327 |
store.notify(); |
| 7328 |
} |
| 7329 |
const opened = desktop?.openWindow?.("desktop-mode-user-edit", { |
| 7330 |
source: "my-wordpress/user-tile" |
| 7331 |
}); |
| 7332 |
if (!opened) { |
| 7333 |
openIframeWindow({ |
| 7334 |
id: `user-edit-${userId}`, |
| 7335 |
url: buildEditUserUrl(userId), |
| 7336 |
title: __("Edit user", "desktop-mode"), |
| 7337 |
icon: "dashicons-admin-users" |
| 7338 |
}); |
| 7339 |
} |
| 7340 |
} |
| 7341 |
function initialsOf(name) { |
| 7342 |
const parts = name.trim().split(/\s+/).filter((s) => s.length > 0); |
| 7343 |
if (parts.length === 0) { |
| 7344 |
return "?"; |
| 7345 |
} |
| 7346 |
if (parts.length === 1) { |
| 7347 |
return parts[0].slice(0, 2).toUpperCase(); |
| 7348 |
} |
| 7349 |
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); |
| 7350 |
} |
| 7351 |
function renderUserFootprint(state, entity, userId, userName) { |
| 7352 |
const host = document.createElement("div"); |
| 7353 |
host.className = "desktop-mode-my-wordpress__footprint"; |
| 7354 |
state.body.appendChild(host); |
| 7355 |
showPreviewLoading(host); |
| 7356 |
paintStatus( |
| 7357 |
state, |
| 7358 |
[ |
| 7359 |
{ |
| 7360 |
id: "loading", |
| 7361 |
label: __("Loading footprint…", "desktop-mode"), |
| 7362 |
align: "start", |
| 7363 |
sort: 10 |
| 7364 |
} |
| 7365 |
], |
| 7366 |
{ view: "detail", entityId: entity.id, postId: userId } |
| 7367 |
); |
| 7368 |
void (async () => { |
| 7369 |
let payload; |
| 7370 |
try { |
| 7371 |
payload = await fetchUserFootprint(userId); |
| 7372 |
} catch (err) { |
| 7373 |
showPreviewError(host, err); |
| 7374 |
paintStatus( |
| 7375 |
state, |
| 7376 |
[ |
| 7377 |
{ |
| 7378 |
id: "error", |
| 7379 |
label: __("Could not load footprint.", "desktop-mode"), |
| 7380 |
align: "start", |
| 7381 |
sort: 10 |
| 7382 |
} |
| 7383 |
], |
| 7384 |
{ view: "detail", entityId: entity.id, postId: userId } |
| 7385 |
); |
| 7386 |
return; |
| 7387 |
} |
| 7388 |
if (state.route.kind !== "user-footprint" || state.route.userId !== userId) { |
| 7389 |
return; |
| 7390 |
} |
| 7391 |
host.replaceChildren(); |
| 7392 |
host.appendChild(buildFootprintHero(payload)); |
| 7393 |
host.appendChild(buildFootprintHeadlineStats(payload)); |
| 7394 |
host.appendChild(buildFootprintCalendar(payload)); |
| 7395 |
host.appendChild(buildFootprintRhythm(payload)); |
| 7396 |
const monthCallout = buildFootprintMonthCallout(payload); |
| 7397 |
if (monthCallout) { |
| 7398 |
host.appendChild(monthCallout); |
| 7399 |
} |
| 7400 |
host.appendChild(buildFootprintTimeline(payload)); |
| 7401 |
host.appendChild( |
| 7402 |
buildFootprintFooter(payload, userId) |
| 7403 |
); |
| 7404 |
paintStatus( |
| 7405 |
state, |
| 7406 |
[ |
| 7407 |
{ |
| 7408 |
id: "count", |
| 7409 |
label: sprintf( |
| 7410 |
// translators: 1: post total, 2: comment total. |
| 7411 |
__( |
| 7412 |
"%1$d posts · %2$d comments tracked", |
| 7413 |
"desktop-mode" |
| 7414 |
), |
| 7415 |
payload.totals.posts + payload.totals.pages, |
| 7416 |
payload.totals.comments |
| 7417 |
), |
| 7418 |
align: "start", |
| 7419 |
sort: 10 |
| 7420 |
}, |
| 7421 |
{ |
| 7422 |
id: "range", |
| 7423 |
label: sprintf( |
| 7424 |
// translators: 1: window-start date, 2: window-end date. |
| 7425 |
__( |
| 7426 |
"Window %1$s → %2$s", |
| 7427 |
"desktop-mode" |
| 7428 |
), |
| 7429 |
formatShortDate(payload.range.from), |
| 7430 |
formatShortDate(payload.range.to) |
| 7431 |
), |
| 7432 |
align: "end", |
| 7433 |
sort: 10 |
| 7434 |
} |
| 7435 |
], |
| 7436 |
{ view: "detail", entityId: entity.id, postId: userId } |
| 7437 |
); |
| 7438 |
})(); |
| 7439 |
} |
| 7440 |
function buildFootprintHero(payload) { |
| 7441 |
const hero = document.createElement("header"); |
| 7442 |
hero.className = "desktop-mode-my-wordpress__footprint-hero"; |
| 7443 |
const avatar = document.createElement("div"); |
| 7444 |
avatar.className = "desktop-mode-my-wordpress__footprint-avatar"; |
| 7445 |
if (payload.profile.avatarUrl) { |
| 7446 |
const img = document.createElement("img"); |
| 7447 |
img.src = payload.profile.avatarUrl; |
| 7448 |
img.alt = ""; |
| 7449 |
avatar.appendChild(img); |
| 7450 |
} else { |
| 7451 |
const span = document.createElement("span"); |
| 7452 |
span.className = "desktop-mode-my-wordpress__user-tile-initials"; |
| 7453 |
span.textContent = initialsOf(payload.profile.name); |
| 7454 |
avatar.appendChild(span); |
| 7455 |
} |
| 7456 |
hero.appendChild(avatar); |
| 7457 |
const text = document.createElement("div"); |
| 7458 |
text.className = "desktop-mode-my-wordpress__footprint-headline"; |
| 7459 |
const h = document.createElement("h1"); |
| 7460 |
h.className = "desktop-mode-my-wordpress__footprint-title"; |
| 7461 |
h.textContent = payload.profile.name; |
| 7462 |
text.appendChild(h); |
| 7463 |
const meta = document.createElement("div"); |
| 7464 |
meta.className = "desktop-mode-my-wordpress__footprint-meta"; |
| 7465 |
const roles = payload.profile.roleLabels ?? []; |
| 7466 |
for (const r of roles) { |
| 7467 |
const chip = document.createElement("span"); |
| 7468 |
chip.className = "desktop-mode-my-wordpress__user-role"; |
| 7469 |
chip.textContent = r; |
| 7470 |
meta.appendChild(chip); |
| 7471 |
} |
| 7472 |
if (payload.profile.registered) { |
| 7473 |
const since = document.createElement("span"); |
| 7474 |
since.className = "desktop-mode-my-wordpress__user-role desktop-mode-my-wordpress__footprint-since"; |
| 7475 |
since.textContent = sprintf( |
| 7476 |
// translators: %s is a year-month label like "January 2023". |
| 7477 |
__("Member since %s", "desktop-mode"), |
| 7478 |
formatYearMonth(payload.profile.registered) |
| 7479 |
); |
| 7480 |
meta.appendChild(since); |
| 7481 |
} |
| 7482 |
text.appendChild(meta); |
| 7483 |
if (payload.profile.link) { |
| 7484 |
const links = document.createElement("div"); |
| 7485 |
links.className = "desktop-mode-my-wordpress__user-links"; |
| 7486 |
const a = document.createElement("a"); |
| 7487 |
a.href = payload.profile.link; |
| 7488 |
a.target = "_blank"; |
| 7489 |
a.rel = "noopener noreferrer"; |
| 7490 |
a.textContent = __("Author archive", "desktop-mode"); |
| 7491 |
links.appendChild(a); |
| 7492 |
text.appendChild(links); |
| 7493 |
} |
| 7494 |
hero.appendChild(text); |
| 7495 |
return hero; |
| 7496 |
} |
| 7497 |
function buildFootprintHeadlineStats(payload) { |
| 7498 |
const wrap = document.createElement("section"); |
| 7499 |
wrap.className = "desktop-mode-my-wordpress__footprint-section desktop-mode-my-wordpress__footprint-stats-row"; |
| 7500 |
const totalContent = payload.totals.posts + payload.totals.pages; |
| 7501 |
wrap.appendChild( |
| 7502 |
buildStatCard( |
| 7503 |
totalContent.toLocaleString(), |
| 7504 |
__("Total content", "desktop-mode"), |
| 7505 |
payload.totals.posts > 0 && payload.totals.pages > 0 ? sprintf( |
| 7506 |
// translators: 1: post count, 2: page count. |
| 7507 |
__( |
| 7508 |
"%1$d posts · %2$d pages", |
| 7509 |
"desktop-mode" |
| 7510 |
), |
| 7511 |
payload.totals.posts, |
| 7512 |
payload.totals.pages |
| 7513 |
) : "" |
| 7514 |
) |
| 7515 |
); |
| 7516 |
wrap.appendChild( |
| 7517 |
buildStatCard( |
| 7518 |
payload.totals.comments.toLocaleString(), |
| 7519 |
__("Comments left", "desktop-mode"), |
| 7520 |
"" |
| 7521 |
) |
| 7522 |
); |
| 7523 |
const updateCount = payload.totals.updates ?? 0; |
| 7524 |
if (updateCount > 0) { |
| 7525 |
wrap.appendChild( |
| 7526 |
buildStatCard( |
| 7527 |
updateCount.toLocaleString(), |
| 7528 |
__("Updates", "desktop-mode"), |
| 7529 |
__("Saves on existing posts", "desktop-mode") |
| 7530 |
) |
| 7531 |
); |
| 7532 |
} |
| 7533 |
const longestRange = payload.streak.longestRange; |
| 7534 |
const longestCaption = longestRange.from && longestRange.to ? sprintf( |
| 7535 |
// translators: 1: start date, 2: end date. |
| 7536 |
__("%1$s → %2$s", "desktop-mode"), |
| 7537 |
formatShortDate(longestRange.from), |
| 7538 |
formatShortDate(longestRange.to) |
| 7539 |
) : ""; |
| 7540 |
wrap.appendChild( |
| 7541 |
buildStatCard( |
| 7542 |
sprintf( |
| 7543 |
// translators: %d is the length in days of the user's longest publishing streak. |
| 7544 |
_n( |
| 7545 |
"%d day", |
| 7546 |
"%d days", |
| 7547 |
payload.streak.longest |
| 7548 |
), |
| 7549 |
payload.streak.longest |
| 7550 |
), |
| 7551 |
__("Longest streak", "desktop-mode"), |
| 7552 |
longestCaption |
| 7553 |
) |
| 7554 |
); |
| 7555 |
wrap.appendChild( |
| 7556 |
buildStatCard( |
| 7557 |
sprintf( |
| 7558 |
// translators: %d is the length in days of the user's current active streak. |
| 7559 |
_n("%d day", "%d days", payload.streak.current), |
| 7560 |
payload.streak.current |
| 7561 |
), |
| 7562 |
__("Current streak", "desktop-mode"), |
| 7563 |
payload.streak.current === 0 ? __("No activity today", "desktop-mode") : __("Including today", "desktop-mode") |
| 7564 |
) |
| 7565 |
); |
| 7566 |
return wrap; |
| 7567 |
} |
| 7568 |
function buildFootprintCalendar(payload) { |
| 7569 |
const section = document.createElement("section"); |
| 7570 |
section.className = "desktop-mode-my-wordpress__footprint-section desktop-mode-my-wordpress__footprint-calendar-section"; |
| 7571 |
const h = document.createElement("h3"); |
| 7572 |
h.textContent = __("A year of activity", "desktop-mode"); |
| 7573 |
section.appendChild(h); |
| 7574 |
const calendar = document.createElement("div"); |
| 7575 |
calendar.className = "desktop-mode-my-wordpress__footprint-calendar"; |
| 7576 |
const dayIntensity = (d) => d.posts + d.comments + (d.updates ?? 0); |
| 7577 |
const maxIntensity = payload.daily.reduce((m, d) => { |
| 7578 |
const v = dayIntensity(d); |
| 7579 |
return v > m ? v : m; |
| 7580 |
}, 0); |
| 7581 |
const bucketize = (v) => { |
| 7582 |
if (v <= 0) { |
| 7583 |
return 0; |
| 7584 |
} |
| 7585 |
if (maxIntensity <= 0) { |
| 7586 |
return 0; |
| 7587 |
} |
| 7588 |
const ratio = v / maxIntensity; |
| 7589 |
if (ratio > 0.75) { |
| 7590 |
return 4; |
| 7591 |
} |
| 7592 |
if (ratio > 0.5) { |
| 7593 |
return 3; |
| 7594 |
} |
| 7595 |
if (ratio > 0.25) { |
| 7596 |
return 2; |
| 7597 |
} |
| 7598 |
return 1; |
| 7599 |
}; |
| 7600 |
const dates = payload.daily.map((d) => /* @__PURE__ */ new Date(d.date + "T00:00:00Z")); |
| 7601 |
if (dates.length === 0) { |
| 7602 |
const empty = document.createElement("p"); |
| 7603 |
empty.className = "desktop-mode-my-wordpress__article-meta"; |
| 7604 |
empty.textContent = __( |
| 7605 |
"No activity recorded in the last year.", |
| 7606 |
"desktop-mode" |
| 7607 |
); |
| 7608 |
section.appendChild(empty); |
| 7609 |
return section; |
| 7610 |
} |
| 7611 |
const firstDow = dates[0].getUTCDay(); |
| 7612 |
const grid = document.createElement("div"); |
| 7613 |
grid.className = "desktop-mode-my-wordpress__footprint-grid"; |
| 7614 |
const placeCell = (el, linearDayOffset) => { |
| 7615 |
const dow = linearDayOffset % 7; |
| 7616 |
const week = Math.floor(linearDayOffset / 7); |
| 7617 |
el.style.gridRow = String(dow + 2); |
| 7618 |
el.style.gridColumn = String(week + 2); |
| 7619 |
}; |
| 7620 |
const weekdaySource = [ |
| 7621 |
// 2024-12-02 was a Monday (UTC). |
| 7622 |
new Date(Date.UTC(2024, 11, 2)), |
| 7623 |
// Mon |
| 7624 |
new Date(Date.UTC(2024, 11, 4)), |
| 7625 |
// Wed |
| 7626 |
new Date(Date.UTC(2024, 11, 6)) |
| 7627 |
// Fri |
| 7628 |
]; |
| 7629 |
const weekdayRows = [2, 4, 6]; |
| 7630 |
for (let i = 0; i < weekdaySource.length; i += 1) { |
| 7631 |
const lbl = document.createElement("span"); |
| 7632 |
lbl.className = "desktop-mode-my-wordpress__footprint-weekday"; |
| 7633 |
lbl.textContent = weekdaySource[i].toLocaleDateString(void 0, { |
| 7634 |
weekday: "short" |
| 7635 |
}); |
| 7636 |
lbl.style.gridColumn = "1"; |
| 7637 |
lbl.style.gridRow = String(weekdayRows[i] + 1); |
| 7638 |
grid.appendChild(lbl); |
| 7639 |
} |
| 7640 |
for (let i = 0; i < firstDow; i += 1) { |
| 7641 |
const blank = document.createElement("span"); |
| 7642 |
blank.className = "desktop-mode-my-wordpress__footprint-cell desktop-mode-my-wordpress__footprint-cell--pad"; |
| 7643 |
blank.setAttribute("aria-hidden", "true"); |
| 7644 |
placeCell(blank, i); |
| 7645 |
grid.appendChild(blank); |
| 7646 |
} |
| 7647 |
let lastMonth = -1; |
| 7648 |
for (let i = 0; i < payload.daily.length; i += 1) { |
| 7649 |
const d = dates[i]; |
| 7650 |
const m = d.getUTCMonth(); |
| 7651 |
if (m === lastMonth) { |
| 7652 |
continue; |
| 7653 |
} |
| 7654 |
lastMonth = m; |
| 7655 |
const linear = firstDow + i; |
| 7656 |
const week = Math.floor(linear / 7); |
| 7657 |
if (week === 0 && linear % 7 !== 0) { |
| 7658 |
continue; |
| 7659 |
} |
| 7660 |
const lbl = document.createElement("span"); |
| 7661 |
lbl.className = "desktop-mode-my-wordpress__footprint-month"; |
| 7662 |
lbl.textContent = d.toLocaleDateString(void 0, { month: "short" }); |
| 7663 |
lbl.style.gridRow = "1"; |
| 7664 |
lbl.style.gridColumn = String(week + 2); |
| 7665 |
grid.appendChild(lbl); |
| 7666 |
} |
| 7667 |
for (let i = 0; i < payload.daily.length; i += 1) { |
| 7668 |
const d = payload.daily[i]; |
| 7669 |
const intensity = bucketize(dayIntensity(d)); |
| 7670 |
const cell = document.createElement("span"); |
| 7671 |
cell.className = `desktop-mode-my-wordpress__footprint-cell desktop-mode-my-wordpress__footprint-cell--l${intensity}`; |
| 7672 |
cell.title = sprintf( |
| 7673 |
// translators: 1: date, 2: post count, 3: comment count, 4: update (re-save) count. |
| 7674 |
__( |
| 7675 |
"%1$s — %2$d posts, %3$d comments, %4$d updates", |
| 7676 |
"desktop-mode" |
| 7677 |
), |
| 7678 |
formatLongDate(d.date), |
| 7679 |
d.posts, |
| 7680 |
d.comments, |
| 7681 |
d.updates ?? 0 |
| 7682 |
); |
| 7683 |
cell.dataset.date = d.date; |
| 7684 |
placeCell(cell, firstDow + i); |
| 7685 |
grid.appendChild(cell); |
| 7686 |
} |
| 7687 |
calendar.appendChild(grid); |
| 7688 |
const legend = document.createElement("div"); |
| 7689 |
legend.className = "desktop-mode-my-wordpress__footprint-legend"; |
| 7690 |
const less = document.createElement("span"); |
| 7691 |
less.className = "desktop-mode-my-wordpress__footprint-legend-label"; |
| 7692 |
less.textContent = __("Less", "desktop-mode"); |
| 7693 |
legend.appendChild(less); |
| 7694 |
for (let i = 0; i <= 4; i += 1) { |
| 7695 |
const sw = document.createElement("span"); |
| 7696 |
sw.className = `desktop-mode-my-wordpress__footprint-cell desktop-mode-my-wordpress__footprint-cell--l${i}`; |
| 7697 |
legend.appendChild(sw); |
| 7698 |
} |
| 7699 |
const more = document.createElement("span"); |
| 7700 |
more.className = "desktop-mode-my-wordpress__footprint-legend-label"; |
| 7701 |
more.textContent = __("More", "desktop-mode"); |
| 7702 |
legend.appendChild(more); |
| 7703 |
calendar.appendChild(legend); |
| 7704 |
section.appendChild(calendar); |
| 7705 |
return section; |
| 7706 |
} |
| 7707 |
function buildFootprintRhythm(payload) { |
| 7708 |
const section = document.createElement("section"); |
| 7709 |
section.className = "desktop-mode-my-wordpress__footprint-section desktop-mode-my-wordpress__footprint-rhythm"; |
| 7710 |
const h = document.createElement("h3"); |
| 7711 |
h.textContent = __("Publishing rhythm", "desktop-mode"); |
| 7712 |
section.appendChild(h); |
| 7713 |
const grid = document.createElement("div"); |
| 7714 |
grid.className = "desktop-mode-my-wordpress__footprint-rhythm-grid"; |
| 7715 |
const weekdayWrap = document.createElement("div"); |
| 7716 |
weekdayWrap.className = "desktop-mode-my-wordpress__footprint-chart"; |
| 7717 |
const weekdayCap = document.createElement("div"); |
| 7718 |
weekdayCap.className = "desktop-mode-my-wordpress__footprint-chart-caption"; |
| 7719 |
weekdayCap.textContent = __("By weekday", "desktop-mode"); |
| 7720 |
weekdayWrap.appendChild(weekdayCap); |
| 7721 |
const weekdayLabels = [ |
| 7722 |
__("S", "desktop-mode"), |
| 7723 |
__("M", "desktop-mode"), |
| 7724 |
__("T", "desktop-mode"), |
| 7725 |
__("W", "desktop-mode"), |
| 7726 |
__("T", "desktop-mode"), |
| 7727 |
__("F", "desktop-mode"), |
| 7728 |
__("S", "desktop-mode") |
| 7729 |
]; |
| 7730 |
const weekdayFull = [ |
| 7731 |
__("Sunday", "desktop-mode"), |
| 7732 |
__("Monday", "desktop-mode"), |
| 7733 |
__("Tuesday", "desktop-mode"), |
| 7734 |
__("Wednesday", "desktop-mode"), |
| 7735 |
__("Thursday", "desktop-mode"), |
| 7736 |
__("Friday", "desktop-mode"), |
| 7737 |
__("Saturday", "desktop-mode") |
| 7738 |
]; |
| 7739 |
weekdayWrap.appendChild( |
| 7740 |
buildBarChart(payload.weekday, weekdayLabels, weekdayFull) |
| 7741 |
); |
| 7742 |
grid.appendChild(weekdayWrap); |
| 7743 |
const hourWrap = document.createElement("div"); |
| 7744 |
hourWrap.className = "desktop-mode-my-wordpress__footprint-chart"; |
| 7745 |
const hourCap = document.createElement("div"); |
| 7746 |
hourCap.className = "desktop-mode-my-wordpress__footprint-chart-caption"; |
| 7747 |
hourCap.textContent = __("By hour of day (site time)", "desktop-mode"); |
| 7748 |
hourWrap.appendChild(hourCap); |
| 7749 |
const hourLabels = [ |
| 7750 |
"0", |
| 7751 |
"", |
| 7752 |
"", |
| 7753 |
"3", |
| 7754 |
"", |
| 7755 |
"", |
| 7756 |
"6", |
| 7757 |
"", |
| 7758 |
"", |
| 7759 |
"9", |
| 7760 |
"", |
| 7761 |
"", |
| 7762 |
"12", |
| 7763 |
"", |
| 7764 |
"", |
| 7765 |
"15", |
| 7766 |
"", |
| 7767 |
"", |
| 7768 |
"18", |
| 7769 |
"", |
| 7770 |
"", |
| 7771 |
"21", |
| 7772 |
"", |
| 7773 |
"" |
| 7774 |
]; |
| 7775 |
const hourFull = Array.from( |
| 7776 |
{ length: 24 }, |
| 7777 |
(_, i) => sprintf( |
| 7778 |
// translators: %d is an hour of the day (0-23). |
| 7779 |
__("%d:00", "desktop-mode"), |
| 7780 |
i |
| 7781 |
) |
| 7782 |
); |
| 7783 |
hourWrap.appendChild( |
| 7784 |
buildBarChart(payload.hour, hourLabels, hourFull) |
| 7785 |
); |
| 7786 |
grid.appendChild(hourWrap); |
| 7787 |
section.appendChild(grid); |
| 7788 |
return section; |
| 7789 |
} |
| 7790 |
function buildBarChart(values, labels, titles) { |
| 7791 |
const chart = document.createElement("div"); |
| 7792 |
chart.className = "desktop-mode-my-wordpress__footprint-bars"; |
| 7793 |
const max = Math.max(1, ...values); |
| 7794 |
values.forEach((v, i) => { |
| 7795 |
const col = document.createElement("div"); |
| 7796 |
col.className = "desktop-mode-my-wordpress__footprint-bar-col"; |
| 7797 |
const bar = document.createElement("div"); |
| 7798 |
bar.className = "desktop-mode-my-wordpress__footprint-bar"; |
| 7799 |
bar.style.height = `${Math.round(v / max * 100)}%`; |
| 7800 |
bar.title = sprintf( |
| 7801 |
// translators: 1: bucket label, 2: count. |
| 7802 |
__( |
| 7803 |
"%1$s · %2$d", |
| 7804 |
"desktop-mode" |
| 7805 |
), |
| 7806 |
titles[i] ?? labels[i] ?? String(i), |
| 7807 |
v |
| 7808 |
); |
| 7809 |
if (v === 0) { |
| 7810 |
bar.classList.add( |
| 7811 |
"desktop-mode-my-wordpress__footprint-bar--empty" |
| 7812 |
); |
| 7813 |
} |
| 7814 |
col.appendChild(bar); |
| 7815 |
const lbl = document.createElement("span"); |
| 7816 |
lbl.className = "desktop-mode-my-wordpress__footprint-bar-label"; |
| 7817 |
lbl.textContent = labels[i] ?? ""; |
| 7818 |
col.appendChild(lbl); |
| 7819 |
chart.appendChild(col); |
| 7820 |
}); |
| 7821 |
return chart; |
| 7822 |
} |
| 7823 |
function buildFootprintMonthCallout(payload) { |
| 7824 |
const m = payload.totals.mostProlificMonth; |
| 7825 |
if (!m) { |
| 7826 |
return null; |
| 7827 |
} |
| 7828 |
const section = document.createElement("section"); |
| 7829 |
section.className = "desktop-mode-my-wordpress__footprint-section desktop-mode-my-wordpress__footprint-callout"; |
| 7830 |
const label = document.createElement("span"); |
| 7831 |
label.className = "desktop-mode-my-wordpress__footprint-callout-label"; |
| 7832 |
label.textContent = __("Most prolific month", "desktop-mode"); |
| 7833 |
section.appendChild(label); |
| 7834 |
const value = document.createElement("h3"); |
| 7835 |
value.className = "desktop-mode-my-wordpress__footprint-callout-value"; |
| 7836 |
value.textContent = formatYearMonth(m.ym + "-01T00:00:00Z"); |
| 7837 |
section.appendChild(value); |
| 7838 |
const detail = document.createElement("p"); |
| 7839 |
detail.className = "desktop-mode-my-wordpress__footprint-callout-detail"; |
| 7840 |
detail.textContent = sprintf( |
| 7841 |
// translators: %d is a post count. |
| 7842 |
_n( |
| 7843 |
"%d post published — their personal record.", |
| 7844 |
"%d posts published — their personal record.", |
| 7845 |
m.n |
| 7846 |
), |
| 7847 |
m.n |
| 7848 |
); |
| 7849 |
section.appendChild(detail); |
| 7850 |
return section; |
| 7851 |
} |
| 7852 |
function buildFootprintTimeline(payload) { |
| 7853 |
const section = document.createElement("section"); |
| 7854 |
section.className = "desktop-mode-my-wordpress__footprint-section desktop-mode-my-wordpress__footprint-timeline-section"; |
| 7855 |
const h = document.createElement("h3"); |
| 7856 |
h.textContent = __("Recent activity", "desktop-mode"); |
| 7857 |
section.appendChild(h); |
| 7858 |
if (payload.timeline.length === 0) { |
| 7859 |
const empty = document.createElement("p"); |
| 7860 |
empty.className = "desktop-mode-my-wordpress__article-meta"; |
| 7861 |
empty.textContent = __("Nothing to show yet.", "desktop-mode"); |
| 7862 |
section.appendChild(empty); |
| 7863 |
return section; |
| 7864 |
} |
| 7865 |
const list = document.createElement("ul"); |
| 7866 |
list.className = "desktop-mode-my-wordpress__footprint-timeline"; |
| 7867 |
for (const ev of payload.timeline) { |
| 7868 |
const li = document.createElement("li"); |
| 7869 |
li.className = `desktop-mode-my-wordpress__footprint-event desktop-mode-my-wordpress__footprint-event--${ev.kind}`; |
| 7870 |
const dot = document.createElement("span"); |
| 7871 |
dot.className = "desktop-mode-my-wordpress__footprint-dot"; |
| 7872 |
const icon = document.createElement("span"); |
| 7873 |
let iconClass = "dashicons-admin-post"; |
| 7874 |
if (ev.kind === "comment") { |
| 7875 |
iconClass = "dashicons-admin-comments"; |
| 7876 |
} else if (ev.kind === "post-update") { |
| 7877 |
iconClass = "dashicons-edit"; |
| 7878 |
} |
| 7879 |
icon.className = "dashicons " + iconClass; |
| 7880 |
icon.setAttribute("aria-hidden", "true"); |
| 7881 |
dot.appendChild(icon); |
| 7882 |
li.appendChild(dot); |
| 7883 |
const body = document.createElement("div"); |
| 7884 |
body.className = "desktop-mode-my-wordpress__footprint-event-body"; |
| 7885 |
const title = ev.title || __("(no title)", "desktop-mode"); |
| 7886 |
const titleNode = ev.link ? document.createElement("a") : document.createElement("span"); |
| 7887 |
titleNode.className = "desktop-mode-my-wordpress__footprint-event-title"; |
| 7888 |
if (ev.kind === "comment") { |
| 7889 |
titleNode.textContent = sprintf( |
| 7890 |
// translators: %s is a post title the user commented on. |
| 7891 |
__("Commented on “%s”", "desktop-mode"), |
| 7892 |
title |
| 7893 |
); |
| 7894 |
} else if (ev.kind === "post-update") { |
| 7895 |
titleNode.textContent = sprintf( |
| 7896 |
// translators: %s is the post title the user re-saved. |
| 7897 |
__("Updated “%s”", "desktop-mode"), |
| 7898 |
title |
| 7899 |
); |
| 7900 |
} else { |
| 7901 |
titleNode.textContent = title; |
| 7902 |
} |
| 7903 |
if (ev.link && titleNode instanceof HTMLAnchorElement) { |
| 7904 |
titleNode.href = ev.link; |
| 7905 |
titleNode.target = "_blank"; |
| 7906 |
titleNode.rel = "noopener noreferrer"; |
| 7907 |
} |
| 7908 |
body.appendChild(titleNode); |
| 7909 |
const meta = document.createElement("span"); |
| 7910 |
meta.className = "desktop-mode-my-wordpress__footprint-event-meta"; |
| 7911 |
const parts = [formatLongDate(ev.date)]; |
| 7912 |
if (ev.status && ev.status !== "publish" && ev.status !== "approved") { |
| 7913 |
parts.push(ev.status); |
| 7914 |
} |
| 7915 |
meta.textContent = parts.join(" · "); |
| 7916 |
body.appendChild(meta); |
| 7917 |
li.appendChild(body); |
| 7918 |
list.appendChild(li); |
| 7919 |
} |
| 7920 |
section.appendChild(list); |
| 7921 |
return section; |
| 7922 |
} |
| 7923 |
function buildFootprintFooter(payload, userId, userName) { |
| 7924 |
const footer = document.createElement("footer"); |
| 7925 |
footer.className = "desktop-mode-my-wordpress__footprint-section desktop-mode-my-wordpress__footprint-footer"; |
| 7926 |
const archiveBtn = document.createElement("wpd-button"); |
| 7927 |
archiveBtn.setAttribute("variant", "ghost"); |
| 7928 |
archiveBtn.textContent = __("View author archive", "desktop-mode"); |
| 7929 |
archiveBtn.addEventListener("click", () => { |
| 7930 |
if (payload.profile.link) { |
| 7931 |
window.open(payload.profile.link, "_blank", "noopener,noreferrer"); |
| 7932 |
} |
| 7933 |
}); |
| 7934 |
if (!payload.profile.link) { |
| 7935 |
archiveBtn.setAttribute("disabled", ""); |
| 7936 |
} |
| 7937 |
footer.appendChild(archiveBtn); |
| 7938 |
const editBtn = document.createElement("wpd-button"); |
| 7939 |
editBtn.setAttribute("variant", "primary"); |
| 7940 |
editBtn.textContent = __("Show profile", "desktop-mode"); |
| 7941 |
editBtn.addEventListener("click", () => { |
| 7942 |
openUserEditWindow(userId); |
| 7943 |
}); |
| 7944 |
footer.appendChild(editBtn); |
| 7945 |
return footer; |
| 7946 |
} |
| 7947 |
function formatShortDate(iso) { |
| 7948 |
if (!iso) { |
| 7949 |
return ""; |
| 7950 |
} |
| 7951 |
try { |
| 7952 |
return new Date(iso).toLocaleDateString(void 0, { |
| 7953 |
month: "short", |
| 7954 |
day: "numeric" |
| 7955 |
}); |
| 7956 |
} catch { |
| 7957 |
return iso; |
| 7958 |
} |
| 7959 |
} |
| 7960 |
function formatLongDate(iso) { |
| 7961 |
if (!iso) { |
| 7962 |
return ""; |
| 7963 |
} |
| 7964 |
try { |
| 7965 |
return new Date(iso).toLocaleDateString(void 0, { |
| 7966 |
year: "numeric", |
| 7967 |
month: "short", |
| 7968 |
day: "numeric" |
| 7969 |
}); |
| 7970 |
} catch { |
| 7971 |
return iso; |
| 7972 |
} |
| 7973 |
} |
| 7974 |
function sanitizeClass(raw) { |
| 7975 |
return (raw || "").replace(/[^a-zA-Z0-9_-]/g, ""); |
| 7976 |
} |
| 7977 |
function extractContentMediaIds(html2) { |
| 7978 |
if (!html2 || typeof html2 !== "string") { |
| 7979 |
return []; |
| 7980 |
} |
| 7981 |
const ids = []; |
| 7982 |
const seen = /* @__PURE__ */ new Set(); |
| 7983 |
const push = (raw) => { |
| 7984 |
const id = parseInt(raw, 10); |
| 7985 |
if (Number.isFinite(id) && id > 0 && !seen.has(id)) { |
| 7986 |
seen.add(id); |
| 7987 |
ids.push(id); |
| 7988 |
} |
| 7989 |
}; |
| 7990 |
const wpImage = /\bwp-image-(\d+)\b/g; |
| 7991 |
let m; |
| 7992 |
while ((m = wpImage.exec(html2)) !== null) { |
| 7993 |
push(m[1]); |
| 7994 |
} |
| 7995 |
const captionShort = /\[caption[^\]]*id="attachment_(\d+)"/g; |
| 7996 |
while ((m = captionShort.exec(html2)) !== null) { |
| 7997 |
push(m[1]); |
| 7998 |
} |
| 7999 |
return ids; |
| 8000 |
} |
| 8001 |
function createTileSelector() { |
| 8002 |
let selected = null; |
| 8003 |
return (tile) => { |
| 8004 |
if (selected === tile) { |
| 8005 |
return; |
| 8006 |
} |
| 8007 |
if (selected) { |
| 8008 |
selected.classList.remove( |
| 8009 |
"desktop-mode-file-tile--selected" |
| 8010 |
); |
| 8011 |
} |
| 8012 |
tile.classList.add("desktop-mode-file-tile--selected"); |
| 8013 |
selected = tile; |
| 8014 |
}; |
| 8015 |
} |
| 8016 |
const TILE_W = 108; |
| 8017 |
const TILE_H = 112; |
| 8018 |
const TILE_PAD = 16; |
| 8019 |
function createTileLayout(host, scope) { |
| 8020 |
const positions = loadPositions(scope); |
| 8021 |
const entries = []; |
| 8022 |
const occupied = /* @__PURE__ */ new Set(); |
| 8023 |
host.classList.add("desktop-mode-my-wordpress__canvas--positioned"); |
| 8024 |
const cellOf = (x, y) => ({ |
| 8025 |
col: Math.max(0, Math.round((x - TILE_PAD) / TILE_W)), |
| 8026 |
row: Math.max(0, Math.round((y - TILE_PAD) / TILE_H)) |
| 8027 |
}); |
| 8028 |
const occupyAt = (x, y) => { |
| 8029 |
const { col, row } = cellOf(x, y); |
| 8030 |
occupied.add(`${col},${row}`); |
| 8031 |
}; |
| 8032 |
const releaseAt = (x, y) => { |
| 8033 |
const { col, row } = cellOf(x, y); |
| 8034 |
occupied.delete(`${col},${row}`); |
| 8035 |
}; |
| 8036 |
const recomputeHostHeight = () => { |
| 8037 |
let maxBottom = 0; |
| 8038 |
for (const child of Array.from(host.children)) { |
| 8039 |
if (!(child instanceof HTMLElement)) { |
| 8040 |
continue; |
| 8041 |
} |
| 8042 |
if (!child.classList.contains("desktop-mode-file-tile")) { |
| 8043 |
continue; |
| 8044 |
} |
| 8045 |
const top = parseFloat(child.style.top || "0"); |
| 8046 |
maxBottom = Math.max(maxBottom, top + TILE_H); |
| 8047 |
} |
| 8048 |
host.style.minHeight = `${Math.max(0, maxBottom + TILE_PAD)}px`; |
| 8049 |
}; |
| 8050 |
const nextFreeCell = (cols) => { |
| 8051 |
for (let n = 0; ; n += 1) { |
| 8052 |
const col = n % cols; |
| 8053 |
const row = Math.floor(n / cols); |
| 8054 |
if (!occupied.has(`${col},${row}`)) { |
| 8055 |
return { col, row }; |
| 8056 |
} |
| 8057 |
} |
| 8058 |
}; |
| 8059 |
const place = (tile, key, sortable) => { |
| 8060 |
const saved = positions[key]; |
| 8061 |
const width = host.clientWidth > 0 ? host.clientWidth : TILE_PAD + 5 * TILE_W; |
| 8062 |
const cols = Math.max( |
| 8063 |
1, |
| 8064 |
Math.floor((width - TILE_PAD) / TILE_W) |
| 8065 |
); |
| 8066 |
const fits = saved && saved.x + TILE_W <= width; |
| 8067 |
const entry = { |
| 8068 |
key, |
| 8069 |
tile, |
| 8070 |
sortable, |
| 8071 |
userPlaced: !!fits |
| 8072 |
}; |
| 8073 |
entries.push(entry); |
| 8074 |
let x; |
| 8075 |
let y; |
| 8076 |
if (fits && saved) { |
| 8077 |
x = saved.x; |
| 8078 |
y = saved.y; |
| 8079 |
} else { |
| 8080 |
if (saved && !fits) { |
| 8081 |
delete positions[key]; |
| 8082 |
savePositions(scope, positions); |
| 8083 |
} |
| 8084 |
const cell = nextFreeCell(cols); |
| 8085 |
x = TILE_PAD + cell.col * TILE_W; |
| 8086 |
y = TILE_PAD + cell.row * TILE_H; |
| 8087 |
} |
| 8088 |
occupyAt(x, y); |
| 8089 |
applyTilePosition(tile, x, y); |
| 8090 |
recomputeHostHeight(); |
| 8091 |
}; |
| 8092 |
const commit = (tile, key, x, y) => { |
| 8093 |
const oldX = parseFloat(tile.style.left || "0"); |
| 8094 |
const oldY = parseFloat(tile.style.top || "0"); |
| 8095 |
releaseAt(oldX, oldY); |
| 8096 |
applyTilePosition(tile, x, y); |
| 8097 |
occupyAt(x, y); |
| 8098 |
positions[key] = { x, y }; |
| 8099 |
savePositions(scope, positions); |
| 8100 |
const entry = entries.find((e) => e.key === key); |
| 8101 |
if (entry) { |
| 8102 |
entry.userPlaced = true; |
| 8103 |
} |
| 8104 |
recomputeHostHeight(); |
| 8105 |
}; |
| 8106 |
const reflow = () => { |
| 8107 |
const width = host.clientWidth > 0 ? host.clientWidth : TILE_PAD + 5 * TILE_W; |
| 8108 |
const cols = Math.max( |
| 8109 |
1, |
| 8110 |
Math.floor((width - TILE_PAD) / TILE_W) |
| 8111 |
); |
| 8112 |
const overflowing = entries.some((entry) => { |
| 8113 |
const left = parseFloat(entry.tile.style.left || "0"); |
| 8114 |
return left + TILE_W > width; |
| 8115 |
}); |
| 8116 |
if (overflowing) { |
| 8117 |
for (const k of Object.keys(positions)) { |
| 8118 |
delete positions[k]; |
| 8119 |
} |
| 8120 |
savePositions(scope, positions); |
| 8121 |
for (const entry of entries) { |
| 8122 |
entry.userPlaced = false; |
| 8123 |
} |
| 8124 |
} |
| 8125 |
occupied.clear(); |
| 8126 |
for (const entry of entries) { |
| 8127 |
if (!entry.userPlaced) { |
| 8128 |
continue; |
| 8129 |
} |
| 8130 |
const left = parseFloat(entry.tile.style.left || "0"); |
| 8131 |
const top = parseFloat(entry.tile.style.top || "0"); |
| 8132 |
occupyAt(left, top); |
| 8133 |
} |
| 8134 |
let autoCount = 0; |
| 8135 |
for (const entry of entries) { |
| 8136 |
if (entry.userPlaced) { |
| 8137 |
continue; |
| 8138 |
} |
| 8139 |
const cell = nextFreeCell(cols); |
| 8140 |
const x = TILE_PAD + cell.col * TILE_W; |
| 8141 |
const y = TILE_PAD + cell.row * TILE_H; |
| 8142 |
applyTilePosition(entry.tile, x, y); |
| 8143 |
occupyAt(x, y); |
| 8144 |
autoCount += 1; |
| 8145 |
} |
| 8146 |
recomputeHostHeight(); |
| 8147 |
doAction("desktop-mode.icon-canvas.reflow", { |
| 8148 |
scope, |
| 8149 |
cols, |
| 8150 |
autoCount, |
| 8151 |
overflowing |
| 8152 |
}); |
| 8153 |
}; |
| 8154 |
const sort = (mode) => { |
| 8155 |
const sorted = entries.slice().sort((a, b) => { |
| 8156 |
switch (mode) { |
| 8157 |
case "name-asc": |
| 8158 |
return a.sortable.name.localeCompare(b.sortable.name); |
| 8159 |
case "name-desc": |
| 8160 |
return b.sortable.name.localeCompare(a.sortable.name); |
| 8161 |
case "date-asc": |
| 8162 |
return Date.parse(a.sortable.date) - Date.parse(b.sortable.date); |
| 8163 |
case "date-desc": |
| 8164 |
return Date.parse(b.sortable.date) - Date.parse(a.sortable.date); |
| 8165 |
default: |
| 8166 |
return 0; |
| 8167 |
} |
| 8168 |
}); |
| 8169 |
const width = host.clientWidth > 0 ? host.clientWidth : TILE_PAD + 5 * TILE_W; |
| 8170 |
const cols = Math.max( |
| 8171 |
1, |
| 8172 |
Math.floor((width - TILE_PAD) / TILE_W) |
| 8173 |
); |
| 8174 |
for (const k of Object.keys(positions)) { |
| 8175 |
delete positions[k]; |
| 8176 |
} |
| 8177 |
occupied.clear(); |
| 8178 |
sorted.forEach((entry, idx) => { |
| 8179 |
const col = idx % cols; |
| 8180 |
const row = Math.floor(idx / cols); |
| 8181 |
const x = TILE_PAD + col * TILE_W; |
| 8182 |
const y = TILE_PAD + row * TILE_H; |
| 8183 |
applyTilePosition(entry.tile, x, y); |
| 8184 |
occupyAt(x, y); |
| 8185 |
positions[entry.key] = { x, y }; |
| 8186 |
entry.userPlaced = true; |
| 8187 |
}); |
| 8188 |
savePositions(scope, positions); |
| 8189 |
for (const entry of sorted) { |
| 8190 |
host.appendChild(entry.tile); |
| 8191 |
} |
| 8192 |
recomputeHostHeight(); |
| 8193 |
}; |
| 8194 |
let lastWidth = host.clientWidth; |
| 8195 |
let resizeObserver = null; |
| 8196 |
if (typeof ResizeObserver !== "undefined") { |
| 8197 |
resizeObserver = new ResizeObserver(() => { |
| 8198 |
const w = host.clientWidth; |
| 8199 |
if (w === lastWidth) { |
| 8200 |
return; |
| 8201 |
} |
| 8202 |
lastWidth = w; |
| 8203 |
reflow(); |
| 8204 |
}); |
| 8205 |
resizeObserver.observe(host); |
| 8206 |
} |
| 8207 |
const peekNextCells = (count) => { |
| 8208 |
const width = host.clientWidth > 0 ? host.clientWidth : TILE_PAD + 5 * TILE_W; |
| 8209 |
const cols = Math.max( |
| 8210 |
1, |
| 8211 |
Math.floor((width - TILE_PAD) / TILE_W) |
| 8212 |
); |
| 8213 |
const taken = new Set(occupied); |
| 8214 |
const out = []; |
| 8215 |
for (let i = 0; i < count; i += 1) { |
| 8216 |
for (let n = 0; ; n += 1) { |
| 8217 |
const col = n % cols; |
| 8218 |
const row = Math.floor(n / cols); |
| 8219 |
const key = `${col},${row}`; |
| 8220 |
if (taken.has(key)) { |
| 8221 |
continue; |
| 8222 |
} |
| 8223 |
taken.add(key); |
| 8224 |
out.push({ |
| 8225 |
x: TILE_PAD + col * TILE_W, |
| 8226 |
y: TILE_PAD + row * TILE_H |
| 8227 |
}); |
| 8228 |
break; |
| 8229 |
} |
| 8230 |
} |
| 8231 |
return out; |
| 8232 |
}; |
| 8233 |
const clear = () => { |
| 8234 |
entries.length = 0; |
| 8235 |
occupied.clear(); |
| 8236 |
host.style.minHeight = ""; |
| 8237 |
}; |
| 8238 |
return { |
| 8239 |
host, |
| 8240 |
scope, |
| 8241 |
place, |
| 8242 |
commit, |
| 8243 |
sort, |
| 8244 |
reflow, |
| 8245 |
peekNextCells, |
| 8246 |
clear, |
| 8247 |
dispose: () => { |
| 8248 |
resizeObserver?.disconnect(); |
| 8249 |
resizeObserver = null; |
| 8250 |
} |
| 8251 |
}; |
| 8252 |
} |
| 8253 |
function applyTilePosition(tile, x, y) { |
| 8254 |
tile.style.left = `${Math.round(x)}px`; |
| 8255 |
tile.style.top = `${Math.round(y)}px`; |
| 8256 |
} |
| 8257 |
function loadPositions(scope) { |
| 8258 |
try { |
| 8259 |
const raw = window.localStorage.getItem(storageKey(scope)); |
| 8260 |
if (!raw) { |
| 8261 |
return {}; |
| 8262 |
} |
| 8263 |
const parsed = JSON.parse(raw); |
| 8264 |
return parsed && typeof parsed === "object" ? parsed : {}; |
| 8265 |
} catch { |
| 8266 |
return {}; |
| 8267 |
} |
| 8268 |
} |
| 8269 |
function savePositions(scope, positions) { |
| 8270 |
try { |
| 8271 |
window.localStorage.setItem( |
| 8272 |
storageKey(scope), |
| 8273 |
JSON.stringify(positions) |
| 8274 |
); |
| 8275 |
} catch { |
| 8276 |
} |
| 8277 |
} |
| 8278 |
function storageKey(scope) { |
| 8279 |
return `desktop-mode-my-wordpress:positions:${scope}`; |
| 8280 |
} |
| 8281 |
let activeState = null; |
| 8282 |
const liveStates = /* @__PURE__ */ new Map(); |
| 8283 |
let pendingRoute = null; |
| 8284 |
let rejectIdCounter = 0; |
| 8285 |
function renderInto(body) { |
| 8286 |
const root = body.querySelector(ROOT_SEL); |
| 8287 |
if (!root) { |
| 8288 |
return void 0; |
| 8289 |
} |
| 8290 |
const breadcrumbsHost = root.querySelector(BREADCRUMBS_SEL); |
| 8291 |
const bodyHost = root.querySelector(BODY_SEL); |
| 8292 |
const statusHost = root.querySelector(STATUS_SEL); |
| 8293 |
if (!breadcrumbsHost || !bodyHost || !statusHost) { |
| 8294 |
return void 0; |
| 8295 |
} |
| 8296 |
const state = { |
| 8297 |
route: { kind: "root" }, |
| 8298 |
body: bodyHost, |
| 8299 |
root, |
| 8300 |
breadcrumbs: breadcrumbsHost, |
| 8301 |
statusBar: statusHost, |
| 8302 |
teardown: [], |
| 8303 |
history: [] |
| 8304 |
}; |
| 8305 |
activeState = state; |
| 8306 |
liveStates.set(bodyHost, state); |
| 8307 |
const windowTeardowns = []; |
| 8308 |
const dragManager = getDragManager(); |
| 8309 |
if (dragManager) { |
| 8310 |
rejectIdCounter += 1; |
| 8311 |
const deregister = dragManager.registerDropTarget({ |
| 8312 |
id: `${WINDOW_ID}-reject-${rejectIdCounter}`, |
| 8313 |
element: body, |
| 8314 |
accept: () => false, |
| 8315 |
onDrop: () => { |
| 8316 |
} |
| 8317 |
}); |
| 8318 |
windowTeardowns.push(deregister); |
| 8319 |
} |
| 8320 |
windowTeardowns.push(() => closeAnyTileMenu()); |
| 8321 |
const initialRoute = pendingRoute ?? { kind: "root" }; |
| 8322 |
pendingRoute = null; |
| 8323 |
navigate(state, initialRoute); |
| 8324 |
return () => { |
| 8325 |
clearTeardown(state); |
| 8326 |
for (const fn of windowTeardowns) { |
| 8327 |
try { |
| 8328 |
fn(); |
| 8329 |
} catch { |
| 8330 |
} |
| 8331 |
} |
| 8332 |
windowTeardowns.length = 0; |
| 8333 |
liveStates.delete(bodyHost); |
| 8334 |
if (activeState === state) { |
| 8335 |
const next = liveStates.size > 0 ? Array.from(liveStates.values()).pop() : null; |
| 8336 |
activeState = next; |
| 8337 |
} |
| 8338 |
}; |
| 8339 |
} |
| 8340 |
const callback = (body) => { |
| 8341 |
try { |
| 8342 |
return renderInto(body); |
| 8343 |
} catch (err) { |
| 8344 |
console.error("[my-wordpress] render failed:", err); |
| 8345 |
return void 0; |
| 8346 |
} |
| 8347 |
}; |
| 8348 |
window.desktopModeNativeWindows = window.desktopModeNativeWindows || {}; |
| 8349 |
window.desktopModeNativeWindows[WINDOW_ID] = callback; |
| 8350 |
registerEntityKind("post", (host, entity) => { |
| 8351 |
renderEntityList(asRenderState(host), entity); |
| 8352 |
}); |
| 8353 |
registerEntityKind("user", (host, entity) => { |
| 8354 |
renderUserEntityList(asRenderState(host), entity); |
| 8355 |
}); |
| 8356 |
registerEntityKind("media", renderMediaList); |
| 8357 |
function asRenderState(host) { |
| 8358 |
const found = liveStates.get(host.body); |
| 8359 |
if (found) { |
| 8360 |
return found; |
| 8361 |
} |
| 8362 |
if (activeState && host.body === activeState.body) { |
| 8363 |
return activeState; |
| 8364 |
} |
| 8365 |
throw new Error( |
| 8366 |
"[my-wordpress] asRenderState: host body does not match any live render state." |
| 8367 |
); |
| 8368 |
} |
| 8369 |
function openDetail(args) { |
| 8370 |
const route = { |
| 8371 |
kind: "detail", |
| 8372 |
entityId: args.entityId, |
| 8373 |
postId: args.postId, |
| 8374 |
postTitle: args.postTitle |
| 8375 |
}; |
| 8376 |
if (activeState) { |
| 8377 |
navigate(activeState, route); |
| 8378 |
return; |
| 8379 |
} |
| 8380 |
pendingRoute = route; |
| 8381 |
const desktop = window.wp?.desktop; |
| 8382 |
desktop?.openWindow?.(WINDOW_ID, { source: "my-wordpress/open-detail" }); |
| 8383 |
} |
| 8384 |
function openMedia(args) { |
| 8385 |
const route = { |
| 8386 |
kind: "media-detail", |
| 8387 |
entityId: "media", |
| 8388 |
mediaId: args.mediaId, |
| 8389 |
mediaTitle: args.mediaTitle ?? `#${args.mediaId}` |
| 8390 |
}; |
| 8391 |
if (activeState) { |
| 8392 |
navigate(activeState, route); |
| 8393 |
return; |
| 8394 |
} |
| 8395 |
pendingRoute = route; |
| 8396 |
const desktop = window.wp?.desktop; |
| 8397 |
desktop?.openWindow?.(WINDOW_ID, { source: "my-wordpress/open-media" }); |
| 8398 |
} |
| 8399 |
const desktopGlobal = window.wp?.desktop; |
| 8400 |
if (desktopGlobal) { |
| 8401 |
const pending = desktopGlobal.myWordpress?.__pendingKinds; |
| 8402 |
if (Array.isArray(pending)) { |
| 8403 |
for (const entry of pending) { |
| 8404 |
try { |
| 8405 |
entry.slot.unregister = registerEntityKind( |
| 8406 |
entry.kind, |
| 8407 |
entry.renderer |
| 8408 |
); |
| 8409 |
} catch (err) { |
| 8410 |
console.error( |
| 8411 |
`[my-wordpress] queued registerEntityKind('${entry.kind}') failed:`, |
| 8412 |
err |
| 8413 |
); |
| 8414 |
} |
| 8415 |
} |
| 8416 |
pending.length = 0; |
| 8417 |
} |
| 8418 |
desktopGlobal.myWordpress = { |
| 8419 |
openDetail, |
| 8420 |
openMedia, |
| 8421 |
registerEntityKind |
| 8422 |
}; |
| 8423 |
} |
| 8424 |
})(); |
| 8425 |
|