@heycys/gallery 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +1 -1
- package/browser/editor/components/columns/page.mjs +4 -4
- package/browser/editor/components/embed/index.mjs +2 -2
- package/browser/editor/components/flowchart/add.mjs +7 -6
- package/browser/editor/components/flowchart/page.mjs +1 -1
- package/browser/editor/components/icons.mjs +8 -0
- package/browser/editor/components/image/crop.mjs +6 -9
- package/browser/editor/components/installed/index.mjs +11 -4
- package/browser/editor/components/installed/page.mjs +27 -16
- package/browser/editor/components/installed/served.mjs +6 -5
- package/browser/editor/components/instances.mjs +18 -0
- package/browser/editor/components/list/page.mjs +3 -6
- package/browser/editor/components/registry.mjs +13 -4
- package/browser/editor/components/table/index.mjs +59 -2
- package/browser/editor/components/table/page.mjs +1 -1
- package/browser/editor/page/annotate/card.mjs +3 -6
- package/browser/editor/page/autosave.mjs +310 -37
- package/browser/editor/page/board/client.mjs +57 -0
- package/browser/editor/page/board/hint.mjs +145 -0
- package/browser/editor/page/board/history.mjs +119 -0
- package/browser/editor/page/board/index.mjs +970 -0
- package/browser/editor/page/board/settings.mjs +209 -0
- package/browser/editor/page/caret.mjs +1 -1
- package/browser/editor/page/changes/bar.mjs +6 -14
- package/browser/editor/page/changes/index.mjs +2 -2
- package/browser/editor/page/clipboard/import/html.mjs +29 -6
- package/browser/editor/page/clipboard/import/inline.mjs +32 -0
- package/browser/editor/page/clipboard/import/markdown.mjs +58 -13
- package/browser/editor/page/clipboard/import/read.mjs +6 -1
- package/browser/editor/page/clipboard/import/to-blocks.mjs +44 -12
- package/browser/editor/page/clipboard/import/yuque.mjs +622 -0
- package/browser/editor/page/clipboard/index.mjs +124 -13
- package/browser/editor/page/clipboard/own/blocks.mjs +11 -11
- package/browser/editor/page/clipboard/own/chart-keys.mjs +41 -28
- package/browser/editor/page/clipboard/own/selection.mjs +1 -1
- package/browser/editor/page/columns/index.mjs +48 -46
- package/browser/editor/page/commit.mjs +80 -22
- package/browser/editor/page/conflicts.mjs +7 -7
- package/browser/editor/page/core.mjs +31 -15
- package/browser/editor/page/custom-page.mjs +106 -0
- package/browser/editor/page/editor.mjs +51 -14
- package/browser/editor/page/emoji-picker/styles.mjs +7 -12
- package/browser/editor/page/emoji-picker/upload.mjs +2 -2
- package/browser/editor/page/handle/index.mjs +5 -8
- package/browser/editor/page/history.mjs +24 -4
- package/browser/editor/page/links/index.mjs +5 -3
- package/browser/editor/page/links/pages.mjs +27 -9
- package/browser/editor/page/look.mjs +54 -3
- package/browser/editor/page/margin.mjs +18 -3
- package/browser/editor/page/media-menu/index.mjs +6 -7
- package/browser/editor/page/menu.mjs +18 -7
- package/browser/editor/page/notices.mjs +2 -2
- package/browser/editor/page/page-objects.mjs +13 -2
- package/browser/editor/page/panel.mjs +4 -9
- package/browser/editor/page/present-links.mjs +11 -2
- package/browser/editor/page/resize/index.mjs +42 -29
- package/browser/editor/page/served.mjs +14 -0
- package/browser/editor/page/session.mjs +48 -4
- package/browser/editor/page/slash-menu/index.mjs +3 -3
- package/browser/editor/page/table/cells.mjs +102 -46
- package/browser/editor/page/table/icons.mjs +2 -0
- package/browser/editor/page/table/index.mjs +29 -10
- package/browser/editor/page/table/pictures.mjs +299 -0
- package/browser/editor/page/table/views.mjs +150 -0
- package/browser/editor/page/text-select/index.mjs +26 -12
- package/browser/editor/page/todo-writes.mjs +75 -0
- package/browser/editor/page/typing/index.mjs +53 -3
- package/browser/editor/page/ui.mjs +13 -0
- package/browser/shared/block-packs.mjs +404 -0
- package/browser/shared/board-places.mjs +224 -0
- package/browser/shared/card-size.mjs +59 -0
- package/browser/shared/colors.mjs +3 -2
- package/browser/shared/component-catalog.mjs +323 -46
- package/browser/shared/component-source.mjs +37 -0
- package/browser/shared/components/index.mjs +12 -3
- package/browser/shared/components/installed.mjs +35 -3
- package/browser/shared/components/page.mjs +18 -8
- package/browser/shared/components/state.mjs +5 -1
- package/browser/shared/document-look.mjs +10 -5
- package/browser/shared/drafts.mjs +432 -0
- package/browser/shared/extension-sidebar.mjs +126 -0
- package/browser/shared/extensions.mjs +79 -0
- package/browser/shared/file-table.mjs +101 -0
- package/browser/shared/i18n/en.mjs +307 -27
- package/browser/shared/json-layout.mjs +47 -0
- package/browser/shared/net-cache.mjs +133 -0
- package/browser/shared/net-keys.mjs +32 -0
- package/browser/shared/net-policy.mjs +177 -0
- package/browser/shared/outline-shape.mjs +128 -0
- package/browser/shared/outline.mjs +331 -67
- package/browser/shared/pack-manifest.mjs +77 -0
- package/browser/shared/page-address.mjs +6 -3
- package/browser/shared/page-cards.mjs +308 -0
- package/browser/shared/page-links.mjs +69 -6
- package/browser/shared/pages.mjs +388 -0
- package/browser/shared/paths.mjs +6 -0
- package/browser/shared/preview-server.mjs +3 -7
- package/browser/shared/session.mjs +8 -1
- package/browser/shared/shipped-blocks.mjs +299 -0
- package/browser/shared/table-chart.mjs +33 -1
- package/browser/shared/todos.mjs +83 -0
- package/browser/shared/user-directory.mjs +24 -0
- package/browser/shared/versions.mjs +14 -0
- package/browser/shared/web-links.mjs +134 -0
- package/browser/shared/workspace-columns.mjs +120 -0
- package/browser/shared/workspace-name.mjs +154 -0
- package/browser/shared/workspace-query.mjs +125 -0
- package/browser/shared/workspace-registry.mjs +272 -0
- package/browser/shared/workspace.mjs +171 -20
- package/dist/chunk-2NRBY6AJ.mjs +2 -0
- package/dist/chunk-32ZUEFZ7.mjs +2 -0
- package/dist/chunk-35NQ4PLQ.mjs +2 -0
- package/dist/chunk-3KJHLHUO.mjs +2 -0
- package/dist/chunk-3NJACR4W.mjs +2 -0
- package/dist/chunk-4AV456RQ.mjs +2 -0
- package/dist/chunk-4OZWXZE5.mjs +5 -0
- package/dist/chunk-5XYHDBJ5.mjs +2 -0
- package/dist/chunk-5ZJA7CXT.mjs +2 -0
- package/dist/chunk-6A53MZYZ.mjs +2 -0
- package/dist/chunk-6EXH3ORZ.mjs +166 -0
- package/dist/chunk-6EZ7FNIK.mjs +3 -0
- package/dist/{chunk-V5JTW2LH.mjs → chunk-6QBHU4DF.mjs} +1 -1
- package/dist/{chunk-Y6RLRPG3.mjs → chunk-6VTU6KEO.mjs} +1 -1
- package/dist/chunk-B4P2CM2J.mjs +3 -0
- package/dist/{chunk-FDLXYYG4.mjs → chunk-BPL64HGA.mjs} +1 -1
- package/dist/chunk-BVEFGE4B.mjs +2 -0
- package/dist/{chunk-LS4F7JUM.mjs → chunk-BZ5RAF7L.mjs} +1 -1
- package/dist/chunk-CRJEG7MT.mjs +2 -0
- package/dist/{chunk-LCTHTZIL.mjs → chunk-D4CKRD3V.mjs} +91 -91
- package/dist/chunk-D4EURAX4.mjs +4 -0
- package/dist/chunk-DQCE7TJY.mjs +5 -0
- package/dist/chunk-DR73Y2UJ.mjs +3 -0
- package/dist/{chunk-TMYZB6AV.mjs → chunk-EKNHM3P4.mjs} +1 -1
- package/dist/{chunk-GB5SOYQJ.mjs → chunk-ERLAIBOA.mjs} +2 -2
- package/dist/chunk-EURLTOTZ.mjs +4 -0
- package/dist/chunk-FJYN7RTH.mjs +4 -0
- package/dist/chunk-FWNRPMCJ.mjs +12 -0
- package/dist/chunk-FWW4CSAZ.mjs +2 -0
- package/dist/{chunk-2B5E4CDJ.mjs → chunk-GPIWQ4X4.mjs} +1 -1
- package/dist/chunk-GY3FAUVM.mjs +2 -0
- package/dist/chunk-HLYV7RIH.mjs +2 -0
- package/dist/chunk-HMYTM3GT.mjs +15 -0
- package/dist/chunk-IA4PSIMD.mjs +2 -0
- package/dist/chunk-JEUK3ZO7.mjs +2 -0
- package/dist/chunk-KTKHRC6H.mjs +2 -0
- package/dist/chunk-L7UP52JO.mjs +2 -0
- package/dist/{chunk-SYGN7GLU.mjs → chunk-MHW7ZKOU.mjs} +1 -1
- package/dist/chunk-N76HWBA3.mjs +10 -0
- package/dist/chunk-N7AZZLGT.mjs +548 -0
- package/dist/chunk-NAMN3RAF.mjs +2 -0
- package/dist/{chunk-5ZKJ3H4P.mjs → chunk-NFMCP2HI.mjs} +3 -3
- package/dist/chunk-NG375ILQ.mjs +2 -0
- package/dist/chunk-NVRNLK25.mjs +6 -0
- package/dist/chunk-NWFBE2YA.mjs +2 -0
- package/dist/chunk-NWR2LT5B.mjs +2 -0
- package/dist/chunk-OJKMINIU.mjs +20 -0
- package/dist/{chunk-4FS7Z7WR.mjs → chunk-OUUP3WKT.mjs} +1 -1
- package/dist/{chunk-BGGDRNYQ.mjs → chunk-OW5NWPOJ.mjs} +1 -1
- package/dist/chunk-PAREWQGB.mjs +4 -0
- package/dist/chunk-POQSE4C2.mjs +2 -0
- package/dist/chunk-PUOQ65NC.mjs +2 -0
- package/dist/{chunk-XNROSH2T.mjs → chunk-PVER7LSC.mjs} +2 -2
- package/dist/chunk-PZDLJJTJ.mjs +3 -0
- package/dist/chunk-Q47PLKIJ.mjs +15 -0
- package/dist/chunk-Q4UEPUWF.mjs +2 -0
- package/dist/chunk-QKXMPUXC.mjs +2 -0
- package/dist/chunk-QOUEFUCH.mjs +16 -0
- package/dist/chunk-RFDZXFJF.mjs +13 -0
- package/dist/chunk-RKPWLGZ3.mjs +4 -0
- package/dist/chunk-RMECZA2K.mjs +2 -0
- package/dist/{chunk-7DTINW6U.mjs → chunk-SQ6Y2ZMA.mjs} +2 -2
- package/dist/chunk-SX4LJYT6.mjs +8 -0
- package/dist/chunk-SXHBYZST.mjs +16 -0
- package/dist/chunk-TANZRINB.mjs +2 -0
- package/dist/chunk-TEZAWDSQ.mjs +2 -0
- package/dist/chunk-UBBRTJ2R.mjs +24 -0
- package/dist/chunk-UFNYFFKO.mjs +20 -0
- package/dist/chunk-UGWYKHBL.mjs +170 -0
- package/dist/chunk-UKSHDZZ6.mjs +2 -0
- package/dist/chunk-UNOYVIYR.mjs +2 -0
- package/dist/{chunk-E7LWZRXY.mjs → chunk-V6RIYQZZ.mjs} +1 -1
- package/dist/chunk-VJPFVNNW.mjs +15 -0
- package/dist/chunk-VS3ZMVGX.mjs +5 -0
- package/dist/chunk-VTKNEYT3.mjs +6 -0
- package/dist/chunk-VXACTLO3.mjs +3 -0
- package/dist/chunk-YI43RG4P.mjs +2 -0
- package/dist/chunk-YQGT4AB2.mjs +6 -0
- package/dist/chunk-ZB4H7ZN4.mjs +2 -0
- package/dist/{chunk-IVDLMMBD.mjs → chunk-ZHEHPMBK.mjs} +1 -1
- package/dist/chunk-ZTHICSSF.mjs +2 -0
- package/dist/chunk-ZUNVAXLH.mjs +2 -0
- package/dist/chunk-ZWPMCK7P.mjs +2 -0
- package/dist/cli.mjs +1 -1
- package/dist/server.mjs +1 -1
- package/dist/{chunk-U6KNPJPR.mjs → update-fetch.mjs} +1 -1
- package/dist/worker.mjs +1 -1
- package/kit/board.css +95 -0
- package/kit/board.js +250 -0
- package/kit/charts.js +5 -71
- package/kit/code.js +14 -14
- package/kit/report.css +255 -17
- package/kit/runtime.js +1600 -125
- package/kit/state.js +10 -1
- package/kit/tables.js +128 -43
- package/kit/ui.js +77 -35
- package/package.json +2 -1
- package/packs/gallery/card/component.json +14 -0
- package/packs/gallery/card/example.html +10 -0
- package/packs/gallery/card/preview.svg +1 -0
- package/packs/gallery/card/style.css +6 -0
- package/packs/gallery/card/template.html +6 -0
- package/packs/gallery/card/writing.md +14 -0
- package/packs/gallery/chart-card/component.json +15 -0
- package/packs/gallery/chart-card/example.html +14 -0
- package/packs/gallery/chart-card/kit.js +130 -0
- package/packs/gallery/chart-card/preview.svg +1 -0
- package/packs/gallery/chart-card/style.css +42 -0
- package/packs/gallery/chart-card/template.html +13 -0
- package/packs/gallery/chart-card/writing.md +20 -0
- package/packs/gallery/chart-view/component.json +20 -0
- package/packs/gallery/chart-view/example.html +13 -0
- package/packs/gallery/chart-view/kit.js +355 -0
- package/packs/gallery/chart-view/preview.svg +1 -0
- package/packs/gallery/chart-view/style.css +94 -0
- package/packs/gallery/chart-view/template.html +3 -0
- package/packs/gallery/chart-view/writing.md +22 -0
- package/packs/gallery/countdown/component.json +18 -0
- package/packs/gallery/countdown/example.html +3 -0
- package/packs/gallery/countdown/kit.js +156 -0
- package/packs/gallery/countdown/preview.svg +1 -0
- package/packs/gallery/countdown/style.css +130 -0
- package/packs/gallery/countdown/template.html +3 -0
- package/packs/gallery/countdown/writing.md +22 -0
- package/packs/gallery/habit/component.json +14 -0
- package/packs/gallery/habit/example.html +3 -0
- package/packs/gallery/habit/kit.js +295 -0
- package/packs/gallery/habit/preview.svg +1 -0
- package/packs/gallery/habit/style.css +246 -0
- package/packs/gallery/habit/template.html +3 -0
- package/packs/gallery/habit/writing.md +21 -0
- package/packs/gallery/heatmap/component.json +18 -0
- package/packs/gallery/heatmap/example.html +3 -0
- package/packs/gallery/heatmap/kit.js +351 -0
- package/packs/gallery/heatmap/preview.svg +1 -0
- package/packs/gallery/heatmap/style.css +201 -0
- package/packs/gallery/heatmap/template.html +3 -0
- package/packs/gallery/heatmap/writing.md +21 -0
- package/packs/gallery/kanban/component.json +18 -0
- package/packs/gallery/kanban/example.html +12 -0
- package/packs/gallery/kanban/kit.js +540 -0
- package/packs/gallery/kanban/preview.svg +1 -0
- package/packs/gallery/kanban/style.css +212 -0
- package/packs/gallery/kanban/template.html +3 -0
- package/packs/gallery/kanban/writing.md +20 -0
- package/packs/gallery/metric/component.json +22 -0
- package/packs/gallery/metric/example.html +15 -0
- package/packs/gallery/metric/kit.js +248 -0
- package/packs/gallery/metric/preview.svg +1 -0
- package/packs/gallery/metric/style.css +92 -0
- package/packs/gallery/metric/template.html +3 -0
- package/packs/gallery/metric/writing.md +23 -0
- package/packs/gallery/note/component.json +14 -0
- package/packs/gallery/note/example.html +3 -0
- package/packs/gallery/note/kit.js +102 -0
- package/packs/gallery/note/preview.svg +1 -0
- package/packs/gallery/note/style.css +76 -0
- package/packs/gallery/note/template.html +3 -0
- package/packs/gallery/note/writing.md +18 -0
- package/packs/gallery/pack.json +8 -0
- package/packs/gallery/recent/component.json +13 -0
- package/packs/gallery/recent/example.html +1 -0
- package/packs/gallery/recent/kit.js +238 -0
- package/packs/gallery/recent/preview.svg +1 -0
- package/packs/gallery/recent/style.css +158 -0
- package/packs/gallery/recent/template.html +1 -0
- package/packs/gallery/recent/writing.md +15 -0
- package/packs/gallery/static/component.json +13 -0
- package/packs/gallery/static/example.html +11 -0
- package/packs/gallery/static/preview.svg +1 -0
- package/packs/gallery/static/style.css +138 -0
- package/packs/gallery/static/template.html +1 -0
- package/packs/gallery/static/writing.md +18 -0
- package/packs/gallery/stats/component.json +13 -0
- package/packs/gallery/stats/example.html +1 -0
- package/packs/gallery/stats/kit.js +166 -0
- package/packs/gallery/stats/preview.svg +1 -0
- package/packs/gallery/stats/style.css +151 -0
- package/packs/gallery/stats/template.html +1 -0
- package/packs/gallery/stats/writing.md +11 -0
- package/packs/gallery/today/component.json +12 -0
- package/packs/gallery/today/example.html +1 -0
- package/packs/gallery/today/kit.js +185 -0
- package/packs/gallery/today/preview.svg +1 -0
- package/packs/gallery/today/style.css +120 -0
- package/packs/gallery/today/template.html +1 -0
- package/packs/gallery/today/writing.md +14 -0
- package/packs/gallery/todos/component.json +19 -0
- package/packs/gallery/todos/example.html +3 -0
- package/packs/gallery/todos/kit.js +342 -0
- package/packs/gallery/todos/preview.svg +1 -0
- package/packs/gallery/todos/style.css +267 -0
- package/packs/gallery/todos/template.html +3 -0
- package/packs/gallery/todos/writing.md +30 -0
- package/packs/gallery/weather/component.json +19 -0
- package/packs/gallery/weather/example.html +3 -0
- package/packs/gallery/weather/kit.js +312 -0
- package/packs/gallery/weather/preview.svg +1 -0
- package/packs/gallery/weather/style.css +258 -0
- package/packs/gallery/weather/template.html +3 -0
- package/packs/gallery/weather/writing.md +26 -0
- package/packs/gallery-soft/pack.json +8 -0
- package/release.json +12 -10
- package/sdk/doc-kit.d.ts +396 -5
- package/sdk/doc-state.d.ts +2 -0
- package/skill/SKILL.md +56 -23
- package/skill/references/block-script.md +140 -0
- package/skill/references/building-blocks.md +257 -0
- package/skill/references/extensions.md +59 -0
- package/skill/references/pages-and-data.md +221 -0
- package/skill/references/writing-html.md +6 -4
- package/skill/sdk/doc-kit.d.ts +396 -5
- package/skill/sdk/doc-state.d.ts +2 -0
- package/web/assets/index-BK2e_07w.js +13 -0
- package/web/assets/index-CWFI83mF.css +1 -0
- package/web/index.html +2 -2
- package/dist/chunk-3HIRPVLU.mjs +0 -2
- package/dist/chunk-5MHMDLZD.mjs +0 -4
- package/dist/chunk-64K7IPLO.mjs +0 -255
- package/dist/chunk-6QXGZM36.mjs +0 -8
- package/dist/chunk-6RGJOMJP.mjs +0 -16
- package/dist/chunk-AJ73XJF3.mjs +0 -2
- package/dist/chunk-AUZGSZJH.mjs +0 -13
- package/dist/chunk-BFE45AKL.mjs +0 -2
- package/dist/chunk-BXO2ZY5P.mjs +0 -2
- package/dist/chunk-CEH4MRCC.mjs +0 -2
- package/dist/chunk-CY2FNNAB.mjs +0 -16
- package/dist/chunk-DFUDV4LX.mjs +0 -2
- package/dist/chunk-DKXG4XDG.mjs +0 -2
- package/dist/chunk-EZQ34M3F.mjs +0 -124
- package/dist/chunk-EZY3WC6Z.mjs +0 -15
- package/dist/chunk-FUKQNOQS.mjs +0 -9
- package/dist/chunk-JPOSLGOC.mjs +0 -6
- package/dist/chunk-L3QKH4OK.mjs +0 -26
- package/dist/chunk-LAE5NQ4B.mjs +0 -2
- package/dist/chunk-LEF6DRCK.mjs +0 -59
- package/dist/chunk-NO5NJCHO.mjs +0 -2
- package/dist/chunk-NQPVAU63.mjs +0 -2
- package/dist/chunk-NSKUTQ5J.mjs +0 -5
- package/dist/chunk-O35FIXRB.mjs +0 -2
- package/dist/chunk-P2CAZPDP.mjs +0 -3
- package/dist/chunk-PL6GHLGD.mjs +0 -15
- package/dist/chunk-QJIR2BLP.mjs +0 -6
- package/dist/chunk-QUPLVPB2.mjs +0 -7
- package/dist/chunk-R7BNENLY.mjs +0 -4
- package/dist/chunk-S6O4UHLC.mjs +0 -2
- package/dist/chunk-SGJWQKEI.mjs +0 -2
- package/dist/chunk-TX6JH7TR.mjs +0 -2
- package/dist/chunk-TYK6VNAB.mjs +0 -10
- package/dist/chunk-UALRNVSE.mjs +0 -4
- package/dist/chunk-V3HXVQEM.mjs +0 -2
- package/dist/chunk-V5VXUJKG.mjs +0 -2
- package/dist/chunk-VBXMADMP.mjs +0 -2
- package/dist/chunk-VNV55RUA.mjs +0 -2
- package/dist/chunk-VPD2PS35.mjs +0 -2
- package/dist/chunk-W4VKN3GF.mjs +0 -7
- package/dist/chunk-W5BXXPDJ.mjs +0 -3
- package/dist/chunk-XUNHRCOG.mjs +0 -6
- package/skill/references/building-components.md +0 -367
- package/web/assets/index-Bh74DfsV.css +0 -1
- package/web/assets/index-UW0RyFFt.js +0 -13
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
import { CELL_ALIGN_CLASSES } from "../../../../shared/components/table.mjs";
|
|
2
|
+
import { t } from "../../../../shared/i18n/index.mjs";
|
|
3
|
+
import { backgroundColor, textColor } from "./colors.mjs";
|
|
4
|
+
import { htmlToBlocks, parseClipboardHTML } from "./html.mjs";
|
|
5
|
+
import { decodeEntities } from "./inline.mjs";
|
|
6
|
+
import { languageFromFence } from "./languages.mjs";
|
|
7
|
+
import { readMarkdownDocument } from "./markdown.mjs";
|
|
8
|
+
import { spec } from "./rich.mjs";
|
|
9
|
+
|
|
10
|
+
// Yuque's (语雀) clipboard, besides Notion's and Feishu's (which come by their HTML): read for its
|
|
11
|
+
// structure, which the HTML it puts beside it says only in part (a formula is its TeX as text, a
|
|
12
|
+
// 提示 a styled div, a board nothing). The user's samples, tests/unit/fixtures/paste/yuque/:
|
|
13
|
+
// ⌘A ⌘C in a document text/plain; text/html, an <ne-clipboard data="…"> first (the document's
|
|
14
|
+
// nodes, ne-inode's JSON, URL-encoded), then its HTML; text/lake (Yuque's own
|
|
15
|
+
// HTML: data-lake-id, <card name value="data:…">, flat lists by data-lake-indent);
|
|
16
|
+
// text/ne-inode (the same JSON)
|
|
17
|
+
// 复制为 Markdown (the toolbar's) text/plain, Markdown with some HTML (pictures, 折叠块, colors),
|
|
18
|
+
// and text/html, its words in one span in Yuque's font
|
|
19
|
+
// Read richest first (readYuque): text/ne-inode's JSON, the <ne-clipboard>'s, text/lake (lakeTree: as
|
|
20
|
+
// that JSON), text/html in the lake form (data-lake-id: an older copy); else Markdown that is Yuque's
|
|
21
|
+
// (yuqueMarkdownCopy: its marks, or its toolbar's span) by markdown.mjs's whole-document reader with
|
|
22
|
+
// `yuque` (what Yuque writes besides: its header), its HTML blocks as the paste reads HTML. Neither:
|
|
23
|
+
// null, and the paste reads the clipboard as any other program's.
|
|
24
|
+
// What each node is (yuqueSpecs: one rule a kind, whichever format it came in):
|
|
25
|
+
// p → text (an image in it a block of its own, the words around it paragraphs; one formula alone
|
|
26
|
+
// in it a formula block: Yuque has no other display formula); h1–h4 → heading 1–4, h5 and h6
|
|
27
|
+
// heading 4 (as Notion's and Markdown's); uli / oli / tli (a level each, flat) → items nested by
|
|
28
|
+
// level (a numbered one's place its number; a to-do's check); quote → quote; alert (提示: info,
|
|
29
|
+
// tips, success, warning, danger) → report_callout, the page's callout of that color (to-blocks.mjs);
|
|
30
|
+
// collapse (折叠块) → toggle (its summary its title); table → table, merged cells kept (a row's
|
|
31
|
+
// spans), each cell's paragraphs lines, its alignment its look; cards: hr → divider, codeblock →
|
|
32
|
+
// code (its mode the language, as a fence names one; its name its title), math → an inline formula
|
|
33
|
+
// (alone in a paragraph a formula block), image → image (its address, downloaded once on the page as
|
|
34
|
+
// any pasted picture; its title its caption when Yuque shows it, never its alt: a file's name),
|
|
35
|
+
// board (画板) and any card the page has no block for → a gray line saying so, with the board's
|
|
36
|
+
// name (its mind map's first node), a card of a web address a link to it.
|
|
37
|
+
// Inline: bold, italic, underline, strikethrough, sup, sub, code, links, color and bgColor (the
|
|
38
|
+
// page's nearest: Yuque's colors are meant, as Word's are; colors.mjs). A soft line break (a "\n"
|
|
39
|
+
// node, a <br>) a line break. One line copied (a paragraph's words, a heading's, an item's) goes in
|
|
40
|
+
// at the caret with its marks, as one line of any page's HTML does.
|
|
41
|
+
|
|
42
|
+
const INODE = "text/ne-inode";
|
|
43
|
+
const LAKE = "text/lake";
|
|
44
|
+
// What is read at most: the clipboard's JSON is anybody's.
|
|
45
|
+
const MAX_NODES = 100000;
|
|
46
|
+
const MAX_DEPTH = 64;
|
|
47
|
+
const MAX_SPAN = 1000;
|
|
48
|
+
|
|
49
|
+
// Characters pasted text loses (html.mjs's: controls, line and paragraph separators, zero-width spaces, tags, lone surrogates).
|
|
50
|
+
const BAD_CHAR = /[\x00-\x08\x0b-\x1f\u2028\u2029\u200b\u{e0000}-\u{e007f}\ud800-\udfff]/gu;
|
|
51
|
+
const clean = (text) => String(text ?? "").replace(BAD_CHAR, "");
|
|
52
|
+
const WEB = /^https?:\/\//i;
|
|
53
|
+
const multiline = (text) => text.trimEnd().includes("\n");
|
|
54
|
+
|
|
55
|
+
// Yuque's 提示 by its kind (lake-alert-<kind>, :::kind) as one of the page's nine; another kind
|
|
56
|
+
// (color1–5) none, the callout's own blue.
|
|
57
|
+
const ALERT_COLORS = { info: "blue", tips: "yellow", success: "green", warning: "orange", danger: "red" };
|
|
58
|
+
const LISTS = { uli: "bulleted_list", oli: "numbered_list", tli: "to_do" };
|
|
59
|
+
const HEADINGS = { h1: "header", h2: "sub_header", h3: "sub_sub_header", h4: "header_4", h5: "header_4", h6: "header_4" };
|
|
60
|
+
const ALIGNS = Object.fromEntries(["left", "center", "right"].map((name, index) => [name, CELL_ALIGN_CLASSES[index]]));
|
|
61
|
+
|
|
62
|
+
// ---------- the clipboard's structure ----------
|
|
63
|
+
|
|
64
|
+
const kids = (node) => (Array.isArray(node?.children) ? node.children : []);
|
|
65
|
+
const nameOf = (node) => (typeof node?.name === "string" ? node.name : "");
|
|
66
|
+
const attrsOf = (node) => (node?.attrs && typeof node.attrs === "object" ? node.attrs : {});
|
|
67
|
+
const isText = (node) => node?.type === "text";
|
|
68
|
+
const isCard = (node) => node?.type === "card";
|
|
69
|
+
const valueOf = (node) => {
|
|
70
|
+
const value = attrsOf(node).value;
|
|
71
|
+
return value && typeof value === "object" ? value : {};
|
|
72
|
+
};
|
|
73
|
+
const count = (value, fallback = 0) => {
|
|
74
|
+
const n = Number.parseInt(value, 10);
|
|
75
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// ne-inode's JSON (a fragment of nodes), or null when it is none or too big to be read.
|
|
79
|
+
function treeOf(text) {
|
|
80
|
+
if (!text) return null;
|
|
81
|
+
let value;
|
|
82
|
+
try {
|
|
83
|
+
value = JSON.parse(text);
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (!value || typeof value !== "object" || !Array.isArray(value.children)) return null;
|
|
88
|
+
let nodes = 0;
|
|
89
|
+
const fits = (node, depth) => node && typeof node === "object" && ++nodes <= MAX_NODES && depth <= MAX_DEPTH
|
|
90
|
+
&& (node.children === undefined || (Array.isArray(node.children) && node.children.every((child) => fits(child, depth + 1))));
|
|
91
|
+
return fits(value, 0) ? value : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The structure of what Yuque copied, as ne-inode's nodes (lake's read so too), or null when the clipboard holds none. */
|
|
95
|
+
export function yuqueTree(data) {
|
|
96
|
+
const inode = treeOf(data.getData(INODE));
|
|
97
|
+
if (inode) return inode;
|
|
98
|
+
const html = data.getData("text/html") || "";
|
|
99
|
+
if (/<ne-clipboard\b/i.test(html)) {
|
|
100
|
+
// Its own document: nothing runs, nothing loads; the attribute's entities read.
|
|
101
|
+
const element = new globalThis.DOMParser().parseFromString(html, "text/html").querySelector("ne-clipboard");
|
|
102
|
+
let json = "";
|
|
103
|
+
try {
|
|
104
|
+
json = decodeURIComponent(element?.getAttribute("data") ?? "");
|
|
105
|
+
} catch {
|
|
106
|
+
// not URL-encoded: none
|
|
107
|
+
}
|
|
108
|
+
const tree = treeOf(json);
|
|
109
|
+
if (tree) return tree;
|
|
110
|
+
}
|
|
111
|
+
const lake = data.getData(LAKE) || (/\sdata-lake-id=/.test(html) ? html : "");
|
|
112
|
+
return lake ? lakeTree(lake) : null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---------- lake: Yuque's HTML as ne-inode's nodes ----------
|
|
116
|
+
|
|
117
|
+
const element = (name, attrs, children) => ({ type: "element", name, attrs, children });
|
|
118
|
+
const textNode = (data, attrs = {}) => ({ type: "text", name: "#text", attrs, data });
|
|
119
|
+
const card = (name, value, cardType) => ({ type: "card", name, attrs: { value, cardType }, cardType });
|
|
120
|
+
// A card's value: "data:" and URL-encoded JSON.
|
|
121
|
+
function cardData(raw) {
|
|
122
|
+
const text = String(raw ?? "");
|
|
123
|
+
if (!text.startsWith("data:")) return {};
|
|
124
|
+
try {
|
|
125
|
+
return JSON.parse(decodeURIComponent(text.slice(5)));
|
|
126
|
+
} catch {
|
|
127
|
+
return {};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const styleValue = (node, name) => new RegExp(`(?:^|;)\\s*${name}\\s*:\\s*([^;]+)`, "i").exec(node.getAttribute("style") ?? "")?.[1].trim() ?? "";
|
|
131
|
+
const alignment = (node) => {
|
|
132
|
+
const align = styleValue(node, "text-align").toLowerCase();
|
|
133
|
+
return ALIGNS[align] ? { alignment: align } : {};
|
|
134
|
+
};
|
|
135
|
+
const LAKE_INLINE = new Set(["span", "strong", "b", "em", "i", "del", "s", "strike", "u", "sub", "sup", "code", "a", "font", "br", "mark", "img"]);
|
|
136
|
+
const TAG_MARKS = { strong: { bold: true }, b: { bold: true }, em: { italic: true }, i: { italic: true }, del: { strikethrough: true }, s: { strikethrough: true }, strike: { strikethrough: true }, u: { underline: true }, sub: { sub: true }, sup: { sup: true } };
|
|
137
|
+
|
|
138
|
+
/** Yuque's own HTML (text/lake, or a copy's HTML in that form) as ne-inode's nodes, or null when it holds none. */
|
|
139
|
+
export function lakeTree(html) {
|
|
140
|
+
const doc = new globalThis.DOMParser().parseFromString(html, "text/html");
|
|
141
|
+
const children = lakeBlocks([...doc.body.childNodes], 0);
|
|
142
|
+
return children.length ? { type: "fragment", name: "#fragment", attrs: {}, children } : null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function lakeBlocks(nodes, depth) {
|
|
146
|
+
const out = [];
|
|
147
|
+
if (depth > MAX_DEPTH) return out;
|
|
148
|
+
let loose = [];
|
|
149
|
+
const flush = () => {
|
|
150
|
+
if (loose.length) out.push(element("p", {}, loose));
|
|
151
|
+
loose = [];
|
|
152
|
+
};
|
|
153
|
+
for (const node of nodes) {
|
|
154
|
+
if (node.nodeType === 3) {
|
|
155
|
+
if (node.data.trim()) loose.push(textNode(node.data));
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (node.nodeType !== 1) continue;
|
|
159
|
+
const tag = node.localName;
|
|
160
|
+
if ((LAKE_INLINE.has(tag) && tag !== "img") || (tag === "card" && node.getAttribute("type") === "inline" && node.getAttribute("name") !== "image")) {
|
|
161
|
+
loose.push(...lakeInline([node], {}, depth));
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
flush();
|
|
165
|
+
out.push(...lakeBlock(node, depth));
|
|
166
|
+
}
|
|
167
|
+
flush();
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function lakeBlock(node, depth) {
|
|
172
|
+
const tag = node.localName;
|
|
173
|
+
const inner = () => lakeBlocks([...node.childNodes], depth + 1);
|
|
174
|
+
if (tag === "p") return [element("p", alignment(node), lakeInline([...node.childNodes], {}, depth))];
|
|
175
|
+
if (HEADINGS[tag]) return [element(tag, alignment(node), lakeInline([...node.childNodes], {}, depth))];
|
|
176
|
+
if (tag === "ul" || tag === "ol") return lakeList(node, depth, count(node.getAttribute("data-lake-indent")));
|
|
177
|
+
if (tag === "blockquote") {
|
|
178
|
+
const kind = /(?:^|\s)lake-alert-([\w-]+)/.exec(node.getAttribute("class") ?? "")?.[1] ?? node.getAttribute("data-type");
|
|
179
|
+
return kind || node.classList.contains("lake-alert") ? [element("alert", { type: kind ?? "" }, inner())] : [element("quote", {}, inner())];
|
|
180
|
+
}
|
|
181
|
+
if (tag === "details") {
|
|
182
|
+
const summary = [...node.children].find((child) => child.localName === "summary");
|
|
183
|
+
return [element("collapse", {}, [element("summary", {}, summary ? lakeInline([...summary.childNodes], {}, depth) : []), ...lakeBlocks([...node.childNodes].filter((child) => child !== summary), depth + 1)])];
|
|
184
|
+
}
|
|
185
|
+
if (tag === "table") {
|
|
186
|
+
const rows = [...node.querySelectorAll("tr")].filter((row) => row.closest("table") === node);
|
|
187
|
+
return [element("table", {}, rows.map((row) => element("tr", {}, [...row.cells].map((cell) => element("td", { rowSpan: cell.rowSpan, colSpan: cell.colSpan, ...alignment(cell) }, lakeBlocks([...cell.childNodes], depth + 1))))))];
|
|
188
|
+
}
|
|
189
|
+
if (tag === "card") return [lakeCard(node)];
|
|
190
|
+
if (tag === "pre") return [card("codeblock", { mode: node.getAttribute("data-language") || /(?:^|\s)language-([\w+#-]+)/.exec(node.getAttribute("class") ?? "")?.[1] || "", code: node.textContent }, "block")];
|
|
191
|
+
if (tag === "hr") return [card("hr", {}, "block")];
|
|
192
|
+
if (tag === "img") return [lakeImage(node)];
|
|
193
|
+
if (["colgroup", "col", "style", "script", "meta", "title", "template"].includes(tag)) return [];
|
|
194
|
+
return inner();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const lakeCard = (node) => card(node.getAttribute("name") ?? "", cardData(node.getAttribute("value")), node.getAttribute("type") || "inline");
|
|
198
|
+
const lakeImage = (node) => card("image", { src: node.getAttribute("src") ?? "", alt: node.getAttribute("alt") ?? "", title: node.getAttribute("title") ?? "" }, "inline");
|
|
199
|
+
|
|
200
|
+
// A list of lake's: flat, each list its level (data-lake-indent) and its first number (start); a to-do's
|
|
201
|
+
// check its checkbox card. A list held in an item (another program's lake-like HTML) its items a level deeper.
|
|
202
|
+
function lakeList(node, depth, level) {
|
|
203
|
+
const out = [];
|
|
204
|
+
const start = count(node.getAttribute("start"), 1) || 1;
|
|
205
|
+
[...node.children].filter((child) => child.localName === "li").forEach((li, index) => {
|
|
206
|
+
const box = [...li.children].find((child) => child.localName === "card" && child.getAttribute("name") === "checkbox");
|
|
207
|
+
const name = box || li.classList.contains("lake-list-task") ? "tli" : node.localName === "ol" ? "oli" : "uli";
|
|
208
|
+
const nested = [...li.children].filter((child) => child.localName === "ul" || child.localName === "ol");
|
|
209
|
+
const words = [...li.childNodes].filter((child) => child !== box && !nested.includes(child));
|
|
210
|
+
out.push(element(name, { level, index: start - 1 + index, ...(name === "tli" && cardData(box?.getAttribute("value")) === true ? { checked: true } : {}) }, lakeInline(words, {}, depth)));
|
|
211
|
+
for (const list of nested) if (depth < MAX_DEPTH) out.push(...lakeList(list, depth + 1, level + 1));
|
|
212
|
+
});
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function lakeInline(nodes, marks, depth) {
|
|
217
|
+
const out = [];
|
|
218
|
+
if (depth > MAX_DEPTH) return out;
|
|
219
|
+
for (const node of nodes) {
|
|
220
|
+
if (node.nodeType === 3) {
|
|
221
|
+
if (node.data) out.push(textNode(node.data, { ...marks }));
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (node.nodeType !== 1) continue;
|
|
225
|
+
const tag = node.localName;
|
|
226
|
+
const children = () => lakeInline([...node.childNodes], marks, depth + 1);
|
|
227
|
+
if (tag === "br") out.push(textNode("\n", { ...marks }));
|
|
228
|
+
else if (tag === "card") out.push(lakeCard(node));
|
|
229
|
+
else if (tag === "img") out.push(lakeImage(node));
|
|
230
|
+
else if (tag === "code") out.push(element("code", {}, children()));
|
|
231
|
+
else if (tag === "a") out.push(element("link", { src: node.getAttribute("href") ?? "" }, children()));
|
|
232
|
+
else {
|
|
233
|
+
const own = { ...marks, ...TAG_MARKS[tag] };
|
|
234
|
+
const color = styleValue(node, "color");
|
|
235
|
+
const background = styleValue(node, "background(?:-color)?");
|
|
236
|
+
const decoration = styleValue(node, "text-decoration(?:-line)?");
|
|
237
|
+
const weight = styleValue(node, "font-weight");
|
|
238
|
+
if (color) own.color = color;
|
|
239
|
+
if (background) own.bgColor = background;
|
|
240
|
+
if (/line-through/.test(decoration)) own.strikethrough = true;
|
|
241
|
+
if (/underline/.test(decoration)) own.underline = true;
|
|
242
|
+
if (weight === "bold" || Number.parseInt(weight, 10) >= 600) own.bold = true;
|
|
243
|
+
if (/italic/.test(styleValue(node, "font-style"))) own.italic = true;
|
|
244
|
+
out.push(...lakeInline([...node.childNodes], own, depth + 1));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ---------- the nodes as block specs ----------
|
|
251
|
+
|
|
252
|
+
const formulaOf = (piece) => piece[1]?.find((annotation) => annotation[0] === "e") ?? null;
|
|
253
|
+
const keyOf = (annotations) => JSON.stringify([...new Set((annotations ?? []).map((a) => JSON.stringify(a)))].sort());
|
|
254
|
+
|
|
255
|
+
// Rich text with neighbours of the same marks joined (never two formulas), each mark once, empty pieces gone.
|
|
256
|
+
function joined(pieces) {
|
|
257
|
+
const out = [];
|
|
258
|
+
for (const [text, annotations = []] of pieces) {
|
|
259
|
+
if (!text) continue;
|
|
260
|
+
const marks = [...new Map(annotations.map((a) => [JSON.stringify(a), a])).values()];
|
|
261
|
+
const last = out.at(-1);
|
|
262
|
+
if (last && !formulaOf(last) && !formulaOf([text, marks]) && keyOf(last[1]) === keyOf(marks)) last[0] += text;
|
|
263
|
+
else out.push(marks.length ? [text, marks] : [text]);
|
|
264
|
+
}
|
|
265
|
+
return out;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Rich text with the white space at its ends taken off.
|
|
269
|
+
function trimmed(pieces) {
|
|
270
|
+
const out = joined(pieces).map((piece) => [...piece]);
|
|
271
|
+
while (out.length && !formulaOf(out[0]) && !out[0][0].trim()) out.shift();
|
|
272
|
+
while (out.length && !formulaOf(out.at(-1)) && !out.at(-1)[0].trim()) out.pop();
|
|
273
|
+
if (out.length && !formulaOf(out[0])) out[0][0] = out[0][0].replace(/^\s+/, "");
|
|
274
|
+
if (out.length && !formulaOf(out.at(-1))) out.at(-1)[0] = out.at(-1)[0].replace(/\s+$/, "");
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// A text node's marks.
|
|
279
|
+
function textMarks(attrs) {
|
|
280
|
+
const marks = [];
|
|
281
|
+
if (attrs.bold) marks.push(["b"]);
|
|
282
|
+
if (attrs.italic) marks.push(["i"]);
|
|
283
|
+
if (attrs.underline) marks.push(["_"]);
|
|
284
|
+
if (attrs.strikethrough) marks.push(["s"]);
|
|
285
|
+
if (attrs.sup) marks.push(["sup"]);
|
|
286
|
+
if (attrs.sub) marks.push(["sub"]);
|
|
287
|
+
if (attrs.code) marks.push(["c"]);
|
|
288
|
+
const text = typeof attrs.color === "string" ? textColor(attrs.color, { nearest: true }) : undefined;
|
|
289
|
+
const background = typeof attrs.bgColor === "string" ? backgroundColor(attrs.bgColor, { nearest: true }) : undefined;
|
|
290
|
+
if (text) marks.push(["h", text]);
|
|
291
|
+
if (background) marks.push(["h", background]);
|
|
292
|
+
return marks;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// What a card of no block of the page's says: its words and a web address of its, if any.
|
|
296
|
+
function cardWords(value) {
|
|
297
|
+
const words = ["name", "title", "text", "label"].map((key) => value[key]).find((each) => typeof each === "string" && each.trim());
|
|
298
|
+
const href = ["src", "url", "href", "link"].map((key) => value[key]).find((each) => typeof each === "string" && WEB.test(each.trim()));
|
|
299
|
+
return { words: words ? clean(words).replace(/\s+/g, " ").trim().slice(0, 200) : "", href: href?.trim() ?? "" };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// A gray line where Yuque had what the page has no block for.
|
|
303
|
+
const missing = (what) => spec("text", [[t("({0}没有粘贴过来:Gallery 没有这种块)", what), [["h", "gray"]]]]);
|
|
304
|
+
|
|
305
|
+
// A card's pieces in a line of text, or null when it is a block (an image, a divider, code, a board…).
|
|
306
|
+
function cardPieces(node, marks) {
|
|
307
|
+
const value = valueOf(node);
|
|
308
|
+
const name = nameOf(node);
|
|
309
|
+
if (name === "math") {
|
|
310
|
+
const tex = clean(value.code).trim();
|
|
311
|
+
return tex ? [["⁍", [["e", tex], ...marks]]] : [];
|
|
312
|
+
}
|
|
313
|
+
if (name === "checkbox") return [];
|
|
314
|
+
if (["image", "hr", "codeblock", "board"].includes(name)) return null;
|
|
315
|
+
const { words, href } = cardWords(value);
|
|
316
|
+
if (href) return [[words || href, [...marks, ["a", href]]]];
|
|
317
|
+
if (words) return [[words, marks]];
|
|
318
|
+
return (node.cardType ?? attrsOf(node).cardType) === "block" ? null : [];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// A card as blocks.
|
|
322
|
+
function cardSpecs(node) {
|
|
323
|
+
const value = valueOf(node);
|
|
324
|
+
const name = nameOf(node);
|
|
325
|
+
if (name === "hr") return [spec("divider")];
|
|
326
|
+
if (name === "checkbox") return [];
|
|
327
|
+
if (name === "codeblock") {
|
|
328
|
+
// Its mode named as a fence names a language (markdown.mjs documentCode): a language the kit does not know kept by that name.
|
|
329
|
+
const mode = typeof value.mode === "string" ? value.mode.trim() : "";
|
|
330
|
+
const language = mode ? languageFromFence(mode) ?? mode[0].toUpperCase() + mode.slice(1) : undefined;
|
|
331
|
+
const code = clean(value.code);
|
|
332
|
+
const title = clean(value.name).replace(/\s+/g, " ").trim();
|
|
333
|
+
return [spec("code", code ? [[code]] : [], { ...(language ? { language } : {}), ...(/^[a-z][\w+#.-]{0,23}$/i.test(mode) ? { fence: mode.toLowerCase() } : {}), ...(title ? { title } : {}) })];
|
|
334
|
+
}
|
|
335
|
+
if (name === "math") {
|
|
336
|
+
const tex = clean(value.code).trim();
|
|
337
|
+
return tex ? [spec("equation", [[tex]])] : [];
|
|
338
|
+
}
|
|
339
|
+
if (name === "image") {
|
|
340
|
+
const src = typeof value.src === "string" ? value.src.trim() : "";
|
|
341
|
+
const caption = value.showTitle && typeof value.title === "string" ? clean(value.title).trim() : "";
|
|
342
|
+
return src ? [spec("image", [], { source: src, ...(caption ? { caption: [[caption]] } : {}) })] : [];
|
|
343
|
+
}
|
|
344
|
+
if (name === "board") {
|
|
345
|
+
// Its name: its mind map's first node, where it has one.
|
|
346
|
+
const shapes = value.diagramData?.body;
|
|
347
|
+
const map = Array.isArray(shapes) ? shapes.find((shape) => shape?.type === "mindmap") : null;
|
|
348
|
+
const title = typeof map?.html === "string" ? decodeEntities(clean(map.html).replace(/<[^>]*>/g, "")).replace(/\s+/g, " ").trim().slice(0, 100) : "";
|
|
349
|
+
return [missing(title ? t("语雀画板「{0}」", title) : t("语雀画板"))];
|
|
350
|
+
}
|
|
351
|
+
const { words, href } = cardWords(value);
|
|
352
|
+
if (href) return [spec("text", [[words || href, [["a", href]]]])];
|
|
353
|
+
if (words) return [spec("text", [[words]])];
|
|
354
|
+
return [missing(t("语雀的「{0}」卡片", clean(name).slice(0, 40) || "?"))];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// A run of inline nodes as pieces of rich text and blocks found in it (an image), in order: [piece | { block }].
|
|
358
|
+
function runOf(nodes, marks, out, depth) {
|
|
359
|
+
if (depth > MAX_DEPTH) return out;
|
|
360
|
+
for (const node of nodes) {
|
|
361
|
+
if (!node || typeof node !== "object") continue;
|
|
362
|
+
if (isText(node)) {
|
|
363
|
+
const text = clean(node.data);
|
|
364
|
+
if (text) out.push([text, [...marks, ...textMarks(attrsOf(node))]]);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (isCard(node)) {
|
|
368
|
+
const pieces = cardPieces(node, marks);
|
|
369
|
+
if (pieces) out.push(...pieces);
|
|
370
|
+
else out.push(...cardSpecs(node).map((block) => ({ block })));
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const name = nameOf(node);
|
|
374
|
+
const href = attrsOf(node).src ?? attrsOf(node).href;
|
|
375
|
+
if (name === "code") runOf(kids(node), [...marks, ["c"]], out, depth + 1);
|
|
376
|
+
else if (name === "link" && typeof href === "string" && href.trim()) runOf(kids(node), [...marks, ["a", href.trim()]], out, depth + 1);
|
|
377
|
+
else runOf(kids(node), marks, out, depth + 1);
|
|
378
|
+
}
|
|
379
|
+
return out;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Inline nodes as one rich text and the blocks found in them (after it: an item's, a heading's).
|
|
383
|
+
function inlineOf(nodes, depth) {
|
|
384
|
+
const run = runOf(nodes, [], [], depth);
|
|
385
|
+
return { title: trimmed(run.filter(Array.isArray)), blocks: run.filter((each) => !Array.isArray(each)).map((each) => each.block) };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// A paragraph's nodes as blocks: its words (empty: none), an image in it a block of its own between them.
|
|
389
|
+
function paragraphOf(nodes, depth) {
|
|
390
|
+
const out = [];
|
|
391
|
+
let words = [];
|
|
392
|
+
const flush = () => {
|
|
393
|
+
const title = trimmed(words);
|
|
394
|
+
if (title.length) out.push(spec("text", title));
|
|
395
|
+
words = [];
|
|
396
|
+
};
|
|
397
|
+
for (const each of runOf(nodes, [], [], depth)) {
|
|
398
|
+
if (Array.isArray(each)) words.push(each);
|
|
399
|
+
else {
|
|
400
|
+
flush();
|
|
401
|
+
out.push(each.block);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
flush();
|
|
405
|
+
return out;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// A table's cell as one rich text: its paragraphs and items lines of it (an item with its marker),
|
|
409
|
+
// a picture a link to it, code as code.
|
|
410
|
+
function cellOf(cell, depth) {
|
|
411
|
+
const lines = [];
|
|
412
|
+
const line = (pieces) => lines.push(pieces.flatMap((each) => {
|
|
413
|
+
if (Array.isArray(each)) return [each];
|
|
414
|
+
const { block } = each;
|
|
415
|
+
if (block.type === "image") return WEB.test(block.props.source) ? [[t("图片"), [["a", block.props.source]]]] : [];
|
|
416
|
+
if (block.type === "equation") return [["⁍", [["e", block.title[0]?.[0] ?? ""]]]];
|
|
417
|
+
if (block.type === "code") return block.title.length ? [[block.title[0][0], [["c"]]]] : [];
|
|
418
|
+
return block.title ?? [];
|
|
419
|
+
}));
|
|
420
|
+
const walk = (nodes, level) => {
|
|
421
|
+
let loose = [];
|
|
422
|
+
const flush = () => {
|
|
423
|
+
if (loose.length) line(runOf(loose, [], [], depth));
|
|
424
|
+
loose = [];
|
|
425
|
+
};
|
|
426
|
+
for (const node of nodes) {
|
|
427
|
+
const name = nameOf(node);
|
|
428
|
+
if (isText(node) || name === "code" || name === "link" || (isCard(node) && cardPieces(node, []) !== null)) {
|
|
429
|
+
loose.push(node);
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
flush();
|
|
433
|
+
if (isCard(node)) line(cardSpecs(node).map((block) => ({ block })));
|
|
434
|
+
else if (LISTS[name]) line([[name === "oli" ? `${count(attrsOf(node).index) + 1}. ` : name === "tli" ? (attrsOf(node).checked ? "☑ " : "☐ ") : "• "], ...runOf(kids(node), [], [], depth)]);
|
|
435
|
+
else if (name === "p" || HEADINGS[name] || name === "summary") line(runOf(kids(node), [], [], depth));
|
|
436
|
+
else if (level < MAX_DEPTH) walk(kids(node), level + 1);
|
|
437
|
+
}
|
|
438
|
+
flush();
|
|
439
|
+
};
|
|
440
|
+
walk(kids(cell), 0);
|
|
441
|
+
const pieces = lines.map(trimmed).filter((each) => each.length).flatMap((each, index) => (index > 0 ? [["\n"], ...each] : each));
|
|
442
|
+
return joined(pieces);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// A cell's alignment as its look: its own, else its first paragraph's.
|
|
446
|
+
function cellLook(cell) {
|
|
447
|
+
const first = kids(cell).find((node) => nameOf(node) === "p");
|
|
448
|
+
return ALIGNS[attrsOf(cell).alignment] ?? ALIGNS[attrsOf(first).alignment] ?? "";
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// A table: its rows' cells placed (a merged cell in each slot it covers), as Notion's table spec, a
|
|
452
|
+
// row's `spans` naming its cells where any is merged (to-blocks.mjs), its cells' alignments their looks.
|
|
453
|
+
function tableOf(node, depth) {
|
|
454
|
+
const rows = [];
|
|
455
|
+
(function find(parent, level) {
|
|
456
|
+
for (const child of kids(parent)) {
|
|
457
|
+
if (nameOf(child) === "tr") rows.push(child);
|
|
458
|
+
else if (!isText(child) && !isCard(child) && level < MAX_DEPTH) find(child, level + 1);
|
|
459
|
+
}
|
|
460
|
+
})(node, 0);
|
|
461
|
+
const cells = [];
|
|
462
|
+
const taken = rows.map(() => []);
|
|
463
|
+
rows.forEach((row, r) => {
|
|
464
|
+
let c = 0;
|
|
465
|
+
for (const cell of kids(row).filter((each) => nameOf(each) === "td" || nameOf(each) === "th")) {
|
|
466
|
+
while (taken[r][c]) c++;
|
|
467
|
+
const attrs = attrsOf(cell);
|
|
468
|
+
const rowSpan = Math.min(Math.max(1, count(attrs.rowSpan ?? attrs.rowspan, 1)), rows.length - r, MAX_SPAN);
|
|
469
|
+
const colSpan = Math.min(Math.max(1, count(attrs.colSpan ?? attrs.colspan, 1)), MAX_SPAN);
|
|
470
|
+
cells.push({ r, c, rowSpan, colSpan, title: cellOf(cell, depth), look: cellLook(cell) });
|
|
471
|
+
for (let dr = 0; dr < rowSpan; dr++) for (let dc = 0; dc < colSpan; dc++) taken[r + dr][c + dc] = true;
|
|
472
|
+
c += colSpan;
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
const width = Math.max(0, ...taken.map((row) => row.length));
|
|
476
|
+
if (!rows.length || !width) return [];
|
|
477
|
+
const columns = Array.from({ length: width }, (_, i) => `c${i}`);
|
|
478
|
+
const merged = cells.some((cell) => cell.rowSpan > 1 || cell.colSpan > 1);
|
|
479
|
+
const looked = cells.some((cell) => cell.look);
|
|
480
|
+
const rowSpecs = rows.map((_, r) => {
|
|
481
|
+
const own = cells.filter((cell) => cell.r === r);
|
|
482
|
+
const by = (value) => Object.fromEntries(own.map((cell) => [columns[cell.c], value(cell)]));
|
|
483
|
+
return spec("table_row", [], { cells: by((cell) => cell.title), ...(merged ? { spans: by((cell) => [cell.rowSpan, cell.colSpan]) } : {}), ...(looked ? { looks: by((cell) => cell.look) } : {}) });
|
|
484
|
+
});
|
|
485
|
+
return [spec("table", [], { table_block_column_order: columns, table_block_column_format: {}, table_block_column_header: false, table_block_row_header: false }, rowSpecs)];
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// A node at a block's level (not a list item: blocksOf nests those).
|
|
489
|
+
function blockOf(node, depth) {
|
|
490
|
+
const name = nameOf(node);
|
|
491
|
+
const inner = () => blocksOf(kids(node), depth + 1);
|
|
492
|
+
if (isCard(node)) return cardSpecs(node);
|
|
493
|
+
if (name === "p" || name === "summary") return paragraphOf(kids(node), depth);
|
|
494
|
+
if (HEADINGS[name]) {
|
|
495
|
+
const { title, blocks } = inlineOf(kids(node), depth);
|
|
496
|
+
return [spec(HEADINGS[name], title), ...blocks];
|
|
497
|
+
}
|
|
498
|
+
if (name === "quote") {
|
|
499
|
+
// Its first paragraph its text, the rest its blocks (html.mjs quoteBlock).
|
|
500
|
+
const blocks = inner();
|
|
501
|
+
const [first, ...rest] = blocks;
|
|
502
|
+
return first?.type === "text" && !first.children.length ? [spec("quote", first.title, {}, rest)] : [spec("quote", [], {}, blocks)];
|
|
503
|
+
}
|
|
504
|
+
if (name === "alert") return [spec("report_callout", [], { alert: String(attrsOf(node).type ?? "").toLowerCase() }, inner())];
|
|
505
|
+
if (name === "collapse") {
|
|
506
|
+
const summary = kids(node).find((child) => nameOf(child) === "summary");
|
|
507
|
+
return [spec("toggle", summary ? inlineOf(kids(summary), depth).title : [], {}, blocksOf(kids(node).filter((child) => child !== summary), depth + 1))];
|
|
508
|
+
}
|
|
509
|
+
if (name === "table") return tableOf(node, depth);
|
|
510
|
+
return inner(); // a hole (hole, tableHole, alertHole, containerHole) or a node of no block: what it holds
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const isInline = (node) => isText(node) || ["code", "link"].includes(nameOf(node)) || (isCard(node) && (node.cardType ?? attrsOf(node).cardType) === "inline" && cardPieces(node, []) !== null);
|
|
514
|
+
|
|
515
|
+
// Nodes at a block's level as specs: list items nested by their levels, inline nodes standing at that
|
|
516
|
+
// level one paragraph.
|
|
517
|
+
function blocksOf(nodes, depth) {
|
|
518
|
+
const out = [];
|
|
519
|
+
if (depth > MAX_DEPTH) return out;
|
|
520
|
+
let loose = [];
|
|
521
|
+
let items = null; // the open list's levels: [{ level, children }]
|
|
522
|
+
const flush = () => {
|
|
523
|
+
if (loose.length) out.push(...paragraphOf(loose, depth));
|
|
524
|
+
loose = [];
|
|
525
|
+
};
|
|
526
|
+
for (const node of nodes) {
|
|
527
|
+
if (!node || typeof node !== "object") continue;
|
|
528
|
+
if (isInline(node)) {
|
|
529
|
+
items = null;
|
|
530
|
+
loose.push(node);
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
flush();
|
|
534
|
+
const type = LISTS[nameOf(node)];
|
|
535
|
+
if (!type) {
|
|
536
|
+
items = null;
|
|
537
|
+
out.push(...blockOf(node, depth));
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
const attrs = attrsOf(node);
|
|
541
|
+
const level = count(attrs.level);
|
|
542
|
+
const { title, blocks } = inlineOf(kids(node).filter((child) => !(isCard(child) && nameOf(child) === "checkbox")), depth);
|
|
543
|
+
const props = type === "to_do" ? { checked: attrs.checked === true || attrs.checked === "true" } : type === "numbered_list" ? { number: count(attrs.index) + 1 } : {};
|
|
544
|
+
const item = spec(type, title, props, blocks);
|
|
545
|
+
items ??= [{ level: -1, children: out }];
|
|
546
|
+
while (items.length > 1 && items.at(-1).level >= level) items.pop();
|
|
547
|
+
items.at(-1).children.push(item);
|
|
548
|
+
items.push({ level, children: item.children });
|
|
549
|
+
}
|
|
550
|
+
flush();
|
|
551
|
+
return out;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// What both readings end with: a 提示's kind its color, a paragraph of one formula alone a formula block.
|
|
555
|
+
function finish(specs) {
|
|
556
|
+
return specs.map((s) => {
|
|
557
|
+
const children = finish(s.children ?? []);
|
|
558
|
+
if (s.type === "report_callout") {
|
|
559
|
+
const color = ALERT_COLORS[s.props?.alert];
|
|
560
|
+
return spec(s.type, [], color ? { color } : {}, children);
|
|
561
|
+
}
|
|
562
|
+
if (s.type === "text" && !children.length) {
|
|
563
|
+
const words = (s.title ?? []).filter((piece) => formulaOf(piece) || piece[0].trim());
|
|
564
|
+
if (words.length === 1 && formulaOf(words[0])) return spec("equation", [[formulaOf(words[0])[1]]]);
|
|
565
|
+
}
|
|
566
|
+
return { ...s, children };
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/** Yuque's nodes (yuqueTree) as block specs. */
|
|
571
|
+
export const yuqueSpecs = (tree) => finish(blocksOf(kids(tree), 0));
|
|
572
|
+
|
|
573
|
+
// ---------- its Markdown ----------
|
|
574
|
+
|
|
575
|
+
// What only Yuque's Markdown has: a ::: 提示, its pictures' and 折叠块's classes, a <font style>.
|
|
576
|
+
const YUQUE_MARKS = [/^:::(?:info|tips|success|warning|danger|color\d)[ \t]*$/m, /<[a-z]+\s[^<>]{0,1000}\bclass="(?:ne|lake)-[a-z-]+"/, /<font\s+style="/];
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Whether the clipboard's text is Yuque's Markdown (its toolbar's 复制为 Markdown): several lines with
|
|
580
|
+
* a mark of Yuque's own, or beside HTML that is its words alone in one span in Yuque's font (Ant
|
|
581
|
+
* Design's "Chinese Quote", white space kept), as its toolbar's copy is.
|
|
582
|
+
*/
|
|
583
|
+
export function yuqueMarkdownCopy(data, text) {
|
|
584
|
+
if (!multiline(text)) return false;
|
|
585
|
+
if (YUQUE_MARKS.some((mark) => mark.test(text))) return true;
|
|
586
|
+
const html = data.getData("text/html") || "";
|
|
587
|
+
return /font-family:\s*(?:"|")Chinese Quote/.test(html) && /white-space:\s*pre/.test(html) && !/<(?:p|div|h[1-6]|ul|ol|li|table|pre|blockquote|img)\b/i.test(html);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// The HTML blocks of Yuque's Markdown read as the paste reads HTML, at any depth.
|
|
591
|
+
const withHtml = (specs, options) => specs.flatMap((s) => (s.type === "html"
|
|
592
|
+
? htmlToBlocks(parseClipboardHTML(String(s.props?.html ?? ""), options), { colors: "nearest" })
|
|
593
|
+
: [{ ...s, children: withHtml(s.children ?? [], options) }]));
|
|
594
|
+
|
|
595
|
+
/** Yuque's Markdown as block specs: read as a whole document with what Yuque writes besides (markdown.mjs `yuque`). */
|
|
596
|
+
export const yuqueMarkdown = (text, options = {}) => finish(withHtml(readMarkdownDocument(text, { yuque: true }).specs, options));
|
|
597
|
+
|
|
598
|
+
// ---------- the paste ----------
|
|
599
|
+
|
|
600
|
+
// Kinds of block whose text is one line, which one line copied goes in as.
|
|
601
|
+
const LINE_TYPES = new Set(["text", "header", "sub_header", "sub_sub_header", "header_4", "bulleted_list", "numbered_list", "to_do", "quote"]);
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* What Yuque put on the clipboard, as read.mjs readExternal says a paste is ({ kind: "blocks", specs }
|
|
605
|
+
* | { kind: "rich", title }), or null when it is not Yuque's (or holds nothing to read).
|
|
606
|
+
*/
|
|
607
|
+
export function readYuque(data, options = {}) {
|
|
608
|
+
const text = (data.getData("text/plain") || "").replace(/\u2028/g, "\n").replace(/\r\n?/g, "\n");
|
|
609
|
+
const tree = yuqueTree(data);
|
|
610
|
+
const specs = tree ? yuqueSpecs(tree) : [];
|
|
611
|
+
if (specs.length) {
|
|
612
|
+
// One line copied (a paragraph's words, a heading's, an item's): its words with their marks at the caret.
|
|
613
|
+
const [only] = specs;
|
|
614
|
+
if (specs.length === 1 && !only.children.length && LINE_TYPES.has(only.type) && only.title.length && !multiline(text)) return { kind: "rich", title: only.title };
|
|
615
|
+
return { kind: "blocks", specs };
|
|
616
|
+
}
|
|
617
|
+
if (!tree && yuqueMarkdownCopy(data, text)) {
|
|
618
|
+
const read = yuqueMarkdown(text, options);
|
|
619
|
+
if (read.length) return { kind: "blocks", specs: read };
|
|
620
|
+
}
|
|
621
|
+
return null;
|
|
622
|
+
}
|