@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
|
@@ -137,9 +137,16 @@ export const EVENTS = {
|
|
|
137
137
|
"doc-theme-request": "a workbench asks for a theme (ui.js listens)",
|
|
138
138
|
"doc-look-request": "the document's look is asked of the page (字体与布局: the page's session for the workbench, the preview's live reload for doc.json written again; ui.js puts it on <html>, src/shared/document-look.mjs)",
|
|
139
139
|
"doc-look-change": "the look the page shows changed (ui.js): the page editor's session tells the workbench",
|
|
140
|
-
"doc-layout-change": "a card's size changed where widths do not tell (the editor), or the document's look changed (ui.js)",
|
|
140
|
+
"doc-layout-change": "a card's size changed where widths do not tell (the editor, a custom page's canvas), or the document's look changed (ui.js)",
|
|
141
|
+
"doc-workspace-change": "what the workspace holds changed, the kind its detail (the page's live script, as the server's \"data <kind>\" says it): the kit tells the instances that hear data and read that kind",
|
|
142
|
+
"doc-todo-set": "a block's context.workspace.setTodo asks whoever shows the page to check a to-do of a document (cancelable: a custom page's board or a document's editor cancels it and answers from the workspace's service)",
|
|
143
|
+
"doc-open-request": "a block's context.workspace.open asks to open a document or a custom page (cancelable: the page's session for the workbench answers it, else the kit goes there)",
|
|
141
144
|
"doc-editable": "the page takes what a component writes from now on (the editor)",
|
|
142
145
|
"doc-component-edit": "a component's context.edit asks to write a part: the editor cancels it to say it took the edit, the kit writing no file (on the instance)",
|
|
146
|
+
"doc-table-intent": "a view's context.table() asks to change the table it reads (set a cell, move, add or take out a row), or whether it would take such a change now (canEdit, op check): the editor cancels it to say it made the change (or would), one undo step, the kit writing nothing (on the instance)",
|
|
147
|
+
"doc-settings-changed": "what gallery.json says of the components changed (the page's event stream, its settings line: hidden, disabled, shown, the consents to the net, unlisted and the packs off): the editor's menus follow it, and the kit starts again an instance whose block's consent changed (runtime/net.js)",
|
|
148
|
+
"doc-net-fetch": "a block's context.fetch asks whoever serves the page to fetch for it (runtime/net.js sends it; the server's live script cancels it and answers from its net route)",
|
|
149
|
+
"doc-net-consent": "a block's 允许访问 asks the reader to let it online (runtime/net.js sends it; the page's session cancels it and the workbench opens its consent dialog)",
|
|
143
150
|
"doc-export-copy-done": "导出当前视图副本 finished (ui.js)",
|
|
144
151
|
"doc-table-sort": "a table's sort changed (tables/, on the table)",
|
|
145
152
|
"doc-jump-request": "演示视图 asks the kit to go to a block of its own page, a step of the history (runtime/reveal.js listens, editor/page/present-links.mjs sends)",
|
|
@@ -171,9 +178,11 @@ export const FLOW_BLOCKS = `${FLOWS.join(", ")}, :is(${GROUPING_FLOWS.join(", ")
|
|
|
171
178
|
* What the kit's runtime reads of the descriptions (runtime/kernel.js): where each component's
|
|
172
179
|
* instances are (`match`) and what it leaves on the page (`runtime`), of the components that say
|
|
173
180
|
* either. /kit/runtime.js carries these alone (assets.mjs, `define`), so an export holds no prose.
|
|
174
|
-
* A field the kernel starts reading is added to RUNTIME_FIELDS (tests/unit/runtime.mjs checks it)
|
|
181
|
+
* A field the kernel starts reading is added to RUNTIME_FIELDS (tests/unit/runtime.mjs checks it):
|
|
182
|
+
* `needs`, what a custom block uses (its context.workspace), which only a package describes (no
|
|
183
|
+
* component of the kit has it, so the runtime carries none).
|
|
175
184
|
*/
|
|
176
|
-
export const RUNTIME_FIELDS = ["match", "runtime", "schema"];
|
|
185
|
+
export const RUNTIME_FIELDS = ["match", "runtime", "schema", "needs"];
|
|
177
186
|
export const runtimeDescriptions = (components = COMPONENTS) => components
|
|
178
187
|
.filter((component) => RUNTIME_FIELDS.some((field) => component[field] !== undefined))
|
|
179
188
|
.map((component) => Object.fromEntries([["id", component.id], ...RUNTIME_FIELDS.filter((field) => component[field] !== undefined).map((field) => [field, component[field]])]));
|
|
@@ -50,6 +50,19 @@ export function heldBy(node) {
|
|
|
50
50
|
return owner !== null && edgeAround(edge) === owner ? null : owner;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* The region what an element holds stands in (its children, its words): the instance itself for an
|
|
55
|
+
* instance, the document's (null) inside a slot that opens it again, and otherwise the region the
|
|
56
|
+
* element stands in, as heldBy says it. heldBy for a child the element does not have yet, or has lost:
|
|
57
|
+
* where a change of what it holds happened.
|
|
58
|
+
*/
|
|
59
|
+
export function heldWithin(element) {
|
|
60
|
+
if (!element.matches(REGION)) return heldBy(element);
|
|
61
|
+
const owner = heldBy(element);
|
|
62
|
+
if (element.matches(INSTANCE)) return owner ?? element;
|
|
63
|
+
return owner !== null && edgeAround(element) === owner ? null : owner;
|
|
64
|
+
}
|
|
65
|
+
|
|
53
66
|
/** Whether an element is a slot that opens the document's region again: one of an instance that stands in the document's. */
|
|
54
67
|
const opensDocument = (node) => node.matches(SLOT_PART) && heldBy(node) !== null && edgeAround(node) === heldBy(node);
|
|
55
68
|
|
|
@@ -110,8 +123,24 @@ export const PACKAGE_MARK = "data-doc-package";
|
|
|
110
123
|
*/
|
|
111
124
|
export const ARRIVING = "data-ge-arriving";
|
|
112
125
|
|
|
113
|
-
/**
|
|
114
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Whether the document's insert entries ("/", 在下方新建, the handle's grid, a table's 视图 ›) offer the
|
|
128
|
+
* block `id` whose manifest's `places` are those (left out: ["document"]), by gallery.json's lists
|
|
129
|
+
* (`settings`: hidden, shown, disabled, unlisted, packsOff, each left out for none): one that says it stands in a
|
|
130
|
+
* document is offered unless hidden; one that does not, only where shown; one disabled never, as it does
|
|
131
|
+
* not load, nor one unlisted (设置 › 组件列表's master switch off: no menu and no library offers it, what
|
|
132
|
+
* is written of it shows as ever), nor one of a pack switched off (packsOff, the ids of the packs off: the
|
|
133
|
+
* scope of its id; the same as unlisted, for a whole pack). The one rule, which the server and the page
|
|
134
|
+
* both run (notes/plans/custom-blocks-spec-2026-11-07.md section 2, block-packs-plan-2026-11-09.md §1).
|
|
135
|
+
*/
|
|
136
|
+
export function inDocumentMenus(places, settings, id) {
|
|
137
|
+
const { hidden = [], shown = [], disabled = [], unlisted = [], packsOff = [] } = settings ?? {};
|
|
138
|
+
if (disabled.includes(id) || unlisted.includes(id) || packsOff.includes(String(id).split("/")[0])) return false;
|
|
139
|
+
return (places ?? ["document"]).includes("document") ? !hidden.includes(id) : shown.includes(id);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** What gallery.json's lists make of a package that loads, its manifest's `places` with them: "disabled", "hidden" (no document's menu offers it: inDocumentMenus, which a block unlisted is too) or "normal". */
|
|
143
|
+
export const settingState = (id, settings, places) => (settings.disabled.includes(id) ? "disabled" : inDocumentMenus(places, settings, id) ? "normal" : "hidden");
|
|
115
144
|
|
|
116
145
|
/** The class of the kit's note in such an instance, in a preview alone. */
|
|
117
146
|
export const NOTE = "doc-component-note";
|
|
@@ -223,10 +252,13 @@ export default {
|
|
|
223
252
|
"data-doc-component": "the package an instance is of: <scope>/<name>, on a block-level div or section",
|
|
224
253
|
"data-doc-schema": "the shape an instance holds its content in: a whole number its component chooses, read-only past its manifest's schema",
|
|
225
254
|
"data-doc-slot": "a slot of an instance: the part of its manifest edited as flow, by name, on a block-level div or section in the instance's own region; what it holds is the document's blocks",
|
|
255
|
+
"data-doc-from": "on an instance of a custom block whose needs hold tables: the key of the table it is a view of, which the table's frame carries as data-doc-chart-source",
|
|
226
256
|
},
|
|
227
257
|
runtime: {
|
|
228
258
|
generated: [".doc-component-note", ".doc-component-data"],
|
|
229
259
|
attrs: { "[data-doc-fallback]": ["data-doc-fallback"], "[data-doc-fallback-control]": ["data-doc-fallback-control", "inert"] },
|
|
230
|
-
|
|
260
|
+
// Of what the kit drew of a script of data: a line saying what it left out, and its marks; and the mark
|
|
261
|
+
// DocKit.drag (runtime/gestures.js) puts on what a component draws while it is dragged, a class.
|
|
262
|
+
internal: [PACKAGE_MARK, "doc-component-more", DATA_ONE, DATA_LINE, DATA_SHORT, "doc-dragging"],
|
|
231
263
|
},
|
|
232
264
|
};
|
|
@@ -15,10 +15,12 @@ export function pageLanguage(html) {
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* A document's look on its <html> (字体与布局: its doc.json's look, which the render puts on and the kit's
|
|
18
|
-
* tokens read; src/shared/document-look.mjs), the attribute of each choice. The
|
|
19
|
-
* data-doc-measure (a column's measure, its line length)
|
|
18
|
+
* tokens read; src/shared/document-look.mjs), the attribute of each choice. The 正文宽度's is
|
|
19
|
+
* data-doc-measure (a column's measure, its line length); 全宽 (the top bar's 更多, under 目录) is
|
|
20
|
+
* data-doc-width="full", the one attribute the kit's page, the editor's room and the display menu's
|
|
21
|
+
* 全宽显示 (the reader's own switch in an export) all read: one way to be wide, not two.
|
|
20
22
|
*/
|
|
21
|
-
export const LOOK_ATTRIBUTES = Object.freeze({ font: "data-doc-font", size: "data-doc-size", spacing: "data-doc-spacing", width: "data-doc-measure" });
|
|
23
|
+
export const LOOK_ATTRIBUTES = Object.freeze({ font: "data-doc-font", size: "data-doc-size", spacing: "data-doc-spacing", width: "data-doc-measure", full: "data-doc-width" });
|
|
22
24
|
|
|
23
25
|
export default {
|
|
24
26
|
id: "page",
|
|
@@ -37,10 +39,18 @@ export default {
|
|
|
37
39
|
classes: ["doc-page", "doc-header", "doc-kicker", "doc-title", "doc-button"],
|
|
38
40
|
runtime: {
|
|
39
41
|
generated: [".doc-display", "script[data-doc-state]"],
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// data-doc-effective-theme: the theme the page shows now (ui.js), "light" or "dark", which a custom page's own colors go by.
|
|
43
|
+
// data-doc-platform: "mac" on an Apple platform (runtime/index.js sets it as the page opens), the text's color and smoothing go by it; a copy of the view lets it go, its reader's machine judges anew.
|
|
44
|
+
root: { attrs: ["data-doc-ready", "data-doc-drawn", "data-doc-controls", "data-doc-effective-theme", "data-doc-platform"] },
|
|
45
|
+
// The reader's theme (ui.js) and the document's look (字体与布局 and 全宽: its doc.json's, put on by
|
|
46
|
+
// the render, src/shared/document-look.mjs; the reader's own 全宽显示 is the same data-doc-width), which
|
|
47
|
+
// a copy of the view keeps; never written in doc.html.
|
|
48
|
+
state: { html: ["data-doc-theme", ...Object.values(LOOK_ATTRIBUTES)] },
|
|
49
|
+
internal: ["doc-display*", "data-doc-export-copy", "data-theme", "doc-export-id", "doc-reveal", "doc-kit-error", "doc-net-consent*", "data-doc-snapshot",
|
|
50
|
+
// The time an export's snapshot was made (its data-doc-snapshot tag's: render/net-snapshot.mjs), which context.workspace.query answers as `at`.
|
|
51
|
+
"data-at",
|
|
52
|
+
// A custom page's (no document's: src/render/page-html.mjs, the kit's board/): its cards' cells, the Agent's
|
|
53
|
+
// (data-x, data-y, data-w, data-h), and its boards' columns now (data-columns, board.js's).
|
|
54
|
+
"data-x", "data-y", "data-w", "data-h", "data-columns"],
|
|
45
55
|
},
|
|
46
56
|
};
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// a key (data-doc-bind), elements shown for a key's value (data-doc-show),
|
|
3
3
|
// keys forgotten when the page is left (data-doc-transient); and the kit's styles of such
|
|
4
4
|
// controls (styles/state.css). A filter (.doc-filter) is one block: its row of buttons and what
|
|
5
|
-
// they filter below it (a list, say), together.
|
|
5
|
+
// they filter below it (a list, say), together. A <select> of any control or block, bound or not, opens
|
|
6
|
+
// the kit's menu of choices (runtime/choice.js, styles/choice.css), never the browser's popup: a card at
|
|
7
|
+
// the end of <body> while it is open, nothing an author writes, which an export of the view leaves out.
|
|
6
8
|
export default {
|
|
7
9
|
id: "state",
|
|
8
10
|
match: "[data-doc-bind], [data-doc-show]",
|
|
@@ -14,7 +16,9 @@ export default {
|
|
|
14
16
|
classes: ["doc-state-fields", "doc-state-field", "doc-state-actions", "doc-filter", "doc-filter-row", "doc-filter-button", "doc-state-list"],
|
|
15
17
|
wholes: ["doc-filter", "doc-filter-row", "doc-state-fields", "doc-state-list"], // the classes above that are a whole block of their own (the others are parts inside one)
|
|
16
18
|
runtime: {
|
|
19
|
+
generated: [".doc-choice"],
|
|
17
20
|
attrs: { "[data-doc-show]": ["hidden"] },
|
|
18
21
|
state: { "button[data-doc-bind]": ["aria-pressed"] },
|
|
22
|
+
internal: ["doc-choice-*"], // the menu's rows, their words, the check and the group headings
|
|
19
23
|
},
|
|
20
24
|
};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
// A document's look (
|
|
2
|
-
// view/frame/page-format.js
|
|
3
|
-
// the
|
|
1
|
+
// A document's look (字体与布局 and 全宽, under the workbench's 更多: Notion's page format, notion-lite's
|
|
2
|
+
// view/frame/page-format.js, whose page_full_width is a field of the page as ours is of the document):
|
|
3
|
+
// its font, the size of its text, the spacing of its lines and blocks, the width of its reading column
|
|
4
|
+
// and whether it is shown at the full width of the page whatever that is (全宽). The document's own, as
|
|
5
|
+
// Notion keeps it on the page: `look` in its
|
|
4
6
|
// doc.json (report.json in a document made before Gallery 0.1), over the workspace's default look,
|
|
5
7
|
// `look` in its gallery.json (which the card's 应用到全部文档 writes, every document's own taken away:
|
|
6
8
|
// ./workspace-look.mjs), choice by choice (effectiveLook). The workspace's keeps each choice that is
|
|
7
9
|
// not the default, the field gone when none is; a doc.json each choice that is not the workspace's,
|
|
8
10
|
// the field gone when none is:
|
|
9
11
|
//
|
|
10
|
-
// "look": { "font": "serif" | "mixed", "size": "small" | "large", "spacing": "compact" | "loose", "width": "narrow" | "wide" }
|
|
12
|
+
// "look": { "font": "serif" | "mixed", "size": "small" | "large", "spacing": "compact" | "loose", "width": "narrow" | "wide", "full": "full" }
|
|
11
13
|
//
|
|
12
14
|
// A doc.json may say a default too ("font": "sans", "size": "default"…): 默认 chosen on a page while
|
|
13
15
|
// the workspace's default says otherwise. The look a document shows is never written in doc.html: the
|
|
@@ -26,6 +28,9 @@ export const LOOK = Object.freeze({
|
|
|
26
28
|
size: Object.freeze(["default", "small", "large"]), // 字体大小: 小号, 默认, 大号
|
|
27
29
|
spacing: Object.freeze(["default", "compact", "loose"]), // 正文间距: 紧凑, 默认, 宽松
|
|
28
30
|
width: Object.freeze(["default", "narrow", "wide"]), // 正文宽度: 偏窄, 默认, 偏宽
|
|
31
|
+
// 全宽 (the 更多 menu's switch under 目录): the page at the full width of its area, whatever its 正文宽度 is; its attribute is
|
|
32
|
+
// the kit's own data-doc-width="full", the reader's display menu's 全宽显示 too (components/page.mjs LOOK_ATTRIBUTES)
|
|
33
|
+
full: Object.freeze(["default", "full"]),
|
|
29
34
|
});
|
|
30
35
|
|
|
31
36
|
/** The attribute each choice is on <html> by: the page's description says them (components/page.mjs). */
|
|
@@ -80,7 +85,7 @@ export function lookOf(value) {
|
|
|
80
85
|
return look;
|
|
81
86
|
}
|
|
82
87
|
|
|
83
|
-
/** Each choice of a look, the default where it makes none: { font, size, spacing, width }. */
|
|
88
|
+
/** Each choice of a look, the default where it makes none: { font, size, spacing, width, full }. */
|
|
84
89
|
export const fullLook = (value) => ({ ...Object.fromEntries(Object.entries(LOOK).map(([key, [first]]) => [key, first])), ...lookOf(value) });
|
|
85
90
|
|
|
86
91
|
/** Whether two looks are the same. */
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { descendantsIn, outlineAndFile, parentIn, placeIn, readOutline, withOutlineLock, writeOutline, writtenAs } from "./outline.mjs";
|
|
6
|
+
import { DOCS, GROUP_LIMITS, isForeign, isTop, newGroupId, topIn, topOf } from "./outline-shape.mjs";
|
|
7
|
+
import { findPreviewServer } from "./preview-server.mjs";
|
|
8
|
+
import { idsOf, shownIcon } from "./workspace-columns.mjs";
|
|
9
|
+
import { nameStops, workspaceLabels, workspaceName } from "./workspace-name.mjs";
|
|
10
|
+
import { isWorkspace, readRegistry, workspaceId, workspaceKey } from "./workspace-registry.mjs";
|
|
11
|
+
import { defaultWorkspace, documentFiles, isSlugName, listDocumentSlugs } from "./workspace.mjs";
|
|
12
|
+
|
|
13
|
+
// The drafts of other workspaces in a workspace's sidebar (notes/plans/workspaces-links-plan-2026-11-08.md
|
|
14
|
+
// §2.2, §2.5, §7.2; docs/workbench.md "Drafts of other workspaces"): an entry of the outline written
|
|
15
|
+
// "~<ws id>/<slug>" (./outline-shape.mjs), the workspace's id its path's hash on the machine's registry
|
|
16
|
+
// (./workspace-registry.mjs), the draft its document <root>/<slug>/doc.html there. Only where the sidebar
|
|
17
|
+
// shows it is this workspace's to keep: the draft's folder never moves, and its own workspace's tree is
|
|
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.
|
|
25
|
+
// A draft whose workspace is not on the registry, two registered folders sharing its id (six hex digits
|
|
26
|
+
// of a hash: a clash is refused, never guessed), or whose folder is gone stays in the tree, shown grey
|
|
27
|
+
// (找不到底稿) until the user removes it. Reads other workspaces' files only; writes this one's outline.
|
|
28
|
+
|
|
29
|
+
/** A draft's entry in the outline. */
|
|
30
|
+
export const DRAFT_ENTRY = /^~(ws-[0-9a-f]{6})\/([a-z0-9]+(?:-[a-z0-9]+)*)$/;
|
|
31
|
+
export const draftEntry = (id, slug) => `~${id}/${slug}`;
|
|
32
|
+
/** The workspace's id and the slug a draft's entry names, or null for an entry that names none. */
|
|
33
|
+
export function draftOf(entry) {
|
|
34
|
+
const match = DRAFT_ENTRY.exec(String(entry ?? ""));
|
|
35
|
+
return match ? { ws: match[1], slug: match[2] } : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const oneLine = (value) => (typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "");
|
|
39
|
+
const exists = (target) => fs.access(target).then(() => true, () => false);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The registered workspaces by their ids: a Map of id to the roots registered under it (two for one id:
|
|
43
|
+
* a clash of the hash's six digits, which names neither: workspaceOf). `registry`: readRegistry()'s.
|
|
44
|
+
*/
|
|
45
|
+
export const registeredIds = (registry = readRegistry()) => idsOf(registry);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The workspace `id` names on the registry: { root }, or { refused: "unknown" (no registered workspace
|
|
49
|
+
* of that id: forgotten, or never registered) | "ambiguous" (two registered folders share it), roots }.
|
|
50
|
+
*/
|
|
51
|
+
export function workspaceOf(id, ids = registeredIds()) {
|
|
52
|
+
const roots = ids.get(id) ?? [];
|
|
53
|
+
if (roots.length === 1) return { root: roots[0] };
|
|
54
|
+
return { refused: roots.length ? "ambiguous" : "unknown", roots };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The ids two or more registered folders share: [{ id, roots }] (gallery workspaces and the sidebar say so). */
|
|
58
|
+
export const clashingIds = (ids = registeredIds()) => [...ids].filter(([, roots]) => roots.length > 1).map(([id, roots]) => ({ id, roots }));
|
|
59
|
+
|
|
60
|
+
/** Whether a draft's doc.json says it is a document (not the 写作偏好 page of an old layout), and what it says. */
|
|
61
|
+
async function metadataAt(folder) {
|
|
62
|
+
const files = documentFiles(folder);
|
|
63
|
+
const [html, metadata] = await Promise.all([exists(files.html), fs.readFile(files.json, "utf8").then((text) => JSON.parse(text.replace(/^/, "")), () => null)]);
|
|
64
|
+
return { files, html, metadata: metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : null };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* What the sidebar shows of draft `entry`: { entry, ws, slug, root (null: no workspace of that id),
|
|
69
|
+
* folder, path (its doc.html, or an old one's report.html), title, description, icon, missing: null, or
|
|
70
|
+
* why it is shown grey: "workspace" (none of that id on the registry, or its folder gone), "ambiguous"
|
|
71
|
+
* (two of that id), "draft" (its document is gone) }.
|
|
72
|
+
*/
|
|
73
|
+
export async function draftFacts(entry, ids = registeredIds()) {
|
|
74
|
+
const { ws, slug } = draftOf(entry) ?? { ws: "", slug: entry };
|
|
75
|
+
const found = workspaceOf(ws, ids);
|
|
76
|
+
const none = { entry, ws, slug, root: null, folder: null, path: null, title: slug, description: "", icon: null };
|
|
77
|
+
if (!found.root) return { ...none, missing: found.refused === "ambiguous" ? "ambiguous" : "workspace" };
|
|
78
|
+
const folder = path.join(found.root, slug);
|
|
79
|
+
const { files, html, metadata } = await metadataAt(folder);
|
|
80
|
+
const missing = !isWorkspace(found.root) ? "workspace" : !html || metadata?.instructions === true ? "draft" : null;
|
|
81
|
+
// An untitled document says so (its title empty: the workbench names it 未命名文档); one with no doc.json to read, its slug.
|
|
82
|
+
return { entry, ws, slug, root: found.root, folder, path: files.html, title: typeof metadata?.title === "string" ? oneLine(metadata.title) : slug, description: oneLine(metadata?.description), icon: shownIcon(metadata?.icon), missing };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The names the sidebar calls the registered workspaces by: a Map of id to { name, above, upper, root,
|
|
87
|
+
* there }, for the ids `wanted`. `above` is what tells it apart from another of `among` (ids: those shown
|
|
88
|
+
* side by side, the sidebar's columns or the list of 添加其他 Gallery 项目…; `wanted` when none is given) that has
|
|
89
|
+
* its name (./workspace-name.mjs workspaceLabels), else null: never a workspace of the machine that is not
|
|
90
|
+
* shown. `upper`: the name of the folder above the one named (null at a drive's root), which the sidebar
|
|
91
|
+
* shows when a column of another kind has the name. `stops`: nameStops()'s (asked once by a server: it
|
|
92
|
+
* reads Windows' registry).
|
|
93
|
+
*/
|
|
94
|
+
export function workspaceNames(wanted, { ids = registeredIds(), stops = nameStops(), among = wanted } = {}) {
|
|
95
|
+
const named = [...ids].filter(([id, roots]) => roots.length === 1 && (wanted.has(id) || among.has(id))).map(([id, [root]]) => ({ id, root, ...workspaceName(root, { stops }) }));
|
|
96
|
+
const compared = named.filter((entry) => among.has(entry.id));
|
|
97
|
+
const labels = new Map(workspaceLabels(compared).map((label, index) => [compared[index].id, label]));
|
|
98
|
+
const names = new Map();
|
|
99
|
+
for (const entry of named) {
|
|
100
|
+
if (wanted.has(entry.id)) names.set(entry.id, { name: entry.name, above: labels.get(entry.id)?.above ?? null, upper: entry.above, root: entry.root, there: isWorkspace(entry.root) });
|
|
101
|
+
}
|
|
102
|
+
return names;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* What a path the user pasted or picked (添加其他 Gallery 项目…) names: a workspace's folder, a document's
|
|
107
|
+
* doc.html (an old one's report.html) or its folder; a file:// address of one too, and quotes around it are
|
|
108
|
+
* taken away. One of: { kind: "workspace", root } (a folder holding Gallery's gallery.json);
|
|
109
|
+
* { kind: "draft", folder, slug, root (the workspace it is in: the nearest folder up holding Gallery's
|
|
110
|
+
* gallery.json; null for none, `parent` then the folder that would become one) }; { kind: "folder", folder }
|
|
111
|
+
* (a folder that is no workspace yet but holds Gallery documents: the one that would become a workspace);
|
|
112
|
+
* { kind: "missing" } (nothing there, or not an absolute path); { kind: "plain" } (an HTML file, or a folder
|
|
113
|
+
* that is no Gallery draft, workspace or folder of documents); { kind: "nested", root } (a draft that is not
|
|
114
|
+
* right in its workspace: the trash's, the 写作偏好 page's); { kind: "name" } (a folder whose name is no slug).
|
|
115
|
+
*/
|
|
116
|
+
export async function findDraft(written) {
|
|
117
|
+
let target = String(written ?? "").trim().replace(/^["']+|["']+$/g, "");
|
|
118
|
+
if (/^file:\/\//i.test(target)) {
|
|
119
|
+
try {
|
|
120
|
+
target = fileURLToPath(target);
|
|
121
|
+
} catch {
|
|
122
|
+
return { kind: "missing", path: target };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!target || !path.isAbsolute(target)) return { kind: "missing", path: target };
|
|
126
|
+
target = path.resolve(target);
|
|
127
|
+
const stat = await fs.stat(target).catch(() => null);
|
|
128
|
+
if (!stat) return { kind: "missing", path: target };
|
|
129
|
+
let folder;
|
|
130
|
+
if (stat.isDirectory()) folder = target;
|
|
131
|
+
else if (/^(doc|report)\.html$/i.test(path.basename(target))) folder = path.dirname(target);
|
|
132
|
+
else return { kind: "plain", path: target };
|
|
133
|
+
if (stat.isDirectory() && isWorkspace(folder)) return { kind: "workspace", path: target, root: folder };
|
|
134
|
+
const { files, html, metadata } = await metadataAt(folder);
|
|
135
|
+
if (stat.isDirectory() && !html && (await listDocumentSlugs(folder).catch(() => [])).some(isSlugName)) return { kind: "folder", path: target, folder };
|
|
136
|
+
if (!html || (!files.old && !metadata) || metadata?.instructions === true) return { kind: "plain", path: target };
|
|
137
|
+
let root = null;
|
|
138
|
+
for (let directory = path.dirname(folder); ; directory = path.dirname(directory)) {
|
|
139
|
+
if (isWorkspace(directory)) {
|
|
140
|
+
root = directory;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
if (path.dirname(directory) === directory) break;
|
|
144
|
+
}
|
|
145
|
+
const slug = path.basename(folder);
|
|
146
|
+
if (root && workspaceKey(path.dirname(folder)) !== workspaceKey(root)) return { kind: "nested", path: target, root };
|
|
147
|
+
if (!isSlugName(slug)) return { kind: "name", path: target };
|
|
148
|
+
return { kind: "draft", path: files.html, folder, slug, root, parent: path.dirname(folder) };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The column of workspace `id` in `outline` (changed in place): the one that has `workspace: id`; with none
|
|
153
|
+
* (a workspace the sidebar does not show as it was read: the outline of a workspace's own tree, or past 50
|
|
154
|
+
* columns), one made right after the column `after` (an id; none: last), picked (`picked`) or whole, or null
|
|
155
|
+
* where no column can be made (50 already: the entry goes to the own column's top).
|
|
156
|
+
*/
|
|
157
|
+
function columnFor(outline, id, { after = null, picked = false } = {}) {
|
|
158
|
+
let group = outline.groups.find((each) => each.workspace === id);
|
|
159
|
+
if (group) return group;
|
|
160
|
+
if (outline.groups.length >= GROUP_LIMITS.groups) return null;
|
|
161
|
+
group = { id: outline.groups.some((each) => each.id === id) ? newGroupId(outline.groups) : id, workspace: id, ...(picked ? { picked: true } : {}) };
|
|
162
|
+
const at = outline.groups.findIndex((each) => each.id === after);
|
|
163
|
+
outline.groups = at < 0 ? [...outline.groups, group] : [...outline.groups.slice(0, at + 1), group, ...outline.groups.slice(at + 1)];
|
|
164
|
+
outline.children[topOf(group.id)] ??= [];
|
|
165
|
+
return group;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** `outline`'s column `id` (changed in place: its object replaced) as `change(column)` gives it, at index `to` when given. */
|
|
169
|
+
function changeColumn(outline, id, change, to = null) {
|
|
170
|
+
const groups = outline.groups.filter((each) => each.id !== id);
|
|
171
|
+
const at = outline.groups.findIndex((each) => each.id === id);
|
|
172
|
+
const next = change(outline.groups[at]);
|
|
173
|
+
groups.splice(to === null ? at : Math.min(to, groups.length), 0, next);
|
|
174
|
+
outline.groups = groups;
|
|
175
|
+
return next;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** `outline`'s column `id` moved right after column `after` (one of it; none, or itself: where it is). */
|
|
179
|
+
function moveAfter(outline, id, after) {
|
|
180
|
+
if (!after || after === id || !outline.groups.some((each) => each.id === after)) return;
|
|
181
|
+
const groups = outline.groups.filter((each) => each.id !== id);
|
|
182
|
+
groups.splice(groups.findIndex((each) => each.id === after) + 1, 0, outline.groups.find((each) => each.id === id));
|
|
183
|
+
outline.groups = groups;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const shownColumn = ({ hidden, ...column }) => column;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Draft `slug` of workspace `draftRoot` put in the sidebar of workspace `treeRoot` (its outline, under the
|
|
190
|
+
* outline's lock; `changed()` once it is written), with the drafts under it in its own workspace's tree
|
|
191
|
+
* (documents: a web link of that workspace is none, nor a draft of a third its tree shows, the drafts under
|
|
192
|
+
* one going under the draft above it), those the tree does not hold yet. Its workspace's column is shown
|
|
193
|
+
* again when the user had removed it (hidden). How, `pick` says:
|
|
194
|
+
* - false (an Agent's gallery new or open there: joinDefaultTree, POST draft-join): one the outline holds
|
|
195
|
+
* (the file, or a whole column as it is read) stays where it is; else (a picked column) under its own
|
|
196
|
+
* parent's entry when the tree holds that, else last in its workspace's column;
|
|
197
|
+
* - true (the user's pick in 添加其他 Gallery 项目…, `after`: the column whose menu it came from): one shown
|
|
198
|
+
* in the sidebar already stays where it is (revealed, `existing`); its workspace's column hidden, or one the
|
|
199
|
+
* sidebar showed not before (`fresh`: its workspace registered just now, its column made only as the outline
|
|
200
|
+
* is read, filled whole), it is made a picked one holding it and those under it alone, right after `after`;
|
|
201
|
+
* one shown picked or whole takes it (under its own parent's entry, else last: one the user had taken out of
|
|
202
|
+
* a whole one); with none, a picked column is made right after `after`.
|
|
203
|
+
* Either way, what is put in a whole column is in its `left` no more.
|
|
204
|
+
* Resolves to { entry, existing: whether the file held it, so that nothing was put in, parent, added: the
|
|
205
|
+
* entries put in, column: the id of the column it is in, under: the entry it is under, or null, shown: whether
|
|
206
|
+
* the column was shown again }.
|
|
207
|
+
*/
|
|
208
|
+
export async function addDraft(treeRoot, draftRoot, slug, { changed = () => {}, pick = false, after = null, fresh = false } = {}) {
|
|
209
|
+
const id = workspaceId(draftRoot);
|
|
210
|
+
const entry = draftEntry(id, slug);
|
|
211
|
+
const own = await readOutline(draftRoot, { others: false }).catch(() => ({ children: {}, links: [] })); // its own tree, read alone
|
|
212
|
+
// No document of that workspace: its web links, and the drafts of others its tree shows (~<ws id>/<slug>).
|
|
213
|
+
const none = (key) => own.links.includes(key) || isForeign(key);
|
|
214
|
+
const ownParent = (child) => {
|
|
215
|
+
let up = parentIn(own.children, child);
|
|
216
|
+
while (up !== null && !isTop(up) && none(up)) up = parentIn(own.children, up);
|
|
217
|
+
return up === null || isTop(up) ? null : up;
|
|
218
|
+
};
|
|
219
|
+
return withOutlineLock(treeRoot, async () => {
|
|
220
|
+
const { outline, stored } = await outlineAndFile(treeRoot);
|
|
221
|
+
const holds = (key) => Object.values(outline.children).some((list) => list.includes(key));
|
|
222
|
+
const storedHolds = Object.values(stored?.children ?? {}).some((list) => list.includes(entry));
|
|
223
|
+
const where = () => {
|
|
224
|
+
const parent = parentIn(outline.children, entry);
|
|
225
|
+
const top = topIn(outline.children, entry);
|
|
226
|
+
return { parent, column: top === null || top === "" ? DOCS : top.slice(1), under: parent !== null && !isTop(parent) ? parent : null };
|
|
227
|
+
};
|
|
228
|
+
const columnOf = (key) => outline.groups.find((each) => topOf(each.id) === topIn(outline.children, key)) ?? null;
|
|
229
|
+
let shown = false;
|
|
230
|
+
let column = outline.groups.find((each) => each.workspace === id) ?? null;
|
|
231
|
+
const put = () => {
|
|
232
|
+
const added = [];
|
|
233
|
+
const top = column ? topOf(column.id) : topOf(DOCS);
|
|
234
|
+
const above = ownParent(slug);
|
|
235
|
+
placeIn(outline, entry, above !== null && holds(draftEntry(id, above)) ? draftEntry(id, above) : top);
|
|
236
|
+
added.push(entry);
|
|
237
|
+
for (const child of descendantsIn(own.children, slug)) {
|
|
238
|
+
const childEntry = draftEntry(id, child);
|
|
239
|
+
if (none(child) || holds(childEntry)) continue;
|
|
240
|
+
placeIn(outline, childEntry, draftEntry(id, ownParent(child) ?? slug));
|
|
241
|
+
added.push(childEntry);
|
|
242
|
+
}
|
|
243
|
+
// Put in again: taken out of a whole column no more.
|
|
244
|
+
if (column?.left) {
|
|
245
|
+
const back = new Set(added.map((each) => draftOf(each).slug));
|
|
246
|
+
column = changeColumn(outline, column.id, ({ left, ...rest }) => {
|
|
247
|
+
const still = left.filter((taken) => !back.has(taken));
|
|
248
|
+
return still.length ? { ...rest, left: still } : rest;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return added;
|
|
252
|
+
};
|
|
253
|
+
let added = [];
|
|
254
|
+
let existing = false;
|
|
255
|
+
if (!pick) {
|
|
256
|
+
if (column?.hidden) {
|
|
257
|
+
column = changeColumn(outline, column.id, shownColumn);
|
|
258
|
+
shown = true;
|
|
259
|
+
}
|
|
260
|
+
existing = storedHolds;
|
|
261
|
+
if (!holds(entry)) {
|
|
262
|
+
column ??= columnFor(outline, id);
|
|
263
|
+
added = put();
|
|
264
|
+
} else if (!storedHolds) added = [entry];
|
|
265
|
+
} else {
|
|
266
|
+
// A column the file holds not, of a workspace the sidebar showed not before: made as it was read, whole.
|
|
267
|
+
const madeNow = Boolean(column) && fresh && !(stored?.groups ?? []).some((each) => each.workspace === id);
|
|
268
|
+
const at = columnOf(entry);
|
|
269
|
+
const there = holds(entry) && !at?.hidden && !(madeNow && at?.id === column.id);
|
|
270
|
+
if (there) existing = true;
|
|
271
|
+
else if (column?.hidden || madeNow) {
|
|
272
|
+
// Made a picked column holding it and those under it alone, shown, right after `after`.
|
|
273
|
+
const top = topOf(column.id);
|
|
274
|
+
for (const key of descendantsIn(outline.children, top)) delete outline.children[key];
|
|
275
|
+
outline.children[top] = [];
|
|
276
|
+
shown = Boolean(column.hidden);
|
|
277
|
+
column = changeColumn(outline, column.id, ({ left: _left, ...each }) => ({ ...shownColumn(each), picked: true }));
|
|
278
|
+
moveAfter(outline, column.id, after);
|
|
279
|
+
added = put();
|
|
280
|
+
} else {
|
|
281
|
+
column ??= columnFor(outline, id, { after, picked: true });
|
|
282
|
+
added = put();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (!writtenAs(outline, stored)) {
|
|
286
|
+
await writeOutline(treeRoot, outline);
|
|
287
|
+
changed();
|
|
288
|
+
}
|
|
289
|
+
return { entry, existing, added, shown, ...where() };
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Workspace `otherRoot` shown whole in the sidebar of workspace `treeRoot` (添加其他 Gallery 项目… with a
|
|
295
|
+
* workspace: its outline, under the outline's lock; `changed()` once it is written): its column (made when
|
|
296
|
+
* there is none) shown, whole (all its documents, those the user had taken out of it too: those no column
|
|
297
|
+
* holds come as the outline is read), right after the column `after` (an id). Resolves to { column: its id,
|
|
298
|
+
* moved, shown, whole: whether each changed }.
|
|
299
|
+
*/
|
|
300
|
+
export async function addWorkspace(treeRoot, otherRoot, { changed = () => {}, after = null } = {}) {
|
|
301
|
+
const id = workspaceId(otherRoot);
|
|
302
|
+
return withOutlineLock(treeRoot, async () => {
|
|
303
|
+
const { outline, stored } = await outlineAndFile(treeRoot);
|
|
304
|
+
let column = columnFor(outline, id, { after });
|
|
305
|
+
if (!column) return { column: null, moved: false, shown: false, whole: false };
|
|
306
|
+
const shown = Boolean(column.hidden);
|
|
307
|
+
const whole = Boolean(column.picked || column.left);
|
|
308
|
+
// Whole: all its documents, those the user took out of it before too (its `left` gone).
|
|
309
|
+
column = changeColumn(outline, column.id, ({ picked: _picked, left: _left, ...each }) => shownColumn(each));
|
|
310
|
+
const before = outline.groups.findIndex((each) => each.id === column.id);
|
|
311
|
+
moveAfter(outline, column.id, after);
|
|
312
|
+
const moved = outline.groups.findIndex((each) => each.id === column.id) !== before;
|
|
313
|
+
if (!writtenAs(outline, stored)) {
|
|
314
|
+
await writeOutline(treeRoot, outline);
|
|
315
|
+
changed();
|
|
316
|
+
}
|
|
317
|
+
return { column: column.id, moved, shown, whole };
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The other registered workspaces (but `treeRoot`'s own), for 添加其他 Gallery 项目…'s list: [{ ws, root, name,
|
|
323
|
+
* above, documents: [{ entry, slug, title, icon, path, depth }] }], each workspace's documents in the order
|
|
324
|
+
* its own sidebar has them (depth: how deep under others there), those whose folder is no workspace now left
|
|
325
|
+
* out, a clash of ids too (refused, never guessed), the one used last first. Reads files only: nothing of
|
|
326
|
+
* another workspace is written (its 写作偏好 page is no draft).
|
|
327
|
+
*/
|
|
328
|
+
export async function projectChoices(treeRoot, { ids = registeredIds(), stops = nameStops(), registry = readRegistry() } = {}) {
|
|
329
|
+
const seen = new Map(registry.workspaces.map(({ root, seen: at }) => [workspaceKey(root), Date.parse(at)]));
|
|
330
|
+
const wanted = new Set([...ids].filter(([, roots]) => roots.length === 1 && workspaceKey(roots[0]) !== workspaceKey(treeRoot)).map(([id]) => id));
|
|
331
|
+
const names = workspaceNames(wanted, { ids, stops });
|
|
332
|
+
const found = await Promise.all([...names].filter(([, { there }]) => there).map(async ([id, { name, above, root }]) => {
|
|
333
|
+
const tree = await readOutline(root, { others: false }).catch(() => null);
|
|
334
|
+
const documents = [];
|
|
335
|
+
const isDocument = (slug) => !(tree?.links ?? []).includes(slug) && !isForeign(slug) && isSlugName(slug);
|
|
336
|
+
const walk = (key, depth) => (tree?.children[key] ?? []).forEach((slug) => {
|
|
337
|
+
const document = isDocument(slug);
|
|
338
|
+
if (document) documents.push({ slug, depth });
|
|
339
|
+
walk(slug, document ? depth + 1 : depth);
|
|
340
|
+
});
|
|
341
|
+
for (const { id: column } of tree?.groups ?? []) walk(topOf(column), 0);
|
|
342
|
+
const listed = await Promise.all(documents.map(async ({ slug, depth }) => {
|
|
343
|
+
const { files, metadata } = await metadataAt(path.join(root, slug));
|
|
344
|
+
return { entry: draftEntry(id, slug), slug, title: oneLine(metadata?.title) || slug, icon: shownIcon(metadata?.icon), path: files.html, depth };
|
|
345
|
+
}));
|
|
346
|
+
return { ws: id, root, name, above, seen: seen.get(workspaceKey(root)) ?? 0, documents: listed };
|
|
347
|
+
}));
|
|
348
|
+
return found.sort((a, b) => b.seen - a.seen).map(({ seen: _seen, ...rest }) => rest);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
/** The environment variable that keeps gallery new and gallery open from putting their draft in the default workspace's sidebar (the tests set it). */
|
|
353
|
+
export const NO_DEFAULT_TREE = "GALLERY_NO_DEFAULT_TREE";
|
|
354
|
+
const set = (value) => !["", "0", undefined].includes(value);
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* gallery new and gallery open in a workspace other than the default one (src/shared/workspace.mjs
|
|
358
|
+
* defaultWorkspace): draft `slug` of workspace `root` put in the default workspace's sidebar (addDraft,
|
|
359
|
+
* with the drafts under it), through the default workspace's server when one runs (POST
|
|
360
|
+
* /__gallery/workspace/draft-join: in its queue, its workbench told at once), else here under the
|
|
361
|
+
* outline's lock. Null where it is not done: NO_DEFAULT_TREE set, `root` the default workspace, no
|
|
362
|
+
* default workspace there yet (nothing is made for it), or `root` not on the registry under its id (a
|
|
363
|
+
* workspace in the system's temporary folder, or a clash of ids: an entry would name none). Else
|
|
364
|
+
* addDraft's answer with `home` (the default workspace) and `names`: { column: the name of the column
|
|
365
|
+
* it is in, under: the title of the draft it is under, or null }; or { failed: why } (nothing written).
|
|
366
|
+
*/
|
|
367
|
+
export async function joinDefaultTree(root, slug, { env = process.env } = {}) {
|
|
368
|
+
if (set(env[NO_DEFAULT_TREE])) return null;
|
|
369
|
+
const home = defaultWorkspace({ env });
|
|
370
|
+
if (workspaceKey(home) === workspaceKey(root) || !isWorkspace(home)) return null;
|
|
371
|
+
const ids = registeredIds();
|
|
372
|
+
const found = workspaceOf(workspaceId(root), ids);
|
|
373
|
+
if (!found.root || workspaceKey(found.root) !== workspaceKey(root)) return null;
|
|
374
|
+
let done = null;
|
|
375
|
+
try {
|
|
376
|
+
const server = await findPreviewServer({ reportsDirectory: home });
|
|
377
|
+
if (server) done = await joinThrough(server, root, slug);
|
|
378
|
+
done ??= await addDraft(home, root, slug);
|
|
379
|
+
} catch (error) {
|
|
380
|
+
return { failed: error.message, home };
|
|
381
|
+
}
|
|
382
|
+
return { ...done, home, names: await placeNames(home, done, ids) };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// POST draft-join to the default workspace's server; null when no server of it takes it (gone, or an older Gallery's).
|
|
386
|
+
async function joinThrough(server, root, slug) {
|
|
387
|
+
const host = `127.0.0.1:${server.port}`;
|
|
388
|
+
try {
|
|
389
|
+
const response = await fetch(`http://${host}/__gallery/workspace/draft-join`, {
|
|
390
|
+
method: "POST",
|
|
391
|
+
headers: { "Content-Type": "application/json", Origin: `http://${host}`, "X-Gallery-Token": server.token },
|
|
392
|
+
body: JSON.stringify({ root, slug }),
|
|
393
|
+
signal: AbortSignal.timeout(30000),
|
|
394
|
+
});
|
|
395
|
+
const answer = await response.json().catch(() => null);
|
|
396
|
+
return answer?.ok === true && typeof answer.entry === "string" ? answer : null;
|
|
397
|
+
} catch {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// The names a line says of where a draft went: its column's (the user's title, else its workspace's
|
|
403
|
+
// name, else 默认文档, the default workspace's own column, or 链接) and the title of the draft or document it is under.
|
|
404
|
+
async function placeNames(home, { column, under }, ids) {
|
|
405
|
+
const { groups } = await readOutline(home);
|
|
406
|
+
const group = groups.find((each) => each.id === column);
|
|
407
|
+
let name = group?.title ?? null;
|
|
408
|
+
if (!name && group?.workspace) name = workspaceNames(new Set([group.workspace]), { ids }).get(group.workspace)?.name ?? null;
|
|
409
|
+
name ??= group?.role === "links" ? "链接" : group?.id === DOCS || !group ? "默认文档" : "未命名栏"; // its own column is the default workspace's
|
|
410
|
+
let above = null;
|
|
411
|
+
if (under) {
|
|
412
|
+
if (draftOf(under)) above = (await draftFacts(under, ids)).title;
|
|
413
|
+
else {
|
|
414
|
+
const metadata = await fs.readFile(documentFiles(path.join(home, under)).json, "utf8").then(JSON.parse, () => null);
|
|
415
|
+
above = oneLine(metadata?.title) || under;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return { column: name, under: above };
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** The line gallery new and gallery open print of joinDefaultTree's answer (null for none). */
|
|
422
|
+
export function joinedLine(joined) {
|
|
423
|
+
if (!joined) return null;
|
|
424
|
+
const where = `默认工作区(${joined.home})的侧边栏`;
|
|
425
|
+
if (joined.failed) return `${where}:没有加进去(${joined.failed})`;
|
|
426
|
+
if (joined.existing) return `${where}里已经有它(${joined.names.under ? `在「${joined.names.under}」下面` : `「${joined.names.column}」栏`})`;
|
|
427
|
+
const more = joined.added.length > 1 ? `,连同它下面的 ${joined.added.length - 1} 篇` : "";
|
|
428
|
+
return `${where}:已加进${joined.names.under ? `「${joined.names.under}」下面` : `「${joined.names.column}」栏`}${more}(拖动只改那里的显示位置,文件不动)`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** Whether a gallery.json at `folder` would be another program's or not readable (what draft-add refuses to make a workspace of). */
|
|
432
|
+
export const foreignMarker = (folder) => existsSync(path.join(folder, "gallery.json")) && !isWorkspace(folder);
|