contour-deck 0.3.0 → 0.3.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 +1 -1
- package/dist/rehype/headings.d.ts +3 -2
- package/dist/rehype/headings.js +20 -9
- package/dist/rehype/index.js +4 -2
- package/dist/remark/index.js +4 -2
- package/dist/shared/structure.d.ts +3 -0
- package/dist/shared/structure.js +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Outline-first slide decks on real DOM.
|
|
|
13
13
|
A deck is blocks, slides, and pages: it opens on an outline of every block, a block opens into a left-right track of slides, and each slide scrolls down one page at a time.
|
|
14
14
|
The core is plain TypeScript with no runtime dependency (7 KB of JavaScript and 3 KB of CSS, gzipped); the phone remote is a separate module that loads on first use.
|
|
15
15
|
|
|
16
|
-
Status: 0.3.
|
|
16
|
+
Status: 0.3.1 on npm. It holds the deck (M1), the M2 client (phone remote, laser pointer, drawing, tilt pointing, and a presenter window, see [Remote](#remote); the relay it talks to is a separate small service, [docs/PROTOCOL.md](docs/PROTOCOL.md)), and, since 0.3.0, an opt-in structure for the Markdown plugins that reads blocks, slides, and pages from headings ([docs/PLUGINS.md](docs/PLUGINS.md#headings-mode)); 0.3.1 lets HTML comments stand anywhere in it and titles a heading-less block by its first slide. Live demo: https://wayne930242.github.io/contour-deck/
|
|
17
17
|
The design is in [docs/DESIGN.md](docs/DESIGN.md) (Traditional Chinese); the HTML the core reads is in [docs/CONTRACT.md](docs/CONTRACT.md).
|
|
18
18
|
|
|
19
19
|
## 看看它怎麼動
|
|
@@ -3,7 +3,8 @@ import { type Group } from "./draft.js";
|
|
|
3
3
|
import { type HastNode } from "./hast.js";
|
|
4
4
|
/**
|
|
5
5
|
* Reads the blocks out of the flow. A block with a title of its own (`::block` title, else its
|
|
6
|
-
* heading) derives its label and the 附錄 kind from it unless `::block` gives them
|
|
7
|
-
* without one takes
|
|
6
|
+
* heading) derives its label and the 附錄 kind from it unless `::block` gives them. A block
|
|
7
|
+
* without one derives nothing: in a deck of several blocks it takes its first slide's title, and
|
|
8
|
+
* the only block takes the deck's title, else the deck's first `h1`, else its first slide's title.
|
|
8
9
|
*/
|
|
9
10
|
export declare function readHeadings(flow: HastNode[], deckTitle: string | undefined, file: DeckFile): Group[];
|
package/dist/rehype/headings.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* - A top-level `---` starts a block.
|
|
5
5
|
* - A top-level `##` starts a slide. What stands before a block's first `##` is the block's first
|
|
6
|
-
* slide (a title, a `# 拆解` divider), unless it is nothing but a `####` kicker, notes,
|
|
7
|
-
*
|
|
6
|
+
* slide (a title, a `# 拆解` divider), unless it is nothing but a `####` kicker, notes,
|
|
7
|
+
* `::block`/`::slide`, and HTML comments: those open the first `##` slide.
|
|
8
8
|
* - A top-level `###` starts a further page of the slide, unless it stands right under the `##`
|
|
9
|
-
* with no content between them
|
|
9
|
+
* with no content between them (notes, `::block`/`::slide`, and HTML comments are not content):
|
|
10
|
+
* then both head the first page.
|
|
10
11
|
* - A block is titled by the first `#` or `####` before its first `##` or `###`; the heading stays
|
|
11
12
|
* where it is. `::block{…}` anywhere at the block's top level overrides what it names.
|
|
12
13
|
* - Headings inside other elements, `####` and deeper, and a `#` after the first `##` are content.
|
|
@@ -15,7 +16,11 @@ import { MARK_BLOCK, MARK_SLIDE, MARK_VERTICAL, NOTES } from "../shared/markers.
|
|
|
15
16
|
import { NO_VERTICAL } from "../shared/structure.js";
|
|
16
17
|
import { blockMarkOf, isRule, leadTitle, ORIGIN, splitAt } from "./draft.js";
|
|
17
18
|
import { isBlank, isElement, markOf, plainText } from "./hast.js";
|
|
18
|
-
import { kindOf, shortOf } from "./titles.js";
|
|
19
|
+
import { kindOf, shortOf, slideTitle } from "./titles.js";
|
|
20
|
+
/** An HTML comment: a `comment` node (after rehype-raw), or a `raw` node of nothing but comments. */
|
|
21
|
+
const isComment = (node) => node.type === "comment" || (node.type === "raw" && /^\s*(<!--(?:(?!-->)[\s\S])*-->\s*)+$/.test(node.value ?? ""));
|
|
22
|
+
/** A slide's pages without their speaker notes, which never reach the audience. */
|
|
23
|
+
const visibleOf = (slide) => slide.pages.map(page => page.filter(node => !(isElement(node) && NOTES in node.properties)));
|
|
19
24
|
function readBlock(nodes, file) {
|
|
20
25
|
let slide = { pages: [[]] };
|
|
21
26
|
const slides = [slide];
|
|
@@ -32,7 +37,9 @@ function readBlock(nodes, file) {
|
|
|
32
37
|
let trailing;
|
|
33
38
|
for (const node of nodes) {
|
|
34
39
|
if (!isElement(node)) {
|
|
35
|
-
if (
|
|
40
|
+
if (isComment(node))
|
|
41
|
+
slide.pages[slide.pages.length - 1].push(node);
|
|
42
|
+
else if (!isBlank(node)) {
|
|
36
43
|
slide.pages[slide.pages.length - 1].push(node);
|
|
37
44
|
bare = trailing = undefined;
|
|
38
45
|
body = true;
|
|
@@ -94,7 +101,7 @@ function readBlock(nodes, file) {
|
|
|
94
101
|
trailing = undefined;
|
|
95
102
|
}
|
|
96
103
|
// Only the opening can be left empty: every other slide starts with its `##`.
|
|
97
|
-
if (!slides[0].pages[0].
|
|
104
|
+
if (!slides[0].pages[0].some(node => !isComment(node))) {
|
|
98
105
|
const marker = idMarkers.get(slides[0]);
|
|
99
106
|
if (marker)
|
|
100
107
|
file.fail("::slide without any content in its slide", marker, ORIGIN);
|
|
@@ -109,8 +116,9 @@ function readBlock(nodes, file) {
|
|
|
109
116
|
}
|
|
110
117
|
/**
|
|
111
118
|
* Reads the blocks out of the flow. A block with a title of its own (`::block` title, else its
|
|
112
|
-
* heading) derives its label and the 附錄 kind from it unless `::block` gives them
|
|
113
|
-
* without one takes
|
|
119
|
+
* heading) derives its label and the 附錄 kind from it unless `::block` gives them. A block
|
|
120
|
+
* without one derives nothing: in a deck of several blocks it takes its first slide's title, and
|
|
121
|
+
* the only block takes the deck's title, else the deck's first `h1`, else its first slide's title.
|
|
114
122
|
*/
|
|
115
123
|
export function readHeadings(flow, deckTitle, file) {
|
|
116
124
|
const blocks = splitAt(flow, isRule, () => true)
|
|
@@ -121,7 +129,10 @@ export function readHeadings(flow, deckTitle, file) {
|
|
|
121
129
|
const own = mark.title ?? heading;
|
|
122
130
|
const spec = own
|
|
123
131
|
? { ...mark, title: own, short: mark.short ?? shortOf(own), kind: mark.kind ?? kindOf(own) }
|
|
124
|
-
: {
|
|
132
|
+
: {
|
|
133
|
+
...mark,
|
|
134
|
+
title: (blocks.length > 1 && slideTitle(visibleOf(slides[0]), "")) || leadTitle(deckTitle, drafts, slides[0]),
|
|
135
|
+
};
|
|
125
136
|
return { spec, slides };
|
|
126
137
|
});
|
|
127
138
|
}
|
package/dist/rehype/index.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* Run it after `contour-deck/remark`, which writes the markers this plugin reads.
|
|
18
18
|
*/
|
|
19
19
|
import { MARK_BLOCK, MARK_SLIDE, MARK_VERTICAL } from "../shared/markers.js";
|
|
20
|
-
import { structureOf } from "../shared/structure.js";
|
|
20
|
+
import { checkStructure, structureOf } from "../shared/structure.js";
|
|
21
21
|
import { blockMarkOf, hasContent, isRule, leadTitle, ORIGIN, splitAt, } from "./draft.js";
|
|
22
22
|
import { element, isElement, markOf, plainText, walkElements } from "./hast.js";
|
|
23
23
|
import { readHeadings } from "./headings.js";
|
|
@@ -200,7 +200,9 @@ export function rehypeDeck(options = {}) {
|
|
|
200
200
|
else
|
|
201
201
|
flow.push(node);
|
|
202
202
|
}
|
|
203
|
-
const
|
|
203
|
+
const structure = structureOf(options.structure, file, ORIGIN);
|
|
204
|
+
checkStructure(file, structure, ORIGIN);
|
|
205
|
+
const groups = structure === "headings"
|
|
204
206
|
? readHeadings(flow, deckTitle, file)
|
|
205
207
|
: groupBlocks(readSlides(flow, file), deckTitle);
|
|
206
208
|
rejectNestedMarkers(groups.flatMap(group => group.slides), file);
|
package/dist/remark/index.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* and `::block` needs no `title`: the block has its heading.
|
|
18
18
|
*/
|
|
19
19
|
import { MARK, MARK_BLOCK, MARK_BUILD, MARK_SLIDE, MARK_VERTICAL, NOTES } from "../shared/markers.js";
|
|
20
|
-
import { NO_VERTICAL, structureOf } from "../shared/structure.js";
|
|
20
|
+
import { NO_VERTICAL, recordStructure, structureOf } from "../shared/structure.js";
|
|
21
21
|
const ORIGIN = "contour-deck:remark";
|
|
22
22
|
const DIRECTIVES = {
|
|
23
23
|
summary: {
|
|
@@ -127,6 +127,8 @@ export function remarkDeck(options = {}) {
|
|
|
127
127
|
return (tree, file) => {
|
|
128
128
|
if (options.match && !options.match(file))
|
|
129
129
|
return;
|
|
130
|
-
|
|
130
|
+
const structure = structureOf(options.structure, file, ORIGIN);
|
|
131
|
+
recordStructure(file, structure);
|
|
132
|
+
visit(tree, file, structure);
|
|
131
133
|
};
|
|
132
134
|
}
|
|
@@ -21,3 +21,6 @@ export declare const NO_VERTICAL = "::vertical has no meaning with structure: \"
|
|
|
21
21
|
* names is an error here, not a silent fallback to the default.
|
|
22
22
|
*/
|
|
23
23
|
export declare function structureOf(option: StructureOption | undefined, file: DeckFile, origin: string): Structure;
|
|
24
|
+
export declare function recordStructure(file: DeckFile, structure: Structure): void;
|
|
25
|
+
/** Fails when `remarkDeck` read the file by another structure than `rehypeDeck` is about to. */
|
|
26
|
+
export declare function checkStructure(file: DeckFile, structure: Structure, origin: string): void;
|
package/dist/shared/structure.js
CHANGED
|
@@ -20,3 +20,18 @@ export function structureOf(option, file, origin) {
|
|
|
20
20
|
return value;
|
|
21
21
|
return file.fail(`structure must be "slides" or "headings", got ${JSON.stringify(value)}`, undefined, origin);
|
|
22
22
|
}
|
|
23
|
+
/** Where `remarkDeck` leaves the structure it read a file by, for `rehypeDeck` to compare. */
|
|
24
|
+
const RECORD = "contourDeckStructure";
|
|
25
|
+
export function recordStructure(file, structure) {
|
|
26
|
+
file.data[RECORD] = structure;
|
|
27
|
+
}
|
|
28
|
+
/** Fails when `remarkDeck` read the file by another structure than `rehypeDeck` is about to. */
|
|
29
|
+
export function checkStructure(file, structure, origin) {
|
|
30
|
+
const data = file.data;
|
|
31
|
+
const recorded = data[RECORD];
|
|
32
|
+
// Read once: a later pass on the same file without remarkDeck must not see a stale record.
|
|
33
|
+
delete data[RECORD];
|
|
34
|
+
if (recorded !== undefined && recorded !== structure) {
|
|
35
|
+
file.fail(`remarkDeck ran with structure "${String(recorded)}" and rehypeDeck with "${structure}": give both the same structure option`, undefined, origin);
|
|
36
|
+
}
|
|
37
|
+
}
|