starlight-obsidian 0.1.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/libs/rehype.ts ADDED
@@ -0,0 +1,74 @@
1
+ import type { Element, ElementContent, Root } from 'hast'
2
+ import type { Literal } from 'mdast'
3
+ import { CONTINUE, SKIP, visit } from 'unist-util-visit'
4
+
5
+ const blockIdentifierRegex = /(?<identifier> *\^(?<name>[\w-]+))$/
6
+
7
+ export function rehypeStarlightObsidian() {
8
+ return function transformer(tree: Root) {
9
+ // Blocks are supported in paragraphs, list items, and blockquotes.
10
+ // https://help.obsidian.md/Linking+notes+and+files/Internal+links#Link%20to%20a%20block%20in%20a%20note
11
+ visit(tree, 'element', (node) => {
12
+ // Handle blockqoutes first as they are block which can contain paragraphs or list items and we want to hoist
13
+ // the IDs to the blockquote element.
14
+ if (node.tagName === 'blockquote') {
15
+ const lastChild = node.children.at(-1)
16
+
17
+ if (
18
+ !lastChild ||
19
+ lastChild.type !== 'element' ||
20
+ !(lastChild.tagName === 'p' || lastChild.tagName === 'ul' || lastChild.tagName === 'ol')
21
+ ) {
22
+ return CONTINUE
23
+ }
24
+
25
+ const lastGrandChild = lastChild.children.at(-1)
26
+
27
+ if (lastChild.tagName === 'p') {
28
+ transformBlockIdentifier(node, lastGrandChild)
29
+ } else if (lastGrandChild?.type === 'element' && lastGrandChild.tagName === 'li') {
30
+ transformBlockIdentifier(node, lastGrandChild.children.at(-1))
31
+ }
32
+ } else if (node.tagName === 'p' || node.tagName === 'li') {
33
+ transformBlockIdentifier(node, node.children.at(-1))
34
+ }
35
+
36
+ return CONTINUE
37
+ })
38
+ }
39
+ }
40
+
41
+ function transformBlockIdentifier(reference: Element, node: ElementContent | undefined) {
42
+ if (!isNodeWithValue(node)) {
43
+ return CONTINUE
44
+ }
45
+
46
+ const identifier = getBlockIdentifer(node)
47
+
48
+ if (!identifier) {
49
+ return CONTINUE
50
+ }
51
+
52
+ node.value = node.value.slice(0, identifier.length * -1)
53
+ reference.properties['id'] = `block-${identifier.name}`
54
+
55
+ return SKIP
56
+ }
57
+
58
+ function isNodeWithValue(node: ElementContent | undefined): node is NodeWithValue {
59
+ return node !== undefined && 'value' in node
60
+ }
61
+
62
+ function getBlockIdentifer(node: NodeWithValue): { length: number; name: string } | undefined {
63
+ const match = node.value.match(blockIdentifierRegex)
64
+ const identifier = match?.groups?.['identifier']
65
+ const name = match?.groups?.['name']
66
+
67
+ if (!identifier || !name) {
68
+ return undefined
69
+ }
70
+
71
+ return { length: identifier.length, name }
72
+ }
73
+
74
+ type NodeWithValue = ElementContent & Literal