@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.
Files changed (159) hide show
  1. package/DESIGN.md +5 -5
  2. package/dist/highlight/highlight.d.ts.map +1 -1
  3. package/dist/highlight/highlight.js +66 -115
  4. package/dist/highlight/vite.d.ts +2 -1
  5. package/dist/highlight/vite.d.ts.map +1 -1
  6. package/dist/highlight/vite.js +5 -6
  7. package/dist/src/code/code.d.ts +4 -4
  8. package/dist/src/code/code.d.ts.map +1 -1
  9. package/dist/src/code/code.js +10 -7
  10. package/dist/src/code/index.d.ts.map +1 -1
  11. package/dist/src/{codeBlock/codeBlock.d.ts → code-block/code-block.d.ts} +2 -3
  12. package/dist/src/code-block/code-block.d.ts.map +1 -0
  13. package/dist/src/{codeBlock/codeBlock.js → code-block/code-block.js} +13 -7
  14. package/dist/src/code-block/index.d.ts +3 -0
  15. package/dist/src/code-block/index.d.ts.map +1 -0
  16. package/dist/src/code-block/index.js +1 -0
  17. package/dist/src/copy/copy.d.ts.map +1 -1
  18. package/dist/src/copy/copy.js +1 -7
  19. package/dist/src/{copyTab/copyTab.d.ts → copy-tab/copy-tab.d.ts} +3 -2
  20. package/dist/src/copy-tab/copy-tab.d.ts.map +1 -0
  21. package/dist/src/{copyTab/copyTab.js → copy-tab/copy-tab.js} +13 -13
  22. package/dist/src/copy-tab/index.d.ts +3 -0
  23. package/dist/src/copy-tab/index.d.ts.map +1 -0
  24. package/dist/src/{copyTab → copy-tab}/index.js +1 -1
  25. package/dist/src/diagram/diagram.d.ts.map +1 -1
  26. package/dist/src/diagram/index.d.ts.map +1 -1
  27. package/dist/src/footer/footer.d.ts.map +1 -1
  28. package/dist/src/footer/index.d.ts.map +1 -1
  29. package/dist/src/footnotes/footnotes.d.ts.map +1 -1
  30. package/dist/src/footnotes/footnotes.js +7 -2
  31. package/dist/src/footnotes/index.d.ts.map +1 -1
  32. package/dist/src/header/header.d.ts +1 -1
  33. package/dist/src/header/header.d.ts.map +1 -1
  34. package/dist/src/header/index.d.ts.map +1 -1
  35. package/dist/src/heading/heading.d.ts.map +1 -1
  36. package/dist/src/heading/index.d.ts.map +1 -1
  37. package/dist/src/index.d.ts +6 -6
  38. package/dist/src/index.d.ts.map +1 -1
  39. package/dist/src/index.js +6 -8
  40. package/dist/src/nav-link/index.d.ts +3 -0
  41. package/dist/src/nav-link/index.d.ts.map +1 -0
  42. package/dist/src/nav-link/index.js +1 -0
  43. package/dist/src/{navLink/navLink.d.ts → nav-link/nav-link.d.ts} +1 -1
  44. package/dist/src/nav-link/nav-link.d.ts.map +1 -0
  45. package/dist/src/{navLink/navLink.js → nav-link/nav-link.js} +1 -1
  46. package/dist/src/note/index.d.ts.map +1 -1
  47. package/dist/src/note/note.d.ts.map +1 -1
  48. package/dist/src/pager/index.d.ts.map +1 -1
  49. package/dist/src/pager/pager.d.ts.map +1 -1
  50. package/dist/src/post-list/index.d.ts +3 -0
  51. package/dist/src/post-list/index.d.ts.map +1 -0
  52. package/dist/src/post-list/index.js +1 -0
  53. package/dist/src/{postList/postList.d.ts → post-list/post-list.d.ts} +3 -2
  54. package/dist/src/post-list/post-list.d.ts.map +1 -0
  55. package/dist/src/post-list/post-list.js +6 -0
  56. package/dist/src/post-meta/index.d.ts +3 -0
  57. package/dist/src/post-meta/index.d.ts.map +1 -0
  58. package/dist/src/post-meta/index.js +1 -0
  59. package/dist/src/{postMeta/postMeta.d.ts → post-meta/post-meta.d.ts} +1 -1
  60. package/dist/src/post-meta/post-meta.d.ts.map +1 -0
  61. package/dist/src/{postMeta/postMeta.js → post-meta/post-meta.js} +3 -3
  62. package/dist/src/scheme/index.d.ts +1 -1
  63. package/dist/src/scheme/index.d.ts.map +1 -1
  64. package/dist/src/scheme/index.js +1 -1
  65. package/dist/src/scheme/{schemeSwitch.d.ts → scheme-switch.d.ts} +3 -2
  66. package/dist/src/scheme/scheme-switch.d.ts.map +1 -0
  67. package/dist/src/scheme/{schemeSwitch.js → scheme-switch.js} +7 -5
  68. package/dist/src/scheme/scheme.d.ts +0 -2
  69. package/dist/src/scheme/scheme.d.ts.map +1 -1
  70. package/dist/src/scheme/scheme.js +0 -2
  71. package/dist/src/scheme/switch.d.ts.map +1 -1
  72. package/dist/src/scheme/switch.js +12 -17
  73. package/dist/src/scrolls/scrolls.d.ts.map +1 -1
  74. package/dist/src/scrolls/scrolls.js +7 -9
  75. package/dist/src/skip-link/index.d.ts +2 -0
  76. package/dist/src/skip-link/index.d.ts.map +1 -0
  77. package/dist/src/skip-link/index.js +1 -0
  78. package/dist/src/{skipLink/skipLink.d.ts → skip-link/skip-link.d.ts} +1 -1
  79. package/dist/src/skip-link/skip-link.d.ts.map +1 -0
  80. package/dist/src/{skipLink/skipLink.js → skip-link/skip-link.js} +1 -1
  81. package/dist/src/table/index.d.ts.map +1 -1
  82. package/dist/src/table/table.d.ts +1 -1
  83. package/dist/src/table/table.d.ts.map +1 -1
  84. package/dist/src/table/table.js +3 -1
  85. package/highlight/highlight.ts +88 -118
  86. package/highlight/vite.ts +20 -10
  87. package/package.json +15 -16
  88. package/src/code/code.ts +18 -11
  89. package/src/code/index.ts +1 -0
  90. package/src/code/lines.d.ts +0 -5
  91. package/src/{codeBlock/codeBlock.ts → code-block/code-block.ts} +32 -14
  92. package/src/code-block/index.ts +3 -0
  93. package/src/copy/copy.ts +8 -17
  94. package/src/{copyTab/copyTab.ts → copy-tab/copy-tab.ts} +24 -8
  95. package/src/copy-tab/index.ts +12 -0
  96. package/src/diagram/diagram.ts +8 -4
  97. package/src/diagram/index.ts +1 -0
  98. package/src/footer/footer.ts +4 -2
  99. package/src/footer/index.ts +1 -0
  100. package/src/footnotes/footnotes.ts +26 -8
  101. package/src/footnotes/index.ts +1 -0
  102. package/src/header/header.ts +8 -5
  103. package/src/header/index.ts +1 -0
  104. package/src/heading/heading.ts +10 -2
  105. package/src/heading/index.ts +1 -0
  106. package/src/index.ts +23 -8
  107. package/src/nav-link/index.ts +3 -0
  108. package/src/{navLink/navLink.ts → nav-link/nav-link.ts} +14 -4
  109. package/src/note/index.ts +1 -0
  110. package/src/note/note.ts +6 -2
  111. package/src/pager/index.ts +1 -0
  112. package/src/pager/pager.ts +6 -2
  113. package/src/post-list/index.ts +3 -0
  114. package/src/post-list/post-list.ts +30 -0
  115. package/src/post-meta/index.ts +3 -0
  116. package/src/{postMeta/postMeta.ts → post-meta/post-meta.ts} +5 -2
  117. package/src/scheme/index.ts +3 -1
  118. package/src/scheme/{schemeSwitch.ts → scheme-switch.ts} +20 -8
  119. package/src/scheme/scheme.ts +3 -4
  120. package/src/scheme/switch.ts +9 -20
  121. package/src/scrolls/scrolls.ts +19 -13
  122. package/src/simple-rat.css +0 -47
  123. package/src/skip-link/index.ts +1 -0
  124. package/src/table/index.ts +1 -0
  125. package/src/table/table.ts +18 -9
  126. package/src/tokens.css +23 -32
  127. package/dist/src/codeBlock/codeBlock.d.ts.map +0 -1
  128. package/dist/src/codeBlock/index.d.ts +0 -3
  129. package/dist/src/codeBlock/index.d.ts.map +0 -1
  130. package/dist/src/codeBlock/index.js +0 -1
  131. package/dist/src/copyTab/copyTab.d.ts.map +0 -1
  132. package/dist/src/copyTab/index.d.ts +0 -3
  133. package/dist/src/copyTab/index.d.ts.map +0 -1
  134. package/dist/src/navLink/index.d.ts +0 -3
  135. package/dist/src/navLink/index.d.ts.map +0 -1
  136. package/dist/src/navLink/index.js +0 -1
  137. package/dist/src/navLink/navLink.d.ts.map +0 -1
  138. package/dist/src/postList/index.d.ts +0 -3
  139. package/dist/src/postList/index.d.ts.map +0 -1
  140. package/dist/src/postList/index.js +0 -1
  141. package/dist/src/postList/postList.d.ts.map +0 -1
  142. package/dist/src/postList/postList.js +0 -3
  143. package/dist/src/postMeta/index.d.ts +0 -3
  144. package/dist/src/postMeta/index.d.ts.map +0 -1
  145. package/dist/src/postMeta/index.js +0 -1
  146. package/dist/src/postMeta/postMeta.d.ts.map +0 -1
  147. package/dist/src/scheme/schemeSwitch.d.ts.map +0 -1
  148. package/dist/src/skipLink/index.d.ts +0 -2
  149. package/dist/src/skipLink/index.d.ts.map +0 -1
  150. package/dist/src/skipLink/index.js +0 -1
  151. package/dist/src/skipLink/skipLink.d.ts.map +0 -1
  152. package/src/codeBlock/index.ts +0 -2
  153. package/src/copyTab/index.ts +0 -2
  154. package/src/navLink/index.ts +0 -2
  155. package/src/postList/index.ts +0 -2
  156. package/src/postList/postList.ts +0 -19
  157. package/src/postMeta/index.ts +0 -2
  158. package/src/skipLink/index.ts +0 -1
  159. /package/src/{skipLink/skipLink.ts → skip-link/skip-link.ts} +0 -0
