@heycys/gallery 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +1 -1
- package/browser/editor/page/annotate/index.mjs +3 -2
- package/browser/editor/page/board/index.mjs +2 -1
- package/browser/editor/page/board/settings.mjs +2 -1
- package/browser/editor/page/core.mjs +3 -2
- package/browser/editor/page/custom-page.mjs +4 -3
- package/browser/editor/page/editor.mjs +13 -7
- package/browser/editor/page/emoji-picker/support.mjs +3 -2
- package/browser/editor/page/links/index.mjs +1 -12
- package/browser/editor/page/links/pages.mjs +3 -2
- package/browser/editor/page/outline/folds.mjs +3 -0
- package/browser/editor/page/post.mjs +2 -1
- package/browser/editor/page/present-links.mjs +4 -3
- package/browser/editor/page/present-shell.mjs +3 -58
- package/browser/editor/page/session.mjs +3 -37
- package/browser/editor/page/storage.mjs +5 -5
- package/browser/shared/component-catalog.mjs +2 -2
- package/browser/shared/drafts.mjs +26 -51
- package/browser/shared/emoji/icon-value.mjs +2 -1
- package/browser/shared/extension-manifest.mjs +90 -0
- package/browser/shared/extensions.mjs +26 -16
- package/browser/shared/http.mjs +1 -1
- package/browser/shared/i18n/en.mjs +46 -15
- package/browser/shared/media.mjs +2 -1
- package/browser/shared/outline.mjs +11 -51
- package/browser/shared/page-address.mjs +8 -15
- package/browser/shared/paths.mjs +2 -1
- package/browser/shared/preferences.mjs +3 -1
- package/browser/shared/preview-server.mjs +51 -63
- package/browser/shared/service-location.mjs +18 -0
- package/browser/shared/session.mjs +3 -6
- package/browser/shared/workspace-address.mjs +22 -0
- package/browser/shared/workspace-columns.mjs +1 -2
- package/browser/shared/workspace-registry.mjs +6 -40
- package/browser/shared/workspace.mjs +3 -6
- package/browser/shared/writing-preferences.mjs +6 -4
- package/dist/{chunk-5MEXZ2UF.mjs → chunk-225ALKYI.mjs} +1 -1
- package/dist/{chunk-7N2LPG33.mjs → chunk-2HS7ZBPU.mjs} +1 -1
- package/dist/chunk-3F57LEN5.mjs +2 -0
- package/dist/{chunk-MKTX5PGV.mjs → chunk-43B75J5T.mjs} +1 -1
- package/dist/{chunk-VKSRM7EY.mjs → chunk-4B2TB5K2.mjs} +1 -1
- package/dist/{chunk-LV24RY2E.mjs → chunk-4K5KC6V2.mjs} +1 -1
- package/dist/chunk-5GK7HS62.mjs +2 -0
- package/dist/chunk-6GV3CBB2.mjs +2 -0
- package/dist/{chunk-TOE6DPLH.mjs → chunk-6NLXUKAY.mjs} +1 -1
- package/dist/{chunk-ZCP7BTOZ.mjs → chunk-6QBHU4DF.mjs} +1 -1
- package/dist/{chunk-QXVNUASA.mjs → chunk-6QWQXHAV.mjs} +1 -1
- package/dist/chunk-6XD6U3QA.mjs +5 -0
- package/dist/{chunk-DKR7LFIY.mjs → chunk-6Y3HYOBM.mjs} +1 -1
- package/dist/{chunk-36FIRJIF.mjs → chunk-742D2WWJ.mjs} +2 -2
- package/dist/{chunk-7M7UPWU6.mjs → chunk-7EFHNQ5B.mjs} +1 -1
- package/dist/{chunk-2GKGEVMR.mjs → chunk-7UZUQQ7Q.mjs} +25 -24
- package/dist/{chunk-VPVOAJ7X.mjs → chunk-AGDILNWB.mjs} +1 -1
- package/dist/{chunk-5IIH335D.mjs → chunk-ALZ6ZENZ.mjs} +1 -1
- package/dist/{chunk-G4YP7GVC.mjs → chunk-BFJGHSQN.mjs} +1 -1
- package/dist/{chunk-AMDGVDNV.mjs → chunk-BQNTTFPL.mjs} +1 -1
- package/dist/chunk-BX5AKINW.mjs +15 -0
- package/dist/{chunk-PAFSFVYB.mjs → chunk-BYNPEUDS.mjs} +1 -1
- package/dist/{chunk-74RPP2M2.mjs → chunk-C564NU2H.mjs} +1 -1
- package/dist/{chunk-DH6GMDNN.mjs → chunk-CRDG6EF2.mjs} +1 -1
- package/dist/{chunk-7XDYK3UC.mjs → chunk-E7656LR7.mjs} +1 -1
- package/dist/{chunk-KFZBYDIS.mjs → chunk-ECZBLQKK.mjs} +1 -1
- package/dist/{chunk-ZEY2RLBT.mjs → chunk-ERLAIBOA.mjs} +2 -2
- package/dist/{chunk-YFJU3O7G.mjs → chunk-FJLM7YE7.mjs} +1 -1
- package/dist/{chunk-AFGUR7SY.mjs → chunk-FNCCVGKX.mjs} +1 -1
- package/dist/{chunk-VNUWREXU.mjs → chunk-FOJ3K5Z3.mjs} +1 -1
- package/dist/{chunk-WCWYNFDF.mjs → chunk-FQ72LZHB.mjs} +1 -1
- package/dist/chunk-G7N7YFVD.mjs +2 -0
- package/dist/chunk-GIRXJBGC.mjs +2 -0
- package/dist/{chunk-VA3KAQZD.mjs → chunk-GKOBB6DH.mjs} +1 -1
- package/dist/chunk-GMJD7ID3.mjs +2 -0
- package/dist/chunk-GQJMVSSN.mjs +2 -0
- package/dist/{chunk-QZ35UAB4.mjs → chunk-GRAXKKG4.mjs} +1 -1
- package/dist/{chunk-SAHS56QM.mjs → chunk-HX4DLE3J.mjs} +1 -1
- package/dist/{chunk-2RKIHJI2.mjs → chunk-HYMEEXKN.mjs} +1 -1
- package/dist/{chunk-QK3VISCA.mjs → chunk-IA4PSIMD.mjs} +1 -1
- package/dist/chunk-IS7N3N6Q.mjs +168 -0
- package/dist/{chunk-CZ5KJSGK.mjs → chunk-JJNHKGTD.mjs} +1 -1
- package/dist/chunk-JPG2LBQM.mjs +11 -0
- package/dist/chunk-JYK5WJPM.mjs +2 -0
- package/dist/{chunk-LE44MD3O.mjs → chunk-KLQP46Y3.mjs} +1 -1
- package/dist/chunk-L55NOXBE.mjs +4 -0
- package/dist/{chunk-FXQBADMN.mjs → chunk-LCB6MARS.mjs} +1 -1
- package/dist/{chunk-SOD23RGL.mjs → chunk-LJLVCU6W.mjs} +8 -8
- package/dist/{chunk-NAGMRVW4.mjs → chunk-LKX55H76.mjs} +1 -1
- package/dist/chunk-LMUZQK5O.mjs +2 -0
- package/dist/{chunk-CG3UHDKH.mjs → chunk-M4NRDYI5.mjs} +1 -1
- package/dist/{chunk-HM4E43VM.mjs → chunk-MAFIGVXY.mjs} +1 -1
- package/dist/chunk-NLDF35P5.mjs +2 -0
- package/dist/{chunk-3OFXMN6N.mjs → chunk-NMF3IPYA.mjs} +1 -1
- package/dist/chunk-ONKC4BCC.mjs +2 -0
- package/dist/{chunk-CPS4SRPF.mjs → chunk-OU6ZZVBX.mjs} +2 -2
- package/dist/{chunk-NWRZXELO.mjs → chunk-PAREWQGB.mjs} +1 -1
- package/dist/{chunk-BYSF5UMM.mjs → chunk-PKVK6WFC.mjs} +2 -2
- package/dist/{chunk-4AMTKWDO.mjs → chunk-PUOQ65NC.mjs} +1 -1
- package/dist/{chunk-L7W624FD.mjs → chunk-QJAVCTY7.mjs} +1 -1
- package/dist/{chunk-6MPKCH3P.mjs → chunk-QNECWMGR.mjs} +1 -1
- package/dist/{chunk-HBAUKKQH.mjs → chunk-QP7BOHE4.mjs} +1 -1
- package/dist/chunk-QVGOPHOF.mjs +2 -0
- package/dist/{chunk-NFABH3QW.mjs → chunk-RRKJMHNX.mjs} +2 -2
- package/dist/chunk-S3SX3COT.mjs +20 -0
- package/dist/chunk-SQ3FQCCN.mjs +8 -0
- package/dist/{chunk-ZO7SFB2E.mjs → chunk-SQ6Y2ZMA.mjs} +2 -2
- package/dist/{chunk-3A5I2GJK.mjs → chunk-T4Y2ZSVX.mjs} +1 -1
- package/dist/{chunk-VG5JEIUQ.mjs → chunk-TKWEX4FD.mjs} +2 -2
- package/dist/{chunk-CCSGFCLZ.mjs → chunk-U3SSPL4K.mjs} +1 -1
- package/dist/{chunk-DHB7UZ7F.mjs → chunk-U5XL7D6A.mjs} +1 -1
- package/dist/{chunk-JXLSHVJ2.mjs → chunk-UMHUSOPJ.mjs} +1 -1
- package/dist/chunk-UTF7GBQU.mjs +4 -0
- package/dist/chunk-UTMAY3WK.mjs +2 -0
- package/dist/{chunk-S2Y7VI4H.mjs → chunk-V4ZHRALW.mjs} +2 -2
- package/dist/{chunk-GWOYMENX.mjs → chunk-V6XZ4VIR.mjs} +1 -1
- package/dist/{chunk-WTEGUPFG.mjs → chunk-VRPVVMRB.mjs} +1 -1
- package/dist/{chunk-XKOHV4H6.mjs → chunk-W5NPH5UA.mjs} +1 -1
- package/dist/{chunk-VGTMLQWZ.mjs → chunk-WD4U6XXT.mjs} +1 -1
- package/dist/{chunk-4DNHHJSL.mjs → chunk-XBNFXWAW.mjs} +1 -1
- package/dist/chunk-XZV72SQ4.mjs +2 -0
- package/dist/{chunk-QJ3TFJLQ.mjs → chunk-Y34NR5JL.mjs} +1 -1
- package/dist/{chunk-YBB5LWZK.mjs → chunk-YFJB534W.mjs} +1 -1
- package/dist/{chunk-V5JRSGZF.mjs → chunk-YN37ZDLW.mjs} +1 -1
- package/dist/chunk-Z6OKLRHC.mjs +2 -0
- package/dist/chunk-ZLS2FRS6.mjs +3 -0
- package/dist/{chunk-JI6T3A4Y.mjs → chunk-ZMHUDYLV.mjs} +1 -1
- package/dist/{chunk-KH24LWAT.mjs → chunk-ZTHICSSF.mjs} +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/server.mjs +1 -1
- package/dist/worker.mjs +1 -1
- package/kit/math.js +1 -1
- package/kit/runtime.js +13 -2
- package/kit/state.js +2 -0
- package/kit/ui.js +5 -1
- package/package.json +1 -1
- package/release.json +7 -7
- package/skill/SKILL.md +13 -13
- package/skill/references/building-blocks.md +1 -1
- package/skill/references/extensions.md +45 -9
- package/web/assets/index-D8P8uhDW.js +15 -0
- package/web/assets/{index-xP0uw3a0.css → index-FLb9jCfl.css} +1 -1
- package/web/index.html +2 -2
- package/dist/chunk-46ACXOU5.mjs +0 -2
- package/dist/chunk-5DPABPBI.mjs +0 -2
- package/dist/chunk-5L26JUYA.mjs +0 -2
- package/dist/chunk-AEMVZYZQ.mjs +0 -166
- package/dist/chunk-ASAXY2DJ.mjs +0 -2
- package/dist/chunk-DAMVDXR3.mjs +0 -2
- package/dist/chunk-DLFZJJEE.mjs +0 -2
- package/dist/chunk-EKNHM3P4.mjs +0 -2
- package/dist/chunk-FCK6NXON.mjs +0 -3
- package/dist/chunk-FX42XU6J.mjs +0 -2
- package/dist/chunk-HATA2W7X.mjs +0 -2
- package/dist/chunk-JJNRIFND.mjs +0 -8
- package/dist/chunk-JL6R4FG4.mjs +0 -4
- package/dist/chunk-KUILYI7H.mjs +0 -4
- package/dist/chunk-NIW7F7LU.mjs +0 -4
- package/dist/chunk-OU3S466R.mjs +0 -20
- package/dist/chunk-QCEATQC4.mjs +0 -2
- package/dist/chunk-TDWJKOLN.mjs +0 -2
- package/dist/chunk-UGO5N3EI.mjs +0 -15
- package/dist/chunk-XMKHCUI6.mjs +0 -15
- package/dist/chunk-XQRUG4G3.mjs +0 -2
- package/dist/chunk-YPPJVDQI.mjs +0 -2
- package/dist/chunk-ZANMKAWS.mjs +0 -2
- package/web/assets/index-BHC5ZOMh.js +0 -15
- /package/dist/{chunk-N3DSBH5S.mjs → chunk-A55HN5OK.mjs} +0 -0
- /package/dist/{chunk-KNZJT4ET.mjs → chunk-QAIHRH3I.mjs} +0 -0
- /package/dist/{chunk-ZW2A6GBW.mjs → chunk-QR6A54KJ.mjs} +0 -0
- /package/dist/{chunk-74UW7QSL.mjs → chunk-UH4SIXTR.mjs} +0 -0
- /package/dist/{chunk-EKGYYOSP.mjs → chunk-W7XHE4CT.mjs} +0 -0
- /package/dist/{chunk-5MMN3N5J.mjs → chunk-YEY3RSFQ.mjs} +0 -0
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Third-party notices
|
|
2
2
|
|
|
3
|
-
@heycys/gallery 0.2.
|
|
3
|
+
@heycys/gallery 0.2.3 carries the following code and data of others, bundled into dist/ and web/, built into kit/, or as files in vendor/ and browser/.
|
|
4
4
|
|
|
5
5
|
## @asamuzakjp/css-color 5.1.11
|
|
6
6
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { documentKey, workspaceAddress } from "../../../shared/workspace-address.mjs";
|
|
1
2
|
import { CARD, RAIL, cardTops, markerLeft, markerTop, newAnnotationId, sameWords, squash, stackItems, tidy } from "../../../shared/annotations.mjs";
|
|
2
3
|
import { t } from "../../../shared/i18n/index.mjs";
|
|
3
4
|
import { SHARES_HEIGHT, standsAsPicture } from "../../components/registry.mjs";
|
|
@@ -106,7 +107,7 @@ const NOTICE_ROOM = 70;
|
|
|
106
107
|
const IN_ROW = [...SHARES_HEIGHT].map((kind) => `[data-ge-unit-kind="${kind}"] > *`).join();
|
|
107
108
|
|
|
108
109
|
export function createAnnotate({ slug, token, isEditing, picked, unpick = () => {}, wholePage = () => [], crossTexts, idFor, changed = () => {}, removed = () => {}, say }) {
|
|
109
|
-
const stashKey = `gallery-edit:${slug}:notes`; // what could not be saved (offline), sent by the next page that reads them
|
|
110
|
+
const stashKey = `gallery-edit:${documentKey(slug)}:notes`; // what could not be saved (offline), sent by the next page that reads them
|
|
110
111
|
let reading = null; // the first read from the server, under way or done (taken back when it failed)
|
|
111
112
|
let counted = false; // that read is in and what it brought found: the page can say how many it has
|
|
112
113
|
let items = new Map(); // id -> { id, created, updated, text, anchor } in page order, as the server last said, with the page's own changes
|
|
@@ -189,7 +190,7 @@ export function createAnnotate({ slug, token, isEditing, picked, unpick = () =>
|
|
|
189
190
|
chain = run.catch(() => {});
|
|
190
191
|
return run;
|
|
191
192
|
};
|
|
192
|
-
const fetchAll = () => fetch(`${ROUTE}?slug=${encodeURIComponent(slug)}
|
|
193
|
+
const fetchAll = () => fetch(workspaceAddress(`${ROUTE}?slug=${encodeURIComponent(slug)}`), { headers: { "X-Gallery-Token": token }, cache: "no-store" })
|
|
193
194
|
.then((response) => response.json()).catch(() => ({ ok: false, reason: "offline" }));
|
|
194
195
|
const wire = (item) => ({ id: item.id, created: item.created, updated: item.updated, text: item.text, anchor: item.anchor });
|
|
195
196
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { workspaceAddress } from "../../../shared/workspace-address.mjs";
|
|
1
2
|
import { GAP, GRID, cellAt, fitHeight, gridFor, moved, pushDown, sizeFor, spanOf } from "../../../shared/board-places.mjs";
|
|
2
3
|
import { cellsOf } from "../../../shared/card-size.mjs";
|
|
3
4
|
import { language, t } from "../../../shared/i18n/index.mjs";
|
|
@@ -793,7 +794,7 @@ export function startBoard({ map, reload, hosted = false }) {
|
|
|
793
794
|
menu.close();
|
|
794
795
|
let listed = [];
|
|
795
796
|
try {
|
|
796
|
-
listed = (await (await fetch(`/__gallery/pages/${slug}/library?language=${language}`)).json()).blocks ?? [];
|
|
797
|
+
listed = (await (await fetch(workspaceAddress(`/__gallery/pages/${slug}/library?language=${language}`))).json()).blocks ?? [];
|
|
797
798
|
} catch {
|
|
798
799
|
say(t("连不上本地服务"), "error");
|
|
799
800
|
return false;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { workspaceAddress } from "../../../shared/workspace-address.mjs";
|
|
1
2
|
import { Z } from "../../../shared/layers.mjs";
|
|
2
3
|
import { language, t } from "../../../shared/i18n/index.mjs";
|
|
3
4
|
import { createMenu } from "../menu.mjs";
|
|
@@ -59,7 +60,7 @@ export const formOpen = () => shown !== null;
|
|
|
59
60
|
/** The workspace's documents, by title: [{ slug, title }] (none where the outline cannot be read). */
|
|
60
61
|
async function documents() {
|
|
61
62
|
try {
|
|
62
|
-
const answer = await (await fetch("/__gallery/workspace/outline")).json();
|
|
63
|
+
const answer = await (await fetch(workspaceAddress("/__gallery/workspace/outline"))).json();
|
|
63
64
|
return Object.values(answer.reports ?? {}).filter((report) => !report.instructions && report.kind !== "link").map(({ slug, title }) => ({ slug, title: title || slug }))
|
|
64
65
|
.sort((a, b) => a.title.localeCompare(b.title, "zh-CN"));
|
|
65
66
|
} catch {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { localPath } from "../../shared/workspace-address.mjs";
|
|
1
2
|
import { escapeAttribute, escapeText, escapeTyped, unescapeText } from "../../shared/escape.mjs";
|
|
2
3
|
import { formatRange, isInkClass, isUnderlineClass, markValue } from "./marks.mjs";
|
|
3
4
|
import { ZWSP, inlineItems, spaceAround } from "../../shared/inline-spacing.mjs";
|
|
@@ -294,13 +295,13 @@ export function newMention(doc, { href, title, icon = null, drawn = null }, asse
|
|
|
294
295
|
export function mentionOf(chip) {
|
|
295
296
|
const img = chip.querySelector(`img.${MENTION_ICON}`);
|
|
296
297
|
const src = img?.getAttribute("data-ge-src") ?? img?.getAttribute("src") ?? "";
|
|
297
|
-
const served = /^\/__gallery\/edit\/asset\/([^/]+)\/(assets\/[^/?#]+)$/.exec(src);
|
|
298
|
+
const served = /^\/__gallery\/edit\/asset\/([^/]+)\/(assets\/[^/?#]+)$/.exec(localPath(src));
|
|
298
299
|
return {
|
|
299
300
|
href: chip.getAttribute("href") ?? "",
|
|
300
301
|
title: chip.textContent.replace(/\s+/g, " ").trim(),
|
|
301
302
|
icon: served ? served[2] : /^assets\/[^/]+$/.test(src) ? src : null,
|
|
302
303
|
slug: served ? decodeURIComponent(served[1]) : null,
|
|
303
|
-
drawn: src.startsWith("/__gallery/edit/icon?") ? src : null,
|
|
304
|
+
drawn: localPath(src).startsWith("/__gallery/edit/icon?") ? src : null,
|
|
304
305
|
};
|
|
305
306
|
}
|
|
306
307
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { localPath } from "../../shared/workspace-address.mjs";
|
|
1
2
|
import { startBoard } from "./board/index.mjs";
|
|
2
3
|
import { relayTo } from "./notices.mjs";
|
|
3
4
|
import { connectWorkbench } from "./session.mjs";
|
|
@@ -21,7 +22,7 @@ import { connectWorkbench } from "./session.mjs";
|
|
|
21
22
|
// document or another page, or a block's context.workspace.open: doc-open-request) and key. Opened on its own (not in a frame of the workbench), it has no
|
|
22
23
|
// session: its board edits all the same, and the live script reloads it.
|
|
23
24
|
|
|
24
|
-
const slug = decodeURIComponent(location.pathname.split("/")[2] ?? "");
|
|
25
|
+
const slug = decodeURIComponent(localPath(location.pathname).split("/")[2] ?? "");
|
|
25
26
|
const IDLE = { editing: false, pending: false, saving: false, offline: false, conflicts: 0, unfit: 0, changed: false };
|
|
26
27
|
// Nothing stands over a custom page as a modal.
|
|
27
28
|
const NO_MODALS = { open: () => [], listen() {}, closeTop: () => false };
|
|
@@ -94,8 +95,8 @@ if (session) {
|
|
|
94
95
|
window.open(url.href, "_blank", "noopener");
|
|
95
96
|
return;
|
|
96
97
|
}
|
|
97
|
-
const custom = /^\/pages\/([a-z0-9-]+)\/?$/.exec(url.pathname)?.[1];
|
|
98
|
-
const page = /^\/generated\/([a-z0-9-]+)\.html$/.exec(url.pathname)?.[1] ?? /^\/([a-z0-9-]+)\/(?:doc|report)\.html$/.exec(url.pathname)?.[1];
|
|
98
|
+
const custom = /^\/pages\/([a-z0-9-]+)\/?$/.exec(localPath(url.pathname))?.[1];
|
|
99
|
+
const page = /^\/generated\/([a-z0-9-]+)\.html$/.exec(localPath(url.pathname))?.[1] ?? /^\/([a-z0-9-]+)\/(?:doc|report)\.html$/.exec(localPath(url.pathname))?.[1];
|
|
99
100
|
if (custom) {
|
|
100
101
|
event.preventDefault();
|
|
101
102
|
session.open({ hash: `@${custom}` });
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { documentKey, workspaceStoragePrefix } from "../../shared/workspace-address.mjs";
|
|
2
|
+
import { workspaceAddress } from "../../shared/workspace-address.mjs";
|
|
1
3
|
// First: what the browser keeps under the names before 2026-10-28 carried to the new ones (the
|
|
2
4
|
// editor's gallery-edit:… keys, src/shared/renamed-storage.mjs), before any module below reads them.
|
|
3
5
|
import "../../shared/renamed-storage.mjs";
|
|
4
|
-
import { OUTER, can, captureLayout, editableAhead, firstHost, isHost, limitText, markFrames, markKinds, matchesSource, newHost, newMention, ownHTML, plainText, prepareBlock, splitBlock, textEnd, tidyEmptied, unitElement, withLeads } from "./core.mjs";
|
|
6
|
+
import { OUTER, can, captureLayout, editableAhead, firstByAttribute, firstHost, isHost, limitText, markFrames, markKinds, matchesSource, newHost, newMention, ownHTML, plainText, prepareBlock, splitBlock, textEnd, tidyEmptied, unitElement, withLeads } from "./core.mjs";
|
|
5
7
|
import { createBlocks } from "./blocks.mjs";
|
|
6
8
|
import { hiddenByFold, reveal } from "./folded.mjs";
|
|
7
9
|
import { createFlags } from "./flags.mjs";
|
|
@@ -61,7 +63,7 @@ import { decoded, fullAddress, isFileLink } from "../../shared/page-address.mjs"
|
|
|
61
63
|
import { goBackTo, placeIn, unmoved } from "../../shared/reading-place.mjs";
|
|
62
64
|
import { heldList, holdPages } from "./links/pages.mjs";
|
|
63
65
|
import { hideBlocks } from "./offered.mjs";
|
|
64
|
-
import {
|
|
66
|
+
import { storageOf } from "./storage.mjs";
|
|
65
67
|
import { createTodoWrites } from "./todo-writes.mjs";
|
|
66
68
|
import { language, t } from "../../shared/i18n/index.mjs";
|
|
67
69
|
|
|
@@ -131,7 +133,9 @@ let conflicts = null; // what the file refused, for the reader to decide (confli
|
|
|
131
133
|
let unsaveable = { blank: 0, unplaced: 0 }; // blocks the page cannot save as they are
|
|
132
134
|
let notice = null; // { text, time }: what a commit just said, to say again if the page reloads by itself
|
|
133
135
|
|
|
134
|
-
const
|
|
136
|
+
const storageScope = { prefix: workspaceStoragePrefix() };
|
|
137
|
+
const local = storageOf(() => localStorage, storageScope);
|
|
138
|
+
const session = storageOf(() => sessionStorage, storageScope);
|
|
135
139
|
|
|
136
140
|
// This tab, kept over reloads (sessionStorage). Each tab keeps what waits to be saved (the crash
|
|
137
141
|
// buffer, autosave.mjs) under its own id, so two tabs of a report never overwrite each other's; the
|
|
@@ -140,7 +144,7 @@ const session = storageOf(() => sessionStorage);
|
|
|
140
144
|
const newTab = () => Math.random().toString(36).slice(2, 10);
|
|
141
145
|
let TAB = session.get(keys.tab) ?? newTab();
|
|
142
146
|
session.set(keys.tab, TAB);
|
|
143
|
-
const channel = (() => { try { return new BroadcastChannel(`gallery-edit:${MAP.slug}`); } catch { return null; } })();
|
|
147
|
+
const channel = (() => { try { return new BroadcastChannel(`gallery-edit:${documentKey(MAP.slug)}`); } catch { return null; } })();
|
|
144
148
|
channel?.addEventListener("message", (event) => {
|
|
145
149
|
if (event.data?.type === "who") channel.postMessage({ type: "here", tab: TAB });
|
|
146
150
|
});
|
|
@@ -397,10 +401,12 @@ function enter() {
|
|
|
397
401
|
if (editing) return;
|
|
398
402
|
// Two passes: every block made editable (or not) as the page was served, then every block rebuilt
|
|
399
403
|
// (core.mjs editableAhead: a contenteditable set after blocks were rebuilt lays the page out again).
|
|
404
|
+
// Resolve the initial hosts in one scan; a query for each block repeatedly walks a long page.
|
|
405
|
+
const marked = firstByAttribute(document, "data-ge-block");
|
|
400
406
|
const nodes = new Map();
|
|
401
407
|
for (const block of MAP.blocks) {
|
|
402
408
|
if (HOSTS[block.kind]?.closed) continue;
|
|
403
|
-
const node =
|
|
409
|
+
const node = marked.get(String(block.id));
|
|
404
410
|
if (!node) continue;
|
|
405
411
|
if (HOSTS[block.kind]?.tex) node.setAttribute("contenteditable", "true"); // a display formula (components/math/)
|
|
406
412
|
else if (!matchesSource(node, block.inner)) {
|
|
@@ -1411,7 +1417,7 @@ async function goTo({ slug = null, block = null, hash = null }, href) {
|
|
|
1411
1417
|
if (block) jumpTo(block);
|
|
1412
1418
|
} else if (SOLO) window.open(href, "_blank", "noopener");
|
|
1413
1419
|
else if (window.parent !== window) workbench?.open(hash === null ? { target: slug, block } : { hash });
|
|
1414
|
-
else if (hash === null || (await leadsToPage(hash))) COMMANDS["prepare-close"]().then(() => location.assign(hash === null ? `/generated/${slug}.html${block ? `#${encodeURIComponent(block)}` : ""}` : href));
|
|
1420
|
+
else if (hash === null || (await leadsToPage(hash))) COMMANDS["prepare-close"]().then(() => location.assign(hash === null ? workspaceAddress( `/generated/${slug}.html${block ? `#${encodeURIComponent(block)}` : ""}` ) : href));
|
|
1415
1421
|
}
|
|
1416
1422
|
|
|
1417
1423
|
function connectParts() {
|
|
@@ -1500,7 +1506,7 @@ function connect() {
|
|
|
1500
1506
|
event.preventDefault();
|
|
1501
1507
|
if (page) {
|
|
1502
1508
|
if (window.parent !== window && !SOLO) workbench?.open({ hash: `@${page}` });
|
|
1503
|
-
else COMMANDS["prepare-close"]().then(() => location.assign(`/pages/${page}`));
|
|
1509
|
+
else COMMANDS["prepare-close"]().then(() => location.assign(workspaceAddress(`/pages/${page}`)));
|
|
1504
1510
|
} else goTo({ slug, block }, `/generated/${slug}.html${block ? `#${encodeURIComponent(block)}` : ""}`);
|
|
1505
1511
|
});
|
|
1506
1512
|
relayTo(workbench ? workbench.notice : null, workbench ? workbench.noticeRoom : null); // the workbench shows the page's notices, above the page's bottom panels
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { workspaceAddress } from "../../../shared/workspace-address.mjs";
|
|
1
2
|
import { Z } from "../../../shared/layers.mjs";
|
|
2
3
|
import { escapeMarkup } from "../../../shared/escape.mjs";
|
|
3
4
|
import { icon } from "./icons.mjs";
|
|
@@ -198,7 +199,7 @@ export function connectPicker({ slug, token, notify }) {
|
|
|
198
199
|
server = { slug, token };
|
|
199
200
|
say = notify;
|
|
200
201
|
setEmojiLibraryStorage({
|
|
201
|
-
loadEmojiLibrary: () => fetch(`/__gallery/edit/emoji-library`).then((response) => response.json()).then((answer) => answer.list ?? []).catch(() => []),
|
|
202
|
-
addEmoji: async ({ name, url }) => (await post("emoji-library", { name, url })).entry,
|
|
202
|
+
loadEmojiLibrary: () => fetch(workspaceAddress(`/__gallery/edit/emoji-library`)).then((response) => response.json()).then((answer) => (answer.list ?? []).map(entry => ({ ...entry, url: workspaceAddress(entry.url) }))).catch(() => []),
|
|
203
|
+
addEmoji: async ({ name, url }) => { const entry = (await post("emoji-library", { name, url })).entry; return { ...entry, url: workspaceAddress(entry.url) }; },
|
|
203
204
|
});
|
|
204
205
|
}
|
|
@@ -752,17 +752,6 @@ function opens(event) {
|
|
|
752
752
|
// workspace's page of one folder name, opened as any web address. A tab
|
|
753
753
|
// of its own opens with the click, the list read after, for only what the click itself opens does the
|
|
754
754
|
// browser leave behind the page.
|
|
755
|
-
async function followElsewhere(address, tab) {
|
|
756
|
-
const opened = tab ? window.open("", "_blank") : null;
|
|
757
|
-
const list = await readPages(use.own());
|
|
758
|
-
const page = list && placeIn(list, address);
|
|
759
|
-
const here = page && fullAddress(location.origin, page, list.links);
|
|
760
|
-
if (opened) {
|
|
761
|
-
opened.opener = null;
|
|
762
|
-
opened.location.replace(here ?? address);
|
|
763
|
-
} else if (page) use.go(page, here);
|
|
764
|
-
else openTab(address);
|
|
765
|
-
}
|
|
766
755
|
|
|
767
756
|
// Go where `href`, a link of this page that is no anchor, leads, read as its file reads it, so that
|
|
768
757
|
// doc.html beside it is this document: a document's address there, with `tab` in a tab of its own, a
|
|
@@ -771,7 +760,7 @@ async function followElsewhere(address, tab) {
|
|
|
771
760
|
function follow(href, tab) {
|
|
772
761
|
const address = fromFile(href, use.slug, location.origin);
|
|
773
762
|
if (!address) return;
|
|
774
|
-
if (elsewhere(address, location.origin)) return void
|
|
763
|
+
if (elsewhere(address, location.origin)) return void openTab(address);
|
|
775
764
|
const place = placeOf(address);
|
|
776
765
|
const file = referencedFile(href);
|
|
777
766
|
if (place && !tab) use.go(place, address);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { workspaceAddress } from "../../../shared/workspace-address.mjs";
|
|
1
2
|
import { iconUrl } from "../../../shared/emoji/icon-value.mjs";
|
|
2
3
|
import { escapeAttribute } from "../../../shared/escape.mjs";
|
|
3
4
|
import { addressOn, decoded, hashAddress } from "../../../shared/page-address.mjs";
|
|
@@ -41,7 +42,7 @@ let shown = () => null; // this page, as readPages takes it
|
|
|
41
42
|
* null when the list cannot be read (`webs`: the links of the sidebar's web links, which are no pages). `own`: this page, { slug, title } as it shows it.
|
|
42
43
|
*/
|
|
43
44
|
export async function readPages(own = null) {
|
|
44
|
-
const listed = await fetch(OUTLINE).then((answer) => answer.json()).catch(() => null);
|
|
45
|
+
const listed = await fetch(workspaceAddress(OUTLINE)).then((answer) => answer.json()).catch(() => null);
|
|
45
46
|
if (!listed?.reports) return null;
|
|
46
47
|
const titleOf = (slug) => (slug === own?.slug && own.title ? own.title : listed.reports[slug]?.title);
|
|
47
48
|
const pages = [];
|
|
@@ -80,7 +81,7 @@ export function heldList() {
|
|
|
80
81
|
|
|
81
82
|
/** Whether document `slug` is in the workspace's trash (not when the trash cannot be read). */
|
|
82
83
|
export async function trashed(slug) {
|
|
83
|
-
const listed = await fetch(TRASH).then((answer) => answer.json()).catch(() => null);
|
|
84
|
+
const listed = await fetch(workspaceAddress(TRASH)).then((answer) => answer.json()).catch(() => null);
|
|
84
85
|
return inTrash(listed?.entries ?? [], slug);
|
|
85
86
|
}
|
|
86
87
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// heading's block id, a tab's area and tab), the reports folded last kept and the oldest let go. The
|
|
4
4
|
// headings folded on the page (fold/) are kept the same way under a key of their own (`key`).
|
|
5
5
|
|
|
6
|
+
import { documentKey } from "../../../shared/workspace-address.mjs";
|
|
6
7
|
import { KEYS } from "../../../shared/renamed-storage.mjs";
|
|
7
8
|
|
|
8
9
|
export const FOLDS_KEY = KEYS.outlineFolds;
|
|
@@ -18,6 +19,7 @@ function read(storage, key) {
|
|
|
18
19
|
|
|
19
20
|
/** The keys folded in report `slug` (`storage`: { get, set }, page/storage.mjs), kept under `key`. */
|
|
20
21
|
export function foldsOf(storage, slug, key = FOLDS_KEY) {
|
|
22
|
+
slug = documentKey(slug);
|
|
21
23
|
const keys = read(storage, key).folds[slug];
|
|
22
24
|
return new Set(Array.isArray(keys) ? keys.filter((key) => typeof key === "string") : []);
|
|
23
25
|
}
|
|
@@ -27,6 +29,7 @@ export function foldsOf(storage, slug, key = FOLDS_KEY) {
|
|
|
27
29
|
* folded last; none folded forgets the report. At most KEEP reports, the oldest going first.
|
|
28
30
|
*/
|
|
29
31
|
export function keepFolds(storage, slug, folded, present = null, key = FOLDS_KEY) {
|
|
32
|
+
slug = documentKey(slug);
|
|
30
33
|
const value = read(storage, key);
|
|
31
34
|
const keys = [...folded].filter((each) => !present || present.has(each));
|
|
32
35
|
delete value.folds[slug]; // put back last: the report folded last
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { workspaceAddress } from "../../shared/workspace-address.mjs";
|
|
1
2
|
import { t, told } from "../../shared/i18n/index.mjs";
|
|
2
3
|
// The page's one way to talk to the preview server: a POST of JSON with the report's token
|
|
3
4
|
// (postJson: the request, a network error rejecting as fetch does; postAnswer: its answer read, a
|
|
@@ -5,7 +6,7 @@ import { t, told } from "../../shared/i18n/index.mjs";
|
|
|
5
6
|
// says (failure) and how an upload carries a file (blobBase64).
|
|
6
7
|
|
|
7
8
|
/** POST `body` as JSON to `route` with `token` (the map's); resolves with the Response. */
|
|
8
|
-
export const postJson = (route, token, body, { keepalive = false } = {}) => fetch(route, {
|
|
9
|
+
export const postJson = (route, token, body, { keepalive = false } = {}) => fetch(workspaceAddress(route), {
|
|
9
10
|
method: "POST",
|
|
10
11
|
headers: { "Content-Type": "application/json", "X-Gallery-Token": token },
|
|
11
12
|
body: JSON.stringify(body),
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { workspaceAddress } from "../../shared/workspace-address.mjs";
|
|
1
2
|
import { t } from "../../shared/i18n/index.mjs";
|
|
2
3
|
import { addressOn, elsewhere, fromFile, isPageAddress, mayBePage, pageAddress, workbenchHash } from "../../shared/page-address.mjs";
|
|
3
4
|
import { inTrash, linkedPages, pageLinks } from "../../shared/page-links.mjs";
|
|
4
5
|
import { entryOfKey } from "../../shared/extension-sidebar.mjs";
|
|
5
|
-
import {
|
|
6
|
+
import { tell } from "./present-shell.mjs";
|
|
6
7
|
|
|
7
8
|
// The links of a page shown in 演示视图 (?present: src/server/routes.mjs), which has no editor and whose
|
|
8
9
|
// frame the presentation holds (src/workbench/app/features/present/). A link to a document or a block
|
|
@@ -21,11 +22,11 @@ import { SHELL, tell } from "./present-shell.mjs";
|
|
|
21
22
|
|
|
22
23
|
const own = pageAddress(location.href)?.slug ?? null;
|
|
23
24
|
const openTab = (address) => window.open(address, "_blank", "noopener");
|
|
24
|
-
const read = (route) => fetch(`/__gallery/workspace/${route}`).then((answer) => answer.json()).catch(() => null);
|
|
25
|
+
const read = (route) => fetch(workspaceAddress(`/__gallery/workspace/${route}`)).then((answer) => answer.json()).catch(() => null);
|
|
25
26
|
|
|
26
27
|
function go({ slug, block }) {
|
|
27
28
|
const fragment = block ? `#${encodeURIComponent(block)}` : "";
|
|
28
|
-
if (slug !== own) location.assign(`/generated/${slug}.html?present${
|
|
29
|
+
if (slug !== own) location.assign(workspaceAddress(`/generated/${slug}.html?present${fragment}`)); // a draft's presentation keeps the workbench around it (present-shell.mjs)
|
|
29
30
|
else if (block) dispatchEvent(new CustomEvent("doc-jump-request", { detail: { hash: fragment } })); // the kit's jump, a step of the history as a link #<block id> is
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -1,63 +1,8 @@
|
|
|
1
1
|
import { pageAddress } from "../../shared/page-address.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { PROTOCOL, PROTOCOLS, VERSION } from "../../shared/session.mjs";
|
|
2
|
+
import { PROTOCOL, VERSION } from "../../shared/session.mjs";
|
|
4
3
|
|
|
5
|
-
//
|
|
6
|
-
// hears from it. The presentation of a document of this workspace is a frame of the same origin, which it reaches into (its
|
|
7
|
-
// scroll, its keys, its theme); that of a draft of another workspace (docs/workbench.md "Drafts of other workspaces") is a frame
|
|
8
|
-
// of that workspace's server, named by the page's address (?shell=<the workbench's origin>) as the editor's page names it
|
|
9
|
-
// (session.mjs): this page then asks its own server whether that is a Gallery server of this computer (GET
|
|
10
|
-
// /__gallery/workspace/shell) and talks to it alone once it is, holding what it says until then; one that is not hears nothing.
|
|
11
|
-
// It tells where the page is scrolled (`present-view`: { x, y }, with `ready` once the page can be spoken to) and the keys pressed
|
|
12
|
-
// in it that are the presentation's (`present-key`: { key, ctrlKey, metaKey, altKey, kept (a control holds the key), prevented }),
|
|
13
|
-
// and does what it is asked: `theme` (the workbench's, light or dark), `go-section` (上一节 / 下一节) and `scroll` ({ left, top }: the wheel). Every message is the session
|
|
14
|
-
// protocol's envelope (src/shared/session.mjs).
|
|
15
|
-
|
|
16
|
-
/** The origin of a workbench of another origin showing this page (?shell=<origin>), or null: this one's. */
|
|
17
|
-
export const SHELL = (() => {
|
|
18
|
-
const named = new URLSearchParams(location.search).get("shell");
|
|
19
|
-
return named && named !== location.origin ? named : null;
|
|
20
|
-
})();
|
|
21
|
-
|
|
22
|
-
const vouched = (origin) => fetch(`/__gallery/workspace/shell?origin=${encodeURIComponent(origin)}`, { cache: "no-store" })
|
|
23
|
-
.then((answer) => answer.json()).then((answer) => answer?.shell === true, () => false);
|
|
24
|
-
|
|
25
|
-
let parentOrigin = SHELL ? null : location.origin;
|
|
26
|
-
let held = SHELL ? [] : null;
|
|
27
|
-
|
|
28
|
-
/** Tell the presentation around the page (`window.parent`) something; held until its origin is vouched for, dropped when it is not. */
|
|
4
|
+
// Presentations and their documents share one origin; links may notify their parent.
|
|
29
5
|
export function tell(type, fields = {}) {
|
|
30
6
|
if (window.parent === window) return;
|
|
31
|
-
|
|
32
|
-
if (!parentOrigin) return;
|
|
33
|
-
window.parent.postMessage({ protocol: PROTOCOL, v: VERSION, type, slug: pageAddress(location.href)?.slug ?? "", ...fields }, parentOrigin);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function bridge() {
|
|
37
|
-
let frame = 0;
|
|
38
|
-
const view = (ready = false) => tell("present-view", { x: scrollX, y: scrollY, ...(ready ? { ready: true } : {}) });
|
|
39
|
-
addEventListener("scroll", () => { frame ||= requestAnimationFrame(() => { frame = 0; view(); }); }, { passive: true });
|
|
40
|
-
addEventListener("keydown", (event) => {
|
|
41
|
-
if (event.isComposing) return;
|
|
42
|
-
tell("present-key", { key: event.key, ctrlKey: event.ctrlKey, metaKey: event.metaKey, altKey: event.altKey, kept: Boolean(event.target?.closest?.(KEEPS_KEYS)), prevented: event.defaultPrevented });
|
|
43
|
-
});
|
|
44
|
-
addEventListener("message", (event) => {
|
|
45
|
-
const message = event.data;
|
|
46
|
-
if (event.source !== window.parent || event.origin !== parentOrigin || !PROTOCOLS.includes(message?.protocol) || message.v !== VERSION || message.type !== "command") return;
|
|
47
|
-
if (message.name === "theme") dispatchEvent(new CustomEvent("doc-theme-request", { detail: { theme: message.theme === "dark" ? "dark" : "light" } })); // the kit's ui.js
|
|
48
|
-
else if (message.name === "go-section") stepSection(window, message.direction > 0 ? 1 : -1);
|
|
49
|
-
else if (message.name === "scroll") scrollBy({ left: Number(message.left) || 0, top: Number(message.top) || 0 }); // the wheel over the presentation's ink
|
|
50
|
-
});
|
|
51
|
-
view(true);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (SHELL && window.parent !== window) {
|
|
55
|
-
vouched(SHELL).then((ok) => {
|
|
56
|
-
const waiting = held;
|
|
57
|
-
held = null;
|
|
58
|
-
if (!ok) return;
|
|
59
|
-
parentOrigin = SHELL;
|
|
60
|
-
for (const [type, fields] of waiting) tell(type, fields);
|
|
61
|
-
bridge();
|
|
62
|
-
});
|
|
7
|
+
window.parent.postMessage({ protocol: PROTOCOL, v: VERSION, type, slug: pageAddress(location.href)?.slug ?? "", ...fields }, location.origin);
|
|
63
8
|
}
|
|
@@ -5,15 +5,7 @@ import { lookOf, lookOfElement } from "../../shared/document-look.mjs";
|
|
|
5
5
|
// tells the workbench its state, never the report's content. Nothing happens when the page is not
|
|
6
6
|
// embedded. The protocol is src/shared/session.mjs; how a workbench uses it, docs/workbench.md.
|
|
7
7
|
//
|
|
8
|
-
// A
|
|
9
|
-
// "Drafts of other workspaces"), naming that origin in the frame's address (?shell=<origin>): the page
|
|
10
|
-
// then asks its own server whether that is a Gallery server of this computer (GET
|
|
11
|
-
// /__gallery/workspace/shell: on the registry, running, its token and process as it announces them),
|
|
12
|
-
// and talks to it alone once it is, holding what it says until then; one that is not hears nothing and
|
|
13
|
-
// is heard by nothing, so that no other page of this computer drives a document by framing it. Going,
|
|
14
|
-
// such a page says `bye` (the workbench of another origin cannot see it go).
|
|
15
|
-
//
|
|
16
|
-
// A message counts only from the parent window, of this origin (or the shell's, vouched for) and protocol. A command names the
|
|
8
|
+
// A message counts only from the parent window, of this exact origin and protocol. A command names the
|
|
17
9
|
// report it is meant for: one for another report (the iframe went to another page meanwhile) is
|
|
18
10
|
// refused. Every command gets exactly one reply, once it is done. Besides the editor's commands the
|
|
19
11
|
// session itself answers five: `theme` (show the report light or dark, or as the reader chose, without
|
|
@@ -27,16 +19,6 @@ import { lookOf, lookOfElement } from "../../shared/document-look.mjs";
|
|
|
27
19
|
|
|
28
20
|
/** A page opened in a frame on its own (?solo), not as the workbench's report: no session, its own notices. */
|
|
29
21
|
export const SOLO = new URLSearchParams(location.search).has("solo");
|
|
30
|
-
/** The origin of a workbench of another workspace showing this page (?shell=<origin>), or null: this one's. */
|
|
31
|
-
const SHELL = (() => {
|
|
32
|
-
const named = new URLSearchParams(location.search).get("shell");
|
|
33
|
-
return named && named !== location.origin ? named : null;
|
|
34
|
-
})();
|
|
35
|
-
|
|
36
|
-
// Whether this page's server vouches for the workbench at `origin` (GET /__gallery/workspace/shell).
|
|
37
|
-
const vouched = (origin) => fetch(`/__gallery/workspace/shell?origin=${encodeURIComponent(origin)}`, { cache: "no-store" })
|
|
38
|
-
.then((answer) => answer.json()).then((answer) => answer?.shell === true, () => false);
|
|
39
|
-
|
|
40
22
|
/**
|
|
41
23
|
* slug: the report's; snapshot(): { title, state } (state: STATE_FLAGS of src/shared/session.mjs);
|
|
42
24
|
* commands: name -> async (message) => result ({ ok, … }); modals: what stands open over the page
|
|
@@ -56,27 +38,11 @@ const vouched = (origin) => fetch(`/__gallery/workspace/shell?origin=${encodeURI
|
|
|
56
38
|
export function connectWorkbench({ slug, snapshot, commands, modals }) {
|
|
57
39
|
if (window.parent === window || SOLO) return null;
|
|
58
40
|
centreCards(modals);
|
|
59
|
-
|
|
60
|
-
// then: what the page says is held, and is dropped when it does not).
|
|
61
|
-
let parentOrigin = SHELL ? null : location.origin;
|
|
62
|
-
let held = SHELL ? [] : null;
|
|
63
|
-
if (SHELL) {
|
|
64
|
-
vouched(SHELL).then((ok) => {
|
|
65
|
-
const waiting = held;
|
|
66
|
-
held = null;
|
|
67
|
-
if (!ok) return;
|
|
68
|
-
parentOrigin = SHELL;
|
|
69
|
-
for (const [type, fields, protocols] of waiting) send(type, fields, protocols);
|
|
70
|
-
// A workbench of another origin sees no pagehide of this page: it is told.
|
|
71
|
-
addEventListener("pagehide", () => send("bye", {}), { once: true });
|
|
72
|
-
});
|
|
73
|
-
}
|
|
41
|
+
const parentOrigin = location.origin;
|
|
74
42
|
// The id the workbench speaks (src/shared/session.mjs, OLD_PROTOCOL): null until its first command,
|
|
75
43
|
// and meanwhile every message goes under both, so that a workbench of either version hears it.
|
|
76
44
|
let spoken = null;
|
|
77
45
|
const send = (type, fields, protocols = spoken ? [spoken] : [PROTOCOL, OLD_PROTOCOL]) => {
|
|
78
|
-
if (held) return void held.push([type, fields, protocols]);
|
|
79
|
-
if (!parentOrigin) return;
|
|
80
46
|
for (const protocol of protocols) window.parent.postMessage({ protocol, v: VERSION, type, slug, ...fields }, parentOrigin);
|
|
81
47
|
};
|
|
82
48
|
let last = null;
|
|
@@ -141,7 +107,7 @@ export function connectWorkbench({ slug, snapshot, commands, modals }) {
|
|
|
141
107
|
|
|
142
108
|
addEventListener("message", async (event) => {
|
|
143
109
|
const message = event.data;
|
|
144
|
-
if (event.source !== window.parent ||
|
|
110
|
+
if (event.source !== window.parent || event.origin !== parentOrigin) return;
|
|
145
111
|
if (!PROTOCOLS.includes(message?.protocol) || message.v !== VERSION || message.type !== "command") return;
|
|
146
112
|
spoken = message.protocol;
|
|
147
113
|
const run = Object.hasOwn(all, message.name) ? all[message.name] : null;
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
// and the page works without (a list used lately is then kept for the page's life only).
|
|
5
5
|
|
|
6
6
|
/** A storage area (`area()`: localStorage or sessionStorage) whose calls never throw. */
|
|
7
|
-
export const storageOf = (area) => ({
|
|
8
|
-
get(key) { try { return area().getItem(key); } catch { return null; } },
|
|
9
|
-
set(key, value) { try { area().setItem(key, value); return true; } catch { return false; } },
|
|
10
|
-
remove(key) { try { area().removeItem(key); } catch {} },
|
|
11
|
-
keys() { try { return Object.keys(area()); } catch { return []; } },
|
|
7
|
+
export const storageOf = (area, { prefix = "" } = {}) => ({
|
|
8
|
+
get(key) { try { return area().getItem(prefix + key); } catch { return null; } },
|
|
9
|
+
set(key, value) { try { area().setItem(prefix + key, value); return true; } catch { return false; } },
|
|
10
|
+
remove(key) { try { area().removeItem(prefix + key); } catch {} },
|
|
11
|
+
keys() { try { return Object.keys(area()).filter(key => key.startsWith(prefix)).map(key => key.slice(prefix.length)); } catch { return []; } },
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
/** localStorage, safe. */
|
|
@@ -11,7 +11,7 @@ import { attr, walk } from "./html-tree.mjs";
|
|
|
11
11
|
import { HOST, NET, netHostsOf } from "./net-policy.mjs";
|
|
12
12
|
import { inLanguage, refusal, said, saying, told } from "./i18n/index.mjs";
|
|
13
13
|
import { DEFAULT_PACK, OWN_SCOPE, PACK_FILE, PACK_ID, readPackManifest } from "./pack-manifest.mjs";
|
|
14
|
-
import { DEVICE_NAMES, PACKAGE_MANIFEST, WORKSPACE_MARK,
|
|
14
|
+
import { DEVICE_NAMES, PACKAGE_MANIFEST, WORKSPACE_MARK, defaultWorkspace, layoutOf, markerOf, packageFolders } from "./workspace.mjs";
|
|
15
15
|
|
|
16
16
|
// 组件包, custom blocks (自定义块) as the UI and the Agent's documents call them: what the workspace's
|
|
17
17
|
// component packages say of themselves to Gallery, read from
|
|
@@ -466,7 +466,7 @@ function readPackage(id, folder) {
|
|
|
466
466
|
}
|
|
467
467
|
}
|
|
468
468
|
|
|
469
|
-
const workspaceRoot = () =>
|
|
469
|
+
const workspaceRoot = () => defaultWorkspace();
|
|
470
470
|
|
|
471
471
|
// A file edited by hand holds what it holds: of its lists, what can be an id is read, once each, and
|
|
472
472
|
// the rest is passed over, one that names nothing there doing nothing. An id may be of a package that
|
|
@@ -2,7 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { descendantsIn, outlineAndFile, parentIn, placeIn, readOutline,
|
|
5
|
+
import { descendantsIn, outlineAndFile, parentIn, placeIn, readOutline, withOutlineLock, writeOutline, writtenAs } from "./outline.mjs";
|
|
6
6
|
import { DOCS, GROUP_LIMITS, isForeign, isTop, newGroupId, topIn, topOf } from "./outline-shape.mjs";
|
|
7
7
|
import { findPreviewServer } from "./preview-server.mjs";
|
|
8
8
|
import { idsOf, shownIcon } from "./workspace-columns.mjs";
|
|
@@ -10,18 +10,11 @@ import { nameStops, workspaceLabels, workspaceName } from "./workspace-name.mjs"
|
|
|
10
10
|
import { isWorkspace, readRegistry, workspaceId, workspaceKey } from "./workspace-registry.mjs";
|
|
11
11
|
import { defaultWorkspace, documentFiles, isSlugName, listDocumentSlugs, readLink } from "./workspace.mjs";
|
|
12
12
|
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// never written (its server shows it as it was). Every registered workspace has a column of its own
|
|
19
|
-
// (./workspace-columns.mjs, ./outline.mjs), "whole" (all its documents) unless the user picked a document
|
|
20
|
-
// of it in 添加其他 Gallery 项目… (POST draft-add) while it had none shown: then it is `picked`, holding that
|
|
21
|
-
// document and those under it alone, and what an Agent makes or opens there later (gallery new, gallery
|
|
22
|
-
// open in another workspace than the default one: joinDefaultTree). The column the user removed is
|
|
23
|
-
// `hidden`; an Agent's gallery new or open there, or the user's pick, shows it again. A draft the user took
|
|
24
|
-
// out of a whole column (从文档树移除) is in its `left`, not filled in again until it is put back here.
|
|
13
|
+
// Registered document folders share the default home's sidebar and outline. A foreign entry is
|
|
14
|
+
// ~<workspace-id>/<slug>; its files, history, trash and exports remain in that folder.
|
|
15
|
+
// Every registered folder contributes its documents. Ordering and hierarchy have one global owner.
|
|
16
|
+
// addDraft/addWorkspace register or reveal a folder through the existing outline operations;
|
|
17
|
+
// joinDefaultTree places documents created by CLI commands in that same tree.
|
|
25
18
|
// A draft whose workspace is not on the registry, two registered folders sharing its id (six hex digits
|
|
26
19
|
// of a hash: a clash is refused, never guessed), or whose folder is gone stays in the tree, shown grey
|
|
27
20
|
// (找不到底稿) until the user removes it. Reads other workspaces' files only; writes this one's outline.
|
|
@@ -193,7 +186,7 @@ const shownColumn = ({ hidden, ...column }) => column;
|
|
|
193
186
|
* (documents: a web link of that workspace is none, nor a draft of a third its tree shows, the drafts under
|
|
194
187
|
* one going under the draft above it), those the tree does not hold yet. Its workspace's column is shown
|
|
195
188
|
* again when the user had removed it (hidden). How, `pick` says:
|
|
196
|
-
* - false (an Agent's gallery new or open there: joinDefaultTree, POST draft-
|
|
189
|
+
* - false (an Agent's gallery new or open there: joinDefaultTree, POST draft-add): one the outline holds
|
|
197
190
|
* (the file, or a whole column as it is read) stays where it is; else (a picked column) under its own
|
|
198
191
|
* parent's entry when the tree holds that, else last in its workspace's column;
|
|
199
192
|
* - true (the user's pick in 添加其他 Gallery 项目…, `after`: the column whose menu it came from): one shown
|
|
@@ -228,9 +221,13 @@ export async function addDraft(treeRoot, draftRoot, slug, { changed = () => {},
|
|
|
228
221
|
return { parent, column: top === null || top === "" ? DOCS : top.slice(1), under: parent !== null && !isTop(parent) ? parent : null };
|
|
229
222
|
};
|
|
230
223
|
const columnOf = (key) => outline.groups.find((each) => topOf(each.id) === topIn(outline.children, key)) ?? null;
|
|
231
|
-
if (
|
|
224
|
+
if (holds(entry)) {
|
|
225
|
+
if (pick && fresh) {
|
|
226
|
+
const column = columnOf(entry);
|
|
227
|
+
if (column) moveAfter(outline, column.id, after);
|
|
228
|
+
}
|
|
232
229
|
if (!writtenAs(outline, stored)) { await writeOutline(treeRoot, outline); changed(); }
|
|
233
|
-
const existing = pick || storedHolds;
|
|
230
|
+
const existing = !fresh && (pick || storedHolds);
|
|
234
231
|
return { entry, existing, added: existing ? [] : [entry], shown: false, ...where() };
|
|
235
232
|
}
|
|
236
233
|
let shown = false;
|
|
@@ -356,23 +353,9 @@ export async function projectChoices(treeRoot, { ids = registeredIds(), stops =
|
|
|
356
353
|
}
|
|
357
354
|
|
|
358
355
|
|
|
359
|
-
/**
|
|
360
|
-
|
|
361
|
-
const set = (value) => !["", "0", undefined].includes(value);
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* gallery new and gallery open in a workspace other than the default one (src/shared/workspace.mjs
|
|
365
|
-
* defaultWorkspace): draft `slug` of workspace `root` put in the default workspace's sidebar (addDraft,
|
|
366
|
-
* with the drafts under it), through the default workspace's server when one runs (POST
|
|
367
|
-
* /__gallery/workspace/draft-join: in its queue, its workbench told at once), else here under the
|
|
368
|
-
* outline's lock. Null where it is not done: NO_DEFAULT_TREE set, `root` the default workspace, no
|
|
369
|
-
* default workspace there yet (nothing is made for it), or `root` not on the registry under its id (a
|
|
370
|
-
* workspace in the system's temporary folder, or a clash of ids: an entry would name none). Else
|
|
371
|
-
* addDraft's answer with `home` (the default workspace) and `names`: { column: the name of the column
|
|
372
|
-
* it is in, under: the title of the draft it is under, or null }; or { failed: why } (nothing written).
|
|
373
|
-
*/
|
|
356
|
+
/** Put a project document in home's shared outline through the single service's draft-add
|
|
357
|
+
* command when running, otherwise through the same outline lock offline. */
|
|
374
358
|
export async function joinDefaultTree(root, slug, { env = process.env } = {}) {
|
|
375
|
-
if (set(env[NO_DEFAULT_TREE])) return null;
|
|
376
359
|
const home = defaultWorkspace({ env });
|
|
377
360
|
if (workspaceKey(home) === workspaceKey(root) || !isWorkspace(home)) return null;
|
|
378
361
|
const ids = registeredIds();
|
|
@@ -380,8 +363,17 @@ export async function joinDefaultTree(root, slug, { env = process.env } = {}) {
|
|
|
380
363
|
if (!found.root || workspaceKey(found.root) !== workspaceKey(root)) return null;
|
|
381
364
|
let done = null;
|
|
382
365
|
try {
|
|
383
|
-
const server = await findPreviewServer(
|
|
384
|
-
if (server)
|
|
366
|
+
const server = await findPreviewServer();
|
|
367
|
+
if (server) {
|
|
368
|
+
const origin = `http://127.0.0.1:${server.port}`;
|
|
369
|
+
const response = await fetch(`${origin}/__gallery/workspace/draft-add`, {
|
|
370
|
+
method: "POST", headers: { "Content-Type": "application/json", Origin: origin, "X-Gallery-Token": server.token },
|
|
371
|
+
body: JSON.stringify({ path: path.join(root, slug) }), signal: AbortSignal.timeout(30000),
|
|
372
|
+
});
|
|
373
|
+
const answer = await response.json();
|
|
374
|
+
if (!answer.ok) throw new Error(answer.message ?? "Could not register the document");
|
|
375
|
+
done = answer;
|
|
376
|
+
}
|
|
385
377
|
done ??= await addDraft(home, root, slug);
|
|
386
378
|
} catch (error) {
|
|
387
379
|
return { failed: error.message, home };
|
|
@@ -389,23 +381,6 @@ export async function joinDefaultTree(root, slug, { env = process.env } = {}) {
|
|
|
389
381
|
return { ...done, home, names: await placeNames(home, done, ids) };
|
|
390
382
|
}
|
|
391
383
|
|
|
392
|
-
// POST draft-join to the default workspace's server; null when no server of it takes it (gone, or an older Gallery's).
|
|
393
|
-
async function joinThrough(server, root, slug) {
|
|
394
|
-
const host = `127.0.0.1:${server.port}`;
|
|
395
|
-
try {
|
|
396
|
-
const response = await fetch(`http://${host}/__gallery/workspace/draft-join`, {
|
|
397
|
-
method: "POST",
|
|
398
|
-
headers: { "Content-Type": "application/json", Origin: `http://${host}`, "X-Gallery-Token": server.token },
|
|
399
|
-
body: JSON.stringify({ root, slug }),
|
|
400
|
-
signal: AbortSignal.timeout(30000),
|
|
401
|
-
});
|
|
402
|
-
const answer = await response.json().catch(() => null);
|
|
403
|
-
return answer?.ok === true && typeof answer.entry === "string" ? answer : null;
|
|
404
|
-
} catch {
|
|
405
|
-
return null;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
384
|
// The names a line says of where a draft went: its column's (the user's title, else its workspace's
|
|
410
385
|
// name, else 默认文档, the default workspace's own column, or 链接) and the title of the draft or document it is under.
|
|
411
386
|
async function placeNames(home, { column, under }, ids) {
|