@heycys/gallery 0.1.1 → 0.1.2
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/components/columns/page.mjs +4 -4
- package/browser/editor/components/embed/index.mjs +2 -2
- package/browser/editor/components/flowchart/add.mjs +7 -6
- package/browser/editor/components/flowchart/page.mjs +1 -1
- package/browser/editor/components/icons.mjs +8 -0
- package/browser/editor/components/image/crop.mjs +6 -9
- package/browser/editor/components/installed/index.mjs +11 -4
- package/browser/editor/components/installed/page.mjs +27 -16
- package/browser/editor/components/installed/served.mjs +6 -5
- package/browser/editor/components/instances.mjs +18 -0
- package/browser/editor/components/list/page.mjs +3 -6
- package/browser/editor/components/registry.mjs +13 -4
- package/browser/editor/components/table/index.mjs +59 -2
- package/browser/editor/components/table/page.mjs +1 -1
- package/browser/editor/page/annotate/card.mjs +3 -6
- package/browser/editor/page/autosave.mjs +310 -37
- package/browser/editor/page/board/client.mjs +57 -0
- package/browser/editor/page/board/hint.mjs +145 -0
- package/browser/editor/page/board/history.mjs +119 -0
- package/browser/editor/page/board/index.mjs +970 -0
- package/browser/editor/page/board/settings.mjs +209 -0
- package/browser/editor/page/caret.mjs +1 -1
- package/browser/editor/page/changes/bar.mjs +6 -14
- package/browser/editor/page/changes/index.mjs +2 -2
- package/browser/editor/page/clipboard/import/html.mjs +29 -6
- package/browser/editor/page/clipboard/import/inline.mjs +32 -0
- package/browser/editor/page/clipboard/import/markdown.mjs +58 -13
- package/browser/editor/page/clipboard/import/read.mjs +6 -1
- package/browser/editor/page/clipboard/import/to-blocks.mjs +44 -12
- package/browser/editor/page/clipboard/import/yuque.mjs +622 -0
- package/browser/editor/page/clipboard/index.mjs +124 -13
- package/browser/editor/page/clipboard/own/blocks.mjs +11 -11
- package/browser/editor/page/clipboard/own/chart-keys.mjs +41 -28
- package/browser/editor/page/clipboard/own/selection.mjs +1 -1
- package/browser/editor/page/columns/index.mjs +48 -46
- package/browser/editor/page/commit.mjs +80 -22
- package/browser/editor/page/conflicts.mjs +7 -7
- package/browser/editor/page/core.mjs +31 -15
- package/browser/editor/page/custom-page.mjs +106 -0
- package/browser/editor/page/editor.mjs +51 -14
- package/browser/editor/page/emoji-picker/styles.mjs +7 -12
- package/browser/editor/page/emoji-picker/upload.mjs +2 -2
- package/browser/editor/page/handle/index.mjs +5 -8
- package/browser/editor/page/history.mjs +24 -4
- package/browser/editor/page/links/index.mjs +5 -3
- package/browser/editor/page/links/pages.mjs +27 -9
- package/browser/editor/page/look.mjs +54 -3
- package/browser/editor/page/margin.mjs +18 -3
- package/browser/editor/page/media-menu/index.mjs +6 -7
- package/browser/editor/page/menu.mjs +18 -7
- package/browser/editor/page/notices.mjs +2 -2
- package/browser/editor/page/page-objects.mjs +13 -2
- package/browser/editor/page/panel.mjs +4 -9
- package/browser/editor/page/present-links.mjs +11 -2
- package/browser/editor/page/resize/index.mjs +42 -29
- package/browser/editor/page/served.mjs +14 -0
- package/browser/editor/page/session.mjs +48 -4
- package/browser/editor/page/slash-menu/index.mjs +3 -3
- package/browser/editor/page/table/cells.mjs +102 -46
- package/browser/editor/page/table/icons.mjs +2 -0
- package/browser/editor/page/table/index.mjs +29 -10
- package/browser/editor/page/table/pictures.mjs +299 -0
- package/browser/editor/page/table/views.mjs +150 -0
- package/browser/editor/page/text-select/index.mjs +26 -12
- package/browser/editor/page/todo-writes.mjs +75 -0
- package/browser/editor/page/typing/index.mjs +53 -3
- package/browser/editor/page/ui.mjs +13 -0
- package/browser/shared/block-packs.mjs +404 -0
- package/browser/shared/board-places.mjs +224 -0
- package/browser/shared/card-size.mjs +59 -0
- package/browser/shared/colors.mjs +3 -2
- package/browser/shared/component-catalog.mjs +323 -46
- package/browser/shared/component-source.mjs +37 -0
- package/browser/shared/components/index.mjs +12 -3
- package/browser/shared/components/installed.mjs +35 -3
- package/browser/shared/components/page.mjs +18 -8
- package/browser/shared/components/state.mjs +5 -1
- package/browser/shared/document-look.mjs +10 -5
- package/browser/shared/drafts.mjs +432 -0
- package/browser/shared/extension-sidebar.mjs +126 -0
- package/browser/shared/extensions.mjs +79 -0
- package/browser/shared/file-table.mjs +101 -0
- package/browser/shared/i18n/en.mjs +307 -27
- package/browser/shared/json-layout.mjs +47 -0
- package/browser/shared/net-cache.mjs +133 -0
- package/browser/shared/net-keys.mjs +32 -0
- package/browser/shared/net-policy.mjs +177 -0
- package/browser/shared/outline-shape.mjs +128 -0
- package/browser/shared/outline.mjs +331 -67
- package/browser/shared/pack-manifest.mjs +77 -0
- package/browser/shared/page-address.mjs +6 -3
- package/browser/shared/page-cards.mjs +308 -0
- package/browser/shared/page-links.mjs +69 -6
- package/browser/shared/pages.mjs +388 -0
- package/browser/shared/paths.mjs +6 -0
- package/browser/shared/preview-server.mjs +3 -7
- package/browser/shared/session.mjs +8 -1
- package/browser/shared/shipped-blocks.mjs +299 -0
- package/browser/shared/table-chart.mjs +33 -1
- package/browser/shared/todos.mjs +83 -0
- package/browser/shared/user-directory.mjs +24 -0
- package/browser/shared/versions.mjs +14 -0
- package/browser/shared/web-links.mjs +134 -0
- package/browser/shared/workspace-columns.mjs +120 -0
- package/browser/shared/workspace-name.mjs +154 -0
- package/browser/shared/workspace-query.mjs +125 -0
- package/browser/shared/workspace-registry.mjs +272 -0
- package/browser/shared/workspace.mjs +171 -20
- package/dist/chunk-2NRBY6AJ.mjs +2 -0
- package/dist/chunk-32ZUEFZ7.mjs +2 -0
- package/dist/chunk-35NQ4PLQ.mjs +2 -0
- package/dist/chunk-3KJHLHUO.mjs +2 -0
- package/dist/chunk-3NJACR4W.mjs +2 -0
- package/dist/chunk-4AV456RQ.mjs +2 -0
- package/dist/chunk-4OZWXZE5.mjs +5 -0
- package/dist/chunk-5XYHDBJ5.mjs +2 -0
- package/dist/chunk-5ZJA7CXT.mjs +2 -0
- package/dist/chunk-6A53MZYZ.mjs +2 -0
- package/dist/chunk-6EXH3ORZ.mjs +166 -0
- package/dist/chunk-6EZ7FNIK.mjs +3 -0
- package/dist/{chunk-V5JTW2LH.mjs → chunk-6QBHU4DF.mjs} +1 -1
- package/dist/{chunk-Y6RLRPG3.mjs → chunk-6VTU6KEO.mjs} +1 -1
- package/dist/chunk-B4P2CM2J.mjs +3 -0
- package/dist/{chunk-FDLXYYG4.mjs → chunk-BPL64HGA.mjs} +1 -1
- package/dist/chunk-BVEFGE4B.mjs +2 -0
- package/dist/{chunk-LS4F7JUM.mjs → chunk-BZ5RAF7L.mjs} +1 -1
- package/dist/chunk-CRJEG7MT.mjs +2 -0
- package/dist/{chunk-LCTHTZIL.mjs → chunk-D4CKRD3V.mjs} +91 -91
- package/dist/chunk-D4EURAX4.mjs +4 -0
- package/dist/chunk-DQCE7TJY.mjs +5 -0
- package/dist/chunk-DR73Y2UJ.mjs +3 -0
- package/dist/{chunk-TMYZB6AV.mjs → chunk-EKNHM3P4.mjs} +1 -1
- package/dist/{chunk-GB5SOYQJ.mjs → chunk-ERLAIBOA.mjs} +2 -2
- package/dist/chunk-EURLTOTZ.mjs +4 -0
- package/dist/chunk-FJYN7RTH.mjs +4 -0
- package/dist/chunk-FWNRPMCJ.mjs +12 -0
- package/dist/chunk-FWW4CSAZ.mjs +2 -0
- package/dist/{chunk-2B5E4CDJ.mjs → chunk-GPIWQ4X4.mjs} +1 -1
- package/dist/chunk-GY3FAUVM.mjs +2 -0
- package/dist/chunk-HLYV7RIH.mjs +2 -0
- package/dist/chunk-HMYTM3GT.mjs +15 -0
- package/dist/chunk-IA4PSIMD.mjs +2 -0
- package/dist/chunk-JEUK3ZO7.mjs +2 -0
- package/dist/chunk-KTKHRC6H.mjs +2 -0
- package/dist/chunk-L7UP52JO.mjs +2 -0
- package/dist/{chunk-SYGN7GLU.mjs → chunk-MHW7ZKOU.mjs} +1 -1
- package/dist/chunk-N76HWBA3.mjs +10 -0
- package/dist/chunk-N7AZZLGT.mjs +548 -0
- package/dist/chunk-NAMN3RAF.mjs +2 -0
- package/dist/{chunk-5ZKJ3H4P.mjs → chunk-NFMCP2HI.mjs} +3 -3
- package/dist/chunk-NG375ILQ.mjs +2 -0
- package/dist/chunk-NVRNLK25.mjs +6 -0
- package/dist/chunk-NWFBE2YA.mjs +2 -0
- package/dist/chunk-NWR2LT5B.mjs +2 -0
- package/dist/chunk-OJKMINIU.mjs +20 -0
- package/dist/{chunk-4FS7Z7WR.mjs → chunk-OUUP3WKT.mjs} +1 -1
- package/dist/{chunk-BGGDRNYQ.mjs → chunk-OW5NWPOJ.mjs} +1 -1
- package/dist/chunk-PAREWQGB.mjs +4 -0
- package/dist/chunk-POQSE4C2.mjs +2 -0
- package/dist/chunk-PUOQ65NC.mjs +2 -0
- package/dist/{chunk-XNROSH2T.mjs → chunk-PVER7LSC.mjs} +2 -2
- package/dist/chunk-PZDLJJTJ.mjs +3 -0
- package/dist/chunk-Q47PLKIJ.mjs +15 -0
- package/dist/chunk-Q4UEPUWF.mjs +2 -0
- package/dist/chunk-QKXMPUXC.mjs +2 -0
- package/dist/chunk-QOUEFUCH.mjs +16 -0
- package/dist/chunk-RFDZXFJF.mjs +13 -0
- package/dist/chunk-RKPWLGZ3.mjs +4 -0
- package/dist/chunk-RMECZA2K.mjs +2 -0
- package/dist/{chunk-7DTINW6U.mjs → chunk-SQ6Y2ZMA.mjs} +2 -2
- package/dist/chunk-SX4LJYT6.mjs +8 -0
- package/dist/chunk-SXHBYZST.mjs +16 -0
- package/dist/chunk-TANZRINB.mjs +2 -0
- package/dist/chunk-TEZAWDSQ.mjs +2 -0
- package/dist/chunk-UBBRTJ2R.mjs +24 -0
- package/dist/chunk-UFNYFFKO.mjs +20 -0
- package/dist/chunk-UGWYKHBL.mjs +170 -0
- package/dist/chunk-UKSHDZZ6.mjs +2 -0
- package/dist/chunk-UNOYVIYR.mjs +2 -0
- package/dist/{chunk-E7LWZRXY.mjs → chunk-V6RIYQZZ.mjs} +1 -1
- package/dist/chunk-VJPFVNNW.mjs +15 -0
- package/dist/chunk-VS3ZMVGX.mjs +5 -0
- package/dist/chunk-VTKNEYT3.mjs +6 -0
- package/dist/chunk-VXACTLO3.mjs +3 -0
- package/dist/chunk-YI43RG4P.mjs +2 -0
- package/dist/chunk-YQGT4AB2.mjs +6 -0
- package/dist/chunk-ZB4H7ZN4.mjs +2 -0
- package/dist/{chunk-IVDLMMBD.mjs → chunk-ZHEHPMBK.mjs} +1 -1
- package/dist/chunk-ZTHICSSF.mjs +2 -0
- package/dist/chunk-ZUNVAXLH.mjs +2 -0
- package/dist/chunk-ZWPMCK7P.mjs +2 -0
- package/dist/cli.mjs +1 -1
- package/dist/server.mjs +1 -1
- package/dist/{chunk-U6KNPJPR.mjs → update-fetch.mjs} +1 -1
- package/dist/worker.mjs +1 -1
- package/kit/board.css +95 -0
- package/kit/board.js +250 -0
- package/kit/charts.js +5 -71
- package/kit/code.js +14 -14
- package/kit/report.css +255 -17
- package/kit/runtime.js +1600 -125
- package/kit/state.js +10 -1
- package/kit/tables.js +128 -43
- package/kit/ui.js +77 -35
- package/package.json +2 -1
- package/packs/gallery/card/component.json +14 -0
- package/packs/gallery/card/example.html +10 -0
- package/packs/gallery/card/preview.svg +1 -0
- package/packs/gallery/card/style.css +6 -0
- package/packs/gallery/card/template.html +6 -0
- package/packs/gallery/card/writing.md +14 -0
- package/packs/gallery/chart-card/component.json +15 -0
- package/packs/gallery/chart-card/example.html +14 -0
- package/packs/gallery/chart-card/kit.js +130 -0
- package/packs/gallery/chart-card/preview.svg +1 -0
- package/packs/gallery/chart-card/style.css +42 -0
- package/packs/gallery/chart-card/template.html +13 -0
- package/packs/gallery/chart-card/writing.md +20 -0
- package/packs/gallery/chart-view/component.json +20 -0
- package/packs/gallery/chart-view/example.html +13 -0
- package/packs/gallery/chart-view/kit.js +355 -0
- package/packs/gallery/chart-view/preview.svg +1 -0
- package/packs/gallery/chart-view/style.css +94 -0
- package/packs/gallery/chart-view/template.html +3 -0
- package/packs/gallery/chart-view/writing.md +22 -0
- package/packs/gallery/countdown/component.json +18 -0
- package/packs/gallery/countdown/example.html +3 -0
- package/packs/gallery/countdown/kit.js +156 -0
- package/packs/gallery/countdown/preview.svg +1 -0
- package/packs/gallery/countdown/style.css +130 -0
- package/packs/gallery/countdown/template.html +3 -0
- package/packs/gallery/countdown/writing.md +22 -0
- package/packs/gallery/habit/component.json +14 -0
- package/packs/gallery/habit/example.html +3 -0
- package/packs/gallery/habit/kit.js +295 -0
- package/packs/gallery/habit/preview.svg +1 -0
- package/packs/gallery/habit/style.css +246 -0
- package/packs/gallery/habit/template.html +3 -0
- package/packs/gallery/habit/writing.md +21 -0
- package/packs/gallery/heatmap/component.json +18 -0
- package/packs/gallery/heatmap/example.html +3 -0
- package/packs/gallery/heatmap/kit.js +351 -0
- package/packs/gallery/heatmap/preview.svg +1 -0
- package/packs/gallery/heatmap/style.css +201 -0
- package/packs/gallery/heatmap/template.html +3 -0
- package/packs/gallery/heatmap/writing.md +21 -0
- package/packs/gallery/kanban/component.json +18 -0
- package/packs/gallery/kanban/example.html +12 -0
- package/packs/gallery/kanban/kit.js +540 -0
- package/packs/gallery/kanban/preview.svg +1 -0
- package/packs/gallery/kanban/style.css +212 -0
- package/packs/gallery/kanban/template.html +3 -0
- package/packs/gallery/kanban/writing.md +20 -0
- package/packs/gallery/metric/component.json +22 -0
- package/packs/gallery/metric/example.html +15 -0
- package/packs/gallery/metric/kit.js +248 -0
- package/packs/gallery/metric/preview.svg +1 -0
- package/packs/gallery/metric/style.css +92 -0
- package/packs/gallery/metric/template.html +3 -0
- package/packs/gallery/metric/writing.md +23 -0
- package/packs/gallery/note/component.json +14 -0
- package/packs/gallery/note/example.html +3 -0
- package/packs/gallery/note/kit.js +102 -0
- package/packs/gallery/note/preview.svg +1 -0
- package/packs/gallery/note/style.css +76 -0
- package/packs/gallery/note/template.html +3 -0
- package/packs/gallery/note/writing.md +18 -0
- package/packs/gallery/pack.json +8 -0
- package/packs/gallery/recent/component.json +13 -0
- package/packs/gallery/recent/example.html +1 -0
- package/packs/gallery/recent/kit.js +238 -0
- package/packs/gallery/recent/preview.svg +1 -0
- package/packs/gallery/recent/style.css +158 -0
- package/packs/gallery/recent/template.html +1 -0
- package/packs/gallery/recent/writing.md +15 -0
- package/packs/gallery/static/component.json +13 -0
- package/packs/gallery/static/example.html +11 -0
- package/packs/gallery/static/preview.svg +1 -0
- package/packs/gallery/static/style.css +138 -0
- package/packs/gallery/static/template.html +1 -0
- package/packs/gallery/static/writing.md +18 -0
- package/packs/gallery/stats/component.json +13 -0
- package/packs/gallery/stats/example.html +1 -0
- package/packs/gallery/stats/kit.js +166 -0
- package/packs/gallery/stats/preview.svg +1 -0
- package/packs/gallery/stats/style.css +151 -0
- package/packs/gallery/stats/template.html +1 -0
- package/packs/gallery/stats/writing.md +11 -0
- package/packs/gallery/today/component.json +12 -0
- package/packs/gallery/today/example.html +1 -0
- package/packs/gallery/today/kit.js +185 -0
- package/packs/gallery/today/preview.svg +1 -0
- package/packs/gallery/today/style.css +120 -0
- package/packs/gallery/today/template.html +1 -0
- package/packs/gallery/today/writing.md +14 -0
- package/packs/gallery/todos/component.json +19 -0
- package/packs/gallery/todos/example.html +3 -0
- package/packs/gallery/todos/kit.js +342 -0
- package/packs/gallery/todos/preview.svg +1 -0
- package/packs/gallery/todos/style.css +267 -0
- package/packs/gallery/todos/template.html +3 -0
- package/packs/gallery/todos/writing.md +30 -0
- package/packs/gallery/weather/component.json +19 -0
- package/packs/gallery/weather/example.html +3 -0
- package/packs/gallery/weather/kit.js +312 -0
- package/packs/gallery/weather/preview.svg +1 -0
- package/packs/gallery/weather/style.css +258 -0
- package/packs/gallery/weather/template.html +3 -0
- package/packs/gallery/weather/writing.md +26 -0
- package/packs/gallery-soft/pack.json +8 -0
- package/release.json +12 -10
- package/sdk/doc-kit.d.ts +396 -5
- package/sdk/doc-state.d.ts +2 -0
- package/skill/SKILL.md +56 -23
- package/skill/references/block-script.md +140 -0
- package/skill/references/building-blocks.md +257 -0
- package/skill/references/extensions.md +59 -0
- package/skill/references/pages-and-data.md +221 -0
- package/skill/references/writing-html.md +6 -4
- package/skill/sdk/doc-kit.d.ts +396 -5
- package/skill/sdk/doc-state.d.ts +2 -0
- package/web/assets/index-BK2e_07w.js +13 -0
- package/web/assets/index-CWFI83mF.css +1 -0
- package/web/index.html +2 -2
- package/dist/chunk-3HIRPVLU.mjs +0 -2
- package/dist/chunk-5MHMDLZD.mjs +0 -4
- package/dist/chunk-64K7IPLO.mjs +0 -255
- package/dist/chunk-6QXGZM36.mjs +0 -8
- package/dist/chunk-6RGJOMJP.mjs +0 -16
- package/dist/chunk-AJ73XJF3.mjs +0 -2
- package/dist/chunk-AUZGSZJH.mjs +0 -13
- package/dist/chunk-BFE45AKL.mjs +0 -2
- package/dist/chunk-BXO2ZY5P.mjs +0 -2
- package/dist/chunk-CEH4MRCC.mjs +0 -2
- package/dist/chunk-CY2FNNAB.mjs +0 -16
- package/dist/chunk-DFUDV4LX.mjs +0 -2
- package/dist/chunk-DKXG4XDG.mjs +0 -2
- package/dist/chunk-EZQ34M3F.mjs +0 -124
- package/dist/chunk-EZY3WC6Z.mjs +0 -15
- package/dist/chunk-FUKQNOQS.mjs +0 -9
- package/dist/chunk-JPOSLGOC.mjs +0 -6
- package/dist/chunk-L3QKH4OK.mjs +0 -26
- package/dist/chunk-LAE5NQ4B.mjs +0 -2
- package/dist/chunk-LEF6DRCK.mjs +0 -59
- package/dist/chunk-NO5NJCHO.mjs +0 -2
- package/dist/chunk-NQPVAU63.mjs +0 -2
- package/dist/chunk-NSKUTQ5J.mjs +0 -5
- package/dist/chunk-O35FIXRB.mjs +0 -2
- package/dist/chunk-P2CAZPDP.mjs +0 -3
- package/dist/chunk-PL6GHLGD.mjs +0 -15
- package/dist/chunk-QJIR2BLP.mjs +0 -6
- package/dist/chunk-QUPLVPB2.mjs +0 -7
- package/dist/chunk-R7BNENLY.mjs +0 -4
- package/dist/chunk-S6O4UHLC.mjs +0 -2
- package/dist/chunk-SGJWQKEI.mjs +0 -2
- package/dist/chunk-TX6JH7TR.mjs +0 -2
- package/dist/chunk-TYK6VNAB.mjs +0 -10
- package/dist/chunk-UALRNVSE.mjs +0 -4
- package/dist/chunk-V3HXVQEM.mjs +0 -2
- package/dist/chunk-V5VXUJKG.mjs +0 -2
- package/dist/chunk-VBXMADMP.mjs +0 -2
- package/dist/chunk-VNV55RUA.mjs +0 -2
- package/dist/chunk-VPD2PS35.mjs +0 -2
- package/dist/chunk-W4VKN3GF.mjs +0 -7
- package/dist/chunk-W5BXXPDJ.mjs +0 -3
- package/dist/chunk-XUNHRCOG.mjs +0 -6
- package/skill/references/building-components.md +0 -367
- package/web/assets/index-Bh74DfsV.css +0 -1
- package/web/assets/index-UW0RyFFt.js +0 -13
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { iconKind } from "./emoji/icon-value.mjs";
|
|
4
|
+
import { isWorkspace, readRegistry, workspaceId, workspaceKey } from "./workspace-registry.mjs";
|
|
5
|
+
import { defaultWorkspace, documentFiles, isSlugName } from "./workspace.mjs";
|
|
6
|
+
|
|
7
|
+
// The other workspaces a workspace's sidebar shows a column of (notes/plans/workspaces-links-plan-2026-11-08.md
|
|
8
|
+
// §7.2; docs/workbench.md "Columns"): every workspace of the machine's registry (./workspace-registry.mjs) but
|
|
9
|
+
// this one, a clash of ids (two registered folders sharing one: never guessed) and one whose folder is no
|
|
10
|
+
// workspace now (an external drive away), in this order: the default workspace (./workspace.mjs
|
|
11
|
+
// defaultWorkspace, GALLERY_DEFAULT_WORKSPACE honoured) first, the rest as they were registered. The outline
|
|
12
|
+
// (./outline.mjs) gives each a column the first time it reads it (a "whole" one, all its documents, laid out
|
|
13
|
+
// as its own sidebar has them) and the place of its documents no column holds; the user's order of the
|
|
14
|
+
// columns is the outline's after that.
|
|
15
|
+
//
|
|
16
|
+
// What a workspace's sidebar is read with (snapshotOf): its documents in its own sidebar's order, the document
|
|
17
|
+
// each is under there (a web link or another workspace's draft passed over upward), and what its doc.json
|
|
18
|
+
// says (title, description, an icon of the emoji or 图标 kind: no picture of another workspace's folder). It is
|
|
19
|
+
// read from its files, never written, and kept SNAPSHOT_KEPT ms at most, so that the reads of a burst (a
|
|
20
|
+
// command and the workbench's read after it, the list of 添加其他 Gallery 项目…) read each folder once while a
|
|
21
|
+
// change there shows at the next read after that; the server drops one at once when that workspace's own
|
|
22
|
+
// server tells of a change (src/workbench/service.mjs, through ./elsewhere.mjs). Reads files only.
|
|
23
|
+
|
|
24
|
+
/** How long a snapshot of another workspace is believed (ms). */
|
|
25
|
+
export const SNAPSHOT_KEPT = 2000;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The registered workspaces by their ids: a Map of id to the roots registered under it (two for one id: a clash
|
|
29
|
+
* of the hash's six digits, which names neither). `registry`: readRegistry()'s.
|
|
30
|
+
*/
|
|
31
|
+
export function idsOf(registry = readRegistry()) {
|
|
32
|
+
const ids = new Map();
|
|
33
|
+
for (const { root } of registry.workspaces) {
|
|
34
|
+
const id = workspaceId(root);
|
|
35
|
+
const roots = ids.get(id) ?? [];
|
|
36
|
+
if (!roots.some((each) => workspaceKey(each) === workspaceKey(root))) roots.push(root);
|
|
37
|
+
ids.set(id, roots);
|
|
38
|
+
}
|
|
39
|
+
return ids;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The other workspaces whose columns the sidebar of workspace `root` shows: [{ id, root, isDefault }], the
|
|
44
|
+
* default workspace first (when it is registered and not `root`), then the registry's order.
|
|
45
|
+
*/
|
|
46
|
+
export function shownWorkspaces(root, { registry = readRegistry(), env = process.env } = {}) {
|
|
47
|
+
const own = workspaceKey(root);
|
|
48
|
+
const home = workspaceKey(defaultWorkspace({ env }));
|
|
49
|
+
const shown = [];
|
|
50
|
+
for (const [id, roots] of idsOf(registry)) {
|
|
51
|
+
if (roots.length !== 1) continue;
|
|
52
|
+
const [each] = roots;
|
|
53
|
+
if (workspaceKey(each) === own || !isWorkspace(each)) continue;
|
|
54
|
+
shown.push({ id, root: each, isDefault: workspaceKey(each) === home });
|
|
55
|
+
}
|
|
56
|
+
return [...shown.filter(({ isDefault }) => isDefault), ...shown.filter(({ isDefault }) => !isDefault)];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const oneLine = (value) => (typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "");
|
|
60
|
+
/** A draft's page icon as another workspace's sidebar may show it: an emoji or a glyph of the 图标 tab. */
|
|
61
|
+
export const shownIcon = (value) => (typeof value === "string" && ["emoji", "glyph"].includes(iconKind(value)) ? value : null);
|
|
62
|
+
|
|
63
|
+
const kept = new Map(); // a workspace's key -> { at, value: Promise }
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* What the sidebar reads of workspace `root` (another's): { documents: Set of slugs, order: [slug] (its own
|
|
67
|
+
* sidebar's order, column after column, each entry followed by those under it), parentOf: Map of slug to the
|
|
68
|
+
* document it is under there (null at a column's top), facts: Map of slug to { title, description, icon, path
|
|
69
|
+
* (its doc.html, an old one's report.html) } }. `readOwn(root)`: its outline as its own sidebar has it (no
|
|
70
|
+
* column of any other workspace: ./outline.mjs readOutline with others false). Kept SNAPSHOT_KEPT ms.
|
|
71
|
+
*/
|
|
72
|
+
export function snapshotOf(root, readOwn, { now = Date.now() } = {}) {
|
|
73
|
+
const key = workspaceKey(root);
|
|
74
|
+
const known = kept.get(key);
|
|
75
|
+
if (known && now - known.at < SNAPSHOT_KEPT) return known.value;
|
|
76
|
+
const value = read(root, readOwn);
|
|
77
|
+
kept.set(key, { at: now, value });
|
|
78
|
+
value.catch(() => kept.delete(key));
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Forget what was read of workspace `root` (none: of every workspace), so that the next read reads it again. */
|
|
83
|
+
export function forgetSnapshot(root = null) {
|
|
84
|
+
if (root === null) kept.clear();
|
|
85
|
+
else kept.delete(workspaceKey(root));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function read(root, readOwn) {
|
|
89
|
+
const own = await readOwn(root);
|
|
90
|
+
const documents = new Set();
|
|
91
|
+
const order = [];
|
|
92
|
+
const parentOf = new Map();
|
|
93
|
+
// Its documents alone: its web links, the drafts of others its tree shows and a folder named by no slug are none.
|
|
94
|
+
const isDocument = (key) => !own.links.includes(key) && !key.startsWith("~") && isSlugName(key);
|
|
95
|
+
const parentIn = (child) => {
|
|
96
|
+
for (const [key, list] of Object.entries(own.children)) if (list.includes(child)) return key;
|
|
97
|
+
return null;
|
|
98
|
+
};
|
|
99
|
+
const above = (slug) => {
|
|
100
|
+
let up = parentIn(slug);
|
|
101
|
+
while (up !== null && !(up === "" || up.startsWith(":")) && !isDocument(up)) up = parentIn(up);
|
|
102
|
+
return up === null || up === "" || up.startsWith(":") ? null : up;
|
|
103
|
+
};
|
|
104
|
+
const walk = (key) => (own.children[key] ?? []).forEach((slug) => {
|
|
105
|
+
if (isDocument(slug) && !documents.has(slug)) {
|
|
106
|
+
documents.add(slug);
|
|
107
|
+
order.push(slug);
|
|
108
|
+
parentOf.set(slug, above(slug));
|
|
109
|
+
}
|
|
110
|
+
walk(slug);
|
|
111
|
+
});
|
|
112
|
+
for (const { id } of own.groups) walk(id === "docs" ? "" : `:${id}`);
|
|
113
|
+
const facts = new Map(await Promise.all(order.map(async (slug) => {
|
|
114
|
+
const files = documentFiles(path.join(root, slug));
|
|
115
|
+
const metadata = await fs.readFile(files.json, "utf8").then((text) => JSON.parse(text.replace(/^/, "")), () => null);
|
|
116
|
+
const value = metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : {};
|
|
117
|
+
return [slug, { title: oneLine(value.title) || slug, description: oneLine(value.description), icon: shownIcon(value.icon), path: files.html }];
|
|
118
|
+
})));
|
|
119
|
+
return { documents, order, parentOf, facts };
|
|
120
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { isDeepStrictEqual } from "node:util";
|
|
6
|
+
import { writeAtomic } from "./files.mjs";
|
|
7
|
+
import { objectAt, ownLine } from "./pages.mjs";
|
|
8
|
+
import { documentsFolder, layoutOf, markerOf, WORKSPACE_MARK } from "./workspace.mjs";
|
|
9
|
+
|
|
10
|
+
// What a workspace is called where Gallery names it beside others (gallery workspaces, gallery list
|
|
11
|
+
// --all, and the sidebar's column of another workspace: notes/plans/workspaces-links-plan-2026-11-08.md
|
|
12
|
+
// §0): its own name, `name` in its gallery.json (gallery init --name, gallery workspaces --name, the
|
|
13
|
+
// user in the sidebar); else its folder's name, climbing past a name that says nothing of the project
|
|
14
|
+
// (GENERIC_NAMES: D:\proj\outputs\gallery is "proj") but never to the user's home, their Documents
|
|
15
|
+
// folder or a drive's root (~/Documents/Gallery stays "Gallery"; a workspace that is a drive's root
|
|
16
|
+
// is called by the drive, "E:"). Two of the same name are told apart by the folder above the one
|
|
17
|
+
// named, which the sidebar writes after the name in grey (workspaceLabels), and where that is called
|
|
18
|
+
// alike too, by the workspace's own path; the path is otherwise shown on hover and in the menus, never
|
|
19
|
+
// as the name, and no workspace is numbered. Reads files only, but writeWorkspaceName.
|
|
20
|
+
|
|
21
|
+
/** The folders' names that say nothing of a project, passed over upward (upper and lower case alike). */
|
|
22
|
+
export const GENERIC_NAMES = ["gallery", "docs", "outputs", "reports", "文档"];
|
|
23
|
+
/** The longest name a workspace is given, in characters. */
|
|
24
|
+
export const NAME_LIMIT = 80;
|
|
25
|
+
|
|
26
|
+
const isGeneric = (name) => GENERIC_NAMES.includes(name.toLowerCase());
|
|
27
|
+
const keyOf = (folder, platform) => (platform === "win32" ? path.resolve(folder).toLowerCase() : path.resolve(folder));
|
|
28
|
+
const oneLine = (value) => (typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "");
|
|
29
|
+
// A folder's name; a drive's root, which has none, by the drive ("E:", "/").
|
|
30
|
+
const folderName = (folder) => path.basename(folder) || folder.replace(/[\\/]+$/, "") || folder;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* What a name given to a workspace is wrong in, or null: empty, longer than NAME_LIMIT characters, or
|
|
34
|
+
* holding a control character (a line break among them).
|
|
35
|
+
*/
|
|
36
|
+
export function nameProblem(name) {
|
|
37
|
+
if (typeof name !== "string" || !name.trim()) return "A workspace's name holds a word at least.";
|
|
38
|
+
if (/[\u0000-\u001f\u007f]/.test(name)) return "A workspace's name is one line, with no control character.";
|
|
39
|
+
if ([...name.trim()].length > NAME_LIMIT) return `A workspace's name is ${NAME_LIMIT} characters at most.`;
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The workspace's own name at `root` (gallery.json's `name`, on one line), or null: none, or a gallery.json that is not ours or does not read. */
|
|
44
|
+
export function ownName(root, { readText = (file) => readFileSync(file, "utf8") } = {}) {
|
|
45
|
+
let text;
|
|
46
|
+
try {
|
|
47
|
+
text = readText(layoutOf(root).marker);
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const { value, ours } = markerOf(text);
|
|
52
|
+
const name = ours ? oneLine(value?.name) : "";
|
|
53
|
+
return name ? [...name].slice(0, NAME_LIMIT).join("") : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The folders no name climbs to: the user's home and their Documents folder (each workspace's name is found against these). */
|
|
57
|
+
export function nameStops({ home = os.homedir(), documents = documentsFolder({ home }) } = {}) {
|
|
58
|
+
return [home, documents].filter(Boolean);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The folder whose name a workspace at `root` goes by when it has none of its own: `root`, or the
|
|
63
|
+
* nearest folder above it whose name is not generic, climbing no higher than below one of `stops` and
|
|
64
|
+
* never to a drive's root (a folder there keeps its name, generic or not).
|
|
65
|
+
*/
|
|
66
|
+
export function namedFolder(root, { stops = [], platform = process.platform } = {}) {
|
|
67
|
+
const stopped = new Set(stops.map((stop) => keyOf(stop, platform)));
|
|
68
|
+
let folder = path.resolve(root);
|
|
69
|
+
for (;;) {
|
|
70
|
+
const parent = path.dirname(folder);
|
|
71
|
+
const atRoot = parent === folder || path.dirname(parent) === parent;
|
|
72
|
+
if (!isGeneric(path.basename(folder)) || atRoot || stopped.has(keyOf(parent, platform))) return folder;
|
|
73
|
+
folder = parent;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* What the workspace at `root` is called: { name, own: whether it is gallery.json's name (else its
|
|
79
|
+
* folder's), folder: the folder named (the workspace's own for a name of its own), above: that
|
|
80
|
+
* folder's parent's name (what tells two of the same name apart), parent: that parent's path }.
|
|
81
|
+
*/
|
|
82
|
+
export function workspaceName(root, { stops = [], platform = process.platform, readText } = {}) {
|
|
83
|
+
const own = ownName(root, readText ? { readText } : undefined);
|
|
84
|
+
const folder = own ? path.resolve(root) : namedFolder(root, { stops, platform });
|
|
85
|
+
const parent = path.dirname(folder);
|
|
86
|
+
return { name: own ?? folderName(folder), own: Boolean(own), folder, above: parent === folder ? null : path.basename(parent) || parent, parent };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The names of several workspaces as the sidebar and the commands write them side by side: each of
|
|
91
|
+
* `named` (workspaceName's, with its `root`, its path) as { root, name, own, above }, `above` null
|
|
92
|
+
* but where another has the same name (upper and lower case alike): then the name of the folder above
|
|
93
|
+
* the one named, and where another of them has that too (two workspaces side by side, or two in one
|
|
94
|
+
* project climbing to its folder), or there is none (a drive's root), the workspace's own path, which
|
|
95
|
+
* no other has.
|
|
96
|
+
*/
|
|
97
|
+
export function workspaceLabels(named) {
|
|
98
|
+
const same = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
99
|
+
return named.map((entry) => {
|
|
100
|
+
const label = { root: entry.root, name: entry.name, own: entry.own, above: null };
|
|
101
|
+
const others = named.filter((other) => other !== entry && same(other.name, entry.name));
|
|
102
|
+
if (!others.length) return label;
|
|
103
|
+
const told = entry.above !== null && !others.some((other) => other.above !== null && same(other.above, entry.above));
|
|
104
|
+
return { ...label, above: told ? entry.above : entry.root };
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* gallery.json at `root` with `name` as the workspace's name (namedIn: in place of the one it had,
|
|
110
|
+
* else right after what marks it; all else it holds kept, and the file's own layout with it), written
|
|
111
|
+
* at once; the workspace must be there and its gallery.json ours (ensureWorkspace first). Resolves to
|
|
112
|
+
* { name: as written, before: the name it had, or null }; throws an Error saying why when the name is
|
|
113
|
+
* wrong (nameProblem), the file is not ours or does not read, or it kept changing under the write.
|
|
114
|
+
*/
|
|
115
|
+
export async function writeWorkspaceName(root, name) {
|
|
116
|
+
const problem = nameProblem(name);
|
|
117
|
+
if (problem) throw new Error(problem);
|
|
118
|
+
const wanted = name.trim();
|
|
119
|
+
const file = layoutOf(root).marker;
|
|
120
|
+
for (let tries = 0; tries < 3; tries++) {
|
|
121
|
+
const text = await fs.readFile(file, "utf8");
|
|
122
|
+
const { value, ours, reads } = markerOf(text);
|
|
123
|
+
if (!ours || !value || typeof value !== "object" || Array.isArray(value)) throw new Error(reads ? `${file} is not Gallery's.` : `${file} does not read as JSON: mend it first.`);
|
|
124
|
+
const before = oneLine(value.name) || null;
|
|
125
|
+
if (value.name === wanted) return { name: wanted, before };
|
|
126
|
+
let next = null;
|
|
127
|
+
try {
|
|
128
|
+
next = namedIn(text, wanted);
|
|
129
|
+
} catch {}
|
|
130
|
+
// What the text says once written is checked: any doubt, the whole file written as JSON.stringify lays it out.
|
|
131
|
+
if (next === null || !isDeepStrictEqual(markerOf(next).value, { ...value, name: wanted })) {
|
|
132
|
+
const { gallery, version, name: _old, ...rest } = value;
|
|
133
|
+
next = `${JSON.stringify({ gallery: gallery ?? WORKSPACE_MARK.gallery, version: version ?? WORKSPACE_MARK.version, name: wanted, ...rest }, null, 2)}\n`;
|
|
134
|
+
}
|
|
135
|
+
if (await writeAtomic(file, next, { expect: text })) return { name: wanted, before };
|
|
136
|
+
}
|
|
137
|
+
throw new Error(`${file} kept changing while its name was written: try again.`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// gallery.json's text (`text`, Gallery's, a JSON object that reads) with `name` as its name, written
|
|
141
|
+
// into it as it stands, as ./pages.mjs writes pages.homeMade (its line ends, indentation, BOM, the order
|
|
142
|
+
// of its keys and what stands on one line kept): the name it had replaced in its place, else one added
|
|
143
|
+
// right after the mark (`gallery`, or `version` where that comes after it), laid out as that member is
|
|
144
|
+
// (on a line of its own at its indentation, or on its line after it, as tightly).
|
|
145
|
+
function namedIn(text, name) {
|
|
146
|
+
const top = objectAt(text, text.indexOf("{"));
|
|
147
|
+
const had = top.members.findLast(({ key }) => key === "name");
|
|
148
|
+
if (had) return `${text.slice(0, had.value)}${JSON.stringify(name)}${text.slice(had.end)}`;
|
|
149
|
+
const mark = top.members.filter(({ key }) => key === "gallery" || key === "version").at(-1);
|
|
150
|
+
if (!mark) return null;
|
|
151
|
+
const indent = ownLine(text, top.open, mark);
|
|
152
|
+
const gap = indent !== null ? (text.includes("\r\n") ? "\r\n" : "\n") + indent : /[ \t]*$/.exec(text.slice(0, mark.from))[0];
|
|
153
|
+
return `${text.slice(0, mark.end)},${gap}"name":${mark.colon}${JSON.stringify(name)}${text.slice(mark.end)}`;
|
|
154
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { CHART_KEY } from "./table-chart.mjs";
|
|
2
|
+
|
|
3
|
+
// What a block reads of the workspace (the spec custom-blocks-spec-2026-11-07.md §3, §4; docs/render.md
|
|
4
|
+
// "A custom block on a page", docs/workbench.md "The workspace's facts"): the kinds of
|
|
5
|
+
// context.workspace.query, how many items each gives, the options each takes, and where the kit asks for
|
|
6
|
+
// them, the one list the kit's runtime (render/kit/runtime/workspace.js), the server's facts
|
|
7
|
+
// (src/workbench/index.mjs) and the event stream's "data <kind>" (src/server/routes.mjs) go by; and how a
|
|
8
|
+
// block checks a to-do of another document (workspace.setTodo: the plan's 5.1), what an export embeds of
|
|
9
|
+
// the workspace (the spec §9). Pure: no DOM, no Node.
|
|
10
|
+
|
|
11
|
+
/** The kinds workspace.query answers: the workspace's facts, and `table`, a keyed table of a document (the plan's 5.2). */
|
|
12
|
+
export const QUERY_KINDS = Object.freeze(["todos", "recent", "stats", "activity", "table"]);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The kinds of the workspace's facts a change may change, each said to the pages as "data <kind>":
|
|
16
|
+
* `table` whenever a document's text changed (any keyed table of it may have).
|
|
17
|
+
*/
|
|
18
|
+
export const FACT_KINDS = Object.freeze(["todos", "recent", "stats", "activity", "table"]);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The codes workspace.query rejects with (the spec §3), never another: the server's own refusals of a query
|
|
22
|
+
* (kind, options, missing: no such document, or no table of that key in it, or one with merged cells; and
|
|
23
|
+
* unsupported, which no server of this version says) as it says them; offline where there is no server to ask
|
|
24
|
+
* (an export without that kind in its snapshot) or it does not answer; failed where it answered and could
|
|
25
|
+
* not (a read that failed, a request it refused, no JSON).
|
|
26
|
+
*/
|
|
27
|
+
export const QUERY_CODES = Object.freeze(["kind", "options", "unsupported", "missing", "offline", "failed"]);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* How many items a query gives where its options say none, and the most it gives: to-dos, documents edited
|
|
31
|
+
* last, days of the record (as many as it keeps, src/workbench/activity.mjs KEEP_DAYS: the 编辑热力图's 53
|
|
32
|
+
* weeks), rows of a table.
|
|
33
|
+
*/
|
|
34
|
+
export const QUERY_LIMITS = Object.freeze({ todos: { default: 200, most: 1000 }, recent: { default: 10, most: 50 }, activity: { default: 365, most: 371 }, table: { default: 200, most: 1000 } });
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* What an export embeds of the workspace for a document whose blocks read it (the spec §9: its snapshot's
|
|
38
|
+
* kinds beside `net`), each kind's whole answer to so many at most: to-dos (done or not), documents edited
|
|
39
|
+
* last, days of the record, and of the tables its instances asked for (the last `tables` of them), rows each.
|
|
40
|
+
*/
|
|
41
|
+
export const SNAPSHOT_LIMITS = Object.freeze({ todos: 200, recent: 50, activity: 371, tables: 20, rows: 1000 });
|
|
42
|
+
|
|
43
|
+
/** Where a page asks for its to-dos to be checked (POST { slug, items: [{ ref, done }] }, the token's: the workspace's service). */
|
|
44
|
+
export const TODO_ROUTE = "/__gallery/workspace/todo";
|
|
45
|
+
|
|
46
|
+
/** What workspace.setTodo asks whoever shows the page (cancelable, on the window: { ask: { slug, ref, done }, answer(promise) }). */
|
|
47
|
+
export const TODO_EVENT = "doc-todo-set";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The codes workspace.setTodo rejects with, never another: ref (not a to-do's ref as query gave it, or
|
|
51
|
+
* `done` no true or false), changed (the item is not as the ref read it: its words changed, or it is gone:
|
|
52
|
+
* the conflict, which the block reads again after), ambiguous (no block id, the file changed since, and
|
|
53
|
+
* more than one item of its words under its headings), locked (the item is there as read, in a place the
|
|
54
|
+
* document's own page cannot check: a list in a table cell, a block of the document's own), missing (no such document), busy (the file kept
|
|
55
|
+
* changing, or stayed half written), offline (nothing on the page writes the workspace: an exported file,
|
|
56
|
+
* the 演示视图, a page on its own without a token; or the server does not answer), failed (anything else).
|
|
57
|
+
*/
|
|
58
|
+
export const TODO_CODES = Object.freeze(["ref", "changed", "ambiguous", "locked", "missing", "busy", "offline", "failed"]);
|
|
59
|
+
|
|
60
|
+
/** How many to-dos one write of a document carries at most (the page merges what waits into one). */
|
|
61
|
+
export const TODO_MOST = 50;
|
|
62
|
+
|
|
63
|
+
const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
64
|
+
const BLOCK = /^[A-Za-z0-9_-]{1,64}$/;
|
|
65
|
+
const VERSION = /^[a-f0-9]{16}$/;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* `ref` as a write carries it, when it is a to-do's ref as workspace.query("todos") gives it ({ slug,
|
|
69
|
+
* block, text, path, nth, version }: `version` the hash of the file the facts read it from, which may be
|
|
70
|
+
* missing), or null.
|
|
71
|
+
*/
|
|
72
|
+
export function todoRef(ref) {
|
|
73
|
+
if (!ref || typeof ref !== "object" || Array.isArray(ref)) return null;
|
|
74
|
+
const { slug, block = null, text, path, nth, version = null } = ref;
|
|
75
|
+
if (typeof slug !== "string" || !SLUG.test(slug) || (block !== null && (typeof block !== "string" || !BLOCK.test(block)))) return null;
|
|
76
|
+
if (typeof text !== "string" || text.length > 4000 || !Array.isArray(path) || path.length > 6 || !path.every((step) => typeof step === "string" && step.length <= 4000)) return null;
|
|
77
|
+
if (!Number.isInteger(nth) || nth < 0 || (version !== null && (typeof version !== "string" || !VERSION.test(version)))) return null;
|
|
78
|
+
return { slug, block, text, path: [...path], nth, version };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Where a query is asked (GET, ?kind=<kind>&options=<JSON>): the workspace's service. */
|
|
82
|
+
export const QUERY_ROUTE = "/__gallery/workspace/query";
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The global a page the server serves holds QUERY_ROUTE in (src/server/routes.mjs, a script of the
|
|
86
|
+
* server's that no export keeps), which the kit's runtime asks: none there, the workspace is offline.
|
|
87
|
+
*/
|
|
88
|
+
export const WORKSPACE_GLOBAL = "__galleryWorkspace";
|
|
89
|
+
|
|
90
|
+
/** What workspace.open asks whoever shows the page to open (a cancelable event on the window, detail { slug, block } or { page }). */
|
|
91
|
+
export const OPEN_EVENT = "doc-open-request";
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The options of a query of `kind` as they are used: { options } (todos: done, slugs, limit; recent:
|
|
95
|
+
* limit; activity: days; every count held to its most), or { problem } (what is wrong, in English).
|
|
96
|
+
*/
|
|
97
|
+
export function queryOptions(kind, options) {
|
|
98
|
+
const given = options ?? {};
|
|
99
|
+
if (typeof given !== "object" || Array.isArray(given)) return { problem: "its options are an object" };
|
|
100
|
+
const count = (name, { default: fallback, most }) => {
|
|
101
|
+
const value = given[name];
|
|
102
|
+
if (value === undefined) return fallback;
|
|
103
|
+
if (!Number.isInteger(value) || value < 1) throw new Error(`${name} is a whole number from 1`);
|
|
104
|
+
return Math.min(value, most);
|
|
105
|
+
};
|
|
106
|
+
try {
|
|
107
|
+
if (kind === "todos") {
|
|
108
|
+
if (given.done !== undefined && typeof given.done !== "boolean") return { problem: "done is true or false" };
|
|
109
|
+
if (given.slugs !== undefined && !(Array.isArray(given.slugs) && given.slugs.every((slug) => typeof slug === "string"))) return { problem: "slugs is a list of documents' slugs" };
|
|
110
|
+
return { options: { done: given.done, slugs: given.slugs ?? null, limit: count("limit", QUERY_LIMITS.todos) } };
|
|
111
|
+
}
|
|
112
|
+
if (kind === "recent") return { options: { limit: count("limit", QUERY_LIMITS.recent) } };
|
|
113
|
+
if (kind === "table") {
|
|
114
|
+
if (typeof given.slug !== "string" || !SLUG.test(given.slug)) return { problem: "slug is a document's slug" };
|
|
115
|
+
if (typeof given.key !== "string" || !CHART_KEY.test(given.key)) return { problem: "key is a table's key (data-doc-chart-source)" };
|
|
116
|
+
const where = given.where ?? {};
|
|
117
|
+
if (typeof where !== "object" || Array.isArray(where) || !Object.values(where).every((value) => typeof value === "string")) return { problem: "where maps each column to the words its cells are to hold" };
|
|
118
|
+
return { options: { slug: given.slug, key: given.key, where: { ...where }, limit: count("limit", QUERY_LIMITS.table) } };
|
|
119
|
+
}
|
|
120
|
+
if (kind === "activity") return { options: { days: count("days", QUERY_LIMITS.activity) } };
|
|
121
|
+
return { options: {} };
|
|
122
|
+
} catch (error) {
|
|
123
|
+
return { problem: error.message };
|
|
124
|
+
}
|
|
125
|
+
}
|