1// Generated from src/app/image-generator/conversation-directory.js. Do not edit directly. 2
3// src/app/loading-state.js 4var SKELETON_WATERFALL_RATIOS = Object.freeze(["4 / 5", "1 / 1", "3 / 4", "4 / 5", "2 / 3", "1 / 1"]); 5var CONTENT_SKELETON_COUNTS = Object.freeze({ cards: 6, list: 6, text: 7 }); 6function createContentSkeleton(document, { layout = "cards", count, ratio = "" } = {}) { 7 const shape = ["cards", "list", "text"].includes(layout) ? layout : "cards"; 8 const skeleton = document.createElement("div"); 9 skeleton.className = `ui-loading-skeleton content-skeleton content-skeleton--${shape}`; 10 skeleton.setAttribute("aria-hidden", "true"); 11 if (shape === "cards" && ratio) skeleton.style.setProperty("--content-skeleton-ratio", String(ratio)); 12 const total = Math.max(1, Number(count) || CONTENT_SKELETON_COUNTS[shape]); 13 for (let index = 0; index < total; index += 1) { 14 if (shape === "text") { 15 skeleton.append(document.createElement("b")); 16 continue; 17 } 18 const item = document.createElement("span"); 19 item.className = "content-skeleton-item"; 20 item.append(document.createElement("i"), document.createElement("b"), document.createElement("b")); 21 skeleton.append(item); 22 } 23 return skeleton; 24} 25
26// src/app/image-generator/conversation-directory.js 27function createConversationDirectory({ 28 document, 29 window, 30 state, 31 host, 32 apiClient, 33 t, 34 currentId = () => "", 35 open = () => { 36 }, 37 allWorksHref = "/image-generator/general", 38 notify = () => { 39 }, 40 previewLegacy = () => { 41 }, 42 metadataChanged = () => { 43 }, 44 pendingConversations = () => [] 45}) { 46 const accountScope = () => String(state.me?.user_id || state.me?.id || state.me?.email || ""); 47 const active = () => state.page === "image-generator" && state.generatedImageMode === "general"; 48 const localePrefix = () => window.location.pathname.match(/^\/(en|zh-hant)(?:\/|$)/)?.[0]?.replace(/\/$/, "") || ""; 49 let account = "", epoch = 0, rows = [], cursor = "", query = "", archived = false, legacy = false; 50 let loading = false, loaded = false, error = "", entered = false; 51 let pane, body, list, search, status, more, archiveToggle, legacyToggle, allWorks; 52 let debounce = 0, refreshTimer = 0, invalidationTimer = 0, lastRead = 0; 53 const nodes = /* @__PURE__ */ new Map(), pending = /* @__PURE__ */ new Set(); 54 const button = (label, handler, className = "") => { 55 const el = document.createElement("button"); 56 el.type = "button"; 57 el.textContent = t(label); 58 el.className = className; 59 el.addEventListener("click", handler); 60 return el; 61 }; 62 function restart() { 63 epoch++; 64 loading = loaded = false; 65 rows = []; 66 cursor = ""; 67 } 68 function mount() { 69 if (pane) return; 70 pane = document.createElement("div"); 71 pane.className = "image-conversation-directory"; 72 pane.id = "imageConversationDirectory"; 73 pane.setAttribute("aria-label", t("ä½å¾è®°å½")); 74 const head = document.createElement("div"); 75 head.className = "image-conversation-directory-head"; 76 search = document.createElement("input"); 77 search.type = "search"; 78 search.placeholder = t("æç´¢ä½å¾è®°å½"); 79 search.setAttribute("aria-label", t("æç´¢å¯¹è¯æ é¢")); 80 search.maxLength = 160; 81 search.addEventListener("input", () => { 82 query = search.value.trim(); 83 restart(); 84 window.clearTimeout(debounce); 85 debounce = window.setTimeout(() => void load(), 250); 86 }); 87 archiveToggle = button("已彿¡£", () => { 88 archived = !archived; 89 legacy = false; 90 legacyToggle.setAttribute("aria-pressed", "false"); 91 archiveToggle.setAttribute("aria-pressed", String(archived)); 92 restart(); 93 void load(); 94 }); 95 archiveToggle.setAttribute("aria-pressed", "false"); 96 legacyToggle = button("è¾æ©è®°å½", () => { 97 legacy = !legacy; 98 archived = false; 99 archiveToggle.setAttribute("aria-pressed", "false"); 100 legacyToggle.setAttribute("aria-pressed", String(legacy)); 101 restart(); 102 void load(); 103 }); 104 legacyToggle.setAttribute("aria-pressed", "false"); 105 legacyToggle.hidden = !apiClient.getLegacyImageConversations; 106 const filters = document.createElement("div"); 107 filters.className = "image-conversation-directory-tools"; 108 filters.append(archiveToggle, legacyToggle); 109 head.append(search, filters); 110 body = document.createElement("div"); 111 body.className = "image-conversation-directory-body"; 112 allWorks = document.createElement("a"); 113 allWorks.className = "image-conversation-directory-all"; 114 allWorks.textContent = t("å ¨é¨ä½å"); 115 status = document.createElement("p"); 116 status.className = "image-conversation-directory-status"; 117 status.setAttribute("role", "status"); 118 list = document.createElement("div"); 119 list.className = "image-conversation-directory-list"; 120 more = button("å è½½æ´å¤", () => void load(Boolean(cursor)), "image-conversation-directory-more"); 121 body.append(allWorks, status, list, more); 122 pane.append(head, body); 123 host.append(pane); 124 } 125 async function load(append = false) { 126 if (!account || !active() || loading) return; 127 const stamp = ++epoch, owner = account; 128 loading = true; 129 error = ""; 130 render(); 131 try { 132 const fetch = legacy ? apiClient.getLegacyImageConversations : apiClient.getImageConversations; 133 const data = await fetch({ limit: 30, q: query, archived: archived ? "1" : "0", ...append && cursor ? { cursor } : {} }); 134 if (stamp !== epoch || owner !== accountScope()) return; 135 const unique = new Map((append ? rows : []).map((row) => [row.id, row])); 136 for (const row of data.conversations || []) unique.set(row.id, row); 137 rows = [...unique.values()]; 138 cursor = data.nextCursor || ""; 139 loaded = true; 140 lastRead = Date.now(); 141 } catch { 142 if (stamp === epoch && owner === accountScope()) error = t("ä½å¾è®°å½ææ¶æ æ³å è½½ï¼è¯·éè¯ã"); 143 } finally { 144 if (stamp === epoch && owner === accountScope()) { 145 loading = false;
146 render(); 147 window.clearTimeout(refreshTimer); 148 refreshTimer = window.setTimeout(() => { 149 if (active() && document.visibilityState !== "hidden" && rows.length <= 30 && !pending.size) void load(); 150 }, 3e4); 151 } 152 } 153 } 154 async function mutate(row, patch, controls) { 155 if (pending.has(row.id)) return; 156 const owner = account, stamp = epoch; 157 pending.add(row.id); 158 controls.forEach((el) => { 159 el.disabled = true; 160 }); 161 try { 162 const result = await apiClient.updateImageConversation(row.id, { ...patch, revision: row.revision }); 163 if (owner !== accountScope() || account !== owner) return; 164 Object.assign(row, result.conversation || result); 165 const live = rows.find((item) => item.id === row.id); 166 if (live && live !== row) Object.assign(live, row); 167 metadataChanged(row); 168 if (Object.hasOwn(patch, "archived") && row.archived !== archived) rows = rows.filter((item) => item.id !== row.id); 169 const restoreFocus = nodes.get(row.id)?.root.contains(document.activeElement); 170 nodes.get(row.id)?.root.remove(); 171 nodes.delete(row.id); 172 render(); 173 if (restoreFocus) (nodes.get(row.id)?.link || search).focus(); 174 } catch { 175 if (owner !== accountScope()) return; 176 notify(t("ä¿®æ¹ç»æææªç¡®è®¤ï¼æ£å¨éæ°è¯»åè®°å½ã")); 177 if (stamp === epoch) await load(); 178 } finally { 179 if (owner === account) { 180 pending.delete(row.id); 181 controls.forEach((el) => { 182 el.disabled = false; 183 }); 184 } 185 } 186 } 187 async function continueLegacy(row, control) { 188 if (pending.has(row.id)) return; 189 const owner = account; 190 const receiptKey = `xxd:image-conversation:legacy:${owner}:${row.id}`; 191 let id; 192 try { 193 id = window.localStorage.getItem(receiptKey); 194 if (!/^[0-9a-f-]{36}$/i.test(id || "")) { 195 id = window.crypto.randomUUID(); 196 window.localStorage.setItem(receiptKey, id); 197 } 198 } catch { 199 notify(t("æ æ³ä¿åæ¢å¤åæ®ï¼è¯·å 许æ¬å°åå¨åéè¯ã")); 200 return; 201 } 202 pending.add(row.id); 203 control.disabled = true; 204 try { 205 const result = await apiClient.attachImageConversationTask(id, row.taskId); 206 if (owner !== accountScope()) return; 207 rows = rows.filter((item) => item.id !== row.id); 208 render(); 209 open(result.id || id); 210 } catch (error2) { 211 if (owner !== accountScope()) return; 212 if (error2?.payload?.conversationId) { 213 rows = rows.filter((item) => item.id !== row.id); 214 render(); 215 open(error2.payload.conversationId); 216 } else notify(t("è®°å½æ¢å¤ç»æå°æªç¡®è®¤ï¼è¯·å次ç¹å»ç»§ç»æ ¸å¯¹ï¼ä¸ä¼èªå¨çæã")); 217 } finally { 218 if (owner === account) { 219 pending.delete(row.id); 220 control.disabled = false; 221 } 222 } 223 } 224 function rowNode(row) { 225 const root = document.createElement("article"); 226 root.className = "image-conversation-directory-row"; 227 root.dataset.conversationId = row.id; 228 const link = row.legacy ? button("", () => previewLegacy(row.taskId)) : document.createElement("a"); 229 if (!row.legacy) { 230 link.href = `${localePrefix()}/image-generator/c/${row.id}`; 231 link.addEventListener("click", (event) => { 232 if (event.button || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return; 233 event.preventDefault(); 234 open(row.id); 235 }); 236 } 237 link.className = "image-conversation-directory-link"; 238 const name = document.createElement("span"); 239 name.className = "image-conversation-directory-title"; 240 name.setAttribute("data-locale-original", ""); 241 const note = document.createElement("span"); 242 note.className = "image-conversation-directory-note"; 243 link.append(name, note); 244 if (row.legacy) { 245 const resume = button("ç»§ç»", () => void continueLegacy(row, resume)); 246 resume.title = t("ç»§ç»è¿æ¬¡åä½"); 247 root.classList.add("is-legacy"); 248 root.append(link, resume); 249 return { root, link, name, note, row }; 250 } 251 if (row.optimistic) { 252 root.append(link); 253 return { root, link, name, note, row }; 254 } 255 const menu = document.createElement("details"); 256 menu.className = "image-conversation-directory-menu"; 257 const summary = document.createElement("summary"); 258 summary.textContent = "â¦"; 259 summary.setAttribute("aria-label", t("å¯¹è¯æä½")); 260 const edit = button("éå½å", () => { 261 menu.open = false;
262 if (root.querySelector("form")) return; 263 const form = document.createElement("form"); 264 form.className = "image-conversation-directory-rename"; 265 const input = document.createElement("input"); 266 input.value = row.title; 267 input.maxLength = 160; 268 input.setAttribute("aria-label", t("å¯¹è¯æ é¢")); 269 const cancel = button("åæ¶", () => { 270 form.remove(); 271 summary.focus(); 272 }, "ui-dialog-cancel"); 273 const save = button("ä¿å", () => { 274 }); 275 save.type = "submit"; 276 form.addEventListener("submit", (event) => { 277 event.preventDefault(); 278 if (input.value.trim()) void mutate(row, { title: input.value.trim() }, [input, save, cancel]); 279 else input.focus(); 280 }); 281 form.append(input, cancel, save); 282 root.append(form); 283 input.focus(); 284 input.select(); 285 }); 286 const archive = button(row.archived ? "æ¢å¤è®°å½" : "彿¡£", () => void mutate(row, { archived: !row.archived }, [archive, edit])); 287 menu.append(summary, edit, archive); 288 root.append(link, menu); 289 return { root, link, name, note, row }; 290 } 291 function render() { 292 if (!pane) return; 293 allWorks.href = `${localePrefix()}${allWorksHref}`; 294 allWorks.setAttribute("aria-current", active() ? "page" : "false"); 295 const temporary = !archived && !legacy ? pendingConversations().filter((row) => !rows.some((item) => item.id === row.id) && (!query || row.title.includes(query))) : []; 296 const visibleRows = [...temporary, ...rows]; 297 status.textContent = error || (loading && !visibleRows.length ? t("æ£å¨å è½½ä½å¾è®°å½â¦") : !visibleRows.length ? t(query ? "没æå¹é ç对è¯" : legacy ? "ææ è¾æ©è®°å½ã" : archived ? "ææ å½æ¡£è®°å½" : "馿¬¡åéåï¼åä½å¯¹è¯ä¼åºç°å¨è¿éã") : ""); 298 status.hidden = !status.textContent; 299 const firstPagePending = loading && !visibleRows.length && !error; 300 status.classList.toggle("sr-only", firstPagePending); 301 if (firstPagePending && !status.nextElementSibling?.classList?.contains("content-skeleton")) { 302 status.after(createContentSkeleton(document, { layout: "list", count: 6 })); 303 } else if (!firstPagePending && status.nextElementSibling?.classList?.contains("content-skeleton")) { 304 status.nextElementSibling.remove(); 305 } 306 more.hidden = !cursor && !error; 307 more.disabled = loading; 308 more.textContent = t(error ? "éè¯" : "å è½½æ´å¤"); 309 const wanted = new Set(visibleRows.map((row) => row.id)); 310 for (const [id, node] of nodes) if (!wanted.has(id)) { 311 node.root.remove(); 312 nodes.delete(id); 313 } 314 const headers = new Map([...list.querySelectorAll(".image-conversation-directory-date")].map((el) => [el.dataset.group, el])); 315 const ordered = []; 316 let previousGroup = ""; 317 for (const row of visibleRows) { 318 let node = nodes.get(row.id); 319 if (!node) { 320 node = rowNode(row); 321 nodes.set(row.id, node); 322 } 323 if (node.row.optimistic && !row.optimistic) { 324 node.root.remove(); 325 node = rowNode(row); 326 nodes.set(row.id, node); 327 } 328 Object.assign(node.row, row); 329 node.name.textContent = row.title || t("æªå½ååä½"); 330 node.link.title = node.name.textContent; 331 const progress = row.optimistic ? t("æ£å¨ç¡®è®¤æäº¤") : row.activeCount ? `${t("çæä¸")} · ${row.activeCount}` : `${row.taskCount} ${t("è½®ä½å¾")}`; 332 node.note.textContent = [ 333 progress,
334 row.attentionCount ? t("æä»»å¡éå¤ç") : "", 335 new Date(row.lastActivityAt).toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }) 336 ].filter(Boolean).join(" · "); 337 node.root.classList.toggle("is-running", Boolean(row.activeCount && !row.optimistic)); 338 node.link.setAttribute("aria-current", row.id === currentId() ? "page" : "false"); 339 const date = new Date(row.lastActivityAt), today = /* @__PURE__ */ new Date(); 340 today.setHours(0, 0, 0, 0); 341 const yesterday = new Date(today); 342 yesterday.setDate(yesterday.getDate() - 1); 343 const group = date >= today ? "ä»å¤©" : date >= yesterday ? "æ¨å¤©" : "æ´æ©"; 344 if (group !== previousGroup) { 345 const label = headers.get(group) || document.createElement("h3"); 346 label.className = "image-conversation-directory-date"; 347 label.dataset.group = group; 348 label.textContent = t(group); 349 ordered.push(label); 350 previousGroup = group; 351 } 352 ordered.push(node.root); 353 } 354 const keep = new Set(ordered); 355 for (const child of [...list.children]) if (!keep.has(child)) child.remove(); 356 ordered.forEach((child, index) => { 357 if (list.children[index] !== child) list.insertBefore(child, list.children[index] || null); 358 }); 359 } 360 function sync() { 361 const next = accountScope(); 362 if (next !== account) { 363 account = next; 364 restart(); 365 query = error = ""; 366 entered = false; 367 pending.clear(); 368 nodes.clear(); 369 list?.replaceChildren(); 370 if (search) search.value = ""; 371 archived = legacy = false; 372 legacyToggle?.setAttribute("aria-pressed", "false"); 373 archiveToggle?.setAttribute("aria-pressed", "false"); 374 } 375 if (!host || !apiClient?.getImageConversations || !account) { 376 if (pane) pane.hidden = true; 377 return; 378 } 379 mount(); 380 pane.hidden = !active(); 381 if (!active()) { 382 if (entered) { 383 epoch++; 384 loading = false; 385 window.clearTimeout(refreshTimer); 386 } 387 entered = false; 388 return; 389 } 390 if (!entered) { 391 entered = true; 392 if (!loaded || rows.length <= 30 && Date.now() - lastRead > 3e4) void load(); 393 } 394 render(); 395 } 396 window.addEventListener("focus", () => { 397 if (active() && pane && Date.now() - lastRead > 3e4 && rows.length <= 30) void load(); 398 }); 399 return { 400 sync, 401 isOpen: () => Boolean(pane && !pane.hidden && active()), 402 invalidate: () => { 403 if (invalidationTimer) return; 404 invalidationTimer = window.setTimeout(() => { 405 invalidationTimer = 0; 406 lastRead = 0; 407 if (active() && rows.length <= 30 && !pending.size) void load(); 408 }, 500); 409 } 410 }; 411} 412export { 413 createConversationDirectory 414};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.