mdast-util-sub 1.0.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/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # mdast-util-sub
2
+
3
+ [mdast](https://github.com/syntax-tree/mdast) utilities for inline substitution (`:sub[INITIAL]{REPLACEMENT}`).
4
+
5
+ Inline substitution enables progressive disclosure — show brief content initially, reveal full content on activation. This package provides the mdast utilities; for a complete solution, use [`remark-sub`](https://www.npmjs.com/package/remark-sub).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install mdast-util-sub
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```javascript
16
+ import { fromMarkdown } from 'mdast-util-from-markdown'
17
+ import { toMarkdown } from 'mdast-util-to-markdown'
18
+ import { sub } from 'micromark-extension-sub'
19
+ import { subFromMarkdown, subToMarkdown } from 'mdast-util-sub'
20
+
21
+ // Parse
22
+ const tree = fromMarkdown(':sub[initial]{replacement}', {
23
+ extensions: [sub()],
24
+ mdastExtensions: [subFromMarkdown()]
25
+ })
26
+
27
+ // Stringify
28
+ const md = toMarkdown(tree, {
29
+ extensions: [subToMarkdown()]
30
+ })
31
+ ```
32
+
33
+ ## AST
34
+
35
+ ### `Sub`
36
+
37
+ ```javascript
38
+ interface Sub extends Parent {
39
+ type: 'sub'
40
+ children: PhrasingContent[] // Empty at this layer
41
+ data: {
42
+ replacement: PhrasingContent[] // Empty at this layer
43
+ rawInitial: string // Raw INITIAL text (escapes processed)
44
+ rawReplacement: string // Raw REPLACEMENT text (escapes processed)
45
+ }
46
+ }
47
+ ```
48
+
49
+ At the mdast-util layer, raw content is stored in `rawInitial` and `rawReplacement`. The `children` and `replacement` arrays are populated later by [`remark-sub`](https://www.npmjs.com/package/remark-sub), which re-parses the raw content as inline Markdown.
50
+
51
+ ## Escaping
52
+
53
+ When serializing to markdown:
54
+ - Unbalanced `]` in INITIAL is escaped as `\]`
55
+ - Unbalanced `}` in REPLACEMENT is escaped as `\}`
56
+ - Balanced brackets/braces don't need escaping
57
+
58
+ ## License
59
+
60
+ MIT
@@ -0,0 +1,18 @@
1
+ import type { Extension } from 'mdast-util-from-markdown';
2
+ import type { PhrasingContent } from 'mdast';
3
+ export interface Sub {
4
+ type: 'sub';
5
+ children: PhrasingContent[];
6
+ data: {
7
+ replacement: PhrasingContent[];
8
+ rawInitial: string;
9
+ rawReplacement: string;
10
+ };
11
+ }
12
+ declare module 'mdast' {
13
+ interface PhrasingContentMap {
14
+ sub: Sub;
15
+ }
16
+ }
17
+ export declare function subFromMarkdown(): Extension;
18
+ //# sourceMappingURL=from-markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-markdown.d.ts","sourceRoot":"","sources":["../src/from-markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,SAAS,EAAS,MAAM,0BAA0B,CAAA;AAChF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAA;AAE5C,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,KAAK,CAAA;IACX,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,IAAI,EAAE;QACJ,WAAW,EAAE,eAAe,EAAE,CAAA;QAC9B,UAAU,EAAE,MAAM,CAAA;QAClB,cAAc,EAAE,MAAM,CAAA;KACvB,CAAA;CACF;AAED,OAAO,QAAQ,OAAO,CAAC;IACrB,UAAU,kBAAkB;QAC1B,GAAG,EAAE,GAAG,CAAA;KACT;CACF;AAED,wBAAgB,eAAe,IAAI,SAAS,CAgD3C"}
@@ -0,0 +1,51 @@
1
+ export function subFromMarkdown() {
2
+ let initialRaw = '';
3
+ let replacementRaw = '';
4
+ return {
5
+ enter: {
6
+ sub: enterSub,
7
+ subInitialData: enterSubInitialData,
8
+ subReplacementData: enterSubReplacementData
9
+ },
10
+ exit: {
11
+ subInitialData: exitSubInitialData,
12
+ subReplacementData: exitSubReplacementData,
13
+ sub: exitSub
14
+ }
15
+ };
16
+ function enterSub(token) {
17
+ // @ts-expect-error: sub is a custom node type
18
+ this.enter({ type: 'sub', children: [], data: { replacement: [], rawInitial: '', rawReplacement: '' } }, token);
19
+ }
20
+ function enterSubInitialData() {
21
+ initialRaw = '';
22
+ }
23
+ function exitSubInitialData(token) {
24
+ initialRaw = processEscapes(this.sliceSerialize(token), 'initial');
25
+ }
26
+ function enterSubReplacementData() {
27
+ replacementRaw = '';
28
+ }
29
+ function exitSubReplacementData(token) {
30
+ replacementRaw = processEscapes(this.sliceSerialize(token), 'replacement');
31
+ }
32
+ function exitSub(token) {
33
+ // @ts-expect-error: accessing custom node
34
+ const node = this.stack[this.stack.length - 1];
35
+ // Store raw text for later re-parsing by the remark plugin
36
+ node.data.rawInitial = initialRaw;
37
+ node.data.rawReplacement = replacementRaw;
38
+ this.exit(token);
39
+ }
40
+ }
41
+ function processEscapes(input, context) {
42
+ if (context === 'initial') {
43
+ // In INITIAL: \] -> ], \[ -> [, \\ -> \
44
+ return input.replace(/\\([\[\]\\])/g, '$1');
45
+ }
46
+ else {
47
+ // In REPLACEMENT: \} -> }, \{ -> {, \\ -> \
48
+ return input.replace(/\\([\{\}\\])/g, '$1');
49
+ }
50
+ }
51
+ //# sourceMappingURL=from-markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-markdown.js","sourceRoot":"","sources":["../src/from-markdown.ts"],"names":[],"mappings":"AAmBA,MAAM,UAAU,eAAe;IAC7B,IAAI,UAAU,GAAG,EAAE,CAAA;IACnB,IAAI,cAAc,GAAG,EAAE,CAAA;IAEvB,OAAO;QACL,KAAK,EAAE;YACL,GAAG,EAAE,QAAQ;YACb,cAAc,EAAE,mBAAmB;YACnC,kBAAkB,EAAE,uBAAuB;SAC5C;QACD,IAAI,EAAE;YACJ,cAAc,EAAE,kBAAkB;YAClC,kBAAkB,EAAE,sBAAsB;YAC1C,GAAG,EAAE,OAAO;SACb;KACF,CAAA;IAED,SAAS,QAAQ,CAAuB,KAAY;QAClD,8CAA8C;QAC9C,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,CAAA;IACjH,CAAC;IAED,SAAS,mBAAmB;QAC1B,UAAU,GAAG,EAAE,CAAA;IACjB,CAAC;IAED,SAAS,kBAAkB,CAAuB,KAAY;QAC5D,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,CAAA;IACpE,CAAC;IAED,SAAS,uBAAuB;QAC9B,cAAc,GAAG,EAAE,CAAA;IACrB,CAAC;IAED,SAAS,sBAAsB,CAAuB,KAAY;QAChE,cAAc,GAAG,cAAc,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,CAAA;IAC5E,CAAC;IAED,SAAS,OAAO,CAAuB,KAAY;QACjD,0CAA0C;QAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAQ,CAAA;QAErD,2DAA2D;QAC3D,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QACjC,IAAI,CAAC,IAAI,CAAC,cAAc,GAAG,cAAc,CAAA;QAEzC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClB,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,KAAa,EAAE,OAAkC;IACvE,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,wCAAwC;QACxC,OAAO,KAAK,CAAC,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC,CAAA;IAC7C,CAAC;SAAM,CAAC;QACN,4CAA4C;QAC5C,OAAO,KAAK,CAAC,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC,CAAA;IAC7C,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { subFromMarkdown, type Sub } from './from-markdown.js';
2
+ export { subToMarkdown } from './to-markdown.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,GAAG,EAAE,MAAM,oBAAoB,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { subFromMarkdown } from './from-markdown.js';
2
+ export { subToMarkdown } from './to-markdown.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAY,MAAM,oBAAoB,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { Options } from 'mdast-util-to-markdown';
2
+ export declare function subToMarkdown(): Options;
3
+ //# sourceMappingURL=to-markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"to-markdown.d.ts","sourceRoot":"","sources":["../src/to-markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAe,MAAM,wBAAwB,CAAA;AAGlE,wBAAgB,aAAa,IAAI,OAAO,CAcvC"}
@@ -0,0 +1,96 @@
1
+ export function subToMarkdown() {
2
+ return {
3
+ handlers: {
4
+ // @ts-expect-error: sub is a custom node type
5
+ sub: handleSub
6
+ },
7
+ unsafe: [
8
+ {
9
+ character: ':',
10
+ after: 'sub\\[',
11
+ inConstruct: ['phrasing']
12
+ }
13
+ ]
14
+ };
15
+ }
16
+ function handleSub(node, _parent, state, info) {
17
+ // Serialize children (INITIAL) back to markdown
18
+ const initialContent = state.containerPhrasing(node, {
19
+ ...info,
20
+ before: '[',
21
+ after: ']'
22
+ });
23
+ // Serialize replacement back to markdown
24
+ // Create a temporary parent node for the replacement content
25
+ const replacementContent = state.containerPhrasing({ type: 'paragraph', children: node.data.replacement }, {
26
+ ...info,
27
+ before: '{',
28
+ after: '}'
29
+ });
30
+ return ':sub[' + escapeInitial(initialContent) + ']{' + escapeReplacement(replacementContent) + '}';
31
+ }
32
+ function escapeInitial(value) {
33
+ // Only escape unbalanced ] that would close the construct prematurely
34
+ let result = '';
35
+ let bracketDepth = 0;
36
+ for (let i = 0; i < value.length; i++) {
37
+ const char = value[i];
38
+ // Handle escape sequences - pass through as-is
39
+ if (char === '\\' && i + 1 < value.length) {
40
+ result += char + value[i + 1];
41
+ i++;
42
+ continue;
43
+ }
44
+ if (char === '[') {
45
+ bracketDepth++;
46
+ result += char;
47
+ }
48
+ else if (char === ']') {
49
+ if (bracketDepth > 0) {
50
+ bracketDepth--;
51
+ result += char;
52
+ }
53
+ else {
54
+ // Unbalanced, must escape
55
+ result += '\\]';
56
+ }
57
+ }
58
+ else {
59
+ result += char;
60
+ }
61
+ }
62
+ return result;
63
+ }
64
+ function escapeReplacement(value) {
65
+ // Only escape unbalanced } that would close the construct prematurely
66
+ let result = '';
67
+ let braceDepth = 0;
68
+ for (let i = 0; i < value.length; i++) {
69
+ const char = value[i];
70
+ // Handle escape sequences - pass through as-is
71
+ if (char === '\\' && i + 1 < value.length) {
72
+ result += char + value[i + 1];
73
+ i++;
74
+ continue;
75
+ }
76
+ if (char === '{') {
77
+ braceDepth++;
78
+ result += char;
79
+ }
80
+ else if (char === '}') {
81
+ if (braceDepth > 0) {
82
+ braceDepth--;
83
+ result += char;
84
+ }
85
+ else {
86
+ // Unbalanced, must escape
87
+ result += '\\}';
88
+ }
89
+ }
90
+ else {
91
+ result += char;
92
+ }
93
+ }
94
+ return result;
95
+ }
96
+ //# sourceMappingURL=to-markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"to-markdown.js","sourceRoot":"","sources":["../src/to-markdown.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "mdast-util-sub",
3
+ "version": "1.0.0",
4
+ "description": "mdast utility to parse and serialize inline substitution (:sub[...]{...})",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": ["dist"],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "test": "echo 'Tests run via remark-sub package'",
18
+ "clean": "rm -rf dist"
19
+ },
20
+ "dependencies": {
21
+ "mdast-util-from-markdown": "^2.0.0",
22
+ "mdast-util-to-markdown": "^2.0.0",
23
+ "micromark-extension-sub": "^1.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/mdast": "^4.0.0",
27
+ "typescript": "^5.7.0",
28
+ "vitest": "^4.0.16"
29
+ },
30
+ "keywords": ["mdast", "substitution", "inline", "toggle"],
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/weavepage/remark-sub.git",
35
+ "directory": "packages/mdast-util-sub"
36
+ }
37
+ }