@heycys/gallery 0.1.0 → 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/THIRD-PARTY-NOTICES.md +51 -1
- package/browser/editor/components/callout/index.mjs +40 -15
- package/browser/editor/components/columns/page.mjs +41 -28
- package/browser/editor/components/details/index.mjs +2 -1
- package/browser/editor/components/dialog/index.mjs +2 -1
- package/browser/editor/components/dialog/page.mjs +0 -18
- package/browser/editor/components/divider/index.mjs +1 -1
- package/browser/editor/components/figure/page.mjs +10 -1
- package/browser/editor/components/file/page.mjs +2 -2
- package/browser/editor/components/flowchart/add.mjs +2 -2
- package/browser/editor/components/heading/index.mjs +2 -0
- package/browser/editor/components/heading/page.mjs +6 -0
- package/browser/editor/components/highlight/index.mjs +4 -20
- package/browser/editor/components/icons.mjs +5 -2
- package/browser/editor/components/image/page.mjs +2 -2
- package/browser/editor/components/installed/index.mjs +71 -25
- package/browser/editor/components/installed/page.mjs +122 -21
- package/browser/editor/components/kit.mjs +3 -0
- package/browser/editor/components/media/page.mjs +44 -14
- package/browser/editor/components/pages.mjs +2 -2
- package/browser/editor/components/registry.mjs +56 -30
- package/browser/editor/components/section/index.mjs +2 -1
- package/browser/editor/components/set-apart.mjs +24 -0
- package/browser/editor/components/table/index.mjs +5 -3
- package/browser/editor/components/table/page.mjs +29 -18
- package/browser/editor/page/annotate/anchors.mjs +4 -3
- package/browser/editor/page/annotate/index.mjs +61 -34
- package/browser/editor/page/autosave.mjs +50 -4
- package/browser/editor/page/beside.mjs +5 -5
- package/browser/editor/page/block-menu/colors.mjs +3 -1
- package/browser/editor/page/block-menu/index.mjs +8 -6
- package/browser/editor/page/blocks.mjs +16 -7
- package/browser/editor/page/caret.mjs +4 -1
- 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/clipboard/import/colors.mjs +3 -3
- package/browser/editor/page/clipboard/index.mjs +32 -13
- package/browser/editor/page/clipboard/own/blocks.mjs +14 -5
- package/browser/editor/page/clipboard/own/describe.mjs +16 -7
- package/browser/editor/page/clipboard/own/export.mjs +6 -6
- package/browser/editor/page/clipboard/own/selection.mjs +2 -2
- package/browser/editor/page/clipboard/own/writers.mjs +0 -11
- package/browser/editor/page/columns/index.mjs +1 -1
- package/browser/editor/page/commit.mjs +19 -4
- package/browser/editor/page/conflicts.mjs +6 -26
- package/browser/editor/page/convert.mjs +8 -7
- package/browser/editor/page/core.mjs +55 -17
- package/browser/editor/page/drag/index.mjs +40 -24
- package/browser/editor/page/drag/preview.mjs +9 -4
- package/browser/editor/page/editor.mjs +70 -15
- package/browser/editor/page/emoji-picker/emoji-tab.mjs +22 -13
- package/browser/editor/page/emoji-picker/grid.mjs +7 -5
- package/browser/editor/page/emoji-picker/icon-tab.mjs +4 -4
- package/browser/editor/page/emoji-picker/icons.mjs +0 -1
- package/browser/editor/page/emoji-picker/index.mjs +11 -9
- package/browser/editor/page/emoji-picker/styles.mjs +161 -2
- package/browser/editor/page/emoji-picker/support.mjs +6 -4
- package/browser/editor/page/emoji-picker/upload.mjs +12 -11
- package/browser/editor/page/fills.mjs +5 -3
- 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 +2 -1
- package/browser/editor/page/format-bar/index.mjs +1 -1
- package/browser/editor/page/geometry.mjs +8 -6
- package/browser/editor/page/handle/index.mjs +17 -6
- package/browser/editor/page/hints/index.mjs +1 -1
- package/browser/editor/page/insert/index.mjs +30 -17
- package/browser/editor/page/long-runs.mjs +75 -0
- package/browser/editor/page/look.mjs +15 -3
- package/browser/editor/page/margin.mjs +45 -24
- package/browser/editor/page/menu.mjs +69 -25
- package/browser/editor/page/merge.mjs +24 -0
- package/browser/editor/page/notices.mjs +1 -1
- package/browser/editor/page/offered.mjs +1 -1
- package/browser/editor/page/outline/folds.mjs +11 -10
- package/browser/editor/page/outline/index.mjs +131 -45
- package/browser/editor/page/outline/list.mjs +23 -11
- package/browser/editor/page/outline/read.mjs +1 -1
- package/browser/editor/page/outline/strip.mjs +7 -1
- package/browser/editor/page/panel.mjs +5 -4
- package/browser/editor/page/resize/index.mjs +1 -1
- package/browser/editor/page/selection/delete.mjs +8 -6
- package/browser/editor/page/selection/index.mjs +10 -3
- package/browser/editor/page/session.mjs +54 -3
- package/browser/editor/page/table/cell-bar.mjs +4 -1
- package/browser/editor/page/table/cells.mjs +1 -1
- package/browser/editor/page/table/icons.mjs +1 -1
- package/browser/editor/page/table/index.mjs +13 -8
- package/browser/editor/page/table/widths.mjs +9 -4
- package/browser/editor/page/text-select/index.mjs +18 -3
- package/browser/editor/page/tooltip.mjs +1 -1
- package/browser/editor/page/type-grid.mjs +4 -3
- package/browser/editor/page/typing/code-edges.mjs +63 -17
- package/browser/editor/page/typing/index.mjs +12 -6
- package/browser/editor/page/typing/leave.mjs +1 -1
- package/browser/shared/annotations.mjs +8 -10
- package/browser/shared/code-folds.mjs +5 -1
- package/browser/shared/colors.mjs +15 -13
- package/browser/shared/component-catalog.mjs +24 -10
- package/browser/shared/components/callout.mjs +4 -2
- package/browser/shared/components/index.mjs +61 -7
- package/browser/shared/components/installed.mjs +75 -5
- package/browser/shared/components/page.mjs +10 -1
- package/browser/shared/components/table.mjs +1 -1
- package/browser/shared/components/text.mjs +2 -2
- package/browser/shared/document-look.mjs +178 -0
- package/browser/shared/emoji/data/LICENSE-lucide.txt +43 -0
- package/browser/shared/emoji/data/README.md +4 -2
- package/browser/shared/emoji/data/categories.json +1 -1
- package/browser/shared/emoji/data/keywords-en.json +1 -1
- package/browser/shared/emoji/data/keywords-zh.json +1 -1
- package/browser/shared/emoji/data/names.json +1 -1
- package/browser/shared/emoji/data/positions.json +1 -1
- package/browser/shared/emoji/data/twemoji-index.json +1 -1
- package/browser/shared/emoji/data/twemoji-svg.bin +0 -0
- package/browser/shared/emoji/data.mjs +10 -12
- package/browser/shared/emoji/emoji.mjs +8 -7
- package/browser/shared/emoji/icon-value.mjs +4 -3
- package/browser/shared/html-tree.mjs +11 -5
- package/browser/shared/i18n/en.mjs +67 -8
- package/browser/shared/long-runs.mjs +39 -0
- package/browser/shared/new-names.mjs +5 -2
- package/browser/shared/outline.mjs +194 -0
- package/browser/shared/preferences.mjs +10 -4
- package/browser/shared/session.mjs +5 -4
- package/browser/shared/workspace-look.mjs +91 -0
- package/browser/shared/zoom.mjs +5 -5
- package/dist/chunk-3HIRPVLU.mjs +2 -0
- package/dist/chunk-4FS7Z7WR.mjs +2 -0
- package/dist/{chunk-LHTFLRB6.mjs → chunk-5MHMDLZD.mjs} +2 -2
- package/dist/{chunk-YXAUQPVK.mjs → chunk-5ZKJ3H4P.mjs} +3 -3
- package/dist/{chunk-ZBIM6ARY.mjs → chunk-64K7IPLO.mjs} +55 -16
- package/dist/{chunk-QYKCJ7FG.mjs → chunk-6QXGZM36.mjs} +1 -1
- package/dist/chunk-6RGJOMJP.mjs +16 -0
- package/dist/{chunk-EUX7IC5J.mjs → chunk-7DTINW6U.mjs} +2 -2
- package/dist/{chunk-6CYG3Z4V.mjs → chunk-AJ73XJF3.mjs} +1 -1
- package/dist/chunk-AUZGSZJH.mjs +13 -0
- package/dist/{chunk-7QQBR7PB.mjs → chunk-BFE45AKL.mjs} +1 -1
- package/dist/chunk-BGGDRNYQ.mjs +10 -0
- package/dist/{chunk-L4DVONOG.mjs → chunk-BXO2ZY5P.mjs} +1 -1
- package/dist/chunk-CEH4MRCC.mjs +2 -0
- package/dist/chunk-CY2FNNAB.mjs +16 -0
- package/dist/{chunk-LC7YNQFG.mjs → chunk-DFUDV4LX.mjs} +1 -1
- package/dist/chunk-DKXG4XDG.mjs +2 -0
- package/dist/chunk-E7LWZRXY.mjs +4 -0
- package/dist/chunk-EZQ34M3F.mjs +124 -0
- package/dist/chunk-FDLXYYG4.mjs +4 -0
- package/dist/chunk-FUKQNOQS.mjs +9 -0
- package/dist/{chunk-DB4WKGYF.mjs → chunk-GB5SOYQJ.mjs} +2 -2
- package/dist/{chunk-ACOPDCNB.mjs → chunk-IVDLMMBD.mjs} +1 -1
- package/dist/chunk-JPOSLGOC.mjs +6 -0
- package/dist/chunk-JVHHHGEA.mjs +3 -0
- package/dist/chunk-L3QKH4OK.mjs +26 -0
- package/dist/{chunk-B3VTYBYR.mjs → chunk-LCTHTZIL.mjs} +2 -2
- package/dist/chunk-LEF6DRCK.mjs +59 -0
- package/dist/chunk-LS4F7JUM.mjs +2 -0
- package/dist/chunk-NO5NJCHO.mjs +2 -0
- package/dist/{chunk-UNUEWH4S.mjs → chunk-NQPVAU63.mjs} +1 -1
- package/dist/chunk-O35FIXRB.mjs +2 -0
- package/dist/chunk-PL6GHLGD.mjs +15 -0
- package/dist/{chunk-G7TOXONE.mjs → chunk-QJIR2BLP.mjs} +1 -1
- package/dist/chunk-QUPLVPB2.mjs +7 -0
- package/dist/chunk-R7BNENLY.mjs +4 -0
- package/dist/chunk-S6O4UHLC.mjs +2 -0
- package/dist/{chunk-HMHK3EQ6.mjs → chunk-SGJWQKEI.mjs} +1 -1
- package/dist/chunk-SYGN7GLU.mjs +2 -0
- package/dist/chunk-TYK6VNAB.mjs +10 -0
- package/dist/{chunk-EVJ3BX3M.mjs → chunk-U6KNPJPR.mjs} +1 -1
- package/dist/{chunk-YZ5Z6H4W.mjs → chunk-UALRNVSE.mjs} +1 -1
- package/dist/chunk-UMHUSOPJ.mjs +2 -0
- package/dist/chunk-V3HXVQEM.mjs +2 -0
- package/dist/{chunk-UXUK4PMC.mjs → chunk-V5JTW2LH.mjs} +1 -1
- package/dist/chunk-V5VXUJKG.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-Z52HTUW7.mjs → chunk-XNROSH2T.mjs} +2 -2
- package/dist/chunk-XUNHRCOG.mjs +6 -0
- package/dist/{chunk-6U2SLSDN.mjs → chunk-Y6RLRPG3.mjs} +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/server.mjs +1 -1
- package/dist/worker.mjs +1 -1
- package/kit/math.js +18 -2
- package/kit/report.css +248 -137
- package/kit/runtime.js +126 -51
- package/kit/tables.js +51 -1
- package/kit/ui.js +97 -0
- package/package.json +1 -1
- package/release.json +8 -8
- package/sdk/doc-kit.d.ts +9 -3
- package/skill/SKILL.md +22 -10
- package/skill/references/building-components.md +46 -6
- package/skill/references/example-report.html +1 -1
- package/skill/references/writing-html.md +4 -4
- package/skill/sdk/doc-kit.d.ts +9 -3
- package/web/assets/index-Bh74DfsV.css +1 -0
- package/web/assets/index-UW0RyFFt.js +13 -0
- package/web/index.html +2 -2
- package/browser/editor/components/callout/page.mjs +0 -13
- package/dist/chunk-2PBALTR3.mjs +0 -57
- package/dist/chunk-3LA3EMDE.mjs +0 -15
- package/dist/chunk-5FRMCDPY.mjs +0 -16
- package/dist/chunk-5LEK2CBS.mjs +0 -2
- package/dist/chunk-5OFN5AHC.mjs +0 -2
- package/dist/chunk-5SIYS5HN.mjs +0 -2
- package/dist/chunk-5TEK3RAD.mjs +0 -2
- package/dist/chunk-7GHT2UCA.mjs +0 -15
- package/dist/chunk-ATCJYAR4.mjs +0 -2
- package/dist/chunk-C7HWOBIF.mjs +0 -2
- package/dist/chunk-E7VYDG2X.mjs +0 -2
- package/dist/chunk-FQGQH4KM.mjs +0 -2
- package/dist/chunk-GPF4YWWB.mjs +0 -9
- package/dist/chunk-GYQV7Y4J.mjs +0 -120
- package/dist/chunk-HQQMJCQJ.mjs +0 -10
- package/dist/chunk-IERUOGC3.mjs +0 -6
- package/dist/chunk-ITNLYXXV.mjs +0 -3
- package/dist/chunk-NFIBI4IP.mjs +0 -26
- package/dist/chunk-O52JMOCZ.mjs +0 -2
- package/dist/chunk-QMQUXTU4.mjs +0 -12
- package/dist/chunk-QVWVFC5K.mjs +0 -6
- package/dist/chunk-QZY6Q64C.mjs +0 -2
- package/dist/chunk-TDQP3DKQ.mjs +0 -10
- package/dist/chunk-V4CPWKMZ.mjs +0 -2
- package/dist/chunk-VACRB2NE.mjs +0 -3
- package/dist/chunk-W63L7367.mjs +0 -7
- package/dist/chunk-W7CXPCRV.mjs +0 -6
- package/dist/chunk-XINNG4F2.mjs +0 -4
- package/dist/chunk-YL2DPWVS.mjs +0 -4
- package/web/assets/index-B6Ai3wsa.js +0 -13
- package/web/assets/index-SYPC3EZy.css +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The kit's nine colors (Notion's text and background colors, light and dark), written once: their
|
|
2
|
-
// names, what the reader calls them and their values
|
|
2
|
+
// names, what the reader calls them and their values (a text color, a block's fill and the deeper
|
|
3
|
+
// fill of words in a line). The kit's stylesheets (render/kit/styles/
|
|
3
4
|
// base.css: .doc-text-* and .doc-highlight-*, tokens.css) are written by hand, as CSS is;
|
|
4
5
|
// tests/unit/clipboard.mjs holds them to these values. Everything else that needs a value reads it
|
|
5
6
|
// here: the editor's color pickers (names), pasted colors matched onto the nine (clipboard/import/
|
|
@@ -7,22 +8,23 @@
|
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Each color in Notion's order: `name` (the classes are doc-text-<name> and doc-highlight-
|
|
10
|
-
* <name>), `label` (what the reader calls it), and its text color
|
|
11
|
-
*
|
|
11
|
+
* <name>), `label` (what the reader calls it), and its text color, its background as a block's
|
|
12
|
+
* (`fill`, base.css's --doc-fill) and its background on words in a line (`mark`, base.css's
|
|
13
|
+
* --doc-mark, deeper than the fill) as [light, dark].
|
|
12
14
|
*/
|
|
13
15
|
export const COLORS = [
|
|
14
|
-
{ name: "gray", label: "灰色", text: ["#6e6e73", "#a1a1a6"], fill: ["#f0efed", "#383836"] },
|
|
15
|
-
{ name: "brown", label: "棕色", text: ["#9f765a", "#ba856f"], fill: ["#f5ede9", "#45362d"] },
|
|
16
|
-
{ name: "orange", label: "橙色", text: ["#d27b2d", "#c77d48"], fill: ["#fbebde", "#53361f"] },
|
|
17
|
-
{ name: "yellow", label: "黄色", text: ["#cb9434", "#ca9849"], fill: ["#f9f3dc", "#504425"] },
|
|
18
|
-
{ name: "green", label: "绿色", text: ["#50946e", "#529e72"], fill: ["#e8f1ec", "#263d30"] },
|
|
19
|
-
{ name: "blue", label: "蓝色", text: ["#387dc9", "#5e87c9"], fill: ["#e5f2fc", "#233850"] },
|
|
20
|
-
{ name: "purple", label: "紫色", text: ["#9a6bb4", "#9d68d3"], fill: ["#f3ebf9", "#3c2d47"] },
|
|
21
|
-
{ name: "pink", label: "粉色", text: ["#c14c8a", "#d15796"], fill: ["#fae9f1", "#4e2b3c"] },
|
|
22
|
-
{ name: "red", label: "红色", text: ["#cf5148", "#df5452"], fill: ["#fce9e7", "#502c29"] },
|
|
16
|
+
{ name: "gray", label: "灰色", text: ["#6e6e73", "#a1a1a6"], fill: ["#f0efed", "#383836"], mark: ["#e0dfdd", "#454542"] },
|
|
17
|
+
{ name: "brown", label: "棕色", text: ["#9f765a", "#ba856f"], fill: ["#f5ede9", "#45362d"], mark: ["#efdbce", "#534034"] },
|
|
18
|
+
{ name: "orange", label: "橙色", text: ["#d27b2d", "#c77d48"], fill: ["#fbebde", "#53361f"], mark: ["#fbddc5", "#654025"] },
|
|
19
|
+
{ name: "yellow", label: "黄色", text: ["#cb9434", "#ca9849"], fill: ["#f9f3dc", "#504425"], mark: ["#f8eab6", "#614f26"] },
|
|
20
|
+
{ name: "green", label: "绿色", text: ["#50946e", "#529e72"], fill: ["#e8f1ec", "#263d30"], mark: ["#cfead9", "#2c4b3a"] },
|
|
21
|
+
{ name: "blue", label: "蓝色", text: ["#387dc9", "#5e87c9"], fill: ["#e5f2fc", "#233850"], mark: ["#cde5fb", "#294564"] },
|
|
22
|
+
{ name: "purple", label: "紫色", text: ["#9a6bb4", "#9d68d3"], fill: ["#f3ebf9", "#3c2d47"], mark: ["#ebdaf6", "#4b3859"] },
|
|
23
|
+
{ name: "pink", label: "粉色", text: ["#c14c8a", "#d15796"], fill: ["#fae9f1", "#4e2b3c"], mark: ["#f8d7e6", "#5d3548"] },
|
|
24
|
+
{ name: "red", label: "红色", text: ["#cf5148", "#df5452"], fill: ["#fce9e7", "#502c29"], mark: ["#fed7d1", "#663632"] },
|
|
23
25
|
];
|
|
24
26
|
|
|
25
27
|
export const COLOR_NAMES = COLORS.map(({ name }) => name);
|
|
26
28
|
|
|
27
29
|
/** The page's own text color and background (tokens.css --doc-text, --doc-surface) as [light, dark]: what no color is. */
|
|
28
|
-
export const BODY = { text: ["#
|
|
30
|
+
export const BODY = { text: ["#2c2c2b", "#f3f3f3"], background: ["#ffffff", "#202020"] };
|
|
@@ -5,7 +5,7 @@ import fs from "node:fs/promises";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { parseFragment, serializeOuter } from "parse5";
|
|
7
7
|
import { isInstance, parseCompound, parsePart, parseSelector, partOf } from "./components/index.mjs";
|
|
8
|
-
import { ATTRIBUTE, NO_TEXT, PACKAGE_ID, PACKAGE_MARK, instanceMatch, refusedInCopy, settingState } from "./components/installed.mjs";
|
|
8
|
+
import { ATTRIBUTE, NO_TEXT, PACKAGE_ID, PACKAGE_MARK, SLOT, instanceMatch, refusedInCopy, settingState } from "./components/installed.mjs";
|
|
9
9
|
import { writeAtomic } from "./files.mjs";
|
|
10
10
|
import { attr, walk } from "./html-tree.mjs";
|
|
11
11
|
import { inLanguage, refusal, said, saying, told } from "./i18n/index.mjs";
|
|
@@ -24,7 +24,7 @@ import { DEVICE_NAMES, PACKAGE_MANIFEST, WORKSPACE_MARK, findWorkspace, layoutOf
|
|
|
24
24
|
// does not run, which counts as disabled, or one the settings turned off) stands in `problems`, by its
|
|
25
25
|
// id in lowercase, as an instance names it, with why (`problem`, said()'s, in either language by
|
|
26
26
|
// problemIn), and touches nothing else: its instances show as those of a package that is not there,
|
|
27
|
-
// and whoever reads them says why. `settings` is what gallery.json says of the components (设置 ›
|
|
27
|
+
// and whoever reads them says why. `settings` is what gallery.json says of the components (设置 › 组件列表,
|
|
28
28
|
// componentSettings): `hidden` and `disabled`, by the id of a package or the name of a built-in
|
|
29
29
|
// component, and `problem` where the file cannot be read as saying anything (settingsNow). A package
|
|
30
30
|
// hidden runs as any other and is left out of the lists that offer one, each of which reads its state
|
|
@@ -51,8 +51,15 @@ const FIELDS = ["id", "apiVersion", "version", "name", "description", "words", "
|
|
|
51
51
|
const PART_FIELDS = ["at", "edit", "optional", "min", "max"];
|
|
52
52
|
const RUNTIME_FIELDS = ["generated", "attrs"];
|
|
53
53
|
|
|
54
|
-
// What a package's part may be edited as: its text, its data through the kit's edit,
|
|
55
|
-
|
|
54
|
+
// What a package's part may be edited as: its text, its data through the kit's edit, the document's
|
|
55
|
+
// blocks (a slot, marked data-doc-slot="<name>" in its markup), or not at all.
|
|
56
|
+
const EDITS = ["text", "data", "flow", "none"];
|
|
57
|
+
// What a slot's name may be: it is written in an attribute's value, and a selector finds it there.
|
|
58
|
+
const SLOT_NAME = /^[a-z][a-z0-9-]*$/;
|
|
59
|
+
/** Where a part edited as flow is in an instance: the slot of its name. */
|
|
60
|
+
export const slotAt = (name) => `div[${SLOT}="${name}"], section[${SLOT}="${name}"]`;
|
|
61
|
+
// A manifest's parts as the engines read them: a slot's `at` is its mark, which the manifest does not write.
|
|
62
|
+
const partsRead = (parts) => Object.fromEntries(Object.entries(parts).map(([name, part]) => [name, part.edit === "flow" ? { ...part, at: slotAt(name) } : part]));
|
|
56
63
|
const MARKDOWN = ["parts", "text"];
|
|
57
64
|
|
|
58
65
|
// The files of a package by their fixed names: those served from its folder (kit.js and style.css are
|
|
@@ -158,13 +165,21 @@ function checkPart(name, part) {
|
|
|
158
165
|
const where = `parts.${name}`;
|
|
159
166
|
if (!isTable(part)) fail(said("{0} 要写成一张表", where));
|
|
160
167
|
onlyFields(part, PART_FIELDS, `${where}.`);
|
|
168
|
+
if (part.edit === "flow") {
|
|
169
|
+
// A slot: found by its mark, one to an instance.
|
|
170
|
+
if (!SLOT_NAME.test(name)) fail(said("{0} 是插槽,名字只用小写字母、数字和连字符", where));
|
|
171
|
+
if (part.at !== undefined) fail(said("{0} 是插槽,由 data-doc-slot=\"{1}\" 标出,不写 at", where, name));
|
|
172
|
+
if (part.min !== undefined || part.max !== undefined) fail(said("{0} 是插槽,一个实例里只有一个,不写 min、max", where));
|
|
173
|
+
if (part.optional !== undefined && typeof part.optional !== "boolean") fail(said("{0} 要写成 true 或 false", `${where}.optional`));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
161
176
|
if (!isWords(part.at)) fail(said("缺少 {0}", `${where}.at`));
|
|
162
177
|
try {
|
|
163
178
|
parsePart(part.at);
|
|
164
179
|
} catch (error) {
|
|
165
180
|
fail(said("{0} 写错了:{1}", `${where}.at`, error.message));
|
|
166
181
|
}
|
|
167
|
-
if (!EDITS.includes(part.edit)) fail(said("{0} 的 edit 只能是 text、data 或 none", where));
|
|
182
|
+
if (!EDITS.includes(part.edit)) fail(said("{0} 的 edit 只能是 text、data、flow 或 none", where));
|
|
168
183
|
if (part.optional !== undefined && typeof part.optional !== "boolean") fail(said("{0} 要写成 true 或 false", `${where}.optional`));
|
|
169
184
|
for (const bound of ["min", "max"]) if (part[bound] !== undefined && !(Number.isInteger(part[bound]) && part[bound] >= 0)) fail(said("{0} 要写成不小于 0 的整数", `${where}.${bound}`));
|
|
170
185
|
if (part.min > part.max) fail(said("{0} 的 min 比 max 大,没有实例能满足", where));
|
|
@@ -253,7 +268,7 @@ function packageOf(id, folder, text) {
|
|
|
253
268
|
return {
|
|
254
269
|
id,
|
|
255
270
|
match: instanceMatch(id),
|
|
256
|
-
parts: manifest.parts,
|
|
271
|
+
parts: partsRead(manifest.parts),
|
|
257
272
|
...(runtime ? { runtime } : {}),
|
|
258
273
|
...(manifest.schema !== undefined ? { schema: manifest.schema } : {}),
|
|
259
274
|
apiVersion: manifest.apiVersion,
|
|
@@ -411,7 +426,7 @@ export const problemIn = (language, problem) => inLanguage(language, () => told(
|
|
|
411
426
|
// Why a package does not load, in both languages, as a page and a list say it.
|
|
412
427
|
const bothProblems = (problem) => ({ zh: problemIn("zh", problem), en: problemIn("en", problem) });
|
|
413
428
|
|
|
414
|
-
/** Why gallery.json cannot be read, in both languages, as 设置 ›
|
|
429
|
+
/** Why gallery.json cannot be read, in both languages, as 设置 › 组件列表 says it, or null. */
|
|
415
430
|
export const settingsProblem = ({ settings }) => (settings.problem ? bothProblems(settings.problem) : null);
|
|
416
431
|
|
|
417
432
|
// What a package is now: "normal", "hidden", "disabled" (turned off, or for an apiVersion this Gallery
|
|
@@ -500,7 +515,7 @@ function readTemplate({ id, template, parts }) {
|
|
|
500
515
|
if (held.length > 0) return { problem: said("template.html 里的 <{0}> 进不了新建的块:脚本、事件、样式和 Gallery 自己的标记都不写在模板里", held[0]) };
|
|
501
516
|
const short = Object.entries(parts).find(([, part]) => {
|
|
502
517
|
const count = partOf(read.root, part.at).length;
|
|
503
|
-
return count < (part.optional ? 0 : part.min ?? 1) || count > (part.max ?? Infinity);
|
|
518
|
+
return count < (part.optional ? 0 : part.min ?? 1) || count > (part.edit === "flow" ? 1 : part.max ?? Infinity);
|
|
504
519
|
});
|
|
505
520
|
return short ? { problem: said("template.html 的 {0} 和清单写的数目不符", short[0]) } : { markup: read.markup };
|
|
506
521
|
}
|
|
@@ -515,8 +530,7 @@ export const templateOf = (found) => readTemplate(found).markup ?? null;
|
|
|
515
530
|
* What the page the editor runs on holds of a catalog: its MAP's `components` (src/editor/service.mjs),
|
|
516
531
|
* no route and no dynamic import. One entry for each package that loads, by id: { id, version, name:
|
|
517
532
|
* { zh, en }, description: { zh, en }, words, definition, stamp, template (the markup "/" puts in: one
|
|
518
|
-
* instance of it, or null), preview (the address of its picture, or null), state ("normal", or "hidden":
|
|
519
|
-
* a menu offers what is "normal" and reads no setting), markdown, assets (the kit's files it asks for,
|
|
533
|
+
* instance of it, or null), preview (the address of its picture, or null), state ("normal", or "hidden": a menu offers what is "normal" and reads no setting), markdown, assets (the kit's files it asks for,
|
|
520
534
|
* by id), tags (packageTags: what loads it in a page that was served without it, the line that tells the
|
|
521
535
|
* kit of its parts among them) }. A page knows what it holds of a package's parts from the kit
|
|
522
536
|
* (descriptionsOf), and of one that came after it was served from these tags.
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
// A callout (提示块)
|
|
1
|
+
// A callout (提示块), a container of blocks set apart on a fill, a line at its left; one written the
|
|
2
|
+
// old way, words straight in it, holds them as one paragraph (the editor's natural HTML).
|
|
2
3
|
export default {
|
|
3
4
|
id: "callout",
|
|
4
|
-
category: "
|
|
5
|
+
category: "container",
|
|
5
6
|
match: ".doc-callout",
|
|
7
|
+
flow: ".doc-callout",
|
|
6
8
|
classes: ["doc-callout"],
|
|
7
9
|
};
|
|
@@ -28,7 +28,7 @@ import table from "./table.mjs";
|
|
|
28
28
|
import text from "./text.mjs";
|
|
29
29
|
import widget from "./widget.mjs";
|
|
30
30
|
import { attr, elementsOf, walk } from "../html-tree.mjs";
|
|
31
|
-
import { ATTRIBUTE, INSTANCE } from "./installed.mjs";
|
|
31
|
+
import { ATTRIBUTE, INSTANCE, SLOT, SLOT_PART } from "./installed.mjs";
|
|
32
32
|
|
|
33
33
|
// The kit's components as every system sees them (docs/internals.md, "Components"): what each is
|
|
34
34
|
// in a report's markup, one plain-data description each, shared by the kit (how it runs), the
|
|
@@ -95,8 +95,8 @@ import { ATTRIBUTE, INSTANCE } from "./installed.mjs";
|
|
|
95
95
|
|
|
96
96
|
export const COMPONENTS = [
|
|
97
97
|
page, text, mention, // the page; inline marks, links shown with their icon
|
|
98
|
-
paragraph, heading, list, quote,
|
|
99
|
-
section, highlight, details, dialog, sheets, table, columns, // containers
|
|
98
|
+
paragraph, heading, list, quote, code, divider, math, // basic blocks
|
|
99
|
+
section, highlight, callout, details, dialog, sheets, table, columns, // containers
|
|
100
100
|
figure, chart, image, file, bookmark, embed, // cards (a figure: a chart or a drawing of the report's own; an image, a file, a bookmark, an embed)
|
|
101
101
|
widget, diff, icon, installed, // whole blocks (the page's icon one, in the header; an instance of a component package)
|
|
102
102
|
state, // reading state, bound to blocks of any kind
|
|
@@ -135,7 +135,9 @@ export const CATEGORIES = {
|
|
|
135
135
|
export const EVENTS = {
|
|
136
136
|
"doc-theme-change": "the theme shown changed (ui.js): the reader chose one, the workbench asked for one, or the system's changed while none is chosen",
|
|
137
137
|
"doc-theme-request": "a workbench asks for a theme (ui.js listens)",
|
|
138
|
-
"doc-
|
|
138
|
+
"doc-look-request": "the document's look is asked of the page (字体与布局: the page's session for the workbench, the preview's live reload for doc.json written again; ui.js puts it on <html>, src/shared/document-look.mjs)",
|
|
139
|
+
"doc-look-change": "the look the page shows changed (ui.js): the page editor's session tells the workbench",
|
|
140
|
+
"doc-layout-change": "a card's size changed where widths do not tell (the editor), or the document's look changed (ui.js)",
|
|
139
141
|
"doc-editable": "the page takes what a component writes from now on (the editor)",
|
|
140
142
|
"doc-component-edit": "a component's context.edit asks to write a part: the editor cancels it to say it took the edit, the kit writing no file (on the instance)",
|
|
141
143
|
"doc-export-copy-done": "导出当前视图副本 finished (ui.js)",
|
|
@@ -239,6 +241,58 @@ export function matchesNode(node, selector) {
|
|
|
239
241
|
/** Whether a parsed node is an instance of a component package (installed.mjs): asked of every element by every walk, so by its attribute first, which few have. */
|
|
240
242
|
export const isInstance = (node) => attr(node, ATTRIBUTE) !== undefined && matchesNode(node, INSTANCE);
|
|
241
243
|
|
|
244
|
+
/** Whether a parsed node is a slot of an instance (installed.mjs SLOT): a block-level div or section marked so, wherever it stands. */
|
|
245
|
+
export const isSlot = (node) => attr(node, SLOT) !== undefined && matchesNode(node, SLOT_PART);
|
|
246
|
+
|
|
247
|
+
// The nearest edge of a region around a parsed node (an instance or a slot, itself left out), or null.
|
|
248
|
+
const edgeAround = (node) => {
|
|
249
|
+
let around = node.parentNode;
|
|
250
|
+
while (around && !(isInstance(around) || isSlot(around))) around = around.parentNode;
|
|
251
|
+
return around ?? null;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* The instance whose own region a parsed node stands in, or null where it is the document's: the rule
|
|
256
|
+
* of installed.mjs heldBy, on parse5's tree (the edges folded from the outermost in: an instance's own
|
|
257
|
+
* slot opens the document's again, what stands in a component's region stays its).
|
|
258
|
+
*/
|
|
259
|
+
export function heldByNode(node) {
|
|
260
|
+
const edge = edgeAround(node);
|
|
261
|
+
if (!edge) return null;
|
|
262
|
+
const owner = heldByNode(edge);
|
|
263
|
+
if (isInstance(edge)) return owner ?? edge;
|
|
264
|
+
return owner !== null && edgeAround(edge) === owner ? null : owner;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Every slot of an instance's own markup, in document order (none of an instance in it, none in
|
|
269
|
+
* another slot), whatever its name: what the checks hold to its manifest.
|
|
270
|
+
*/
|
|
271
|
+
export function ownSlots(instance) {
|
|
272
|
+
const found = [];
|
|
273
|
+
const look = (node) => {
|
|
274
|
+
for (const child of elementsOf(node)) {
|
|
275
|
+
if (isSlot(child)) found.push(child);
|
|
276
|
+
else if (!isInstance(child)) look(child);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
look(instance);
|
|
280
|
+
return found;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** The slots of an instance, the first of each name: where what it holds is the document's (a second of a name is a failure of the checks). */
|
|
284
|
+
export function instanceSlots(instance) {
|
|
285
|
+
const named = new Set();
|
|
286
|
+
return ownSlots(instance).filter((slot) => !named.has(attr(slot, SLOT)) && named.add(attr(slot, SLOT)));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* How a walk (html-tree.mjs) goes through what is the document's own: it stops at an instance of a
|
|
291
|
+
* component package and goes on in its slots, never handing a slot itself to the visit (its tag is
|
|
292
|
+
* the component's, what it holds the document's).
|
|
293
|
+
*/
|
|
294
|
+
export const BODY = Object.freeze({ stop: isInstance, through: instanceSlots });
|
|
295
|
+
|
|
242
296
|
/** A part's elements in a parsed host, each once, in document order. */
|
|
243
297
|
export function partOf(host, at) {
|
|
244
298
|
const pieces = piecesOf(at);
|
|
@@ -254,12 +308,12 @@ export function partOf(host, at) {
|
|
|
254
308
|
|
|
255
309
|
/**
|
|
256
310
|
* Each instance of a component in a parsed tree, in document order. What stands in an instance of a
|
|
257
|
-
* component package is its component's and no other's,
|
|
258
|
-
* alone, whatever class it carries.
|
|
311
|
+
* component package is its component's and no other's, but for what its slots hold, which is the
|
|
312
|
+
* document's (BODY), and such an element is of its own package alone, whatever class it carries.
|
|
259
313
|
*/
|
|
260
314
|
export function instancesOf(root, component) {
|
|
261
315
|
const found = [];
|
|
262
|
-
if (component.match) walk(root, (element) => { if (matchesNode(element, component.match) && (!isInstance(element) || attr(element, ATTRIBUTE) === component.id)) found.push(element); },
|
|
316
|
+
if (component.match) walk(root, (element) => { if (matchesNode(element, component.match) && (!isInstance(element) || attr(element, ATTRIBUTE) === component.id)) found.push(element); }, BODY);
|
|
263
317
|
return found;
|
|
264
318
|
}
|
|
265
319
|
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
// src/shared/component-catalog.mjs): a block-level div or section naming its package, which is all a
|
|
3
3
|
// system needs to tell where the instance stands. The boundary depends on no catalog, so it holds in
|
|
4
4
|
// a file sent to someone who has no such package, or that has lost it: nothing of Gallery's goes into
|
|
5
|
-
// an instance, whatever it holds (the scan and naturalize, the kit's other components,
|
|
6
|
-
// spacing and a picture's viewer), but the parts its manifest gives the editor. The rest of
|
|
7
|
-
// the component's, its events too
|
|
8
|
-
//
|
|
9
|
-
//
|
|
5
|
+
// an instance's own region, whatever it holds (the scan and naturalize, the kit's other components,
|
|
6
|
+
// formulas, spacing and a picture's viewer), but the parts its manifest gives the editor. The rest of
|
|
7
|
+
// it is the component's, its events too, but for its slots (SLOT: a div or section of its own markup
|
|
8
|
+
// marked data-doc-slot, its manifest's part edited as "flow"), whose insides are the document's again:
|
|
9
|
+
// its blocks, read, edited, checked and drawn as anywhere in a flow, by the same mark and no catalog
|
|
10
|
+
// either. One rule tells whose an element is (heldBy), the same on the server (components/index.mjs
|
|
11
|
+
// heldByNode), in the kit and on the page. One of no package is shown as it stands, read-only, and the
|
|
12
|
+
// kit marks it so (FALLBACK), a note beside what it holds in a preview, and what a script of data in
|
|
13
|
+
// it holds, which no reader would see, drawn as the kit draws any (DATA_SHOWN); its slots stay the
|
|
14
|
+
// document's, and editable.
|
|
10
15
|
|
|
11
16
|
/** The attribute that makes an element an instance, its value the package's id: <scope>/<name>. */
|
|
12
17
|
export const ATTRIBUTE = "data-doc-component";
|
|
@@ -17,6 +22,66 @@ export const PACKAGE_ID = /^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
|
|
|
17
22
|
/** Where a document's instances are: what stops every system at the edge of one. */
|
|
18
23
|
export const INSTANCE = `div[${ATTRIBUTE}], section[${ATTRIBUTE}]`;
|
|
19
24
|
|
|
25
|
+
/** The attribute that makes an element of an instance's own markup a slot, its value the name of the part its manifest edits as "flow". */
|
|
26
|
+
export const SLOT = "data-doc-slot";
|
|
27
|
+
|
|
28
|
+
/** Where slots are: a block-level div or section so marked. */
|
|
29
|
+
export const SLOT_PART = `div[${SLOT}], section[${SLOT}]`;
|
|
30
|
+
|
|
31
|
+
// The edges of a region: an instance, where the component's begins, and a slot, where the document's may again.
|
|
32
|
+
const REGION = `${INSTANCE}, ${SLOT_PART}`;
|
|
33
|
+
|
|
34
|
+
// The nearest edge of a region around a node (itself left out), or null.
|
|
35
|
+
const edgeAround = (node) => node?.parentElement?.closest(REGION) ?? null;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The instance whose own region a node of a page stands in, or null where it is the document's: the
|
|
39
|
+
* edges around it folded from the outermost in. An instance met in the document's region opens its
|
|
40
|
+
* component's; its own slot (one whose nearest edge is that instance) opens the document's again; an
|
|
41
|
+
* instance or a slot met in a component's region stays that component's. A slot itself is its
|
|
42
|
+
* instance's (the component places it), and an instance itself stands in the region around it (its
|
|
43
|
+
* frame and its handle are Gallery's). For a page's DOM; the server's is heldByNode.
|
|
44
|
+
*/
|
|
45
|
+
export function heldBy(node) {
|
|
46
|
+
const edge = edgeAround(node);
|
|
47
|
+
if (!edge) return null;
|
|
48
|
+
const owner = heldBy(edge);
|
|
49
|
+
if (edge.matches(INSTANCE)) return owner ?? edge;
|
|
50
|
+
return owner !== null && edgeAround(edge) === owner ? null : owner;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Whether an element is a slot that opens the document's region again: one of an instance that stands in the document's. */
|
|
54
|
+
const opensDocument = (node) => node.matches(SLOT_PART) && heldBy(node) !== null && edgeAround(node) === heldBy(node);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether what happens on an element (a press, a key, a change of what it holds) is a component's: it
|
|
58
|
+
* stands in an instance's own region. Not a slot that opens the document's (its blank is the page's
|
|
59
|
+
* box), nor an instance itself (its frame is Gallery's).
|
|
60
|
+
*/
|
|
61
|
+
export const componentAt = (node) => node?.nodeType === 1 && heldBy(node) !== null && !opensDocument(node);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The slot whose inside is the document's region a node stands in (one of an instance that stands in
|
|
65
|
+
* the document's, opening it again), the nearest, or null for the page around it: where what is one
|
|
66
|
+
* block of the page's is looked for (the editor's registry.mjs ownerOf).
|
|
67
|
+
*/
|
|
68
|
+
export function slotAround(node) {
|
|
69
|
+
for (let at = node; at;) {
|
|
70
|
+
const owner = heldBy(at);
|
|
71
|
+
if (owner) {
|
|
72
|
+
at = owner;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const edge = edgeAround(at); // a slot, or none: no instance holds it
|
|
76
|
+
if (!edge || opensDocument(edge)) return edge;
|
|
77
|
+
at = edge; // a slot of no instance's: the region around it
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The slots of an instance: those of its own markup (no other instance's, nor one in another slot), in document order. */
|
|
83
|
+
export const slotsIn = (instance) => [...instance.querySelectorAll(SLOT_PART)].filter((slot) => edgeAround(slot) === instance);
|
|
84
|
+
|
|
20
85
|
/** The kit's mark on an instance of no package, for its look (styles/installed.css). */
|
|
21
86
|
export const FALLBACK = "data-doc-fallback";
|
|
22
87
|
|
|
@@ -150,9 +215,14 @@ export default {
|
|
|
150
215
|
id: "installed",
|
|
151
216
|
category: "whole",
|
|
152
217
|
match: INSTANCE,
|
|
218
|
+
// An instance's slots are flows: what they hold is the document's blocks (FLOWS, styles/flow.css). A
|
|
219
|
+
// plain string, [SLOT]: an expression here would keep this description in every bundle that imports a
|
|
220
|
+
// constant of this file (the runtime takes none of its prose).
|
|
221
|
+
flow: "[data-doc-slot]",
|
|
153
222
|
attrs: {
|
|
154
223
|
"data-doc-component": "the package an instance is of: <scope>/<name>, on a block-level div or section",
|
|
155
224
|
"data-doc-schema": "the shape an instance holds its content in: a whole number its component chooses, read-only past its manifest's schema",
|
|
225
|
+
"data-doc-slot": "a slot of an instance: the part of its manifest edited as flow, by name, on a block-level div or section in the instance's own region; what it holds is the document's blocks",
|
|
156
226
|
},
|
|
157
227
|
runtime: {
|
|
158
228
|
generated: [".doc-component-note", ".doc-component-data"],
|
|
@@ -13,6 +13,13 @@ export function pageLanguage(html) {
|
|
|
13
13
|
return /\slang\s*=\s*["']?en(?:-[^"'\s>]*)?(?=["'\s>]|$)/i.test(tag) ? "en" : "zh";
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* A document's look on its <html> (字体与布局: its doc.json's look, which the render puts on and the kit's
|
|
18
|
+
* tokens read; src/shared/document-look.mjs), the attribute of each choice. The width's is
|
|
19
|
+
* data-doc-measure (a column's measure, its line length): data-doc-width is the reader's own 全宽.
|
|
20
|
+
*/
|
|
21
|
+
export const LOOK_ATTRIBUTES = Object.freeze({ font: "data-doc-font", size: "data-doc-size", spacing: "data-doc-spacing", width: "data-doc-measure" });
|
|
22
|
+
|
|
16
23
|
export default {
|
|
17
24
|
id: "page",
|
|
18
25
|
match: "main.doc-page",
|
|
@@ -31,7 +38,9 @@ export default {
|
|
|
31
38
|
runtime: {
|
|
32
39
|
generated: [".doc-display", "script[data-doc-state]"],
|
|
33
40
|
root: { attrs: ["data-doc-ready", "data-doc-drawn", "data-doc-controls"] },
|
|
34
|
-
|
|
41
|
+
// The reader's theme and width (ui.js), and the document's look (字体与布局: its doc.json's, put on by
|
|
42
|
+
// the render, src/shared/document-look.mjs), which a copy of the view keeps; never written in doc.html.
|
|
43
|
+
state: { html: ["data-doc-theme", "data-doc-width", ...Object.values(LOOK_ATTRIBUTES)] },
|
|
35
44
|
internal: ["doc-display*", "data-doc-export-copy", "data-theme", "doc-export-id", "doc-reveal", "doc-kit-error"],
|
|
36
45
|
},
|
|
37
46
|
};
|
|
@@ -31,7 +31,7 @@ export default {
|
|
|
31
31
|
"data-doc-chart-source": "on its frame: the key a chart drawn from it reads it by (data-doc-chart-data on the chart's host)",
|
|
32
32
|
},
|
|
33
33
|
// A cell may also be given a text color or a fill (doc-text-*, doc-highlight-*: text.mjs), an alignment, a vertical one and bold
|
|
34
|
-
// (doc-bold); a header cell's look is doc-bold doc-highlight-gray, as the page editor gives it.
|
|
34
|
+
// (doc-bold); a header row's cell's look is doc-bold doc-highlight-gray, as the page editor gives it (a header column's th scope="row" none).
|
|
35
35
|
classes: ["doc-table", "doc-table-wrap", "doc-table-meta", "doc-table--sticky-first", ...CELL_ALIGN_CLASSES, ...CELL_VALIGN_CLASSES, CELL_BOLD_CLASS],
|
|
36
36
|
fills: { "th": ["data-sort-type"] },
|
|
37
37
|
runtime: {
|
|
@@ -9,6 +9,6 @@ export default {
|
|
|
9
9
|
...COLORS.map((color) => `doc-highlight-${color}`),
|
|
10
10
|
...["orange", "blue", "purple", "plain", "thin", "thick"].map((kind) => `doc-underline-${kind}`),
|
|
11
11
|
],
|
|
12
|
-
// A number range in the text (3–13, 1–3B), held on one line (runtime/spacing.js).
|
|
13
|
-
runtime: { classes: { ".doc-range": ["doc-range"] } },
|
|
12
|
+
// A number range in the text (3–13, 1–3B), held on one line, and a long run (a path, a URL: src/shared/long-runs.mjs) held in a span that breaks anywhere (runtime/spacing.js).
|
|
13
|
+
runtime: { classes: { ".doc-range": ["doc-range"], ".doc-long-run": ["doc-long-run"] } },
|
|
14
14
|
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// A document's look (字体与布局, under the workbench's 更多: Notion's page format, notion-lite's
|
|
2
|
+
// view/frame/page-format.js): its font, the size of its text, the spacing of its lines and blocks and
|
|
3
|
+
// the width of its reading column. The document's own, as Notion keeps it on the page: `look` in its
|
|
4
|
+
// doc.json (report.json in a document made before Gallery 0.1), over the workspace's default look,
|
|
5
|
+
// `look` in its gallery.json (which the card's 应用到全部文档 writes, every document's own taken away:
|
|
6
|
+
// ./workspace-look.mjs), choice by choice (effectiveLook). The workspace's keeps each choice that is
|
|
7
|
+
// not the default, the field gone when none is; a doc.json each choice that is not the workspace's,
|
|
8
|
+
// the field gone when none is:
|
|
9
|
+
//
|
|
10
|
+
// "look": { "font": "serif" | "mixed", "size": "small" | "large", "spacing": "compact" | "loose", "width": "narrow" | "wide" }
|
|
11
|
+
//
|
|
12
|
+
// A doc.json may say a default too ("font": "sans", "size": "default"…): 默认 chosen on a page while
|
|
13
|
+
// the workspace's default says otherwise. The look a document shows is never written in doc.html: the
|
|
14
|
+
// render puts it on the page's <html> as data attributes
|
|
15
|
+
// (withLook, render/report-html.mjs: the preview, 演示视图, a build, an export, its PDF), which the
|
|
16
|
+
// kit's tokens read (styles/tokens.css, @kit-look, written by render/look-css.mjs); a page takes a
|
|
17
|
+
// change at once (`doc-look-request`, the kit's ui.js: the workbench's choice, sent by the page's
|
|
18
|
+
// session, and doc.json changed, said by the preview's live reload). A value it does not know is the
|
|
19
|
+
// default. docs/render.md "A document's look" says what each choice does.
|
|
20
|
+
|
|
21
|
+
import { LOOK_ATTRIBUTES } from "./components/page.mjs";
|
|
22
|
+
|
|
23
|
+
/** Each choice and its values, the default first. */
|
|
24
|
+
export const LOOK = Object.freeze({
|
|
25
|
+
font: Object.freeze(["sans", "serif", "mixed"]), // 等线体, 衬线体, 混合体 (the title and headings serif)
|
|
26
|
+
size: Object.freeze(["default", "small", "large"]), // 字体大小: 小号, 默认, 大号
|
|
27
|
+
spacing: Object.freeze(["default", "compact", "loose"]), // 正文间距: 紧凑, 默认, 宽松
|
|
28
|
+
width: Object.freeze(["default", "narrow", "wide"]), // 正文宽度: 偏窄, 默认, 偏宽
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/** The attribute each choice is on <html> by: the page's description says them (components/page.mjs). */
|
|
32
|
+
export { LOOK_ATTRIBUTES };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 字体大小: the factor every size of the text takes, and its line height with it (body text 15px, 16px,
|
|
36
|
+
* 18px): the type alone, not the gaps between blocks (正文间距's) nor the column's width (正文宽度's).
|
|
37
|
+
* 14px (Notion's small text) set small text and captions at 11px, too small for Chinese; 17px was hard
|
|
38
|
+
* to tell from 16px.
|
|
39
|
+
*/
|
|
40
|
+
export const SIZE_FACTOR = Object.freeze({ default: 1, small: 15 / 16, large: 18 / 16 });
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 正文间距: what a line's leading (its line height less its font size) and the gaps between blocks
|
|
44
|
+
* are taken at (body text's line 24px, 26px, 28px at 16px; a paragraph's gap 9px, 12px, 15px at any
|
|
45
|
+
* 字体大小).
|
|
46
|
+
*/
|
|
47
|
+
export const SPACING = Object.freeze({
|
|
48
|
+
default: Object.freeze({ leading: 1, gaps: 1 }),
|
|
49
|
+
compact: Object.freeze({ leading: 0.8, gaps: 0.75 }),
|
|
50
|
+
loose: Object.freeze({ leading: 1.2, gaps: 1.25 }),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 正文宽度: the reading column in px, whatever the 字体大小 (which changes the type alone): 默认 the
|
|
55
|
+
* kit's 712px (tokens.css --doc-content-max-width), 44 characters of Chinese body text to a line at the
|
|
56
|
+
* default 16px; 偏窄 and 偏宽 the widths of 40 and 48 of them (40.5 and 48.5 times 16px, as 712 is 44.5
|
|
57
|
+
* times it). The user counted a line in characters only to fix these widths (2026-11-07: until then
|
|
58
|
+
* they were counted at the size chosen, so 大号 widened the column too). A narrow window narrows it as
|
|
59
|
+
* ever.
|
|
60
|
+
*/
|
|
61
|
+
export const WIDTH = Object.freeze({ default: 712, narrow: 648, wide: 776 });
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The serif (衬线体, and 混合体's title and headings): the system's own book serifs, nothing to download
|
|
65
|
+
* (the sidebar's Gallery wordmark is set in the first of them), then the Chinese serifs: Songti SC
|
|
66
|
+
* (macOS), Noto or Source Han Serif where installed, SimSun (Windows).
|
|
67
|
+
*/
|
|
68
|
+
export const SERIF_FONT = '"Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, "Songti SC", "Noto Serif SC", "Noto Serif CJK SC", "Source Han Serif SC", SimSun, serif';
|
|
69
|
+
|
|
70
|
+
const isPlain = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
71
|
+
|
|
72
|
+
/** A look as kept: the choices `value` makes that are known and not the default, {} for none. */
|
|
73
|
+
export function lookOf(value) {
|
|
74
|
+
const look = {};
|
|
75
|
+
if (!isPlain(value)) return look;
|
|
76
|
+
for (const [key, choices] of Object.entries(LOOK)) {
|
|
77
|
+
const chosen = value[key];
|
|
78
|
+
if (typeof chosen === "string" && choices.includes(chosen) && chosen !== choices[0]) look[key] = chosen;
|
|
79
|
+
}
|
|
80
|
+
return look;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Each choice of a look, the default where it makes none: { font, size, spacing, width }. */
|
|
84
|
+
export const fullLook = (value) => ({ ...Object.fromEntries(Object.entries(LOOK).map(([key, [first]]) => [key, first])), ...lookOf(value) });
|
|
85
|
+
|
|
86
|
+
/** Whether two looks are the same. */
|
|
87
|
+
export const sameLook = (a, b) => JSON.stringify(lookOf(a)) === JSON.stringify(lookOf(b));
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A document's own look as its doc.json keeps it: each known choice `value` makes, the default too
|
|
91
|
+
* ("sans", "default": 默认 chosen on a page while the workspace's default look says otherwise), {} for
|
|
92
|
+
* none. lookOf is what it shows of that alone.
|
|
93
|
+
*/
|
|
94
|
+
export function ownLookOf(value) {
|
|
95
|
+
const look = {};
|
|
96
|
+
if (!isPlain(value)) return look;
|
|
97
|
+
for (const [key, choices] of Object.entries(LOOK)) {
|
|
98
|
+
const chosen = value[key];
|
|
99
|
+
if (typeof chosen === "string" && choices.includes(chosen)) look[key] = chosen;
|
|
100
|
+
}
|
|
101
|
+
return look;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The look a document shows (lookOf's form: the defaults left out), choice by choice: its own (doc.json's
|
|
106
|
+
* `look`, ownLookOf), else the workspace's default look (gallery.json's `look`, 应用到全部文档), else the
|
|
107
|
+
* default.
|
|
108
|
+
*/
|
|
109
|
+
export const effectiveLook = (own, workspace) => lookOf({ ...lookOf(workspace), ...ownLookOf(own) });
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* What a document's doc.json keeps for it to show `look` under the workspace's default look
|
|
113
|
+
* `workspace`: each choice that is not the workspace's, a default too (ownLookOf), {} when it shows
|
|
114
|
+
* the workspace's own.
|
|
115
|
+
*/
|
|
116
|
+
export function ownLookFor(look, workspace) {
|
|
117
|
+
const [shown, base] = [fullLook(look), fullLook(workspace)];
|
|
118
|
+
return Object.fromEntries(Object.keys(LOOK).filter((key) => shown[key] !== base[key]).map((key) => [key, shown[key]]));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* A document's doc.json (its parsed object) with its own look as given (ownLookOf: an explicit default
|
|
123
|
+
* kept): in the field's place when it had one, else last; the field gone when it makes no choice.
|
|
124
|
+
* Every other field stays as it is.
|
|
125
|
+
*/
|
|
126
|
+
export function withLookField(metadata, look) {
|
|
127
|
+
const next = ownLookOf(look);
|
|
128
|
+
const keep = Object.keys(next).length > 0;
|
|
129
|
+
const base = isPlain(metadata) ? metadata : {};
|
|
130
|
+
if (!Object.hasOwn(base, "look")) return keep ? { ...base, look: next } : { ...base };
|
|
131
|
+
return Object.fromEntries(Object.entries(base).flatMap(([key, value]) => (key !== "look" ? [[key, value]] : keep ? [[key, next]] : [])));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The look's attributes on <html>: [name, value] for each choice that is not the default. */
|
|
135
|
+
export const lookAttributes = (look) => Object.entries(lookOf(look)).map(([key, value]) => [LOOK_ATTRIBUTES[key], value]);
|
|
136
|
+
|
|
137
|
+
const ATTRIBUTE_VALUE = String.raw`(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]+))?`;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* A page's text with the look on its <html> start tag (the first one), as the render serves and
|
|
141
|
+
* exports it: whatever look attributes the file writes there taken off (doc.json is their one home),
|
|
142
|
+
* the look's put on. A page without an <html> tag is returned as it is.
|
|
143
|
+
*/
|
|
144
|
+
export function withLook(html, look) {
|
|
145
|
+
const text = String(html ?? "");
|
|
146
|
+
const found = /<html\b[^>]*>/i.exec(text);
|
|
147
|
+
if (!found) return text;
|
|
148
|
+
let tag = found[0];
|
|
149
|
+
for (const name of Object.values(LOOK_ATTRIBUTES)) tag = tag.replace(new RegExp(String.raw`\s${name}${ATTRIBUTE_VALUE}(?=[\s/>])`, "gi"), "");
|
|
150
|
+
const added = lookAttributes(look).map(([name, value]) => ` ${name}="${value}"`).join("");
|
|
151
|
+
if (added) tag = tag.replace(/\s*>$/, (end) => `${added}${end.trimStart()}`);
|
|
152
|
+
return text.slice(0, found.index) + tag + text.slice(found.index + found[0].length);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The look an element (the page's <html>) carries by its attributes. */
|
|
156
|
+
export function lookOfElement(element) {
|
|
157
|
+
return lookOf(Object.fromEntries(Object.entries(LOOK_ATTRIBUTES).map(([key, name]) => [key, element?.getAttribute?.(name) ?? undefined])));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Put a look on an element (the page's <html>) by its attributes; whether anything changed. */
|
|
161
|
+
export function setLookOn(element, look) {
|
|
162
|
+
const next = lookOf(look);
|
|
163
|
+
let changed = false;
|
|
164
|
+
for (const [key, name] of Object.entries(LOOK_ATTRIBUTES)) {
|
|
165
|
+
const value = next[key] ?? null;
|
|
166
|
+
if (element.getAttribute(name) === value) continue;
|
|
167
|
+
if (value === null) element.removeAttribute(name);
|
|
168
|
+
else element.setAttribute(name, value);
|
|
169
|
+
changed = true;
|
|
170
|
+
}
|
|
171
|
+
return changed;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** A look as a line's word (the preview's live reload says "look <word>"): "font=serif&size=small", "" for the default. */
|
|
175
|
+
export const lookWord = (look) => Object.entries(lookOf(look)).map(([key, value]) => `${key}=${value}`).join("&");
|
|
176
|
+
|
|
177
|
+
/** The look a word says (lookWord's inverse; anything else the default). */
|
|
178
|
+
export const lookOfWord = (word) => lookOf(Object.fromEntries(new URLSearchParams(String(word ?? ""))));
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucide Icons and Contributors
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
The following Lucide icons are derived from the Feather project:
|
|
20
|
+
|
|
21
|
+
airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
|
|
22
|
+
|
|
23
|
+
The MIT License (MIT) (for the icons listed above)
|
|
24
|
+
|
|
25
|
+
Copyright (c) 2013-present Cole Bemis
|
|
26
|
+
|
|
27
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
28
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
29
|
+
in the Software without restriction, including without limitation the rights
|
|
30
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
31
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
32
|
+
furnished to do so, subject to the following conditions:
|
|
33
|
+
|
|
34
|
+
The above copyright notice and this permission notice shall be included in all
|
|
35
|
+
copies or substantial portions of the Software.
|
|
36
|
+
|
|
37
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
38
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
39
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
40
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
41
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
42
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
43
|
+
SOFTWARE.
|