oast-to-hast 4.5.2 → 4.6.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/lib/handlers/headline.d.ts.map +1 -1
- package/lib/handlers/headline.js +2 -1
- package/lib/handlers/section.d.ts +6 -0
- package/lib/handlers/section.d.ts.map +1 -1
- package/lib/handlers/section.js +2 -2
- package/lib/ids.d.ts +23 -0
- package/lib/ids.d.ts.map +1 -0
- package/lib/ids.js +90 -0
- package/lib/state.d.ts +6 -3
- package/lib/state.d.ts.map +1 -1
- package/lib/state.js +52 -11
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headline.d.ts","sourceRoot":"","sources":["headline.js"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,gCAJW,OAAO,aAAa,EAAE,KAAK,QAC3B,OAAO,MAAM,EAAE,QAAQ,GACrB,OAAO,MAAM,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"headline.d.ts","sourceRoot":"","sources":["headline.js"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,gCAJW,OAAO,aAAa,EAAE,KAAK,QAC3B,OAAO,MAAM,EAAE,QAAQ,GACrB,OAAO,MAAM,EAAE,OAAO,CAUlC"}
|
package/lib/handlers/headline.js
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* @returns {import('hast').Element}
|
|
5
5
|
*/
|
|
6
6
|
export function headline(state, node) {
|
|
7
|
+
const id = state.ids.byNode.get(node)
|
|
7
8
|
return state.patch(node, {
|
|
8
9
|
type: 'element',
|
|
9
10
|
tagName: `h${node.level}`,
|
|
10
|
-
properties: {},
|
|
11
|
+
properties: id ? { id } : {},
|
|
11
12
|
children: state.all(node)
|
|
12
13
|
})
|
|
13
14
|
}
|
|
@@ -4,4 +4,10 @@
|
|
|
4
4
|
* @returns {import('hast').Element|undefined}
|
|
5
5
|
*/
|
|
6
6
|
export function section(state: import("../state.js").State, node: import("orga").Section): import("hast").Element | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* @param {Pick<import('../state.js').Config, 'selectTags' | 'excludeTags'>} config
|
|
9
|
+
* @param {string[]} tags
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
export function shouldSkip({ selectTags, excludeTags }: Pick<import("../state.js").Config, "selectTags" | "excludeTags">, tags: string[]): boolean;
|
|
7
13
|
//# sourceMappingURL=section.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section.d.ts","sourceRoot":"","sources":["section.js"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,+BAJW,OAAO,aAAa,EAAE,KAAK,QAC3B,OAAO,MAAM,EAAE,OAAO,GACpB,OAAO,MAAM,EAAE,OAAO,GAAC,SAAS,CA6B5C"}
|
|
1
|
+
{"version":3,"file":"section.d.ts","sourceRoot":"","sources":["section.js"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,+BAJW,OAAO,aAAa,EAAE,KAAK,QAC3B,OAAO,MAAM,EAAE,OAAO,GACpB,OAAO,MAAM,EAAE,OAAO,GAAC,SAAS,CA6B5C;AAED;;;;GAIG;AACH,wDAJW,IAAI,CAAC,OAAO,aAAa,EAAE,MAAM,EAAE,YAAY,GAAG,aAAa,CAAC,QAChE,MAAM,EAAE,GACN,OAAO,CAYnB"}
|
package/lib/handlers/section.js
CHANGED
|
@@ -33,11 +33,11 @@ export function section(state, node) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* @param {import('../state.js').Config} config
|
|
36
|
+
* @param {Pick<import('../state.js').Config, 'selectTags' | 'excludeTags'>} config
|
|
37
37
|
* @param {string[]} tags
|
|
38
38
|
* @returns {boolean}
|
|
39
39
|
*/
|
|
40
|
-
function shouldSkip({ selectTags = [], excludeTags = [] }, tags) {
|
|
40
|
+
export function shouldSkip({ selectTags = [], excludeTags = [] }, tags) {
|
|
41
41
|
if (selectTags.length > 0) {
|
|
42
42
|
return !tags.some((tag) => selectTags.includes(tag))
|
|
43
43
|
}
|
package/lib/ids.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slug of heading text: lowercased, punctuation dropped, spaces as dashes.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} text
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
export function slug(text: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Assign every rendered headline a unique id: its section's `CUSTOM_ID` when
|
|
10
|
+
* set, otherwise a slug of its text (`-1`, `-2`… on repeats). Sections skipped
|
|
11
|
+
* by `selectTags`/`excludeTags` are left out, as they are when rendering.
|
|
12
|
+
*
|
|
13
|
+
* @param {Nodes} tree
|
|
14
|
+
* @param {Pick<Config, 'selectTags' | 'excludeTags'>} config
|
|
15
|
+
*/
|
|
16
|
+
export function headingIds(tree: Nodes, config: Pick<Config, "selectTags" | "excludeTags">): {
|
|
17
|
+
byNode: Map<Headline, string>;
|
|
18
|
+
byText: Map<string, string>;
|
|
19
|
+
};
|
|
20
|
+
import type { Nodes } from 'orga';
|
|
21
|
+
import type { Config } from './state.js';
|
|
22
|
+
import type { Headline } from 'orga';
|
|
23
|
+
//# sourceMappingURL=ids.d.ts.map
|
package/lib/ids.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["ids.js"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,2BAHW,MAAM,GACJ,MAAM,CAQlB;AAED;;;;;;;GAOG;AACH,iCAHW,KAAK,UACL,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CAAC;;;EAiDpD;2BA3EiC,MAAM;4BACf,YAAY;8BADH,MAAM"}
|
package/lib/ids.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import {Headline, Nodes} from 'orga'
|
|
3
|
+
* @import {Config} from './state.js'
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { shouldSkip } from './handlers/section.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Slug of heading text: lowercased, punctuation dropped, spaces as dashes.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} text
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
export function slug(text) {
|
|
15
|
+
return text
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.trim()
|
|
18
|
+
.replace(/[^\p{L}\p{N}\s_-]/gu, '')
|
|
19
|
+
.replace(/\s+/g, '-')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Assign every rendered headline a unique id: its section's `CUSTOM_ID` when
|
|
24
|
+
* set, otherwise a slug of its text (`-1`, `-2`… on repeats). Sections skipped
|
|
25
|
+
* by `selectTags`/`excludeTags` are left out, as they are when rendering.
|
|
26
|
+
*
|
|
27
|
+
* @param {Nodes} tree
|
|
28
|
+
* @param {Pick<Config, 'selectTags' | 'excludeTags'>} config
|
|
29
|
+
*/
|
|
30
|
+
export function headingIds(tree, config) {
|
|
31
|
+
/** @type {Array<{headline: Headline, customId: string | undefined}>} */
|
|
32
|
+
const headlines = []
|
|
33
|
+
collect(tree)
|
|
34
|
+
|
|
35
|
+
const used = new Set(headlines.map((h) => h.customId).filter(Boolean))
|
|
36
|
+
/** @type {Map<Headline, string>} */
|
|
37
|
+
const byNode = new Map()
|
|
38
|
+
/** @type {Map<string, string>} first heading with this text slug → its id */
|
|
39
|
+
const byText = new Map()
|
|
40
|
+
|
|
41
|
+
for (const { headline, customId } of headlines) {
|
|
42
|
+
const base = slug(text(headline))
|
|
43
|
+
let id = customId
|
|
44
|
+
if (!id && base) {
|
|
45
|
+
id = base
|
|
46
|
+
for (let n = 1; used.has(id); n++) id = `${base}-${n}`
|
|
47
|
+
used.add(id)
|
|
48
|
+
}
|
|
49
|
+
if (!id) continue
|
|
50
|
+
byNode.set(headline, id)
|
|
51
|
+
if (base && !byText.has(base)) byText.set(base, id)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { byNode, byText }
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {Nodes} node
|
|
58
|
+
* @param {Nodes} [parent]
|
|
59
|
+
*/
|
|
60
|
+
function collect(node, parent) {
|
|
61
|
+
if (node.type === 'headline') {
|
|
62
|
+
const customId =
|
|
63
|
+
parent?.type === 'section' ? parent.properties.custom_id : undefined
|
|
64
|
+
headlines.push({
|
|
65
|
+
headline: node,
|
|
66
|
+
customId: typeof customId === 'string' ? customId : undefined
|
|
67
|
+
})
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
if (!('children' in node)) return
|
|
71
|
+
if (node.type === 'section') {
|
|
72
|
+
const headline = node.children.find((n) => n.type === 'headline')
|
|
73
|
+
if (headline && shouldSkip(config, headline.tags || [])) return
|
|
74
|
+
}
|
|
75
|
+
for (const child of node.children) collect(child, node)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const skip = new Set(['stars', 'todo', 'priority', 'tags'])
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {Nodes} node
|
|
83
|
+
* @returns {string}
|
|
84
|
+
*/
|
|
85
|
+
function text(node) {
|
|
86
|
+
if (skip.has(node.type)) return ''
|
|
87
|
+
if ('children' in node) return node.children.map(text).join('')
|
|
88
|
+
if (node.type === 'text') return node.value
|
|
89
|
+
return ''
|
|
90
|
+
}
|
package/lib/state.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @param {OastNodes}
|
|
2
|
+
* @param {OastNodes} tree
|
|
3
3
|
* @param {Partial<Config> | null | undefined} [options = {}]
|
|
4
4
|
*/
|
|
5
|
-
export function createState(
|
|
5
|
+
export function createState(tree: OastNodes, options?: Partial<Config> | null | undefined): {
|
|
6
6
|
one: (node: OastNodes, parent?: OastParent | undefined) => HastRoot | HastElementContent | HastElementContent[] | undefined;
|
|
7
|
+
ids: {
|
|
8
|
+
byNode: Map<import("orga").Headline, string>;
|
|
9
|
+
byText: Map<string, string>;
|
|
10
|
+
};
|
|
7
11
|
all: (parent: OastNodes) => Array<HastElementContent>;
|
|
8
12
|
handlers: Partial<Record<"newline" | "document" | "headline" | "paragraph" | "keyword" | "block" | "list" | "html" | "jsx" | "stars" | "todo" | "link" | "section" | "drawer" | "planning" | "table" | "hr" | "latex" | "footnote" | "table.row" | "table.hr" | "table.cell" | "list.item" | "text" | "footnote.reference" | "emptyLine" | "priority" | "tags" | "planning.keyword" | "planning.timestamp" | "list.item.tag" | "list.item.checkbox" | "list.item.bullet" | "table.columnSeparator" | "footnote.label" | "block.begin" | "block.end" | "latex.begin" | "latex.end" | "drawer.begin" | "drawer.end" | "comment" | "opening" | "closing" | "link.path", Handler>>;
|
|
9
13
|
getAttrHtml: (node: OastNodes) => Record<string, string> | undefined;
|
|
10
14
|
patch: <T extends HastNodes>(from: OastNodes, to: T) => T;
|
|
11
|
-
/** @type {Config} */
|
|
12
15
|
options: Config;
|
|
13
16
|
};
|
|
14
17
|
export type HastNodes = import("hast").Nodes;
|
package/lib/state.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["state.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["state.js"],"names":[],"mappings":"AAkCA;;;GAGG;AACH,kCAHW,SAAS,YACT,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,SAAS;gBA+EjC,SAAS,WACT,UAAU,GAAG,SAAS;;;;;kBAzBtB,SAAS,KACP,KAAK,CAAC,kBAAkB,CAAC;;wBAgC3B,SAAS,KACP,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS;YAYtB,CAAC,SAAZ,SAAU,QACb,SAAS,MACT,CAAC,KACC,CAAC;;EAQd;wBAnJY,OAAO,MAAM,EAAE,KAAK;yBAEpB,OAAO,MAAM,EAAE,MAAM;wBACrB,OAAO,MAAM,EAAE,KAAK;;cAKnB,QAAQ;gBACR,MAAM;cACN,CAAC,IAAI,EAAE,OAAO,MAAM,EAAE,IAAI,KAAK,MAAM;;;;gBACrC,MAAM,EAAE;;;;iBACR,MAAM,EAAE;;oBAIT,UAAU,CAAC,OAAO,WAAW,CAAC;8BAGhC,KAAK,QACL,GAAG,UACH,UAAU,GAAG,SAAS,KACpB,KAAK,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,GAAG,QAAQ,GAAG,SAAS;;;;uBAGrE,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;sCAxBe,MAAM;0DAAN,MAAM"}
|
package/lib/state.js
CHANGED
|
@@ -30,37 +30,64 @@
|
|
|
30
30
|
|
|
31
31
|
import { position } from 'unist-util-position'
|
|
32
32
|
import { handlers as defaultHandlers } from './handlers/index.js'
|
|
33
|
+
import { headingIds, slug } from './ids.js'
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
* @param {OastNodes}
|
|
36
|
+
* @param {OastNodes} tree
|
|
36
37
|
* @param {Partial<Config> | null | undefined} [options = {}]
|
|
37
38
|
*/
|
|
38
|
-
export function createState(
|
|
39
|
+
export function createState(tree, options = {}) {
|
|
39
40
|
/** @type {Handlers} */
|
|
40
41
|
let handlers = { ...defaultHandlers }
|
|
41
42
|
if (options?.handlers) {
|
|
42
43
|
handlers = { ...handlers, ...options.handlers }
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
/** @type {Config} */
|
|
47
|
+
const config = {
|
|
48
|
+
handlers,
|
|
49
|
+
linkTarget: '_self',
|
|
50
|
+
selectTags: [],
|
|
51
|
+
excludeTags: ['noexport'],
|
|
52
|
+
linkHref: defaultLinkHref,
|
|
53
|
+
...options
|
|
54
|
+
}
|
|
55
|
+
const ids = headingIds(tree, config)
|
|
56
|
+
|
|
45
57
|
const state = {
|
|
46
58
|
one,
|
|
59
|
+
ids,
|
|
47
60
|
all,
|
|
48
61
|
handlers,
|
|
49
62
|
getAttrHtml,
|
|
50
63
|
patch,
|
|
51
|
-
|
|
52
|
-
options: {
|
|
53
|
-
handlers,
|
|
54
|
-
linkTarget: '_self',
|
|
55
|
-
selectTags: [],
|
|
56
|
-
excludeTags: ['noexport'],
|
|
57
|
-
linkHref: (link) => link.path.value,
|
|
58
|
-
...options
|
|
59
|
-
}
|
|
64
|
+
options: config
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
return state
|
|
63
68
|
|
|
69
|
+
/**
|
|
70
|
+
* @param {import('orga').Link} link
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
function defaultLinkHref(link) {
|
|
74
|
+
const { protocol, value, search } = link.path
|
|
75
|
+
if (protocol === 'internal' && value.startsWith('*')) {
|
|
76
|
+
const target = slug(value.slice(1))
|
|
77
|
+
return `#${ids.byText.get(target) ?? target}`
|
|
78
|
+
}
|
|
79
|
+
if (!protocol || protocol === 'internal') {
|
|
80
|
+
return value
|
|
81
|
+
}
|
|
82
|
+
if (protocol === 'file') {
|
|
83
|
+
return value + fragment(search)
|
|
84
|
+
}
|
|
85
|
+
if (protocol === 'http' || protocol === 'https') {
|
|
86
|
+
return value
|
|
87
|
+
}
|
|
88
|
+
return `${protocol}:${value}`
|
|
89
|
+
}
|
|
90
|
+
|
|
64
91
|
/**
|
|
65
92
|
* @param {OastNodes} parent
|
|
66
93
|
* @returns {Array<HastElementContent>}
|
|
@@ -121,6 +148,20 @@ export function createState(_tree, options = {}) {
|
|
|
121
148
|
}
|
|
122
149
|
}
|
|
123
150
|
|
|
151
|
+
/**
|
|
152
|
+
* URL fragment for a `file:` link's search option: `*Heading` and `#custom-id`
|
|
153
|
+
* target a heading; other searches (line numbers, text) are dropped.
|
|
154
|
+
*
|
|
155
|
+
* @param {string | number | undefined} search
|
|
156
|
+
* @returns {string}
|
|
157
|
+
*/
|
|
158
|
+
function fragment(search) {
|
|
159
|
+
if (typeof search !== 'string') return ''
|
|
160
|
+
if (search.startsWith('#')) return search
|
|
161
|
+
if (search.startsWith('*')) return `#${slug(search.slice(1))}`
|
|
162
|
+
return ''
|
|
163
|
+
}
|
|
164
|
+
|
|
124
165
|
/** @type {Handler} */
|
|
125
166
|
function unkownHandler(state, node) {
|
|
126
167
|
if (node?.children) {
|