@heycys/gallery 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +50 -2
- package/THIRD-PARTY-NOTICES.md +3253 -0
- package/assets/writing-preferences.md +107 -0
- package/bin/gallery.mjs +10 -0
- package/browser/editor/components/bookmark/icons.mjs +6 -0
- package/browser/editor/components/bookmark/index.mjs +80 -0
- package/browser/editor/components/bookmark/page.mjs +147 -0
- package/browser/editor/components/builders.mjs +19 -0
- package/browser/editor/components/callout/index.mjs +39 -0
- package/browser/editor/components/callout/page.mjs +13 -0
- package/browser/editor/components/code/index.mjs +327 -0
- package/browser/editor/components/code/page.mjs +561 -0
- package/browser/editor/components/colors.mjs +50 -0
- package/browser/editor/components/columns/index.mjs +104 -0
- package/browser/editor/components/columns/page.mjs +297 -0
- package/browser/editor/components/details/index.mjs +72 -0
- package/browser/editor/components/details/page.mjs +86 -0
- package/browser/editor/components/dialog/index.mjs +83 -0
- package/browser/editor/components/dialog/page.mjs +219 -0
- package/browser/editor/components/divider/index.mjs +48 -0
- package/browser/editor/components/embed/icons.mjs +11 -0
- package/browser/editor/components/embed/index.mjs +123 -0
- package/browser/editor/components/embed/page.mjs +309 -0
- package/browser/editor/components/figure/index.mjs +87 -0
- package/browser/editor/components/figure/page.mjs +33 -0
- package/browser/editor/components/file/icons.mjs +14 -0
- package/browser/editor/components/file/index.mjs +77 -0
- package/browser/editor/components/file/page.mjs +114 -0
- package/browser/editor/components/flowchart/add.mjs +123 -0
- package/browser/editor/components/flowchart/index.mjs +246 -0
- package/browser/editor/components/flowchart/page.mjs +453 -0
- package/browser/editor/components/heading/index.mjs +51 -0
- package/browser/editor/components/highlight/index.mjs +79 -0
- package/browser/editor/components/icon/index.mjs +57 -0
- package/browser/editor/components/icon/page.mjs +286 -0
- package/browser/editor/components/icons.mjs +45 -0
- package/browser/editor/components/image/crop.mjs +189 -0
- package/browser/editor/components/image/index.mjs +104 -0
- package/browser/editor/components/image/page.mjs +385 -0
- package/browser/editor/components/image/viewer.mjs +213 -0
- package/browser/editor/components/installed/index.mjs +133 -0
- package/browser/editor/components/installed/page.mjs +422 -0
- package/browser/editor/components/installed/served.mjs +35 -0
- package/browser/editor/components/kit.mjs +64 -0
- package/browser/editor/components/list/index.mjs +194 -0
- package/browser/editor/components/list/join.mjs +68 -0
- package/browser/editor/components/list/numbers.mjs +107 -0
- package/browser/editor/components/list/page.mjs +632 -0
- package/browser/editor/components/math/index.mjs +88 -0
- package/browser/editor/components/math/page.mjs +236 -0
- package/browser/editor/components/math/tex-logo.mjs +6 -0
- package/browser/editor/components/media/icons.mjs +19 -0
- package/browser/editor/components/media/page.mjs +162 -0
- package/browser/editor/components/media/parts.mjs +60 -0
- package/browser/editor/components/media/size.mjs +20 -0
- package/browser/editor/components/media/toolbar.mjs +37 -0
- package/browser/editor/components/media/write.mjs +7 -0
- package/browser/editor/components/pages.mjs +46 -0
- package/browser/editor/components/paragraph/index.mjs +43 -0
- package/browser/editor/components/paragraph/page.mjs +52 -0
- package/browser/editor/components/placeholder-bar.mjs +17 -0
- package/browser/editor/components/placeholder.mjs +128 -0
- package/browser/editor/components/quote/index.mjs +33 -0
- package/browser/editor/components/registry.mjs +833 -0
- package/browser/editor/components/rows.mjs +16 -0
- package/browser/editor/components/section/index.mjs +41 -0
- package/browser/editor/components/section/page.mjs +141 -0
- package/browser/editor/components/sheets/index.mjs +137 -0
- package/browser/editor/components/sheets/page.mjs +227 -0
- package/browser/editor/components/sheets/pills.mjs +226 -0
- package/browser/editor/components/table/grid.mjs +73 -0
- package/browser/editor/components/table/index.mjs +306 -0
- package/browser/editor/components/table/page.mjs +672 -0
- package/browser/editor/components/table/slots.mjs +41 -0
- package/browser/editor/components/widget/index.mjs +50 -0
- package/browser/editor/page/annotate/anchors.mjs +229 -0
- package/browser/editor/page/annotate/card.mjs +102 -0
- package/browser/editor/page/annotate/index.mjs +1629 -0
- package/browser/editor/page/at-menu/index.mjs +317 -0
- package/browser/editor/page/autosave.mjs +977 -0
- package/browser/editor/page/beside.mjs +21 -0
- package/browser/editor/page/block-info.mjs +63 -0
- package/browser/editor/page/block-menu/colors.mjs +82 -0
- package/browser/editor/page/block-menu/index.mjs +215 -0
- package/browser/editor/page/blocks.mjs +246 -0
- package/browser/editor/page/caret.mjs +679 -0
- package/browser/editor/page/chip-fills.mjs +126 -0
- package/browser/editor/page/clipboard/import/colors.mjs +192 -0
- package/browser/editor/page/clipboard/import/html.mjs +792 -0
- package/browser/editor/page/clipboard/import/inline.mjs +232 -0
- package/browser/editor/page/clipboard/import/languages.mjs +42 -0
- package/browser/editor/page/clipboard/import/markdown.mjs +760 -0
- package/browser/editor/page/clipboard/import/math.mjs +100 -0
- package/browser/editor/page/clipboard/import/office.mjs +245 -0
- package/browser/editor/page/clipboard/import/read.mjs +120 -0
- package/browser/editor/page/clipboard/import/rich.mjs +29 -0
- package/browser/editor/page/clipboard/import/to-blocks.mjs +290 -0
- package/browser/editor/page/clipboard/index.mjs +797 -0
- package/browser/editor/page/clipboard/own/blocks.mjs +202 -0
- package/browser/editor/page/clipboard/own/chart-keys.mjs +58 -0
- package/browser/editor/page/clipboard/own/describe.mjs +115 -0
- package/browser/editor/page/clipboard/own/export.mjs +167 -0
- package/browser/editor/page/clipboard/own/ids.mjs +39 -0
- package/browser/editor/page/clipboard/own/index.mjs +13 -0
- package/browser/editor/page/clipboard/own/rules.mjs +44 -0
- package/browser/editor/page/clipboard/own/selection.mjs +159 -0
- package/browser/editor/page/clipboard/own/writers.mjs +326 -0
- package/browser/editor/page/columns/index.mjs +360 -0
- package/browser/editor/page/commit.mjs +1798 -0
- package/browser/editor/page/conflicts.mjs +269 -0
- package/browser/editor/page/convert.mjs +830 -0
- package/browser/editor/page/core.mjs +1655 -0
- package/browser/editor/page/drag/drop.mjs +307 -0
- package/browser/editor/page/drag/index.mjs +335 -0
- package/browser/editor/page/drag/preview.mjs +121 -0
- package/browser/editor/page/edge-scroll.mjs +50 -0
- package/browser/editor/page/editor.mjs +1725 -0
- package/browser/editor/page/emoji-picker/emoji-tab.mjs +278 -0
- package/browser/editor/page/emoji-picker/glyph-html.mjs +26 -0
- package/browser/editor/page/emoji-picker/grid.mjs +79 -0
- package/browser/editor/page/emoji-picker/icon-tab.mjs +217 -0
- package/browser/editor/page/emoji-picker/icons.mjs +32 -0
- package/browser/editor/page/emoji-picker/index.mjs +164 -0
- package/browser/editor/page/emoji-picker/styles.mjs +2 -0
- package/browser/editor/page/emoji-picker/support.mjs +202 -0
- package/browser/editor/page/emoji-picker/upload.mjs +235 -0
- package/browser/editor/page/export-report.mjs +23 -0
- package/browser/editor/page/fills.mjs +60 -0
- package/browser/editor/page/find/index.mjs +107 -0
- package/browser/editor/page/flags.mjs +70 -0
- package/browser/editor/page/format-bar/bar.mjs +448 -0
- package/browser/editor/page/format-bar/brush.mjs +77 -0
- package/browser/editor/page/format-bar/colors.mjs +46 -0
- package/browser/editor/page/format-bar/index.mjs +404 -0
- package/browser/editor/page/formula/index.mjs +236 -0
- package/browser/editor/page/formula/panel.mjs +336 -0
- package/browser/editor/page/formula/symbols.mjs +156 -0
- package/browser/editor/page/frame.mjs +74 -0
- package/browser/editor/page/frames.mjs +196 -0
- package/browser/editor/page/geometry.mjs +400 -0
- package/browser/editor/page/gesture.mjs +45 -0
- package/browser/editor/page/handle/index.mjs +639 -0
- package/browser/editor/page/handle-sizes.mjs +14 -0
- package/browser/editor/page/hints/index.mjs +84 -0
- package/browser/editor/page/history.mjs +401 -0
- package/browser/editor/page/insert/index.mjs +243 -0
- package/browser/editor/page/kept.mjs +38 -0
- package/browser/editor/page/links/address.mjs +40 -0
- package/browser/editor/page/links/index.mjs +866 -0
- package/browser/editor/page/links/pages.mjs +131 -0
- package/browser/editor/page/links/views.mjs +369 -0
- package/browser/editor/page/look.mjs +65 -0
- package/browser/editor/page/made.mjs +24 -0
- package/browser/editor/page/margin.mjs +352 -0
- package/browser/editor/page/markdown/index.mjs +88 -0
- package/browser/editor/page/markdown/rules.mjs +33 -0
- package/browser/editor/page/marks.mjs +377 -0
- package/browser/editor/page/media-menu/index.mjs +158 -0
- package/browser/editor/page/menu.mjs +500 -0
- package/browser/editor/page/merge.mjs +100 -0
- package/browser/editor/page/notices.mjs +142 -0
- package/browser/editor/page/offered.mjs +32 -0
- package/browser/editor/page/outline/folds.mjs +52 -0
- package/browser/editor/page/outline/index.mjs +697 -0
- package/browser/editor/page/outline/list.mjs +172 -0
- package/browser/editor/page/outline/read.mjs +201 -0
- package/browser/editor/page/outline/strip.mjs +281 -0
- package/browser/editor/page/outline/tree.mjs +248 -0
- package/browser/editor/page/overlay-layer.mjs +227 -0
- package/browser/editor/page/overlays.mjs +205 -0
- package/browser/editor/page/own.mjs +28 -0
- package/browser/editor/page/pads.mjs +71 -0
- package/browser/editor/page/page-objects.mjs +105 -0
- package/browser/editor/page/panel.mjs +64 -0
- package/browser/editor/page/placement.mjs +58 -0
- package/browser/editor/page/post.mjs +41 -0
- package/browser/editor/page/present-links.mjs +50 -0
- package/browser/editor/page/quiet.mjs +48 -0
- package/browser/editor/page/resize/index.mjs +327 -0
- package/browser/editor/page/select-all/index.mjs +262 -0
- package/browser/editor/page/selection/delete.mjs +59 -0
- package/browser/editor/page/selection/index.mjs +157 -0
- package/browser/editor/page/selection/marquee.mjs +174 -0
- package/browser/editor/page/session.mjs +117 -0
- package/browser/editor/page/slash-menu/index.mjs +291 -0
- package/browser/editor/page/storage.mjs +48 -0
- package/browser/editor/page/sync-state.mjs +267 -0
- package/browser/editor/page/table/aligns.mjs +29 -0
- package/browser/editor/page/table/cell-bar.mjs +138 -0
- package/browser/editor/page/table/cells.mjs +527 -0
- package/browser/editor/page/table/icons.mjs +40 -0
- package/browser/editor/page/table/index.mjs +985 -0
- package/browser/editor/page/table/lists.mjs +187 -0
- package/browser/editor/page/table/widths.mjs +308 -0
- package/browser/editor/page/tex.mjs +32 -0
- package/browser/editor/page/text-select/index.mjs +656 -0
- package/browser/editor/page/tooltip.mjs +98 -0
- package/browser/editor/page/type-grid.mjs +82 -0
- package/browser/editor/page/typing/code-edges.mjs +228 -0
- package/browser/editor/page/typing/index.mjs +276 -0
- package/browser/editor/page/typing/leave.mjs +68 -0
- package/browser/editor/page/ui.mjs +160 -0
- package/browser/shared/annotations.mjs +571 -0
- package/browser/shared/code-folds.mjs +10 -0
- package/browser/shared/code-languages.mjs +70 -0
- package/browser/shared/code-lines.mjs +30 -0
- package/browser/shared/colors.mjs +28 -0
- package/browser/shared/component-catalog.mjs +559 -0
- package/browser/shared/components/bookmark.mjs +26 -0
- package/browser/shared/components/callout.mjs +7 -0
- package/browser/shared/components/chart.mjs +20 -0
- package/browser/shared/components/code.mjs +37 -0
- package/browser/shared/components/columns.mjs +16 -0
- package/browser/shared/components/details.mjs +15 -0
- package/browser/shared/components/dialog.mjs +28 -0
- package/browser/shared/components/diff.mjs +18 -0
- package/browser/shared/components/divider.mjs +6 -0
- package/browser/shared/components/embed.mjs +25 -0
- package/browser/shared/components/figure.mjs +14 -0
- package/browser/shared/components/file.mjs +23 -0
- package/browser/shared/components/heading.mjs +6 -0
- package/browser/shared/components/highlight.mjs +9 -0
- package/browser/shared/components/icon.mjs +18 -0
- package/browser/shared/components/image.mjs +39 -0
- package/browser/shared/components/index.mjs +342 -0
- package/browser/shared/components/installed.mjs +162 -0
- package/browser/shared/components/list.mjs +146 -0
- package/browser/shared/components/math.mjs +49 -0
- package/browser/shared/components/mention.mjs +33 -0
- package/browser/shared/components/page.mjs +37 -0
- package/browser/shared/components/paragraph.mjs +9 -0
- package/browser/shared/components/quote.mjs +6 -0
- package/browser/shared/components/section.mjs +8 -0
- package/browser/shared/components/sheets.mjs +80 -0
- package/browser/shared/components/state.mjs +20 -0
- package/browser/shared/components/table.mjs +49 -0
- package/browser/shared/components/text.mjs +14 -0
- package/browser/shared/components/widget.mjs +10 -0
- package/browser/shared/copy-ids.mjs +72 -0
- package/browser/shared/css-references.mjs +110 -0
- package/browser/shared/emoji/custom.mjs +91 -0
- package/browser/shared/emoji/data/LICENSE-mingcute.txt +202 -0
- package/browser/shared/emoji/data/LICENSE-tabler.txt +21 -0
- package/browser/shared/emoji/data/README.md +26 -0
- package/browser/shared/emoji/data/categories.json +1 -0
- package/browser/shared/emoji/data/icons.json +1 -0
- package/browser/shared/emoji/data/keywords-en.json +1 -0
- package/browser/shared/emoji/data/keywords-zh.json +1 -0
- package/browser/shared/emoji/data/names.json +1 -0
- package/browser/shared/emoji/data/positions.json +1 -0
- package/browser/shared/emoji/data/twemoji-index.json +1 -0
- package/browser/shared/emoji/data/twemoji-svg.bin +0 -0
- package/browser/shared/emoji/data.mjs +196 -0
- package/browser/shared/emoji/emoji.mjs +37 -0
- package/browser/shared/emoji/glyphs.mjs +134 -0
- package/browser/shared/emoji/icon-value.mjs +90 -0
- package/browser/shared/escape.mjs +20 -0
- package/browser/shared/export-wrap.mjs +89 -0
- package/browser/shared/files.mjs +33 -0
- package/browser/shared/html-tree.mjs +53 -0
- package/browser/shared/http.mjs +22 -0
- package/browser/shared/i18n/en.mjs +1164 -0
- package/browser/shared/i18n/index.mjs +132 -0
- package/browser/shared/ids.mjs +24 -0
- package/browser/shared/import-types.mjs +23 -0
- package/browser/shared/inline-spacing.mjs +97 -0
- package/browser/shared/layers.mjs +42 -0
- package/browser/shared/media.mjs +162 -0
- package/browser/shared/new-names.mjs +64 -0
- package/browser/shared/number-reading.mjs +92 -0
- package/browser/shared/old-names.mjs +143 -0
- package/browser/shared/page-address.mjs +140 -0
- package/browser/shared/page-links.mjs +86 -0
- package/browser/shared/paths.mjs +73 -0
- package/browser/shared/preferences.mjs +110 -0
- package/browser/shared/preview-server.mjs +247 -0
- package/browser/shared/qa-levels.mjs +93 -0
- package/browser/shared/reading-place.mjs +91 -0
- package/browser/shared/renamed-storage.mjs +220 -0
- package/browser/shared/reports.mjs +46 -0
- package/browser/shared/search.mjs +85 -0
- package/browser/shared/session.mjs +76 -0
- package/browser/shared/table-chart.mjs +113 -0
- package/browser/shared/table-hides.mjs +120 -0
- package/browser/shared/table-slots.mjs +75 -0
- package/browser/shared/words.mjs +23 -0
- package/browser/shared/workspace.mjs +535 -0
- package/browser/shared/writing-preferences.mjs +68 -0
- package/browser/shared/zoom.mjs +57 -0
- package/dist/chunk-2B5E4CDJ.mjs +2 -0
- package/dist/chunk-2PBALTR3.mjs +57 -0
- package/dist/chunk-342HXE3Q.mjs +2 -0
- package/dist/chunk-3LA3EMDE.mjs +15 -0
- package/dist/chunk-43B75J5T.mjs +2 -0
- package/dist/chunk-4K5KC6V2.mjs +2 -0
- package/dist/chunk-5FRMCDPY.mjs +16 -0
- package/dist/chunk-5L7RYZCL.mjs +2 -0
- package/dist/chunk-5LEK2CBS.mjs +2 -0
- package/dist/chunk-5OFN5AHC.mjs +2 -0
- package/dist/chunk-5SIYS5HN.mjs +2 -0
- package/dist/chunk-5TEK3RAD.mjs +2 -0
- package/dist/chunk-6CYG3Z4V.mjs +2 -0
- package/dist/chunk-6U2SLSDN.mjs +3 -0
- package/dist/chunk-742D2WWJ.mjs +19 -0
- package/dist/chunk-7GHT2UCA.mjs +15 -0
- package/dist/chunk-7QQBR7PB.mjs +2 -0
- package/dist/chunk-A55HN5OK.mjs +2 -0
- package/dist/chunk-ACOPDCNB.mjs +2 -0
- package/dist/chunk-AGDILNWB.mjs +2 -0
- package/dist/chunk-ATCJYAR4.mjs +2 -0
- package/dist/chunk-B3VTYBYR.mjs +960 -0
- package/dist/chunk-C564NU2H.mjs +77 -0
- package/dist/chunk-C7HWOBIF.mjs +2 -0
- package/dist/chunk-DB4WKGYF.mjs +7 -0
- package/dist/chunk-E7VYDG2X.mjs +2 -0
- package/dist/chunk-EUX7IC5J.mjs +48 -0
- package/dist/chunk-EVJ3BX3M.mjs +2 -0
- package/dist/chunk-EZY3WC6Z.mjs +15 -0
- package/dist/chunk-FQGQH4KM.mjs +2 -0
- package/dist/chunk-G7TOXONE.mjs +6 -0
- package/dist/chunk-GPF4YWWB.mjs +9 -0
- package/dist/chunk-GRAXKKG4.mjs +2 -0
- package/dist/chunk-GYQV7Y4J.mjs +120 -0
- package/dist/chunk-HMHK3EQ6.mjs +2 -0
- package/dist/chunk-HQQMJCQJ.mjs +10 -0
- package/dist/chunk-HYMEEXKN.mjs +4 -0
- package/dist/chunk-IERUOGC3.mjs +6 -0
- package/dist/chunk-ITNLYXXV.mjs +3 -0
- package/dist/chunk-L4DVONOG.mjs +2 -0
- package/dist/chunk-LAE5NQ4B.mjs +2 -0
- package/dist/chunk-LC7YNQFG.mjs +2 -0
- package/dist/chunk-LHTFLRB6.mjs +4 -0
- package/dist/chunk-LKX55H76.mjs +2 -0
- package/dist/chunk-MAFIGVXY.mjs +2 -0
- package/dist/chunk-NFIBI4IP.mjs +26 -0
- package/dist/chunk-NSKUTQ5J.mjs +5 -0
- package/dist/chunk-O52JMOCZ.mjs +2 -0
- package/dist/chunk-P2CAZPDP.mjs +3 -0
- package/dist/chunk-QAIHRH3I.mjs +13 -0
- package/dist/chunk-QMQUXTU4.mjs +12 -0
- package/dist/chunk-QR6A54KJ.mjs +3 -0
- package/dist/chunk-QVWVFC5K.mjs +6 -0
- package/dist/chunk-QYKCJ7FG.mjs +8 -0
- package/dist/chunk-QZY6Q64C.mjs +2 -0
- package/dist/chunk-TDQP3DKQ.mjs +10 -0
- package/dist/chunk-TMYZB6AV.mjs +2 -0
- package/dist/chunk-TX6JH7TR.mjs +2 -0
- package/dist/chunk-U3SSPL4K.mjs +8 -0
- package/dist/chunk-UH4SIXTR.mjs +2 -0
- package/dist/chunk-UNUEWH4S.mjs +2 -0
- package/dist/chunk-UXUK4PMC.mjs +27 -0
- package/dist/chunk-V4CPWKMZ.mjs +2 -0
- package/dist/chunk-V6XZ4VIR.mjs +2 -0
- package/dist/chunk-VACRB2NE.mjs +3 -0
- package/dist/chunk-W63L7367.mjs +7 -0
- package/dist/chunk-W7CXPCRV.mjs +6 -0
- package/dist/chunk-W7XHE4CT.mjs +2 -0
- package/dist/chunk-XINNG4F2.mjs +4 -0
- package/dist/chunk-Y34NR5JL.mjs +2 -0
- package/dist/chunk-YEY3RSFQ.mjs +2 -0
- package/dist/chunk-YL2DPWVS.mjs +4 -0
- package/dist/chunk-YXAUQPVK.mjs +8 -0
- package/dist/chunk-YZ5Z6H4W.mjs +4 -0
- package/dist/chunk-Z52HTUW7.mjs +4 -0
- package/dist/chunk-ZBIM6ARY.mjs +216 -0
- package/dist/cli.mjs +2 -0
- package/dist/server.mjs +2 -0
- package/dist/worker.mjs +5 -0
- package/kit/charts.js +355 -0
- package/kit/code.js +164 -0
- package/kit/dialogs.js +157 -0
- package/kit/math.js +1988 -0
- package/kit/report.css +2821 -0
- package/kit/runtime.js +1707 -0
- package/kit/sheets.js +123 -0
- package/kit/state.js +217 -0
- package/kit/tables.js +1449 -0
- package/kit/tokens.js +29 -0
- package/kit/ui.js +561 -0
- package/package.json +41 -4
- package/release.json +25 -0
- package/sdk/column-widths.d.ts +56 -0
- package/sdk/doc-charts.d.ts +82 -0
- package/sdk/doc-code.d.ts +28 -0
- package/sdk/doc-dialogs.d.ts +4 -0
- package/sdk/doc-kit.d.ts +141 -0
- package/sdk/doc-math.d.ts +3 -0
- package/sdk/doc-sheets.d.ts +15 -0
- package/sdk/doc-state.d.ts +41 -0
- package/sdk/doc-tables.d.ts +24 -0
- package/sdk/doc-tokens.d.ts +22 -0
- package/sdk/index.d.ts +30 -0
- package/skill/SKILL.md +138 -0
- package/skill/agents/openai.yaml +5 -0
- package/skill/references/building-components.md +327 -0
- package/skill/references/example-report.html +356 -0
- package/skill/references/writing-html.md +187 -0
- package/skill/sdk/column-widths.d.ts +56 -0
- package/skill/sdk/doc-charts.d.ts +82 -0
- package/skill/sdk/doc-code.d.ts +28 -0
- package/skill/sdk/doc-dialogs.d.ts +4 -0
- package/skill/sdk/doc-kit.d.ts +141 -0
- package/skill/sdk/doc-math.d.ts +3 -0
- package/skill/sdk/doc-sheets.d.ts +15 -0
- package/skill/sdk/doc-state.d.ts +41 -0
- package/skill/sdk/doc-tables.d.ts +24 -0
- package/skill/sdk/doc-tokens.d.ts +22 -0
- package/skill/sdk/index.d.ts +30 -0
- package/starter/doc.html +21 -0
- package/starter/doc.json +5 -0
- package/vendor/d3.v7.9.0.min.js +2 -0
- package/vendor/katex/auto-render.min.js +1 -0
- package/vendor/katex/contrib/mhchem.min.js +1 -0
- package/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/vendor/katex/katex.min.css +1 -0
- package/vendor/katex/katex.min.js +1 -0
- package/vendor/pdfjs/LICENSE +177 -0
- package/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
- package/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
- package/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
- package/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
- package/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
- package/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
- package/vendor/pdfjs/cmaps/LICENSE +36 -0
- package/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
- package/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/V.bcmap +0 -0
- package/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
- package/vendor/pdfjs/legacy/build/pdf.min.mjs +27 -0
- package/vendor/pdfjs/legacy/build/pdf.worker.min.mjs +27 -0
- package/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
- package/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
- package/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
- package/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
- package/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
- package/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
- package/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
- package/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
- package/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
- package/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
- package/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
- package/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +363 -0
- package/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
- package/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
- package/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
- package/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
- package/vendor/types/@types/d3/LICENSE +21 -0
- package/vendor/types/@types/d3/index.d.ts +39 -0
- package/vendor/types/@types/d3/package.json +81 -0
- package/vendor/types/@types/d3-array/LICENSE +21 -0
- package/vendor/types/@types/d3-array/index.d.ts +1118 -0
- package/vendor/types/@types/d3-array/package.json +56 -0
- package/vendor/types/@types/d3-axis/LICENSE +21 -0
- package/vendor/types/@types/d3-axis/index.d.ts +293 -0
- package/vendor/types/@types/d3-axis/package.json +47 -0
- package/vendor/types/@types/d3-brush/LICENSE +21 -0
- package/vendor/types/@types/d3-brush/index.d.ts +255 -0
- package/vendor/types/@types/d3-brush/package.json +42 -0
- package/vendor/types/@types/d3-chord/LICENSE +21 -0
- package/vendor/types/@types/d3-chord/index.d.ts +471 -0
- package/vendor/types/@types/d3-chord/package.json +40 -0
- package/vendor/types/@types/d3-color/LICENSE +21 -0
- package/vendor/types/@types/d3-color/index.d.ts +669 -0
- package/vendor/types/@types/d3-color/package.json +55 -0
- package/vendor/types/@types/d3-contour/LICENSE +21 -0
- package/vendor/types/@types/d3-contour/index.d.ts +222 -0
- package/vendor/types/@types/d3-contour/package.json +38 -0
- package/vendor/types/@types/d3-delaunay/LICENSE +21 -0
- package/vendor/types/@types/d3-delaunay/index.d.ts +316 -0
- package/vendor/types/@types/d3-delaunay/package.json +30 -0
- package/vendor/types/@types/d3-dispatch/LICENSE +21 -0
- package/vendor/types/@types/d3-dispatch/index.d.ts +96 -0
- package/vendor/types/@types/d3-dispatch/package.json +51 -0
- package/vendor/types/@types/d3-drag/LICENSE +21 -0
- package/vendor/types/@types/d3-drag/index.d.ts +395 -0
- package/vendor/types/@types/d3-drag/package.json +42 -0
- package/vendor/types/@types/d3-dsv/LICENSE +21 -0
- package/vendor/types/@types/d3-dsv/index.d.ts +482 -0
- package/vendor/types/@types/d3-dsv/package.json +45 -0
- package/vendor/types/@types/d3-ease/LICENSE +21 -0
- package/vendor/types/@types/d3-ease/index.d.ts +316 -0
- package/vendor/types/@types/d3-ease/package.json +40 -0
- package/vendor/types/@types/d3-fetch/LICENSE +21 -0
- package/vendor/types/@types/d3-fetch/index.d.ts +282 -0
- package/vendor/types/@types/d3-fetch/package.json +37 -0
- package/vendor/types/@types/d3-force/LICENSE +21 -0
- package/vendor/types/@types/d3-force/index.d.ts +899 -0
- package/vendor/types/@types/d3-force/package.json +45 -0
- package/vendor/types/@types/d3-format/LICENSE +21 -0
- package/vendor/types/@types/d3-format/index.d.ts +337 -0
- package/vendor/types/@types/d3-format/package.json +45 -0
- package/vendor/types/@types/d3-geo/LICENSE +21 -0
- package/vendor/types/@types/d3-geo/index.d.ts +1371 -0
- package/vendor/types/@types/d3-geo/package.json +48 -0
- package/vendor/types/@types/d3-hierarchy/LICENSE +21 -0
- package/vendor/types/@types/d3-hierarchy/index.d.ts +928 -0
- package/vendor/types/@types/d3-hierarchy/package.json +50 -0
- package/vendor/types/@types/d3-interpolate/LICENSE +21 -0
- package/vendor/types/@types/d3-interpolate/index.d.ts +387 -0
- package/vendor/types/@types/d3-interpolate/package.json +47 -0
- package/vendor/types/@types/d3-path/LICENSE +21 -0
- package/vendor/types/@types/d3-path/index.d.ts +108 -0
- package/vendor/types/@types/d3-path/package.json +41 -0
- package/vendor/types/@types/d3-polygon/LICENSE +21 -0
- package/vendor/types/@types/d3-polygon/index.d.ts +41 -0
- package/vendor/types/@types/d3-polygon/package.json +40 -0
- package/vendor/types/@types/d3-quadtree/LICENSE +21 -0
- package/vendor/types/@types/d3-quadtree/index.d.ts +204 -0
- package/vendor/types/@types/d3-quadtree/package.json +45 -0
- package/vendor/types/@types/d3-random/LICENSE +21 -0
- package/vendor/types/@types/d3-random/index.d.ts +313 -0
- package/vendor/types/@types/d3-random/package.json +41 -0
- package/vendor/types/@types/d3-scale/LICENSE +21 -0
- package/vendor/types/@types/d3-scale/index.d.ts +2669 -0
- package/vendor/types/@types/d3-scale/package.json +53 -0
- package/vendor/types/@types/d3-scale-chromatic/LICENSE +21 -0
- package/vendor/types/@types/d3-scale-chromatic/index.d.ts +520 -0
- package/vendor/types/@types/d3-scale-chromatic/package.json +46 -0
- package/vendor/types/@types/d3-selection/LICENSE +21 -0
- package/vendor/types/@types/d3-selection/index.d.ts +1210 -0
- package/vendor/types/@types/d3-selection/package.json +56 -0
- package/vendor/types/@types/d3-shape/LICENSE +21 -0
- package/vendor/types/@types/d3-shape/index.d.ts +2792 -0
- package/vendor/types/@types/d3-shape/package.json +53 -0
- package/vendor/types/@types/d3-time/LICENSE +21 -0
- package/vendor/types/@types/d3-time/index.d.ts +697 -0
- package/vendor/types/@types/d3-time/package.json +46 -0
- package/vendor/types/@types/d3-time-format/LICENSE +21 -0
- package/vendor/types/@types/d3-time-format/index.d.ts +202 -0
- package/vendor/types/@types/d3-time-format/package.json +40 -0
- package/vendor/types/@types/d3-timer/LICENSE +21 -0
- package/vendor/types/@types/d3-timer/index.d.ts +68 -0
- package/vendor/types/@types/d3-timer/package.json +45 -0
- package/vendor/types/@types/d3-transition/LICENSE +21 -0
- package/vendor/types/@types/d3-transition/index.d.ts +664 -0
- package/vendor/types/@types/d3-transition/package.json +47 -0
- package/vendor/types/@types/d3-zoom/LICENSE +21 -0
- package/vendor/types/@types/d3-zoom/index.d.ts +611 -0
- package/vendor/types/@types/d3-zoom/package.json +48 -0
- package/vendor/types/@types/geojson/LICENSE +21 -0
- package/vendor/types/@types/geojson/index.d.ts +202 -0
- package/vendor/types/@types/geojson/package.json +46 -0
- package/vendor/typescript/LICENSE.txt +55 -0
- package/vendor/typescript/lib.decorators.d.ts +384 -0
- package/vendor/typescript/lib.decorators.legacy.d.ts +22 -0
- package/vendor/typescript/lib.dom.d.ts +39429 -0
- package/vendor/typescript/lib.dom.iterable.d.ts +571 -0
- package/vendor/typescript/lib.es2015.collection.d.ts +147 -0
- package/vendor/typescript/lib.es2015.core.d.ts +597 -0
- package/vendor/typescript/lib.es2015.d.ts +28 -0
- package/vendor/typescript/lib.es2015.generator.d.ts +77 -0
- package/vendor/typescript/lib.es2015.iterable.d.ts +605 -0
- package/vendor/typescript/lib.es2015.promise.d.ts +81 -0
- package/vendor/typescript/lib.es2015.proxy.d.ts +128 -0
- package/vendor/typescript/lib.es2015.reflect.d.ts +144 -0
- package/vendor/typescript/lib.es2015.symbol.d.ts +46 -0
- package/vendor/typescript/lib.es2015.symbol.wellknown.d.ts +326 -0
- package/vendor/typescript/lib.es2016.array.include.d.ts +116 -0
- package/vendor/typescript/lib.es2016.d.ts +21 -0
- package/vendor/typescript/lib.es2016.intl.d.ts +31 -0
- package/vendor/typescript/lib.es2017.arraybuffer.d.ts +21 -0
- package/vendor/typescript/lib.es2017.d.ts +26 -0
- package/vendor/typescript/lib.es2017.date.d.ts +31 -0
- package/vendor/typescript/lib.es2017.intl.d.ts +44 -0
- package/vendor/typescript/lib.es2017.object.d.ts +49 -0
- package/vendor/typescript/lib.es2017.sharedmemory.d.ts +135 -0
- package/vendor/typescript/lib.es2017.string.d.ts +45 -0
- package/vendor/typescript/lib.es2017.typedarrays.d.ts +53 -0
- package/vendor/typescript/lib.es2018.asyncgenerator.d.ts +77 -0
- package/vendor/typescript/lib.es2018.asynciterable.d.ts +53 -0
- package/vendor/typescript/lib.es2018.d.ts +24 -0
- package/vendor/typescript/lib.es2018.intl.d.ts +83 -0
- package/vendor/typescript/lib.es2018.promise.d.ts +30 -0
- package/vendor/typescript/lib.es2018.regexp.d.ts +37 -0
- package/vendor/typescript/lib.es2019.array.d.ts +79 -0
- package/vendor/typescript/lib.es2019.d.ts +24 -0
- package/vendor/typescript/lib.es2019.intl.d.ts +23 -0
- package/vendor/typescript/lib.es2019.object.d.ts +33 -0
- package/vendor/typescript/lib.es2019.string.d.ts +37 -0
- package/vendor/typescript/lib.es2019.symbol.d.ts +24 -0
- package/vendor/typescript/lib.es2020.bigint.d.ts +765 -0
- package/vendor/typescript/lib.es2020.d.ts +27 -0
- package/vendor/typescript/lib.es2020.date.d.ts +42 -0
- package/vendor/typescript/lib.es2020.intl.d.ts +474 -0
- package/vendor/typescript/lib.es2020.number.d.ts +28 -0
- package/vendor/typescript/lib.es2020.promise.d.ts +47 -0
- package/vendor/typescript/lib.es2020.sharedmemory.d.ts +99 -0
- package/vendor/typescript/lib.es2020.string.d.ts +44 -0
- package/vendor/typescript/lib.es2020.symbol.wellknown.d.ts +41 -0
- package/vendor/typescript/lib.es2021.d.ts +23 -0
- package/vendor/typescript/lib.es2021.intl.d.ts +166 -0
- package/vendor/typescript/lib.es2021.promise.d.ts +48 -0
- package/vendor/typescript/lib.es2021.string.d.ts +33 -0
- package/vendor/typescript/lib.es2021.weakref.d.ts +78 -0
- package/vendor/typescript/lib.es2022.array.d.ts +121 -0
- package/vendor/typescript/lib.es2022.d.ts +25 -0
- package/vendor/typescript/lib.es2022.error.d.ts +75 -0
- package/vendor/typescript/lib.es2022.intl.d.ts +145 -0
- package/vendor/typescript/lib.es2022.object.d.ts +26 -0
- package/vendor/typescript/lib.es2022.regexp.d.ts +39 -0
- package/vendor/typescript/lib.es2022.string.d.ts +25 -0
- package/vendor/typescript/lib.es5.d.ts +4601 -0
- package/vendor/typescript/package.json +4 -0
- package/vendor/typescript/typescript.js +446 -0
- package/web/assets/index-B6Ai3wsa.js +13 -0
- package/web/assets/index-SYPC3EZy.css +1 -0
- package/web/index.html +14 -0
|
@@ -0,0 +1,1798 @@
|
|
|
1
|
+
import { ELEMENT_ID, blocksText, ownHTML, ownText, captureLayout, collectEdits, firstByAttribute, identityOf, knownElements, lockHost, madeFrom, matchesSource, prepareBlock, serializeInner, setOwnHTML, shownAs, snapshotLayout } from "./core.mjs";
|
|
2
|
+
import { BLOCK_TYPES, SEALED, accepts, blockTypeOf, boxIn, boxOwner, boxRole, capsFor, holdsStraight, takesBox } from "../components/registry.mjs";
|
|
3
|
+
import { COPY_KEYS } from "../components/figure/index.mjs";
|
|
4
|
+
import { select } from "./caret.mjs";
|
|
5
|
+
import { ownTypeOf } from "./convert.mjs";
|
|
6
|
+
import { rangeAt, rangeOffsets } from "./marks.mjs";
|
|
7
|
+
import { pageOfKind, pageOfUnit } from "./page-objects.mjs";
|
|
8
|
+
import { containerTypeOf, pack, project } from "./sync-state.mjs";
|
|
9
|
+
import { outsideKey, randomBlockId } from "../../shared/ids.mjs";
|
|
10
|
+
import { CHART_DATA, CHART_SOURCE } from "../../shared/table-chart.mjs";
|
|
11
|
+
|
|
12
|
+
// The page's commits (autosave; docs/editor.md, "Commits"). A commit is what changed on the page
|
|
13
|
+
// since the last one, as collectEdits finds it against the layout and baselines the last commit
|
|
14
|
+
// left, sent to POST /__gallery/edit/commit with everything named by identity instead of by where
|
|
15
|
+
// it was: a unit by its data-block-id, or, untouched since the page opened and without one yet, by
|
|
16
|
+
// its number in the page's map and its hash; a text block by its unit (the unit's own text, or a
|
|
17
|
+
// field of it by the path the map gives) and the source it had at the last commit (`before`).
|
|
18
|
+
// Every unit whose text the commit changes, and the units around it, is named too (`touch`), so
|
|
19
|
+
// that it has an id afterwards; so is a unit something of the page's own asked to name (idFor: the
|
|
20
|
+
// blocks a 批注 is about, annotate/), with the next commit, alone when nothing else waits.
|
|
21
|
+
//
|
|
22
|
+
// What of the file each element is lives in a table here, under the element's identity (core.mjs
|
|
23
|
+
// ELEMENT_ID), never on the element: the unit, text block and box it is, what the file dropped
|
|
24
|
+
// that the page still shows, the ids blocks made on the page propose. The marks the rest of the
|
|
25
|
+
// editor reads (data-ge-unit, data-ge-new…) are written from it (sync-state.mjs project) before
|
|
26
|
+
// every look at the page and after every answer, so an element undo brings back is marked from
|
|
27
|
+
// what the file has now, not from what it had when the element went.
|
|
28
|
+
//
|
|
29
|
+
// The answer is taken in without reloading: text blocks start from what was committed (a block
|
|
30
|
+
// merged with someone else's change shows the merge), blocks and containers made on the page join
|
|
31
|
+
// the map as ordinary ones, ids and hashes are kept, and the next layout is the one the commit was
|
|
32
|
+
// built from (snapshotLayout), so what the reader did while it was on its way goes with the next.
|
|
33
|
+
// A block or operation the file refused (a conflict) is held back from later commits until the
|
|
34
|
+
// reader decides (resolve). A block made or moved on the page whose place the file no longer has
|
|
35
|
+
// goes to the end of its box instead, once; only then is it a conflict. The texts committed are
|
|
36
|
+
// kept (the journal), and so is what components wrote (`writes`), so that the page can ask whether
|
|
37
|
+
// someone else's write put them back as they were (revertedTexts, revertedParts) before it shows
|
|
38
|
+
// that write.
|
|
39
|
+
//
|
|
40
|
+
// Undo reaches across commits: what it puts back is a change like any other. A unit deleted whole
|
|
41
|
+
// comes back from the source the file gave for it (a restore); a container deleted while its blocks
|
|
42
|
+
// went elsewhere (a merged section, a list turned into paragraphs) comes back around them, as made
|
|
43
|
+
// on the page, with the start tag that source has: the same unit, its id and all it carried in the
|
|
44
|
+
// file (around); the element a conversion replaced is the same unit, of the type it was, and converts back.
|
|
45
|
+
|
|
46
|
+
// How many writes of a part of an instance the revert check asks of, the newest (noteWrite), and how
|
|
47
|
+
// many texts a block's commits left it as between its first and its last (committedText).
|
|
48
|
+
const WRITES_KEPT = 100;
|
|
49
|
+
export const TEXTS_KEPT = 20;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* root: the document; map: the page's map (instrument), extended here as blocks are committed;
|
|
53
|
+
* baselines: text block id -> the html the page compares with; units, boxes: the editor's maps of
|
|
54
|
+
* the map's units and boxes; getLayout, setLayout: the layout collectEdits works against;
|
|
55
|
+
* restore: what a page that is gone knew (save), to take up where it was (autosave.mjs recover).
|
|
56
|
+
*/
|
|
57
|
+
export function createCommits({ root, map, baselines, units, boxes, getLayout, setLayout, restore = null }) {
|
|
58
|
+
const openingUnits = restore?.openingUnits ?? map.units.length;
|
|
59
|
+
const openingBlocks = restore?.openingBlocks ?? map.blocks.length;
|
|
60
|
+
// Each text block's source as the file has it since the last commit: its next edit's `before`.
|
|
61
|
+
const sources = restore?.sources ?? new Map(map.blocks.map((block) => [block.id, block.inner]));
|
|
62
|
+
// What the file did not take, until the reader decides: by issueKey (a text of a block, an
|
|
63
|
+
// operation of a unit, a block made or put back on the page by the id it proposes) -> the
|
|
64
|
+
// conflict, with the reader's words (`mine`) where there are any.
|
|
65
|
+
const issues = restore?.issues ?? new Map();
|
|
66
|
+
const refused = restore?.refused ?? new Map(); // identity of a block made (or put back) on the page the file did not take -> its key in issues
|
|
67
|
+
// unit id -> { source, seal, type, types, blocks: [[id, source, baseline]], element }: deleted
|
|
68
|
+
// whole by a commit, with the text blocks that went with it and the types it and the units that
|
|
69
|
+
// went with it showed as; undo puts it back.
|
|
70
|
+
const removed = restore?.removed ?? new Map();
|
|
71
|
+
const removedWith = restore?.removedWith ?? new Map(); // unit id -> the unit deleted whole it went with (in removed)
|
|
72
|
+
// Held keys the reader gave up, never sent again (the page shows the file's once nothing else waits): by
|
|
73
|
+
// issueKey -> what was given up, of a unit's change { kind: of that change (kindOf); change: that change as
|
|
74
|
+
// the card held it, the one never sent again, the unit's other changes going as any, another of that kind
|
|
75
|
+
// too; reason: the conflict's, a unit the file has no one of (`lacks`) giving up all of it; words: whether
|
|
76
|
+
// the texts in it went with it (holdsWords) }. A page of an earlier Gallery kept a set of keys, each giving
|
|
77
|
+
// up all there was, or no `change`, giving up every change of that kind: read so.
|
|
78
|
+
const discarded = restore?.discarded instanceof Map ? restore.discarded : new Map([...(restore?.discarded ?? [])].map((key) => [key, { words: true }]));
|
|
79
|
+
const unwritten = restore?.unwritten ?? new Set(); // units whose component's writes are never sent again: by a manifest that is no more (the page is shown again)
|
|
80
|
+
// Unit id -> the reader's word to make a change of it whatever the file did to it (仍然删除, 仍然转换类型,
|
|
81
|
+
// 仍然调整), until that change goes in: { kind } of the change it was given for (`kindOf`), with the hash of
|
|
82
|
+
// the unit the refusal they were shown gave (`hash`), or for what its component writes the parts still to
|
|
83
|
+
// go (`parts`). A set of units, as a page of an earlier Gallery kept it, says no kind or hash: no word,
|
|
84
|
+
// each change asking again.
|
|
85
|
+
const forced = restore?.forced instanceof Map ? restore.forced : new Map();
|
|
86
|
+
const elsewhere = restore?.elsewhere ?? new Set(); // identities of blocks made or put back on the page, sent to the end of their box
|
|
87
|
+
const movedElsewhere = restore?.movedElsewhere ?? new Set(); // unit ids likewise, for a move
|
|
88
|
+
const removedElsewhere = restore?.removedElsewhere ?? new Set(); // unit ids likewise, for a deletion that would have taken along blocks with no place yet
|
|
89
|
+
const journal = restore?.journal ?? new Map(); // "b<block>" -> { key, block, original, mine, earlier? }: texts committed (committedText)
|
|
90
|
+
// What components wrote that went in, by unit: { parts, time }, each part written by name { before,
|
|
91
|
+
// after }: every hash it had in the file as the page wrote it but the one it has now, and that one
|
|
92
|
+
// (noteWrite); `time`, when it was last written.
|
|
93
|
+
const writes = restore?.writes ?? new Map();
|
|
94
|
+
// Units this load made or moved, as the commits left them: unit id -> { parent, from, around } (the
|
|
95
|
+
// file's id of the unit it is in, null for none; for one it moved, of the one it was in before; for
|
|
96
|
+
// one put back around its blocks, the source it came back with: dropped's `around`).
|
|
97
|
+
const placed = restore?.placed ?? new Map();
|
|
98
|
+
const keyedBlocks = restore?.keyedBlocks ?? new Map(); // id -> the element of a keyed block (a detail card) made on the page and saved (adoptKeyed)
|
|
99
|
+
// Units the page opened on standing where their box takes none of their kind from the reader (a
|
|
100
|
+
// section in a section): unit id -> the kind of that box. The reader can put none there, so undo alone
|
|
101
|
+
// brings one back, and every commit says so of it (`stood`), or the file would refuse what it held.
|
|
102
|
+
const stood = restore?.stood ?? new Map();
|
|
103
|
+
let serial = restore?.serial ?? 0;
|
|
104
|
+
// Units of the file without an id yet that the page asked to name (idFor), until a commit has.
|
|
105
|
+
const naming = new Set();
|
|
106
|
+
|
|
107
|
+
// ---------- the table ----------
|
|
108
|
+
// identity -> { unit, block, box }: what of the file the element of that identity is (a paragraph
|
|
109
|
+
// is a unit and its own text block, a list a unit and its box). The map's entries know their
|
|
110
|
+
// element's identity back (`cid`).
|
|
111
|
+
const table = restore?.table ?? new Map();
|
|
112
|
+
// identity -> { unit, block, box, around }: what of the file it was, which the file dropped while the
|
|
113
|
+
// page still had it: made on the page from now on (sync-state.mjs asMade). `around`: a container
|
|
114
|
+
// deleted while its blocks went elsewhere, the source the file gave ({ source, seal, type }).
|
|
115
|
+
const dropped = restore?.dropped ?? new Map();
|
|
116
|
+
const keyedIds = restore?.keyedIds ?? new Set(); // identities of keyed blocks (detail cards) made on the page that the file has
|
|
117
|
+
const proposals = restore?.proposals ?? new Map(); // identity of a block made (or put back) on the page -> the id it proposes
|
|
118
|
+
const LISTS = { unit: map.units, block: map.blocks, box: map.boxes };
|
|
119
|
+
|
|
120
|
+
const entryOf = (node) => {
|
|
121
|
+
const key = node?.getAttribute?.(ELEMENT_ID);
|
|
122
|
+
return key ? table.get(key) : undefined;
|
|
123
|
+
};
|
|
124
|
+
const idOf = (node) => entryOf(node)?.unit ?? null;
|
|
125
|
+
const blockOf = (node) => entryOf(node)?.block ?? null;
|
|
126
|
+
const boxOf = (node) => entryOf(node)?.box ?? null;
|
|
127
|
+
// The element of `field` `id` (a unit, block or box of the map) is `element` from now on.
|
|
128
|
+
const bind = (element, field, id) => {
|
|
129
|
+
const key = identityOf(element);
|
|
130
|
+
const entry = LISTS[field][id];
|
|
131
|
+
const before = entry.cid !== undefined && entry.cid !== key ? table.get(entry.cid) : undefined;
|
|
132
|
+
if (before?.[field] === id) delete before[field];
|
|
133
|
+
table.set(key, { ...table.get(key), [field]: id });
|
|
134
|
+
entry.cid = key;
|
|
135
|
+
dropped.delete(key);
|
|
136
|
+
};
|
|
137
|
+
// No element is `field` `id` any more; `drop`: the one that was is made on the page from now on.
|
|
138
|
+
const unbind = (field, id, drop = false) => {
|
|
139
|
+
const key = LISTS[field][id]?.cid;
|
|
140
|
+
const entry = key === undefined ? undefined : table.get(key);
|
|
141
|
+
if (entry?.[field] !== id) return;
|
|
142
|
+
delete entry[field];
|
|
143
|
+
if (drop) {
|
|
144
|
+
dropped.set(key, { ...dropped.get(key), [field]: id });
|
|
145
|
+
proposals.delete(key);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
const onPage = (key) => key === undefined ? null : root.querySelector(`[${ELEMENT_ID}="${key}"]`);
|
|
149
|
+
const unitElement = (id) => entryOf(onPage(map.units[id]?.cid))?.unit === id ? onPage(map.units[id].cid) : null;
|
|
150
|
+
const hostOf = (id) => {
|
|
151
|
+
const key = map.blocks[id]?.cid;
|
|
152
|
+
return key !== undefined && table.get(key)?.block === id ? onPage(key) : null;
|
|
153
|
+
};
|
|
154
|
+
// The unit an element is or is in.
|
|
155
|
+
const unitAround = (node) => {
|
|
156
|
+
for (let at = node; at?.nodeType === 1; at = at.parentElement) {
|
|
157
|
+
const id = idOf(at);
|
|
158
|
+
if (id !== null) return id;
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
};
|
|
162
|
+
// The id the file has for a unit of the page, or the one a block made on the page proposes.
|
|
163
|
+
const fileIdOf = (node) => {
|
|
164
|
+
const unit = idOf(node);
|
|
165
|
+
return unit !== null ? map.units[unit].blockId : proposals.get(node?.getAttribute?.(ELEMENT_ID));
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Elements not seen yet get their identity; one that carries the marks of a block of the file
|
|
169
|
+
// (the page as it was served; a code block opened for editing, components/code/) is that block. One
|
|
170
|
+
// given an identity before the commits saw it is not seen yet either: a table's cells, which the
|
|
171
|
+
// layout's grid names by identity before the page's first marks (components/table/grid.mjs).
|
|
172
|
+
function learn() {
|
|
173
|
+
for (const node of knownElements(root)) {
|
|
174
|
+
if (table.has(node.getAttribute(ELEMENT_ID))) continue;
|
|
175
|
+
identityOf(node);
|
|
176
|
+
for (const [field, name] of [["unit", "data-ge-unit"], ["block", "data-ge-block"], ["box", "data-ge-box"]]) {
|
|
177
|
+
const value = node.getAttribute(name) ?? "";
|
|
178
|
+
if (value !== "" && LISTS[field][Number(value)]) bind(node, field, Number(value));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Identities more than one element on the page carries: none ever should (a copy has none of
|
|
184
|
+
// its own, core.mjs); tests look.
|
|
185
|
+
let doubled = [];
|
|
186
|
+
const view = {
|
|
187
|
+
entry: (key) => table.get(key),
|
|
188
|
+
dropped: (key) => dropped.get(key),
|
|
189
|
+
keyed: (key) => keyedIds.has(key),
|
|
190
|
+
proposal: (key) => proposals.get(key),
|
|
191
|
+
units: map.units,
|
|
192
|
+
blocks: map.blocks,
|
|
193
|
+
boxes: map.boxes,
|
|
194
|
+
shown: (unit) => getLayout()?.types?.get(unit),
|
|
195
|
+
element: (unit) => unitElement(unit),
|
|
196
|
+
};
|
|
197
|
+
/** Write the page's marks from the table (sync-state.mjs): before every look at the page, after every answer. */
|
|
198
|
+
function marks() {
|
|
199
|
+
learn();
|
|
200
|
+
// What is sealed goes by its id from the moment it is on the page (the kit keeps a component's
|
|
201
|
+
// state under it): one made there proposes it now, where any other block does with its commit.
|
|
202
|
+
if (SEALED) for (const node of root.querySelectorAll(`:is(${SEALED})[data-ge-new]`)) proposedId(node);
|
|
203
|
+
const nodes = [...root.querySelectorAll(`[${ELEMENT_ID}]`)];
|
|
204
|
+
const index = new Map();
|
|
205
|
+
doubled = [];
|
|
206
|
+
for (const node of nodes) {
|
|
207
|
+
const key = node.getAttribute(ELEMENT_ID);
|
|
208
|
+
if (index.has(key)) doubled.push(key);
|
|
209
|
+
else index.set(key, node);
|
|
210
|
+
}
|
|
211
|
+
const shown = (id) => {
|
|
212
|
+
const key = map.units[id]?.cid;
|
|
213
|
+
return key !== undefined && table.get(key)?.unit === id ? index.get(key) ?? null : null;
|
|
214
|
+
};
|
|
215
|
+
// A unit a commit deleted that the page shows again as another type than it had (an item put
|
|
216
|
+
// back by undo, then its list turned into paragraphs): its source would put back what it was,
|
|
217
|
+
// where that cannot stand, so it is a block made on the page instead, as is what went with it.
|
|
218
|
+
for (const [id, entry] of removed) {
|
|
219
|
+
const element = shown(id);
|
|
220
|
+
if (!element || entry.type === undefined || ownTypeOf(element) === entry.type) continue;
|
|
221
|
+
touched(); // its text blocks leave the baselines
|
|
222
|
+
removed.delete(id);
|
|
223
|
+
for (const [other, owner] of removedWith) {
|
|
224
|
+
if (owner !== id) continue;
|
|
225
|
+
removedWith.delete(other);
|
|
226
|
+
drop(other);
|
|
227
|
+
}
|
|
228
|
+
drop(id);
|
|
229
|
+
}
|
|
230
|
+
project(nodes, { ...view, element: shown });
|
|
231
|
+
// Words no commit will send any more, given up for the file's (保留 AI 的, 使用最新版本; the page shows
|
|
232
|
+
// the file's once nothing else waits), and what the reader would write beside them: read-only until
|
|
233
|
+
// then, whatever element stands for them (the block converted, or put back by undo), as the server's
|
|
234
|
+
// locks are (a hover says why).
|
|
235
|
+
if (discarded.size > 0) {
|
|
236
|
+
for (const host of root.querySelectorAll("[contenteditable='true']")) if (discarded.has(issueKey("text", blockOf(host))) || givenWords(unitAround(host))) lockHost(host, "given-up");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ---------- naming ----------
|
|
241
|
+
const openingHashes = restore?.openingHashes ?? map.units.map((unit) => unit.hash);
|
|
242
|
+
const unitRef = (id) => {
|
|
243
|
+
const unit = map.units[id];
|
|
244
|
+
// With its id, what it was when the page opened too: found by that if a file written from an
|
|
245
|
+
// earlier read has lost the id.
|
|
246
|
+
if (unit.blockId) return { key: id, id: unit.blockId, hash: unit.hash, ...(id < openingUnits ? { open: id, openHash: openingHashes[id] } : {}) };
|
|
247
|
+
unit.proposed ??= randomBlockId(); // the id it is to get: the same in every commit until it has one
|
|
248
|
+
return { key: id, open: id < openingUnits ? id : -1, hash: unit.hash, blockId: unit.proposed };
|
|
249
|
+
};
|
|
250
|
+
const blockRef = (id) => {
|
|
251
|
+
const block = map.blocks[id];
|
|
252
|
+
const before = sources.get(id) ?? "";
|
|
253
|
+
if (block.kind === "title") return { title: true, before };
|
|
254
|
+
if (block.unit === null || block.unit === undefined) return id < openingBlocks ? { open: id, before } : { outside: true, before };
|
|
255
|
+
return block.self ? { unit: unitRef(block.unit), self: true, before } : { unit: unitRef(block.unit), path: block.path, before };
|
|
256
|
+
};
|
|
257
|
+
const boxRef = (id) => {
|
|
258
|
+
const box = map.boxes[id];
|
|
259
|
+
if (box.kind === "main") return { main: true };
|
|
260
|
+
if (box.unit === null || box.unit === undefined) return { kind: box.kind, ...(box.key !== undefined && box.key !== null ? { key: box.key } : {}) }; // in no unit: the report's header, a detail card's content (by its key)
|
|
261
|
+
return { unit: unitRef(box.unit), kind: box.kind, ...(box.key !== undefined && box.key !== null ? { key: box.key } : {}) };
|
|
262
|
+
};
|
|
263
|
+
const placeRef = (place) => place.into !== undefined ? { into: boxRef(place.into) }
|
|
264
|
+
: place.after !== undefined ? { after: unitRef(pastWaiting(place.after)) } : { before: unitRef(place.before) };
|
|
265
|
+
// The box of the layout a unit stands in, or undefined.
|
|
266
|
+
const layoutBox = (id) => [...(getLayout()?.orders ?? [])].find(([, list]) => list.includes(id))?.[0];
|
|
267
|
+
// The units the layout has right after a unit in its box that wait off the page where the file has them
|
|
268
|
+
// still (a deletion held for the reader, or given up, of a unit the file has one of: not `lacks`). What is
|
|
269
|
+
// placed after the unit goes after them, as the page shows none of them, and what stood after them stays
|
|
270
|
+
// after them (a section dissolved after a paragraph whose deletion waits: its blocks go after that
|
|
271
|
+
// paragraph, not before it: pastWaiting); where the unit leaves a container the commit deletes, they go
|
|
272
|
+
// with it (a section dissolved around such a paragraph: build).
|
|
273
|
+
const waitingAfter = (id) => {
|
|
274
|
+
const order = getLayout()?.orders.get(layoutBox(id)) ?? [];
|
|
275
|
+
const units = [];
|
|
276
|
+
for (const other of order.slice(order.indexOf(id) + 1)) {
|
|
277
|
+
const key = issueKey("unit", other);
|
|
278
|
+
const waits = issues.get(key) ?? discarded.get(key);
|
|
279
|
+
if (unitElement(other) || !waits || lacks(waits)) break;
|
|
280
|
+
units.push(other);
|
|
281
|
+
}
|
|
282
|
+
return units;
|
|
283
|
+
};
|
|
284
|
+
const pastWaiting = (id) => waitingAfter(id).at(-1) ?? id;
|
|
285
|
+
// The end of the box an element is in, when the box is one the file has.
|
|
286
|
+
const endOf = (element) => {
|
|
287
|
+
const value = element?.parentElement?.closest("[data-ge-box]")?.getAttribute("data-ge-box") ?? "";
|
|
288
|
+
return value === "" ? null : { into: Number(value) };
|
|
289
|
+
};
|
|
290
|
+
// The id a block made on the page is to have in the file, proposed by the page and kept for its
|
|
291
|
+
// identity, so every commit that carries the block proposes the same one: sent twice (its answer
|
|
292
|
+
// lost, or again from the crash buffer), a commit makes it once (commitEdits). A unit put back
|
|
293
|
+
// proposes the id it had, and so does one the file dropped, written anew (a column of a row put back).
|
|
294
|
+
const proposedId = (element) => {
|
|
295
|
+
if (!element) return undefined;
|
|
296
|
+
const unit = idOf(element);
|
|
297
|
+
if (unit !== null && map.units[unit].blockId) return map.units[unit].blockId;
|
|
298
|
+
const key = identityOf(element);
|
|
299
|
+
if (!proposals.has(key)) proposals.set(key, map.units[dropped.get(key)?.unit]?.blockId ?? randomBlockId()); // never one an element carries: a copy's would be its original's
|
|
300
|
+
return proposals.get(key);
|
|
301
|
+
};
|
|
302
|
+
const identity = (element) => element?.getAttribute?.(ELEMENT_ID) ?? null;
|
|
303
|
+
// The ids the page knows its blocks by: its units' (the file's, as far as the page knows) and those
|
|
304
|
+
// it proposed for the blocks it made. Every commit carries them, so that a unit the page names by
|
|
305
|
+
// what it said when it opened is never one of these (commitEdits, `pageIds`): a block's hash
|
|
306
|
+
// leaves its id out, and an empty paragraph made on the page says what an empty one of the file says.
|
|
307
|
+
const pageIds = () => [...new Set([...map.units.map((unit) => unit.blockId), ...proposals.values()].filter(Boolean))];
|
|
308
|
+
// The units a unit is in, as the map has them (the file's, as far as the page knows): the unit
|
|
309
|
+
// itself first, then the one around it, outward.
|
|
310
|
+
function* chain(id) {
|
|
311
|
+
for (let at = id; at !== null && at !== undefined && map.units[at]; at = map.units[at].parent) yield at;
|
|
312
|
+
}
|
|
313
|
+
const isInside = (unit, id) => { // unit is somewhere in unit `id`
|
|
314
|
+
for (const at of chain(unit.parent)) if (at === id) return true;
|
|
315
|
+
return false;
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// What a commit leaves out: a block or operation held back (unless `all`), a block that must keep
|
|
319
|
+
// some text and has none (a new section's title), a block made on the page the file refused.
|
|
320
|
+
// A unit (or one around it) whose change is held back or given up, where that holds its words
|
|
321
|
+
// (holdsWords, the `words` of what was given up; a deletion those of the blocks it took off the page,
|
|
322
|
+
// `off`): so are the texts in it. Any other holds none of them: they go as words do, the change held or
|
|
323
|
+
// given up alone.
|
|
324
|
+
const givenWords = (id, off = false) => [...chain(id)].some((at) => {
|
|
325
|
+
const given = discarded.get(issueKey("unit", at));
|
|
326
|
+
return given?.words || (off && given?.kind === "remove");
|
|
327
|
+
});
|
|
328
|
+
const unitHeld = (id, all, off = false) => givenWords(id, off) || (!all && [...chain(id)].map((at) => issues.get(issueKey("unit", at))).some((entry) => entry && holdsWords(entry, off)));
|
|
329
|
+
// Whether a unit's change is one the reader gave up: the one its card was about, as it was (of what a
|
|
330
|
+
// component writes, each part as it was: givenParts), or any where the file has no one unit for it. Another
|
|
331
|
+
// change of that kind made since (the block moved elsewhere, converted to another type, its table's rows
|
|
332
|
+
// changed too) goes as any does, and asks where the file has the unit otherwise.
|
|
333
|
+
const givenUp = (change) => {
|
|
334
|
+
const given = discarded.get(issueKey("unit", change.id));
|
|
335
|
+
if (!given?.kind || lacks(given)) return Boolean(given);
|
|
336
|
+
if (given.kind !== kindOf(change)) return false;
|
|
337
|
+
if (change.source) return Object.keys(change.source).every((name) => givenParts(change).has(name));
|
|
338
|
+
return !given.change || JSON.stringify({ ...given.change, id: undefined }) === JSON.stringify({ ...change, id: undefined });
|
|
339
|
+
};
|
|
340
|
+
// Of what a unit's component writes (`change.source`), the parts given up as they are now: none other goes.
|
|
341
|
+
const givenParts = (change) => {
|
|
342
|
+
const given = discarded.get(issueKey("unit", change.id))?.change?.source;
|
|
343
|
+
return new Set(Object.keys(change.source).filter((name) => given?.[name] === change.source[name]));
|
|
344
|
+
};
|
|
345
|
+
// A text block in the content of a keyed block (a detail card) made on the page and saved,
|
|
346
|
+
// while the block is off the page (its making undone): the file keeps it, and it is no unit to
|
|
347
|
+
// delete, so nothing in it is taken out either (adoptKeyed).
|
|
348
|
+
const keyedAway = (edit) => {
|
|
349
|
+
const key = map.blocks[edit.id]?.cid;
|
|
350
|
+
return key !== undefined && [...keyedBlocks.values()].some((block) => !block.isConnected && block.querySelector(`[${ELEMENT_ID}="${key}"]`));
|
|
351
|
+
};
|
|
352
|
+
// A text block off the page of a kind that goes with its block alone (a part a component's drawing
|
|
353
|
+
// took out, the block still there): the file keeps it, as nothing can take it out.
|
|
354
|
+
const stays = (edit) => edit.parts.length === 0 && !capsFor(map.blocks[edit.id].kind).split(" ").includes("remove");
|
|
355
|
+
const skipText = (edit, blanks, all) => {
|
|
356
|
+
const host = hostOf(edit.id);
|
|
357
|
+
return (issues.has(issueKey("text", edit.id)) && !all) || discarded.has(issueKey("text", edit.id)) || (host && blanks.has(host)) || !map.blocks[edit.id] || unitHeld(map.blocks[edit.id].unit, all, !host) || keyedAway(edit) || stays(edit);
|
|
358
|
+
};
|
|
359
|
+
// Of what a unit's component wrote (`change.source`), the parts a write held back for the reader holds,
|
|
360
|
+
// with those one edit wrote together with any of them: the rest goes, as the write of another part
|
|
361
|
+
// does where someone else changed one (source/commit.mjs). Null where no such write is held.
|
|
362
|
+
const heldParts = (change) => {
|
|
363
|
+
const entry = issues.get(issueKey("unit", change.id));
|
|
364
|
+
if (!change.source || !entry?.change?.source) return null;
|
|
365
|
+
const held = new Set(Object.keys(entry.change.source));
|
|
366
|
+
const element = unitElement(change.id);
|
|
367
|
+
const groups = element ? pageOfUnit(element)?.together?.(element, [...new Set([...held, ...Object.keys(change.source)])]) ?? [] : [];
|
|
368
|
+
for (const group of groups) if (group.some((name) => held.has(name))) group.forEach((name) => held.add(name));
|
|
369
|
+
return held;
|
|
370
|
+
};
|
|
371
|
+
const skipUnit = (change, blanks, all) => {
|
|
372
|
+
if (change.insert) {
|
|
373
|
+
const element = madeFrom.get(change);
|
|
374
|
+
return !element || (refused.has(identity(element)) && !all) || [...blanks].some((node) => element.contains(node));
|
|
375
|
+
}
|
|
376
|
+
const held = all ? null : heldParts(change);
|
|
377
|
+
const apart = Boolean(held) && Object.keys(change.source).some((name) => !held.has(name));
|
|
378
|
+
if ((issues.has(issueKey("unit", change.id)) && !all && !apart) || givenUp(change) || (change.source && unwritten.has(change.id))) return true;
|
|
379
|
+
// A unit in a block not finished (a new row of columns one of which is still empty): it stays as
|
|
380
|
+
// the file has it, its move and its conversion going with that block once it is written.
|
|
381
|
+
if (blanks.size > 0) {
|
|
382
|
+
const element = unitElement(change.id);
|
|
383
|
+
if (element && unfinished(blanks).some((top) => top.contains(element))) return true;
|
|
384
|
+
}
|
|
385
|
+
if (change.move && removed.has(change.id)) {
|
|
386
|
+
const element = unitElement(change.id);
|
|
387
|
+
return !element || (refused.has(identity(element)) && !all);
|
|
388
|
+
}
|
|
389
|
+
return false;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// ---------- what changed since ----------
|
|
393
|
+
// collect() reads the page and what the commits know of it (the baselines, the layout, the table
|
|
394
|
+
// the marks are written from), and nothing else. Asked again with neither changed, it gives the
|
|
395
|
+
// answer it gave: a page that is left asked twelve to seventeen times (the last keystrokes
|
|
396
|
+
// counted, the flush and the crash buffer in prepare-close, then all of it again in pagehide and
|
|
397
|
+
// visibilitychange as its frame went: 0.3-1 s on a large report before the next one was even
|
|
398
|
+
// asked for), and so does a commit after the pause that was counted, each a pass over every block
|
|
399
|
+
// of the report. The page changing is any mutation of the document (a MutationObserver, its
|
|
400
|
+
// records taken at once when asked: takeRecords) but the attributes of <html> itself (data-ge-save
|
|
401
|
+
// and the like, which no block is in); what the commits know changes
|
|
402
|
+
// with an answer taken in (apply), a decision on a conflict (resolve), a block put back (putBack)
|
|
403
|
+
// and a unit to name (idFor), which count as a change too (touched). The marks collect writes
|
|
404
|
+
// first are a change like any other: they are counted after they are written. A document of its
|
|
405
|
+
// own with no window (recover's) has no observer: there every collect is anew. Watched from once
|
|
406
|
+
// the page has its first marks (the end of createCommits).
|
|
407
|
+
const Observer = root.defaultView?.MutationObserver ?? null;
|
|
408
|
+
let changes = 0;
|
|
409
|
+
const matters = (records) => records.some((record) => record.type !== "attributes" || record.target !== root.documentElement);
|
|
410
|
+
const observer = Observer ? new Observer((records) => { if (matters(records)) changes += 1; }) : null;
|
|
411
|
+
const touched = () => { changes += 1; };
|
|
412
|
+
/**
|
|
413
|
+
* Where the page and what the commits know of it stand: the same number as long as neither
|
|
414
|
+
* changed (null when not watched: never the same).
|
|
415
|
+
*/
|
|
416
|
+
function generation() {
|
|
417
|
+
if (!observer) return null;
|
|
418
|
+
if (matters(observer.takeRecords())) changes += 1;
|
|
419
|
+
return changes;
|
|
420
|
+
}
|
|
421
|
+
let last = null; // collect()'s last answer: { collected, generation, layout }
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* What changed on the page since the last commit (collectEdits), its marks written first; the
|
|
425
|
+
* answer it gave last when nothing changed since (generation).
|
|
426
|
+
*/
|
|
427
|
+
function collect() {
|
|
428
|
+
const now = generation();
|
|
429
|
+
if (now !== null && last?.generation === now && last.layout === getLayout()) return last.collected;
|
|
430
|
+
marks();
|
|
431
|
+
const collected = collectEdits(root, baselines, getLayout());
|
|
432
|
+
last = { collected, generation: generation(), layout: getLayout() };
|
|
433
|
+
return collected;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Whether `collected` (collect()'s) is what collect() would answer now: nothing changed since. */
|
|
437
|
+
const current = (collected) => collected !== null && collected !== undefined && last?.collected === collected && last.generation !== null && last.generation === generation() && last.layout === getLayout();
|
|
438
|
+
|
|
439
|
+
/** How many changes the next commit would carry (of `collected`: collect()'s result). */
|
|
440
|
+
function waiting(collected = collect()) {
|
|
441
|
+
const blanks = new Set(collected.blank);
|
|
442
|
+
return collected.edits.filter((edit) => !skipText(edit, blanks, false)).length + collected.units.filter((change) => !skipUnit(change, blanks, false)).length
|
|
443
|
+
+ [...naming].filter((id) => map.units[id] && !map.units[id].blockId && !removed.has(id) && unitElement(id)).length;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// The outermost blocks made on the page around blocks no commit can carry yet (`nodes`).
|
|
447
|
+
const unfinished = (nodes) => {
|
|
448
|
+
const roots = new Set();
|
|
449
|
+
for (const node of nodes) {
|
|
450
|
+
let top = null;
|
|
451
|
+
for (let at = node; at && at !== root; at = at.parentElement) if (at.hasAttribute?.("data-ge-new")) top = at;
|
|
452
|
+
if (top) roots.add(top);
|
|
453
|
+
}
|
|
454
|
+
return [...roots].filter((top) => ![...roots].some((other) => other !== top && other.contains(top)));
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
// Blocks no commit can carry yet (a new disclosure's title still empty, a new block with no place,
|
|
458
|
+
// a row of columns one of which holds nothing): the text of the outermost block made on the page
|
|
459
|
+
// around each, where it has any. A block of the file that must keep some text is not one of them:
|
|
460
|
+
// the file has its text.
|
|
461
|
+
function drafts({ blank, unplaced }) {
|
|
462
|
+
return unfinished([...blank, ...unplaced]).map(blocksText).filter(Boolean);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* How many changes the next commit would carry, and how many blocks none can carry yet (drafts):
|
|
467
|
+
* counted with or without text, as the page shows them and the file does not have them (an empty
|
|
468
|
+
* paragraph is written once it has a place: a row holding one beside an empty code block is not).
|
|
469
|
+
*/
|
|
470
|
+
function counts(collected = collect()) {
|
|
471
|
+
return { waiting: waiting(collected), drafts: unfinished([...collected.blank, ...collected.unplaced]).length };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** The texts of the page's blocks, as the reader sees them (the crash buffer's safety net: checkLeft). */
|
|
475
|
+
const texts = () => [...root.querySelectorAll("[data-ge-block], [data-ge-new], [data-ge-part]")].filter((node) => node.getAttribute("contenteditable") === "true").map(ownText).filter(Boolean);
|
|
476
|
+
|
|
477
|
+
// Words typed in a text block the file writes from elsewhere, before it has: no commit can carry them.
|
|
478
|
+
const typedParts = () => [...root.querySelectorAll("[data-ge-part]")].filter((host) => host.getAttribute("data-ge-part") !== ownHTML(host)).map(ownText).filter(Boolean);
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* What the crash buffer keeps: the commit of everything not in the file (what is held back too),
|
|
482
|
+
* the texts of the blocks no commit can carry yet, and those typed in a block the commit makes
|
|
483
|
+
* (they go with the next commit, once it has made it). { request, drafts: [text], typed: [text] }
|
|
484
|
+
* `collected`: collect()'s result, when the caller has it from the same task.
|
|
485
|
+
*/
|
|
486
|
+
function buffer(collected = collect()) {
|
|
487
|
+
return { request: build({ held: true, whole: true, collected })?.request ?? null, drafts: drafts(collected), typed: typedParts() };
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* The commit of everything changed since the last one: { request, pending }, or null when
|
|
492
|
+
* nothing is left to commit. What is held back is left out, unless `held` (the crash buffer), and
|
|
493
|
+
* of what components wrote what one commit carries goes, unless `whole` (the crash buffer again,
|
|
494
|
+
* which keeps all a page leaves).
|
|
495
|
+
*/
|
|
496
|
+
function build({ held: all = false, whole = false, collected = null } = {}) {
|
|
497
|
+
const { edits, units: changes, blank } = collected ?? collect();
|
|
498
|
+
const snapshot = snapshotLayout(root);
|
|
499
|
+
const blanks = new Set(blank);
|
|
500
|
+
const texts = [];
|
|
501
|
+
const ops = [];
|
|
502
|
+
const touch = new Map();
|
|
503
|
+
// `moves`: the units the page shows moved, whether or not the commit carries their move (apply: a unit that
|
|
504
|
+
// waits stands where the file has it).
|
|
505
|
+
const pending = { texts: new Map(), units: new Map(), made: new Map(), restores: new Map(), html: new Map(), snapshot, moves: new Set(changes.filter((change) => change.move).map((change) => change.id)) };
|
|
506
|
+
// Every new text block's html as sent: what it starts from once committed.
|
|
507
|
+
for (const host of root.querySelectorAll("[data-ge-new][contenteditable='true'], [data-ge-convert] [contenteditable='true'], [contenteditable='true'][data-ge-convert]")) pending.html.set(host, serializeInner(host));
|
|
508
|
+
// A unit takes along what its component says (a Sheet tab, its panel: boxOwner), so a change in
|
|
509
|
+
// such a box is a change of that unit too (its hash covers the box).
|
|
510
|
+
const touchOwner = (box) => {
|
|
511
|
+
const owner = box ? boxOwner(box) : null;
|
|
512
|
+
const id = owner ? idOf(owner) : null;
|
|
513
|
+
if (id !== null && !removed.has(id)) touchUnits(id);
|
|
514
|
+
};
|
|
515
|
+
// The boxes a unit stood in when the last commit left it: what leaves them changes them. Each box
|
|
516
|
+
// as a search for it finds it, from one index of the page's boxes (nothing here moves one).
|
|
517
|
+
let boxElements = null;
|
|
518
|
+
const touchLeft = (id) => {
|
|
519
|
+
for (const [box, order] of getLayout()?.orders ?? []) if (order.includes(id)) touchOwner((boxElements ??= firstByAttribute(root, "data-ge-box")).get(String(box)) ?? null);
|
|
520
|
+
};
|
|
521
|
+
function touchUnits(id) {
|
|
522
|
+
for (const at of chain(id)) {
|
|
523
|
+
if (touch.has(at)) continue;
|
|
524
|
+
touch.set(at, unitRef(at));
|
|
525
|
+
touchLeft(at);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const touchAround = (node) => {
|
|
529
|
+
for (let at = node; at?.nodeType === 1; at = at.parentElement) {
|
|
530
|
+
const id = idOf(at);
|
|
531
|
+
if (id !== null && !removed.has(id) && !touch.has(id)) touch.set(id, unitRef(id));
|
|
532
|
+
if (at.hasAttribute("data-ge-box")) touchOwner(at);
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
// The units inside one that moves go to a new indentation with it: their text changes too.
|
|
536
|
+
const touchWithin = (element) => {
|
|
537
|
+
for (const node of element?.querySelectorAll?.(`[${ELEMENT_ID}]`) ?? []) {
|
|
538
|
+
const id = idOf(node);
|
|
539
|
+
if (id !== null && !removed.has(id) && !touch.has(id)) touch.set(id, unitRef(id));
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
const key = () => `n${serial++}`;
|
|
543
|
+
// The parts of what a component wrote that this commit carries: as many as one commit takes (`most`,
|
|
544
|
+
// its page object's `dataMost`, in bytes) with what it carries already, the first whatever it is (the
|
|
545
|
+
// server says what it makes of it), the parts one edit wrote together (`groups`) all or none. Those
|
|
546
|
+
// left out still differ from what the file holds, and go with the next commit.
|
|
547
|
+
let carried = 0;
|
|
548
|
+
const within = (source, most, groups) => {
|
|
549
|
+
const kept = {};
|
|
550
|
+
const apart = Object.keys(source).filter((name) => !groups.some((group) => group.includes(name))).map((name) => [name]);
|
|
551
|
+
for (const names of [...groups, ...apart]) {
|
|
552
|
+
const bytes = names.reduce((sum, name) => sum + new TextEncoder().encode(source[name]).length, 0);
|
|
553
|
+
if (carried > 0 && carried + bytes > most) continue;
|
|
554
|
+
carried += bytes;
|
|
555
|
+
for (const name of names) kept[name] = source[name];
|
|
556
|
+
}
|
|
557
|
+
return Object.fromEntries(Object.keys(source).filter((name) => Object.hasOwn(kept, name)).map((name) => [name, kept[name]]));
|
|
558
|
+
};
|
|
559
|
+
const stoodOf = (id) => (stood.has(id) ? { stood: stood.get(id) } : {});
|
|
560
|
+
// A deleted unit undo put back: its source, to be written again.
|
|
561
|
+
const restoreNode = (id, element) => {
|
|
562
|
+
const nodeKey = key();
|
|
563
|
+
const { source, seal } = removed.get(id);
|
|
564
|
+
pending.made.set(nodeKey, element);
|
|
565
|
+
pending.restores.set(nodeKey, id);
|
|
566
|
+
return { restore: { source, seal }, key: nodeKey, blockId: proposedId(element), ...stoodOf(id) };
|
|
567
|
+
};
|
|
568
|
+
const nodeRef = (node) => {
|
|
569
|
+
// A copy of a card or a table: the file copies the unit's own source (with the keys the
|
|
570
|
+
// paste gave the charts drawn from tables in it: core.mjs nodeOf).
|
|
571
|
+
if (node.copy !== undefined) {
|
|
572
|
+
const nodeKey = key();
|
|
573
|
+
const element = madeFrom.get(node);
|
|
574
|
+
if (element) pending.made.set(nodeKey, element);
|
|
575
|
+
return { copy: unitRef(node.copy), key: nodeKey, blockId: proposedId(element), ...(node.keys ? { keys: node.keys } : {}) };
|
|
576
|
+
}
|
|
577
|
+
if (node.unit !== undefined) {
|
|
578
|
+
if (removed.has(node.unit)) return restoreNode(node.unit, madeFrom.get(node) ?? unitElement(node.unit));
|
|
579
|
+
touchUnits(map.units[node.unit]?.parent); // taken from its place
|
|
580
|
+
touchLeft(node.unit);
|
|
581
|
+
touchWithin(madeFrom.get(node) ?? unitElement(node.unit));
|
|
582
|
+
return { unit: unitRef(node.unit), ...stoodOf(node.unit) };
|
|
583
|
+
}
|
|
584
|
+
const nodeKey = key();
|
|
585
|
+
const element = madeFrom.get(node);
|
|
586
|
+
if (element) pending.made.set(nodeKey, element);
|
|
587
|
+
const { children, ...rest } = node;
|
|
588
|
+
const blockId = BLOCK_TYPES[node.type]?.keyed ? undefined : proposedId(element); // a keyed block (a detail card) is known by its own id
|
|
589
|
+
// One of the file put back around its blocks, still of the type the source says.
|
|
590
|
+
const was = dropped.get(identity(element));
|
|
591
|
+
const put = was?.around?.type === node.type ? { restore: { source: was.around.source, seal: was.around.seal } } : {};
|
|
592
|
+
return { ...rest, ...put, ...stoodOf(was?.unit), key: nodeKey, ...(blockId ? { blockId } : {}), ...(children ? { children: children.map(nodeRef) } : {}) };
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
for (const edit of edits) {
|
|
596
|
+
const textKey = issueKey("text", edit.id); // the key of a conflict of its text too
|
|
597
|
+
const host = hostOf(edit.id);
|
|
598
|
+
if (skipText(edit, blanks, all)) continue;
|
|
599
|
+
const parts = edit.parts.map((part) => {
|
|
600
|
+
if (part.keep) return { keep: true, html: part.html };
|
|
601
|
+
const partKey = key();
|
|
602
|
+
const element = madeFrom.get(part);
|
|
603
|
+
pending.made.set(partKey, element);
|
|
604
|
+
return { tag: part.tag, html: part.html, ...(part.classes ? { classes: part.classes } : {}), ...(part.attributes ? { attributes: part.attributes } : {}), key: partKey, ...(element ? { blockId: proposedId(element) } : {}) };
|
|
605
|
+
});
|
|
606
|
+
texts.push({ key: textKey, block: blockRef(edit.id), parts });
|
|
607
|
+
pending.texts.set(textKey, { id: edit.id, parts, before: sources.get(edit.id) ?? "" });
|
|
608
|
+
if (host) touchAround(host);
|
|
609
|
+
else touchUnits(map.blocks[edit.id].unit);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Units put back (undo of a deletion an earlier commit made): what changed meanwhile in them and in
|
|
613
|
+
// what they hold or took along (converted, moved within or out, resized) waits for the next commit,
|
|
614
|
+
// once the file has them back (it names nothing of them before).
|
|
615
|
+
const restored = changes.filter((change) => change.move && removed.has(change.id)).map((change) => change.id);
|
|
616
|
+
const restoring = restored.map(unitElement).filter(Boolean);
|
|
617
|
+
// The units a change concerns: its own, or those a block made on the page takes along.
|
|
618
|
+
const concerned = (change) => {
|
|
619
|
+
if (!change.insert) return [change.id];
|
|
620
|
+
const units = [];
|
|
621
|
+
const visit = (node) => {
|
|
622
|
+
if (node.unit !== undefined) units.push(node.unit);
|
|
623
|
+
node.children?.forEach(visit);
|
|
624
|
+
};
|
|
625
|
+
visit(change.insert);
|
|
626
|
+
return units;
|
|
627
|
+
};
|
|
628
|
+
// Units that went with one deleted whole (an earlier commit) and stand on the page again without
|
|
629
|
+
// it (undo put it back, then the reader took them out of it; or it put back a block made on the
|
|
630
|
+
// page holding them): the file has them only inside it, so it comes back first, from its source,
|
|
631
|
+
// where the first of them stands, holding them; taking them out of it (and it away) goes with the
|
|
632
|
+
// next commit, once the file has them back.
|
|
633
|
+
const ownerOf = (change) => (change.move || change.insert ? concerned(change).map((id) => removedWith.get(id)).find((owner) => owner !== undefined) : undefined);
|
|
634
|
+
const orphans = new Map(); // owner -> the first of them (its move, or the block made holding it)
|
|
635
|
+
for (const change of changes) {
|
|
636
|
+
const owner = ownerOf(change);
|
|
637
|
+
if (owner !== undefined && removed.has(owner) && !unitElement(owner) && !orphans.has(owner)) orphans.set(owner, change);
|
|
638
|
+
}
|
|
639
|
+
const withinRestored = (change) => {
|
|
640
|
+
if (change.move && removed.has(change.id)) return false;
|
|
641
|
+
if (concerned(change).some((id) => restored.includes(removedWith.get(id)))) return true;
|
|
642
|
+
if (change.insert) return false;
|
|
643
|
+
const element = unitElement(change.id);
|
|
644
|
+
return Boolean(element) && restoring.some((other) => other.contains(element));
|
|
645
|
+
};
|
|
646
|
+
// Items the file has holding lists (in their own box) that the page turned into another type:
|
|
647
|
+
// what this commit does to their lists (taken out of them, or away) goes first, their
|
|
648
|
+
// conversion and their move (a paragraph again, in a box that takes no items) with the next
|
|
649
|
+
// commit, once the file has them holding none (an indent undone while it was on its way).
|
|
650
|
+
const holding = new Set([...(getLayout()?.orders ?? [])].filter(([box, order]) => boxRole(map.boxes[box]?.kind) === "nest" && order.length > 0).map(([box]) => map.boxes[box].unit));
|
|
651
|
+
const waitsForLists = (change) => holding.has(change.id) && Boolean(change.replace || change.move) && ownTypeOf(unitElement(change.id) ?? root.documentElement) !== getLayout()?.types?.get(change.id);
|
|
652
|
+
const removing = new Set(); // the units this commit deletes (a unit's deletion is among its changes before any move)
|
|
653
|
+
const leavesRemoved = (id) => removing.has(map.boxes[layoutBox(id)]?.unit);
|
|
654
|
+
// A unit held for the reader that leaves a container this commit deletes (a section dissolved around a block
|
|
655
|
+
// whose conversion waits) goes with the blocks around it, its place alone (`along`): what it waits with stands.
|
|
656
|
+
const leaves = (change) => {
|
|
657
|
+
const held = change.move && !all ? issues.get(issueKey("unit", change.id)) : null;
|
|
658
|
+
return held?.kind === "unit" && kindOf(held.change) !== "move" && leavesRemoved(change.id);
|
|
659
|
+
};
|
|
660
|
+
for (const change of changes) {
|
|
661
|
+
const opKey = `u${serial++}`; // this operation's key in the request (the key of its conflict is issueKey("unit", unit))
|
|
662
|
+
const along = leaves(change);
|
|
663
|
+
if (skipUnit(change, blanks, all || along)) continue;
|
|
664
|
+
if (withinRestored(change) || waitsForLists(change)) continue;
|
|
665
|
+
const owner = ownerOf(change);
|
|
666
|
+
if (orphans.has(owner)) {
|
|
667
|
+
if (orphans.get(owner) !== change) continue;
|
|
668
|
+
const element = removed.get(owner).element;
|
|
669
|
+
const node = restoreNode(owner, element);
|
|
670
|
+
ops.push({ key: opKey, insert: node, place: placeRef(change.move ?? change.place) });
|
|
671
|
+
pending.units.set(opKey, { insert: true, element, node });
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (change.insert) {
|
|
675
|
+
const element = madeFrom.get(change);
|
|
676
|
+
const node = nodeRef(change.insert);
|
|
677
|
+
const place = (elsewhere.has(identity(element)) && endOf(element)) || change.place;
|
|
678
|
+
ops.push({ key: opKey, insert: node, place: placeRef(place) });
|
|
679
|
+
pending.units.set(opKey, { insert: true, element, node });
|
|
680
|
+
touchAround(element.parentElement);
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
const element = unitElement(change.id);
|
|
684
|
+
if (change.move && removed.has(change.id)) {
|
|
685
|
+
// Deleted by an earlier commit, back on the page (undo).
|
|
686
|
+
const node = restoreNode(change.id, element);
|
|
687
|
+
ops.push({ key: opKey, insert: node, place: placeRef((elsewhere.has(identity(element)) && endOf(element)) || change.move) });
|
|
688
|
+
pending.units.set(opKey, { insert: true, element, node });
|
|
689
|
+
touchAround(element.parentElement);
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
const unit = unitRef(change.id);
|
|
693
|
+
const hash = map.units[change.id].hash;
|
|
694
|
+
let op;
|
|
695
|
+
if (change.remove) {
|
|
696
|
+
// A container that goes when emptied (a list, a quote) names the units it held: if the file
|
|
697
|
+
// has it changed by someone else, only these go (commitEdits), what they added stays.
|
|
698
|
+
const items = getLayout()?.elements.get(change.id)?.hasAttribute("data-ge-pure") ? map.units.filter((other) => other.parent === change.id && !removed.has(other.id)).map((other) => unitRef(other.id)) : [];
|
|
699
|
+
op = { remove: true, hash, ...(items.length > 0 ? { items } : {}) };
|
|
700
|
+
touchUnits(map.units[change.id].parent);
|
|
701
|
+
touchLeft(change.id);
|
|
702
|
+
} else if (change.move) {
|
|
703
|
+
op = { move: placeRef((movedElsewhere.has(change.id) && endOf(element)) || change.move), ...stoodOf(change.id) };
|
|
704
|
+
touchUnits(map.units[change.id].parent);
|
|
705
|
+
touchLeft(change.id);
|
|
706
|
+
touchAround(element);
|
|
707
|
+
touchWithin(element);
|
|
708
|
+
} else if (change.replace) op = { replace: change.replace, hash };
|
|
709
|
+
else if (change.table) op = { table: change.table, hash }; // its rows and columns (components/table/grid.mjs)
|
|
710
|
+
else if (change.source) { // what its component wrote, but the parts a write held back holds (heldParts) and those given up (givenParts)
|
|
711
|
+
const waits = new Set([...(all ? [] : heldParts(change) ?? []), ...givenParts(change)]);
|
|
712
|
+
const going = Object.fromEntries(Object.entries(change.source).filter(([name]) => !waits.has(name)));
|
|
713
|
+
const written = pageOfUnit(element).together?.(element, Object.keys(going)) ?? [];
|
|
714
|
+
const source = whole ? going : within(going, pageOfUnit(element).dataMost, written);
|
|
715
|
+
if (Object.keys(source).length === 0) continue;
|
|
716
|
+
// What each part held as the file last said: it goes in where someone else changed only the rest,
|
|
717
|
+
// the parts one edit wrote together together.
|
|
718
|
+
const held = map.units[change.id].parts ?? {};
|
|
719
|
+
const parts = Object.fromEntries(Object.keys(source).filter((name) => held[name]).map((name) => [name, held[name]]));
|
|
720
|
+
const together = written.filter((group) => group.every((name) => Object.hasOwn(source, name)));
|
|
721
|
+
op = { source, hash, parts, ...(together.length > 0 ? { together } : {}), definition: change.definition };
|
|
722
|
+
} else if (change.retag) op = { retag: change.retag };
|
|
723
|
+
else if (change.toggle) op = { toggle: change.toggle, on: change.on };
|
|
724
|
+
else if (change.size) op = { size: change.size, value: change.value };
|
|
725
|
+
else if (change.attribute) op = { attribute: change.attribute, value: change.value };
|
|
726
|
+
else continue;
|
|
727
|
+
// The reader said so, of a change of this kind: whatever the file did to it, so long as the file still
|
|
728
|
+
// has the unit as the refusal they were shown gave it (its `hash`). A change of another kind made since
|
|
729
|
+
// (the block converted anyway deleted meanwhile) is held to what the page saw, as any is.
|
|
730
|
+
const word = forced.get(change.id);
|
|
731
|
+
if (word && word.kind === kindOf(change)) Object.assign(op, { force: true }, word.hash !== undefined ? { hash: word.hash } : {});
|
|
732
|
+
if (!change.remove) touchAround(element);
|
|
733
|
+
ops.push({ key: opKey, unit, ...op });
|
|
734
|
+
pending.units.set(opKey, { id: change.id, change: op.source ? { ...change, source: op.source } : change, ...(op.source ? { parts: op.parts } : {}), ...(op.force ? { forced: true } : {}), ...(along ? { along } : {}) });
|
|
735
|
+
// What waits off the page right after a unit that leaves a container this commit deletes (a section
|
|
736
|
+
// dissolved around a paragraph whose deletion waits: waitingAfter) goes with it, where the file has it
|
|
737
|
+
// still, so that the blocks keep their order and none stands in the container alone, its place alone
|
|
738
|
+
// too (`along`). An answer that does not take such a place says nothing: the unit waits as it did.
|
|
739
|
+
if (change.remove) removing.add(change.id);
|
|
740
|
+
for (const id of change.move && !all && leavesRemoved(change.id) ? waitingAfter(change.id) : []) {
|
|
741
|
+
const alongKey = `u${serial++}`;
|
|
742
|
+
ops.push({ key: alongKey, unit: unitRef(id), move: op.move });
|
|
743
|
+
pending.units.set(alongKey, { id, change: { id, move: change.move }, along: true });
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
// The units asked to be named, each with the id it proposes (unitRef), alone when nothing else changed.
|
|
747
|
+
pending.named = [];
|
|
748
|
+
for (const id of naming) {
|
|
749
|
+
if (!map.units[id] || map.units[id].blockId) naming.delete(id);
|
|
750
|
+
else if (!removed.has(id) && unitElement(id)) {
|
|
751
|
+
if (!touch.has(id)) touch.set(id, unitRef(id));
|
|
752
|
+
pending.named.push(id);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
marks(); // the ids proposed
|
|
756
|
+
if (texts.length === 0 && ops.length === 0 && pending.named.length === 0) return null;
|
|
757
|
+
return { request: { opening: map.version, texts, units: ops, touch: [...touch.values()], pageIds: pageIds() }, pending };
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// ---------- taking in the answer ----------
|
|
761
|
+
// A text block of the page is block `id` of the map from now on (a new one, unless it is one
|
|
762
|
+
// already): in the page unit `unit` (null: in none), at the place the file
|
|
763
|
+
// says (`file`: { self } or { path }, never counted on the page). It starts from what the commit
|
|
764
|
+
// sent when it sent it as new (a block made on the page, the text of a unit turned into another
|
|
765
|
+
// type), else from what it shows, when that is what the file says it is (`file`: { inner, atoms };
|
|
766
|
+
// a copy's text, which the page shows as it was pasted). Showing other words (typed while the commit was on its way into a block the file
|
|
767
|
+
// has, though the page took it for gone), it starts from the file's text, as the page shows
|
|
768
|
+
// that, so that the page's own words go with the next commit.
|
|
769
|
+
const registerBlock = (host, pending, file = null, unit = null) => {
|
|
770
|
+
const start = () => pending.html.get(host)
|
|
771
|
+
?? (file && !matchesSource(host, file.inner) ? shownAs(host, file.inner, file.atoms) : null)
|
|
772
|
+
?? serializeInner(host);
|
|
773
|
+
const known = blockOf(host);
|
|
774
|
+
if (known !== null && map.blocks[known]) {
|
|
775
|
+
const block = map.blocks[known];
|
|
776
|
+
if (file?.path && !block.self) block.path = file.path;
|
|
777
|
+
if (block.gone || pending.html.has(host)) {
|
|
778
|
+
const html = start();
|
|
779
|
+
Object.assign(block, { gone: false, tag: host.localName, kind: host.getAttribute("data-ge-kind") ?? block.kind });
|
|
780
|
+
sources.set(known, html);
|
|
781
|
+
baselines.set(known, html);
|
|
782
|
+
}
|
|
783
|
+
return known;
|
|
784
|
+
}
|
|
785
|
+
const id = map.blocks.length;
|
|
786
|
+
// Words of a block the file wrote from what it holds (`source`), as the page made them: those it
|
|
787
|
+
// made them from were old, or hold what only the file's text says how to write back, and it shows
|
|
788
|
+
// the file's. Others were typed, and go with the next commit.
|
|
789
|
+
if (file && host.getAttribute("data-ge-part") === ownHTML(host) && (file.atoms || !matchesSource(host, file.inner))) asLoaded(host, file);
|
|
790
|
+
const html = start();
|
|
791
|
+
map.blocks.push({
|
|
792
|
+
id,
|
|
793
|
+
tag: host.localName,
|
|
794
|
+
kind: host.getAttribute("data-ge-kind") ?? "flow",
|
|
795
|
+
caps: (host.getAttribute("data-ge-caps") ?? "").split(/\s+/).filter(Boolean),
|
|
796
|
+
inner: html,
|
|
797
|
+
atoms: [],
|
|
798
|
+
unit,
|
|
799
|
+
...(file?.self ? { self: true } : file?.path ? { path: file.path } : {}), // where in its unit the file has it
|
|
800
|
+
});
|
|
801
|
+
bind(host, "block", id);
|
|
802
|
+
sources.set(id, html);
|
|
803
|
+
baselines.set(id, html);
|
|
804
|
+
return id;
|
|
805
|
+
};
|
|
806
|
+
// A text block as a load of the page shows the file's (its inline elements of the report's own
|
|
807
|
+
// atoms, written back as they are: core.mjs prepareBlock), the caret where it stood.
|
|
808
|
+
const asLoaded = (host, { inner, atoms = [], kind, caps }) => {
|
|
809
|
+
const selection = host.ownerDocument.getSelection();
|
|
810
|
+
const place = selection?.rangeCount && host.contains(selection.anchorNode) ? rangeOffsets(host, selection.getRangeAt(0)) : null;
|
|
811
|
+
prepareBlock(host, { inner, atoms, kind, caps });
|
|
812
|
+
if (place) select(rangeAt(host, place));
|
|
813
|
+
};
|
|
814
|
+
// The selection in a text block someone else's words are about to be merged into, as it stands among the
|
|
815
|
+
// reader's words: what puts it back there once they are in. Counted from the block's end where the words
|
|
816
|
+
// after it are still what follows it (theirs went in before it), from its start where the words before it
|
|
817
|
+
// are still what precedes it (theirs went in after it), else right after as much of the reader's words
|
|
818
|
+
// before it as the merged text holds, the place nearest where it was (theirs went in on both sides).
|
|
819
|
+
const caretKept = (host) => {
|
|
820
|
+
const selection = host.ownerDocument.getSelection();
|
|
821
|
+
if (!selection?.rangeCount || !host.contains(selection.anchorNode)) return () => {};
|
|
822
|
+
const length = () => rangeOffsets(host, rangeAt(host, [0, Infinity]))[1];
|
|
823
|
+
const words = (from, to) => rangeAt(host, [from, to]).toString();
|
|
824
|
+
const [from, to] = rangeOffsets(host, selection.getRangeAt(0));
|
|
825
|
+
const [total, before, after] = [length(), words(0, from), words(to, Infinity)];
|
|
826
|
+
// How many characters of `text` there are up to the end of the longest end of the reader's words before
|
|
827
|
+
// it that `text` holds, the one nearest where it was.
|
|
828
|
+
const pastTail = (text) => {
|
|
829
|
+
for (let size = before.length; size > 0; size -= 1) {
|
|
830
|
+
const ends = [];
|
|
831
|
+
for (let at = text.indexOf(before.slice(-size)); at >= 0; at = text.indexOf(before.slice(-size), at + 1)) ends.push(at + size);
|
|
832
|
+
if (ends.length > 0) return ends.reduce((best, end) => (Math.abs(end - before.length) < Math.abs(best - before.length) ? end : best));
|
|
833
|
+
}
|
|
834
|
+
return 0;
|
|
835
|
+
};
|
|
836
|
+
// The place `count` characters of the block's words in (a line break is a place, and no character).
|
|
837
|
+
const placeOf = (count) => {
|
|
838
|
+
let [low, high] = [0, length()];
|
|
839
|
+
while (low < high) {
|
|
840
|
+
const middle = (low + high) >> 1;
|
|
841
|
+
if (words(0, middle).length < count) low = middle + 1;
|
|
842
|
+
else high = middle;
|
|
843
|
+
}
|
|
844
|
+
return low;
|
|
845
|
+
};
|
|
846
|
+
return () => {
|
|
847
|
+
const now = length();
|
|
848
|
+
const shift = now - total;
|
|
849
|
+
if (to + shift >= 0 && words(to + shift, Infinity) === after) return select(rangeAt(host, [Math.max(0, from + shift), to + shift]));
|
|
850
|
+
if (from <= now && words(0, from) === before) return select(rangeAt(host, [from, Math.min(to, now)]));
|
|
851
|
+
const place = placeOf(pastTail(words(0, Infinity)));
|
|
852
|
+
select(rangeAt(host, [place, Math.min(place + to - from, now)]));
|
|
853
|
+
};
|
|
854
|
+
};
|
|
855
|
+
// A block the file knows by its id, not as a unit (`keyed`: a detail card).
|
|
856
|
+
const keyedBlock = (node) => Boolean(BLOCK_TYPES[blockTypeOf(node)]?.keyed);
|
|
857
|
+
const registerBox = (node, kind, key = null) => {
|
|
858
|
+
const id = map.boxes.length;
|
|
859
|
+
// A keyed block's box is named by the block's id; any other box by the unit it is in (and its
|
|
860
|
+
// key there: a Sheet panel's).
|
|
861
|
+
let keyed = node;
|
|
862
|
+
while (keyed && !(keyedBlock(keyed) && BLOCK_TYPES[blockTypeOf(keyed)].box === kind)) keyed = keyed.parentElement;
|
|
863
|
+
const entry = { id, kind, unit: unitAround(keyed ?? node), ...(keyed ? { key: keyed.id } : key !== null && key !== undefined ? { key } : {}) };
|
|
864
|
+
map.boxes.push(entry);
|
|
865
|
+
boxes?.set(id, entry);
|
|
866
|
+
bind(node, "box", id);
|
|
867
|
+
return id;
|
|
868
|
+
};
|
|
869
|
+
// ---------- what the file says ----------
|
|
870
|
+
// After a commit the page knows of the file only what the file says (the answer's `file`: the
|
|
871
|
+
// units the commit named or made, by id; commitEdits in source/), never what it sent and
|
|
872
|
+
// expects the file to hold: a text's `before` is the file's text (its whitespace too), a block the
|
|
873
|
+
// commit wrote is known whether or not it is still on the page (deleted while the commit was on
|
|
874
|
+
// its way, it is in the file all the same, and the next commit takes it out), and the boxes a
|
|
875
|
+
// unit has are those the file gives it (every list item takes lists, for one).
|
|
876
|
+
|
|
877
|
+
// The box of `element` (a unit of the page) that is the file's box of `kind` (and `key`), as its component says.
|
|
878
|
+
const boxElement = (element, { kind, key }) => boxIn(element, kind, key ?? null);
|
|
879
|
+
// The text blocks of a sealed unit (what a component drew around them moves a path off), which are
|
|
880
|
+
// those the file gives in its order, or null: what is sealed holds no blocks of its own.
|
|
881
|
+
const inFileOrder = (element, blocks) => {
|
|
882
|
+
if (!SEALED || !element.matches(SEALED)) return null;
|
|
883
|
+
const hosts = [...element.querySelectorAll("[contenteditable='true']")].filter((host) => host.parentElement?.closest("[data-ge-unit], [data-ge-unit-kind]") === element);
|
|
884
|
+
return hosts.length === blocks.length ? hosts : null;
|
|
885
|
+
};
|
|
886
|
+
// The text block of `element` (a unit of the page) that is the file's { self } or { path } one.
|
|
887
|
+
const blockElement = (element, block) => {
|
|
888
|
+
// Its own text: the element itself, or the span of its own its description's `title` names (a Sheet tab's name).
|
|
889
|
+
if (block.self) return element.matches("[contenteditable='true']") ? element : BLOCK_TYPES[blockTypeOf(element)]?.title?.(element) ?? null;
|
|
890
|
+
if (!Array.isArray(block.path) || block.path[0] !== "unit") return null;
|
|
891
|
+
const want = JSON.stringify(block.path);
|
|
892
|
+
const owner = (host) => host.parentElement?.closest("[data-ge-unit], [data-ge-unit-kind]");
|
|
893
|
+
return [...element.querySelectorAll("[contenteditable='true']")].find((host) => owner(host) === element && JSON.stringify(pathFrom(element, host)) === want) ?? null;
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* Take in what the file says of the units a commit named or made (`facts`, by id): `elements`,
|
|
898
|
+
* the page's elements by the id they carry, are those units; `skip`, page units whose operation
|
|
899
|
+
* the file refused (they keep what the page knew); `converted`, page units the commit turned
|
|
900
|
+
* into another type -> that change; `written`, the text blocks the commit wrote; `data`, what the next layout
|
|
901
|
+
* holds of what components write, by page unit (apply), to which it adds what the file says of the
|
|
902
|
+
* units the commit wrote whole, made or put back; `wrote`, page unit -> the parts of what its component
|
|
903
|
+
* writes that the commit wrote, as sent; `given`, the hashes the answer gives (`hashes`: of the units the
|
|
904
|
+
* page named, by page unit, and of the blocks it made, by key). Returns { orders: the order of the units in each
|
|
905
|
+
* box of those units, by page box id, as the file has them; madeIn: page unit made by this commit ->
|
|
906
|
+
* the page unit the file has it in (null: the page); arounds: page unit put back around its blocks ->
|
|
907
|
+
* the source it was put back with }.
|
|
908
|
+
*
|
|
909
|
+
* `alone`: nobody but this page wrote the file since the page showed it, so what the file says
|
|
910
|
+
* is what the page's own commits made of it, all of which the page takes in. Otherwise the file
|
|
911
|
+
* holds someone else's changes the page does not show yet (it shows them once nothing is left to
|
|
912
|
+
* commit): those must stay changes of theirs, not become what the page started from (its next
|
|
913
|
+
* commit would undo them: move a block back, write over their words). Then the page takes in
|
|
914
|
+
* only what the commit itself wrote: the blocks it made and their boxes, and the texts it sent.
|
|
915
|
+
*/
|
|
916
|
+
function adopt(facts, elements, skip, converted, pending, written, alone, data, wrote, given) {
|
|
917
|
+
const orders = new Map();
|
|
918
|
+
const adopted = new Map(); // id -> [page unit id, element]
|
|
919
|
+
const made = new Set(); // page units this commit made (or converted)
|
|
920
|
+
const gone = new Set([...removed.keys(), ...removedWith.keys()]); // page units deleted whole, and what went with them: those the file has are put back
|
|
921
|
+
const madeIn = new Map(); // page unit made by this commit -> the page unit the file has it in (null: the page)
|
|
922
|
+
const parents = new Map(); // page unit -> the file's id of the unit the file has it in (null: none)
|
|
923
|
+
const strays = new Map(); // page unit id -> the units that went with it, standing elsewhere on the page
|
|
924
|
+
const arounds = new Map(); // page unit put back around its blocks -> its source (dropped's `around`)
|
|
925
|
+
// The keys the commit made blocks under (pending.made), by element: the answer gives the hash of each
|
|
926
|
+
// it holds as the page sent it.
|
|
927
|
+
const madeKeys = new Map([...pending.made].map(([key, element]) => [element, key]));
|
|
928
|
+
for (const [id, fact] of Object.entries(facts)) {
|
|
929
|
+
const element = elements.get(id);
|
|
930
|
+
if (!element) continue;
|
|
931
|
+
let unit = idOf(element);
|
|
932
|
+
if (unit !== null && skip.has(unit)) {
|
|
933
|
+
// Of what its component wrote, the parts that went in are the file's, the rest held.
|
|
934
|
+
if (wrote.has(unit)) map.units[unit].parts = { ...map.units[unit].parts, ...Object.fromEntries(Object.keys(wrote.get(unit)).filter((name) => fact.parts?.[name]).map((name) => [name, fact.parts[name]])) };
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
const fresh = unit === null;
|
|
938
|
+
if (fresh) {
|
|
939
|
+
// Put back around its blocks, it is the unit it was; else a new one.
|
|
940
|
+
const was = dropped.get(identity(element));
|
|
941
|
+
unit = was?.around ? was.unit : map.units.length;
|
|
942
|
+
if (!was?.around) {
|
|
943
|
+
const entry = { id: unit, kind: fact.kind, parent: null, hash: fact.hash };
|
|
944
|
+
map.units.push(entry);
|
|
945
|
+
units?.set(unit, entry);
|
|
946
|
+
}
|
|
947
|
+
bind(element, "unit", unit);
|
|
948
|
+
if (was?.around) arounds.set(unit, was.around);
|
|
949
|
+
if (stood.has(was?.unit)) stood.set(unit, stood.get(was.unit)); // whichever unit it comes back as
|
|
950
|
+
// Back under the id it had (proposedId): one unit of the map has an id, the file naming units by it.
|
|
951
|
+
if (was?.unit !== unit && map.units[was?.unit]?.blockId === id) map.units[was.unit].blockId = undefined;
|
|
952
|
+
}
|
|
953
|
+
if (fresh || converted.has(unit)) made.add(unit);
|
|
954
|
+
// Written whole, from its template, what was copied or the source it went with: what its component
|
|
955
|
+
// writes is what the file says it holds, whatever the page shows (that goes with the next commit).
|
|
956
|
+
if (fact.markup !== undefined && (made.has(unit) || gone.has(unit))) data.set(unit, pageOfKind(fact.kind)?.dataIn?.(fact.markup) ?? null);
|
|
957
|
+
// Its hash is the file's where nobody else wrote, or where the file holds it as this commit wrote it:
|
|
958
|
+
// put back from its source, converted whole, or made, the answer giving the hash of what it made
|
|
959
|
+
// (none of one made around a unit someone else changed, or copied from one: their change is in it).
|
|
960
|
+
// A unit retagged, or whose table's rows and cells changed, holds the rest as the file had it: as for
|
|
961
|
+
// any other, its hash is the one the answer gave (`hashes`) or the one the page saw, and one made
|
|
962
|
+
// holding someone else's change has none the page saw (null). Someone else's change of it stays
|
|
963
|
+
// theirs: deleting, converting or writing it asks first. So do the hashes of the parts its component
|
|
964
|
+
// writes, but those this commit wrote or made, the file's now.
|
|
965
|
+
const own = alone || gone.has(unit) || (converted.has(unit) ? Boolean(converted.get(unit).replace) : fresh && Object.hasOwn(given, madeKeys.get(element) ?? ""));
|
|
966
|
+
const hash = own ? fact.hash : fresh ? null : map.units[unit].hash;
|
|
967
|
+
const parts = own || fresh ? fact.parts : { ...map.units[unit].parts, ...Object.fromEntries(Object.keys(wrote.get(unit) ?? {}).filter((name) => fact.parts?.[name]).map((name) => [name, fact.parts[name]])) };
|
|
968
|
+
Object.assign(map.units[unit], { kind: fact.kind, hash, parts, blockId: id, keep: fact.keep ?? undefined, grid: fact.grid || undefined });
|
|
969
|
+
// Put back (undo): its text blocks, as the file gave them when it went, are its own again.
|
|
970
|
+
for (const [block, source, baseline] of removed.get(unit)?.blocks ?? []) {
|
|
971
|
+
if (source !== undefined) sources.set(block, source);
|
|
972
|
+
if (baseline !== undefined) baselines.set(block, baseline);
|
|
973
|
+
}
|
|
974
|
+
removed.delete(unit);
|
|
975
|
+
for (const [other, owner] of removedWith) {
|
|
976
|
+
if (owner !== unit) continue;
|
|
977
|
+
removedWith.delete(other);
|
|
978
|
+
const stray = unitElement(other);
|
|
979
|
+
if (stray && !element.contains(stray)) strays.set(unit, [...(strays.get(unit) ?? []), stray]);
|
|
980
|
+
}
|
|
981
|
+
adopted.set(id, [unit, element]);
|
|
982
|
+
}
|
|
983
|
+
for (const [id, [unit, element]] of adopted) {
|
|
984
|
+
const fact = facts[id];
|
|
985
|
+
const parent = fact.parent ? adopted.get(fact.parent)?.[0] : undefined;
|
|
986
|
+
if (fact.parent === null) map.units[unit].parent = null;
|
|
987
|
+
else if (parent !== undefined) map.units[unit].parent = parent;
|
|
988
|
+
// Made by this commit: where the file has it is what the commit wrote, whoever else wrote.
|
|
989
|
+
const owner = fact.parent === null ? null : (parent ?? map.units.find((other) => other.blockId === fact.parent)?.id);
|
|
990
|
+
if (made.has(unit) && owner !== undefined) madeIn.set(unit, owner);
|
|
991
|
+
parents.set(unit, fact.parent);
|
|
992
|
+
// Its boxes, with the units in each: by id, or by hash for those the file gives none (units
|
|
993
|
+
// the commit did not change), matched in order with the page's units of that box.
|
|
994
|
+
for (const box of fact.boxes) {
|
|
995
|
+
const node = boxElement(element, box);
|
|
996
|
+
if (!node) continue;
|
|
997
|
+
const fresh = boxOf(node) === null; // joins the map now: it holds only what the commit put there
|
|
998
|
+
if (fresh) registerBox(node, box.kind, box.key);
|
|
999
|
+
if (alone || fresh) orders.set(boxOf(node), orderOf(node, box.units, adopted, strays.get(unit)));
|
|
1000
|
+
}
|
|
1001
|
+
// Its text blocks, with the file's texts. Of a unit it converted, one that stays (a table's cell, a
|
|
1002
|
+
// list's item retagged) holds what the file held, someone else's words too, which are no `before`
|
|
1003
|
+
// of the page's (its words would go over theirs): it keeps the one the page had, but where nobody else wrote.
|
|
1004
|
+
const matched = new Set();
|
|
1005
|
+
const inOrder = inFileOrder(element, fact.blocks);
|
|
1006
|
+
for (const [index, block] of fact.blocks.entries()) {
|
|
1007
|
+
const host = blockElement(element, block) ?? inOrder?.[index];
|
|
1008
|
+
if (!host) continue;
|
|
1009
|
+
const fresh = blockOf(host) === null;
|
|
1010
|
+
const own = registerBlock(host, pending, block, unit);
|
|
1011
|
+
matched.add(own);
|
|
1012
|
+
const taken = alone || fresh || written.has(own) || (made.has(unit) && !converted.has(unit));
|
|
1013
|
+
if (!issues.has(issueKey("text", own)) && taken) sources.set(own, block.inner);
|
|
1014
|
+
}
|
|
1015
|
+
// What the page made a text block of a block it made, and the file takes for none: not one, as
|
|
1016
|
+
// a load of the page shows it.
|
|
1017
|
+
if (made.has(unit)) for (const host of element.querySelectorAll("[data-ge-part]")) if (blockOf(host) === null) pageOfUnit(element)?.notText?.(host);
|
|
1018
|
+
// Its text blocks the file no longer has went with it: off the page, or the text of what it
|
|
1019
|
+
// was before this commit converted it.
|
|
1020
|
+
for (const block of map.blocks) {
|
|
1021
|
+
if (block.unit !== unit || block.gone || matched.has(block.id) || (hostOf(block.id) && !converted.has(unit))) continue;
|
|
1022
|
+
block.gone = true;
|
|
1023
|
+
sources.delete(block.id);
|
|
1024
|
+
baselines.delete(block.id);
|
|
1025
|
+
unbind("block", block.id);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return { orders, madeIn, made, parents, arounds };
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// The page's units of box `node` in the order the file has them (`units`: the file's units of
|
|
1032
|
+
// the box, by id, or { hash } for those without, matched in order with the page's units of that
|
|
1033
|
+
// box that have none). `adopted`: id -> [page unit id, element], the units just taken in;
|
|
1034
|
+
// `elsewhere`: page units of that box standing elsewhere on the page (taken out of a unit put back).
|
|
1035
|
+
function orderOf(node, units, adopted = new Map(), elsewhere = []) {
|
|
1036
|
+
const plain = [...node.children, ...elsewhere].filter((child) => idOf(child) !== null && !map.units[idOf(child)]?.blockId);
|
|
1037
|
+
const order = [];
|
|
1038
|
+
for (const child of units) {
|
|
1039
|
+
if (typeof child === "string") {
|
|
1040
|
+
const known = adopted.get(child)?.[0] ?? map.units.find((other) => other.blockId === child)?.id;
|
|
1041
|
+
if (known !== undefined) order.push(known);
|
|
1042
|
+
} else {
|
|
1043
|
+
const at = plain.findIndex((other) => map.units[idOf(other)].hash === child.hash);
|
|
1044
|
+
if (at >= 0) order.push(idOf(plain.splice(0, at + 1).at(-1)));
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return order;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* Take in what the file says of the keyed blocks (detail cards) a commit made (the answer's
|
|
1052
|
+
* `keyed`: by id, the units of its content). A keyed block is no unit: the file knows it by its id.
|
|
1053
|
+
* It becomes one of the file, as the page shows one it opened with: its content a box of the map
|
|
1054
|
+
* (the units the commit wrote in it already taken in by adopt; a block made in it later goes in as
|
|
1055
|
+
* a new block of that box). Nothing of it is made again. Its making undone, it stays in the file
|
|
1056
|
+
* with nothing opening it (as one whose entry the reader took out), and redone the page shows the
|
|
1057
|
+
* file's again. Adds the box's order to `orders`.
|
|
1058
|
+
*/
|
|
1059
|
+
function adoptKeyed(facts, pending, orders) {
|
|
1060
|
+
for (const [key, card] of Object.entries(facts ?? {})) {
|
|
1061
|
+
// The block made on the page the file knows by that id (of a type that is `keyed`).
|
|
1062
|
+
const block = [...pending.made.values()].find((element) => element?.id === key && keyedBlock(element));
|
|
1063
|
+
if (!block || keyedIds.has(identityOf(block))) continue;
|
|
1064
|
+
const spec = BLOCK_TYPES[blockTypeOf(block)];
|
|
1065
|
+
const content = spec.content?.(block) ?? block;
|
|
1066
|
+
if (content && boxOf(content) === null) orders.set(registerBox(content, spec.box), orderOf(content, card.units));
|
|
1067
|
+
keyedIds.add(identityOf(block));
|
|
1068
|
+
keyedBlocks.set(key, block); // see keyedAway
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// Whether what the file gave of a deleted unit is all of it: not `around`, its blocks gone elsewhere,
|
|
1073
|
+
// and not nothing, the file having had it no more.
|
|
1074
|
+
const deletedWhole = (source) => Boolean(source?.source && source.seal && !source.around);
|
|
1075
|
+
const elementDeleted = (id, pending) => unitElement(id) ?? pending.snapshot.units.find((node) => idOf(node) === id) ?? getLayout()?.elements.get(id) ?? null;
|
|
1076
|
+
// The units a deleted one took with it (`source`: what the file gave of it): itself, and those inside
|
|
1077
|
+
// it or in a box it takes along (a Sheet tab, its panel) that were not on the page when the commit
|
|
1078
|
+
// was built (the others went elsewhere: a merged section's blocks). One deleted by itself, by an
|
|
1079
|
+
// earlier commit or by this one, is none of them, nor is what it holds: it comes back from its own
|
|
1080
|
+
// source. Of one deleted whole, what this commit deleted in it is among them: that source holds it.
|
|
1081
|
+
const deletedWith = (id, source, pending) => {
|
|
1082
|
+
// Those the commit moved are elsewhere, on the page or waiting off it (`along`).
|
|
1083
|
+
const present = new Set([...pending.snapshot.units.map(idOf), ...[...pending.units.values()].filter((sent) => sent.change?.move).map((sent) => sent.id)].filter((unit) => unit !== null));
|
|
1084
|
+
const itself = new Set(deletedWhole(source) ? [] : [...pending.units.values()].filter((sent) => sent.change?.remove).map((sent) => sent.id));
|
|
1085
|
+
const element = elementDeleted(id, pending);
|
|
1086
|
+
const apart = (unit) => present.has(unit) || itself.has(unit) || removed.has(unit) || removedWith.has(unit);
|
|
1087
|
+
const along = new Set(map.boxes.filter((box) => box.unit === map.units[id]?.parent && takesBox(element, box)).flatMap((box) => getLayout()?.orders.get(box.id) ?? []));
|
|
1088
|
+
const within = (unit) => {
|
|
1089
|
+
for (const at of chain(unit)) {
|
|
1090
|
+
if (at === id) return true;
|
|
1091
|
+
if (apart(at)) return false;
|
|
1092
|
+
if (along.has(at)) return true;
|
|
1093
|
+
}
|
|
1094
|
+
return false;
|
|
1095
|
+
};
|
|
1096
|
+
return new Set([id, ...map.units.map((unit) => unit.id).filter(within)]);
|
|
1097
|
+
};
|
|
1098
|
+
// The units inside a deleted one that the page showed elsewhere when the commit was built but the
|
|
1099
|
+
// commit placed nowhere (no move takes them, nor a block made on the page holding them: they
|
|
1100
|
+
// stand in a container not written yet, a new row of columns one of whose columns is still
|
|
1101
|
+
// empty). The file deleted them with it all the same.
|
|
1102
|
+
const leftBehind = (id, pending) => {
|
|
1103
|
+
const placed = new Set();
|
|
1104
|
+
const visit = (node) => {
|
|
1105
|
+
if (node?.unit && typeof node.unit === "object") placed.add(node.unit.key);
|
|
1106
|
+
for (const child of node?.children ?? []) visit(child);
|
|
1107
|
+
};
|
|
1108
|
+
for (const sent of pending.units.values()) {
|
|
1109
|
+
if (sent.change?.move) placed.add(sent.id);
|
|
1110
|
+
if (sent.insert) visit(sent.node);
|
|
1111
|
+
}
|
|
1112
|
+
for (const unit of pending.restores.values()) placed.add(unit); // put back by the same commit, from its own source
|
|
1113
|
+
const present = new Set(pending.snapshot.units.map(idOf).filter((unit) => unit !== null));
|
|
1114
|
+
const away = (unit) => {
|
|
1115
|
+
for (const at of chain(unit.id)) {
|
|
1116
|
+
if (at === id) break;
|
|
1117
|
+
if (placed.has(at)) return false;
|
|
1118
|
+
}
|
|
1119
|
+
return true;
|
|
1120
|
+
};
|
|
1121
|
+
return map.units.filter((unit) => present.has(unit.id) && isInside(unit, id) && away(unit)).map((unit) => unit.id);
|
|
1122
|
+
};
|
|
1123
|
+
// A unit the file dropped while the page may still show it: its element (whichever stands for it)
|
|
1124
|
+
// is made on the page from now on, and so are its text blocks and boxes (sync-state.mjs asMade).
|
|
1125
|
+
const drop = (id) => {
|
|
1126
|
+
if (!map.units[id]) return;
|
|
1127
|
+
unbind("unit", id, true);
|
|
1128
|
+
for (const block of map.blocks) {
|
|
1129
|
+
if (block.unit !== id || block.gone) continue;
|
|
1130
|
+
block.gone = true;
|
|
1131
|
+
sources.delete(block.id);
|
|
1132
|
+
baselines.delete(block.id);
|
|
1133
|
+
unbind("block", block.id, true);
|
|
1134
|
+
}
|
|
1135
|
+
for (const box of map.boxes) if (box.unit === id) unbind("box", box.id, true);
|
|
1136
|
+
};
|
|
1137
|
+
// A text block the file dropped (emptied), likewise.
|
|
1138
|
+
const dropBlock = (id) => {
|
|
1139
|
+
if (!map.blocks[id]) return;
|
|
1140
|
+
map.blocks[id].gone = true;
|
|
1141
|
+
unbind("block", id, true);
|
|
1142
|
+
};
|
|
1143
|
+
// A unit deleted by a commit. Its text blocks leave the commits (they went with it); deleted
|
|
1144
|
+
// whole, it keeps the source the file gave, for undo: whatever stands for it on the page (undo put
|
|
1145
|
+
// it back while the commit was on its way, or later) is that unit put back, and the next commit
|
|
1146
|
+
// writes its source again. Else (`around`, its blocks went elsewhere; or the file had it no more)
|
|
1147
|
+
// it and what went with it are made on the page from now on: a container whose blocks went
|
|
1148
|
+
// elsewhere comes back holding them, around them from that source when they stand straight in it.
|
|
1149
|
+
const registerRemoved = (id, source, pending) => {
|
|
1150
|
+
// What it took along that the page still shows elsewhere is made on the page from now on (it is
|
|
1151
|
+
// written anew once it stands where the file can have it).
|
|
1152
|
+
for (const unit of leftBehind(id, pending)) drop(unit);
|
|
1153
|
+
const element = elementDeleted(id, pending);
|
|
1154
|
+
const gone = deletedWith(id, source, pending);
|
|
1155
|
+
const blocks = map.blocks.filter((block) => gone.has(block.unit) && !block.gone).map((block) => [block.id, sources.get(block.id), baselines.get(block.id)]);
|
|
1156
|
+
for (const [block] of blocks) {
|
|
1157
|
+
sources.delete(block);
|
|
1158
|
+
baselines.delete(block);
|
|
1159
|
+
}
|
|
1160
|
+
if (deletedWhole(source)) {
|
|
1161
|
+
// The types it and what went with it showed as, and their looks and tables' rows and cells as the
|
|
1162
|
+
// layout had them: what its source puts back.
|
|
1163
|
+
const types = new Map([...gone].map((unit) => [unit, getLayout()?.types?.get(unit)]));
|
|
1164
|
+
const looks = new Map([...gone].map((unit) => [unit, Object.fromEntries(LOOKS.map((part) => [part, getLayout()?.[part]?.get(unit)]))]));
|
|
1165
|
+
removed.set(id, { source: source.source, seal: source.seal, type: types.get(id), types, looks, blocks, element });
|
|
1166
|
+
for (const unit of gone) if (unit !== id) removedWith.set(unit, id);
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
for (const unit of gone) drop(unit);
|
|
1170
|
+
const was = dropped.get(map.units[id]?.cid);
|
|
1171
|
+
const type = element && containerTypeOf(element);
|
|
1172
|
+
if (was && source?.around && source.seal && holdsStraight(BLOCK_TYPES[type])) was.around = { source: source.source, seal: source.seal, type };
|
|
1173
|
+
};
|
|
1174
|
+
// The units a commit deleted (`deletions`: unit id -> what the file gave of it). One deleted in a unit
|
|
1175
|
+
// deleted whole is no deletion of its own, however the page took it out (a selection across blocks
|
|
1176
|
+
// empties a list item by item): it went with that unit, and comes back with it.
|
|
1177
|
+
const registerDeletions = (deletions, pending) => {
|
|
1178
|
+
const taken = new Set();
|
|
1179
|
+
for (const [id, source] of deletions) {
|
|
1180
|
+
if (!deletedWhole(source)) continue;
|
|
1181
|
+
for (const unit of deletedWith(id, source, pending)) if (unit !== id) taken.add(unit);
|
|
1182
|
+
}
|
|
1183
|
+
for (const [id, source] of deletions) if (!taken.has(id)) registerRemoved(id, source, pending);
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
// The text the file has of a text block outside any unit (the answer's `outside`, by key: the
|
|
1187
|
+
// report title's "title", else the element's id; shared/ids.mjs outsideKey), or undefined.
|
|
1188
|
+
const outsideText = (id, outside) => {
|
|
1189
|
+
const block = map.blocks[id];
|
|
1190
|
+
if (!block || (block.unit !== null && block.unit !== undefined)) return undefined;
|
|
1191
|
+
const key = outsideKey(block.kind === "title", hostOf(id)?.id);
|
|
1192
|
+
return key ? outside?.[key] : undefined;
|
|
1193
|
+
};
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Take in the answer to a commit (build's `pending`, the endpoint's receipt; `alone`: nobody but
|
|
1197
|
+
* this page wrote the file since it showed it, see adopt). Returns { conflicts, merged: hosts now
|
|
1198
|
+
* showing a merge, stale: a merge the page could not show, outdated: it runs a manifest that is no
|
|
1199
|
+
* more, forced: the page units a change the reader said to make anyway went in for, all it held }.
|
|
1200
|
+
* A conflict of a change sent as the reader said to make it anyway says so (`again`).
|
|
1201
|
+
*/
|
|
1202
|
+
function apply(pending, receipt, { alone = true } = {}) {
|
|
1203
|
+
touched(); // the baselines, the layout and the table change: collect() anew
|
|
1204
|
+
// Asked once: a unit the file could not name (gone meanwhile) is not asked for again and again.
|
|
1205
|
+
for (const id of pending.named ?? []) naming.delete(id);
|
|
1206
|
+
marks();
|
|
1207
|
+
const conflicts = [];
|
|
1208
|
+
const merged = [];
|
|
1209
|
+
let stale = false;
|
|
1210
|
+
let shownAgain = false; // what a component wrote went by a manifest that is no more
|
|
1211
|
+
const written = new Set(); // text blocks the commit wrote
|
|
1212
|
+
// The page's elements the commit was built from, by page unit id (on the page or not by now).
|
|
1213
|
+
const built = new Map(pending.snapshot.units.filter((node) => idOf(node) !== null).map((node) => [idOf(node), node]));
|
|
1214
|
+
const elementOf = (id) => built.get(id) ?? unitElement(id) ?? getLayout()?.elements.get(id) ?? null;
|
|
1215
|
+
// The ids the file gives: to units the page named, and to the blocks it made or put back (the
|
|
1216
|
+
// one the page proposed, or another when the file has that one already).
|
|
1217
|
+
for (const [key, id] of Object.entries(receipt.ids ?? {})) if (map.units[Number(key)]) map.units[Number(key)].blockId = id;
|
|
1218
|
+
for (const [key, id] of Object.entries(receipt.made ?? {})) {
|
|
1219
|
+
const element = pending.made.get(key);
|
|
1220
|
+
if (element && idOf(element) !== null) map.units[idOf(element)].blockId = id; // put back
|
|
1221
|
+
else if (element) proposals.set(identityOf(element), id);
|
|
1222
|
+
}
|
|
1223
|
+
for (const [key, hash] of Object.entries(receipt.hashes ?? {})) if (map.units[Number(key)]) map.units[Number(key)].hash = hash;
|
|
1224
|
+
// The keys of charts drawn from tables the file gave the blocks the commit made, other than the
|
|
1225
|
+
// page chose (source/chart-keys.mjs: one the file had meanwhile): theirs on the page too, so
|
|
1226
|
+
// that each card draws the table the file links it to ({ old: new | null }, null: off a table).
|
|
1227
|
+
if (receipt.chartKeys) {
|
|
1228
|
+
for (const element of pending.made.values()) {
|
|
1229
|
+
// A copy not written yet sends its keys again: the file's now.
|
|
1230
|
+
const sent = element?.getAttribute(COPY_KEYS);
|
|
1231
|
+
if (sent) {
|
|
1232
|
+
const keys = Object.fromEntries(Object.entries(JSON.parse(sent)).map(([from, to]) => [from, to !== null && Object.hasOwn(receipt.chartKeys, to) ? receipt.chartKeys[to] : to]));
|
|
1233
|
+
element.setAttribute(COPY_KEYS, JSON.stringify(keys));
|
|
1234
|
+
}
|
|
1235
|
+
for (const node of element ? [element, ...element.querySelectorAll(`[${CHART_DATA}], [${CHART_SOURCE}]`)] : []) {
|
|
1236
|
+
for (const name of [CHART_DATA, CHART_SOURCE]) {
|
|
1237
|
+
const key = node.getAttribute(name);
|
|
1238
|
+
if (key === null || !Object.hasOwn(receipt.chartKeys, key)) continue;
|
|
1239
|
+
const next = receipt.chartKeys[key];
|
|
1240
|
+
if (typeof next === "string") node.setAttribute(name, next);
|
|
1241
|
+
else if (next === null && name === CHART_SOURCE) node.removeAttribute(name);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
for (const entry of receipt.texts ?? []) {
|
|
1248
|
+
const sent = pending.texts.get(entry.key);
|
|
1249
|
+
if (!sent) continue;
|
|
1250
|
+
if (entry.status === "conflict") {
|
|
1251
|
+
// What the reader wrote there: the block's text and the blocks split off it (they go with it).
|
|
1252
|
+
const conflict = { ...entry, kind: "text", block: sent.id, mine: sent.parts.map((part) => part.html).filter(Boolean).join("\n") || null };
|
|
1253
|
+
issues.set(entry.key, conflict);
|
|
1254
|
+
conflicts.push(conflict);
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
const kept = sent.parts.find((part) => part.keep);
|
|
1258
|
+
if (!kept) {
|
|
1259
|
+
sources.delete(sent.id);
|
|
1260
|
+
baselines.delete(sent.id);
|
|
1261
|
+
// Emptied (the file dropped it, whatever blocks split off it went in): what stands for it
|
|
1262
|
+
// on the page is a new block from now on, the block itself or what it became meanwhile (an
|
|
1263
|
+
// empty last item turned into a paragraph after its list, say).
|
|
1264
|
+
const block = map.blocks[sent.id];
|
|
1265
|
+
if (block?.self) drop(block.unit);
|
|
1266
|
+
else dropBlock(sent.id);
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
const host = hostOf(sent.id);
|
|
1270
|
+
// What the file has now (its own text, below, for a block of a unit; the answer's `outside`
|
|
1271
|
+
// for one outside any unit), until then what went in.
|
|
1272
|
+
sources.set(sent.id, outsideText(sent.id, receipt.outside) ?? (entry.status === "merged" ? entry.html : kept.html));
|
|
1273
|
+
if (entry.status === "merged" && !(host && serializeInner(host) === kept.html && !host.querySelector("[data-ge-atom]"))) {
|
|
1274
|
+
// Someone else's words went in too, and the block is not as it was sent (or holds a formula): the
|
|
1275
|
+
// next commit merges again, from what the page sent, which stays its `before` (a commit of the
|
|
1276
|
+
// block from the file's text, theirs in it, would write theirs out). So it is none the commit wrote.
|
|
1277
|
+
sources.set(sent.id, kept.html);
|
|
1278
|
+
baselines.set(sent.id, kept.html);
|
|
1279
|
+
stale = true;
|
|
1280
|
+
} else if (entry.status === "merged") {
|
|
1281
|
+
// Shown, the block as it was sent.
|
|
1282
|
+
const caret = caretKept(host);
|
|
1283
|
+
setOwnHTML(host, entry.html);
|
|
1284
|
+
caret();
|
|
1285
|
+
baselines.set(sent.id, serializeInner(host));
|
|
1286
|
+
merged.push(host);
|
|
1287
|
+
written.add(sent.id);
|
|
1288
|
+
} else {
|
|
1289
|
+
baselines.set(sent.id, kept.html);
|
|
1290
|
+
written.add(sent.id);
|
|
1291
|
+
}
|
|
1292
|
+
committedText(entry.key, sent);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
const skip = new Set(); // page units whose operation the file refused: they keep what the page knew
|
|
1296
|
+
const again = new Set(); // of those, the ones it goes again for (its place gone, tried once more)
|
|
1297
|
+
const converted = new Map(); // page units the commit turned into another type -> that change
|
|
1298
|
+
const landed = new Set(); // page units the change the reader said to make anyway went in for, all of it
|
|
1299
|
+
const movedFrom = new Map(); // page units the commit moved -> the file's id of the unit they were in
|
|
1300
|
+
const deletions = new Map(); // page units the commit deleted -> what the file gave of each
|
|
1301
|
+
const wrote = new Map(); // page units whose component's parts the commit wrote -> those parts, as sent
|
|
1302
|
+
const went = new Map(); // page units -> what of the layout the changes of them that went in are about (layoutOf)
|
|
1303
|
+
const fileParent = (id) => {
|
|
1304
|
+
const parent = map.units[id]?.parent;
|
|
1305
|
+
return parent === null || parent === undefined ? null : map.units[parent]?.blockId ?? "";
|
|
1306
|
+
};
|
|
1307
|
+
for (const entry of receipt.units ?? []) {
|
|
1308
|
+
const sent = pending.units.get(entry.key);
|
|
1309
|
+
if (!sent) continue;
|
|
1310
|
+
if (entry.status === "conflict") {
|
|
1311
|
+
// A block made on the page that is not on it any more (turned into another meanwhile, as an
|
|
1312
|
+
// empty paragraph "- " made a list of, sent while offline): nothing to hold; what stands for
|
|
1313
|
+
// it now goes with the next commit, and is held itself if it has no place either. Nor is the place
|
|
1314
|
+
// of a unit that waits, sent with the blocks around it (`along`): it waits as it did.
|
|
1315
|
+
if ((sent.insert && sent.element && !sent.element.isConnected) || sent.along) continue;
|
|
1316
|
+
// The page runs an old manifest of its package: nothing for the reader to decide. It is given
|
|
1317
|
+
// up and that alone: what else of the unit went in is taken in as any unit's, what waits goes its
|
|
1318
|
+
// way, and the page shown again runs the manifest there is.
|
|
1319
|
+
if (entry.reason === "definition") {
|
|
1320
|
+
unwritten.add(sent.id);
|
|
1321
|
+
shownAgain = true;
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
// Its place is gone: the end of its box, once, before it is the reader's to decide.
|
|
1325
|
+
if (entry.reason === "position" && sent.insert && !elsewhere.has(identity(sent.element)) && endOf(sent.element)) {
|
|
1326
|
+
elsewhere.add(identityOf(sent.element));
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1329
|
+
if (entry.reason === "position" && sent.change?.move && !movedElsewhere.has(sent.id) && endOf(unitElement(sent.id))) {
|
|
1330
|
+
movedElsewhere.add(sent.id);
|
|
1331
|
+
again.add(sent.id);
|
|
1332
|
+
skip.add(sent.id);
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
// Deleted, with blocks in it that go elsewhere and had no place (their neighbour gone): with
|
|
1336
|
+
// them, once they go to the end of their box.
|
|
1337
|
+
if (entry.reason === "position" && sent.change?.remove && !removedElsewhere.has(sent.id)) {
|
|
1338
|
+
removedElsewhere.add(sent.id);
|
|
1339
|
+
again.add(sent.id);
|
|
1340
|
+
skip.add(sent.id);
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
// Of what its component wrote, the parts someone else left alone went in all the same (`wrote`):
|
|
1344
|
+
// those are the file's, and the rest is the change held.
|
|
1345
|
+
let change = sent.change;
|
|
1346
|
+
if (change?.source && Array.isArray(entry.wrote)) {
|
|
1347
|
+
const [went, waits] = [true, false].map((goes) => Object.fromEntries(Object.entries(change.source).filter(([name]) => entry.wrote.includes(name) === goes)));
|
|
1348
|
+
wrote.set(sent.id, went);
|
|
1349
|
+
change = { ...change, source: waits };
|
|
1350
|
+
}
|
|
1351
|
+
// Held by the key it keeps until the reader decides: a block made on the page by the id it
|
|
1352
|
+
// proposes, with what it holds (the reader's words, to copy and for the crash buffer).
|
|
1353
|
+
const key = sent.insert ? issueKey("insert", (sent.element && proposals.get(identity(sent.element))) || sent.node?.blockId || sent.element?.id || serial++) : issueKey("unit", sent.id);
|
|
1354
|
+
// A write of other parts refused where one is held already: one conflict of the instance, holding both.
|
|
1355
|
+
const before = issues.get(key);
|
|
1356
|
+
if (change?.source && before?.change?.source) change = { ...change, source: { ...before.change.source, ...change.source } };
|
|
1357
|
+
const kept = change?.source && before?.change?.source ? { theirs: { ...before.theirs, ...entry.theirs }, parts: { ...before.parts, ...entry.parts } } : {};
|
|
1358
|
+
// Sent as the reader said to make it anyway, it was written again since they were shown it (`again`).
|
|
1359
|
+
const conflict = sent.insert ? { ...entry, key, kind: "insert", element: sent.element, mine: nodeHTML(sent.node) || null } : { ...entry, ...kept, key, kind: "unit", unit: sent.id, change, mine: writtenHTML(change), ...(sent.forced ? { again: true } : {}) };
|
|
1360
|
+
issues.set(key, conflict);
|
|
1361
|
+
if (sent.insert && sent.element) refused.set(identityOf(sent.element), key);
|
|
1362
|
+
else skip.add(sent.id);
|
|
1363
|
+
conflicts.push(conflict);
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
// Moved: from the unit it was in (the file's id of it), for the revert check.
|
|
1367
|
+
if (sent.change?.move) movedFrom.set(sent.id, fileParent(sent.id));
|
|
1368
|
+
if (sent.change?.replace || sent.change?.retag || sent.change?.table) converted.set(sent.id, sent.change);
|
|
1369
|
+
if (sent.change?.source) wrote.set(sent.id, sent.change.source);
|
|
1370
|
+
if (!sent.insert) went.set(sent.id, new Set([...(went.get(sent.id) ?? []), ...layoutOf(sent.change)]));
|
|
1371
|
+
// The reader's word is for the change it was given for, gone in once all it held has: what they do to
|
|
1372
|
+
// the unit next is held to what the page saw, as any change is.
|
|
1373
|
+
const word = sent.forced ? forced.get(sent.id) : null;
|
|
1374
|
+
if (word?.parts) word.parts = word.parts.filter((name) => !Object.hasOwn(sent.change.source, name));
|
|
1375
|
+
if (word && !word.parts?.length) {
|
|
1376
|
+
forced.delete(sent.id);
|
|
1377
|
+
landed.add(sent.id);
|
|
1378
|
+
}
|
|
1379
|
+
// A table's rows made on the page are the file's now (its cells are adopted as text blocks).
|
|
1380
|
+
if (sent.change?.table) elementOf(sent.id)?.querySelectorAll("tr[data-ge-new]").forEach((row) => row.removeAttribute("data-ge-new"));
|
|
1381
|
+
else if (sent.change?.remove) deletions.set(sent.id, receipt.removed?.[entry.key]);
|
|
1382
|
+
}
|
|
1383
|
+
registerDeletions(deletions, pending);
|
|
1384
|
+
for (const unit of heldUnits(issues.keys())) skip.add(unit);
|
|
1385
|
+
// Units the file no longer has though this commit did not delete them (a list goes with its last
|
|
1386
|
+
// item): taken as deleted (no source to put back), so what stands for them on the page, if
|
|
1387
|
+
// anything, is made anew.
|
|
1388
|
+
const deleted = new Set([...pending.units.values()].filter((sent) => sent.change?.remove).map((sent) => sent.id));
|
|
1389
|
+
for (const id of receipt.gone ?? []) {
|
|
1390
|
+
const unit = map.units.find((entry) => entry.blockId === id);
|
|
1391
|
+
if (!unit || deleted.has(unit.id) || skip.has(unit.id)) continue;
|
|
1392
|
+
registerRemoved(unit.id, null, pending);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// Units know the unit they are in (the chain a later commit touches), as committed: those the
|
|
1396
|
+
// file refused to move, or held unmoved, are in the one the file has them in still (adopt gives
|
|
1397
|
+
// the others the file's own).
|
|
1398
|
+
for (const [box, children] of pending.snapshot.boxes) {
|
|
1399
|
+
const owner = unitAround(box);
|
|
1400
|
+
for (const child of children) {
|
|
1401
|
+
const id = idOf(child);
|
|
1402
|
+
if (id !== null && map.units[id] && (!skip.has(id) || went.get(id)?.has("place"))) map.units[id].parent = owner;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
// What the file says of the units the commit named or made: those elements by the id they carry
|
|
1406
|
+
// (the commit's own, then any other the page has).
|
|
1407
|
+
const elements = new Map();
|
|
1408
|
+
const note = (node, id = fileIdOf(node)) => {
|
|
1409
|
+
if (node && id && !elements.has(id)) elements.set(id, node);
|
|
1410
|
+
};
|
|
1411
|
+
pending.snapshot.units.forEach((node) => note(node));
|
|
1412
|
+
pending.made.forEach((node) => note(node));
|
|
1413
|
+
for (const unit of map.units) if (unit.blockId) note(elementOf(unit.id), unit.blockId);
|
|
1414
|
+
// Units put back show as what was put back: the source they had, of the type they had then
|
|
1415
|
+
// (an element standing for one turned into another type since converts it next).
|
|
1416
|
+
const restoredTypes = new Map([...pending.restores.values()].filter((id) => removed.has(id)).flatMap((id) => [...(removed.get(id).types ?? new Map([[id, removed.get(id).type]]))]));
|
|
1417
|
+
// Their looks and tables' rows and cells too, as the layout had them when they went: what changed on
|
|
1418
|
+
// the page since (put back, then resized or a column taken out) goes with the next commit.
|
|
1419
|
+
const restoredLooks = [...pending.restores.values()].filter((id) => removed.has(id)).flatMap((id) => [...(removed.get(id).looks ?? [])]);
|
|
1420
|
+
// What the file holds of what components write: what the last commit left, with the parts this one
|
|
1421
|
+
// wrote, and of the units it wrote whole what the file says (adopt). A write it did not carry still
|
|
1422
|
+
// differs from that, and goes with the next.
|
|
1423
|
+
const previous = getLayout();
|
|
1424
|
+
const data = new Map([...(previous?.data ?? [])].map(([id, held]) => [id, wrote.has(id) ? { ...held, ...wrote.get(id) } : held]));
|
|
1425
|
+
const { orders, madeIn, made, parents, arounds } = adopt(receipt.file ?? {}, elements, skip, converted, pending, written, alone, data, wrote, receipt.hashes ?? {});
|
|
1426
|
+
for (const [unit, parent] of parents) {
|
|
1427
|
+
if (made.has(unit)) placed.set(unit, { parent, ...(arounds.has(unit) ? { around: arounds.get(unit) } : {}) });
|
|
1428
|
+
else if (movedFrom.has(unit)) placed.set(unit, { parent, from: placed.get(unit)?.from ?? movedFrom.get(unit) });
|
|
1429
|
+
else if (placed.has(unit)) placed.set(unit, { ...placed.get(unit), parent }); // taken along by a block made or put back
|
|
1430
|
+
}
|
|
1431
|
+
// What its component wrote that went in, for the revert check: each part by the hash the commit said the
|
|
1432
|
+
// file held of it, and the one the file gives it now.
|
|
1433
|
+
for (const sent of pending.units.values()) {
|
|
1434
|
+
if (sent.change?.source) for (const name of Object.keys(wrote.get(sent.id) ?? {})) noteWrite(sent.id, name, sent.parts?.[name], map.units[sent.id]?.parts?.[name]);
|
|
1435
|
+
}
|
|
1436
|
+
adoptKeyed(receipt.keyed, pending, orders);
|
|
1437
|
+
// What is held back or goes again stays to be done: those units keep, in the next layout, all they had
|
|
1438
|
+
// in the last, so that what waits still shows against it, and so does whatever else of them the reader
|
|
1439
|
+
// did since, which no commit sends while they are held (a move, a look, a column taken out): a move the
|
|
1440
|
+
// file did not make is no move made. What of them went in with this commit is the file's, taken from
|
|
1441
|
+
// what it left (`went`: a table's operation that went in beside a move refused for its place, kept as it
|
|
1442
|
+
// was, would be sent again, against a table it no longer names).
|
|
1443
|
+
// A unit deleted and not put back (its putting back refused, say) is none the file has.
|
|
1444
|
+
const present = (node) => {
|
|
1445
|
+
const id = idOf(node);
|
|
1446
|
+
return id === null || removed.has(id) ? null : id;
|
|
1447
|
+
};
|
|
1448
|
+
const next = captureLayout(root, map, pending.snapshot, { unitOf: present, boxOf, typeOf: ownTypeOf, data });
|
|
1449
|
+
for (const [id, type] of restoredTypes) if (type !== undefined && next.elements.has(id) && !removed.has(id)) next.types.set(id, type);
|
|
1450
|
+
for (const [id, look] of restoredLooks) {
|
|
1451
|
+
if (!next.elements.has(id) || removed.has(id)) continue;
|
|
1452
|
+
for (const part of LOOKS) if (look[part] !== undefined) next[part].set(id, look[part]);
|
|
1453
|
+
}
|
|
1454
|
+
// A unit is in the one box the file has it in.
|
|
1455
|
+
for (const [box, order] of orders) {
|
|
1456
|
+
next.orders.set(box, order);
|
|
1457
|
+
for (const [other, list] of next.orders) if (other !== box && list.some((id) => order.includes(id))) next.orders.set(other, list.filter((id) => !order.includes(id)));
|
|
1458
|
+
}
|
|
1459
|
+
// A block this commit made is in the unit the file has it in: where the page shows it in another
|
|
1460
|
+
// (the commit wrote it next to a block that had not moved where the page moved it), the next
|
|
1461
|
+
// commit moves it there.
|
|
1462
|
+
for (const [unit, owner] of madeIn) {
|
|
1463
|
+
for (const [box, list] of next.orders) if (list.includes(unit) && (map.boxes[box]?.unit ?? null) !== owner) next.orders.set(box, list.filter((id) => id !== unit));
|
|
1464
|
+
}
|
|
1465
|
+
// A unit put back that the page no longer shows (the blocks it held taken out of it): known to
|
|
1466
|
+
// the next commit, which takes it away again.
|
|
1467
|
+
for (const [key, element] of pending.made) {
|
|
1468
|
+
const id = pending.restores.has(key) ? idOf(element) : null;
|
|
1469
|
+
if (id !== null && !element.isConnected) {
|
|
1470
|
+
next.elements.set(id, element);
|
|
1471
|
+
next.types.set(id, ownTypeOf(element));
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
// One the file deleted, and the page has not put back, waits for nothing (`present`): what was given up of
|
|
1475
|
+
// it stays given up should it come back.
|
|
1476
|
+
const waiting = new Set([...heldUnits(discarded.keys()), ...[...issues.values()].filter((entry) => entry.kind === "unit").map((entry) => entry.unit), ...again].filter((id) => !removed.has(id)));
|
|
1477
|
+
for (const id of previous ? waiting : []) {
|
|
1478
|
+
const done = went.get(id) ?? new Set();
|
|
1479
|
+
for (const part of ["elements", "types", ...LOOKS]) if (!done.has(part) && previous[part]?.has(id)) next[part].set(id, previous[part].get(id));
|
|
1480
|
+
// What its component writes is none of them: it is what the commits say the file holds (`data`), which
|
|
1481
|
+
// holds what went in and not what waits, kept for it off the page too.
|
|
1482
|
+
if (!next.data.has(id) && data.has(id)) next.data.set(id, data.get(id));
|
|
1483
|
+
// Where the page shows it in its place among the units around it (no move of it: `moves`), the file has it
|
|
1484
|
+
// there too, whatever this commit moved around it.
|
|
1485
|
+
if (done.has("place") || (pending.moves && !pending.moves.has(id) && [...next.orders.values()].some((order) => order.includes(id)))) continue;
|
|
1486
|
+
// Its own place only, where the page shows it moved and no commit sends that, or not at all: out of the
|
|
1487
|
+
// boxes it was not in, and back after the unit that stood before it that this commit left where it was
|
|
1488
|
+
// (one it moved stands elsewhere in the file). The others' places are as this commit left them (taking
|
|
1489
|
+
// back a whole box undid their moves too, and sent them again, without end).
|
|
1490
|
+
for (const [box, order] of next.orders) if (order.includes(id) && !previous.orders.get(box)?.includes(id)) next.orders.set(box, order.filter((other) => other !== id));
|
|
1491
|
+
for (const [box, before] of previous.orders) {
|
|
1492
|
+
if (!before.includes(id)) continue;
|
|
1493
|
+
const order = (next.orders.get(box) ?? []).filter((other) => other !== id);
|
|
1494
|
+
const after = before.slice(0, before.indexOf(id)).reverse().find((other) => order.includes(other) && !went.get(other)?.has("place"));
|
|
1495
|
+
order.splice(after === undefined ? 0 : order.indexOf(after) + 1, 0, id);
|
|
1496
|
+
next.orders.set(box, order);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
setLayout(next);
|
|
1500
|
+
marks();
|
|
1501
|
+
return { conflicts, merged, stale, outdated: shownAgain, forced: [...landed] };
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
// A text committed: what it replaced (the first time this page committed it), what it is now, and what
|
|
1505
|
+
// the commits of it between left it as (`earlier`, the newest TEXTS_KEPT): someone writing back what
|
|
1506
|
+
// they read then puts back what the page committed after. Back as it was first, it is asked of what it
|
|
1507
|
+
// was between, as a part of what a component writes is (noteWrite): an undo is a commit like any other.
|
|
1508
|
+
function committedText(key, sent) {
|
|
1509
|
+
const mine = sources.get(sent.id);
|
|
1510
|
+
const was = journal.get(key);
|
|
1511
|
+
const original = was?.original ?? sent.before;
|
|
1512
|
+
if (mine === undefined) return void journal.delete(key);
|
|
1513
|
+
const earlier = [...new Set([...(was?.earlier ?? []), ...(was ? [was.mine] : [])])].filter((text) => text !== mine && text !== original).slice(-TEXTS_KEPT);
|
|
1514
|
+
if (mine === original && earlier.length === 0) return void journal.delete(key);
|
|
1515
|
+
journal.set(key, { key, block: blockRef(sent.id), original, mine, ...(earlier.length > 0 ? { earlier } : {}) });
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// A part of what a unit's component writes, written by a commit (`before`, `after`: the hashes the file
|
|
1519
|
+
// had of it then and has now), for the revert check: every hash it had in the file as the page wrote it
|
|
1520
|
+
// (before each of its writes, and as each left it), the newest WRITES_KEPT, but the one it has now
|
|
1521
|
+
// (undone, or written so), and that one. A commit that leaves it as the file held it (`before` is
|
|
1522
|
+
// `after`: written over what someone else wrote, as the reader said to) settles it there all the same.
|
|
1523
|
+
function noteWrite(unit, name, before, after) {
|
|
1524
|
+
if (typeof before !== "string" || typeof after !== "string") return;
|
|
1525
|
+
const entry = writes.get(unit) ?? { parts: {} };
|
|
1526
|
+
const was = entry.parts[name];
|
|
1527
|
+
const passed = [...(was?.before ?? []), ...(was ? [was.after] : []), before].filter((hash) => hash !== after);
|
|
1528
|
+
const counting = passed.filter((hash, index) => passed.lastIndexOf(hash) === index).slice(-WRITES_KEPT);
|
|
1529
|
+
if (counting.length > 0) entry.parts[name] = { before: counting, after };
|
|
1530
|
+
else delete entry.parts[name];
|
|
1531
|
+
entry.time = Date.now();
|
|
1532
|
+
if (Object.keys(entry.parts).length > 0) writes.set(unit, entry);
|
|
1533
|
+
else writes.delete(unit);
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// Parts of what a unit's component wrote that are the file's now (the reader kept the file's, or took
|
|
1537
|
+
// it in place of theirs): no longer asked of.
|
|
1538
|
+
function unwrite(unit, names) {
|
|
1539
|
+
const entry = writes.get(unit);
|
|
1540
|
+
for (const name of names) delete entry?.parts[name];
|
|
1541
|
+
if (entry && Object.keys(entry.parts).length === 0) writes.delete(unit);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
/**
|
|
1545
|
+
* The reader decided on a held conflict (key: an issueKey): "mine" commits
|
|
1546
|
+
* the reader's text over the file's (the file's becomes its `before`), "force" deletes, converts or
|
|
1547
|
+
* adjusts the unit anyway, by the next commit that carries a change of that kind, over the unit as the
|
|
1548
|
+
* card showed it, "discard" gives the change
|
|
1549
|
+
* up, that alone (never sent again: the page shows the file's once it reloads; a block made on the page is
|
|
1550
|
+
* taken off it; what a component wrote, that write; of a unit the file has no one of, all of it), "forget" drops what the page knew of
|
|
1551
|
+
* a block the file no longer has (the reader deleted it too, or made it a new block), "retry" sends
|
|
1552
|
+
* a block made on the page again where it stands now (the reader put it elsewhere), and whatever
|
|
1553
|
+
* else was held goes with the next commit, should the page ask for it again (undone, then redone).
|
|
1554
|
+
*/
|
|
1555
|
+
function resolve(key, action) {
|
|
1556
|
+
touched(); // what is held back, the baselines: collect() and the next commit anew
|
|
1557
|
+
const entry = issues.get(key);
|
|
1558
|
+
issues.delete(key);
|
|
1559
|
+
if (action === "retry" && entry?.element) {
|
|
1560
|
+
refused.delete(identity(entry.element));
|
|
1561
|
+
elsewhere.delete(identity(entry.element));
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
const { kind, ref: id } = parseIssueKey(key);
|
|
1565
|
+
if (kind === "unit" && action !== "force") forced.delete(id); // an earlier word of the reader's, refused again since, is not this one
|
|
1566
|
+
// The end of its box was tried before its place's card: a move the reader makes from now on goes where the page
|
|
1567
|
+
// has it.
|
|
1568
|
+
if (kind === "unit" && entry?.reason === "position") movedElsewhere.delete(id);
|
|
1569
|
+
if (action === "mine" && kind === "text" && typeof entry?.theirs === "string") sources.set(id, entry.theirs);
|
|
1570
|
+
else if (action === "force" && kind === "unit") {
|
|
1571
|
+
// For the change the card was about, over the unit as it showed it: what someone wrote since asks
|
|
1572
|
+
// again. What its component writes goes over each part as the reader was shown it (the refusal's
|
|
1573
|
+
// hashes of them), the word lasting until every part it held has gone in.
|
|
1574
|
+
const source = entry?.change?.source;
|
|
1575
|
+
forced.set(id, { kind: kindOf(entry?.change), ...(typeof entry?.hash === "string" ? { hash: entry.hash } : {}), ...(source ? { parts: Object.keys(source) } : {}) });
|
|
1576
|
+
if (entry?.parts && map.units[id]) map.units[id].parts = { ...map.units[id].parts, ...entry.parts };
|
|
1577
|
+
} else if (action === "discard") {
|
|
1578
|
+
discarded.set(key, kind === "unit" ? { kind: kindOf(entry?.change), ...(entry?.change ? { change: entry.change } : {}), reason: entry?.reason, words: holdsWords(entry) } : {});
|
|
1579
|
+
// The parts its component wrote are the file's, as the reader chose: what the page wrote of them before
|
|
1580
|
+
// is asked of no more.
|
|
1581
|
+
if (kind === "unit" && entry?.change?.source) unwrite(id, Object.keys(entry.change.source));
|
|
1582
|
+
} else if (action === "forget" && kind === "text") {
|
|
1583
|
+
sources.delete(id);
|
|
1584
|
+
baselines.delete(id);
|
|
1585
|
+
journal.delete(key);
|
|
1586
|
+
}
|
|
1587
|
+
marks(); // what was given up read-only from now on
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
/**
|
|
1591
|
+
* The units this load made or moved that the page still shows, as the revert check names them
|
|
1592
|
+
* (revertedUnits): [{ key, id, parent, from? }], key the page's unit.
|
|
1593
|
+
*/
|
|
1594
|
+
const placedUnits = () => [...placed].filter(([unit]) => map.units[unit]?.blockId && !removed.has(unit) && !issues.has(issueKey("unit", unit)) && unitElement(unit))
|
|
1595
|
+
.map(([unit, { parent, from }]) => ({ key: unit, id: map.units[unit].blockId, parent, ...(from !== undefined ? { from } : {}) }));
|
|
1596
|
+
|
|
1597
|
+
/**
|
|
1598
|
+
* Someone else's write put back a unit this load made (`gone`) or moved (`moved`), and the reader
|
|
1599
|
+
* keeps theirs (`mine`): one it made is a block made on the page again, written anew (around its
|
|
1600
|
+
* blocks from the source it was put back with, if it was); one it moved goes where the page has it
|
|
1601
|
+
* again. Otherwise it is no longer checked.
|
|
1602
|
+
*/
|
|
1603
|
+
function putBack(unit, { gone, mine }) {
|
|
1604
|
+
const { around } = placed.get(unit) ?? {};
|
|
1605
|
+
placed.delete(unit);
|
|
1606
|
+
if (!mine) return;
|
|
1607
|
+
touched(); // the layout changes in place
|
|
1608
|
+
if (gone) {
|
|
1609
|
+
const key = map.units[unit].cid;
|
|
1610
|
+
drop(unit);
|
|
1611
|
+
if (around && dropped.has(key)) dropped.get(key).around = around;
|
|
1612
|
+
} else for (const [box, order] of getLayout()?.orders ?? []) getLayout().orders.set(box, order.filter((id) => id !== unit));
|
|
1613
|
+
marks();
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/** The text of the blocks in a unit the page shows (the reader's words), or "". */
|
|
1617
|
+
const unitText = (unit) => {
|
|
1618
|
+
const element = unitElement(unit);
|
|
1619
|
+
return element ? blocksText(element) : "";
|
|
1620
|
+
};
|
|
1621
|
+
|
|
1622
|
+
/**
|
|
1623
|
+
* What the components of the units the page shows wrote that went in, as the revert check names them
|
|
1624
|
+
* (revertedParts): [{ key, id, parts, time }], key the page's unit, id the file's it has now; none of a
|
|
1625
|
+
* unit deleted, held for the reader or off the page, while it is.
|
|
1626
|
+
*/
|
|
1627
|
+
const written = () => [...writes].filter(([unit]) => map.units[unit]?.blockId && !removed.has(unit) && !issues.has(issueKey("unit", unit)) && unitElement(unit))
|
|
1628
|
+
.map(([unit, { parts, time }]) => ({ key: unit, id: map.units[unit].blockId, parts, time }));
|
|
1629
|
+
|
|
1630
|
+
/**
|
|
1631
|
+
* What components wrote in an earlier load of the page (written() as it kept it: [{ id, parts, time }]),
|
|
1632
|
+
* asked of as this load's own: each by the unit of the page that has its id. Entries of no such shape
|
|
1633
|
+
* are none.
|
|
1634
|
+
*/
|
|
1635
|
+
function carry(entries) {
|
|
1636
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
1637
|
+
if (typeof entry?.id !== "string" || !entry.parts || typeof entry.parts !== "object") continue;
|
|
1638
|
+
const unit = map.units.findIndex((other) => other.blockId === entry.id);
|
|
1639
|
+
if (unit < 0 || writes.has(unit) || !unitElement(unit)) continue;
|
|
1640
|
+
const parts = Object.fromEntries(Object.entries(entry.parts).filter(([, part]) => Array.isArray(part?.before) && part.before.every((hash) => typeof hash === "string") && typeof part.after === "string"));
|
|
1641
|
+
if (Object.keys(parts).length > 0) writes.set(unit, { parts, time: typeof entry.time === "number" ? entry.time : Date.now() });
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
/** What a unit's component holds on the page of the parts named, by name, or null. */
|
|
1646
|
+
const dataOfUnit = (unit, names) => {
|
|
1647
|
+
const element = unitElement(unit);
|
|
1648
|
+
const held = element ? pageOfUnit(element)?.dataOf?.(element) : null;
|
|
1649
|
+
return held ? Object.fromEntries(names.filter((name) => typeof held[name] === "string").map((name) => [name, held[name]])) : null;
|
|
1650
|
+
};
|
|
1651
|
+
|
|
1652
|
+
/**
|
|
1653
|
+
* Of `parts` (by name, a hash of each), those a unit's component holds on the page otherwise than the
|
|
1654
|
+
* file's markup of the unit (`markup`) holds them, read as the component reads one (`dataIn`): a part the
|
|
1655
|
+
* file spells otherwise than the page writes it, holding what the page shows, put back nothing the reader
|
|
1656
|
+
* would lose. All of them where that cannot be told.
|
|
1657
|
+
*/
|
|
1658
|
+
const heldOtherwise = (unit, parts, markup) => {
|
|
1659
|
+
const names = Object.keys(parts);
|
|
1660
|
+
const held = dataOfUnit(unit, names);
|
|
1661
|
+
const file = held && typeof markup === "string" ? pageOfUnit(unitElement(unit))?.dataIn?.(markup) : null;
|
|
1662
|
+
return Object.fromEntries(names.filter((name) => typeof held?.[name] !== "string" || typeof file?.[name] !== "string" || held[name] !== file[name]).map((name) => [name, parts[name]]));
|
|
1663
|
+
};
|
|
1664
|
+
|
|
1665
|
+
/**
|
|
1666
|
+
* Someone else's write put back parts of what a unit's component wrote (`parts`: by name, the hashes the
|
|
1667
|
+
* file holds of them), and the reader keeps theirs (`mine`): the next commit writes those parts as the
|
|
1668
|
+
* page holds them over what the file holds, asking should it be written again since. Otherwise they are
|
|
1669
|
+
* the file's, and no longer asked of.
|
|
1670
|
+
*/
|
|
1671
|
+
function rewrite(unit, parts, mine) {
|
|
1672
|
+
if (!mine) return void unwrite(unit, Object.keys(parts));
|
|
1673
|
+
if (!map.units[unit]) return;
|
|
1674
|
+
touched(); // the layout changes in place
|
|
1675
|
+
map.units[unit].parts = { ...map.units[unit].parts, ...parts };
|
|
1676
|
+
const data = getLayout()?.data;
|
|
1677
|
+
if (data?.has(unit)) data.set(unit, { ...data.get(unit), ...Object.fromEntries(Object.keys(parts).map((name) => [name, null])) });
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
/** The texts committed, as the revert check names them (revertedTexts): [{ key, block, original, mine, earlier? }]. */
|
|
1681
|
+
// Each named as the page knows it now (a later commit may have given its unit an id, or moved it).
|
|
1682
|
+
const committed = () => [...journal.values()].filter((entry) => !issues.has(entry.key) && sources.get(parseIssueKey(entry.key).ref) === entry.mine && map.blocks[parseIssueKey(entry.key).ref])
|
|
1683
|
+
.map((entry) => ({ ...entry, block: { ...blockRef(parseIssueKey(entry.key).ref), before: entry.mine } }));
|
|
1684
|
+
|
|
1685
|
+
/** A committed text the reader let someone else put back: no longer checked. */
|
|
1686
|
+
const unjournal = (key) => journal.delete(key);
|
|
1687
|
+
|
|
1688
|
+
/**
|
|
1689
|
+
* What the page knows, as values and the elements they name: all a page opening once this one is
|
|
1690
|
+
* gone needs to take up exactly where it was (createCommits' `restore`), `pending` being the commit
|
|
1691
|
+
* whose answer is not known. As JSON: keep().
|
|
1692
|
+
*/
|
|
1693
|
+
const save = (pending) => ({
|
|
1694
|
+
map: { version: map.version, units: map.units, blocks: map.blocks, boxes: map.boxes },
|
|
1695
|
+
openingUnits, openingBlocks, openingHashes,
|
|
1696
|
+
baselines, sources, issues, refused, removed, removedWith, discarded, unwritten, forced, elsewhere, movedElsewhere, removedElsewhere, journal, writes, placed, keyedBlocks, stood, serial,
|
|
1697
|
+
table, dropped, keyedIds, proposals,
|
|
1698
|
+
layout: getLayout(),
|
|
1699
|
+
pending,
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
// The page as it opened: every element the map knows, with its identity; the layout with the
|
|
1703
|
+
// type each unit shows as.
|
|
1704
|
+
marks();
|
|
1705
|
+
const start = getLayout();
|
|
1706
|
+
if (start?.types && start.types.size === 0) for (const [id, node] of start.elements) start.types.set(id, ownTypeOf(node));
|
|
1707
|
+
if (!restore) {
|
|
1708
|
+
for (const [box, order] of start?.orders ?? []) {
|
|
1709
|
+
for (const id of order) if (!accepts(map.boxes[box].kind, map.units[id].kind)) stood.set(id, map.boxes[box].kind);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
observer?.observe(root, { subtree: true, childList: true, characterData: true, attributes: true });
|
|
1713
|
+
|
|
1714
|
+
/**
|
|
1715
|
+
* The id `element` (a unit of the page, or a block made on it) has in the file, or is to have: the
|
|
1716
|
+
* file's, else the one the page proposes for it, which the next commit gives it (a unit of the file
|
|
1717
|
+
* without one is named by it, alone when nothing else waits); null for what is no unit.
|
|
1718
|
+
*/
|
|
1719
|
+
function idFor(element) {
|
|
1720
|
+
const unit = idOf(element);
|
|
1721
|
+
if (unit !== null) {
|
|
1722
|
+
if (map.units[unit].blockId) return map.units[unit].blockId;
|
|
1723
|
+
if (!naming.has(unit)) touched(); // the next commit names it: there is something to commit
|
|
1724
|
+
naming.add(unit);
|
|
1725
|
+
return (map.units[unit].proposed ??= randomBlockId());
|
|
1726
|
+
}
|
|
1727
|
+
return element?.hasAttribute?.("data-ge-new") ? proposedId(element) ?? null : null;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
return {
|
|
1731
|
+
build, apply, collect, current, marks, counts, buffer, texts, resolve, committed, unjournal, placedUnits, putBack, unitText, written, carry, dataOfUnit, heldOtherwise, rewrite, issues, removed, pageIds, idFor,
|
|
1732
|
+
/** The id a unit of the page has in the file, or the one it proposes for it; null for none. */
|
|
1733
|
+
fileId: (unit) => map.units[unit]?.blockId ?? map.units[unit]?.proposed ?? null,
|
|
1734
|
+
/** save(pending) as JSON, with the page's body (sync-state.mjs pack). */
|
|
1735
|
+
keep: (pending) => pack(root, save(pending)),
|
|
1736
|
+
/** Identities more than one element on the page carries (none, ever). */
|
|
1737
|
+
doubled: () => [...doubled],
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// What the file did not take is held under a key (the commits' `issues`): of a text block ("b"), of a
|
|
1742
|
+
// unit's operation ("u") or of a block made or put back on the page, by the id it proposes ("i");
|
|
1743
|
+
// the conflict's `kind` is "text", "unit" or "insert". The key is built and read only here (and
|
|
1744
|
+
// kept as it is by the crash buffer and the conflict cards).
|
|
1745
|
+
const ISSUE_PREFIX = { text: "b", unit: "u", insert: "i" };
|
|
1746
|
+
export const issueKey = (kind, ref) => `${ISSUE_PREFIX[kind]}${ref}`;
|
|
1747
|
+
/** { kind, ref } of a key: ref the block's or the unit's number, or the id (a string) of an insert. */
|
|
1748
|
+
export function parseIssueKey(key) {
|
|
1749
|
+
const kind = Object.keys(ISSUE_PREFIX).find((name) => ISSUE_PREFIX[name] === key[0]);
|
|
1750
|
+
return { kind, ref: kind === "insert" ? key.slice(1) : Number(key.slice(1)) };
|
|
1751
|
+
}
|
|
1752
|
+
// The units whose operation is held, of these keys.
|
|
1753
|
+
const heldUnits = (keys) => [...keys].map(parseIssueKey).filter(({ kind }) => kind === "unit").map(({ ref }) => ref);
|
|
1754
|
+
|
|
1755
|
+
// The kind of a unit's change (collectEdits'): the name of what it does, its one field besides the unit and
|
|
1756
|
+
// what that field takes (`on`, `value`, `definition`): "remove", "replace", "source" and the like; null for none.
|
|
1757
|
+
export const kindOf = (change) => Object.keys(change ?? {}).find((key) => !["id", "on", "value", "definition"].includes(key)) ?? null;
|
|
1758
|
+
|
|
1759
|
+
// Whether a unit's conflict (or what was given up of one) is of a unit the file has no one of: gone, or
|
|
1760
|
+
// there twice.
|
|
1761
|
+
const lacks = (entry) => entry?.reason === "missing" || entry?.reason === "ambiguous";
|
|
1762
|
+
|
|
1763
|
+
// Whether the texts in a unit wait with a change of it held back (`entry`, its conflict): where the change
|
|
1764
|
+
// carries them (a conversion the words of its block, a table's operation those of its cells, a deletion
|
|
1765
|
+
// those of the blocks it took off the page, `off`, which go or are given up with it), or where the file has
|
|
1766
|
+
// no one unit to write them to (they would be refused the same). A move, a look, a deletion of blocks on the
|
|
1767
|
+
// page again (undone since) or what a component writes holds none of them.
|
|
1768
|
+
const holdsWords = (entry, off = false) => !entry?.change || Boolean(entry.change.replace || entry.change.table || (entry.change.remove && off)) || lacks(entry);
|
|
1769
|
+
|
|
1770
|
+
// What a layout holds of a unit's look and of its table's rows and cells (core.mjs captureLayout): what a
|
|
1771
|
+
// unit held back keeps, and what one deleted whole takes along and is put back with.
|
|
1772
|
+
const LOOKS = ["toggles", "sizes", "attributes", "tables"];
|
|
1773
|
+
|
|
1774
|
+
// What of a layout a unit's change is about: a conversion its element and type, and all its looks, which
|
|
1775
|
+
// the element it writes carries (a list given the other tag those two alone: its switches go as changes of
|
|
1776
|
+
// their own), a move its place ("place"), a look or a table's operation that part (LOOKS, each named for the
|
|
1777
|
+
// field of the change it holds: "sizes" a `size`'s); a deletion or what its component writes none of them.
|
|
1778
|
+
const layoutOf = (change) => (change?.replace ? ["elements", "types", ...LOOKS] : change?.retag ? ["elements", "types"] : change?.move ? ["place"] : LOOKS.filter((part) => change?.[part.slice(0, -1)] !== undefined));
|
|
1779
|
+
|
|
1780
|
+
// What an operation of a commit carries in the reader's words, where the file did not take it: the
|
|
1781
|
+
// text of a unit turned into another type, what a component wrote; null for any other.
|
|
1782
|
+
export const writtenHTML = (op) => op?.replace?.html ?? (op?.source ? Object.values(op.source).join("\n") : null);
|
|
1783
|
+
|
|
1784
|
+
// What a block made on the page holds, as a commit carries it: its text, its title and those of the
|
|
1785
|
+
// blocks in it, one a line (the reader's words, when the file did not take it).
|
|
1786
|
+
export const nodeHTML = (node) => [node?.title, node?.html, node?.restore?.source, ...(node?.children ?? []).map(nodeHTML)].filter(Boolean).join("\n");
|
|
1787
|
+
|
|
1788
|
+
// Where a text block of the page sits in its unit, as the server's scan writes it (pathIn): the
|
|
1789
|
+
// element steps from the unit down, each [tag, index among the parent's children of that tag, how
|
|
1790
|
+
// many there are]. Only to find the element a path of the file names (blockElement), never to say where a block is.
|
|
1791
|
+
function pathFrom(unit, host) {
|
|
1792
|
+
const steps = [];
|
|
1793
|
+
for (let node = host; node && node !== unit; node = node.parentElement) {
|
|
1794
|
+
const same = [...(node.parentElement?.children ?? [])].filter((child) => child.localName === node.localName);
|
|
1795
|
+
steps.unshift([node.localName, same.indexOf(node), same.length]);
|
|
1796
|
+
}
|
|
1797
|
+
return ["unit", ...steps];
|
|
1798
|
+
}
|