@kamishibai/sdk 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/README.md +192 -0
- package/package.json +54 -0
- package/src/blocks/board.js +210 -0
- package/src/blocks/callout.js +63 -0
- package/src/blocks/code.js +28 -0
- package/src/blocks/deck.js +76 -0
- package/src/blocks/diagram.js +265 -0
- package/src/blocks/element.js +51 -0
- package/src/blocks/graph.js +264 -0
- package/src/blocks/grid.js +156 -0
- package/src/blocks/index.js +106 -0
- package/src/blocks/list.js +50 -0
- package/src/blocks/placement.js +119 -0
- package/src/blocks/prose.js +28 -0
- package/src/blocks/quote.js +25 -0
- package/src/blocks/raw.js +47 -0
- package/src/blocks/registry.js +158 -0
- package/src/blocks/schema-parts.js +19 -0
- package/src/blocks/section.js +53 -0
- package/src/blocks/slide.js +104 -0
- package/src/blocks/stat.js +83 -0
- package/src/blocks/table.js +50 -0
- package/src/blocks/timeline.js +80 -0
- package/src/cli/commands/close.js +51 -0
- package/src/cli/commands/comments.js +73 -0
- package/src/cli/commands/debug.js +34 -0
- package/src/cli/commands/example.js +22 -0
- package/src/cli/commands/export.js +10 -0
- package/src/cli/commands/init.js +53 -0
- package/src/cli/commands/lint.js +93 -0
- package/src/cli/commands/list.js +54 -0
- package/src/cli/commands/open.js +31 -0
- package/src/cli/commands/promote.js +38 -0
- package/src/cli/commands/render.js +31 -0
- package/src/cli/commands/replay.js +49 -0
- package/src/cli/commands/schema.js +10 -0
- package/src/cli/commands/serve.js +178 -0
- package/src/cli/commands/setup.js +64 -0
- package/src/cli/commands/snapshot.js +29 -0
- package/src/cli/commands/templates.js +75 -0
- package/src/cli/deliver.js +54 -0
- package/src/cli/emit.js +29 -0
- package/src/cli/format.js +153 -0
- package/src/cli/index.js +365 -0
- package/src/cli/registry.js +18 -0
- package/src/core/blocks.js +147 -0
- package/src/core/diagram.js +282 -0
- package/src/core/errors.js +156 -0
- package/src/core/example.js +109 -0
- package/src/core/ir.js +62 -0
- package/src/core/lint-gates.js +427 -0
- package/src/core/lint.js +281 -0
- package/src/core/scan.js +84 -0
- package/src/core/schema.js +88 -0
- package/src/core/spec-check.js +33 -0
- package/src/core/validate.js +44 -0
- package/src/core/version.js +18 -0
- package/src/core/vocabulary.js +140 -0
- package/src/delivery/atomic.js +71 -0
- package/src/delivery/comments.js +180 -0
- package/src/delivery/home.js +70 -0
- package/src/delivery/open.js +31 -0
- package/src/delivery/project.js +141 -0
- package/src/delivery/read.js +109 -0
- package/src/delivery/run.js +95 -0
- package/src/delivery/scaffold-blueprints.js +728 -0
- package/src/delivery/store.js +219 -0
- package/src/delivery/template-extensions.js +183 -0
- package/src/delivery/template-format.js +112 -0
- package/src/delivery/template-package.js +376 -0
- package/src/delivery/template-promote.js +240 -0
- package/src/delivery/template-scaffold.js +181 -0
- package/src/delivery/templates.js +192 -0
- package/src/delivery/toml.js +195 -0
- package/src/delivery/write.js +35 -0
- package/src/export/browser.js +130 -0
- package/src/export/index.js +96 -0
- package/src/export/pdf.js +25 -0
- package/src/export/png.js +40 -0
- package/src/export/pptx.js +48 -0
- package/src/export/slides.js +33 -0
- package/src/export/snapshot.js +33 -0
- package/src/layouts/article.js +103 -0
- package/src/layouts/canvas.js +144 -0
- package/src/layouts/card.js +128 -0
- package/src/layouts/deck.js +88 -0
- package/src/layouts/index.js +90 -0
- package/src/layouts/one-page.js +161 -0
- package/src/layouts/registry.js +251 -0
- package/src/layouts/resume.js +172 -0
- package/src/layouts/template-index.js +78 -0
- package/src/parser/artifact.js +38 -0
- package/src/parser/container.js +103 -0
- package/src/parser/index.js +223 -0
- package/src/parser/tokens.js +265 -0
- package/src/render/board-filter.client.js +80 -0
- package/src/render/compile.js +29 -0
- package/src/render/context.js +98 -0
- package/src/render/element.js +32 -0
- package/src/render/fonts.js +129 -0
- package/src/render/graph-hover.client.js +148 -0
- package/src/render/html.js +52 -0
- package/src/render/index.js +241 -0
- package/src/render/measure.js +60 -0
- package/src/render/placement.js +136 -0
- package/src/render/playback.client.js +74 -0
- package/src/render/scale-to-fit.client.js +136 -0
- package/src/render/scale.js +41 -0
- package/src/render/skeleton.js +131 -0
- package/src/render/ssr.js +24 -0
- package/src/render/styles.js +56 -0
- package/src/render/templates.js +191 -0
- package/src/serve/daemon.js +117 -0
- package/src/serve/overlay.js +213 -0
- package/src/serve/protocol.js +36 -0
- package/src/serve/server.js +264 -0
- package/templates/kami/cards/components.js +40 -0
- package/templates/kami/cards/index.js +25 -0
- package/templates/kami/cards/manifest.js +67 -0
- package/templates/kami/cards/styles.css +389 -0
- package/templates/kami/long-form/components.js +102 -0
- package/templates/kami/long-form/index.js +25 -0
- package/templates/kami/long-form/manifest.js +87 -0
- package/templates/kami/long-form/styles.css +481 -0
- package/templates/kami/one-page/components.js +48 -0
- package/templates/kami/one-page/index.js +27 -0
- package/templates/kami/one-page/manifest.js +65 -0
- package/templates/kami/one-page/styles.css +375 -0
- package/templates/kami/resume/components.js +51 -0
- package/templates/kami/resume/index.js +27 -0
- package/templates/kami/resume/manifest.js +65 -0
- package/templates/kami/resume/styles.css +424 -0
- package/templates/kami/slides/components.js +41 -0
- package/templates/kami/slides/index.js +26 -0
- package/templates/kami/slides/manifest.js +64 -0
- package/templates/kami/slides/styles.css +406 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { el } from '../blocks/element.js'
|
|
2
|
+
import { CANVAS_FLOWING } from './registry.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `card` — the flowing 文體 whose unit of reading is a **card**, not a column.
|
|
6
|
+
*
|
|
7
|
+
* A long-form page answers 「從頭讀到尾」; this one answers 「掃過去,挑一張看」.
|
|
8
|
+
* That is the whole difference, and it is a difference of *cut* rather than of
|
|
9
|
+
* content: the same document tree that `article` draws as one scrolling column
|
|
10
|
+
* is drawn here as a board of cards laid on a responsive grid — the home of a
|
|
11
|
+
* dashboard, a set of knowledge cards, a status board (CONTRACT F2c P1).
|
|
12
|
+
*
|
|
13
|
+
* The canvas stays `flowing`. A board of cards has no fixed logical size: it
|
|
14
|
+
* reflows to whatever window it lands in, and asking it to scale-to-fit would
|
|
15
|
+
* be asking a dashboard to be a slide. `maxWidth` is therefore how wide the
|
|
16
|
+
* board may get and `measure` is still the width of a line of text (CONTRACT
|
|
17
|
+
* C3) — two numbers, and on this 文體 they are pulled far apart, because a
|
|
18
|
+
* three-across board is nothing like a column of prose.
|
|
19
|
+
*
|
|
20
|
+
* ## The cut: top-level sections become cards, everything else leads in
|
|
21
|
+
*
|
|
22
|
+
* The document tree the parser produces nests every heading under its parent,
|
|
23
|
+
* so a document's **top-level `section` blocks are its chapters**. Each one
|
|
24
|
+
* becomes a card; whatever else sits at the top level (an opening paragraph, a
|
|
25
|
+
* callout, a table of contents someone wrote by hand) is the board's lead-in.
|
|
26
|
+
*
|
|
27
|
+
* CONTRACT F2c P1 words this as 「level-1 section 各成一卡」, and in an ordinary
|
|
28
|
+
* document — one that starts at `#` — the two sentences pick out exactly the
|
|
29
|
+
* same blocks. They part company only for a document that starts at `##`, and
|
|
30
|
+
* there the level test is the worse of the two: it would silently yield a board
|
|
31
|
+
* with no cards at all, because a heading-depth detail the author never thought
|
|
32
|
+
* about decided the shape of the page. Nothing is lost either way (every block
|
|
33
|
+
* is drawn), so the rule that does not depend on how deep the author started is
|
|
34
|
+
* the one this module takes (CONTRACT R10, executor's reason).
|
|
35
|
+
*
|
|
36
|
+
* ## Why there is no knob here
|
|
37
|
+
*
|
|
38
|
+
* `article` declares knobs because a rail is geometry the *markup* has to know
|
|
39
|
+
* about: the region only exists when the knob is on. A card board has no such
|
|
40
|
+
* switch — how many of the 24 columns a card spans, how wide the gutters are,
|
|
41
|
+
* where the grid breaks to one column: every one of those is read by the
|
|
42
|
+
* stylesheet and by nothing else, which makes them decoration, and decoration
|
|
43
|
+
* lives in the skin (`templates/kami/cards/styles.css`), never in a 文體.
|
|
44
|
+
* Declaring them here would put numbers in the layout that no other layer could
|
|
45
|
+
* ever ask about — the exact shape 「layout 只擁有切法」 rules out.
|
|
46
|
+
*
|
|
47
|
+
* It equally does **not** declare `gridAddressed`. The 24 tracks belong to the
|
|
48
|
+
* card grid, whose children are cards and only cards; a bare `col`/`colSpan` on
|
|
49
|
+
* a top-level block would land in the lead-in region, where there are no tracks
|
|
50
|
+
* to resolve against. Claiming the page is grid-addressed would licence exactly
|
|
51
|
+
* that coordinate and let CSS auto-placement drop it in silence.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/** Verbatim — the block type one card is made of. */
|
|
55
|
+
const CARD_BLOCK = 'section'
|
|
56
|
+
|
|
57
|
+
/** Verbatim — the three regions of a board, as class names. */
|
|
58
|
+
const BOARD_CLASS = 'cards'
|
|
59
|
+
const LEAD_IN_CLASS = 'cards-intro'
|
|
60
|
+
const GRID_CLASS = 'card-grid'
|
|
61
|
+
const CARD_CLASS = 'card'
|
|
62
|
+
|
|
63
|
+
const isCard = (block) => block?.type === CARD_BLOCK
|
|
64
|
+
|
|
65
|
+
const region = (className, nodes) =>
|
|
66
|
+
nodes.length > 0 ? [el('div', { class: className }, nodes)] : []
|
|
67
|
+
|
|
68
|
+
export default Object.freeze({
|
|
69
|
+
name: 'card',
|
|
70
|
+
|
|
71
|
+
canvas: CANVAS_FLOWING,
|
|
72
|
+
|
|
73
|
+
/** No required root form: a board draws whatever sits under `doc` (as `article`). */
|
|
74
|
+
rootForm: null,
|
|
75
|
+
|
|
76
|
+
/** The built-in generic template of this 文體 — `render` never wants for one. */
|
|
77
|
+
defaultTemplate: 'kami/cards',
|
|
78
|
+
|
|
79
|
+
slots: Object.freeze(['cards-lead', 'cards-foot']),
|
|
80
|
+
|
|
81
|
+
styleHooks: Object.freeze([BOARD_CLASS, LEAD_IN_CLASS, GRID_CLASS, CARD_CLASS]),
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Factory geometry, in CSS pixels. Geometry only — a content-shaped default
|
|
85
|
+
* would make this layout mean something, and a layout that means something can
|
|
86
|
+
* only carry the documents it already imagined.
|
|
87
|
+
*
|
|
88
|
+
* 1200 is a board wide enough for three cards side by side without pretending
|
|
89
|
+
* to be a full-bleed application shell; 740 is the width of a line of text,
|
|
90
|
+
* unchanged from every other 文體 (CONTRACT C3: the page and the column are
|
|
91
|
+
* two numbers).
|
|
92
|
+
*/
|
|
93
|
+
defaults: Object.freeze({ maxWidth: 1200, measure: 740 }),
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A region is drawn only when something goes in it.
|
|
97
|
+
*
|
|
98
|
+
* The opposite reading of `article`'s rail, and deliberately so: the rail is
|
|
99
|
+
* *applied state* (a knob the template turned), so it stays in the markup even
|
|
100
|
+
* when empty, or the same package would be a different page for each document.
|
|
101
|
+
* These two regions have no knob and never could — whether a document has a
|
|
102
|
+
* lead-in is a fact about the document. Drawing an empty one would put a box
|
|
103
|
+
* in every artifact to say nothing, and a `null` child would put a real empty
|
|
104
|
+
* comment node there instead, which is worse: it is invisible and permanent.
|
|
105
|
+
*/
|
|
106
|
+
root: (doc, ctx) => {
|
|
107
|
+
const children = doc?.children ?? []
|
|
108
|
+
const cards = children.filter(isCard)
|
|
109
|
+
const leadIn = children.filter((block) => !isCard(block))
|
|
110
|
+
return el(
|
|
111
|
+
'article',
|
|
112
|
+
{ class: BOARD_CLASS, 'data-template': ctx.templateKey },
|
|
113
|
+
[
|
|
114
|
+
...ctx.chrome('cards-lead', ctx.meta),
|
|
115
|
+
...region(LEAD_IN_CLASS, ctx.renderChildren(leadIn)),
|
|
116
|
+
...region(
|
|
117
|
+
GRID_CLASS,
|
|
118
|
+
cards.map((block, index) =>
|
|
119
|
+
el('div', { class: CARD_CLASS }, [
|
|
120
|
+
ctx.renderBlock(block, { index, total: cards.length }),
|
|
121
|
+
]),
|
|
122
|
+
),
|
|
123
|
+
),
|
|
124
|
+
...ctx.chrome('cards-foot', ctx.meta),
|
|
125
|
+
],
|
|
126
|
+
)
|
|
127
|
+
},
|
|
128
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { CANVAS_BOUNDED } from './registry.js'
|
|
2
|
+
|
|
3
|
+
/** Verbatim — the root form that makes an artifact a deck. */
|
|
4
|
+
const DECK_ROOT = 'deck'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Verbatim — the deck's fixed logical canvas, in CSS pixels (CONTRACT F2f P3).
|
|
8
|
+
*
|
|
9
|
+
* 1280×720 rather than any other 16:9 pair because it is the size every slide
|
|
10
|
+
* tool, every projector mode and every screen-capture default already speaks,
|
|
11
|
+
* and because a *logical* canvas only has to be the one everybody means: the
|
|
12
|
+
* artifact is scaled to whatever the window is (`src/render/scale.js`), so the
|
|
13
|
+
* number's job is to be shared, not to be large.
|
|
14
|
+
*/
|
|
15
|
+
const DECK_CANVAS = Object.freeze({ width: 1280, height: 720 })
|
|
16
|
+
|
|
17
|
+
/** The deck sits directly under `doc` (parser/index.js buildDeck). */
|
|
18
|
+
const deckOf = (doc) => (doc?.children ?? []).find((block) => block?.type === DECK_ROOT)
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `deck` — the slide 文體.
|
|
22
|
+
*
|
|
23
|
+
* Its root form is a single `deck` block, and that requirement is the layout's,
|
|
24
|
+
* not any one template's: handing a flat document tree to a deck template used
|
|
25
|
+
* to type-check completely and then draw nothing at all. Stating it here is
|
|
26
|
+
* what turns 「replay 換皮不換文體」 from a rule into a structure — every deck
|
|
27
|
+
* template inherits the refusal, and none of them can decline it.
|
|
28
|
+
*
|
|
29
|
+
* ## The canvas is `bounded`, and the canvas is one slide
|
|
30
|
+
*
|
|
31
|
+
* F2a built the mechanism and deliberately did not connect it: declaring
|
|
32
|
+
* `bounded` moves every slide artifact's bytes, and that slice was only allowed
|
|
33
|
+
* to flip the structure (CONTRACT F2a P4「只建不接」). F2f is the slice that may
|
|
34
|
+
* move them, so the declaration is now true — and with it v1's 「fullscreen
|
|
35
|
+
* 只多黑框」 finally ends: going fullscreen used to enlarge the page and leave
|
|
36
|
+
* the artifact exactly as small as before, because nothing was ever scaled.
|
|
37
|
+
*
|
|
38
|
+
* The fixed logical canvas is **one slide**, not the whole deck. A deck has N
|
|
39
|
+
* pages; a page is the thing with a fixed size, and 1280×720 is what a page of
|
|
40
|
+
* a deck is. Putting the canvas on the deck instead would have made the
|
|
41
|
+
* artifact a single 720px-tall box containing every slide — which reads as
|
|
42
|
+
* one slide and hides the rest, and would take the no-JS fallback (a plain
|
|
43
|
+
* scrolling document, `playback.client.js` の前提) with it.
|
|
44
|
+
*
|
|
45
|
+
* ## Why it also declares `gridAddressed`
|
|
46
|
+
*
|
|
47
|
+
* Because it now lays the tracks. `gridAddressed` is a claim the placement gate
|
|
48
|
+
* takes at face value (`src/render/placement.js`), so a layout that declared it
|
|
49
|
+
* without building the 24 columns would license coordinates that CSS
|
|
50
|
+
* auto-placement then drops in silence — the exact hole F2a's gate closes. The
|
|
51
|
+
* tracks are laid on the slide's content region by `src/blocks/slide.js`, and
|
|
52
|
+
* they are laid **because this layout says so**: the block asks the resolved
|
|
53
|
+
* canvas rather than assuming, so the same module still draws a flowing
|
|
54
|
+
* deck-rooted layout the plain way.
|
|
55
|
+
*/
|
|
56
|
+
export default Object.freeze({
|
|
57
|
+
name: 'deck',
|
|
58
|
+
|
|
59
|
+
canvas: CANVAS_BOUNDED,
|
|
60
|
+
|
|
61
|
+
rootForm: DECK_ROOT,
|
|
62
|
+
|
|
63
|
+
defaultTemplate: 'kami/slides',
|
|
64
|
+
|
|
65
|
+
slots: Object.freeze(['slide-lead', 'deck-chrome']),
|
|
66
|
+
|
|
67
|
+
styleHooks: Object.freeze(['deck', 'slide', 'slide-inner', 'slide-cell']),
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The whole page is addressable by 24-grid coordinates — see above, and note
|
|
71
|
+
* that `bounded` deliberately does **not** imply this (F2a 複驗補丁).
|
|
72
|
+
*/
|
|
73
|
+
gridAddressed: true,
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Factory geometry, in CSS pixels. Three numbers, three different jobs, and
|
|
77
|
+
* the reason they are three:
|
|
78
|
+
*
|
|
79
|
+
* - `canvasSize` 1280×720 — the logical page that gets scaled to fit.
|
|
80
|
+
* - `maxWidth` 1120 — how wide the content region inside that page may grow.
|
|
81
|
+
* A slide with type running the full 1280 has no margin at all.
|
|
82
|
+
* - `measure` 740 — the width a *line of text* wants, unchanged from every
|
|
83
|
+
* other 文體 (CONTRACT C3: the page and the column are two numbers).
|
|
84
|
+
*/
|
|
85
|
+
defaults: Object.freeze({ maxWidth: 1120, measure: 740, canvasSize: DECK_CANVAS }),
|
|
86
|
+
|
|
87
|
+
root: (doc, ctx) => ctx.renderBlock(deckOf(doc)),
|
|
88
|
+
})
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { registerLayout, sealCoreLayouts, layoutModule, layoutNames } from './registry.js'
|
|
2
|
+
|
|
3
|
+
import article from './article.js'
|
|
4
|
+
import card from './card.js'
|
|
5
|
+
import deck from './deck.js'
|
|
6
|
+
import onePage from './one-page.js'
|
|
7
|
+
import resume from './resume.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The factory 文體 set, in canonical order.
|
|
11
|
+
*
|
|
12
|
+
* **All five** of the v1 layouts (issue 13 裁決 1) exist as of F2e. `article` and
|
|
13
|
+
* `deck` were here from F2a because they are what the two shipped templates
|
|
14
|
+
* already were; `card` is the first one this SDK *added* rather than lifted, and
|
|
15
|
+
* adding it was the measurement F2c existed to take — a new 文體 should cost one
|
|
16
|
+
* layout module, one built-in template, one scaffold blueprint and this line,
|
|
17
|
+
* with no edit to the layout interface itself (CONTRACT F2c P2).
|
|
18
|
+
*
|
|
19
|
+
* `one-page` (F2d) takes that measurement again on the harder half: it is the
|
|
20
|
+
* first **bounded** 文體 added through the public shape rather than lifted from
|
|
21
|
+
* an existing template, so it is the first to exercise `defaults.canvasSize`,
|
|
22
|
+
* the registry's bounded-geometry refusal and the scale-to-fit skeleton from
|
|
23
|
+
* the outside. It cost the same one line here.
|
|
24
|
+
*
|
|
25
|
+
* `resume` (F2e) closes the set, and it is the one that asks the interface a
|
|
26
|
+
* question none of the other four could: its artifact is *printed*, so its
|
|
27
|
+
* canvas is a sheet of A4 rather than a shape a window has. It too cost one
|
|
28
|
+
* line here — the paper lives in the layout module, not in the SDK.
|
|
29
|
+
*
|
|
30
|
+
* A sixth does not belong here at all: the v1 list is five, and a third-party
|
|
31
|
+
* layout calls `registerLayout` from outside and needs no edit to any file
|
|
32
|
+
* inside `src/`.
|
|
33
|
+
*/
|
|
34
|
+
const CORE_LAYOUTS = Object.freeze([article, card, deck, onePage, resume])
|
|
35
|
+
|
|
36
|
+
for (const mod of CORE_LAYOUTS) registerLayout(mod)
|
|
37
|
+
sealCoreLayouts()
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The 文體 an artifact is drawn in, resolved from the template's manifest.
|
|
41
|
+
*
|
|
42
|
+
* A manifest that names no layout falls back to the first factory layout, which
|
|
43
|
+
* keeps every pre-F2a manifest (and every third-party one written against it)
|
|
44
|
+
* loadable: the flowing article is what a template *was* before layouts had
|
|
45
|
+
* names.
|
|
46
|
+
*/
|
|
47
|
+
export const DEFAULT_LAYOUT = article.name
|
|
48
|
+
|
|
49
|
+
export function resolveLayout(manifest) {
|
|
50
|
+
return layoutModule(manifest?.layout ?? DEFAULT_LAYOUT)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The built-in generic template of a layout — the tail of the resolution chain. */
|
|
54
|
+
export const defaultTemplateOf = (name = DEFAULT_LAYOUT) =>
|
|
55
|
+
layoutModule(name)?.defaultTemplate
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
CANVAS_BOUNDED,
|
|
59
|
+
CANVAS_FLOWING,
|
|
60
|
+
CANVAS_MODES,
|
|
61
|
+
GRID_ADDRESSED_FIELD,
|
|
62
|
+
LAYOUT_DEFAULT_KEYS,
|
|
63
|
+
LAYOUT_KNOBS_FIELD,
|
|
64
|
+
LAYOUT_MODULE_FIELDS,
|
|
65
|
+
PLUGIN_LAYOUT_PREFIX,
|
|
66
|
+
layoutModule,
|
|
67
|
+
layoutModules,
|
|
68
|
+
layoutNames,
|
|
69
|
+
layoutRegistryVersion,
|
|
70
|
+
pluginLayoutNames,
|
|
71
|
+
registerLayout,
|
|
72
|
+
resetLayoutRegistry,
|
|
73
|
+
} from './registry.js'
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
CANVAS_ATTR,
|
|
77
|
+
CANVAS_INVALID_CODE,
|
|
78
|
+
canvasAttrs,
|
|
79
|
+
canvasProblems,
|
|
80
|
+
resolveCanvas,
|
|
81
|
+
} from './canvas.js'
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
registerTemplateLayoutSource,
|
|
85
|
+
resetTemplateLayoutSources,
|
|
86
|
+
templateLayoutEntries,
|
|
87
|
+
} from './template-index.js'
|
|
88
|
+
|
|
89
|
+
/** Exported for the layer that has to name every 文體 in a message. */
|
|
90
|
+
export const knownLayouts = () => layoutNames()
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { el } from '../blocks/element.js'
|
|
2
|
+
import { CANVAS_BOUNDED } from './registry.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `one-page` — the 文體 of a general one-page static website.
|
|
6
|
+
*
|
|
7
|
+
* A long-form page answers 「從頭讀到尾」, a board of cards answers 「掃過去,挑
|
|
8
|
+
* 一張看」; this one answers 「一直往下捲,一屏換一屏」. It is the shape a landing
|
|
9
|
+
* page, a product page or a service page already has: a few full-bleed screens,
|
|
10
|
+
* each holding one idea, read by scrolling past them in order (13 號票 v1 五版面
|
|
11
|
+
* 之四).
|
|
12
|
+
*
|
|
13
|
+
* ## The cut: one top-level section is one screen
|
|
14
|
+
*
|
|
15
|
+
* The parser nests every heading under its parent, so a document's **top-level
|
|
16
|
+
* `section` blocks are its chapters** — and on this 文體 a chapter is a screen.
|
|
17
|
+
* Whatever else sits at the top level (an opening paragraph, a callout, a lead
|
|
18
|
+
* sentence someone wrote before the first heading) joins the template's
|
|
19
|
+
* `page-lead` chrome to become the **opening screen**, which is what a hero is.
|
|
20
|
+
*
|
|
21
|
+
* The test is 「是不是頂層 section」 and deliberately **not** 「是不是 level 1」,
|
|
22
|
+
* the same R10 reasoning `card` records: an ordinary document (one that starts
|
|
23
|
+
* at `#`) makes the two sentences pick out identical blocks, and they part
|
|
24
|
+
* company only for a document that starts at `##` — where the level test would
|
|
25
|
+
* silently yield a site with no screens at all, because a heading-depth detail
|
|
26
|
+
* the author never thought about decided the shape of the page.
|
|
27
|
+
*
|
|
28
|
+
* ## The canvas is bounded, and **one screen is one canvas** (CONTRACT F2d R10)
|
|
29
|
+
*
|
|
30
|
+
* A bounded 文體 has a fixed logical canvas that is scaled to fit (issue 14
|
|
31
|
+
* 裁決 4), and the contract left *which* canvas to the executor. There were two
|
|
32
|
+
* candidates and the measurement settles it. Making the whole scrolling page a
|
|
33
|
+
* single tall canvas puts a page-height divided by a window-height into
|
|
34
|
+
* `Math.min(vw / w, vh / h)` — so the whole site is shrunk to a postage stamp.
|
|
35
|
+
* Measured in a real browser on `fixtures/one-page-sample.md` (6 screens,
|
|
36
|
+
* natural height 2398px) with the real `scale-to-fit.client.js` running:
|
|
37
|
+
*
|
|
38
|
+
* viewport 1440x900 whole page 0.375x -> 18px body text lands at 6.8px
|
|
39
|
+
* per screen 1.000x -> 18px body text lands at 18.0px
|
|
40
|
+
* viewport 1280x720 whole page 0.300x -> 5.4px per screen 0.800x -> 14.4px
|
|
41
|
+
* viewport 1920x1080 whole page 0.450x -> 8.1px per screen 1.200x -> 21.6px
|
|
42
|
+
*
|
|
43
|
+
* and the whole-page half gets *worse the longer the page is* — which is the
|
|
44
|
+
* defining property of a one-page site. So the canvas is a screen, exactly as a
|
|
45
|
+
* `deck`'s canvas is a slide and not the deck: the artifact is a scrollable
|
|
46
|
+
* sequence of bounded screens, which is also what keeps the no-JS form honest
|
|
47
|
+
* (nothing is scaled, everything is still there, in order).
|
|
48
|
+
*
|
|
49
|
+
* ## Why there is no knob here
|
|
50
|
+
*
|
|
51
|
+
* How a screen is padded, how the opening screen arranges itself, how large the
|
|
52
|
+
* type is: every one of those is read by the stylesheet and by nothing else,
|
|
53
|
+
* which makes them decoration, and decoration lives in the skin
|
|
54
|
+
* (`templates/kami/one-page/styles.css`). The logical canvas is not a knob — it
|
|
55
|
+
* is `defaults.canvasSize`, the one extra field every bounded 文體 has, and the
|
|
56
|
+
* registry requires it at registration time.
|
|
57
|
+
*
|
|
58
|
+
* ## Why it does **not** declare `gridAddressed`
|
|
59
|
+
*
|
|
60
|
+
* `deck` may declare it because a slide's children are a flat run of blocks:
|
|
61
|
+
* the 24 tracks are laid on that content region and a bare `col: 17` resolves
|
|
62
|
+
* to a real cell. A screen is not that shape. A screen's child is exactly **one
|
|
63
|
+
* `section`**, and a `#` heading swallows everything beneath it — which is why
|
|
64
|
+
* even the deck scaffold's own 預設 md warns that a titled slide places its
|
|
65
|
+
* content relative to the section rather than to the page. Tracks laid here
|
|
66
|
+
* would have a single child, and any coordinate an author wrote *inside* the
|
|
67
|
+
* section would not be a direct child of the region at all: CSS auto-placement
|
|
68
|
+
* would drop it in silence, and the placement gate — which takes the
|
|
69
|
+
* declaration at face value (`src/render/placement.js`) — would have stopped
|
|
70
|
+
* refusing it. The claim and the tracks must land together, so neither lands.
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/** Verbatim — the block type one screen is made of. */
|
|
74
|
+
const SCREEN_BLOCK = 'section'
|
|
75
|
+
|
|
76
|
+
/** Verbatim — the two regions of a one-page site, as class names. */
|
|
77
|
+
const PAGE_CLASS = 'one-page'
|
|
78
|
+
const SCREEN_CLASS = 'screen'
|
|
79
|
+
const SCREEN_INNER_CLASS = 'screen-inner'
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Verbatim — the fixed logical canvas of **one screen**, in CSS pixels.
|
|
83
|
+
*
|
|
84
|
+
* 1440×900 rather than the deck's 1280×720 because a screen of a website is not
|
|
85
|
+
* a slide: 1440 is the width every design tool's desktop artboard already is,
|
|
86
|
+
* and 16:10 rather than 16:9 leaves a screen the extra height a page of a site
|
|
87
|
+
* uses for a heading plus a paragraph plus a call to action. Like every logical
|
|
88
|
+
* canvas the number's job is to be shared, not to be large — the artifact is
|
|
89
|
+
* scaled to whatever window it lands in (`src/render/scale.js`).
|
|
90
|
+
*/
|
|
91
|
+
const SCREEN_CANVAS = Object.freeze({ width: 1440, height: 900 })
|
|
92
|
+
|
|
93
|
+
const isScreen = (block) => block?.type === SCREEN_BLOCK
|
|
94
|
+
|
|
95
|
+
export default Object.freeze({
|
|
96
|
+
name: 'one-page',
|
|
97
|
+
|
|
98
|
+
canvas: CANVAS_BOUNDED,
|
|
99
|
+
|
|
100
|
+
/** No required root form: a site draws whatever sits under `doc` (as `article`). */
|
|
101
|
+
rootForm: null,
|
|
102
|
+
|
|
103
|
+
/** The built-in generic template of this 文體 — `render` never wants for one. */
|
|
104
|
+
defaultTemplate: 'kami/one-page',
|
|
105
|
+
|
|
106
|
+
slots: Object.freeze(['page-lead', 'page-foot']),
|
|
107
|
+
|
|
108
|
+
styleHooks: Object.freeze([PAGE_CLASS, SCREEN_CLASS, SCREEN_INNER_CLASS]),
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Factory geometry, in CSS pixels. Three numbers, three different jobs:
|
|
112
|
+
*
|
|
113
|
+
* - `canvasSize` 1440×900 — the logical screen that gets scaled to fit.
|
|
114
|
+
* - `maxWidth` 1200 — how wide the content region inside a screen may grow.
|
|
115
|
+
* A screen with type running the full 1440 has no margin at all.
|
|
116
|
+
* - `measure` 740 — the width a *line of text* wants, unchanged from every
|
|
117
|
+
* other 文體 (CONTRACT C3: the page and the column are two numbers).
|
|
118
|
+
*/
|
|
119
|
+
defaults: Object.freeze({ maxWidth: 1200, measure: 740, canvasSize: SCREEN_CANVAS }),
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* A screen is drawn only when something goes in it.
|
|
123
|
+
*
|
|
124
|
+
* The opening screen exists when the document has top-level content that is
|
|
125
|
+
* not a chapter, or when the template filled `page-lead` — and not otherwise.
|
|
126
|
+
* The same reading as `card`'s two regions, and for the same reason: whether
|
|
127
|
+
* a site has an opening is a fact about the document, not applied state a
|
|
128
|
+
* template turned, so drawing an empty one would put a blank screen in front
|
|
129
|
+
* of every artifact that has no lead-in. `...(x ? [n] : [])` rather than
|
|
130
|
+
* `x ? n : null`, because a `null` child is a real, invisible, permanent empty
|
|
131
|
+
* comment node in the artifact.
|
|
132
|
+
*
|
|
133
|
+
* The canvas attributes come from `ctx.canvas`, never from the constant above:
|
|
134
|
+
* a template may not move the logical canvas, but the resolved canvas is the
|
|
135
|
+
* one place that answer is computed, and a second computation here is a scale
|
|
136
|
+
* factor free to disagree with the one the skeleton uses.
|
|
137
|
+
*/
|
|
138
|
+
root: (doc, ctx) => {
|
|
139
|
+
const children = doc?.children ?? []
|
|
140
|
+
const screens = children.filter(isScreen)
|
|
141
|
+
const opening = [
|
|
142
|
+
...ctx.chrome('page-lead', ctx.meta),
|
|
143
|
+
...ctx.renderChildren(children.filter((block) => !isScreen(block))),
|
|
144
|
+
]
|
|
145
|
+
const screen = (nodes) =>
|
|
146
|
+
el('section', { class: SCREEN_CLASS, ...(ctx.canvas?.canvasAttrs ?? {}) }, [
|
|
147
|
+
el('div', { class: SCREEN_INNER_CLASS }, nodes),
|
|
148
|
+
])
|
|
149
|
+
return el(
|
|
150
|
+
'article',
|
|
151
|
+
{ class: PAGE_CLASS, 'data-template': ctx.templateKey },
|
|
152
|
+
[
|
|
153
|
+
...(opening.length > 0 ? [screen(opening)] : []),
|
|
154
|
+
...screens.map((block, index) =>
|
|
155
|
+
screen([ctx.renderBlock(block, { index, total: screens.length })]),
|
|
156
|
+
),
|
|
157
|
+
...ctx.chrome('page-foot', ctx.meta),
|
|
158
|
+
],
|
|
159
|
+
)
|
|
160
|
+
},
|
|
161
|
+
})
|