@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
|
@@ -29,11 +29,13 @@ import { unpack } from "./sync-state.mjs";
|
|
|
29
29
|
// (settled) and the reader pauses, the page reloads to show it, where the reader was
|
|
30
30
|
// (reloadView). Only when that write put texts the reader committed back as they were (an Agent
|
|
31
31
|
// writing the whole file from what it read earlier) does the page ask first: it keeps the texts it
|
|
32
|
-
// committed (commits.committed(), and those of its earlier loads
|
|
33
|
-
// made and moved this load (commits.placedUnits()) and what its
|
|
34
|
-
// those of earlier loads too), and the server says which the
|
|
35
|
-
// /__gallery/edit/reverted): a text as it was, a block it made gone, a
|
|
36
|
-
// it was in, a part as before a write of the page's.
|
|
32
|
+
// committed (commits.committed(), and those of its earlier loads: the journal), the blocks it
|
|
33
|
+
// made and moved this load (commits.placedUnits(), and those earlier loads made) and what its
|
|
34
|
+
// components wrote (commits.written(), those of earlier loads too), and the server says which the
|
|
35
|
+
// file now has as before (POST /__gallery/edit/reverted): a text as it was, a block it made gone, a
|
|
36
|
+
// block it moved back in the unit it was in, a part as before a write of the page's. A page going away
|
|
37
|
+
// keeps all of it in localStorage (journalKey) with the version of the file it last knew; the next
|
|
38
|
+
// page of the document, in this tab or another, opening on another version, asks at once.
|
|
37
39
|
|
|
38
40
|
const IDLE = 1600;
|
|
39
41
|
const LONGEST = 8000;
|
|
@@ -41,6 +43,7 @@ const RETRY = [1000, 2000, 5000, 10000, 30000];
|
|
|
41
43
|
const QUIET = 1500; // how long the reader has changed nothing before the page reloads by itself
|
|
42
44
|
const STORE_EVERY = 1000; // the crash buffer while typing, at most this often
|
|
43
45
|
const JOURNAL_FOR = 60 * 60 * 1000; // how long a committed text is checked for being put back
|
|
46
|
+
const JOURNAL_SIZE = 100_000; // the most characters one document's journal keeps (the newest entries)
|
|
44
47
|
const OWN_FOR = 5000; // how long the live-reload channel may still announce a version this page wrote
|
|
45
48
|
// A page of the Agent's own (its address's ?agent: the Agent 自用 one gallery open prints, gallery
|
|
46
49
|
// component try's): its commits say so (`agent`), and are no reader's edit to the review of the
|
|
@@ -52,6 +55,33 @@ const AGENT_PAGE = typeof location !== "undefined" && new URLSearchParams(locati
|
|
|
52
55
|
// names it but the words it had.
|
|
53
56
|
const sameText = (block) => JSON.stringify(block?.title ? "title" : block?.unit ? [block.unit.id ?? block.unit.blockId ?? [block.unit.key, block.unit.open], block.self ? true : block.path ?? null] : { ...block, before: undefined });
|
|
54
57
|
|
|
58
|
+
// The journal's texts (journal()) with those of commits not answered yet (`requests`: on their way, or
|
|
59
|
+
// the crash buffer's) taken as committed, as committedText (commit.mjs) takes a commit's: what each
|
|
60
|
+
// replaced first stays where the text started, what it was between is `earlier`, and one back as it
|
|
61
|
+
// was first and nothing between is none; committed now (`at`).
|
|
62
|
+
function folded(texts, requests) {
|
|
63
|
+
const named = new Map(texts.map((entry) => [sameText(entry.block), entry]));
|
|
64
|
+
let serial = 0;
|
|
65
|
+
for (const request of requests) {
|
|
66
|
+
for (const text of request?.texts ?? []) {
|
|
67
|
+
const mine = text?.parts?.find((part) => part.keep)?.html;
|
|
68
|
+
if (typeof mine !== "string" || !text.block) continue;
|
|
69
|
+
const name = sameText(text.block);
|
|
70
|
+
const was = named.get(name);
|
|
71
|
+
const original = was?.original ?? text.block.before;
|
|
72
|
+
if (typeof original !== "string" || (was && was.mine === mine)) continue;
|
|
73
|
+
const earlier = [...new Set([...(was?.earlier ?? []), ...(was ? [was.mine] : [])])].filter((each) => each !== mine && each !== original).slice(-TEXTS_KEPT);
|
|
74
|
+
if (mine === original && earlier.length === 0) {
|
|
75
|
+
named.delete(name);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const entry = { key: was?.key ?? `f${serial++}`, block: { ...text.block, before: mine }, original, mine, ...(earlier.length > 0 ? { earlier } : {}), ...(was?.time ? { time: was.time } : {}), at: Date.now() };
|
|
79
|
+
named.set(name, entry);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return [...named.values()];
|
|
83
|
+
}
|
|
84
|
+
|
|
55
85
|
/**
|
|
56
86
|
* map: the page's map (slug, version, token); commits: createCommits(); blocked(): commits wait
|
|
57
87
|
* (an input method is composing); changedAt(): when the reader last changed anything; holding(asked):
|
|
@@ -62,14 +92,14 @@ const sameText = (block) => JSON.stringify(block?.title ? "title" : block?.unit
|
|
|
62
92
|
* warn("storage"): the crash buffer could not be written; storage: localStorage
|
|
63
93
|
* (get/set/remove/keys); tab(): this tab's id (a duplicated tab takes a new one); notice(): what the
|
|
64
94
|
* page says after it reloads by itself (a merge it just made), or null; session: sessionStorage
|
|
65
|
-
* (get/
|
|
95
|
+
* (get/remove), read once for the journal an earlier Gallery kept there; reverted(entries): texts, blocks and components' writes of the reader's that
|
|
66
96
|
* someone else's write put back, for the reader to decide on (decide) before the page shows that
|
|
67
97
|
* write (a component's with `written`: the parts, by name, and the hashes the file holds of them);
|
|
68
|
-
* before(): called right
|
|
98
|
+
* listed(): words were added to what the page could not put back (lostKey), to be shown; before(): called right
|
|
69
99
|
* before a commit goes, a promise it waits for (or null): what goes with the edit elsewhere is told
|
|
70
100
|
* first (where the words of the reader's 批注 went: annotate/ sync).
|
|
71
101
|
*/
|
|
72
|
-
export function createAutosave({ map, commits, blocked, changedAt, holding = () => false, refresh, applied, failed, warn = () => {}, storage, tab, notice = () => null, session = null, reverted = () => {}, before = () => null }) {
|
|
102
|
+
export function createAutosave({ map, commits, blocked, changedAt, holding = () => false, refresh, applied, failed, warn = () => {}, storage, tab, notice = () => null, session = null, reverted = () => {}, listed = () => {}, before = () => null }) {
|
|
73
103
|
let known = map.version; // the file's version as the page last knew it
|
|
74
104
|
// Versions this page's commits wrote (and the one it opened on), in order, and when: the
|
|
75
105
|
// live-reload channel announces them too, a little later, in the order they were written. Only
|
|
@@ -100,17 +130,39 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
100
130
|
let unstored = false; // the crash buffer could not be written
|
|
101
131
|
let storedFrom = null; // what the page held (collect()'s result) when the buffer was last written with nothing on its way; null: write it anew
|
|
102
132
|
const events = []; // [version, when]: what the live-reload channel announced while a commit was on its way
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
|
|
133
|
+
// What earlier pages of the document committed, made and wrote (the journal, journalKey: what each
|
|
134
|
+
// page kept as it went away, by tab), taken by this one as its own: the texts, still checked for an
|
|
135
|
+
// hour, each under a key of its own (`c<n>`: an earlier load's keys name its blocks, as this one's
|
|
136
|
+
// name others); the blocks they made (`m<n>`: by the file's id, with their words), until the file is
|
|
137
|
+
// found without one (listed) or the page no longer shows one it was found with (the reader deleted
|
|
138
|
+
// it); what their components wrote, the commits' own again (carry), by instance. This tab's are taken
|
|
139
|
+
// as the page is made, those of tabs no longer open once it knows which are (opened).
|
|
107
140
|
let carried = [];
|
|
141
|
+
let carriedMade = [];
|
|
142
|
+
let textSerial = 0;
|
|
143
|
+
let madeSerial = 0;
|
|
144
|
+
// The version of the file the page last knew: every write up to it the page's own or asked of (the
|
|
145
|
+
// revert check answered about it, what it put back held for the reader). What the page keeps as it
|
|
146
|
+
// goes away says it; null while a question about what earlier pages kept is not answered yet.
|
|
147
|
+
let seen = map.version;
|
|
148
|
+
let asking = false; // what earlier pages kept, taken, is to be asked of (the file not the version they last knew)
|
|
149
|
+
const adopt = (departures) => {
|
|
150
|
+
let ask = false;
|
|
151
|
+
for (const { known, entries } of departures) {
|
|
152
|
+
if (known !== map.version) ask = true;
|
|
153
|
+
carried.push(...entries.filter((entry) => entry.instance === undefined && entry.made === undefined && entry.block).map((entry) => ({ ...entry, key: `c${textSerial++}` })));
|
|
154
|
+
carriedMade.push(...entries.filter((entry) => typeof entry.made === "string" && !carriedMade.some((other) => other.id === entry.made))
|
|
155
|
+
.map(({ made, parent, text, time }) => ({ key: `m${madeSerial++}`, id: made, parent: parent ?? null, text: String(text ?? ""), time, confirmed: known === map.version })));
|
|
156
|
+
commits.carry(entries.filter((entry) => entry.instance !== undefined).map(({ instance, parts, time }) => ({ id: instance, parts, time })));
|
|
157
|
+
}
|
|
158
|
+
if (ask) {
|
|
159
|
+
asking = true;
|
|
160
|
+
seen = null;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
108
163
|
try {
|
|
109
|
-
|
|
110
|
-
carried = kept.filter((entry) => entry.instance === undefined).map((entry, index) => ({ ...entry, key: `c${index}` }));
|
|
111
|
-
commits.carry(kept.filter((entry) => entry.instance !== undefined).map(({ instance, parts, time }) => ({ id: instance, parts, time })));
|
|
164
|
+
adopt([...legacyJournal(session, map.slug), ...takeJournal(storage, map.slug, (id) => id === tab())]);
|
|
112
165
|
} catch {}
|
|
113
|
-
session?.remove(journalKey);
|
|
114
166
|
const reverts = new Map(); // key -> entry: put back by someone else, waiting for the reader
|
|
115
167
|
let checking = false;
|
|
116
168
|
const waiters = [];
|
|
@@ -231,8 +283,11 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
231
283
|
retryAt = 0;
|
|
232
284
|
offline = false;
|
|
233
285
|
known = receipt.version;
|
|
286
|
+
// Written over the version the page last knew, no one else's write between: the page knows this one.
|
|
287
|
+
if (!receipt.foreign && seen !== null && sent.request.known === seen) seen = receipt.version;
|
|
234
288
|
if (receipt.wrote !== false) own.push({ version: receipt.version, at: Date.now() }); // one that changed nothing wrote no version
|
|
235
289
|
if (receipt.foreign) foreign = true;
|
|
290
|
+
deleted(sent.request, receipt);
|
|
236
291
|
applied(commits.apply(sent.pending, receipt, { alone: !foreign }), receipt);
|
|
237
292
|
after = commits.collect();
|
|
238
293
|
recount(after);
|
|
@@ -339,7 +394,7 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
339
394
|
if (closed) return;
|
|
340
395
|
leaving = true;
|
|
341
396
|
store();
|
|
342
|
-
keepJournal();
|
|
397
|
+
keepJournal([flight?.request ?? parked?.request, buffer]); // what is on its way or in the buffer, as the file is to have it
|
|
343
398
|
if (flight || parked || stopped || waiting() === 0 || !buffer) return; // what is on its way is in the buffer
|
|
344
399
|
postCommit(map, buffer, { keepalive: true }).catch(() => {});
|
|
345
400
|
}
|
|
@@ -392,18 +447,40 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
392
447
|
}
|
|
393
448
|
const texts = journal();
|
|
394
449
|
const units = commits.placedUnits();
|
|
450
|
+
// The blocks earlier pages made: those the file was not yet found with, and those the page shows (one
|
|
451
|
+
// found with it and no longer shown, the reader deleted).
|
|
452
|
+
const made = carriedMade.filter((entry) => !entry.confirmed || commits.unitOf(entry.id) !== null);
|
|
395
453
|
const instances = commits.written();
|
|
396
|
-
if (texts.length === 0 && units.length === 0 && instances.length === 0) return true;
|
|
454
|
+
if (texts.length === 0 && units.length === 0 && made.length === 0 && instances.length === 0) return true;
|
|
397
455
|
checking = true;
|
|
398
456
|
let answer = null;
|
|
399
457
|
try {
|
|
400
|
-
const response = await postJson("/__gallery/edit/reverted", map.token, { slug: map.slug, opening: map.version, texts: texts.map(({ key, block, original, earlier }) => ({ key, block, original, ...(earlier ? { earlier } : {}) })), units, instances: instances.map(({ key, id, parts }) => ({ key, id, parts })), pageIds: commits.pageIds() });
|
|
458
|
+
const response = await postJson("/__gallery/edit/reverted", map.token, { slug: map.slug, opening: map.version, texts: texts.map(({ key, block, original, earlier }) => ({ key, block, original, ...(earlier ? { earlier } : {}) })), units: [...units, ...made.map(({ key, id, parent }) => ({ key, id, parent }))], instances: instances.map(({ key, id, parts }) => ({ key, id, parts })), pageIds: commits.pageIds() });
|
|
401
459
|
answer = await response.json();
|
|
402
460
|
} catch {}
|
|
403
461
|
checking = false;
|
|
404
462
|
if (closed) return false;
|
|
463
|
+
if (answer?.ok) {
|
|
464
|
+
// A block an earlier page made that the file no longer has is no block of this load's to hold a
|
|
465
|
+
// card for: its words are listed (lostKey), as what a page could not put back is; the others are
|
|
466
|
+
// found with the file, asked of while the page shows them.
|
|
467
|
+
const gone = made.filter(({ key }) => (answer.units ?? []).some((entry) => entry.key === key && entry.gone));
|
|
468
|
+
// One made inside another gone (a quote's paragraph, a list's item) went with it, its words among that one's.
|
|
469
|
+
const goneIds = new Set(gone.map(({ id }) => id));
|
|
470
|
+
const words = gone.filter(({ text, parent }) => text && !goneIds.has(parent)).map(({ text }) => ({ why: "reverted", text }));
|
|
471
|
+
const kept = words.length === 0 || keepLost(storage, map.slug, words);
|
|
472
|
+
if (kept) carriedMade = carriedMade.filter((entry) => !gone.includes(entry)); // asked again otherwise
|
|
473
|
+
for (const entry of made) if (!gone.includes(entry)) entry.confirmed = true;
|
|
474
|
+
if (kept && words.length > 0) listed();
|
|
475
|
+
// All of it asked of (no question waits to be asked again, which may carry more): the page knows the
|
|
476
|
+
// file as it was answered about.
|
|
477
|
+
if (kept && !recheck) {
|
|
478
|
+
seen = answer.version ?? null;
|
|
479
|
+
asking = false;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
405
482
|
// Of the blocks it made that the write took out, those inside another such go with it, one card.
|
|
406
|
-
const goneUnits = (answer?.units ?? []).filter(({ gone }) => gone).map(({ key }) => key);
|
|
483
|
+
const goneUnits = (answer?.units ?? []).filter(({ gone, key }) => gone && !made.some((entry) => entry.key === key)).map(({ key }) => key);
|
|
407
484
|
const back = answer?.ok ? [
|
|
408
485
|
...answer.reverted.map(({ key, theirs }) => ({ ...texts.find((entry) => entry.key === key), theirs })).filter((entry) => entry.key),
|
|
409
486
|
// A block put back: by its unit, with what it holds (the reader's words, to copy) and, taken out, the
|
|
@@ -457,19 +534,22 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
457
534
|
storage.remove(bufferKey());
|
|
458
535
|
storage.remove(keptKey());
|
|
459
536
|
storedFrom = null; // written anew if anything stores after all
|
|
460
|
-
keepJournal();
|
|
537
|
+
keepJournal(); // a write landing after that answer and before the page shown again is served is asked of there (opened)
|
|
461
538
|
refresh();
|
|
462
539
|
reloadView(map.slug, null, notice());
|
|
463
540
|
}
|
|
464
541
|
|
|
465
|
-
// The texts committed, this load's and earlier ones', one of each block (sameText): what
|
|
466
|
-
//
|
|
467
|
-
// between (two of one block are no one question: the
|
|
468
|
-
// was first, it is asked of what it was between.
|
|
542
|
+
// The texts committed, this load's and earlier ones', one of each block (sameText): what was committed
|
|
543
|
+
// of a block before (`at`, when it was last committed: two tabs' pages are taken in no order of their
|
|
544
|
+
// own) is where that text started, and what it was between (two of one block are no one question: the
|
|
545
|
+
// server's answer would name neither); back as it was first, it is asked of what it was between. Joined
|
|
546
|
+
// with one of this load's, it goes by that one's key and naming (decide lets this load's go by its key).
|
|
469
547
|
const journal = () => {
|
|
470
548
|
const now = commits.committed().map((entry) => ({ ...entry, time: Date.now() }));
|
|
549
|
+
const own = new Set(now.map((entry) => entry.key));
|
|
471
550
|
const texts = new Map();
|
|
472
|
-
|
|
551
|
+
const when = (entry) => entry.at ?? entry.time ?? 0;
|
|
552
|
+
for (const entry of [...carried, ...now].sort((a, b) => when(a) - when(b))) {
|
|
473
553
|
const name = sameText(entry.block);
|
|
474
554
|
const was = texts.get(name);
|
|
475
555
|
if (!was) {
|
|
@@ -477,7 +557,8 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
477
557
|
continue;
|
|
478
558
|
}
|
|
479
559
|
const earlier = [...new Set([...(was.earlier ?? []), was.mine, entry.original, ...(entry.earlier ?? [])])].filter((text) => text !== entry.mine && text !== was.original).slice(-TEXTS_KEPT);
|
|
480
|
-
const
|
|
560
|
+
const named = own.has(was.key) && !own.has(entry.key) ? { key: was.key, block: { ...was.block, before: entry.mine } } : {};
|
|
561
|
+
const joined = { ...entry, ...named, original: was.original, earlier, time: Math.max(was.time ?? 0, entry.time ?? 0) || undefined };
|
|
481
562
|
if (earlier.length === 0) delete joined.earlier;
|
|
482
563
|
if (entry.mine === was.original && earlier.length === 0) texts.delete(name);
|
|
483
564
|
else texts.set(name, joined);
|
|
@@ -485,16 +566,73 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
485
566
|
return [...texts.values()];
|
|
486
567
|
};
|
|
487
568
|
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
//
|
|
491
|
-
function
|
|
492
|
-
const
|
|
493
|
-
|
|
569
|
+
// A commit of the page's that the file took deleted blocks (`request`'s removals, answered `receipt`): of
|
|
570
|
+
// the blocks earlier pages made, those (and those made inside them) are the reader's own deletion, asked
|
|
571
|
+
// of by no page any more: this one's, and those kept by pages of tabs gone that no page took yet.
|
|
572
|
+
function deleted(request, receipt) {
|
|
573
|
+
const ids = (request.units ?? []).filter((op) => op.remove && !(receipt.units ?? []).some((answer) => answer.key === op.key && answer.status === "conflict"))
|
|
574
|
+
.flatMap((op) => [op.unit?.id, ...(op.items ?? []).map((item) => item?.id)]).filter((id) => typeof id === "string");
|
|
575
|
+
if (ids.length === 0) return;
|
|
576
|
+
const gone = madeWithin(ids, carriedMade.map(({ id, parent }) => ({ id, parent })));
|
|
577
|
+
carriedMade = carriedMade.filter((entry) => !gone.has(entry.id));
|
|
578
|
+
try {
|
|
579
|
+
forgetMade(storage, map.slug, ids);
|
|
580
|
+
} catch {}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// What the page committed (journal()), made (its blocks the file has, by their ids, with their words:
|
|
584
|
+
// this load's and those of earlier loads it still shows or has not found the file with yet) and what
|
|
585
|
+
// its components wrote (commits.written()), kept in the journal under this tab with the version of the
|
|
586
|
+
// file it last knew (`known`, null when it does not), for the next page of the document, in this tab
|
|
587
|
+
// or another, which takes them as its own, and asks of them at once if it opens on another version
|
|
588
|
+
// (opened): when the page goes to show the file again, when it goes away (a reload of the reader's,
|
|
589
|
+
// another document opened, the tab closed) and after a write the reader asked for (showOwn). `pending`:
|
|
590
|
+
// commits not answered yet (on their way, or the crash buffer's, which the next page sends), whose
|
|
591
|
+
// texts are taken as committed, the reader's own words (an undo back to what an earlier commit
|
|
592
|
+
// replaced) being no one else's write.
|
|
593
|
+
function keepJournal(pending = []) {
|
|
594
|
+
const time = Date.now();
|
|
595
|
+
const made = new Map();
|
|
596
|
+
for (const { key, id, parent, from } of commits.placedUnits()) if (from === undefined && !reverts.has(`p${key}`)) made.set(id, { made: id, parent, text: commits.unitText(key), time });
|
|
597
|
+
for (const entry of carriedMade) {
|
|
598
|
+
const unit = commits.unitOf(entry.id);
|
|
599
|
+
if (made.has(entry.id) || (entry.confirmed && unit === null)) continue;
|
|
600
|
+
made.set(entry.id, { made: entry.id, parent: entry.parent, text: (unit !== null && commits.unitText(unit)) || entry.text, time: entry.time ?? time });
|
|
601
|
+
}
|
|
602
|
+
const entries = [
|
|
603
|
+
...folded(journal(), pending.filter(Boolean)).map((entry) => ({ ...entry, time: entry.time ?? time })),
|
|
494
604
|
...commits.written().map(({ id, parts, time }) => ({ instance: id, parts, time })),
|
|
605
|
+
...made.values(),
|
|
495
606
|
];
|
|
496
|
-
|
|
497
|
-
|
|
607
|
+
try {
|
|
608
|
+
keepDeparture(storage, map.slug, tab(), entries.length > 0 ? { known: seen, time, entries } : null);
|
|
609
|
+
} catch {}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* The page opened and edits, the workbench told it is ready (`open`: the ids of the tabs that answered
|
|
614
|
+
* they show the document now). What pages of tabs no longer open kept is taken too (the document
|
|
615
|
+
* opened again in another tab), and where this page opened on another version of the file than an
|
|
616
|
+
* earlier page last knew (someone else's write landing while no page was there to hear of it, or
|
|
617
|
+
* between the last question of a page showing the file again and this page being served, which it
|
|
618
|
+
* never hears of as a write), all it took is asked of at once (checkReverted): a text put back is a
|
|
619
|
+
* card, a block made that the file no longer has is listed. On the very version, nothing is asked.
|
|
620
|
+
* A page done with, or going to be shown again (it kept its journal already), takes nothing: the next
|
|
621
|
+
* page does; one whose write (showOwn) failed takes it then.
|
|
622
|
+
*/
|
|
623
|
+
let unopened = null; // opened()'s call while the page was to be shown again by showOwn: { open }
|
|
624
|
+
function opened(open = null) {
|
|
625
|
+
if (closed) return;
|
|
626
|
+
if (reloading) return void (unopened = { open });
|
|
627
|
+
if (open) {
|
|
628
|
+
try {
|
|
629
|
+
adopt(takeJournal(storage, map.slug, (id) => id !== tab() && !open.has(id)));
|
|
630
|
+
} catch {}
|
|
631
|
+
forgetOld(storage);
|
|
632
|
+
}
|
|
633
|
+
if (!asking) return;
|
|
634
|
+
if (checking) recheck = true;
|
|
635
|
+
else checkReverted();
|
|
498
636
|
}
|
|
499
637
|
|
|
500
638
|
/**
|
|
@@ -534,7 +672,7 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
534
672
|
|
|
535
673
|
/**
|
|
536
674
|
* A write the reader asked for that changes the file (撤回 of the Agent's changes and its 撤销:
|
|
537
|
-
* changes/): `run()` makes it, resolving with whether it did; then the page shows the file as after a
|
|
675
|
+
* changes/): `run()` makes it, resolving with whether it did (the version it wrote, when known); then the page shows the file as after a
|
|
538
676
|
* write of its own, at once, asking nothing of what it put back (the revert check: the reader asked
|
|
539
677
|
* for it), and what is heard meanwhile shows nothing first. The caller saved what waited first
|
|
540
678
|
* (flush) and made sure nothing waits for the reader. Resolves with whether the page goes.
|
|
@@ -547,14 +685,22 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
547
685
|
missed = false;
|
|
548
686
|
clearTimeout(reloadTimer);
|
|
549
687
|
let done = false;
|
|
688
|
+
let wrote = null; // the version its write left, when run() says
|
|
550
689
|
try {
|
|
551
|
-
|
|
690
|
+
const result = await run();
|
|
691
|
+
done = Boolean(result);
|
|
692
|
+
if (typeof result === "string") wrote = result;
|
|
552
693
|
} catch {}
|
|
553
694
|
owning = false;
|
|
554
695
|
if (closed) return done;
|
|
555
696
|
if (!done) {
|
|
556
697
|
reloading = false;
|
|
557
698
|
refresh();
|
|
699
|
+
if (unopened) {
|
|
700
|
+
const { open } = unopened;
|
|
701
|
+
unopened = null;
|
|
702
|
+
opened(open);
|
|
703
|
+
}
|
|
558
704
|
if (missed) heard(null, true);
|
|
559
705
|
else if (foreign) maybeReload();
|
|
560
706
|
return false;
|
|
@@ -562,6 +708,10 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
562
708
|
storage.remove(bufferKey());
|
|
563
709
|
storage.remove(keptKey());
|
|
564
710
|
storedFrom = null;
|
|
711
|
+
// The page knew the file it asked to be written as; a write landing before the page shown again is
|
|
712
|
+
// served is asked of there. Not told which version that is, or with a question of its own not answered
|
|
713
|
+
// yet, the next page asks whatever it opens on.
|
|
714
|
+
seen = asking ? null : wrote;
|
|
565
715
|
keepJournal();
|
|
566
716
|
refresh();
|
|
567
717
|
reloadView(map.slug, null, null);
|
|
@@ -597,6 +747,7 @@ export function createAutosave({ map, commits, blocked, changedAt, holding = ()
|
|
|
597
747
|
changed,
|
|
598
748
|
flush,
|
|
599
749
|
leave,
|
|
750
|
+
opened,
|
|
600
751
|
heard,
|
|
601
752
|
store,
|
|
602
753
|
decide,
|
|
@@ -692,6 +843,128 @@ function keepLost(storage, slug, items) {
|
|
|
692
843
|
return storage.set(lostKey(slug), JSON.stringify([...readLost(storage, slug), ...items.map((item) => ({ ...item, time }))].slice(-100))) !== false;
|
|
693
844
|
}
|
|
694
845
|
|
|
846
|
+
/**
|
|
847
|
+
* Where the pages of a document keep, as they go away, what they committed, made and wrote, for the
|
|
848
|
+
* next page of it to ask of (createAutosave: keepJournal, opened), in localStorage: { v: 1, tabs: {
|
|
849
|
+
* <tab>: { known, time, entries } } }, one departure a tab, `known` the version of the file that page
|
|
850
|
+
* last knew (null: it did not), each entry with its `time`: a text ({ key, block, original, mine,
|
|
851
|
+
* earlier?, at }, `at` when it was last committed), a block made ({ made, parent, text }; taken out of
|
|
852
|
+
* every departure once a page's commit deletes it: forgetMade) or what a component wrote ({ instance, parts }).
|
|
853
|
+
* Entries are kept for JOURNAL_FOR, the newest of them JOURNAL_SIZE characters at most.
|
|
854
|
+
*/
|
|
855
|
+
export const journalKey = (slug) => `gallery-edit:${slug}:journal`;
|
|
856
|
+
const JOURNAL_KEY = /^gallery-edit:(.+):journal$/;
|
|
857
|
+
|
|
858
|
+
// The departures the journal holds, by tab, their entries still asked of (none left: no departure).
|
|
859
|
+
function readJournal(storage, slug) {
|
|
860
|
+
let record = null;
|
|
861
|
+
try {
|
|
862
|
+
record = JSON.parse(storage.get(journalKey(slug)) ?? "null");
|
|
863
|
+
} catch {}
|
|
864
|
+
const tabs = {};
|
|
865
|
+
const now = Date.now();
|
|
866
|
+
for (const [tab, departure] of Object.entries(record?.tabs && typeof record.tabs === "object" ? record.tabs : {})) {
|
|
867
|
+
const entries = (Array.isArray(departure?.entries) ? departure.entries : []).filter((entry) => entry && typeof entry === "object" && now - entry.time < JOURNAL_FOR);
|
|
868
|
+
if (entries.length > 0) tabs[tab] = { known: typeof departure.known === "string" ? departure.known : null, time: departure.time, entries };
|
|
869
|
+
}
|
|
870
|
+
return tabs;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// The journal written as `tabs`, the newest entries first as long as they fit (one too long left out),
|
|
874
|
+
// or taken away when nothing is left. A write that fails leaves it as it was: the crash buffer comes first.
|
|
875
|
+
function writeJournal(storage, slug, tabs) {
|
|
876
|
+
const all = Object.entries(tabs).flatMap(([tab, departure]) => departure.entries.map((entry) => ({ tab, entry, size: JSON.stringify(entry).length })))
|
|
877
|
+
.sort((a, b) => b.entry.time - a.entry.time);
|
|
878
|
+
let room = JOURNAL_SIZE;
|
|
879
|
+
const fits = new Set();
|
|
880
|
+
for (const { entry, size } of all) {
|
|
881
|
+
if (size > room) continue;
|
|
882
|
+
room -= size;
|
|
883
|
+
fits.add(entry);
|
|
884
|
+
}
|
|
885
|
+
const kept = {};
|
|
886
|
+
for (const [tab, departure] of Object.entries(tabs)) {
|
|
887
|
+
const entries = departure.entries.filter((entry) => fits.has(entry));
|
|
888
|
+
if (entries.length > 0) kept[tab] = { ...departure, entries };
|
|
889
|
+
}
|
|
890
|
+
if (Object.keys(kept).length === 0) return void storage.remove(journalKey(slug));
|
|
891
|
+
const text = JSON.stringify({ v: 1, tabs: kept });
|
|
892
|
+
if (storage.get(journalKey(slug)) !== text) storage.set(journalKey(slug), text);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// The departures of the tabs `which` picks, taken out of the journal: [{ known, entries }].
|
|
896
|
+
function takeJournal(storage, slug, which) {
|
|
897
|
+
const tabs = readJournal(storage, slug);
|
|
898
|
+
const taken = Object.keys(tabs).filter(which);
|
|
899
|
+
if (taken.length === 0) return [];
|
|
900
|
+
const departures = taken.map((tab) => tabs[tab]);
|
|
901
|
+
for (const tab of taken) delete tabs[tab];
|
|
902
|
+
writeJournal(storage, slug, tabs);
|
|
903
|
+
return departures;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// The ids of `ids` and of the blocks made (`made`: [{ id, parent }]) inside one of them, at any depth.
|
|
907
|
+
function madeWithin(ids, made) {
|
|
908
|
+
const gone = new Set(ids);
|
|
909
|
+
for (let more = true; more;) {
|
|
910
|
+
more = false;
|
|
911
|
+
for (const { id, parent } of made) {
|
|
912
|
+
if (gone.has(id) || !gone.has(parent)) continue;
|
|
913
|
+
gone.add(id);
|
|
914
|
+
more = true;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return gone;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// The blocks made that the reader deleted (`ids`, and those made inside them) taken out of every
|
|
921
|
+
// departure the journal holds.
|
|
922
|
+
function forgetMade(storage, slug, ids) {
|
|
923
|
+
const tabs = readJournal(storage, slug);
|
|
924
|
+
let changed = false;
|
|
925
|
+
for (const departure of Object.values(tabs)) {
|
|
926
|
+
const gone = madeWithin(ids, departure.entries.filter((entry) => typeof entry.made === "string").map(({ made, parent }) => ({ id: made, parent })));
|
|
927
|
+
const entries = departure.entries.filter((entry) => typeof entry.made !== "string" || !gone.has(entry.made));
|
|
928
|
+
if (entries.length === departure.entries.length) continue;
|
|
929
|
+
departure.entries = entries;
|
|
930
|
+
changed = true;
|
|
931
|
+
}
|
|
932
|
+
if (changed) writeJournal(storage, slug, tabs);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// This tab's departure written (null: none), beside the other tabs'.
|
|
936
|
+
function keepDeparture(storage, slug, tab, departure) {
|
|
937
|
+
const tabs = readJournal(storage, slug);
|
|
938
|
+
if (departure) tabs[tab] = departure;
|
|
939
|
+
else if (tabs[tab]) delete tabs[tab];
|
|
940
|
+
else if (storage.get(journalKey(slug)) === null) return;
|
|
941
|
+
writeJournal(storage, slug, tabs);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// What a Gallery before 2026-11-08 kept for the next load in the tab (sessionStorage, under the same
|
|
945
|
+
// key): its entries, or { asked, entries }, read once as this tab's departure, the version it last knew
|
|
946
|
+
// that `asked` (none of "own" or nothing: not known).
|
|
947
|
+
function legacyJournal(session, slug) {
|
|
948
|
+
if (!session) return [];
|
|
949
|
+
let stored = null;
|
|
950
|
+
try {
|
|
951
|
+
stored = JSON.parse(session.get(journalKey(slug)) ?? "null");
|
|
952
|
+
} catch {}
|
|
953
|
+
session.remove(journalKey(slug));
|
|
954
|
+
const entries = (Array.isArray(stored) ? stored : Array.isArray(stored?.entries) ? stored.entries : []).filter((entry) => entry && typeof entry === "object" && Date.now() - entry.time < JOURNAL_FOR);
|
|
955
|
+
const known = !Array.isArray(stored) && typeof stored?.asked === "string" && stored.asked !== "own" ? stored.asked : null;
|
|
956
|
+
return entries.length > 0 ? [{ known, entries }] : [];
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// The journals of documents whose every entry is older than JOURNAL_FOR, taken away (a document not
|
|
960
|
+
// opened again within the hour leaves none behind).
|
|
961
|
+
function forgetOld(storage) {
|
|
962
|
+
for (const key of storage.keys?.() ?? []) {
|
|
963
|
+
const slug = JOURNAL_KEY.exec(key)?.[1];
|
|
964
|
+
if (slug && Object.keys(readJournal(storage, slug)).length === 0) storage.remove(key);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
695
968
|
/**
|
|
696
969
|
* Reload the page to show the file as it is now, keeping where the reader was (`view`, or now):
|
|
697
970
|
* the scroll position, the block the caret was in (by its data-block-id) with the caret's offset in
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { postAnswer } from "../post.mjs";
|
|
2
|
+
|
|
3
|
+
// What a custom page's board editing sends the page service (POST /__gallery/pages/<slug>/cards:
|
|
4
|
+
// src/editor/page-service.mjs), one task at a time, in the order asked. A task is the whole of what the
|
|
5
|
+
// page does for one gesture or one step of undo: what it sends, built only once its turn comes, and what
|
|
6
|
+
// it does with the answer (each card's cells and base taken in, the undo step pushed or moved), all done
|
|
7
|
+
// before the next task starts. So a write sent while one before it waits for its answer goes with the
|
|
8
|
+
// bases that answer gave (a block's two context.edit calls in a row, two quick Ctrl+Z on one card, a
|
|
9
|
+
// Ctrl+Z pressed before a drop is answered, which undoes the drop), never with those it had when it was
|
|
10
|
+
// asked, which the service would refuse as changed. It knows which versions of the page are its own (the
|
|
11
|
+
// one it was served at, and each its ops wrote), so that the live script's "page <version>" of its own
|
|
12
|
+
// write changes nothing, and it says when an answer was applied to a version that is none of them:
|
|
13
|
+
// someone else wrote the page meanwhile (`others`), which the page then shows again.
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* slug, token, version: the page map's. others(): someone else wrote the page (an op's answer was
|
|
17
|
+
* applied to another version than the page's). Returns { run(task) (task(send) run once every task asked
|
|
18
|
+
* before it is done, `send(body)` giving the service's answer: { ok, … }, its refusal, or { ok: false,
|
|
19
|
+
* reason: "offline" } where it cannot be reached; resolves with what the task gives), ours(version), busy,
|
|
20
|
+
* idle() (a promise of the tasks asked so far being done) }.
|
|
21
|
+
*/
|
|
22
|
+
export function createClient({ slug, token, version, others = () => {} }) {
|
|
23
|
+
const route = `/__gallery/pages/${slug}/cards`;
|
|
24
|
+
const ours = new Set([version]);
|
|
25
|
+
let current = version;
|
|
26
|
+
let waiting = 0;
|
|
27
|
+
let chain = Promise.resolve();
|
|
28
|
+
const send = async (body) => {
|
|
29
|
+
const answer = await postAnswer(route, token, body);
|
|
30
|
+
if (answer?.ok) {
|
|
31
|
+
const before = answer.before;
|
|
32
|
+
ours.add(answer.version);
|
|
33
|
+
if (before !== current && !ours.has(before)) others();
|
|
34
|
+
current = answer.version;
|
|
35
|
+
}
|
|
36
|
+
return answer;
|
|
37
|
+
};
|
|
38
|
+
const run = (task) => {
|
|
39
|
+
waiting++;
|
|
40
|
+
const done = chain.then(() => task(send)).finally(() => {
|
|
41
|
+
waiting--;
|
|
42
|
+
});
|
|
43
|
+
chain = done.catch(() => {}); // a task that threw is its asker's to hear of; the next goes on
|
|
44
|
+
return done;
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
run,
|
|
48
|
+
/** Whether `version` is the page's own: served at it, or written to it by an op. */
|
|
49
|
+
ours: (version) => ours.has(version),
|
|
50
|
+
/** Whether a task is asked and not yet done (sent, answered and taken in). */
|
|
51
|
+
get busy() {
|
|
52
|
+
return waiting > 0;
|
|
53
|
+
},
|
|
54
|
+
/** Once every task asked so far is done. */
|
|
55
|
+
idle: () => chain,
|
|
56
|
+
};
|
|
57
|
+
}
|