@pitlane/content 0.2.0 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @pitlane/content
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - f2fa73e: Read a heading's `text`, and the `id` made from it, from what the heading shows on the page. Three kinds of heading change, and an anchor written by hand against one of them needs checking once:
8
+
9
+ - Inline HTML now contributes its words and not its tags. `## <span>Visible</span> text` was `<span>Visible</span> text` with the `id` `spanvisiblespan-text`, and is now `Visible text` with `visible-text`.
10
+ - An image no longer contributes its alt text, which matches GitHub and Astro. `## [A](url) ![Cat photo](cat.png)` was `a-cat-photo` and is now `a-`, keeping the space before the image as GitHub does.
11
+ - In MDX, an expression that is a single string literal now contributes its value. `## Hello {"world"}` was `hello-` and is now `hello-world`, and `## The {"{"} key` reads `The { key`. Any other expression, such as `{name}`, still contributes nothing.
12
+
13
+ Headings without inline HTML, images, or expressions keep their `text` and `id`.
14
+
3
15
  ## 0.2.0
4
16
 
5
17
  ### Minor Changes
@@ -3,7 +3,8 @@ import { HastPluginEntry, MdastPluginEntry } from "satteri";
3
3
  /**
4
4
  * Collects every heading of a document as `{ depth, slug, text }`, publishes the
5
5
  * list as `data.headings`, and gives each heading an `id` matching its slug so an
6
- * anchor link lands on it. On MDX the list is also appended to the tree as
6
+ * anchor link lands on it. A heading's text is the text it shows on the page,
7
+ * as `visibleText` reads it. On MDX the list is also appended to the tree as
7
8
  * `export const headings`, so the compiled module carries its own table of
8
9
  * contents.
9
10
  *
package/dist/satteri.mjs CHANGED
@@ -47,9 +47,73 @@ function uniqueSlug(text, taken) {
47
47
  return slug;
48
48
  }
49
49
  /**
50
+ * The text a heading shows on the page, which is what GitHub and Astro slug.
51
+ * Sätteri's `textContent` differs in three places: it keeps raw HTML tags as
52
+ * text, includes image alt text, and reads every MDX expression as nothing.
53
+ * Its options drop the first two, but an expression only carries its source,
54
+ * so the walk is done here.
55
+ *
56
+ * Raw HTML nodes hold only the tags; the text between them is already its own
57
+ * sibling node, so dropping `html` leaves the visible words. Images render no
58
+ * text.
59
+ */
60
+ function visibleText(node) {
61
+ switch (node.type) {
62
+ case "html":
63
+ case "image":
64
+ case "imageReference": return "";
65
+ case "mdxTextExpression": return stringLiteralValue(node.value ?? "") ?? "";
66
+ }
67
+ if (node.children) return node.children.map(visibleText).join("");
68
+ return node.value ?? "";
69
+ }
70
+ /** A lone JavaScript string or template literal, surrounded by nothing but whitespace. */
71
+ const STRING_LITERAL = /^\s*(?:"((?:[^"\\\n\r]|\\[^])*)"|'((?:[^'\\\n\r]|\\[^])*)'|`((?:[^`\\$]|\\[^]|\$(?!\{))*)`)\s*$/;
72
+ /**
73
+ * One escape sequence: a code point in either `\u` form, a `\x` byte, `\0` not
74
+ * followed by a digit, a line continuation, or any other escaped character.
75
+ */
76
+ const ESCAPE = /\\(?:u\{([\da-fA-F]+)\}|u([\da-fA-F]{4})|x([\da-fA-F]{2})|(0)(?!\d)|(\r\n|[\n\r\u2028\u2029])|([^]))/g;
77
+ const SINGLE_CHARACTER_ESCAPES = {
78
+ b: "\b",
79
+ f: "\f",
80
+ n: "\n",
81
+ r: "\r",
82
+ t: " ",
83
+ v: "\v"
84
+ };
85
+ /**
86
+ * The value of an MDX expression whose source is a lone string literal, such
87
+ * as `{"{"}`, the usual way to write a brace in MDX text. `undefined` for any
88
+ * other expression, whose value is only known once the module runs.
89
+ *
90
+ * Escapes decode as in a module, which is strict code: a legacy octal escape
91
+ * such as `\1`, a `\8`, or a malformed `\x` or `\u` makes the literal invalid.
92
+ */
93
+ function stringLiteralValue(source) {
94
+ let match = STRING_LITERAL.exec(source);
95
+ if (!match) return void 0;
96
+ let [, double, single, template] = match;
97
+ let body = double ?? single ?? template.replace(/\r\n?/g, "\n");
98
+ let valid = true;
99
+ let value = body.replace(ESCAPE, (_, braced, unit, byte, nul, lineBreak, other) => {
100
+ if (braced !== void 0) {
101
+ let codePoint = Number.parseInt(braced, 16);
102
+ if (codePoint <= 1114111) return String.fromCodePoint(codePoint);
103
+ } else if (unit !== void 0 || byte !== void 0) return String.fromCharCode(Number.parseInt(unit ?? byte, 16));
104
+ else if (nul !== void 0) return "\0";
105
+ else if (lineBreak !== void 0) return "";
106
+ else if (!/[ux\d]/.test(other)) return SINGLE_CHARACTER_ESCAPES[other] ?? other;
107
+ valid = false;
108
+ return "";
109
+ });
110
+ return valid ? value : void 0;
111
+ }
112
+ /**
50
113
  * Collects every heading of a document as `{ depth, slug, text }`, publishes the
51
114
  * list as `data.headings`, and gives each heading an `id` matching its slug so an
52
- * anchor link lands on it. On MDX the list is also appended to the tree as
115
+ * anchor link lands on it. A heading's text is the text it shows on the page,
116
+ * as `visibleText` reads it. On MDX the list is also appended to the tree as
53
117
  * `export const headings`, so the compiled module carries its own table of
54
118
  * contents.
55
119
  *
@@ -72,7 +136,7 @@ function headings() {
72
136
  return {
73
137
  name: "pitlane-headings",
74
138
  heading(node, context) {
75
- let text = context.textContent(node);
139
+ let text = visibleText(node);
76
140
  let slug = uniqueSlug(text, taken);
77
141
  collected.push({
78
142
  depth: node.depth,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pitlane/content",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Schema-validated content collections for Remix.",
5
5
  "keywords": [
6
6
  "content",