@@ -1,128 +1,98 @@
1
- // Syntax colours for code blocks, by Shiki, for a build to run: the theme maps TextMate
2
- // scopes to the --t-* tokens in tokens.css (Catppuccin: Latte in light, darkened for AA;
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
- const t = (token: string) => `var(--t-${token})`;
6
+ type Syntax = Record<string, string | ThemeSyntaxSettings>;
17
7
 
18
- const codeTheme: ThemeRegistration = {
19
- name: "simple-rat",
20
- type: "light",
21
- colors: { "editor.foreground": "var(--code-ink)", "editor.background": "var(--code-fill)" },
22
- tokenColors: [
23
- {
24
- scope: ["comment", "punctuation.definition.comment", "meta.diff.header"],
25
- settings: { foreground: t("com"), fontStyle: "italic" },
26
- },
27
- {
28
- scope: [
29
- "string",
30
- "punctuation.definition.string",
31
- "string.unquoted.argument",
32
- "markup.inserted",
33
- ],
34
- settings: { foreground: t("str") },
35
- },
36
- {
37
- scope: [
38
- "constant.numeric",
39
- "constant.language",
40
- "constant.character",
41
- "constant.other.color",
42
- "keyword.other.unit",
43
- "support.constant.property-value",
44
- "variable.language.this",
45
- ],
46
- settings: { foreground: t("num") },
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
- // Built once, when a build first imports this file. The grammars come precompiled, so
110
- // nothing translates their patterns. TypeScript is read with the TSX grammar.
111
- const highlighter = createHighlighterCoreSync({
112
- themes: [codeTheme],
113
- langs: [css, diff, html, json, shellscript, tsx],
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
- highlighter.codeToTokens(code, { lang, theme: "simple-rat" }).tokens.map((line) =>
122
- line.map((token) => {
123
- // Italic is bit 1 of Shiki's font style.
124
- const isItalic = ((token.fontStyle ?? 0) & 1) === 1;
125
- const isPlain = token.color === "var(--code-ink)" && !isItalic;
126
- return { content: token.content, color: isPlain ? undefined : token.color, isItalic };
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 { LANGUAGES, type Language } from "../src/code/code.ts";
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 import("@foldkit/vite-plugin").foldkitViewIdentity>;
9
+ type Plugin = ReturnType<typeof foldkitViewIdentity>;
7
10
 
8
- // A file's path, then `lines=<lang>` anywhere in its query.
9
- const LINES_IMPORT = /^([^?]+)\?(?:.*&)?lines=([^&]*)/;
11
+ const LINES_IMPORT = /^(?<file>[^?]+)\?(?:.*&)?lines=(?<lang>[^&]*)/u;
10
12
 
11
- const isLanguage = (lang: string): lang is Language => LANGUAGES.some((known) => known === lang);
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 [, file, lang] = LINES_IMPORT.exec(id) ?? [];
34
- if (file === undefined || lang === undefined) return null;
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(`simple-rat: ?lines=${lang} is none of ${LANGUAGES.join(", ")}`);
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, "utf8");
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.7.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
- "@shikijs/langs-precompiled": "^4.4.3",
47
- "shiki": "^4.4.3"
46
+ "@pierre/highlights": "^0.2.0"
48
47
  },
49
48
  "devDependencies": {
50
- "@effect/platform-browser": "4.0.2",
51
- "@foldkit/ui": "0.168.0",
52
- "@foldkit/vite-plugin": "0.27.1",
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": "^26.6.2",
55
- "effect": "4.0.2",
56
- "foldkit": "0.168.0",
57
- "happy-dom": "^20.14.5",
58
- "typescript": "^7.0.2",
59
- "vite": "npm:@voidzero-dev/vite-plus-core@1.0.0",
60
- "vite-plus": "1.0.0"
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.168.0",
65
- "@foldkit/vite-plugin": "^0.27.0",
63
+ "@foldkit/ui": "^0.169.0",
64
+ "@foldkit/vite-plugin": "^0.28.0",
66
65
  "effect": "^4.0.0",
67
- "foldkit": "^0.168.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 = ["ts", "tsx", "sh", "json", "css", "html", "diff"] as const;
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 Shiki gave it, and whether it
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<{ content: string; color: string | undefined; isItalic: boolean }>;
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<ReadonlyArray<Run>>;
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
@@ -1,2 +1,3 @@
1
1
  export { LANGUAGES } from "./code.ts";
2
+
2
3
  export type { Highlighted, Language, Lines } from "./code.ts";
@@ -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 "../copyTab/copyTab.ts";
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
- return file ?? (firstLine.length > 40 ? `${firstLine.slice(0, 40)}…` : firstLine);
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 = (code: string, file?: string): Omit<CopyInputs, "toBlockView"> => ({
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>(lines: Lines, h: HtmlBuilder<Message>): ReadonlyArray<Html | string> =>
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
- [h.Style({ color: run.color, ...(run.isItalic ? { fontStyle: "italic" } : {}) })],
50
- [run.content],
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
- return file === undefined ? body : h.figure([h.Class("file")], [h.figcaption([], [file]), body]);
97
+
98
+ return file === undefined
99
+ ? body
100
+ : h.figure([h.Class("file")], [h.figcaption([], [file]), body]);
83
101
  };
@@ -0,0 +1,3 @@
1
+ export { copyInputs, view } from "./code-block.ts";
2
+
3
+ export type { ViewConfig } from "./code-block.ts";
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
- // MESSAGE
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
- // INIT
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.catch(() => Effect.succeed(Message.FailedCopyText()))),
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(Effect.as(Message.CompletedWaitBeforeSettling())),
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, type Model } from "../copy/copy.ts";
5
+ import { CopyState, Message } from "../copy/copy.ts";
6
+ import type { Model } from "../copy/copy.ts";
6
7
 
7
- export * from "../copy/copy.ts";
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
- // VIEW
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([...button, h.AriaLabel(`Copy ${name}`)], [word(model.state)]),
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([h.Class("visually-hidden"), h.Role("status")], [said(model.state)]),
59
- ],
60
- ),
71
+ h.span(
72
+ [h.Class("visually-hidden"), h.Role("status")],
73
+ [said(model.state)]
74
+ ),
75
+ ]
76
+ )
61
77
  );
@@ -0,0 +1,12 @@
1
+ export {
2
+ CopyText,
3
+ Message,
4
+ Model,
5
+ WaitBeforeSettling,
6
+ id,
7
+ init,
8
+ update,
9
+ view,
10
+ } from "./copy-tab.ts";
11
+
12
+ export type { InitConfig, ViewInputs } from "./copy-tab.ts";
@@ -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(Scrolls.WatchOverflow({ maybeName: Option.none() }), toParentMessage),
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
  };
@@ -1,2 +1,3 @@
1
1
  export { view } from "./diagram.ts";
2
+
2
3
  export type { ViewConfig } from "./diagram.ts";
@@ -7,5 +7,7 @@ export type ViewConfig = Readonly<{
7
7
  }>;
8
8
 
9
9
  /** A rule and one line. */
10
- export const view = <Message>(config: ViewConfig, h: HtmlBuilder<Message>): Html =>
11
- h.footer([], [h.hr([]), h.p([], config.body)]);
10
+ export const view = <Message>(
11
+ config: ViewConfig,
12
+ h: HtmlBuilder<Message>
13
+ ): Html => h.footer([], [h.hr([]), h.p([], config.body)]);