@heycys/gallery 0.0.0-stage → 0.1.1
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 +3303 -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 +64 -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 +310 -0
- package/browser/editor/components/details/index.mjs +73 -0
- package/browser/editor/components/details/page.mjs +86 -0
- package/browser/editor/components/dialog/index.mjs +84 -0
- package/browser/editor/components/dialog/page.mjs +201 -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 +42 -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 +53 -0
- package/browser/editor/components/heading/page.mjs +6 -0
- package/browser/editor/components/highlight/index.mjs +63 -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 +48 -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 +179 -0
- package/browser/editor/components/installed/page.mjs +523 -0
- package/browser/editor/components/installed/served.mjs +35 -0
- package/browser/editor/components/kit.mjs +67 -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 +192 -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 +859 -0
- package/browser/editor/components/rows.mjs +16 -0
- package/browser/editor/components/section/index.mjs +42 -0
- package/browser/editor/components/section/page.mjs +141 -0
- package/browser/editor/components/set-apart.mjs +24 -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 +308 -0
- package/browser/editor/components/table/page.mjs +683 -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 +230 -0
- package/browser/editor/page/annotate/card.mjs +102 -0
- package/browser/editor/page/annotate/index.mjs +1656 -0
- package/browser/editor/page/at-menu/index.mjs +317 -0
- package/browser/editor/page/autosave.mjs +1023 -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 +84 -0
- package/browser/editor/page/block-menu/index.mjs +217 -0
- package/browser/editor/page/blocks.mjs +255 -0
- package/browser/editor/page/caret.mjs +682 -0
- package/browser/editor/page/changes/bar.mjs +102 -0
- package/browser/editor/page/changes/ghost.mjs +206 -0
- package/browser/editor/page/changes/gone-words.mjs +94 -0
- package/browser/editor/page/changes/index.mjs +447 -0
- package/browser/editor/page/changes/words.mjs +27 -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 +816 -0
- package/browser/editor/page/clipboard/own/blocks.mjs +211 -0
- package/browser/editor/page/clipboard/own/chart-keys.mjs +58 -0
- package/browser/editor/page/clipboard/own/describe.mjs +124 -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 +315 -0
- package/browser/editor/page/columns/index.mjs +360 -0
- package/browser/editor/page/commit.mjs +1813 -0
- package/browser/editor/page/conflicts.mjs +249 -0
- package/browser/editor/page/convert.mjs +831 -0
- package/browser/editor/page/core.mjs +1693 -0
- package/browser/editor/page/drag/drop.mjs +307 -0
- package/browser/editor/page/drag/index.mjs +351 -0
- package/browser/editor/page/drag/preview.mjs +126 -0
- package/browser/editor/page/edge-scroll.mjs +50 -0
- package/browser/editor/page/editor.mjs +1780 -0
- package/browser/editor/page/emoji-picker/emoji-tab.mjs +287 -0
- package/browser/editor/page/emoji-picker/glyph-html.mjs +26 -0
- package/browser/editor/page/emoji-picker/grid.mjs +81 -0
- package/browser/editor/page/emoji-picker/icon-tab.mjs +217 -0
- package/browser/editor/page/emoji-picker/icons.mjs +31 -0
- package/browser/editor/page/emoji-picker/index.mjs +166 -0
- package/browser/editor/page/emoji-picker/styles.mjs +161 -0
- package/browser/editor/page/emoji-picker/support.mjs +204 -0
- package/browser/editor/page/emoji-picker/upload.mjs +236 -0
- package/browser/editor/page/export-report.mjs +23 -0
- package/browser/editor/page/fills.mjs +62 -0
- package/browser/editor/page/find/index.mjs +107 -0
- package/browser/editor/page/flags.mjs +70 -0
- package/browser/editor/page/fold/index.mjs +348 -0
- package/browser/editor/page/fold/rule.mjs +125 -0
- package/browser/editor/page/folded.mjs +23 -0
- package/browser/editor/page/format-bar/bar.mjs +449 -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 +402 -0
- package/browser/editor/page/gesture.mjs +45 -0
- package/browser/editor/page/handle/index.mjs +650 -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 +256 -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/long-runs.mjs +75 -0
- package/browser/editor/page/look.mjs +77 -0
- package/browser/editor/page/made.mjs +24 -0
- package/browser/editor/page/margin.mjs +373 -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 +544 -0
- package/browser/editor/page/merge.mjs +124 -0
- package/browser/editor/page/notices.mjs +142 -0
- package/browser/editor/page/offered.mjs +32 -0
- package/browser/editor/page/outline/folds.mjs +53 -0
- package/browser/editor/page/outline/index.mjs +783 -0
- package/browser/editor/page/outline/list.mjs +184 -0
- package/browser/editor/page/outline/read.mjs +201 -0
- package/browser/editor/page/outline/strip.mjs +287 -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 +65 -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 +61 -0
- package/browser/editor/page/selection/index.mjs +164 -0
- package/browser/editor/page/selection/marquee.mjs +174 -0
- package/browser/editor/page/session.mjs +168 -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 +141 -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 +990 -0
- package/browser/editor/page/table/lists.mjs +187 -0
- package/browser/editor/page/table/widths.mjs +313 -0
- package/browser/editor/page/tex.mjs +32 -0
- package/browser/editor/page/text-select/index.mjs +671 -0
- package/browser/editor/page/tooltip.mjs +98 -0
- package/browser/editor/page/type-grid.mjs +83 -0
- package/browser/editor/page/typing/code-edges.mjs +274 -0
- package/browser/editor/page/typing/index.mjs +282 -0
- package/browser/editor/page/typing/leave.mjs +68 -0
- package/browser/editor/page/ui.mjs +160 -0
- package/browser/shared/annotations.mjs +569 -0
- package/browser/shared/code-folds.mjs +14 -0
- package/browser/shared/code-languages.mjs +70 -0
- package/browser/shared/code-lines.mjs +30 -0
- package/browser/shared/colors.mjs +30 -0
- package/browser/shared/component-catalog.mjs +573 -0
- package/browser/shared/components/bookmark.mjs +26 -0
- package/browser/shared/components/callout.mjs +9 -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 +396 -0
- package/browser/shared/components/installed.mjs +232 -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 +46 -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/document-look.mjs +178 -0
- package/browser/shared/emoji/custom.mjs +91 -0
- package/browser/shared/emoji/data/LICENSE-lucide.txt +43 -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 +28 -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 +194 -0
- package/browser/shared/emoji/emoji.mjs +38 -0
- package/browser/shared/emoji/glyphs.mjs +134 -0
- package/browser/shared/emoji/icon-value.mjs +91 -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 +59 -0
- package/browser/shared/http.mjs +22 -0
- package/browser/shared/i18n/en.mjs +1223 -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/long-runs.mjs +39 -0
- package/browser/shared/media.mjs +162 -0
- package/browser/shared/new-names.mjs +67 -0
- package/browser/shared/number-reading.mjs +92 -0
- package/browser/shared/old-names.mjs +143 -0
- package/browser/shared/outline.mjs +194 -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 +116 -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 +77 -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-look.mjs +91 -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-342HXE3Q.mjs +2 -0
- package/dist/chunk-3HIRPVLU.mjs +2 -0
- package/dist/chunk-43B75J5T.mjs +2 -0
- package/dist/chunk-4FS7Z7WR.mjs +2 -0
- package/dist/chunk-4K5KC6V2.mjs +2 -0
- package/dist/chunk-5L7RYZCL.mjs +2 -0
- package/dist/chunk-5MHMDLZD.mjs +4 -0
- package/dist/chunk-5ZKJ3H4P.mjs +8 -0
- package/dist/chunk-64K7IPLO.mjs +255 -0
- package/dist/chunk-6QXGZM36.mjs +8 -0
- package/dist/chunk-6RGJOMJP.mjs +16 -0
- package/dist/chunk-742D2WWJ.mjs +19 -0
- package/dist/chunk-7DTINW6U.mjs +48 -0
- package/dist/chunk-A55HN5OK.mjs +2 -0
- package/dist/chunk-AGDILNWB.mjs +2 -0
- package/dist/chunk-AJ73XJF3.mjs +2 -0
- package/dist/chunk-AUZGSZJH.mjs +13 -0
- package/dist/chunk-BFE45AKL.mjs +2 -0
- package/dist/chunk-BGGDRNYQ.mjs +10 -0
- package/dist/chunk-BXO2ZY5P.mjs +2 -0
- package/dist/chunk-C564NU2H.mjs +77 -0
- package/dist/chunk-CEH4MRCC.mjs +2 -0
- package/dist/chunk-CY2FNNAB.mjs +16 -0
- package/dist/chunk-DFUDV4LX.mjs +2 -0
- package/dist/chunk-DKXG4XDG.mjs +2 -0
- package/dist/chunk-E7LWZRXY.mjs +4 -0
- package/dist/chunk-EZQ34M3F.mjs +124 -0
- package/dist/chunk-EZY3WC6Z.mjs +15 -0
- package/dist/chunk-FDLXYYG4.mjs +4 -0
- package/dist/chunk-FUKQNOQS.mjs +9 -0
- package/dist/chunk-GB5SOYQJ.mjs +7 -0
- package/dist/chunk-GRAXKKG4.mjs +2 -0
- package/dist/chunk-HYMEEXKN.mjs +4 -0
- package/dist/chunk-IVDLMMBD.mjs +2 -0
- package/dist/chunk-JPOSLGOC.mjs +6 -0
- package/dist/chunk-JVHHHGEA.mjs +3 -0
- package/dist/chunk-L3QKH4OK.mjs +26 -0
- package/dist/chunk-LAE5NQ4B.mjs +2 -0
- package/dist/chunk-LCTHTZIL.mjs +960 -0
- package/dist/chunk-LEF6DRCK.mjs +59 -0
- package/dist/chunk-LKX55H76.mjs +2 -0
- package/dist/chunk-LS4F7JUM.mjs +2 -0
- package/dist/chunk-MAFIGVXY.mjs +2 -0
- package/dist/chunk-NO5NJCHO.mjs +2 -0
- package/dist/chunk-NQPVAU63.mjs +2 -0
- package/dist/chunk-NSKUTQ5J.mjs +5 -0
- package/dist/chunk-O35FIXRB.mjs +2 -0
- package/dist/chunk-P2CAZPDP.mjs +3 -0
- package/dist/chunk-PL6GHLGD.mjs +15 -0
- package/dist/chunk-QAIHRH3I.mjs +13 -0
- package/dist/chunk-QJIR2BLP.mjs +6 -0
- package/dist/chunk-QR6A54KJ.mjs +3 -0
- package/dist/chunk-QUPLVPB2.mjs +7 -0
- package/dist/chunk-R7BNENLY.mjs +4 -0
- package/dist/chunk-S6O4UHLC.mjs +2 -0
- package/dist/chunk-SGJWQKEI.mjs +2 -0
- package/dist/chunk-SYGN7GLU.mjs +2 -0
- package/dist/chunk-TMYZB6AV.mjs +2 -0
- package/dist/chunk-TX6JH7TR.mjs +2 -0
- package/dist/chunk-TYK6VNAB.mjs +10 -0
- package/dist/chunk-U3SSPL4K.mjs +8 -0
- package/dist/chunk-U6KNPJPR.mjs +2 -0
- package/dist/chunk-UALRNVSE.mjs +4 -0
- package/dist/chunk-UH4SIXTR.mjs +2 -0
- package/dist/chunk-UMHUSOPJ.mjs +2 -0
- package/dist/chunk-V3HXVQEM.mjs +2 -0
- package/dist/chunk-V5JTW2LH.mjs +27 -0
- package/dist/chunk-V5VXUJKG.mjs +2 -0
- package/dist/chunk-V6XZ4VIR.mjs +2 -0
- package/dist/chunk-VBXMADMP.mjs +2 -0
- package/dist/chunk-VNV55RUA.mjs +2 -0
- package/dist/chunk-VPD2PS35.mjs +2 -0
- package/dist/chunk-W3BGBJQW.mjs +2 -0
- package/dist/chunk-W4VKN3GF.mjs +7 -0
- package/dist/chunk-W5BXXPDJ.mjs +3 -0
- package/dist/chunk-W7XHE4CT.mjs +2 -0
- package/dist/chunk-XNROSH2T.mjs +4 -0
- package/dist/chunk-XUNHRCOG.mjs +6 -0
- package/dist/chunk-Y34NR5JL.mjs +2 -0
- package/dist/chunk-Y6RLRPG3.mjs +3 -0
- package/dist/chunk-YEY3RSFQ.mjs +2 -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 +2004 -0
- package/kit/report.css +2932 -0
- package/kit/runtime.js +1782 -0
- package/kit/sheets.js +123 -0
- package/kit/state.js +217 -0
- package/kit/tables.js +1499 -0
- package/kit/tokens.js +29 -0
- package/kit/ui.js +658 -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 +147 -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 +150 -0
- package/skill/agents/openai.yaml +5 -0
- package/skill/references/building-components.md +367 -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 +147 -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-Bh74DfsV.css +1 -0
- package/web/assets/index-UW0RyFFt.js +13 -0
- package/web/index.html +14 -0
|
@@ -0,0 +1,1780 @@
|
|
|
1
|
+
// First: what the browser keeps under the names before 2026-10-28 carried to the new ones (the
|
|
2
|
+
// editor's gallery-edit:… keys, src/shared/renamed-storage.mjs), before any module below reads them.
|
|
3
|
+
import "../../shared/renamed-storage.mjs";
|
|
4
|
+
import { OUTER, can, captureLayout, editableAhead, firstHost, isHost, limitText, markFrames, markKinds, matchesSource, newHost, newMention, ownHTML, plainText, prepareBlock, splitBlock, textEnd, tidyEmptied, unitElement, withLeads } from "./core.mjs";
|
|
5
|
+
import { createBlocks } from "./blocks.mjs";
|
|
6
|
+
import { hiddenByFold, reveal } from "./folded.mjs";
|
|
7
|
+
import { createFlags } from "./flags.mjs";
|
|
8
|
+
import { createFrame } from "./frame.mjs";
|
|
9
|
+
import { createHistory } from "./history.mjs";
|
|
10
|
+
import { editable, hostOf, select, selectAt, caretAt, caretAtOffset, atEdge, endArrowRun, arrow, besideLine, chipsPress, followPress, outOfAtom, showCaret, followEnd, selectionContext, textNear } from "./caret.mjs";
|
|
11
|
+
import { closePanel, messageKind, noticeNode, panelItem, panelNode, relayTo, say, settle, showPanel } from "./notices.mjs";
|
|
12
|
+
import { failure, postAnswer } from "./post.mjs";
|
|
13
|
+
import { exportReport } from "./export-report.mjs";
|
|
14
|
+
import { createCommits, kindOf } from "./commit.mjs";
|
|
15
|
+
import { createConflicts, packageChanged, unfit } from "./conflicts.mjs";
|
|
16
|
+
import { checkLeft, createAutosave, keepWith, lostKey, pendingKey, readLost, recover, reloadView, setReloader, takeView, viewNow } from "./autosave.mjs";
|
|
17
|
+
import { SOLO, connectWorkbench } from "./session.mjs";
|
|
18
|
+
import { lookOfElement } from "../../shared/document-look.mjs";
|
|
19
|
+
import { belowTargets, convertBlock, convertGroup, groupParents, groupTargets, noTextToCarry, targetsOf, touchedBy, turnInto, turnParents, turnUnit, typeOf } from "./convert.mjs";
|
|
20
|
+
import { joinCode, rangeAt, rangeOffsets } from "./marks.mjs";
|
|
21
|
+
import { addStyle, button, copyAddress, element, layer } from "./ui.mjs";
|
|
22
|
+
import { createClipboard } from "./clipboard/index.mjs";
|
|
23
|
+
import { createFormatting } from "./format-bar/index.mjs";
|
|
24
|
+
import { connectHints, hideHint, readOnlyReason, scheduleHint, trackPointer } from "./hints/index.mjs";
|
|
25
|
+
import { createInsert } from "./insert/index.mjs";
|
|
26
|
+
import { connectLinks, dismissLink, drawPasted, followLink, leadsToPage, linkBoxNode, linkHolds, linkNodes, linkOf, linkPending, openLink, pageLinkOf } from "./links/index.mjs";
|
|
27
|
+
import { bookmarkItems, connectViews, linkTitle, offerViews } from "./links/views.mjs";
|
|
28
|
+
import { connectChipFills } from "./chip-fills.mjs";
|
|
29
|
+
import { connectFormula, dismissFormula, formulaBoxNode, loadKatex, openFormula } from "./formula/index.mjs";
|
|
30
|
+
import { settlePads } from "./pads.mjs";
|
|
31
|
+
import { followLongRuns } from "./long-runs.mjs";
|
|
32
|
+
import { beforeDelete, codeArrow, codeClick, codePress, deletedBack, deletingBack, settleCodeEdge, takeEntry } from "./typing/code-edges.mjs";
|
|
33
|
+
import { createMarkdown } from "./markdown/index.mjs";
|
|
34
|
+
import { createResize } from "./resize/index.mjs";
|
|
35
|
+
import { connectPages, fieldOpen, keptByComponent, menuRow, modalOps, pageAt, pageIcon, pageObjects, pageOf, pageOfHost, pageOfType, pageOfUnit, panelOpen, typedAsIs } from "./page-objects.mjs";
|
|
36
|
+
import { preloading } from "./type-grid.mjs";
|
|
37
|
+
import { createColumnGaps } from "./columns/index.mjs";
|
|
38
|
+
import { createTableEditing } from "./table/index.mjs";
|
|
39
|
+
import { createAtMenu } from "./at-menu/index.mjs";
|
|
40
|
+
import { createSlashMenu } from "./slash-menu/index.mjs";
|
|
41
|
+
import { createTyping } from "./typing/index.mjs";
|
|
42
|
+
import { createLeaving } from "./typing/leave.mjs";
|
|
43
|
+
import { createTextSelection } from "./text-select/index.mjs";
|
|
44
|
+
import { createSelectAll, topStep } from "./select-all/index.mjs";
|
|
45
|
+
import { revealText } from "./find/index.mjs";
|
|
46
|
+
import { createOutline } from "./outline/index.mjs";
|
|
47
|
+
import { createAnnotate } from "./annotate/index.mjs";
|
|
48
|
+
import { createChanges as createAgentChanges } from "./changes/index.mjs";
|
|
49
|
+
import { BLOCK_TYPES, HOSTS, PLAIN_TEXT, TYPE_INFO, accepts, blankSaysOf, boxOwner, texOnly, textFilters } from "../components/registry.mjs";
|
|
50
|
+
import { createPages } from "../components/pages.mjs";
|
|
51
|
+
import { openMediaPicker } from "./emoji-picker/index.mjs";
|
|
52
|
+
import { connectPicker, popupOpen } from "./emoji-picker/support.mjs";
|
|
53
|
+
import { menuOpen } from "./menu.mjs";
|
|
54
|
+
import { DEFAULT_TAB_ICON, iconUrl } from "../../shared/emoji/icon-value.mjs";
|
|
55
|
+
import { mediaUrl } from "../../shared/media.mjs";
|
|
56
|
+
import { decoded, fullAddress, isFileLink } from "../../shared/page-address.mjs";
|
|
57
|
+
import { goBackTo, placeIn, unmoved } from "../../shared/reading-place.mjs";
|
|
58
|
+
import { heldList, holdPages } from "./links/pages.mjs";
|
|
59
|
+
import { hideBlocks } from "./offered.mjs";
|
|
60
|
+
import { local, storageOf } from "./storage.mjs";
|
|
61
|
+
import { language, t } from "../../shared/i18n/index.mjs";
|
|
62
|
+
|
|
63
|
+
// In-place editing for the live preview (`npm run dev:local` only; never exported).
|
|
64
|
+
// Each editable block is its own contenteditable host, so a selection never spans blocks and
|
|
65
|
+
// every block maps to one source range (a list item's text; the lists it holds are blocks of their
|
|
66
|
+
// own, never part of its content). Plain typing and IME composition stay with the browser.
|
|
67
|
+
//
|
|
68
|
+
// This module starts the editor and routes the page's events to the editing tools, each in a
|
|
69
|
+
// folder of its own (docs/editor.md, "Where the editing tools live"):
|
|
70
|
+
//
|
|
71
|
+
// typing/ Enter, Backspace/Delete at block edges, auto spacing, placeholders
|
|
72
|
+
// insert/ new blocks: below a block (its menu), a click below the last block, a paragraph before or after
|
|
73
|
+
// format-bar/ the bar over selected text and its keys (marks.mjs does the marks)
|
|
74
|
+
// links/ the link box (Ctrl+K), following links; hints/: why something is read-only
|
|
75
|
+
// formula/ the formula box: selected text made an inline formula (Ctrl+Shift+E)
|
|
76
|
+
// slash-menu/ the "/" menu; at-menu/: the "@" menu; markdown/: Markdown shortcuts as the reader types
|
|
77
|
+
// clipboard/ copying, cutting and pasting blocks and text
|
|
78
|
+
// resize/ dragging the edges of cards, tables and code blocks
|
|
79
|
+
// columns/ the gaps between columns (分栏) and between a flowchart's nodes in a row: resizing the two beside one, a column added at its pin
|
|
80
|
+
// table/ a table's rows and columns: their grips and menus, + to put one in, the bars that add them
|
|
81
|
+
// select-all/ Ctrl+A: the block's text, then its container's blocks, one level a press, then the page
|
|
82
|
+
// outline/ 目录: the page's headings at its right (the room there is margin.mjs's)
|
|
83
|
+
// annotate/ 批注: started from the format bar, a block's button or Ctrl+Alt+M; cards in the margin, with no room markers in the gutter and the card by the words
|
|
84
|
+
// changes/ the Agent's changes (设置 › 显示变更): what an Agent wrote since the reader last kept or reverted it, marked; the bar at the bottom
|
|
85
|
+
// blocks.mjs the block tools: handle/, block-menu/, selection/, drag/
|
|
86
|
+
//
|
|
87
|
+
// and the components' own editing (src/editor/components/<name>/page.mjs). Undo and redo are
|
|
88
|
+
// history.mjs; turning blocks into other types goes through convert.mjs (convertHost, convertUnit
|
|
89
|
+
// below). A report's page is always editing and saves by itself (autosave.mjs, commit.mjs).
|
|
90
|
+
|
|
91
|
+
const mapElement = document.getElementById("doc-edit-map");
|
|
92
|
+
const MAP = JSON.parse(mapElement.textContent);
|
|
93
|
+
mapElement.remove();
|
|
94
|
+
|
|
95
|
+
const keys = { tab: "gallery-edit:tab" };
|
|
96
|
+
const units = new Map((MAP.units ?? []).map((unit) => [unit.id, unit]));
|
|
97
|
+
const boxes = new Map((MAP.boxes ?? []).map((box) => [box.id, box]));
|
|
98
|
+
const baselines = new Map();
|
|
99
|
+
let layout = null; // the page's units as the last commit left them (captureLayout, commit.mjs)
|
|
100
|
+
let blocks = null; // block handles, selection and deleting (blocks.mjs)
|
|
101
|
+
let resize = null; // resizing cards and tables (resize/)
|
|
102
|
+
let gaps = null; // the gaps between columns (columns/)
|
|
103
|
+
let tables = null; // a table's rows and columns (table/)
|
|
104
|
+
let formatting = null; // the bar over selected text (format-bar/)
|
|
105
|
+
let slash = null; // the "/" menu (slash-menu/)
|
|
106
|
+
let atMenu = null; // the "@" menu (at-menu/)
|
|
107
|
+
let markdown = null; // Markdown shortcuts as the reader types (markdown/)
|
|
108
|
+
let selectAll = null; // Ctrl+A widening step by step (select-all/)
|
|
109
|
+
let outline = null; // the page's headings at its right (outline/), not on a page opened on its own (?solo)
|
|
110
|
+
let annotate = null; // 批注 (annotate/), on every page of a session
|
|
111
|
+
let agentChanges = null; // what an Agent changed, under review (changes/), on every page of a session
|
|
112
|
+
let editing = false;
|
|
113
|
+
let composing = false; // an input method is composing
|
|
114
|
+
// A composition ends with compositionend, which Chrome does not always send (a paste over the text
|
|
115
|
+
// being composed): a key, an input, a press or a paste that is no part of one ends it too, so that
|
|
116
|
+
// saving and the format bar never wait on a composition that is over.
|
|
117
|
+
const compositionOver = (event) => {
|
|
118
|
+
if (composing && !event.isComposing && event.keyCode !== 229) composing = false;
|
|
119
|
+
};
|
|
120
|
+
let pressing = false; // the main button is down on the page (a click or a drag under way)
|
|
121
|
+
let pressedText = null; // the text block a press of the main button came down in (onPointerDown), for the click that ends it
|
|
122
|
+
let commits = null; // the page's commits (commit.mjs)
|
|
123
|
+
let autosave = null; // when they go (autosave.mjs)
|
|
124
|
+
let conflicts = null; // what the file refused, for the reader to decide (conflicts.mjs)
|
|
125
|
+
let unsaveable = { blank: 0, unplaced: 0 }; // blocks the page cannot save as they are
|
|
126
|
+
let notice = null; // { text, time }: what a commit just said, to say again if the page reloads by itself
|
|
127
|
+
|
|
128
|
+
const session = storageOf(() => sessionStorage);
|
|
129
|
+
|
|
130
|
+
// This tab, kept over reloads (sessionStorage). Each tab keeps what waits to be saved (the crash
|
|
131
|
+
// buffer, autosave.mjs) under its own id, so two tabs of a report never overwrite each other's; the
|
|
132
|
+
// open tabs answer with their ids (a BroadcastChannel), so a page only sends what tabs that are gone
|
|
133
|
+
// left there.
|
|
134
|
+
const newTab = () => Math.random().toString(36).slice(2, 10);
|
|
135
|
+
let TAB = session.get(keys.tab) ?? newTab();
|
|
136
|
+
session.set(keys.tab, TAB);
|
|
137
|
+
const channel = (() => { try { return new BroadcastChannel(`gallery-edit:${MAP.slug}`); } catch { return null; } })();
|
|
138
|
+
channel?.addEventListener("message", (event) => {
|
|
139
|
+
if (event.data?.type === "who") channel.postMessage({ type: "here", tab: TAB });
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
function openTabs(wait = 150) {
|
|
143
|
+
if (!channel) return Promise.resolve(new Set());
|
|
144
|
+
const open = new Set();
|
|
145
|
+
const listen = (event) => { if (event.data?.type === "here") open.add(event.data.tab); };
|
|
146
|
+
channel.addEventListener("message", listen);
|
|
147
|
+
channel.postMessage({ type: "who" });
|
|
148
|
+
return new Promise((resolve) => setTimeout(() => {
|
|
149
|
+
channel.removeEventListener("message", listen);
|
|
150
|
+
resolve(open);
|
|
151
|
+
}, wait));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const STYLE = `
|
|
155
|
+
/* A frame shows only around what is being edited: the text with the caret, or the component
|
|
156
|
+
whose text it is, drawn in the editor's layer (frame.mjs) with the corners given here, never
|
|
157
|
+
as the element's own outline. Selected blocks get a fill instead (selection/). */
|
|
158
|
+
/* Editable text has a text block's corners and, framed, a light fill under the pointer, unless its
|
|
159
|
+
component declares others: --ge-text-radius, and --ge-text-tint (none: plain, as Notion's and
|
|
160
|
+
Feishu's; a cell, a code block and its title, a disclosure's title, a media block's
|
|
161
|
+
caption, a card's title and caption). */
|
|
162
|
+
[data-ge-editing] [contenteditable="true"] { border-radius: var(--ge-text-radius, 3px); }
|
|
163
|
+
[data-ge-editing] [contenteditable="true"]:focus { outline: none; }
|
|
164
|
+
[data-ge-editing] [data-ge-round="box"] { border-radius: 8px; }
|
|
165
|
+
[data-ge-editing] [data-ge-round="text"] { border-radius: 3px; } /* as a text block's */
|
|
166
|
+
/* Text inside a component: the component carries the frame, the text lights up under the pointer. */
|
|
167
|
+
[data-ge-editing] [data-ge-framed][contenteditable="true"]:is(:hover, :focus) { box-shadow: var(--ge-text-tint, inset 0 0 0 100vmax color-mix(in srgb, var(--doc-accent, #2678d8) 9%, transparent)); }
|
|
168
|
+
/* An inline element of the report's own kept whole (data-ge-own) looks as the report draws it. */
|
|
169
|
+
[data-ge-editing] [data-ge-atom]:not([data-ge-own]) { border-radius: 4px; }
|
|
170
|
+
/* A chip of the text is one character to a selection (Notion's user-select: all): a drag that reaches it takes all of it. */
|
|
171
|
+
[data-ge-editing] [data-ge-atom] { user-select: all; }
|
|
172
|
+
/* An empty block under the caret says what it takes; out of the flow, so the caret stays at its start. */
|
|
173
|
+
[data-ge-editing] [data-ge-placeholder]::before { content: attr(data-ge-placeholder); position: absolute; color: var(--doc-muted, #6e6e73); opacity: .7; white-space: nowrap; pointer-events: none; }
|
|
174
|
+
[data-ge-editing] :is(h1, h2, h3, h4)[data-ge-placeholder]::before { opacity: .35; }
|
|
175
|
+
/* 输入以筛选… while the handle's "+" menu is open (typing/ updatePlaceholder): on Notion's gray of its temporary input (notion-lite's slash-menu.js decorate: --ca-graBacPriTra, a 5.5px outline of it, the tertiary text). */
|
|
176
|
+
[data-ge-editing] [data-ge-adding][data-ge-placeholder]::before { color: var(--ge-tx3); opacity: 1; background: var(--ge-field); border-radius: .5px; outline: 5.5px solid var(--ge-field); }
|
|
177
|
+
/* An empty block one is typing in is a line high, yet Chrome lets the margin above it pass
|
|
178
|
+
through it as through an empty box, and counts it again below it: a new paragraph made a
|
|
179
|
+
quote's bar, a panel, a paragraph gap longer until it got a character. A trace of padding
|
|
180
|
+
keeps the margin where it is (a minimum height does not). It weighs nothing: a text with
|
|
181
|
+
padding of its own keeps it (a new code block's empty title lost its 8px below, standing 4px
|
|
182
|
+
off its fold arrow until a character came). */
|
|
183
|
+
:where([data-ge-editing]) :where([contenteditable="true"]:empty) { padding-bottom: .1px; }
|
|
184
|
+
`;
|
|
185
|
+
|
|
186
|
+
// ---------- state ----------
|
|
187
|
+
|
|
188
|
+
// What the editor says when the preview server cannot be reached.
|
|
189
|
+
const OFFLINE = t("连不上 Gallery 的服务(它还在运行吗?gallery open 会启动它)");
|
|
190
|
+
|
|
191
|
+
// Where saving stands: what the workbench session (session.mjs) reports and data-ge-save says.
|
|
192
|
+
const IDLE_STATE = { waiting: 0, syncing: false, offline: false, stopped: null, foreign: false, reloading: false, held: 0, drafts: 0, unstored: false };
|
|
193
|
+
// A change made in the last moment, not counted yet (checkChanges), waits too.
|
|
194
|
+
const view = () => {
|
|
195
|
+
const state = autosave?.state ?? IDLE_STATE;
|
|
196
|
+
return {
|
|
197
|
+
editing, ...state, waiting: state.waiting + (checkPending ? 1 : 0), ...unsaveable,
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// The editor's state changed: data-ge-save, the workbench session, and a word when commits stop
|
|
202
|
+
// getting through or get through again. While editing, the preview's live reload leaves
|
|
203
|
+
// reloading to the page (autosave.mjs: once nothing waits).
|
|
204
|
+
let lastState = "saved";
|
|
205
|
+
let reconnecting = false; // back from offline, what it held on its way: said to be saved once it is
|
|
206
|
+
function refresh() {
|
|
207
|
+
window.__galleryEditBusy = editing;
|
|
208
|
+
const state = saveState(view());
|
|
209
|
+
document.documentElement.setAttribute("data-ge-save", state);
|
|
210
|
+
document.documentElement.toggleAttribute("data-ge-drafts", view().drafts > 0); // blocks not finished, not written (tests read it)
|
|
211
|
+
if (autosave) {
|
|
212
|
+
if (state === "offline" && lastState !== "offline") say(t("{0}:修改暂存在浏览器里,连上后自动保存。", OFFLINE), "error");
|
|
213
|
+
else if (state === "saved" && reconnecting) say(t("已重新连上,修改已保存。"));
|
|
214
|
+
reconnecting = state !== "held" && state !== "saved" && (reconnecting || state === "offline");
|
|
215
|
+
lastState = state;
|
|
216
|
+
}
|
|
217
|
+
workbench?.update();
|
|
218
|
+
showTabIcon();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// The browser tab shows the page's icon (the server's link in the head, render/emoji.mjs), changed
|
|
222
|
+
// with it: a new icon, one taken away, an undo. A page with none shows the default document icon
|
|
223
|
+
// (DEFAULT_TAB_ICON: a tab whose link is taken away keeps the image it showed), its link the
|
|
224
|
+
// preview's own (data-doc-dev: an export of the view leaves it out, as the file has no icon).
|
|
225
|
+
let tabIcon;
|
|
226
|
+
function showTabIcon() {
|
|
227
|
+
const value = pageIcon();
|
|
228
|
+
if (value === tabIcon) return;
|
|
229
|
+
tabIcon = value;
|
|
230
|
+
let link = document.head.querySelector("link[data-doc-tab-icon]");
|
|
231
|
+
if (!link) {
|
|
232
|
+
link = element("link", { rel: "icon", "data-doc-tab-icon": "" });
|
|
233
|
+
document.head.append(link);
|
|
234
|
+
}
|
|
235
|
+
link.toggleAttribute("data-doc-dev", !value);
|
|
236
|
+
const href = value ? iconUrl(value, MAP.slug, { tab: true }) : DEFAULT_TAB_ICON;
|
|
237
|
+
if (link.getAttribute("href") !== href) link.setAttribute("href", href);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Where saving stands, in one word (on the page's root as data-ge-save, for styles and tests).
|
|
241
|
+
const saveState = ({ waiting, syncing, offline, stopped, reloading, held }) => stopped ? "stopped" : reloading ? "reloading" : offline ? "offline"
|
|
242
|
+
: syncing ? "syncing" : waiting > 0 ? "waiting" : held > 0 ? "held" : "saved";
|
|
243
|
+
|
|
244
|
+
// ---------- undo, the components, typing ----------
|
|
245
|
+
|
|
246
|
+
const history = createHistory({
|
|
247
|
+
changed: markChanged, beforeUndo: () => closeTyped(), blocks: () => blocks, show: showLanded, tidy: () => [...pageObjects().flatMap((page) => page.tidy ?? []), tidyEmptied],
|
|
248
|
+
afterText: (element) => pageOf(element)?.afterText?.(element), // what follows a text step undone or redone
|
|
249
|
+
refuse: () => say(t("这里合并进了 AI 同时写的字,撤销会把它们一起撤掉:没有撤销,可以直接改。"), "error"),
|
|
250
|
+
});
|
|
251
|
+
const { lastKnown, recordText, recordStep, sourceStep, closeTyping, struct, finish: finishStep, lookStep, undo, redo } = history;
|
|
252
|
+
|
|
253
|
+
// Where undo or redo landed shown: the Sheet panels out of sight around it, from the outside in (a
|
|
254
|
+
// click on a panel's tab switches to it), and the detail card it is in.
|
|
255
|
+
function showLanded(node) {
|
|
256
|
+
const panels = [];
|
|
257
|
+
for (let at = node?.nodeType === 1 ? node : node?.parentElement; at; at = at.parentElement) if (at.hidden) panels.unshift(at);
|
|
258
|
+
panels.forEach((panel) => boxOwner(panel)?.click());
|
|
259
|
+
modalOps().show(node);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const insert = createInsert({ struct, blocks: () => blocks });
|
|
263
|
+
const { newParagraph } = insert;
|
|
264
|
+
|
|
265
|
+
// What a component does on the page beyond what every block does is its own page module
|
|
266
|
+
// (src/editor/components/<name>/page.mjs, listed in components/pages.mjs); the editor calls it where
|
|
267
|
+
// that happens (Enter, Backspace, Tab, typing, a press, undo) and gives them all one `ctx`.
|
|
268
|
+
|
|
269
|
+
// The icon picker's images go into the report's folder (editor/media-store.mjs), where the server serves them.
|
|
270
|
+
const assetUrl = (file) => mediaUrl("asset", MAP.slug, file);
|
|
271
|
+
connectPicker({ slug: MAP.slug, token: MAP.token, notify: say });
|
|
272
|
+
// Whether a block tool may act: the page edits, no reload is on its way and no field of a component is being edited.
|
|
273
|
+
const active = () => editing && !autosave?.state.reloading && !fieldOpen();
|
|
274
|
+
// A picture or file sent to the report's folder (kind: "image", "file" or "embed"): the server's answer.
|
|
275
|
+
async function uploadTo(kind, body) {
|
|
276
|
+
const answer = await post("/__gallery/edit/upload", { slug: MAP.slug, kind, ...body });
|
|
277
|
+
if (!answer.ok) throw new Error(failure(answer, t("上传失败")));
|
|
278
|
+
return answer;
|
|
279
|
+
}
|
|
280
|
+
// A web page as the server reads it (parts: "all" for a bookmark, "icon" or "title" for a link's
|
|
281
|
+
// views: links/views.mjs), its images kept in the report's folder.
|
|
282
|
+
async function readPage(url, parts = "all") {
|
|
283
|
+
const answer = await post("/__gallery/edit/unfurl", { slug: MAP.slug, url, parts });
|
|
284
|
+
if (!answer.ok) throw new Error(failure(answer, t("打不开这个网页")));
|
|
285
|
+
return answer;
|
|
286
|
+
}
|
|
287
|
+
// The one `ctx` of the components' page modules, each taking what it uses of it (docs/editor.md,
|
|
288
|
+
// "Component definitions"); createPages adds `pages`, what they have of each other. What is made
|
|
289
|
+
// only in boot() (the block tools, the format bar, the "/" menu) is asked for when it is used.
|
|
290
|
+
connectPages(createPages({
|
|
291
|
+
// the page's file and undo: the edit map, the baselines, the last recorded content of each block, an undo step of each kind
|
|
292
|
+
map: MAP, baselines, lastKnown, recordText, closeTyping, struct, finishStep, look: lookStep, sourceStep,
|
|
293
|
+
// the editor's state, and what it does for a component
|
|
294
|
+
say, isEditing: () => editing, isComposing: () => composing, active, closeTools,
|
|
295
|
+
hideFormatBar: () => formatting.hide(),
|
|
296
|
+
// text blocks and their types
|
|
297
|
+
limitText, caretAtOffset, selectionContext, typeOf, targetsOf, convertHost, newParagraph,
|
|
298
|
+
// the block tools, and a table's tools (table/); a key that is the "/" or "@" menu's, standing on text a component takes keys of (their key())
|
|
299
|
+
blocks: () => blocks, tables: () => tables,
|
|
300
|
+
typedKey,
|
|
301
|
+
// the report's folder: pictures, files, embedded files and a bookmark's images go there (editor/media-store.mjs)
|
|
302
|
+
assetUrl, upload: uploadTo,
|
|
303
|
+
fileUrl: (path) => mediaUrl("file", MAP.slug, path),
|
|
304
|
+
embedUrl: (path) => mediaUrl("embed", MAP.slug, path),
|
|
305
|
+
async importEmoji(library) {
|
|
306
|
+
const answer = await post("/__gallery/edit/upload", { slug: MAP.slug, library });
|
|
307
|
+
if (!answer.ok) throw new Error(failure(answer, t("无法使用这个自定义表情")));
|
|
308
|
+
return answer.path;
|
|
309
|
+
},
|
|
310
|
+
// the web, through the server: a bookmark's page, whether a page lets frames show it
|
|
311
|
+
unfurl: (url) => readPage(url),
|
|
312
|
+
frameable: (url) => post("/__gallery/edit/frameable", { slug: MAP.slug, url }),
|
|
313
|
+
// tools of the page's: the icon picker, a link's 显示为 › rows (links/views.mjs), KaTeX
|
|
314
|
+
openMediaPicker, showAs: (figure, link) => bookmarkItems(figure, link), loadKatex,
|
|
315
|
+
})); // what the engines ask of a component (page-objects.mjs)
|
|
316
|
+
|
|
317
|
+
const typing = createTyping({
|
|
318
|
+
struct, say, keepText: (node) => blocks.keepText(node), paragraphBefore: insert.paragraphBefore, removeBlocks: (units) => blocks.remove(units), selectBlock: (unit) => blocks.select(unit),
|
|
319
|
+
adding: () => slash?.adding ?? null,
|
|
320
|
+
leave: createLeaving({ struct, touchedBy, convertHost, typeOf, targetsOf }),
|
|
321
|
+
showHidden: (node) => blocks?.folds.showAround(node), // a folded heading's section opened before a join takes in its words (fold/)
|
|
322
|
+
});
|
|
323
|
+
// Selecting text across blocks: its listeners go before the editor's own keys and clipboard.
|
|
324
|
+
const crossText = createTextSelection({
|
|
325
|
+
isEditing: () => editing, struct, say, keepText: (node) => blocks.keepText(node), selectUnits: (list) => blocks.select(list), markUnits: (list) => blocks.mark(list), split: (host) => typing.split(host),
|
|
326
|
+
copy: (data, range, pieces, cut) => clipboard.copyAcross(data, range, pieces, cut), // its blocks, each of its type (clipboard/)
|
|
327
|
+
claims: (from, to) => tables?.claims(from, to) ?? false, // a drag between cells of a table selects them (table/)
|
|
328
|
+
selectAll: (event, touched) => selectAll?.across(event, touched) ?? false, // Ctrl+A: the first container holding its blocks (select-all/)
|
|
329
|
+
// The format bar: shown for it, its keys and buttons formatting it (format-bar/).
|
|
330
|
+
keeps: (node) => formatting?.holds(node) ?? false, formatKey: (event) => formatting?.key(event) ?? false, changed: () => formatting?.schedule(),
|
|
331
|
+
});
|
|
332
|
+
// A key that is the "/" or "@" menu's, standing on what is typed: it comes before anything of the text's.
|
|
333
|
+
function typedKey(event) {
|
|
334
|
+
return slash.key(event) || atMenu.key(event);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// What stands on what is being typed goes, the text staying: the "/" menu and the "@" menu, which
|
|
338
|
+
// hold commits back while they stand.
|
|
339
|
+
function closeTyped() {
|
|
340
|
+
slash?.close();
|
|
341
|
+
atMenu?.close();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// The box of a formula in the text closes, and what stands open over the page as a component's editor
|
|
345
|
+
// (`panel`), what is in them kept: commits wait while one stands.
|
|
346
|
+
function keepPanels() {
|
|
347
|
+
if (formulaBoxNode()) dismissFormula();
|
|
348
|
+
panelOpen()?.dismiss();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// What a press that edits something closes first: what opens over the page.
|
|
352
|
+
function closeTools() {
|
|
353
|
+
formatting.hide();
|
|
354
|
+
closeTyped();
|
|
355
|
+
blocks.closeMenu();
|
|
356
|
+
if (linkBoxNode()) dismissLink();
|
|
357
|
+
keepPanels();
|
|
358
|
+
}
|
|
359
|
+
const clipboard = createClipboard({
|
|
360
|
+
editing: () => editing, blocks: () => blocks, struct, recordText, say,
|
|
361
|
+
loadKatex,
|
|
362
|
+
// A link shown with its icon pasted: its icon kept where it is a file of this report's folder
|
|
363
|
+
// (served from it, or a copy of this report's own blocks), else a globe; a page block as it is, its
|
|
364
|
+
// address good in any document, but for one to a block of the page it was copied from, and shown
|
|
365
|
+
// as its document is now.
|
|
366
|
+
mention(parts, same, from) {
|
|
367
|
+
const chip = newMention(document, { href: parts.href.startsWith("#") && !same && from ? `../${from}/doc.html${parts.href}` : parts.href, title: isFileLink(parts.href) ? parts.title : linkTitle(parts.title, parts.href), icon: (parts.slug ? parts.slug === MAP.slug : same) ? parts.icon : null, drawn: parts.drawn }, assetUrl);
|
|
368
|
+
drawPasted(chip);
|
|
369
|
+
return chip;
|
|
370
|
+
},
|
|
371
|
+
linkPasted: (link, page) => offerViews(link, { pasted: true, page }),
|
|
372
|
+
pageLink: pageLinkOf,
|
|
373
|
+
titleBold: () => formatting?.armed ?? false, // a node's empty title: what is pasted there bold, as typed
|
|
374
|
+
showCaret,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// ---------- editing ----------
|
|
378
|
+
|
|
379
|
+
// The page edits from the start: every block prepared, the layout and the commits set up.
|
|
380
|
+
function enter() {
|
|
381
|
+
if (editing) return;
|
|
382
|
+
// Two passes: every block made editable (or not) as the page was served, then every block rebuilt
|
|
383
|
+
// (core.mjs editableAhead: a contenteditable set after blocks were rebuilt lays the page out again).
|
|
384
|
+
const nodes = new Map();
|
|
385
|
+
for (const block of MAP.blocks) {
|
|
386
|
+
if (HOSTS[block.kind]?.closed) continue;
|
|
387
|
+
const node = document.querySelector(`[data-ge-block="${block.id}"]`);
|
|
388
|
+
if (!node) continue;
|
|
389
|
+
if (HOSTS[block.kind]?.tex) node.setAttribute("contenteditable", "true"); // a display formula (components/math/)
|
|
390
|
+
else if (!matchesSource(node, block.inner)) {
|
|
391
|
+
node.setAttribute("data-ge-lock", "script");
|
|
392
|
+
continue;
|
|
393
|
+
} else editableAhead(node, block);
|
|
394
|
+
nodes.set(block, node);
|
|
395
|
+
}
|
|
396
|
+
const hosts = [];
|
|
397
|
+
// The blocks rebuilt from the file look as the page drew them: the kit's components hear it
|
|
398
|
+
// quietly (DocKit.quietly: its tables lay themselves out again once the page is idle, not in
|
|
399
|
+
// the frame editing starts in, which the outline's first drawing and a workbench's ready wait for).
|
|
400
|
+
const quietly = globalThis.DocKit?.quietly ?? ((rebuild) => rebuild());
|
|
401
|
+
quietly(() => {
|
|
402
|
+
for (const block of MAP.blocks) {
|
|
403
|
+
if (HOSTS[block.kind]?.closed) {
|
|
404
|
+
pageOfHost(block.kind).prepare(block);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
const node = nodes.get(block);
|
|
408
|
+
if (!node) continue;
|
|
409
|
+
if (HOSTS[block.kind]?.tex) { // a display formula: the kit's drawing as its chip (components/math/)
|
|
410
|
+
baselines.set(block.id, pageOfHost(block.kind).prepare(block, node));
|
|
411
|
+
lastKnown.set(node, ownHTML(node));
|
|
412
|
+
hosts.push(node);
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
baselines.set(block.id, prepareBlock(node, block));
|
|
416
|
+
const host = node.hasAttribute("data-ge-block") ? node : node.firstElementChild; // a Sheet tab's name span
|
|
417
|
+
lastKnown.set(host, ownHTML(host));
|
|
418
|
+
hosts.push(host);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
layout = captureLayout(document, MAP);
|
|
422
|
+
markFrames(document, layout, hosts);
|
|
423
|
+
// A text block holding a long run (a path, a URL) breaks anywhere, judged now and as its text changes (long-runs.mjs).
|
|
424
|
+
followLongRuns(document.querySelector("main") ?? document.body);
|
|
425
|
+
commits = createCommits({ root: document, map: MAP, baselines, units, boxes, getLayout: () => layout, setLayout: (next) => (layout = next) });
|
|
426
|
+
// Commits wait while an input method composes, a formula is being edited (formula/), the "/" menu
|
|
427
|
+
// or the "@" menu stands on what is typed for it (half of it is no text of the file's) or a gap
|
|
428
|
+
// between a flowchart's nodes is dragged (columns/: the row is shown at the weights being dragged,
|
|
429
|
+
// written on its nodes, until letting go makes them one undo step or Esc puts them back); the page
|
|
430
|
+
// is not shown again (someone else's write) while the pointer is down, a 批注 is being written
|
|
431
|
+
// (annotate/) or 仍然调整 has just written over someone else's change, its undo the reader's to take
|
|
432
|
+
// (keepsOverwrite), unless the page asked to be shown again itself.
|
|
433
|
+
autosave = createAutosave({
|
|
434
|
+
map: MAP, commits, blocked: () => composing || formulaBoxNode() !== null || panelOpen() !== null || linkPending() || Boolean(gaps?.dragging || slash?.host || atMenu?.host), changedAt: () => lastChange, holding: (asked) => pressing || Boolean(annotate?.writing()) || keepsOverwrite(asked), refresh, applied: committed, failed: refused, storage: local, tab: () => TAB,
|
|
435
|
+
before: () => annotate?.sync() ?? null, // where the words of 批注 went with the edit, told before it is written (annotate/)
|
|
436
|
+
warn: () => say(t("浏览器存储写不进去(空间满了,或被禁用):没保存的修改只在这个页面上,先不要关掉它。"), "error"),
|
|
437
|
+
notice: () => notice && Date.now() - notice.time < 10000 ? notice.text : null,
|
|
438
|
+
session,
|
|
439
|
+
reverted: (entries) => {
|
|
440
|
+
conflicts.add(entries.map((entry) => ({ ...entry, key: `r:${entry.key}`, revert: entry.key, kind: entry.written ? "reverted-source" : entry.unit === undefined ? "reverted" : "reverted-unit" })));
|
|
441
|
+
say(t("AI 的写入把你之前保存的 {0} 处修改改回去了:在左下方选择保留哪一边。", entries.length), "error");
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
conflicts = createConflicts({ act: decide, nodeOf: conflictNode, restorable, copyBlock: (node) => clipboard.copyBlocks([node]), say });
|
|
445
|
+
editing = true;
|
|
446
|
+
document.documentElement.setAttribute("data-ge-editing", "");
|
|
447
|
+
blocks?.folds.start(); // what was folded, folded again in this task: no frame shows it open (fold/)
|
|
448
|
+
refresh();
|
|
449
|
+
for (const page of pageObjects()) page.entered?.();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// What the page says when what a component wrote went by a manifest that changed: given up, the page shown again.
|
|
453
|
+
const outdatedNotice = () => t("组件包刚刚变了,这次改动没有保存,页面已按新的组件重新显示。");
|
|
454
|
+
|
|
455
|
+
// What 仍然调整 wrote over someone else's change of what a component writes, by page unit: the step that
|
|
456
|
+
// gives theirs back (installed/ overwrite), and when its write went in, all of it (commit.mjs `forced`).
|
|
457
|
+
// Refused again (someone wrote it again since the reader was shown it), the step stands for nothing and
|
|
458
|
+
// goes; gone in, no undo goes below it (history fold), and the page is not shown again for someone else's
|
|
459
|
+
// write before the reader's next change (that undo among them) or KEEP_OVERWRITE: shown again, the page has
|
|
460
|
+
// no undo steps, and theirs would be gone from it. A showing the page asks for itself (保留 AI 的, a
|
|
461
|
+
// package changed) is not held: `asked`.
|
|
462
|
+
const overwrites = new Map(); // unit -> { step, landed }
|
|
463
|
+
const KEEP_OVERWRITE = 30000;
|
|
464
|
+
const keepsOverwrite = (asked) => !asked && [...overwrites.values()].some(({ landed }) => landed > lastChange && Date.now() - landed < KEEP_OVERWRITE);
|
|
465
|
+
|
|
466
|
+
// A commit was taken in (commit.mjs): a block merged with someone else's words shows the merge;
|
|
467
|
+
// what the file refused is flagged and held back, the rest goes on.
|
|
468
|
+
function committed({ conflicts: held, merged, outdated, forced = [] }, receipt) {
|
|
469
|
+
agentChanges?.committed(receipt);
|
|
470
|
+
for (const host of merged) history.merged(host);
|
|
471
|
+
for (const unit of forced) {
|
|
472
|
+
const written = overwrites.get(unit);
|
|
473
|
+
if (!written || written.landed > 0) continue;
|
|
474
|
+
history.fold(written.step);
|
|
475
|
+
written.landed = Date.now();
|
|
476
|
+
}
|
|
477
|
+
for (const entry of held) {
|
|
478
|
+
const written = entry.kind === "unit" && entry.again && entry.change?.source ? overwrites.get(entry.unit) : null; // that write, refused again
|
|
479
|
+
if (!written || written.landed > 0) continue;
|
|
480
|
+
history.drop(written.step);
|
|
481
|
+
overwrites.delete(entry.unit);
|
|
482
|
+
}
|
|
483
|
+
if (outdated) autosave.reloadSoon(); // what a component wrote by a manifest that changed: the page goes by the new one, shown again
|
|
484
|
+
if (held.length > 0) {
|
|
485
|
+
conflicts.add(held);
|
|
486
|
+
const conflicted = held.filter((entry) => !unfit(entry) && !packageChanged(entry)).length;
|
|
487
|
+
const packaged = held.filter(packageChanged).length;
|
|
488
|
+
say(conflicted > 0 ? t("{0} 处修改和文件的最新版本冲突,没有保存:在左下方选择怎么处理。", conflicted) : packaged > 0 ? t("组件包刚刚变了,{0} 处修改没有保存:在左下方选择怎么处理。", packaged) : t("{0} 处内容文档存不下,没有保存:在左下方选择怎么处理。", held.length), "error");
|
|
489
|
+
}
|
|
490
|
+
else if (outdated || merged.length > 0 || receipt.texts?.some((entry) => entry.status === "merged")) {
|
|
491
|
+
const count = receipt.texts.filter((entry) => entry.status === "merged").length;
|
|
492
|
+
notice = { text: outdated ? outdatedNotice() : t("已自动合并 {0} 段 AI 同时做的修改。", count), time: Date.now() };
|
|
493
|
+
say(notice.text, "note", 5000);
|
|
494
|
+
}
|
|
495
|
+
// What the server says besides (service.mjs retitle, reicon: "标题已更新", what did not follow it),
|
|
496
|
+
// each note in the UI's language, as one sentence.
|
|
497
|
+
else if (receipt.notes?.length) say(t("{0}。", receipt.notes.map((note) => t(note)).join(t(";"))), "note", receipt.notes.length > 1 ? 6000 : undefined);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ---------- conflicts ----------
|
|
501
|
+
|
|
502
|
+
// The block a conflict is about, on the page.
|
|
503
|
+
function conflictNode(entry) {
|
|
504
|
+
if (entry.kind === "text") return document.querySelector(`[data-ge-block="${entry.block}"]`);
|
|
505
|
+
if (entry.kind === "unit" || entry.kind === "reverted-unit" || entry.kind === "reverted-source") return document.querySelector(`[data-ge-unit="${entry.unit}"]`);
|
|
506
|
+
if (entry.kind === "insert") return entry.element?.isConnected ? entry.element : null;
|
|
507
|
+
// Someone else put a committed text back: found by what names it (it may be from an earlier load).
|
|
508
|
+
if (entry.block?.title) return document.querySelector('[data-ge-kind="title"]');
|
|
509
|
+
const unit = entry.block?.unit?.id ? document.querySelector(`[data-block-id="${CSS.escape(entry.block.unit.id)}"]`) : null;
|
|
510
|
+
return entry.block?.self ? unit : null;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Whether a key types or edits text: a character or an input method's (229), Enter, Backspace, Delete
|
|
514
|
+
// or Tab, with no Ctrl, Alt or ⌘.
|
|
515
|
+
const EDIT_KEYS = new Set(["Enter", "Backspace", "Delete", "Tab"]);
|
|
516
|
+
const editsText = (event) => !event.ctrlKey && !event.metaKey && !event.altKey && (event.key.length === 1 || event.keyCode === 229 || EDIT_KEYS.has(event.key));
|
|
517
|
+
|
|
518
|
+
// A text the file no longer has can be made again as a new block when it is a plain one.
|
|
519
|
+
// The keys that only modify others: pressed alone, nothing is typed.
|
|
520
|
+
const MODIFIER_KEYS = new Set(["Alt", "AltGraph", "Control", "Shift", "Meta", "OS", "CapsLock", "Fn"]);
|
|
521
|
+
|
|
522
|
+
function restorable(entry) {
|
|
523
|
+
const host = entry.kind === "text" && entry.reason === "missing" ? conflictNode(entry) : null;
|
|
524
|
+
return Boolean(host && MAP.blocks[entry.block]?.self && PLAIN_TEXT.has(typeOf(host)));
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Whether a held entry is about nothing the page still changes: a block made on the page that is off
|
|
528
|
+
// it, a text or a block's change the file cannot hold, which the page no longer asks for, or a block's
|
|
529
|
+
// change refused for someone else's change of it (`changed`) when the page makes no change of that kind
|
|
530
|
+
// any more (the deletion undone, the conversion undone or the block deleted since): what the reader does
|
|
531
|
+
// to it then goes, or asks, as any change does. What a component wrote stays on its card through undo and
|
|
532
|
+
// redo, as the reader's version of the block.
|
|
533
|
+
function undoneRefusal(entry, { edits, units }) {
|
|
534
|
+
if (entry.kind === "insert") return Boolean(entry.element) && !entry.element.isConnected;
|
|
535
|
+
if (entry.kind === "unit" && entry.reason === "changed" && !entry.change?.source) return !units.some((change) => change.id === entry.unit && kindOf(change) === kindOf(entry.change));
|
|
536
|
+
if (!unfit(entry)) return false;
|
|
537
|
+
return entry.kind === "text" ? !edits.some((edit) => edit.id === entry.block) : entry.kind === "unit" && !units.some((change) => change.id === entry.unit);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** Do what the reader chose for a conflict (conflicts.mjs); true when it is settled. */
|
|
541
|
+
async function decide(entry, action) {
|
|
542
|
+
if (entry.kind.startsWith("reverted")) return autosave.decide(entry.revert, action === "mine");
|
|
543
|
+
const key = entry.key; // as commit.mjs holds it: "b<block>", "u<unit>" or "i<id>"
|
|
544
|
+
const node = conflictNode(entry);
|
|
545
|
+
if (action === "mine" || action === "force") {
|
|
546
|
+
commits.resolve(key, action);
|
|
547
|
+
// Written over what someone else wrote of what its component writes (`theirs`, by part, as the reader
|
|
548
|
+
// was shown it): one undo gives the file's back.
|
|
549
|
+
const step = action === "force" && entry.theirs && typeof entry.theirs === "object" && node ? pageOfUnit(node)?.overwrite?.(node, entry.theirs) : null;
|
|
550
|
+
if (step) overwrites.set(entry.unit, { step, landed: 0 });
|
|
551
|
+
} else if (action === "theirs") {
|
|
552
|
+
commits.resolve(key, "discard"); // the page shows the file's once nothing else waits
|
|
553
|
+
autosave.reloadSoon();
|
|
554
|
+
return true;
|
|
555
|
+
} else if (action === "accept") { // the file deleted it: so does the page
|
|
556
|
+
const unit = entry.kind === "text" ? node?.closest("[data-ge-unit]") : node;
|
|
557
|
+
unit && withLeads(unit).forEach((part) => part.remove());
|
|
558
|
+
commits.resolve(key, entry.kind === "text" ? "forget" : "discard");
|
|
559
|
+
} else if (action === "restore") { // made again, as a new block where it was
|
|
560
|
+
const next = newHost(document, typeOf(node), node);
|
|
561
|
+
next.innerHTML = ownHTML(node);
|
|
562
|
+
node.replaceWith(next);
|
|
563
|
+
commits.resolve(key, "forget");
|
|
564
|
+
} else if (action === "undo" && entry.element) { // the block made on the page goes
|
|
565
|
+
withLeads(entry.element).forEach((part) => part.remove());
|
|
566
|
+
commits.resolve(key, "discard");
|
|
567
|
+
} else if (action === "nearby" && entry.element?.isConnected) {
|
|
568
|
+
// Where the file surely takes it: right after the block it stands in, in the page's own flow,
|
|
569
|
+
// or at the end of that flow when it stands there already. An undoable move; then written anew.
|
|
570
|
+
const block = entry.element;
|
|
571
|
+
const flow = block.closest("main");
|
|
572
|
+
let top = block;
|
|
573
|
+
while (top.parentElement && top.parentElement !== flow) top = top.parentElement;
|
|
574
|
+
if (!flow || !top.parentElement) return false;
|
|
575
|
+
const moved = struct([block.parentElement, flow], [], () => {
|
|
576
|
+
if (top === block) flow.append(block);
|
|
577
|
+
else top.after(block);
|
|
578
|
+
return { touched: [], focus: null, select: block };
|
|
579
|
+
});
|
|
580
|
+
if (!moved) return false;
|
|
581
|
+
commits.resolve(key, "retry");
|
|
582
|
+
} else return false;
|
|
583
|
+
markChanged(true);
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// A commit the server refuses whatever the page does: saving stops, and the page says why (the
|
|
588
|
+
// server's message, a whole one, in the UI's language).
|
|
589
|
+
function refused(reason, message) {
|
|
590
|
+
if (reason === "token" || reason === "origin" || reason === "host") {
|
|
591
|
+
say(t("预览服务器不认得这个页面了(重启过?),正在刷新;没保存的修改会自动放回。"), "prompt");
|
|
592
|
+
autosave.store();
|
|
593
|
+
setTimeout(() => reloadView(MAP.slug), 600);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
say(reason === "missing" ? t("这份文档的文件不见了(被移动或删除了?)。修改暂存在浏览器里,没有保存。") : t("没有保存:{0}", message ? t(message) : reason), "error");
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// ---------- change tracking ----------
|
|
600
|
+
|
|
601
|
+
// Comparing every block with its baseline is cheap but not free, so batch bursts of input.
|
|
602
|
+
// A timer, not requestAnimationFrame: animation frames pause while the page is not painted.
|
|
603
|
+
// `now`: a discrete change (not typing), committed without waiting for a pause.
|
|
604
|
+
let checkPending = false;
|
|
605
|
+
let discrete = false;
|
|
606
|
+
let lastChange = 0; // when the reader last changed anything (the page reloads by itself only in a pause)
|
|
607
|
+
function markChanged(now = false) {
|
|
608
|
+
if (now) {
|
|
609
|
+
// A structural change (undo among them): blocks it brought back are marked from what the file
|
|
610
|
+
// has, and a conflict's flag is on the block it is about now.
|
|
611
|
+
commits?.marks();
|
|
612
|
+
conflicts?.reflag();
|
|
613
|
+
}
|
|
614
|
+
if (messageKind() === "error") settle(); // the reader is dealing with it
|
|
615
|
+
lastChange = Date.now();
|
|
616
|
+
discrete ||= now;
|
|
617
|
+
const counting = checkPending;
|
|
618
|
+
checkPending = true; // counts as waiting until counted
|
|
619
|
+
refresh();
|
|
620
|
+
if (!counting) setTimeout(checkChanges, 60);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Also run at once when the page goes away, so the last keystrokes are counted.
|
|
624
|
+
function checkChanges() {
|
|
625
|
+
if (!checkPending || !autosave) return;
|
|
626
|
+
checkPending = false;
|
|
627
|
+
const now = discrete;
|
|
628
|
+
discrete = false;
|
|
629
|
+
document.querySelectorAll('[data-ge-flag="check"]').forEach((node) => node.removeAttribute("data-ge-flag"));
|
|
630
|
+
const collected = commits.collect();
|
|
631
|
+
// What the file refused and the reader has undone: nothing is held for it any more.
|
|
632
|
+
const lapsed = conflicts.drop((entry) => undoneRefusal(entry, collected));
|
|
633
|
+
lapsed.forEach((entry) => commits.resolve(entry.key, "retry"));
|
|
634
|
+
if (lapsed.length > 0 && messageKind() === "error") settle();
|
|
635
|
+
// A new block with no place, or one that must keep some text: left out until the reader fixes it.
|
|
636
|
+
collected.unplaced.forEach((node) => node.setAttribute("data-ge-flag", "check"));
|
|
637
|
+
const before = unsaveable;
|
|
638
|
+
unsaveable = { blank: collected.blank.length, unplaced: collected.unplaced.length };
|
|
639
|
+
if (unsaveable.unplaced > before.unplaced) say(t("红框里的新段落所在的位置放不下段落,没有保存:请并入相邻段落,或拖到别处。"), "error");
|
|
640
|
+
else if (unsaveable.blank > before.blank && !messageKind()) {
|
|
641
|
+
say(collected.blank.map(blankSaysOf).find(Boolean) ?? t("这里还空着:写上内容后才会保存。")); // what its component says
|
|
642
|
+
}
|
|
643
|
+
autosave.changed(now, collected);
|
|
644
|
+
refresh();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// ---------- block types ----------
|
|
648
|
+
// Turning blocks into other types (convert.mjs) as undo steps: from the "/" menu (slash-menu/),
|
|
649
|
+
// Markdown typed at a block's start (markdown/), the block menu (block-menu/) and the components.
|
|
650
|
+
|
|
651
|
+
// Turn a text block into another type as one undo step; the caret goes to the start of the block
|
|
652
|
+
// that holds its text now. `typed`: a range of what the reader typed to ask for it ("/…", "# "),
|
|
653
|
+
// taken away first (the block it leaves empty stands for a new highlight block or disclosure:
|
|
654
|
+
// convertBlock's `fresh`).
|
|
655
|
+
// `after(focus)`: what else the same undo step does to the converted block (a list made by "3. "
|
|
656
|
+
// beginning at 3), returning the containers it made.
|
|
657
|
+
function convertHost(host, type, typed = null, after = null) {
|
|
658
|
+
const result = struct(touchedBy(host), [host], () => {
|
|
659
|
+
typed?.deleteContents();
|
|
660
|
+
const { focus, created } = convertBlock(host, type, { fresh: typed !== null });
|
|
661
|
+
if (!focus && !typed) return null;
|
|
662
|
+
const more = focus && after ? after(focus) : [];
|
|
663
|
+
return { touched: [focus ?? host], focus: focus ?? host, created: [...created, ...more] };
|
|
664
|
+
});
|
|
665
|
+
if (result) afterConvert(result.focus, type, result.focus !== host ? t("已转换为{0}", menuRow(type).label) : null);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// What the component of a type does with a new block of it (`onCreated`, registry.mjs): true when
|
|
669
|
+
// what comes next is its own.
|
|
670
|
+
const onCreated = (type, block) => pageOfType(type)?.onCreated?.(block, type);
|
|
671
|
+
|
|
672
|
+
// What a block turned or made into a type needs next: its component's (a media block's picture,
|
|
673
|
+
// file, link or page chosen, a formula block's formula typed), else the caret goes in it, on the
|
|
674
|
+
// words it came with where those are a sample, which typing takes the place of (`sample`); `said`,
|
|
675
|
+
// what the status line says of it.
|
|
676
|
+
function afterConvert(focus, type, said) {
|
|
677
|
+
if (onCreated(type, focus)) return;
|
|
678
|
+
caretAt(focus, "start");
|
|
679
|
+
if (menuRow(type)?.sample && isHost(focus)) {
|
|
680
|
+
const words = document.createRange();
|
|
681
|
+
words.selectNodeContents(focus);
|
|
682
|
+
select(words);
|
|
683
|
+
}
|
|
684
|
+
if (said) say(t("{0} · Ctrl+Z 撤销", said));
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* A block of `type` below `target` (a text block or any other block), as the "/" menu makes one in a
|
|
689
|
+
* block with text and the block menu's 在下方新建 (Notion's): the typed "/…" (`typed`) taken away; a
|
|
690
|
+
* text block splits at its end (as Enter there would: an item after an item, a paragraph after a
|
|
691
|
+
* heading) and the new block turns into the type; below any other block, an empty paragraph of its
|
|
692
|
+
* box does (the first box out that takes one). One undo step.
|
|
693
|
+
*/
|
|
694
|
+
function insertBelow(target, type, typed = null) {
|
|
695
|
+
const splits = isHost(target) && can(target, "split");
|
|
696
|
+
let after = target;
|
|
697
|
+
while (!splits && after && !accepts(after.parentElement?.getAttribute("data-ge-box-kind"), "paragraph")) after = after.parentElement?.closest(OUTER);
|
|
698
|
+
if (!splits && !after) return say(t("这里不能新建块。"));
|
|
699
|
+
const result = struct(splits ? touchedBy(target) : [after.parentElement], splits ? [target] : [], () => {
|
|
700
|
+
typed?.deleteContents();
|
|
701
|
+
let fresh;
|
|
702
|
+
if (splits) {
|
|
703
|
+
const end = document.createRange();
|
|
704
|
+
end.selectNodeContents(target);
|
|
705
|
+
end.collapse(false);
|
|
706
|
+
fresh = splitBlock(target, end);
|
|
707
|
+
} else {
|
|
708
|
+
fresh = newHost(document, "paragraph");
|
|
709
|
+
after.after(fresh);
|
|
710
|
+
}
|
|
711
|
+
if (!fresh) return null;
|
|
712
|
+
const turned = type === "paragraph" && fresh.localName === "p" ? { focus: null, created: [] } : convertBlock(fresh, type, { fresh: true });
|
|
713
|
+
const focus = turned.focus ?? fresh;
|
|
714
|
+
return { touched: splits ? [target, focus] : [focus], focus, created: turned.created };
|
|
715
|
+
});
|
|
716
|
+
if (result) afterConvert(result.focus, type, t("已插入{0}", menuRow(type).label));
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// The block handle's "+" (handle/; notion-lite's hover menu, 398193): an empty block at the block's
|
|
720
|
+
// level below it (`where` "above": Alt, before it), the caret in it and the "/" menu open there
|
|
721
|
+
// unfiltered (slash-menu/ add: what is typed then filters it, as after a "/"), one undo step taking
|
|
722
|
+
// the block away again. A flowchart's node gets a node beside it (flowchart/ addAfter: its title the
|
|
723
|
+
// menu's, whose blocks go below the title, as a "/" typed there), any other block an empty paragraph
|
|
724
|
+
// or list item (insert/ addNear).
|
|
725
|
+
function addNear(unit, where) {
|
|
726
|
+
const above = where === "above";
|
|
727
|
+
const own = pageOfUnit(unit)?.addNear?.(unit, above);
|
|
728
|
+
const host = own === undefined ? insert.addNear(unit, above ? "above" : "below") : own;
|
|
729
|
+
if (!host) return say(t("这里不能新建块。"));
|
|
730
|
+
if (slash.add(host)) typing.updatePlaceholder(host); // 输入以筛选… while nothing is typed
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// 公式, 链接 and 页面 from the "/" menu: the typed "/…" goes (as typing), then a formula, a link or a
|
|
734
|
+
// page block where it was, its field open (formula/, links/).
|
|
735
|
+
const inlineInsert = {
|
|
736
|
+
formula(host, typed) {
|
|
737
|
+
typed.deleteContents();
|
|
738
|
+
recordText(host);
|
|
739
|
+
select(typed);
|
|
740
|
+
openFormula(host, { fresh: true });
|
|
741
|
+
},
|
|
742
|
+
link(host, typed) {
|
|
743
|
+
typed.deleteContents();
|
|
744
|
+
recordText(host);
|
|
745
|
+
select(typed);
|
|
746
|
+
openLink(host, { fresh: true });
|
|
747
|
+
},
|
|
748
|
+
page(host, typed) {
|
|
749
|
+
typed.deleteContents();
|
|
750
|
+
recordText(host);
|
|
751
|
+
select(typed);
|
|
752
|
+
openLink(host, { fresh: true, page: true });
|
|
753
|
+
},
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
// The block menu: a block (its handle) turned into a type, as one undo step (convert.mjs turnInto,
|
|
757
|
+
// turnUnit). What the type draws as it is made (`preload`) comes first when the block has text, the
|
|
758
|
+
// grid's preload (type-grid.mjs) not having brought it yet: what undo and redo put back is what was made.
|
|
759
|
+
function convertUnit(unit, type, { loaded = false } = {}) {
|
|
760
|
+
const made = noTextToCarry(unit); // with no text to carry it is a new block, whose component says what follows (`onCreated`)
|
|
761
|
+
const waits = !loaded && !made ? preloading(type) : null;
|
|
762
|
+
if (waits) return void waits.then(() => unit.isConnected && convertUnit(unit, type, { loaded: true }));
|
|
763
|
+
let emptied = false; // no block left to select: an empty one to write in
|
|
764
|
+
const result = struct(turnParents(unit), [], () => {
|
|
765
|
+
const turned = turnUnit(unit, type);
|
|
766
|
+
emptied = Boolean(turned.emptied);
|
|
767
|
+
return turned.focus ? { touched: [turned.focus], focus: turned.focus, created: turned.created, select: emptied ? null : unit } : null;
|
|
768
|
+
});
|
|
769
|
+
if (!result) return;
|
|
770
|
+
if (!made || !onCreated(type, result.focus)) {
|
|
771
|
+
if (emptied && isHost(result.focus)) caretAt(result.focus, "start");
|
|
772
|
+
else {
|
|
773
|
+
// What it is now: the new highlight block or callout a block went into, as a disclosure (notion-lite's
|
|
774
|
+
// transformBlocks selects the callout), the flowchart a block or a list went into (a container
|
|
775
|
+
// of a type that takes a block as it is, no `wraps`), else the block holding the text.
|
|
776
|
+
const { container, wraps } = BLOCK_TYPES[type];
|
|
777
|
+
const target = container && !wraps ? result.focus.closest(container) : unitElement(result.focus);
|
|
778
|
+
if (target) blocks.select(target, { quiet: true });
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
say(t("已转换为{0} · Ctrl+Z 撤销", TYPE_INFO[type].label));
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Several blocks turned into one type at once (convert.mjs convertGroup, notion-lite's
|
|
786
|
+
* transformBlocks: the block menu of a group of selected blocks, the format bar over text across
|
|
787
|
+
* blocks), one undo step. `select`: what they are now is selected (the block menu: undo selects what
|
|
788
|
+
* they were, redo again what they are now); the format bar keeps its text selection itself. Returns
|
|
789
|
+
* convertGroup's outcome (`hosts`: where each text block's text is now), or null when nothing changed.
|
|
790
|
+
*/
|
|
791
|
+
function convertAll(group, type, { select = true } = {}) {
|
|
792
|
+
let outcome = null;
|
|
793
|
+
const result = struct(groupParents(group, type), group.filter(isHost), () => {
|
|
794
|
+
outcome = convertGroup(group, type);
|
|
795
|
+
if (!outcome.changed) return null;
|
|
796
|
+
const texts = [...outcome.hosts.values()].filter((host) => host?.isConnected && isHost(host));
|
|
797
|
+
return { touched: texts, focus: texts[0] ?? null, created: outcome.created, ...(select ? { select: [...group], selectAfter: outcome.units } : {}) };
|
|
798
|
+
});
|
|
799
|
+
if (!result) return null;
|
|
800
|
+
if (select) blocks.select(outcome.units, { quiet: true });
|
|
801
|
+
preloading(type)?.then(() => pageOfType(type).redraw?.(outcome.units)); // what the type draws, come after the blocks did
|
|
802
|
+
say(t("已转换为{0} · Ctrl+Z 撤销", TYPE_INFO[type].label));
|
|
803
|
+
return outcome;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function onKeydown(event) {
|
|
807
|
+
compositionOver(event);
|
|
808
|
+
if (!editing) return;
|
|
809
|
+
// A 批注's card is a field of its own: its keys are its own, but for Ctrl+S, which saves it and the report.
|
|
810
|
+
if (annotate?.holds(event.target)) {
|
|
811
|
+
const mod = event.ctrlKey || event.metaKey;
|
|
812
|
+
if (mod && !event.altKey && !event.shiftKey && event.key.toLowerCase() === "s") {
|
|
813
|
+
event.preventDefault();
|
|
814
|
+
saveNow();
|
|
815
|
+
}
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (keptByComponent(event.target, event)) return; // the field of a code block's language menu keeps its keys, and all that stands in an instance of a component package
|
|
819
|
+
followEnd(); // the caret it takes into a detail card's last block: the content to its end (caret.mjs)
|
|
820
|
+
if (annotate?.key(event)) return; // Ctrl+Alt+M: what is selected or the caret's block annotated, or the marked words' card
|
|
821
|
+
if (event.target?.closest?.(".ge-pop") && event.target.localName === "input") return; // so does any field of a menu card (a list number's)
|
|
822
|
+
if (event.target?.closest?.(".notion-popup-layer")) return; // and the icon picker (emoji-picker/), which keeps its own
|
|
823
|
+
if (typedKey(event)) return;
|
|
824
|
+
const mod = event.ctrlKey || event.metaKey;
|
|
825
|
+
if ((event.key !== "ArrowUp" && event.key !== "ArrowDown") || mod || event.altKey || event.shiftKey) endArrowRun();
|
|
826
|
+
const key = event.key.toLowerCase();
|
|
827
|
+
if (mod && !event.altKey && !event.shiftKey && key === "s") {
|
|
828
|
+
event.preventDefault();
|
|
829
|
+
saveNow();
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
if (pageAt(event.target)?.key?.(event)) return; // F2 on a tab, Enter or Esc in a tab being renamed
|
|
833
|
+
// The outline keeps its own keys (outline/list.mjs); undo and redo work there as on the page itself.
|
|
834
|
+
if (outline?.holds(event.target)) {
|
|
835
|
+
if (mod && !event.altKey && (key === "z" || key === "y")) {
|
|
836
|
+
event.preventDefault();
|
|
837
|
+
key === "y" || event.shiftKey ? redo() : undo();
|
|
838
|
+
}
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
const renamed = fieldOpen() === event.target;
|
|
842
|
+
// Undo and redo also work with the focus on the page itself, or where a component says.
|
|
843
|
+
if ((pageAt(event.target)?.undoHere?.(event.target) || event.target === document.body) && mod && !event.altKey && (key === "z" || key === "y")) {
|
|
844
|
+
event.preventDefault();
|
|
845
|
+
key === "y" || event.shiftKey ? redo() : undo();
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
const host = hostOf(event);
|
|
849
|
+
// A formula block has no text the keys go into, its TeX changing in its editor alone (notion-lite's
|
|
850
|
+
// has no text area, its keys those of a selected block). With the caret on one (a right click puts it
|
|
851
|
+
// there), Enter opens its editor, as on it selected (blocks.mjs), and any other key that types or
|
|
852
|
+
// edits selects it, saying so, and does nothing more, the focus staying on the page: the next key is
|
|
853
|
+
// the selected block's (Backspace and Delete delete it), as Backspace selects a block above that holds
|
|
854
|
+
// no text before it deletes it (typing/).
|
|
855
|
+
if (texOnly(host) && editsText(event)) {
|
|
856
|
+
const enter = event.key === "Enter";
|
|
857
|
+
blocks.select(unitElement(host), { quiet: enter });
|
|
858
|
+
if (!enter || !blocks.onKeydown(event)) event.preventDefault();
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
if (!host && !renamed && blocks.onKeydown(event)) return; // a selected block (keys wait while blocks are dragged)
|
|
862
|
+
// Ctrl+A: the block's text, then its container's blocks, one level a press, then the page; a tab
|
|
863
|
+
// being renamed keeps the browser's (its name).
|
|
864
|
+
if (!renamed && selectAll.key(event, host)) return;
|
|
865
|
+
if (!host || event.isComposing || event.keyCode === 229) return;
|
|
866
|
+
// A modifier pressed alone types nothing: the handle and a hint stay (Alt held over the handle's
|
|
867
|
+
// "+" adds above, handle/).
|
|
868
|
+
if (!MODIFIER_KEYS.has(event.key)) {
|
|
869
|
+
hideHint();
|
|
870
|
+
blocks.typing();
|
|
871
|
+
}
|
|
872
|
+
if (formatting.key(event)) return; // Esc hides the format bar, its keys format the selected text
|
|
873
|
+
// Backspace and Delete never stop at a pad by inline code (typing/code-edges.mjs); beside a
|
|
874
|
+
// component's entry, outside it, they take the whole entry away, one undo step.
|
|
875
|
+
if ((event.key === "Backspace" || event.key === "Delete") && !mod && !event.altKey && textFilters(host)) {
|
|
876
|
+
const before = ownHTML(host);
|
|
877
|
+
const removed = beforeDelete(host, event.key);
|
|
878
|
+
if (removed) {
|
|
879
|
+
event.preventDefault();
|
|
880
|
+
recordStep(host, before);
|
|
881
|
+
say(removed);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
const unit = event.key === "Escape" && !typing.escape(host) ? unitElement(host) : null;
|
|
886
|
+
if (unit) {
|
|
887
|
+
event.preventDefault(); // Esc in text selects the block around it, where no component keeps it
|
|
888
|
+
blocks.select(unit);
|
|
889
|
+
} else if (mod && !event.altKey && (key === "z" || key === "y")) {
|
|
890
|
+
event.preventDefault();
|
|
891
|
+
key === "y" || event.shiftKey ? redo() : undo();
|
|
892
|
+
} else if (mod && !event.altKey && key === "k") {
|
|
893
|
+
event.preventDefault();
|
|
894
|
+
openLink(host);
|
|
895
|
+
} else if (event.key === "Tab" && !mod && !event.altKey) {
|
|
896
|
+
if (typing.tab(host, event.shiftKey)) event.preventDefault();
|
|
897
|
+
else tabFrom(host, event.shiftKey); // the browser's, which passes over a formula block (passTab)
|
|
898
|
+
} else if (event.key === "Enter" && mod && !event.altKey && !event.shiftKey && typing.commandEnter(host)) {
|
|
899
|
+
event.preventDefault();
|
|
900
|
+
} else if (event.key === "Enter" && !event.shiftKey) {
|
|
901
|
+
event.preventDefault();
|
|
902
|
+
typing.split(host);
|
|
903
|
+
} else if (event.key === "Enter") {
|
|
904
|
+
if (typing.shiftEnter(host)) event.preventDefault();
|
|
905
|
+
} else if (event.key === "Backspace" && !mod && !event.altKey && atEdge(host, false)) {
|
|
906
|
+
if (typing.backspace(host)) event.preventDefault();
|
|
907
|
+
} else if (event.key === "Delete" && !mod && !event.altKey && atEdge(host, true)) {
|
|
908
|
+
if (typing.forwardDelete(host)) event.preventDefault();
|
|
909
|
+
} else if (mod && event.shiftKey && !event.altKey && (event.key === "ArrowUp" || event.key === "ArrowDown") && !renamed) {
|
|
910
|
+
event.preventDefault();
|
|
911
|
+
moveHostBlock(host, event.key === "ArrowUp" ? -1 : 1);
|
|
912
|
+
} else if (event.key.startsWith("Arrow") && !mod && !event.altKey && !event.shiftKey && !renamed) {
|
|
913
|
+
// ←/→ stop on both sides of inline code's edges (typing/code-edges.mjs); a tab's caret stays in its name.
|
|
914
|
+
if ((textFilters(host) && codeArrow(host, event.key)) || arrow(host, event.key)) event.preventDefault();
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// The browser's Tab or Shift+Tab from a text block while it moves the focus (tabFrom): the block, which
|
|
919
|
+
// way, and where the caret was in it; null once the focus has moved, or left the page.
|
|
920
|
+
let tabbing = null;
|
|
921
|
+
function tabFrom(host, back) {
|
|
922
|
+
const selection = getSelection();
|
|
923
|
+
tabbing = { host, back, range: selection.rangeCount ? selection.getRangeAt(0).cloneRange() : null };
|
|
924
|
+
setTimeout(() => { tabbing = null; });
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// A formula block is no stop of the browser's Tab, having no text to type in, as the arrow keys pass
|
|
928
|
+
// over it (caret.mjs places): the focus Tab puts on one goes on to the text block beyond it that way,
|
|
929
|
+
// the caret at its start, or, with none there, back to where it was.
|
|
930
|
+
function passTab(event) {
|
|
931
|
+
const tab = tabbing;
|
|
932
|
+
tabbing = null;
|
|
933
|
+
const formula = tab ? editable(event.target) : null;
|
|
934
|
+
if (!texOnly(formula)) return;
|
|
935
|
+
const beyond = textNear(formula, tab.back ? -1 : 1);
|
|
936
|
+
if (beyond) return void caretAt(beyond, "start");
|
|
937
|
+
if (!tab.host.isConnected || !tab.range) return;
|
|
938
|
+
tab.host.focus({ preventScroll: true });
|
|
939
|
+
select(tab.range);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function onBeforeInput(event) {
|
|
943
|
+
if (!editing) return;
|
|
944
|
+
endArrowRun();
|
|
945
|
+
const host = hostOf(event);
|
|
946
|
+
if (!host) return;
|
|
947
|
+
const type = event.inputType;
|
|
948
|
+
if (type === "historyUndo" || type === "historyRedo") {
|
|
949
|
+
event.preventDefault();
|
|
950
|
+
// Chrome has one undo stack for a page: past what a field of a component or a 批注's card can undo,
|
|
951
|
+
// its Ctrl+Z lands on the last text block typed in, and with the focus there it is no undo of the page's.
|
|
952
|
+
if (keptByComponent(document.activeElement) || annotate?.holds(document.activeElement)) return;
|
|
953
|
+
type === "historyUndo" ? undo() : redo();
|
|
954
|
+
} else if (type === "insertParagraph" && !event.isComposing) {
|
|
955
|
+
event.preventDefault();
|
|
956
|
+
typing.split(host);
|
|
957
|
+
} else if (type === "insertLineBreak" && typedAsIs(host, "\n")) {
|
|
958
|
+
event.preventDefault();
|
|
959
|
+
} else if (type === "insertLineBreak" && can(host, "plain")) {
|
|
960
|
+
event.preventDefault();
|
|
961
|
+
say(limitText(host, "split"));
|
|
962
|
+
} else if (/^delete[A-Za-z]+(Backward|Forward)$/.test(type) && !event.isComposing && textFilters(host)) {
|
|
963
|
+
// Backspace or Delete beside a component's entry whose keydown onKeydown passed by (an input
|
|
964
|
+
// method's, keyCode 229): the whole entry, as there (typing/code-edges.mjs takeEntry). A
|
|
965
|
+
// deletion backward the browser does: the caret kept outside inline code it comes to (deletedBack).
|
|
966
|
+
const back = type.endsWith("Backward");
|
|
967
|
+
const before = type === "deleteContentBackward" || type === "deleteContentForward" ? ownHTML(host) : null;
|
|
968
|
+
const removed = before !== null ? takeEntry(host, back) : undefined;
|
|
969
|
+
if (removed) {
|
|
970
|
+
event.preventDefault();
|
|
971
|
+
recordStep(host, before);
|
|
972
|
+
say(removed);
|
|
973
|
+
} else if (back) deletingBack(host);
|
|
974
|
+
} else if (type.startsWith("format")) {
|
|
975
|
+
event.preventDefault(); // formatting goes through format-bar/: the kit's markup only
|
|
976
|
+
} else if (type === "insertFromDrop" || type === "deleteByDrag") {
|
|
977
|
+
event.preventDefault(); // no drag-moves between blocks
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Whether what is typed in `host` is a menu's to look for.
|
|
982
|
+
const typedFor = (host) => slash.host === host || atMenu.host === host;
|
|
983
|
+
|
|
984
|
+
function onInput(event) {
|
|
985
|
+
compositionOver(event);
|
|
986
|
+
const host = editing && hostOf(event);
|
|
987
|
+
if (!host) return;
|
|
988
|
+
if (event.isComposing) {
|
|
989
|
+
if (slash.host === host) slash.update(); // the "/" menu filters on the pinyin being typed too (its letters)
|
|
990
|
+
if (atMenu.host === host) atMenu.update(); // and so does the "@" menu
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
settlePads();
|
|
994
|
+
// What is typed after a "/" or an "@" while its menu is open is the menu's (what it looks for), no
|
|
995
|
+
// words: no space goes beside it, to stay in the text once the menu has put something there.
|
|
996
|
+
if ((event.inputType === "insertText" || event.inputType === "insertCompositionText") && !typedFor(host)) {
|
|
997
|
+
formatting.typed(host, event.data); // the formats the caret kept (Ctrl+B with nothing selected)
|
|
998
|
+
typing.autoSpace(host, event.data);
|
|
999
|
+
}
|
|
1000
|
+
// What stood between two pieces of inline code deleted: they are one (marks.mjs joinCode).
|
|
1001
|
+
if (event.inputType.startsWith("delete") && textFilters(host)) joinCode(host);
|
|
1002
|
+
if (event.inputType.startsWith("delete") && event.inputType.endsWith("Backward") && textFilters(host)) deletedBack(host);
|
|
1003
|
+
pageOf(host)?.typed?.(host);
|
|
1004
|
+
recordText(host);
|
|
1005
|
+
markdown.typed(host, event.data);
|
|
1006
|
+
typing.updatePlaceholder(host);
|
|
1007
|
+
if (event.inputType === "insertText" || event.inputType === "insertCompositionText") showCaret(); // what an input method put in without composing ("、")
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function onCompositionEnd(event) {
|
|
1011
|
+
composing = false;
|
|
1012
|
+
settlePads();
|
|
1013
|
+
const host = editing && hostOf(event);
|
|
1014
|
+
if (host) {
|
|
1015
|
+
if (!typedFor(host)) {
|
|
1016
|
+
formatting.typed(host, event.data);
|
|
1017
|
+
typing.autoSpace(host, event.data); // a menu's query: no space beside it (onInput)
|
|
1018
|
+
}
|
|
1019
|
+
pageOf(host)?.typed?.(host);
|
|
1020
|
+
// Typed text as a key's is: committed after the reader's pause, not at once. Each phrase put in
|
|
1021
|
+
// was once a commit of its own (a file written and a version announced per phrase, about 100ms
|
|
1022
|
+
// of the server's and 200–400ms of the page's each); the commit still waits out a composition
|
|
1023
|
+
// under way (autosave.mjs blocked()), and the crash buffer still takes the first change at once.
|
|
1024
|
+
recordText(host);
|
|
1025
|
+
markdown.typed(host, event.data);
|
|
1026
|
+
typing.updatePlaceholder(host);
|
|
1027
|
+
showCaret(); // the browser followed the composition, not the text put in for it (caret.mjs)
|
|
1028
|
+
}
|
|
1029
|
+
formatting.schedule();
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function onPointerDown(event) {
|
|
1033
|
+
compositionOver(event);
|
|
1034
|
+
// A press on the editor's own controls (the handle, its menu, the format bar) keeps the caret
|
|
1035
|
+
// where it is, so it moves no focus: in a workbench, where the focus may still be outside the
|
|
1036
|
+
// report's frame, the page takes it here, or the keys after it (Ctrl+Z) would go to the workbench.
|
|
1037
|
+
if (editing && !document.hasFocus()) window.focus();
|
|
1038
|
+
hideHint();
|
|
1039
|
+
endArrowRun();
|
|
1040
|
+
if (event.button === 0) pressing = true;
|
|
1041
|
+
pressedText = event.button === 0 && editing ? editable(event.target) : null;
|
|
1042
|
+
// What the press came down on may take it, by its component's `press`: code opens for editing, a
|
|
1043
|
+
// display formula goes into the formula editor, and no caret goes in.
|
|
1044
|
+
if (editing && event.button === 0 && pageAt(event.target)?.press?.(event)) return;
|
|
1045
|
+
if (!editing || annotate?.holds(event.target) || formatting.holds(event.target) || linkHolds(event.target) || formulaBoxNode()?.contains(event.target) || panelOpen()?.holds(event.target) || slash.contains(event.target) || atMenu.contains(event.target)) return;
|
|
1046
|
+
if (event.target.closest?.('[data-ge-flag="conflict"]')) conflicts?.show(event.target);
|
|
1047
|
+
closeTyped();
|
|
1048
|
+
formatting.pressed(event.button === 0, keptByComponent(event.target));
|
|
1049
|
+
if (messageKind() === "note" && editable(event.target)) settle(); // an old note never outlives a click into the text
|
|
1050
|
+
if (linkBoxNode()) dismissLink(); // a press elsewhere keeps what the link card says
|
|
1051
|
+
keepPanels();
|
|
1052
|
+
const reason = readOnlyReason(event.target);
|
|
1053
|
+
if (reason) say(reason, "hint"); // what its hover hint says (hints/)
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// ---------- the block under the caret ----------
|
|
1057
|
+
|
|
1058
|
+
function onSelectionChange() {
|
|
1059
|
+
if (!editing) return;
|
|
1060
|
+
formatting.schedule();
|
|
1061
|
+
if (!composing) formatting.moved();
|
|
1062
|
+
const selection = getSelection();
|
|
1063
|
+
// The caret, or an end of a text selection, in what a folded heading hides (undo put it back there):
|
|
1064
|
+
// the fold opens (fold/).
|
|
1065
|
+
for (const end of [selection.anchorNode, selection.focusNode]) if (end && hiddenByFold(end)) blocks?.folds.showAround(end);
|
|
1066
|
+
const host = selection.rangeCount ? editable(selection.anchorNode) : null;
|
|
1067
|
+
// A text block no step named, one a block made on the page came with: what the caret finds there is
|
|
1068
|
+
// what the first change of it undoes to, as a change is only seen once made.
|
|
1069
|
+
if (host && !lastKnown.has(host)) lastKnown.set(host, ownHTML(host));
|
|
1070
|
+
if (slash.host && host !== slash.host) slash.close();
|
|
1071
|
+
else if (slash.host) slash.update(); // the caret before the "/": it closes
|
|
1072
|
+
if (atMenu.host && host !== atMenu.host) atMenu.close();
|
|
1073
|
+
else atMenu.moved();
|
|
1074
|
+
// A caret after the lists a list item holds (a click below them) goes to the end of its text.
|
|
1075
|
+
if (host && selection.isCollapsed && selection.anchorNode === host && selection.anchorOffset > textEnd(host)) {
|
|
1076
|
+
caretAt(host, "end");
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
settlePads(true);
|
|
1080
|
+
if (host && !pressing && outOfAtom()) return; // never a caret inside a formula (a click settles on pointerup)
|
|
1081
|
+
if (host && !pressing && textFilters(host)) settleCodeEdge(host); // a click settles on pointerup
|
|
1082
|
+
typing.updatePlaceholder(host);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// ---------- moving the block under the caret, Enter on a selected block ----------
|
|
1086
|
+
|
|
1087
|
+
// Ctrl+Shift+↑/↓ in text moves the block around it; the caret stays where it was.
|
|
1088
|
+
function moveHostBlock(host, delta) {
|
|
1089
|
+
const unit = unitElement(host);
|
|
1090
|
+
if (!unit) return;
|
|
1091
|
+
const selection = getSelection();
|
|
1092
|
+
const offsets = selection.rangeCount ? rangeOffsets(host, selection.getRangeAt(0)) : null;
|
|
1093
|
+
if (!blocks.moveBy(unit, delta, { keepFocus: true })) return;
|
|
1094
|
+
host.focus({ preventScroll: true });
|
|
1095
|
+
if (offsets) select(rangeAt(host, offsets));
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// Enter on a selected block (the first of those selected, when it is text: blocks.mjs): what the
|
|
1099
|
+
// page object it is the block of does with it (notion-lite's `activate`: a formula block, which has
|
|
1100
|
+
// no text, its editor open), each testing the element itself, else the caret at the end of its first
|
|
1101
|
+
// editable text, as notion-lite's (block-keys.js: the end of the block's text).
|
|
1102
|
+
function editBlock(unit) {
|
|
1103
|
+
if (pageObjects().some((page) => page.activate?.(unit))) return;
|
|
1104
|
+
const host = isHost(unit) ? unit : firstHost(unit);
|
|
1105
|
+
if (host) caretAt(host, "end");
|
|
1106
|
+
else say(t("这一块里没有可以直接修改的文字。"));
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// ---------- the parts, and a workbench around the page ----------
|
|
1110
|
+
|
|
1111
|
+
// How many of what is held the file cannot hold (conflicts.mjs unfit), no conflicts with a newer version.
|
|
1112
|
+
const unfitCount = () => [...(commits?.issues.values() ?? [])].filter(unfit).length;
|
|
1113
|
+
|
|
1114
|
+
// Ctrl+S: whatever waits goes now.
|
|
1115
|
+
async function saveNow() {
|
|
1116
|
+
checkAll();
|
|
1117
|
+
await annotate?.flush();
|
|
1118
|
+
const outcome = await autosave?.flush();
|
|
1119
|
+
const { held, drafts } = view();
|
|
1120
|
+
const unfitHeld = unfitCount();
|
|
1121
|
+
if (outcome === "saved" || outcome === "nothing") say(unfitHeld === 0 && held > 0 ? t("其余修改已保存;红框处的冲突还没有保存。") : held > unfitHeld ? t("其余修改已保存;红框处的内容还没有保存。") : held > 0 ? t("其余修改已保存;红框处的内容文档存不下,没有保存。") : drafts > 0 ? t("其余修改已保存;还没写完的块(红框或空标题)没有保存。") : t("已保存。"));
|
|
1122
|
+
else if (outcome === "offline") say(t("{0}:修改暂存在浏览器里,连上后自动保存。", OFFLINE), "error");
|
|
1123
|
+
return outcome;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// The last keystrokes, counted now (not after the pause): what is typed for a menu is text now.
|
|
1127
|
+
function checkNow() {
|
|
1128
|
+
if (!autosave) return;
|
|
1129
|
+
closeTyped();
|
|
1130
|
+
checkPending = true;
|
|
1131
|
+
checkChanges();
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// All the page holds, as it is saved or left: the last keystrokes, and what is in a formula's box or a component's editor.
|
|
1135
|
+
function checkAll() {
|
|
1136
|
+
keepPanels();
|
|
1137
|
+
checkNow();
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// The report built from the saved source, once whatever waits is saved.
|
|
1141
|
+
async function exportNow() {
|
|
1142
|
+
await saveNow();
|
|
1143
|
+
return exportReport(MAP);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function post(route, body) {
|
|
1147
|
+
return postAnswer(route, MAP.token, body);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
let workbench = null; // the session with a workbench embedding the page (session.mjs), if any
|
|
1151
|
+
let released = false; // the workbench closes the page: what does not get saved in time stays in the crash buffer
|
|
1152
|
+
|
|
1153
|
+
// A new page under this one (a page block's box: links/), the page staying as it is: the sidebar
|
|
1154
|
+
// hears of it; as the workbench's 新建 makes one, its title typed. Throws what stopped it.
|
|
1155
|
+
async function newSubpage(title) {
|
|
1156
|
+
const answer = await post("/__gallery/workspace/create", { title, description: t("新文档"), parent: MAP.slug, language });
|
|
1157
|
+
if (!answer.ok) throw new Error(failure(answer, t("没能新建页面")));
|
|
1158
|
+
workbench?.workspace();
|
|
1159
|
+
return answer.report;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// The report's title as the reader sees it now (while editing, the title block's text).
|
|
1163
|
+
const currentTitle = () => (document.querySelector('[data-ge-kind="title"]') ?? document.querySelector("h1.doc-title, .doc-header h1"))?.textContent.replace(/\s+/g, " ").trim() ?? "";
|
|
1164
|
+
|
|
1165
|
+
// This page, as the list of pages takes it (links/pages.mjs).
|
|
1166
|
+
const thisPage = () => ({ slug: MAP.slug, title: currentTitle() });
|
|
1167
|
+
|
|
1168
|
+
// What the page tells the workbench of its state (session.mjs).
|
|
1169
|
+
const sessionState = () => {
|
|
1170
|
+
const state = view();
|
|
1171
|
+
return { editing, pending: state.waiting > 0 || state.syncing, saving: state.syncing, offline: state.offline, conflicts: state.held, unfit: unfitCount(), changed: state.foreign };
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
// What the workbench can ask of the page (session.mjs).
|
|
1175
|
+
const COMMANDS = {
|
|
1176
|
+
// As Ctrl+S.
|
|
1177
|
+
async save() {
|
|
1178
|
+
const outcome = await saveNow();
|
|
1179
|
+
return { ok: outcome === "saved" || outcome === "nothing", outcome };
|
|
1180
|
+
},
|
|
1181
|
+
// The page may always be closed: it saves what waits first (for a moment), and what does not get
|
|
1182
|
+
// saved in time stays in the crash buffer, for the next page of this report to send.
|
|
1183
|
+
async "prepare-close"() {
|
|
1184
|
+
checkAll();
|
|
1185
|
+
annotate?.flush();
|
|
1186
|
+
await Promise.race([autosave?.flush(), new Promise((resolve) => setTimeout(resolve, 1000))]);
|
|
1187
|
+
autosave?.store();
|
|
1188
|
+
released = true;
|
|
1189
|
+
return { ok: true, canClose: true };
|
|
1190
|
+
},
|
|
1191
|
+
// The report built from its saved source (once whatever waits is saved), or with `copy` the view
|
|
1192
|
+
// copy with the reading state (the kit's "导出当前视图副本").
|
|
1193
|
+
async export({ copy = false }) {
|
|
1194
|
+
if (copy) {
|
|
1195
|
+
const control = document.querySelector("[data-doc-export-copy]");
|
|
1196
|
+
if (!control) return { ok: false, reason: "unavailable" };
|
|
1197
|
+
if (control.disabled) return { ok: false, reason: "busy" };
|
|
1198
|
+
const done = new Promise((resolve) => addEventListener("doc-export-copy-done", (event) => resolve(event.detail), { once: true }));
|
|
1199
|
+
control.click();
|
|
1200
|
+
return done;
|
|
1201
|
+
}
|
|
1202
|
+
return exportNow();
|
|
1203
|
+
},
|
|
1204
|
+
// 搜索文档 opened the report at a hit: its words scrolled into view and marked for a moment (find/).
|
|
1205
|
+
async reveal(message) {
|
|
1206
|
+
return revealText(message, (node) => {
|
|
1207
|
+
blocks?.folds.showAround(node); // a folded heading's section opened (fold/)
|
|
1208
|
+
return modalOps().show(node);
|
|
1209
|
+
});
|
|
1210
|
+
},
|
|
1211
|
+
// A link or an address opened the report at a block.
|
|
1212
|
+
async "reveal-block"({ block }) {
|
|
1213
|
+
return revealBlock(block);
|
|
1214
|
+
},
|
|
1215
|
+
// The way back or forth to a place a link to a block of the page left (jump).
|
|
1216
|
+
async "show-place"({ place }) {
|
|
1217
|
+
return showPlace(place);
|
|
1218
|
+
},
|
|
1219
|
+
// 批注 (annotate/): what there is read and drawn ({ refresh }), and the workbench's list, 复制批注, 全部删除 and 撤销.
|
|
1220
|
+
async annotate(message) {
|
|
1221
|
+
if (!editing || !annotate) return { ok: false, reason: "editing" };
|
|
1222
|
+
return annotate.command(message);
|
|
1223
|
+
},
|
|
1224
|
+
async annotations(message) {
|
|
1225
|
+
if (!editing || !annotate) return { ok: false, reason: "editing" };
|
|
1226
|
+
return annotate.action(message);
|
|
1227
|
+
},
|
|
1228
|
+
// Rename the report: its title, selected for typing (saving it updates <title> and report.json).
|
|
1229
|
+
async "edit-title"() {
|
|
1230
|
+
const title = document.querySelector('[data-ge-kind="title"]');
|
|
1231
|
+
if (!editing || !title || !isHost(title)) return { ok: false, reason: "no-title" };
|
|
1232
|
+
title.focus({ preventScroll: true });
|
|
1233
|
+
const range = document.createRange();
|
|
1234
|
+
range.selectNodeContents(title);
|
|
1235
|
+
select(range);
|
|
1236
|
+
title.scrollIntoView({ block: "nearest" });
|
|
1237
|
+
return { ok: true };
|
|
1238
|
+
},
|
|
1239
|
+
// Rename the report from outside the page (the workbench's sidebar): the title block takes the new
|
|
1240
|
+
// text as a step of its own, undone like one and saved at once (with <title> and report.json).
|
|
1241
|
+
async "set-title"({ title: text }) {
|
|
1242
|
+
const title = document.querySelector('[data-ge-kind="title"]');
|
|
1243
|
+
if (!editing || !title || !isHost(title)) return { ok: false, reason: "no-title" };
|
|
1244
|
+
const name = typeof text === "string" ? text.replace(/\s+/g, " ").trim() : "";
|
|
1245
|
+
if (!name) return { ok: false, reason: "title" };
|
|
1246
|
+
if (title.textContent === name) return { ok: true };
|
|
1247
|
+
const before = ownHTML(title);
|
|
1248
|
+
title.textContent = name;
|
|
1249
|
+
recordStep(title, before);
|
|
1250
|
+
typing.updatePlaceholder(title);
|
|
1251
|
+
return { ok: true };
|
|
1252
|
+
},
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
// ---------- links to blocks and documents ----------
|
|
1256
|
+
|
|
1257
|
+
// The block of this id, once the page is drawn (what its charts move is moved by then), or an element
|
|
1258
|
+
// of this id (an address may name a heading's): what hides it opened (a folded heading's section,
|
|
1259
|
+
// folded.mjs reveal, then the kit's DocKit.reveal: a detail card that does not hold it closed, a
|
|
1260
|
+
// disclosure, a tab, the card it is in), in the middle of the window and selected, as notion-lite's
|
|
1261
|
+
// revealBlock, and the keys are the page's. What was just shown lays itself out in the frames after (a
|
|
1262
|
+
// tab's tables) and a card closed gives the focus back then, so it is brought there again before it is
|
|
1263
|
+
// selected. A block that is gone is said, unless
|
|
1264
|
+
// `quiet`, the page staying as it is; #top with no such element is the top of the page, as for a browser.
|
|
1265
|
+
// A later step (a link, the way back or forth, an address) started past its waits leaves it there.
|
|
1266
|
+
async function revealBlock(id, { quiet = false } = {}) {
|
|
1267
|
+
const step = ++stepping;
|
|
1268
|
+
await reached("data-doc-drawn");
|
|
1269
|
+
if (step !== stepping) return { ok: false, reason: "moved" };
|
|
1270
|
+
const block = document.querySelector(`[data-block-id="${CSS.escape(id)}"]`) ?? document.getElementById(id);
|
|
1271
|
+
if (!block) {
|
|
1272
|
+
if (id === "top") scrollTo(0, 0);
|
|
1273
|
+
else if (!quiet) say(t("这个块已经不在了。"));
|
|
1274
|
+
return { ok: false, reason: "missing" };
|
|
1275
|
+
}
|
|
1276
|
+
reveal(block); // a fold over it too (fold/)
|
|
1277
|
+
block.scrollIntoView({ block: "center" });
|
|
1278
|
+
await painted();
|
|
1279
|
+
await painted();
|
|
1280
|
+
if (step !== stepping) return { ok: false, reason: "moved" };
|
|
1281
|
+
block.scrollIntoView({ block: "center" });
|
|
1282
|
+
if (block.hasAttribute("data-block-id")) blocks.select(block, { quiet: true });
|
|
1283
|
+
window.focus();
|
|
1284
|
+
return { ok: true };
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
let stepping = 0; // the newest step to a block or a place (revealBlock, showPlace): one a later step started past does nothing more
|
|
1288
|
+
|
|
1289
|
+
// A link to a block of this page (links/, goTo), a step of the history as a browser's jump to an
|
|
1290
|
+
// anchor is, so that the way back comes to where the reader was: in a workbench the workbench's
|
|
1291
|
+
// (`jump`, its address staying the page's), a page alone in a tab its own, the address as it is
|
|
1292
|
+
// (`docPlace` kept in the entry left, `docBlock` the one made, as the kit's for a reader); a page
|
|
1293
|
+
// opened in a frame on its own (?solo) makes none, nor a link to a block that is not there, nor one to
|
|
1294
|
+
// the block the page has just gone to (its entry the one made), as the browser makes none for the
|
|
1295
|
+
// address it is at.
|
|
1296
|
+
function jumpTo(id) {
|
|
1297
|
+
const there = Boolean(document.querySelector(`[data-block-id="${CSS.escape(id)}"]`) ?? document.getElementById(id) ?? (id === "top" || null));
|
|
1298
|
+
if (there && !SOLO) {
|
|
1299
|
+
const place = { ...viewNow(), ...placeIn(window) };
|
|
1300
|
+
if (workbench) workbench.jump({ block: id, place });
|
|
1301
|
+
else if (window.parent === window && window.history.state?.docBlock !== id) { // the browser's history: `history` is the page's undo
|
|
1302
|
+
const state = window.history.state;
|
|
1303
|
+
try {
|
|
1304
|
+
window.history.replaceState({ ...(state && typeof state === "object" ? state : {}), docPlace: place }, "");
|
|
1305
|
+
window.history.pushState({ docBlock: id }, "");
|
|
1306
|
+
} catch { /* no history to write */ }
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
return revealBlock(id);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// Back where a jump to a block left the reader (the way back, `show-place` in a workbench): the
|
|
1313
|
+
// detail cards that were open open again and those the jump opened close, the block that stood at the
|
|
1314
|
+
// top of the window stands there again (what the jump opened above it moving the rest) and the caret
|
|
1315
|
+
// is back in the block it was in, else no block is selected (the jump selected its target).
|
|
1316
|
+
async function showPlace(place) {
|
|
1317
|
+
if (!place || typeof place !== "object") return { ok: false, reason: "place" };
|
|
1318
|
+
const step = ++stepping;
|
|
1319
|
+
await reached("data-doc-drawn");
|
|
1320
|
+
if (step !== stepping) return { ok: false, reason: "moved" };
|
|
1321
|
+
const cards = modalOps();
|
|
1322
|
+
const kept = Array.isArray(place.modals) ? place.modals : Array.isArray(place.cards) ? place.cards.filter((card) => typeof card?.id === "string") : []; // the kit's entry (runtime/reveal.js), a jump taken before the page edited
|
|
1323
|
+
const keep = new Set(kept.map((card) => card.id));
|
|
1324
|
+
while (cards.open().some((card) => !keep.has(card.id)) && cards.closeTop()) { /* the cards opened since */ }
|
|
1325
|
+
const open = new Set(cards.open().map((card) => card.id));
|
|
1326
|
+
cards.reopen(kept.filter((card) => !open.has(card.id)));
|
|
1327
|
+
blocks.clear();
|
|
1328
|
+
const unit = typeof place.unit === "string" ? document.querySelector(`[data-block-id="${CSS.escape(place.unit)}"]`) : null;
|
|
1329
|
+
const host = unit && (place.self ? unit : [...unit.querySelectorAll("[contenteditable='true']")].find(isHost));
|
|
1330
|
+
if (host && isHost(host) && Number.isInteger(place.offset)) {
|
|
1331
|
+
host.focus({ preventScroll: true });
|
|
1332
|
+
caretAtOffset(host, place.offset);
|
|
1333
|
+
}
|
|
1334
|
+
goBackTo(window, place);
|
|
1335
|
+
const put = [scrollX, scrollY];
|
|
1336
|
+
await painted();
|
|
1337
|
+
if (step !== stepping) return { ok: false, reason: "moved" };
|
|
1338
|
+
if (unmoved(window, place, put)) goBackTo(window, place); // what was laid out since, or a browser putting the entry's scroll position back (a page alone), moved it; the reader scrolling meanwhile did not
|
|
1339
|
+
window.focus();
|
|
1340
|
+
return { ok: true };
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// What an address's hash asks of a page alone in a tab, on load and as it changes (in a workbench the
|
|
1344
|
+
// workbench asks: reveal-block). One that changes while the page is open may be no block's, a report's
|
|
1345
|
+
// script keeping its state there, and what it finds nothing for is `quiet`.
|
|
1346
|
+
function followHash(options) {
|
|
1347
|
+
if (location.hash.length > 1) revealBlock(decoded(location.hash.slice(1)), options);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// The id `element` has in the file, or is given now, by a commit that goes at once (commit.mjs idFor),
|
|
1351
|
+
// so that asking for it is what writes it; null for what cannot be named yet.
|
|
1352
|
+
function blockId(element) {
|
|
1353
|
+
const named = element.getAttribute("data-block-id");
|
|
1354
|
+
const id = commits?.idFor(element) ?? null;
|
|
1355
|
+
if (!named) markChanged(true);
|
|
1356
|
+
return id;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// 复制块链接 (the block menu): the block's address, which opens it in a workbench from anywhere, by
|
|
1360
|
+
// the link the address bar shows for this page, the title it shows counted; in the click on the row,
|
|
1361
|
+
// the clipboard's own rule, so by the list held ready (links/pages.mjs).
|
|
1362
|
+
function copyBlockLink(unit) {
|
|
1363
|
+
const id = blockId(unit);
|
|
1364
|
+
if (id) copyAddress(fullAddress(location.origin, { slug: MAP.slug, block: id }, heldList().named), say);
|
|
1365
|
+
else say(t("这一块还没有保存,保存后才能复制它的链接。"));
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// Where a link leads when that is a document of the workspace (links/, shared/page-address.mjs): a
|
|
1369
|
+
// block of this one is gone to where it stands; another opens in the workbench around the page, which
|
|
1370
|
+
// has this page save what waits, as for a click in its sidebar, and reads a workbench's address
|
|
1371
|
+
// (`hash`) on its own list. A page alone in a tab goes there itself, once what waits is saved
|
|
1372
|
+
// (prepare-close), so that nothing holds it back on beforeunload, and to a workbench's address only
|
|
1373
|
+
// when the list holds it, as a page in a workbench stays where a link leads nowhere; a page opened on
|
|
1374
|
+
// its own in a frame (?solo: the 写作偏好 dialog's) opens it in a new tab, the dialog staying.
|
|
1375
|
+
async function goTo({ slug = null, block = null, hash = null }, href) {
|
|
1376
|
+
if (slug === MAP.slug) {
|
|
1377
|
+
if (block) jumpTo(block);
|
|
1378
|
+
} else if (SOLO) window.open(href, "_blank", "noopener");
|
|
1379
|
+
else if (window.parent !== window) workbench?.open(hash === null ? { target: slug, block } : { hash });
|
|
1380
|
+
else if (hash === null || (await leadsToPage(hash))) COMMANDS["prepare-close"]().then(() => location.assign(hash === null ? `/generated/${slug}.html${block ? `#${encodeURIComponent(block)}` : ""}` : href));
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
function connectParts() {
|
|
1384
|
+
holdPages(thisPage);
|
|
1385
|
+
addEventListener("focus", () => holdPages(thisPage));
|
|
1386
|
+
connectLinks({
|
|
1387
|
+
say, recordStep, hideFormatBar: () => formatting.hide(), active: () => editing && !composing,
|
|
1388
|
+
served: (file) => mediaUrl("file", MAP.slug, file), // a link to a file of the folder: where the preview hands it out
|
|
1389
|
+
closing: () => menuOpen() || popupOpen() || Boolean(formulaBoxNode() || panelOpen()),
|
|
1390
|
+
slug: MAP.slug, own: thisPage, reveal: jumpTo, go: goTo,
|
|
1391
|
+
subpage: SOLO ? null : newSubpage, // 写作偏好 stands outside the tree of pages
|
|
1392
|
+
});
|
|
1393
|
+
connectViews({
|
|
1394
|
+
say, recordStep, struct, assetUrl, slug: MAP.slug, unfurl: readPage,
|
|
1395
|
+
blocksAt: (host, range, made, kinds) => clipboard.blocksAt(host, range, made, kinds),
|
|
1396
|
+
});
|
|
1397
|
+
connectFormula({ say, recordStep, active: () => editing && !composing });
|
|
1398
|
+
connectChipFills(() => editing);
|
|
1399
|
+
connectHints({ editing: () => editing });
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// Opening: first what this tab left unsaved when it reloaded or crashed (the crash buffer), then
|
|
1403
|
+
// editing, where the reader was when the page reloaded by itself; what pages that are gone left
|
|
1404
|
+
// comes after, once the open tabs have answered.
|
|
1405
|
+
// The kit holds the page's first paint until the report is set up (the kit's styles/base.css; ui.js sets
|
|
1406
|
+
// data-doc-ready on a frame after DOMContentLoaded, which may come after the editor starts).
|
|
1407
|
+
// Until then nothing on the page can take the focus, so a workbench hears the page is ready only
|
|
1408
|
+
// once it shows (else "edit-title" would select the title of a hidden page and keys would go
|
|
1409
|
+
// nowhere). The kit shows it after 1.2s whatever happens; so does this.
|
|
1410
|
+
// The kit also says when its drawing is done (data-doc-drawn: charts drawn, nothing left to move).
|
|
1411
|
+
function reached(attribute) {
|
|
1412
|
+
const root = document.documentElement;
|
|
1413
|
+
if (root.hasAttribute(attribute)) return Promise.resolve();
|
|
1414
|
+
return new Promise((resolve) => {
|
|
1415
|
+
const done = () => {
|
|
1416
|
+
observer.disconnect();
|
|
1417
|
+
clearTimeout(timer);
|
|
1418
|
+
resolve();
|
|
1419
|
+
};
|
|
1420
|
+
const observer = new MutationObserver(() => { if (root.hasAttribute(attribute)) done(); });
|
|
1421
|
+
observer.observe(root, { attributes: true, attributeFilter: [attribute] });
|
|
1422
|
+
const timer = setTimeout(done, 1300);
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// A frame drawn (the outline shown before enter(): start), or 200ms gone by: a page put out of sight
|
|
1427
|
+
// meanwhile draws no frame, and its editor does not wait for one.
|
|
1428
|
+
function painted() {
|
|
1429
|
+
return new Promise((resolve) => {
|
|
1430
|
+
const timer = setTimeout(resolve, 200);
|
|
1431
|
+
requestAnimationFrame(() => setTimeout(() => {
|
|
1432
|
+
clearTimeout(timer);
|
|
1433
|
+
resolve();
|
|
1434
|
+
}, 0));
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
// In a workbench, showing the report again is the workbench's: this page stops (it commits and
|
|
1439
|
+
// stores nothing more) and asks; the workbench swaps in a page it loaded out of sight meanwhile, so
|
|
1440
|
+
// the reader never sees a reload (session.mjs reload). The preview's live reload asks the same way
|
|
1441
|
+
// when the page does not edit (src/server/routes.mjs).
|
|
1442
|
+
function handOff() {
|
|
1443
|
+
window.__galleryEditReload = () => reloadView(MAP.slug);
|
|
1444
|
+
keepWith(() => ({ modals: modalOps().kept() }));
|
|
1445
|
+
if (!workbench) return;
|
|
1446
|
+
setReloader(() => {
|
|
1447
|
+
autosave?.close();
|
|
1448
|
+
workbench.reload();
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
// The workbench around the page, if any: it hears the page's state and notices, and asks it to save,
|
|
1453
|
+
// close and show the report again.
|
|
1454
|
+
function connect() {
|
|
1455
|
+
// The look the page shows (字体与布局: its <html>'s, src/shared/document-look.mjs), which the workbench's
|
|
1456
|
+
// 更多 marks as chosen; told again when it changes (the kit's ui.js, doc-look-change).
|
|
1457
|
+
workbench = connectWorkbench({ slug: MAP.slug, snapshot: () => ({ title: currentTitle(), icon: pageIcon(), look: lookOfElement(document.documentElement), state: sessionState(), annotations: annotate?.count() }), commands: COMMANDS, modals: modalOps() });
|
|
1458
|
+
addEventListener("doc-look-change", () => workbench?.update());
|
|
1459
|
+
relayTo(workbench ? workbench.notice : null, workbench ? workbench.noticeRoom : null); // the workbench shows the page's notices, above the page's bottom panels
|
|
1460
|
+
handOff();
|
|
1461
|
+
// A page on its own in a frame (?solo: the 写作偏好 dialog's, which no session asks to save) is saved
|
|
1462
|
+
// by the frame's owner through this, quietly (the dialog's 保存, which writes its file once the page
|
|
1463
|
+
// has saved): what waits goes now; the outcome as Ctrl+S's.
|
|
1464
|
+
window.__galleryEditFlush = async () => {
|
|
1465
|
+
checkAll();
|
|
1466
|
+
return (await autosave?.flush()) ?? "nothing";
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
async function start() {
|
|
1471
|
+
hideBlocks(MAP.hidden);
|
|
1472
|
+
const view = takeView(MAP.slug);
|
|
1473
|
+
const own = await recover({ map: MAP, storage: local, keys: [pendingKey(MAP.slug, TAB)] }); // what this tab left unsaved, put back first
|
|
1474
|
+
const notice = own.outdated ? outdatedNotice() : view?.notice; // what a component wrote by a manifest that changed, given up
|
|
1475
|
+
const note = (words) => say(words, "note", 6000);
|
|
1476
|
+
if (own.sent) return reloadView(MAP.slug, view, own.outdated ? notice : null); // show what was put back
|
|
1477
|
+
// The outline (目录) comes with the page, before its blocks are made editable: it reads the headings
|
|
1478
|
+
// by their units' kinds, which enter() marks (captureLayout), marked here first from the map (the same
|
|
1479
|
+
// marks); drawn at once while the page's layout is still clean (after enter() its first reading of
|
|
1480
|
+
// the window's width and the headings' places laid the whole page out: 24-128 ms), and shown in a
|
|
1481
|
+
// frame of its own before enter() holds the main thread (100 ms to 1.5 s on a long report). What
|
|
1482
|
+
// enter() changes in the headings its observer sees: it reads them again on the next frame. Where the
|
|
1483
|
+
// reader was (a page shown again) is scrolled to first, so that frame has the page and the outline's
|
|
1484
|
+
// current row there (restoreView scrolls there again once the page edits).
|
|
1485
|
+
if (!SOLO) {
|
|
1486
|
+
if (view) scrollTo(view.x, view.y);
|
|
1487
|
+
markKinds(document, MAP);
|
|
1488
|
+
outline = createOutline({ slug: MAP.slug });
|
|
1489
|
+
if (outline.shown && !document.hidden) await painted();
|
|
1490
|
+
}
|
|
1491
|
+
enter();
|
|
1492
|
+
const restored = view ? restoreView(view) : null;
|
|
1493
|
+
if (notice) note(notice);
|
|
1494
|
+
checkLeft(local, MAP.slug, commits.texts()); // words a page it recovered showed that this one does not
|
|
1495
|
+
showLost(); // what could not be put back, at every opening until the reader is done with it
|
|
1496
|
+
if (own.offline) say(t("有上次没保存的修改,但现在连不上预览服务器;连上后刷新页面即可放回。"), "error");
|
|
1497
|
+
else if (own.left) say(t("有上次没保存的修改还没能放回(文件正在被修改,或预览服务器出错);稍后刷新页面会再试。"), "error");
|
|
1498
|
+
await reached("data-doc-ready");
|
|
1499
|
+
await restored; // ready (a workbench may show it now) once where the reader was
|
|
1500
|
+
connect();
|
|
1501
|
+
if (!view) followHash(); // a page shown again stays where the reader was
|
|
1502
|
+
addEventListener("hashchange", () => followHash({ quiet: true }));
|
|
1503
|
+
// The way back or forth over a page alone's jumps (jumpTo), its address the same all along.
|
|
1504
|
+
addEventListener("popstate", (event) => {
|
|
1505
|
+
const state = event.state;
|
|
1506
|
+
if (workbench || SOLO || !state || typeof state !== "object") return;
|
|
1507
|
+
if (state.docPlace && typeof state.docPlace === "object") showPlace(state.docPlace);
|
|
1508
|
+
else if (typeof state.docBlock === "string") revealBlock(state.docBlock, { quiet: true });
|
|
1509
|
+
});
|
|
1510
|
+
annotate?.start();
|
|
1511
|
+
agentChanges?.start();
|
|
1512
|
+
const open = await openTabs();
|
|
1513
|
+
if (open.has(TAB)) { // a duplicated tab: its own id from now on
|
|
1514
|
+
TAB = newTab();
|
|
1515
|
+
session.set(keys.tab, TAB);
|
|
1516
|
+
}
|
|
1517
|
+
const prefix = pendingKey(MAP.slug, "");
|
|
1518
|
+
const left = local.keys().filter((key) => key.startsWith(prefix) && key !== pendingKey(MAP.slug, TAB) && !open.has(key.slice(prefix.length)));
|
|
1519
|
+
if (left.length === 0) return;
|
|
1520
|
+
const others = await recover({ map: MAP, storage: local, keys: left });
|
|
1521
|
+
if (others.outdated) note(outdatedNotice());
|
|
1522
|
+
if (others.sent) autosave.heard(null, true); // shown once nothing waits here (checked then)
|
|
1523
|
+
else checkLeft(local, MAP.slug, commits.texts());
|
|
1524
|
+
if (readLost(local, MAP.slug).length > 0) showLost();
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
// Back where the reader was: the scroll position, the detail cards open and the caret in the block
|
|
1528
|
+
// it was in. Done once the kit has drawn (its charts, which may have moved things) and the scroll
|
|
1529
|
+
// position is put again. Where the page before this one did not hold the focus (`data-no-focus`, the
|
|
1530
|
+
// workbench's: a dialog stands open over it, a field holds it) what holds it keeps it, and the keys;
|
|
1531
|
+
// the caret comes back when the focus does, by anything but a press, which puts it where it lands.
|
|
1532
|
+
function restoreView({ x, y, top = null, unit, self, offset, modals = [] }) {
|
|
1533
|
+
const put = () => (top ? goBackTo(window, { x, y, top }) : scrollTo(x, y)); // the way back from another document: by the place the workbench kept (reading-place.mjs)
|
|
1534
|
+
put();
|
|
1535
|
+
const outside = window.frameElement?.hasAttribute("data-no-focus") ? window.frameElement.ownerDocument.activeElement : null;
|
|
1536
|
+
modalOps().reopen(modals);
|
|
1537
|
+
outside?.focus();
|
|
1538
|
+
const block = unit ? document.querySelector(`[data-block-id="${CSS.escape(unit)}"]`) : null;
|
|
1539
|
+
const host = block && (self ? block : [...block.querySelectorAll("[contenteditable='true']")].find(isHost));
|
|
1540
|
+
if (host && isHost(host) && offset !== null) {
|
|
1541
|
+
const caretBack = () => {
|
|
1542
|
+
host.focus({ preventScroll: true });
|
|
1543
|
+
caretAtOffset(host, offset);
|
|
1544
|
+
};
|
|
1545
|
+
if (outside) addEventListener("focus", () => { if (!pressing && host.isConnected && document.activeElement === document.body) caretBack(); }, { once: true });
|
|
1546
|
+
else caretBack();
|
|
1547
|
+
}
|
|
1548
|
+
return reached("data-doc-drawn").then(put); // charts drawn since may have moved things
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
// What pages of this report could not put back into the file (recover: lostKey), the reader's
|
|
1552
|
+
// words, listed at every opening until the reader says they are dealt with; closing the panel only
|
|
1553
|
+
// hides it.
|
|
1554
|
+
const LOST = {
|
|
1555
|
+
changed: t("文件里的这一段已被修改,你的版本如下:"),
|
|
1556
|
+
missing: t("文件里的这一块已被删除,你的版本如下:"),
|
|
1557
|
+
insert: t("新建的这一块放不进文件(周围的块已被改动或删除):"),
|
|
1558
|
+
draft: t("还没写完、没能保存的块:"),
|
|
1559
|
+
reverted: t("被 AI 的写入改回去的修改,你的版本如下:"),
|
|
1560
|
+
rebase: t("上次没等到保存结果的新块里,后来写的内容:"),
|
|
1561
|
+
refused: t("没能保存的修改:"),
|
|
1562
|
+
unreadable: t("读不出来的暂存内容:"),
|
|
1563
|
+
};
|
|
1564
|
+
function showLost() {
|
|
1565
|
+
const lost = readLost(local, MAP.slug);
|
|
1566
|
+
if (lost.length === 0) return;
|
|
1567
|
+
showPanel(t("{0} 处修改没能放回", lost.length));
|
|
1568
|
+
for (const item of lost) {
|
|
1569
|
+
// What a component wrote, where its block stands on the page: that block holding it, to copy and paste
|
|
1570
|
+
// back, as a conflict's card offers it; its parts' insides are no text to read.
|
|
1571
|
+
const instance = typeof item.unit === "string" && item.parts ? document.querySelector(`main [data-block-id="${CSS.escape(item.unit)}"]`) : null;
|
|
1572
|
+
const block = instance ? pageOfUnit(instance)?.withData?.(instance, item.parts) ?? null : null;
|
|
1573
|
+
if (block) {
|
|
1574
|
+
panelItem(
|
|
1575
|
+
element("p", { text: t("文件里的这一块已被修改:复制你的版本,再粘贴到页面上。") }),
|
|
1576
|
+
element("div", { class: "ge-actions" }, button(t("复制这一块"), () => { if (!clipboard.copyBlocks([block])) say(t("没能完成,请再试一次。"), "error"); })),
|
|
1577
|
+
);
|
|
1578
|
+
continue;
|
|
1579
|
+
}
|
|
1580
|
+
const text = item.text ?? String(item.html ?? "").split(/\n/).map((line) => plainText(line)).filter(Boolean).join("\n");
|
|
1581
|
+
panelItem(
|
|
1582
|
+
element("p", { text: LOST[item.why] ?? LOST.refused }),
|
|
1583
|
+
element("div", { class: "ge-version", text }),
|
|
1584
|
+
element("div", { class: "ge-actions" }, button(t("复制文字"), () => navigator.clipboard?.writeText(text).then(() => say(t("已复制。"))))),
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
panelItem(element("div", { class: "ge-actions" }, button(t("都处理好了,不再保留"), () => {
|
|
1588
|
+
local.remove(lostKey(MAP.slug));
|
|
1589
|
+
closePanel();
|
|
1590
|
+
}, { primary: true })));
|
|
1591
|
+
say(t("有 {0} 处修改没能放回文件,列在右下方:复制保存好再标记处理完。", lost.length), "error"); // said over the panel, which stands first
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
function boot() {
|
|
1595
|
+
connectParts();
|
|
1596
|
+
addStyle(STYLE);
|
|
1597
|
+
formatting = createFormatting({
|
|
1598
|
+
active: () => editing && !composing, say, recordStep, struct, convert: convertHost, cross: crossText,
|
|
1599
|
+
turnIntoAll: groupTargets, convertAll: (hosts, type) => convertAll(hosts, type, { select: false }), selectBlocks: (list) => blocks.select(list, { quiet: true }),
|
|
1600
|
+
annotates: () => Boolean(annotate?.canSelection()), annotate: () => annotate?.annotateSelection(), // the bar's 批注
|
|
1601
|
+
});
|
|
1602
|
+
slash = createSlashMenu({
|
|
1603
|
+
convert: convertHost, insertBelow, inline: inlineInsert, composing: () => composing,
|
|
1604
|
+
// The "+"'s menu closed: its block's hint is its own again (where it still shows one).
|
|
1605
|
+
closed: (host) => { if (host.hasAttribute("data-ge-placeholder")) typing.updatePlaceholder(host); },
|
|
1606
|
+
});
|
|
1607
|
+
atMenu = createAtMenu({ composing: () => composing, page: thisPage, recordText, recordStep });
|
|
1608
|
+
markdown = createMarkdown({ slash, at: atMenu, convert: convertHost, recordStep });
|
|
1609
|
+
blocks = createBlocks({
|
|
1610
|
+
slug: MAP.slug, units, boxes, active, struct, look: lookStep, say, edit: editBlock, turnInto, convert: convertUnit, turnIntoAll: groupTargets, convertAll, add: insert.addAfter, after: insert.paragraphAfter,
|
|
1611
|
+
below: insertBelow, belowTypes: belowTargets,
|
|
1612
|
+
blank: insert.clickBlank, nest: (units, delta) => pageObjects().some((page) => page.nestBlocks?.(units, delta)),
|
|
1613
|
+
keepsHandle: (node) => tables?.holds(node) ?? false,
|
|
1614
|
+
addNear,
|
|
1615
|
+
// 批注 on blocks: the button beside one (annotate/).
|
|
1616
|
+
notes: { can: (units) => annotate?.canBlocks(units) ?? false, at: (unit) => annotate?.markerSpot(unit) ?? null, write: (units) => annotate?.annotateBlocks(units) },
|
|
1617
|
+
copyLink: SOLO ? null : copyBlockLink, // what ?solo shows (写作偏好) only its dialog opens: no address leads there
|
|
1618
|
+
linkAhead: () => holdPages(thisPage),
|
|
1619
|
+
});
|
|
1620
|
+
resize = createResize({ units, active, look: lookStep, say, owns: (node) => tables?.owns(node) ?? false });
|
|
1621
|
+
gaps = createColumnGaps({ active, struct, look: lookStep, say });
|
|
1622
|
+
for (const page of pageObjects()) page.install?.(); // the round "+" at a flowchart node's right, renaming Sheet tabs
|
|
1623
|
+
tables = createTableEditing({ active, struct, look: lookStep, selectBlock: (unit) => blocks.select(unit), resizeTable: (event, unit, axis) => resize.startTable(event, unit, axis), say, ownCells: (data) => clipboard.ownCells(data), typedKey });
|
|
1624
|
+
selectAll = createSelectAll({ blocks, say, cells: { table: () => tables.selectedTable, clear: () => tables.clear() } });
|
|
1625
|
+
createFrame(() => editing);
|
|
1626
|
+
createFlags(() => editing);
|
|
1627
|
+
// A page of its own in a frame (?solo) has no session, and so no 批注: nothing would show them.
|
|
1628
|
+
if (!SOLO) annotate = createAnnotate({
|
|
1629
|
+
slug: MAP.slug, token: MAP.token, isEditing: () => editing, say,
|
|
1630
|
+
picked: () => blocks?.picked ?? [], wholePage: () => topStep(document).units, crossTexts: () => crossText.texts(),
|
|
1631
|
+
unpick() {
|
|
1632
|
+
crossText.letGo();
|
|
1633
|
+
if (blocks?.picked.length > 0) blocks.clear();
|
|
1634
|
+
if (messageKind() === "note") settle();
|
|
1635
|
+
},
|
|
1636
|
+
// The blocks it is about are named with the next commit (commit.mjs idFor), at once.
|
|
1637
|
+
idFor(element) {
|
|
1638
|
+
const named = element.getAttribute("data-block-id");
|
|
1639
|
+
const id = commits?.idFor(element) ?? null;
|
|
1640
|
+
if (!named) markChanged(true); // a commit names it now
|
|
1641
|
+
return id;
|
|
1642
|
+
},
|
|
1643
|
+
changed: () => workbench?.update(),
|
|
1644
|
+
removed: (items) => workbench?.annotationsRemoved(items),
|
|
1645
|
+
});
|
|
1646
|
+
// Read at once, drawn once the page is ready; commits and autosave are made as the page edits (enter).
|
|
1647
|
+
if (!SOLO) agentChanges = createAgentChanges({
|
|
1648
|
+
slug: MAP.slug, token: MAP.token, commits: () => commits, autosave: () => autosave, say, session,
|
|
1649
|
+
across: crossText, // a selection across blocks, kept on its words as the marks go and come
|
|
1650
|
+
flush: async () => {
|
|
1651
|
+
checkAll();
|
|
1652
|
+
return (await autosave?.flush()) ?? "nothing";
|
|
1653
|
+
},
|
|
1654
|
+
});
|
|
1655
|
+
document.addEventListener("pointerup", (event) => {
|
|
1656
|
+
pressing = false;
|
|
1657
|
+
formatting.released();
|
|
1658
|
+
// A click at an edge of inline code or an entry: the caret on the side it was made on, when the
|
|
1659
|
+
// press came down on that text; a press elsewhere that put the caret there (a card's backdrop,
|
|
1660
|
+
// which closed it and put the caret at its entry; the page's empty space) leaves it at the stop
|
|
1661
|
+
// it is at.
|
|
1662
|
+
const selection = getSelection();
|
|
1663
|
+
const host = editing && event.button === 0 && selection.rangeCount && selection.isCollapsed ? editable(selection.anchorNode) : null;
|
|
1664
|
+
const pressed = pressedText;
|
|
1665
|
+
pressedText = null;
|
|
1666
|
+
if (host && outOfAtom(event.clientX)) return; // a click beside a formula that put the caret in it
|
|
1667
|
+
if (!host || !textFilters(host)) return;
|
|
1668
|
+
if (pressed && (pressed === host || pressed.contains(host))) codeClick(host, event.clientX);
|
|
1669
|
+
else settleCodeEdge(host);
|
|
1670
|
+
}, true);
|
|
1671
|
+
document.addEventListener("focusin", passTab, true);
|
|
1672
|
+
document.addEventListener("compositionstart", (event) => {
|
|
1673
|
+
// On an empty formula block (an input method that sends no key before it): the block selected, as a
|
|
1674
|
+
// key there selects it (onKeydown), and the composition has no text to go into. On one drawn, whose
|
|
1675
|
+
// chip is all it holds, the browser begins none, and what it puts in is refused as anything typed
|
|
1676
|
+
// there is (components/math/page.mjs).
|
|
1677
|
+
const host = editing ? hostOf(event) : null;
|
|
1678
|
+
if (texOnly(host)) return void blocks.select(unitElement(host));
|
|
1679
|
+
composing = true;
|
|
1680
|
+
formatting.hide();
|
|
1681
|
+
atMenu.follow(); // its hint goes
|
|
1682
|
+
}, true);
|
|
1683
|
+
addEventListener("resize", () => {
|
|
1684
|
+
formatting.schedule();
|
|
1685
|
+
atMenu.follow();
|
|
1686
|
+
});
|
|
1687
|
+
// The workbench took the focus: no menu stays, holding commits back, where no press reaches it, and a
|
|
1688
|
+
// formula's box goes as for a press elsewhere. Another window taking it leaves the box, for what is copied there.
|
|
1689
|
+
addEventListener("blur", () => {
|
|
1690
|
+
closeTyped();
|
|
1691
|
+
if (window.frameElement?.ownerDocument.hasFocus()) keepPanels();
|
|
1692
|
+
});
|
|
1693
|
+
document.addEventListener("keydown", onKeydown, true);
|
|
1694
|
+
document.addEventListener("beforeinput", onBeforeInput, true);
|
|
1695
|
+
document.addEventListener("input", onInput, true);
|
|
1696
|
+
document.addEventListener("compositionend", onCompositionEnd, true);
|
|
1697
|
+
document.addEventListener("paste", (event) => {
|
|
1698
|
+
compositionOver(event);
|
|
1699
|
+
if (annotate?.holds(event.target) || keptByComponent(event.target)) return; // a 批注's card is a field of its own, and so is what a component keeps
|
|
1700
|
+
if (!atMenu.paste(event)) clipboard.paste(event);
|
|
1701
|
+
}, true);
|
|
1702
|
+
const copied = (event) => { if (!annotate?.holds(event.target) && !keptByComponent(event.target)) clipboard.copy(event); };
|
|
1703
|
+
document.addEventListener("copy", copied, true);
|
|
1704
|
+
document.addEventListener("cut", copied, true);
|
|
1705
|
+
document.addEventListener("selectionchange", onSelectionChange);
|
|
1706
|
+
document.addEventListener("mouseover", scheduleHint, true);
|
|
1707
|
+
document.documentElement.addEventListener("mouseleave", hideHint); // a pointer gone into a workbench moves nothing here
|
|
1708
|
+
document.addEventListener("mousemove", (event) => {
|
|
1709
|
+
trackPointer(event);
|
|
1710
|
+
}, { passive: true, capture: true });
|
|
1711
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
1712
|
+
// A press beside a line of text on its block's empty space puts the caret where that line is
|
|
1713
|
+
// nearest, where the browser would put it elsewhere (caret.mjs besideLine); one at an edge of
|
|
1714
|
+
// inline code, on the side of the edge it was made on at once (typing/code-edges.mjs codePress);
|
|
1715
|
+
// one in a block holding chips of the text, for a drag to go by whole chips (chipsPress). The press
|
|
1716
|
+
// is the editor's then, and so is a drag from it (followPress).
|
|
1717
|
+
document.addEventListener("mousedown", (event) => {
|
|
1718
|
+
const host = editing && event.button === 0 && !event.shiftKey && event.detail < 2 ? editable(event.target) : null;
|
|
1719
|
+
if (!host) return;
|
|
1720
|
+
const place = besideLine(host, event.clientX, event.clientY) ?? chipsPress(host, event.clientX, event.clientY);
|
|
1721
|
+
if (place) {
|
|
1722
|
+
event.preventDefault();
|
|
1723
|
+
host.focus({ preventScroll: true });
|
|
1724
|
+
selectAt(place, event.clientY);
|
|
1725
|
+
followPress(host, place);
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
const stop = textFilters(host) && codePress(host, event.clientX, event.clientY);
|
|
1729
|
+
if (!stop) return;
|
|
1730
|
+
event.preventDefault();
|
|
1731
|
+
followPress(host, stop);
|
|
1732
|
+
}, true);
|
|
1733
|
+
addEventListener("scroll", (event) => {
|
|
1734
|
+
hideHint();
|
|
1735
|
+
if (formatting.visible) formatting.schedule();
|
|
1736
|
+
if (slash.host && !slash.contains(event.target)) slash.update(); // it follows its block
|
|
1737
|
+
if (atMenu.host && !atMenu.contains(event.target)) atMenu.follow();
|
|
1738
|
+
}, { passive: true, capture: true });
|
|
1739
|
+
document.addEventListener("drop", (event) => { if (editing && editable(event.target)) event.preventDefault(); }, true);
|
|
1740
|
+
// A click goes first to the component of what it came down on: a code block's fold arrow or wrap
|
|
1741
|
+
// button (an edit), a disclosure's title being typed in, a tab being renamed.
|
|
1742
|
+
document.addEventListener("click", (event) => {
|
|
1743
|
+
if (editing && pageAt(event.target)?.click?.(event)) return;
|
|
1744
|
+
const link = editing ? linkOf(event.target) : null;
|
|
1745
|
+
if (link) followLink(event, link);
|
|
1746
|
+
}, true);
|
|
1747
|
+
// Keep the editor UI usable while a modal dialog is open.
|
|
1748
|
+
new MutationObserver(() => {
|
|
1749
|
+
const target = layer();
|
|
1750
|
+
for (const node of [noticeNode(), formatting.node, panelNode(), ...linkNodes(), formulaBoxNode(), panelOpen()?.panel, conflicts?.node(), ...slash.nodes, ...atMenu.nodes, ...blocks.nodes, ...resize.nodes, ...gaps.nodes, ...tables.nodes, ...pageObjects().flatMap((page) => page.nodes ?? [])]) if (node && node.parentNode !== target) target.append(node);
|
|
1751
|
+
}).observe(document.body, { subtree: true, attributes: true, attributeFilter: ["open"] });
|
|
1752
|
+
window.addEventListener("doc-source-changed", (event) => autosave?.heard(event.detail?.version ?? null));
|
|
1753
|
+
window.addEventListener("doc-assets-changed", () => say(t("页面的样式或编辑器已更新,刷新页面即可看到。"), "note", 6000));
|
|
1754
|
+
// A component package the page holds an instance of changed: what it runs of it is another page's.
|
|
1755
|
+
window.addEventListener("doc-components-changed", () => autosave?.reloadSoon());
|
|
1756
|
+
// What the reader's settings offer changed: the menus follow, and no page is shown again for it.
|
|
1757
|
+
window.addEventListener("doc-settings-changed", (event) => hideBlocks(event.detail.hidden));
|
|
1758
|
+
// Only while something waits: it is in the crash buffer too, so this is a courtesy.
|
|
1759
|
+
window.addEventListener("beforeunload", (event) => {
|
|
1760
|
+
const state = autosave?.state;
|
|
1761
|
+
if (state && (state.waiting > 0 || state.syncing) && !state.reloading && !released) event.preventDefault();
|
|
1762
|
+
});
|
|
1763
|
+
// Closing the page: keep what waits and try a last commit. Hiding it: commit now.
|
|
1764
|
+
window.addEventListener("pagehide", () => {
|
|
1765
|
+
checkAll();
|
|
1766
|
+
autosave?.leave();
|
|
1767
|
+
annotate?.leave();
|
|
1768
|
+
agentChanges?.leave();
|
|
1769
|
+
});
|
|
1770
|
+
document.addEventListener("visibilitychange", () => {
|
|
1771
|
+
if (!document.hidden || !autosave) return;
|
|
1772
|
+
checkNow();
|
|
1773
|
+
autosave.flush();
|
|
1774
|
+
});
|
|
1775
|
+
// Once the shared kit has rendered formulas.
|
|
1776
|
+
setTimeout(start, 0);
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
if (document.readyState === "complete") boot();
|
|
1780
|
+
else window.addEventListener("load", boot, { once: true });
|