@gjermundgaraba-designs/simple-rat 0.7.0 → 0.8.0
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/DESIGN.md +5 -5
- package/dist/highlight/highlight.d.ts.map +1 -1
- package/dist/highlight/highlight.js +66 -115
- package/dist/highlight/vite.d.ts +2 -1
- package/dist/highlight/vite.d.ts.map +1 -1
- package/dist/highlight/vite.js +5 -6
- package/dist/src/code/code.d.ts +4 -4
- package/dist/src/code/code.d.ts.map +1 -1
- package/dist/src/code/code.js +10 -7
- package/dist/src/code/index.d.ts.map +1 -1
- package/dist/src/{codeBlock/codeBlock.d.ts → code-block/code-block.d.ts} +2 -3
- package/dist/src/code-block/code-block.d.ts.map +1 -0
- package/dist/src/{codeBlock/codeBlock.js → code-block/code-block.js} +13 -7
- package/dist/src/code-block/index.d.ts +3 -0
- package/dist/src/code-block/index.d.ts.map +1 -0
- package/dist/src/code-block/index.js +1 -0
- package/dist/src/copy/copy.d.ts.map +1 -1
- package/dist/src/copy/copy.js +1 -7
- package/dist/src/{copyTab/copyTab.d.ts → copy-tab/copy-tab.d.ts} +3 -2
- package/dist/src/copy-tab/copy-tab.d.ts.map +1 -0
- package/dist/src/{copyTab/copyTab.js → copy-tab/copy-tab.js} +13 -13
- package/dist/src/copy-tab/index.d.ts +3 -0
- package/dist/src/copy-tab/index.d.ts.map +1 -0
- package/dist/src/{copyTab → copy-tab}/index.js +1 -1
- package/dist/src/diagram/diagram.d.ts.map +1 -1
- package/dist/src/diagram/index.d.ts.map +1 -1
- package/dist/src/footer/footer.d.ts.map +1 -1
- package/dist/src/footer/index.d.ts.map +1 -1
- package/dist/src/footnotes/footnotes.d.ts.map +1 -1
- package/dist/src/footnotes/footnotes.js +7 -2
- package/dist/src/footnotes/index.d.ts.map +1 -1
- package/dist/src/header/header.d.ts +1 -1
- package/dist/src/header/header.d.ts.map +1 -1
- package/dist/src/header/index.d.ts.map +1 -1
- package/dist/src/heading/heading.d.ts.map +1 -1
- package/dist/src/heading/index.d.ts.map +1 -1
- package/dist/src/index.d.ts +6 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -8
- package/dist/src/nav-link/index.d.ts +3 -0
- package/dist/src/nav-link/index.d.ts.map +1 -0
- package/dist/src/nav-link/index.js +1 -0
- package/dist/src/{navLink/navLink.d.ts → nav-link/nav-link.d.ts} +1 -1
- package/dist/src/nav-link/nav-link.d.ts.map +1 -0
- package/dist/src/{navLink/navLink.js → nav-link/nav-link.js} +1 -1
- package/dist/src/note/index.d.ts.map +1 -1
- package/dist/src/note/note.d.ts.map +1 -1
- package/dist/src/pager/index.d.ts.map +1 -1
- package/dist/src/pager/pager.d.ts.map +1 -1
- package/dist/src/post-list/index.d.ts +3 -0
- package/dist/src/post-list/index.d.ts.map +1 -0
- package/dist/src/post-list/index.js +1 -0
- package/dist/src/{postList/postList.d.ts → post-list/post-list.d.ts} +3 -2
- package/dist/src/post-list/post-list.d.ts.map +1 -0
- package/dist/src/post-list/post-list.js +6 -0
- package/dist/src/post-meta/index.d.ts +3 -0
- package/dist/src/post-meta/index.d.ts.map +1 -0
- package/dist/src/post-meta/index.js +1 -0
- package/dist/src/{postMeta/postMeta.d.ts → post-meta/post-meta.d.ts} +1 -1
- package/dist/src/post-meta/post-meta.d.ts.map +1 -0
- package/dist/src/{postMeta/postMeta.js → post-meta/post-meta.js} +3 -3
- package/dist/src/scheme/index.d.ts +1 -1
- package/dist/src/scheme/index.d.ts.map +1 -1
- package/dist/src/scheme/index.js +1 -1
- package/dist/src/scheme/{schemeSwitch.d.ts → scheme-switch.d.ts} +3 -2
- package/dist/src/scheme/scheme-switch.d.ts.map +1 -0
- package/dist/src/scheme/{schemeSwitch.js → scheme-switch.js} +7 -5
- package/dist/src/scheme/scheme.d.ts +0 -2
- package/dist/src/scheme/scheme.d.ts.map +1 -1
- package/dist/src/scheme/scheme.js +0 -2
- package/dist/src/scheme/switch.d.ts.map +1 -1
- package/dist/src/scheme/switch.js +12 -17
- package/dist/src/scrolls/scrolls.d.ts.map +1 -1
- package/dist/src/scrolls/scrolls.js +7 -9
- package/dist/src/skip-link/index.d.ts +2 -0
- package/dist/src/skip-link/index.d.ts.map +1 -0
- package/dist/src/skip-link/index.js +1 -0
- package/dist/src/{skipLink/skipLink.d.ts → skip-link/skip-link.d.ts} +1 -1
- package/dist/src/skip-link/skip-link.d.ts.map +1 -0
- package/dist/src/{skipLink/skipLink.js → skip-link/skip-link.js} +1 -1
- package/dist/src/table/index.d.ts.map +1 -1
- package/dist/src/table/table.d.ts +1 -1
- package/dist/src/table/table.d.ts.map +1 -1
- package/dist/src/table/table.js +3 -1
- package/highlight/highlight.ts +88 -118
- package/highlight/vite.ts +20 -10
- package/package.json +15 -16
- package/src/code/code.ts +18 -11
- package/src/code/index.ts +1 -0
- package/src/code/lines.d.ts +0 -5
- package/src/{codeBlock/codeBlock.ts → code-block/code-block.ts} +32 -14
- package/src/code-block/index.ts +3 -0
- package/src/copy/copy.ts +8 -17
- package/src/{copyTab/copyTab.ts → copy-tab/copy-tab.ts} +24 -8
- package/src/copy-tab/index.ts +12 -0
- package/src/diagram/diagram.ts +8 -4
- package/src/diagram/index.ts +1 -0
- package/src/footer/footer.ts +4 -2
- package/src/footer/index.ts +1 -0
- package/src/footnotes/footnotes.ts +26 -8
- package/src/footnotes/index.ts +1 -0
- package/src/header/header.ts +8 -5
- package/src/header/index.ts +1 -0
- package/src/heading/heading.ts +10 -2
- package/src/heading/index.ts +1 -0
- package/src/index.ts +23 -8
- package/src/nav-link/index.ts +3 -0
- package/src/{navLink/navLink.ts → nav-link/nav-link.ts} +14 -4
- package/src/note/index.ts +1 -0
- package/src/note/note.ts +6 -2
- package/src/pager/index.ts +1 -0
- package/src/pager/pager.ts +6 -2
- package/src/post-list/index.ts +3 -0
- package/src/post-list/post-list.ts +30 -0
- package/src/post-meta/index.ts +3 -0
- package/src/{postMeta/postMeta.ts → post-meta/post-meta.ts} +5 -2
- package/src/scheme/index.ts +3 -1
- package/src/scheme/{schemeSwitch.ts → scheme-switch.ts} +20 -8
- package/src/scheme/scheme.ts +3 -4
- package/src/scheme/switch.ts +9 -20
- package/src/scrolls/scrolls.ts +19 -13
- package/src/simple-rat.css +0 -47
- package/src/skip-link/index.ts +1 -0
- package/src/table/index.ts +1 -0
- package/src/table/table.ts +18 -9
- package/src/tokens.css +23 -32
- package/dist/src/codeBlock/codeBlock.d.ts.map +0 -1
- package/dist/src/codeBlock/index.d.ts +0 -3
- package/dist/src/codeBlock/index.d.ts.map +0 -1
- package/dist/src/codeBlock/index.js +0 -1
- package/dist/src/copyTab/copyTab.d.ts.map +0 -1
- package/dist/src/copyTab/index.d.ts +0 -3
- package/dist/src/copyTab/index.d.ts.map +0 -1
- package/dist/src/navLink/index.d.ts +0 -3
- package/dist/src/navLink/index.d.ts.map +0 -1
- package/dist/src/navLink/index.js +0 -1
- package/dist/src/navLink/navLink.d.ts.map +0 -1
- package/dist/src/postList/index.d.ts +0 -3
- package/dist/src/postList/index.d.ts.map +0 -1
- package/dist/src/postList/index.js +0 -1
- package/dist/src/postList/postList.d.ts.map +0 -1
- package/dist/src/postList/postList.js +0 -3
- package/dist/src/postMeta/index.d.ts +0 -3
- package/dist/src/postMeta/index.d.ts.map +0 -1
- package/dist/src/postMeta/index.js +0 -1
- package/dist/src/postMeta/postMeta.d.ts.map +0 -1
- package/dist/src/scheme/schemeSwitch.d.ts.map +0 -1
- package/dist/src/skipLink/index.d.ts +0 -2
- package/dist/src/skipLink/index.d.ts.map +0 -1
- package/dist/src/skipLink/index.js +0 -1
- package/dist/src/skipLink/skipLink.d.ts.map +0 -1
- package/src/codeBlock/index.ts +0 -2
- package/src/copyTab/index.ts +0 -2
- package/src/navLink/index.ts +0 -2
- package/src/postList/index.ts +0 -2
- package/src/postList/postList.ts +0 -19
- package/src/postMeta/index.ts +0 -2
- package/src/skipLink/index.ts +0 -1
- /package/src/{skipLink/skipLink.ts → skip-link/skip-link.ts} +0 -0
package/highlight/highlight.ts
CHANGED
|
@@ -1,128 +1,98 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// Mocha in dark), so the colours switch with the page. It sits outside src/, which never
|
|
4
|
-
// imports it: a page is given the lines `highlight` made (vite.ts makes them of a file).
|
|
5
|
-
import { createHighlighterCoreSync, type ThemeRegistration } from "shiki/core";
|
|
6
|
-
import { createJavaScriptRawEngine } from "shiki/engine/javascript";
|
|
7
|
-
import css from "@shikijs/langs-precompiled/css";
|
|
8
|
-
import diff from "@shikijs/langs-precompiled/diff";
|
|
9
|
-
import html from "@shikijs/langs-precompiled/html";
|
|
10
|
-
import json from "@shikijs/langs-precompiled/json";
|
|
11
|
-
import shellscript from "@shikijs/langs-precompiled/shellscript";
|
|
12
|
-
import tsx from "@shikijs/langs-precompiled/tsx";
|
|
1
|
+
import { codeToTokens } from "@pierre/highlights";
|
|
2
|
+
import type { Theme, ThemeSyntaxSettings } from "@pierre/highlights";
|
|
13
3
|
|
|
14
4
|
import type { Language, Lines } from "../src/code/code.ts";
|
|
15
5
|
|
|
16
|
-
|
|
6
|
+
type Syntax = Record<string, string | ThemeSyntaxSettings>;
|
|
17
7
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
scope: [
|
|
50
|
-
"keyword",
|
|
51
|
-
"storage.type",
|
|
52
|
-
"storage.modifier",
|
|
53
|
-
"keyword.control.at-rule",
|
|
54
|
-
"support.function.misc.css",
|
|
55
|
-
"support.function.transform.css",
|
|
56
|
-
"meta.diff.range",
|
|
57
|
-
],
|
|
58
|
-
settings: { foreground: t("kw") },
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
scope: [
|
|
62
|
-
"keyword.operator",
|
|
63
|
-
"punctuation.definition.tag",
|
|
64
|
-
"storage.type.function.arrow",
|
|
65
|
-
"punctuation.separator.pipe",
|
|
66
|
-
],
|
|
67
|
-
settings: { foreground: t("op") },
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
scope: [
|
|
71
|
-
"entity.name.type",
|
|
72
|
-
"support.type.primitive",
|
|
73
|
-
"support.class",
|
|
74
|
-
"entity.other.inherited-class",
|
|
75
|
-
"entity.other.attribute-name",
|
|
76
|
-
"entity.name.tag.css",
|
|
77
|
-
"support.type.builtin",
|
|
78
|
-
],
|
|
79
|
-
settings: { foreground: t("ty") },
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
scope: [
|
|
83
|
-
"entity.name.function",
|
|
84
|
-
"support.function",
|
|
85
|
-
"entity.name.tag",
|
|
86
|
-
"support.class.component",
|
|
87
|
-
"support.type.property-name",
|
|
88
|
-
"variable.css",
|
|
89
|
-
],
|
|
90
|
-
settings: { foreground: t("fn") },
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
scope: ["entity.name.command", "support.function.builtin.shell"],
|
|
94
|
-
settings: { foreground: t("fn"), fontStyle: "italic" },
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
scope: [
|
|
98
|
-
"variable.other.property",
|
|
99
|
-
"variable.other.object.property",
|
|
100
|
-
"meta.object-literal.key",
|
|
101
|
-
"variable.argument.css",
|
|
102
|
-
],
|
|
103
|
-
settings: { foreground: t("prop") },
|
|
104
|
-
},
|
|
105
|
-
{ scope: "markup.deleted", settings: { foreground: t("del") } },
|
|
106
|
-
],
|
|
8
|
+
const ITALIC_FONT_STYLE_BIT = 1;
|
|
9
|
+
|
|
10
|
+
const SYNTAX = {
|
|
11
|
+
comment: { color: "com", font_style: "italic" },
|
|
12
|
+
string: "str",
|
|
13
|
+
"diff.plus": "str",
|
|
14
|
+
number: "num",
|
|
15
|
+
boolean: "num",
|
|
16
|
+
constant: "num",
|
|
17
|
+
"variable.special": "num",
|
|
18
|
+
keyword: "kw",
|
|
19
|
+
"keyword.operator": "op",
|
|
20
|
+
operator: "op",
|
|
21
|
+
"punctuation.bracket.html": "op",
|
|
22
|
+
"punctuation.bracket.jsx": "op",
|
|
23
|
+
type: "ty",
|
|
24
|
+
attribute: "ty",
|
|
25
|
+
selector: "ty",
|
|
26
|
+
function: "fn",
|
|
27
|
+
tag: "fn",
|
|
28
|
+
"property.json_key": "fn",
|
|
29
|
+
property: "prop",
|
|
30
|
+
"diff.minus": "del",
|
|
31
|
+
} satisfies Syntax;
|
|
32
|
+
|
|
33
|
+
const OVERRIDES: Partial<Record<Language, Syntax>> = {
|
|
34
|
+
css: { property: "fn", variable: "prop", function: "kw", tag: "ty" },
|
|
35
|
+
sh: { function: { color: "fn", font_style: "italic" } },
|
|
36
|
+
diff: { attribute: "kw" },
|
|
107
37
|
};
|
|
108
38
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
langAlias: { ts: "tsx" },
|
|
115
|
-
engine: createJavaScriptRawEngine(),
|
|
39
|
+
const themeOf = (syntax: Syntax): Theme => ({
|
|
40
|
+
name: "simple-rat",
|
|
41
|
+
appearance: "light",
|
|
42
|
+
style: { syntax },
|
|
43
|
+
cssVariables: { prefix: "--t-" },
|
|
116
44
|
});
|
|
117
45
|
|
|
46
|
+
const BASE = themeOf(SYNTAX);
|
|
47
|
+
|
|
48
|
+
const THEMES: Record<Language, Theme> = {
|
|
49
|
+
ts: BASE,
|
|
50
|
+
tsx: BASE,
|
|
51
|
+
json: BASE,
|
|
52
|
+
html: BASE,
|
|
53
|
+
css: themeOf({ ...SYNTAX, ...OVERRIDES.css }),
|
|
54
|
+
sh: themeOf({ ...SYNTAX, ...OVERRIDES.sh }),
|
|
55
|
+
diff: themeOf({ ...SYNTAX, ...OVERRIDES.diff }),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
interface Run {
|
|
59
|
+
content: string;
|
|
60
|
+
color: string | undefined;
|
|
61
|
+
isItalic: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
118
64
|
/** A block's runs, line by line, for `CodeBlock`'s `lines`. A run in the plain code ink,
|
|
119
65
|
upright, has no colour. */
|
|
120
|
-
export const highlight = (code: string, lang: Language): Lines =>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
66
|
+
export const highlight = (code: string, lang: Language): Lines => {
|
|
67
|
+
const { tokens, fg } = codeToTokens(code, { lang, theme: THEMES[lang] });
|
|
68
|
+
|
|
69
|
+
return tokens.map((line) => {
|
|
70
|
+
const runs: Run[] = [];
|
|
71
|
+
|
|
72
|
+
for (const token of line) {
|
|
73
|
+
const isBlank = token.content.trim() === "";
|
|
74
|
+
|
|
75
|
+
const isItalic =
|
|
76
|
+
!isBlank &&
|
|
77
|
+
((token.fontStyle ?? 0) & ITALIC_FONT_STYLE_BIT) ===
|
|
78
|
+
ITALIC_FONT_STYLE_BIT;
|
|
79
|
+
|
|
80
|
+
const color =
|
|
81
|
+
isBlank || (token.color === fg && !isItalic) ? undefined : token.color;
|
|
82
|
+
|
|
83
|
+
const last = runs.at(-1);
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
last !== undefined &&
|
|
87
|
+
last.color === color &&
|
|
88
|
+
last.isItalic === isItalic
|
|
89
|
+
) {
|
|
90
|
+
last.content += token.content;
|
|
91
|
+
} else {
|
|
92
|
+
runs.push({ content: token.content, color, isItalic });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return runs;
|
|
97
|
+
});
|
|
98
|
+
};
|
package/highlight/vite.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import type { foldkitViewIdentity } from "@foldkit/vite-plugin";
|
|
4
|
+
|
|
5
|
+
import { LANGUAGES } from "../src/code/code.ts";
|
|
6
|
+
import type { Language } from "../src/code/code.ts";
|
|
4
7
|
import { highlight } from "./highlight.ts";
|
|
5
8
|
|
|
6
|
-
type Plugin = ReturnType<typeof
|
|
9
|
+
type Plugin = ReturnType<typeof foldkitViewIdentity>;
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
const LINES_IMPORT = /^([^?]+)\?(?:.*&)?lines=([^&]*)/;
|
|
11
|
+
const LINES_IMPORT = /^(?<file>[^?]+)\?(?:.*&)?lines=(?<lang>[^&]*)/u;
|
|
10
12
|
|
|
11
|
-
const isLanguage = (lang: string): lang is Language =>
|
|
13
|
+
const isLanguage = (lang: string): lang is Language =>
|
|
14
|
+
LANGUAGES.some((known) => known === lang);
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
17
|
* Colours code while the site is built, so no page loads the highlighter. An import with the
|
|
@@ -27,16 +30,23 @@ const isLanguage = (lang: string): lang is Language => LANGUAGES.some((known) =>
|
|
|
27
30
|
*/
|
|
28
31
|
export const codeLines = (): Plugin => ({
|
|
29
32
|
name: "simple-rat:code-lines",
|
|
30
|
-
// Before Vite's own loaders, which would take a .txt for an asset.
|
|
31
33
|
enforce: "pre",
|
|
32
34
|
async load(id) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
+
const { file, lang } = LINES_IMPORT.exec(id)?.groups ?? {};
|
|
36
|
+
|
|
37
|
+
if (file === undefined || lang === undefined) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
if (!isLanguage(lang)) {
|
|
36
|
-
return this.error(
|
|
42
|
+
return this.error(
|
|
43
|
+
`simple-rat: ?lines=${lang} is none of ${LANGUAGES.join(", ")}`
|
|
44
|
+
);
|
|
37
45
|
}
|
|
46
|
+
|
|
38
47
|
this.addWatchFile(file);
|
|
39
|
-
const code = await readFile(file, "
|
|
48
|
+
const code = await readFile(file, "utf-8");
|
|
49
|
+
|
|
40
50
|
return `export default ${JSON.stringify({ code, lines: highlight(code, lang) })};`;
|
|
41
51
|
},
|
|
42
52
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjermundgaraba-designs/simple-rat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,28 +43,27 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@
|
|
47
|
-
"shiki": "^4.4.3"
|
|
46
|
+
"@pierre/highlights": "^0.2.0"
|
|
48
47
|
},
|
|
49
48
|
"devDependencies": {
|
|
50
|
-
"@effect/platform-browser": "4.0.
|
|
51
|
-
"@foldkit/ui": "0.
|
|
52
|
-
"@foldkit/vite-plugin": "0.
|
|
49
|
+
"@effect/platform-browser": "4.0.6",
|
|
50
|
+
"@foldkit/ui": "0.169.0",
|
|
51
|
+
"@foldkit/vite-plugin": "0.28.0",
|
|
53
52
|
"@my-designs/specimen": "0.0.0",
|
|
54
|
-
"@types/node": "
|
|
55
|
-
"effect": "4.0.
|
|
56
|
-
"foldkit": "0.
|
|
57
|
-
"happy-dom": "
|
|
58
|
-
"typescript": "
|
|
59
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@1.
|
|
60
|
-
"vite-plus": "1.
|
|
53
|
+
"@types/node": "26.6.5",
|
|
54
|
+
"effect": "4.0.6",
|
|
55
|
+
"foldkit": "0.169.0",
|
|
56
|
+
"happy-dom": "21.0.4",
|
|
57
|
+
"typescript": "7.0.2",
|
|
58
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.4.0",
|
|
59
|
+
"vite-plus": "1.4.0"
|
|
61
60
|
},
|
|
62
61
|
"peerDependencies": {
|
|
63
62
|
"@effect/platform-browser": "^4.0.0",
|
|
64
|
-
"@foldkit/ui": "^0.
|
|
65
|
-
"@foldkit/vite-plugin": "^0.
|
|
63
|
+
"@foldkit/ui": "^0.169.0",
|
|
64
|
+
"@foldkit/vite-plugin": "^0.28.0",
|
|
66
65
|
"effect": "^4.0.0",
|
|
67
|
-
"foldkit": "^0.
|
|
66
|
+
"foldkit": "^0.169.0"
|
|
68
67
|
},
|
|
69
68
|
"peerDependenciesMeta": {
|
|
70
69
|
"@foldkit/vite-plugin": {
|
package/src/code/code.ts
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
|
-
// The `?lines=<lang>` imports are declared for every program that reaches this file, which
|
|
2
|
-
// is every one that imports the package: an ambient declaration has no import to travel by.
|
|
3
1
|
// oxlint-disable-next-line typescript/triple-slash-reference
|
|
4
|
-
/// <reference path="./lines.d.ts" />
|
|
5
|
-
|
|
6
|
-
// A code block's colours as data, with no highlighter: what `CodeBlock` renders and what a
|
|
7
|
-
// build makes with `highlight` (highlight/highlight.ts), so a page carries the colours and
|
|
8
|
-
// not Shiki.
|
|
2
|
+
/// <reference path="./lines.d.ts" preserve="true" />
|
|
9
3
|
|
|
10
4
|
/** The languages a block can be coloured in. */
|
|
11
|
-
export const LANGUAGES = [
|
|
5
|
+
export const LANGUAGES = [
|
|
6
|
+
"ts",
|
|
7
|
+
"tsx",
|
|
8
|
+
"sh",
|
|
9
|
+
"json",
|
|
10
|
+
"css",
|
|
11
|
+
"html",
|
|
12
|
+
"diff",
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
12
15
|
export type Language = (typeof LANGUAGES)[number];
|
|
13
16
|
|
|
14
|
-
/** One coloured run of a line: its text, the `--t-*` colour
|
|
17
|
+
/** One coloured run of a line: its text, the `--t-*` colour the highlighter gave it, and whether it
|
|
15
18
|
is italic. */
|
|
16
|
-
type Run = Readonly<{
|
|
19
|
+
export type Run = Readonly<{
|
|
20
|
+
content: string;
|
|
21
|
+
color: string | undefined;
|
|
22
|
+
isItalic: boolean;
|
|
23
|
+
}>;
|
|
17
24
|
|
|
18
25
|
/** A block's runs, line by line. A run in the plain code ink, upright, has no colour. */
|
|
19
|
-
export type Lines = ReadonlyArray<
|
|
26
|
+
export type Lines = ReadonlyArray<readonly Run[]>;
|
|
20
27
|
|
|
21
28
|
/** A file's text and its coloured lines: what an import with `?lines=<lang>` gives (see
|
|
22
29
|
lines.d.ts). */
|
package/src/code/index.ts
CHANGED
package/src/code/lines.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// A file imported with `?lines=<lang>`, one of `LANGUAGES`: its text, and its lines coloured
|
|
2
|
-
// at build by `codeLines()` (highlight/vite.ts, the package's `./highlight/vite`). code.ts
|
|
3
|
-
// refers to this file, so every program that imports the package has these declarations. A
|
|
4
|
-
// module pattern takes one wildcard, so there is a declaration for each language.
|
|
5
|
-
|
|
6
1
|
declare module "*?lines=ts" {
|
|
7
2
|
const highlighted: import("./code.ts").Highlighted;
|
|
8
3
|
export default highlighted;
|
|
@@ -3,7 +3,7 @@ import type { Html, HtmlBuilder } from "foldkit/html";
|
|
|
3
3
|
import * as Mount from "foldkit/mount";
|
|
4
4
|
|
|
5
5
|
import type { Language, Lines } from "../code/code.ts";
|
|
6
|
-
import type { ViewInputs as CopyInputs } from "../
|
|
6
|
+
import type { ViewInputs as CopyInputs } from "../copy-tab/copy-tab.ts";
|
|
7
7
|
import * as Scrolls from "../scrolls/scrolls.ts";
|
|
8
8
|
|
|
9
9
|
/** Configuration for rendering a code block with `view`. */
|
|
@@ -26,29 +26,42 @@ export type ViewConfig<ParentMessage> = Readonly<{
|
|
|
26
26
|
toParentMessage: (message: Scrolls.Message) => ParentMessage;
|
|
27
27
|
}>;
|
|
28
28
|
|
|
29
|
-
/** The block's name for assistive technology: its file, or its first line. */
|
|
30
29
|
export const nameOf = (code: string, file?: string): string => {
|
|
31
30
|
const firstLine = code.trim().split("\n")[0] ?? "";
|
|
32
|
-
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
file ?? (firstLine.length > 40 ? `${firstLine.slice(0, 40)}…` : firstLine)
|
|
34
|
+
);
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
/** What a block's `CopyTab` is given: the block's name and its code, without a trailing
|
|
36
38
|
newline. */
|
|
37
|
-
export const copyInputs = (
|
|
39
|
+
export const copyInputs = (
|
|
40
|
+
code: string,
|
|
41
|
+
file?: string
|
|
42
|
+
): Omit<CopyInputs, "toBlockView"> => ({
|
|
38
43
|
name: nameOf(code, file),
|
|
39
44
|
text: code.trimEnd(),
|
|
40
45
|
});
|
|
41
46
|
|
|
42
|
-
const linesView = <Message>(
|
|
47
|
+
const linesView = <Message>(
|
|
48
|
+
lines: Lines,
|
|
49
|
+
h: HtmlBuilder<Message>
|
|
50
|
+
): ReadonlyArray<Html | string> =>
|
|
43
51
|
lines.flatMap((line, index) => [
|
|
44
52
|
...(index > 0 ? ["\n"] : []),
|
|
45
53
|
...line.map((run) =>
|
|
46
54
|
run.color === undefined
|
|
47
55
|
? run.content
|
|
48
56
|
: h.span(
|
|
49
|
-
[
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
[
|
|
58
|
+
h.Style({
|
|
59
|
+
color: run.color,
|
|
60
|
+
...(run.isItalic ? { fontStyle: "italic" } : {}),
|
|
61
|
+
}),
|
|
62
|
+
],
|
|
63
|
+
[run.content]
|
|
64
|
+
)
|
|
52
65
|
),
|
|
53
66
|
]);
|
|
54
67
|
|
|
@@ -56,10 +69,11 @@ const linesView = <Message>(lines: Lines, h: HtmlBuilder<Message>): ReadonlyArra
|
|
|
56
69
|
copy tab. Only a block that overflows takes focus, through `Scrolls.WatchOverflow`. */
|
|
57
70
|
export const view = <ParentMessage>(
|
|
58
71
|
config: ViewConfig<ParentMessage>,
|
|
59
|
-
h: HtmlBuilder<ParentMessage
|
|
72
|
+
h: HtmlBuilder<ParentMessage>
|
|
60
73
|
): Html => {
|
|
61
74
|
const { code, lang, lines, file, copy, toParentMessage } = config;
|
|
62
75
|
const name = nameOf(code, file);
|
|
76
|
+
|
|
63
77
|
const toBlockView = (): Html =>
|
|
64
78
|
h.pre(
|
|
65
79
|
[
|
|
@@ -67,17 +81,21 @@ export const view = <ParentMessage>(
|
|
|
67
81
|
h.OnMount(
|
|
68
82
|
Mount.mapMessage(
|
|
69
83
|
Scrolls.WatchOverflow({ maybeName: Option.some(name) }),
|
|
70
|
-
toParentMessage
|
|
71
|
-
)
|
|
84
|
+
toParentMessage
|
|
85
|
+
)
|
|
72
86
|
),
|
|
73
87
|
],
|
|
74
88
|
[
|
|
75
89
|
h.code(
|
|
76
90
|
lang === undefined ? [] : [h.Class(`language-${lang}`)],
|
|
77
|
-
lines === undefined ? [code] : linesView(lines, h)
|
|
91
|
+
lines === undefined ? [code] : linesView(lines, h)
|
|
78
92
|
),
|
|
79
|
-
]
|
|
93
|
+
]
|
|
80
94
|
);
|
|
95
|
+
|
|
81
96
|
const body = copy === undefined ? toBlockView() : copy(toBlockView);
|
|
82
|
-
|
|
97
|
+
|
|
98
|
+
return file === undefined
|
|
99
|
+
? body
|
|
100
|
+
: h.figure([h.Class("file")], [h.figcaption([], [file]), body]);
|
|
83
101
|
};
|
package/src/copy/copy.ts
CHANGED
|
@@ -6,10 +6,6 @@ import { defineTaggedUnion } from "foldkit/schema";
|
|
|
6
6
|
import { modifyFields } from "foldkit/struct";
|
|
7
7
|
import type * as Update from "foldkit/update";
|
|
8
8
|
|
|
9
|
-
// A copy to the clipboard, without its view: the system's own control renders it.
|
|
10
|
-
|
|
11
|
-
// MODEL
|
|
12
|
-
|
|
13
9
|
/** Where a copy is: nothing yet, writing to the clipboard, just copied, or just refused. */
|
|
14
10
|
export const CopyState = defineTaggedUnion({
|
|
15
11
|
Idle: {},
|
|
@@ -17,6 +13,7 @@ export const CopyState = defineTaggedUnion({
|
|
|
17
13
|
Copied: {},
|
|
18
14
|
Failed: {},
|
|
19
15
|
});
|
|
16
|
+
|
|
20
17
|
export type CopyState = typeof CopyState.Type;
|
|
21
18
|
|
|
22
19
|
/** Schema for a copy control: what it says it just did. */
|
|
@@ -24,9 +21,8 @@ export const Model = Schema.Struct({
|
|
|
24
21
|
id: Schema.String,
|
|
25
22
|
state: CopyState,
|
|
26
23
|
});
|
|
27
|
-
export type Model = typeof Model.Type;
|
|
28
24
|
|
|
29
|
-
|
|
25
|
+
export type Model = typeof Model.Type;
|
|
30
26
|
|
|
31
27
|
/** Union of all Messages a copy control can produce. */
|
|
32
28
|
export const Message = defineMessageUnion({
|
|
@@ -35,9 +31,8 @@ export const Message = defineMessageUnion({
|
|
|
35
31
|
FailedCopyText: {},
|
|
36
32
|
CompletedWaitBeforeSettling: {},
|
|
37
33
|
});
|
|
38
|
-
export type Message = typeof Message.Type;
|
|
39
34
|
|
|
40
|
-
|
|
35
|
+
export type Message = typeof Message.Type;
|
|
41
36
|
|
|
42
37
|
/** Configuration for creating a copy control with `init`: its id, unique in the document. */
|
|
43
38
|
export type InitConfig = Readonly<{ id: string }>;
|
|
@@ -48,14 +43,9 @@ export const init = (config: InitConfig): Model => ({
|
|
|
48
43
|
state: CopyState.Idle(),
|
|
49
44
|
});
|
|
50
45
|
|
|
51
|
-
// ACCESSORS
|
|
52
|
-
|
|
53
46
|
/** The control's id, unique in the document. */
|
|
54
47
|
export const id = (model: Model): string => model.id;
|
|
55
48
|
|
|
56
|
-
// COMMAND
|
|
57
|
-
|
|
58
|
-
/** How long the control answers for: that it copied, or that the copy failed. */
|
|
59
49
|
const WORD_BEAT = Duration.millis(1600);
|
|
60
50
|
|
|
61
51
|
/** Writes the text to the clipboard through Effect's `Clipboard` port. Provide
|
|
@@ -67,18 +57,19 @@ export const CopyText = Command.define("CopyText", {
|
|
|
67
57
|
Effect.gen(function* () {
|
|
68
58
|
const clipboard = yield* Clipboard.Clipboard;
|
|
69
59
|
yield* clipboard.writeString(text);
|
|
60
|
+
|
|
70
61
|
return Message.SucceededCopyText();
|
|
71
|
-
}).pipe(Effect.
|
|
62
|
+
}).pipe(Effect.orElseSucceed(() => Message.FailedCopyText())),
|
|
72
63
|
});
|
|
73
64
|
|
|
74
65
|
/** Waits out the beat the answer shows for. */
|
|
75
66
|
export const WaitBeforeSettling = Command.define("WaitBeforeSettling", {
|
|
76
67
|
messages: [Message.CompletedWaitBeforeSettling],
|
|
77
|
-
execute: Effect.sleep(WORD_BEAT).pipe(
|
|
68
|
+
execute: Effect.sleep(WORD_BEAT).pipe(
|
|
69
|
+
Effect.as(Message.CompletedWaitBeforeSettling())
|
|
70
|
+
),
|
|
78
71
|
});
|
|
79
72
|
|
|
80
|
-
// UPDATE
|
|
81
|
-
|
|
82
73
|
type UpdateReturn = Update.Return<Model, Message, Clipboard.Clipboard>;
|
|
83
74
|
|
|
84
75
|
const holdAnswer = (model: Model, state: CopyState): UpdateReturn => ({
|
|
@@ -2,11 +2,21 @@ import { Button as UiButton } from "@foldkit/ui";
|
|
|
2
2
|
import type { Html } from "foldkit/html";
|
|
3
3
|
import { defineView } from "foldkit/submodel";
|
|
4
4
|
|
|
5
|
-
import { CopyState, Message
|
|
5
|
+
import { CopyState, Message } from "../copy/copy.ts";
|
|
6
|
+
import type { Model } from "../copy/copy.ts";
|
|
6
7
|
|
|
7
|
-
export
|
|
8
|
+
export {
|
|
9
|
+
CopyState,
|
|
10
|
+
CopyText,
|
|
11
|
+
Message,
|
|
12
|
+
Model,
|
|
13
|
+
WaitBeforeSettling,
|
|
14
|
+
id,
|
|
15
|
+
init,
|
|
16
|
+
update,
|
|
17
|
+
} from "../copy/copy.ts";
|
|
8
18
|
|
|
9
|
-
|
|
19
|
+
export type { InitConfig } from "../copy/copy.ts";
|
|
10
20
|
|
|
11
21
|
/** Per-render inputs passed to `view` through `h.submodel`'s `viewInputs`. */
|
|
12
22
|
export type ViewInputs = Readonly<{
|
|
@@ -51,11 +61,17 @@ export const view = defineView<Model, Message, ViewInputs>(
|
|
|
51
61
|
isDisabled: !CopyState.guards.Idle(model.state),
|
|
52
62
|
onClick: Message.ClickedCopy({ text }),
|
|
53
63
|
toView: ({ button }) =>
|
|
54
|
-
h.button(
|
|
64
|
+
h.button(
|
|
65
|
+
[...button, h.AriaLabel(`Copy ${name}`)],
|
|
66
|
+
[word(model.state)]
|
|
67
|
+
),
|
|
55
68
|
},
|
|
56
|
-
h
|
|
69
|
+
h
|
|
57
70
|
),
|
|
58
|
-
h.span(
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
h.span(
|
|
72
|
+
[h.Class("visually-hidden"), h.Role("status")],
|
|
73
|
+
[said(model.state)]
|
|
74
|
+
),
|
|
75
|
+
]
|
|
76
|
+
)
|
|
61
77
|
);
|
package/src/diagram/diagram.ts
CHANGED
|
@@ -21,9 +21,10 @@ export type ViewConfig<ParentMessage> = Readonly<{
|
|
|
21
21
|
(through `Scrolls.WatchOverflow`) and scroll. */
|
|
22
22
|
export const view = <ParentMessage>(
|
|
23
23
|
config: ViewConfig<ParentMessage>,
|
|
24
|
-
h: HtmlBuilder<ParentMessage
|
|
24
|
+
h: HtmlBuilder<ParentMessage>
|
|
25
25
|
): Html => {
|
|
26
26
|
const { drawing, label, caption = [label], toParentMessage } = config;
|
|
27
|
+
|
|
27
28
|
return h.figure(
|
|
28
29
|
[],
|
|
29
30
|
[
|
|
@@ -32,12 +33,15 @@ export const view = <ParentMessage>(
|
|
|
32
33
|
h.Role("img"),
|
|
33
34
|
h.AriaLabel(label),
|
|
34
35
|
h.OnMount(
|
|
35
|
-
Mount.mapMessage(
|
|
36
|
+
Mount.mapMessage(
|
|
37
|
+
Scrolls.WatchOverflow({ maybeName: Option.none() }),
|
|
38
|
+
toParentMessage
|
|
39
|
+
)
|
|
36
40
|
),
|
|
37
41
|
],
|
|
38
|
-
[h.code([], [drawing])]
|
|
42
|
+
[h.code([], [drawing])]
|
|
39
43
|
),
|
|
40
44
|
h.figcaption([], caption),
|
|
41
|
-
]
|
|
45
|
+
]
|
|
42
46
|
);
|
|
43
47
|
};
|
package/src/diagram/index.ts
CHANGED
package/src/footer/footer.ts
CHANGED
|
@@ -7,5 +7,7 @@ export type ViewConfig = Readonly<{
|
|
|
7
7
|
}>;
|
|
8
8
|
|
|
9
9
|
/** A rule and one line. */
|
|
10
|
-
export const view = <Message>(
|
|
11
|
-
|
|
10
|
+
export const view = <Message>(
|
|
11
|
+
config: ViewConfig,
|
|
12
|
+
h: HtmlBuilder<Message>
|
|
13
|
+
): Html => h.footer([], [h.hr([]), h.p([], config.body)]);
|