@osdlc/markdown-link 0.0.0-stage → 0.5.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/CHANGELOG.md +7 -0
- package/CONTRIBUTING.md +64 -0
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +113 -2
- package/SECURITY.md +23 -0
- package/dist/body-links.d.ts +64 -0
- package/dist/body-links.js +219 -0
- package/dist/code.d.ts +29 -0
- package/dist/code.js +353 -0
- package/dist/display.d.ts +36 -0
- package/dist/display.js +88 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/parse.d.ts +68 -0
- package/dist/parse.js +217 -0
- package/package.json +64 -3
package/dist/display.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a reader sees for a link, and typed references. A port of the
|
|
3
|
+
* `markdown-link` crate's `display_text` and `parse_typed_ref`.
|
|
4
|
+
*/
|
|
5
|
+
import { isWhitespace, parseInner, splitAlias, trim } from "./parse.js";
|
|
6
|
+
/**
|
|
7
|
+
* Replace each wikilink and embed with what a reader sees for it, for
|
|
8
|
+
* snippets and search: its alias, or else its target. An embed's trailing
|
|
9
|
+
* `|300` or `|300x200` is a size, not an alias, so
|
|
10
|
+
* `![[img.png|300]]` shows `img.png`. An empty alias shows the target. Code
|
|
11
|
+
* is not skipped.
|
|
12
|
+
*
|
|
13
|
+
* Every delimiter is ASCII, so the scan can step UTF-16 units: a surrogate
|
|
14
|
+
* never matches one.
|
|
15
|
+
*/
|
|
16
|
+
export function displayText(text) {
|
|
17
|
+
let out = '';
|
|
18
|
+
let i = 0;
|
|
19
|
+
while (i < text.length) {
|
|
20
|
+
const embed = text[i] === '!' && text.startsWith('[[', i + 1);
|
|
21
|
+
const open = embed ? i + 1 : i;
|
|
22
|
+
if (text.startsWith('[[', open)) {
|
|
23
|
+
const close = linkClose(text, open);
|
|
24
|
+
if (close !== null) {
|
|
25
|
+
const link = parseInner(text.slice(open + 2, close), embed);
|
|
26
|
+
if (link !== null)
|
|
27
|
+
out += shown(link);
|
|
28
|
+
i = close + 2;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
out += text[i];
|
|
33
|
+
i++;
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
/** Where the `]]` closing the `[[` at `open` starts, or `null`. */
|
|
38
|
+
function linkClose(text, open) {
|
|
39
|
+
for (let j = open + 2; j + 1 < text.length; j++) {
|
|
40
|
+
if (text[j] === ']' && text[j + 1] === ']')
|
|
41
|
+
return j;
|
|
42
|
+
if (text[j] === '[' || text[j] === ']')
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/** What a reader sees for one link. See `displayText`. */
|
|
48
|
+
function shown(link) {
|
|
49
|
+
let alias = link.alias;
|
|
50
|
+
if (alias !== null && link.embed) {
|
|
51
|
+
const pipe = alias.lastIndexOf('|');
|
|
52
|
+
const last = pipe === -1 ? alias : alias.slice(pipe + 1);
|
|
53
|
+
if (isSize(trim(last)))
|
|
54
|
+
alias = pipe === -1 ? '' : trim(alias.slice(0, pipe));
|
|
55
|
+
}
|
|
56
|
+
return alias || link.target;
|
|
57
|
+
}
|
|
58
|
+
/** `300` or `300x200`: an embed's width, or width and height. */
|
|
59
|
+
function isSize(s) {
|
|
60
|
+
return /^[0-9]+(?:x[0-9]+)?$/.test(s);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Parse a whole value as a typed reference, or `null`.
|
|
64
|
+
*
|
|
65
|
+
* Shape only: the target is not resolved. The value is exactly `[[...]]`
|
|
66
|
+
* with no `[`, `]` or line break inside. The target ends at the first `|`,
|
|
67
|
+
* minus a `\` that escapes it, as in `parseInner`. Its type and each
|
|
68
|
+
* `/`-separated slug segment are non-empty and hold no whitespace.
|
|
69
|
+
*/
|
|
70
|
+
export function parseTypedRef(value) {
|
|
71
|
+
if (!value.startsWith('[[') || !value.endsWith(']]'))
|
|
72
|
+
return null;
|
|
73
|
+
const inner = value.slice(2, -2);
|
|
74
|
+
if (/[[\]\n\r]/.test(inner))
|
|
75
|
+
return null;
|
|
76
|
+
const [target, alias] = splitAlias(inner);
|
|
77
|
+
const slash = target.indexOf('/');
|
|
78
|
+
if (slash === -1)
|
|
79
|
+
return null;
|
|
80
|
+
const kind = target.slice(0, slash);
|
|
81
|
+
const slug = target.slice(slash + 1);
|
|
82
|
+
if (!isWord(kind) || !slug.split('/').every(isWord))
|
|
83
|
+
return null;
|
|
84
|
+
return { kind, slug, alias };
|
|
85
|
+
}
|
|
86
|
+
function isWord(s) {
|
|
87
|
+
return s !== '' && !Array.from(s).some(isWhitespace);
|
|
88
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wikilink parsing that follows documented link-resolution conventions: the TypeScript twin of the `markdown-link`
|
|
3
|
+
* crate's parsing half. Each function gives the same answer as its Rust
|
|
4
|
+
* namesake. Both suites read the crate's case file,
|
|
5
|
+
* `packages/rust/markdown-link/tests/fixtures/conformance.json`.
|
|
6
|
+
*
|
|
7
|
+
* Spans and ranges are UTF-16 offsets, so they index a JavaScript string.
|
|
8
|
+
* The Rust crate reports byte offsets.
|
|
9
|
+
*/
|
|
10
|
+
export { type CodeRules, stripCode, stripCodeWith } from './code.ts';
|
|
11
|
+
export { ATTACHMENT_EXTENSIONS, type TextRange, type WikiLink, isVaultLinkUrl, mdLinkTarget, parseInner, targetRange, } from './parse.ts';
|
|
12
|
+
export { type TypedRef, displayText, parseTypedRef } from './display.ts';
|
|
13
|
+
export { type BodyLink, type BodyLinkKind, type LinkKind, type RawLink, bodyLinks, bodyLinksSpanned, } from './body-links.ts';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wikilink parsing that follows documented link-resolution conventions: the TypeScript twin of the `markdown-link`
|
|
3
|
+
* crate's parsing half. Each function gives the same answer as its Rust
|
|
4
|
+
* namesake. Both suites read the crate's case file,
|
|
5
|
+
* `packages/rust/markdown-link/tests/fixtures/conformance.json`.
|
|
6
|
+
*
|
|
7
|
+
* Spans and ranges are UTF-16 offsets, so they index a JavaScript string.
|
|
8
|
+
* The Rust crate reports byte offsets.
|
|
9
|
+
*/
|
|
10
|
+
export { stripCode, stripCodeWith } from "./code.js";
|
|
11
|
+
export { ATTACHMENT_EXTENSIONS, isVaultLinkUrl, mdLinkTarget, parseInner, targetRange, } from "./parse.js";
|
|
12
|
+
export { displayText, parseTypedRef } from "./display.js";
|
|
13
|
+
export { bodyLinks, bodyLinksSpanned, } from "./body-links.js";
|
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One wikilink body, and one markdown link URL. A port of the `wikilink`
|
|
3
|
+
* crate's `parse_inner`, `target_range`, `is_vault_link_url` and
|
|
4
|
+
* `md_link_target`.
|
|
5
|
+
*/
|
|
6
|
+
/** A parsed `[[target#heading|alias]]`. */
|
|
7
|
+
export interface WikiLink {
|
|
8
|
+
target: string;
|
|
9
|
+
heading: string | null;
|
|
10
|
+
alias: string | null;
|
|
11
|
+
embed: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** A half-open range of UTF-16 offsets, `[start, end)`. */
|
|
14
|
+
export interface TextRange {
|
|
15
|
+
start: number;
|
|
16
|
+
end: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parse a single wikilink body (the text between the brackets).
|
|
20
|
+
*
|
|
21
|
+
* A `\` right before the first `|` is the escape a GFM table cell needs
|
|
22
|
+
* (`[[Note\|alias]]`) and is dropped from the target or heading.
|
|
23
|
+
*
|
|
24
|
+
* @returns `null` when the target is empty: a blank body, or one holding only
|
|
25
|
+
* a `#heading` or an `|alias`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseInner(inner: string, embed: boolean): WikiLink | null;
|
|
28
|
+
/**
|
|
29
|
+
* The range of `parseInner`'s target within `inner`, for a caller that
|
|
30
|
+
* rewrites the target in place and must leave the rest of the link as written.
|
|
31
|
+
* UTF-16 offsets, where the Rust crate returns byte offsets.
|
|
32
|
+
*/
|
|
33
|
+
export declare function targetRange(inner: string): TextRange | null;
|
|
34
|
+
/** `inner` split at the first `|`, minus the `\` that escapes it. */
|
|
35
|
+
export declare function splitAlias(inner: string): [string, string | null];
|
|
36
|
+
export declare function trim(s: string): string;
|
|
37
|
+
/** Whether a single code point is Unicode White_Space. */
|
|
38
|
+
export declare function isWhitespace(c: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Every non-markdown extension that counts as a vault attachment (lowercase,
|
|
41
|
+
* no dot). Matched case-insensitively, unlike the `.md` rule.
|
|
42
|
+
*
|
|
43
|
+
* This answers "is this file an attachment", not "can it be displayed".
|
|
44
|
+
* `tiff` and `heic` are real attachments a photo library produces that no
|
|
45
|
+
* browser reliably paints: they belong in a graph and a search index, not in
|
|
46
|
+
* an `<img>`.
|
|
47
|
+
*
|
|
48
|
+
* The Rust `markdown-link` crate's `ATTACHMENT_EXTENSIONS` is the same list;
|
|
49
|
+
* `tests/attachment-extensions.test.ts` fails if they disagree.
|
|
50
|
+
*/
|
|
51
|
+
export declare const ATTACHMENT_EXTENSIONS: readonly string[];
|
|
52
|
+
/**
|
|
53
|
+
* Whether a markdown link's URL is one this dialect harvests: a note, or a
|
|
54
|
+
* vault attachment.
|
|
55
|
+
*
|
|
56
|
+
* The `.md` test is case-sensitive (`x.MD` is not a link) and needs at least
|
|
57
|
+
* one character before the extension (`.md` is not either).
|
|
58
|
+
*/
|
|
59
|
+
export declare function isVaultLinkUrl(url: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The resolution target of a markdown link's URL: percent-decoded, minus its
|
|
62
|
+
* trailing `.md`.
|
|
63
|
+
*
|
|
64
|
+
* `.md` and then `.MD` are stripped **repeatedly**, so `a.md.md` targets `a`.
|
|
65
|
+
* Only when neither strips anything does a single case-insensitive `.md` come
|
|
66
|
+
* off.
|
|
67
|
+
*/
|
|
68
|
+
export declare function mdLinkTarget(url: string): string;
|
package/dist/parse.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One wikilink body, and one markdown link URL. A port of the `wikilink`
|
|
3
|
+
* crate's `parse_inner`, `target_range`, `is_vault_link_url` and
|
|
4
|
+
* `md_link_target`.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Parse a single wikilink body (the text between the brackets).
|
|
8
|
+
*
|
|
9
|
+
* A `\` right before the first `|` is the escape a GFM table cell needs
|
|
10
|
+
* (`[[Note\|alias]]`) and is dropped from the target or heading.
|
|
11
|
+
*
|
|
12
|
+
* @returns `null` when the target is empty: a blank body, or one holding only
|
|
13
|
+
* a `#heading` or an `|alias`.
|
|
14
|
+
*/
|
|
15
|
+
export function parseInner(inner, embed) {
|
|
16
|
+
const target = targetRange(inner);
|
|
17
|
+
if (target === null)
|
|
18
|
+
return null;
|
|
19
|
+
const [beforeAlias, alias] = splitAlias(inner);
|
|
20
|
+
const hash = beforeAlias.indexOf('#');
|
|
21
|
+
return {
|
|
22
|
+
target: inner.slice(target.start, target.end),
|
|
23
|
+
heading: hash === -1 ? null : trim(beforeAlias.slice(hash + 1)),
|
|
24
|
+
alias: alias === null ? null : trim(alias),
|
|
25
|
+
embed,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The range of `parseInner`'s target within `inner`, for a caller that
|
|
30
|
+
* rewrites the target in place and must leave the rest of the link as written.
|
|
31
|
+
* UTF-16 offsets, where the Rust crate returns byte offsets.
|
|
32
|
+
*/
|
|
33
|
+
export function targetRange(inner) {
|
|
34
|
+
const [beforeAlias] = splitAlias(inner);
|
|
35
|
+
const hash = beforeAlias.indexOf('#');
|
|
36
|
+
const raw = hash === -1 ? beforeAlias : beforeAlias.slice(0, hash);
|
|
37
|
+
const target = trim(raw);
|
|
38
|
+
if (target === '')
|
|
39
|
+
return null;
|
|
40
|
+
const lead = raw.length - trimStart(raw).length;
|
|
41
|
+
return { start: lead, end: lead + target.length };
|
|
42
|
+
}
|
|
43
|
+
/** `inner` split at the first `|`, minus the `\` that escapes it. */
|
|
44
|
+
export function splitAlias(inner) {
|
|
45
|
+
const pipe = inner.indexOf('|');
|
|
46
|
+
if (pipe === -1)
|
|
47
|
+
return [inner, null];
|
|
48
|
+
const before = inner.slice(0, pipe);
|
|
49
|
+
return [before.endsWith('\\') ? before.slice(0, -1) : before, inner.slice(pipe + 1)];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Whether a UTF-16 unit is Unicode White_Space, as Rust's `char::is_whitespace`
|
|
53
|
+
* reads it. Every White_Space character is in the BMP. JavaScript's `trim` and
|
|
54
|
+
* `\s` differ: they treat U+FEFF as space and U+0085 as not.
|
|
55
|
+
*/
|
|
56
|
+
function isSpaceUnit(unit) {
|
|
57
|
+
return ((unit >= 0x09 && unit <= 0x0d) ||
|
|
58
|
+
unit === 0x20 ||
|
|
59
|
+
unit === 0x85 ||
|
|
60
|
+
unit === 0xa0 ||
|
|
61
|
+
unit === 0x1680 ||
|
|
62
|
+
(unit >= 0x2000 && unit <= 0x200a) ||
|
|
63
|
+
unit === 0x2028 ||
|
|
64
|
+
unit === 0x2029 ||
|
|
65
|
+
unit === 0x202f ||
|
|
66
|
+
unit === 0x205f ||
|
|
67
|
+
unit === 0x3000);
|
|
68
|
+
}
|
|
69
|
+
function trimStart(s) {
|
|
70
|
+
let start = 0;
|
|
71
|
+
while (start < s.length && isSpaceUnit(s.charCodeAt(start)))
|
|
72
|
+
start++;
|
|
73
|
+
return s.slice(start);
|
|
74
|
+
}
|
|
75
|
+
export function trim(s) {
|
|
76
|
+
const rest = trimStart(s);
|
|
77
|
+
let end = rest.length;
|
|
78
|
+
while (end > 0 && isSpaceUnit(rest.charCodeAt(end - 1)))
|
|
79
|
+
end--;
|
|
80
|
+
return rest.slice(0, end);
|
|
81
|
+
}
|
|
82
|
+
/** Whether a single code point is Unicode White_Space. */
|
|
83
|
+
export function isWhitespace(c) {
|
|
84
|
+
return c.length === 1 && isSpaceUnit(c.charCodeAt(0));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Every non-markdown extension that counts as a vault attachment (lowercase,
|
|
88
|
+
* no dot). Matched case-insensitively, unlike the `.md` rule.
|
|
89
|
+
*
|
|
90
|
+
* This answers "is this file an attachment", not "can it be displayed".
|
|
91
|
+
* `tiff` and `heic` are real attachments a photo library produces that no
|
|
92
|
+
* browser reliably paints: they belong in a graph and a search index, not in
|
|
93
|
+
* an `<img>`.
|
|
94
|
+
*
|
|
95
|
+
* The Rust `markdown-link` crate's `ATTACHMENT_EXTENSIONS` is the same list;
|
|
96
|
+
* `tests/attachment-extensions.test.ts` fails if they disagree.
|
|
97
|
+
*/
|
|
98
|
+
export const ATTACHMENT_EXTENSIONS = [
|
|
99
|
+
'png',
|
|
100
|
+
'jpg',
|
|
101
|
+
'jpeg',
|
|
102
|
+
'gif',
|
|
103
|
+
'svg',
|
|
104
|
+
'webp',
|
|
105
|
+
'avif',
|
|
106
|
+
'bmp',
|
|
107
|
+
'ico',
|
|
108
|
+
'tiff',
|
|
109
|
+
'heic',
|
|
110
|
+
'mp4',
|
|
111
|
+
'webm',
|
|
112
|
+
'ogv',
|
|
113
|
+
'mov',
|
|
114
|
+
'mkv',
|
|
115
|
+
'mp3',
|
|
116
|
+
'wav',
|
|
117
|
+
'm4a',
|
|
118
|
+
'3gp',
|
|
119
|
+
'flac',
|
|
120
|
+
'ogg',
|
|
121
|
+
'oga',
|
|
122
|
+
'opus',
|
|
123
|
+
'pdf',
|
|
124
|
+
'base',
|
|
125
|
+
'canvas',
|
|
126
|
+
];
|
|
127
|
+
/**
|
|
128
|
+
* Whether a markdown link's URL is one this dialect harvests: a note, or a
|
|
129
|
+
* vault attachment.
|
|
130
|
+
*
|
|
131
|
+
* The `.md` test is case-sensitive (`x.MD` is not a link) and needs at least
|
|
132
|
+
* one character before the extension (`.md` is not either).
|
|
133
|
+
*/
|
|
134
|
+
export function isVaultLinkUrl(url) {
|
|
135
|
+
if (url.length > 3 && url.endsWith('.md'))
|
|
136
|
+
return true;
|
|
137
|
+
const last = url.slice(url.lastIndexOf('/') + 1);
|
|
138
|
+
const dot = last.lastIndexOf('.');
|
|
139
|
+
// A leading dot is a hidden file, not an extension, and a trailing dot is no
|
|
140
|
+
// extension at all.
|
|
141
|
+
if (dot <= 0)
|
|
142
|
+
return false;
|
|
143
|
+
return ATTACHMENT_EXTENSIONS.includes(last.slice(dot + 1).toLowerCase());
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The resolution target of a markdown link's URL: percent-decoded, minus its
|
|
147
|
+
* trailing `.md`.
|
|
148
|
+
*
|
|
149
|
+
* `.md` and then `.MD` are stripped **repeatedly**, so `a.md.md` targets `a`.
|
|
150
|
+
* Only when neither strips anything does a single case-insensitive `.md` come
|
|
151
|
+
* off.
|
|
152
|
+
*/
|
|
153
|
+
export function mdLinkTarget(url) {
|
|
154
|
+
const decoded = percentDecode(url);
|
|
155
|
+
let trimmed = decoded;
|
|
156
|
+
while (trimmed.endsWith('.md'))
|
|
157
|
+
trimmed = trimmed.slice(0, -3);
|
|
158
|
+
while (trimmed.endsWith('.MD'))
|
|
159
|
+
trimmed = trimmed.slice(0, -3);
|
|
160
|
+
return trimmed.length === decoded.length ? stripMdExt(decoded) : trimmed;
|
|
161
|
+
}
|
|
162
|
+
/** Drop a trailing `.md`, ignoring ASCII case. */
|
|
163
|
+
function stripMdExt(s) {
|
|
164
|
+
return /\.[mM][dD]$/.test(s) ? s.slice(0, -3) : s;
|
|
165
|
+
}
|
|
166
|
+
const utf8Decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
167
|
+
/**
|
|
168
|
+
* Percent-decode a URL over its UTF-8 bytes. Bytes that do not decode to UTF-8
|
|
169
|
+
* leave the URL as written, rather than becoming U+FFFD.
|
|
170
|
+
*/
|
|
171
|
+
function percentDecode(s) {
|
|
172
|
+
if (!s.includes('%'))
|
|
173
|
+
return s;
|
|
174
|
+
const bytes = new TextEncoder().encode(s);
|
|
175
|
+
const out = [];
|
|
176
|
+
let i = 0;
|
|
177
|
+
while (i < bytes.length) {
|
|
178
|
+
if (bytes[i] === 0x25 && i + 2 < bytes.length) {
|
|
179
|
+
const value = hexByte(bytes[i + 1], bytes[i + 2]);
|
|
180
|
+
if (value !== null) {
|
|
181
|
+
out.push(value);
|
|
182
|
+
i += 3;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
out.push(bytes[i]);
|
|
187
|
+
i++;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
return utf8Decoder.decode(Uint8Array.from(out));
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return s;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Two bytes read as Rust's `u8::from_str_radix(_, 16)` reads them: two hex
|
|
198
|
+
* digits, or a `+` sign and one hex digit.
|
|
199
|
+
*/
|
|
200
|
+
function hexByte(a, b) {
|
|
201
|
+
const low = hexDigit(b);
|
|
202
|
+
if (low === null)
|
|
203
|
+
return null;
|
|
204
|
+
if (a === 0x2b)
|
|
205
|
+
return low;
|
|
206
|
+
const high = hexDigit(a);
|
|
207
|
+
return high === null ? null : high * 16 + low;
|
|
208
|
+
}
|
|
209
|
+
function hexDigit(byte) {
|
|
210
|
+
if (byte >= 0x30 && byte <= 0x39)
|
|
211
|
+
return byte - 0x30;
|
|
212
|
+
if (byte >= 0x41 && byte <= 0x46)
|
|
213
|
+
return byte - 0x37;
|
|
214
|
+
if (byte >= 0x61 && byte <= 0x66)
|
|
215
|
+
return byte - 0x57;
|
|
216
|
+
return null;
|
|
217
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@osdlc/markdown-link",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Wikilink parsing that follows documented link-resolution conventions: every [[link]], ![[embed]] and relative markdown link in a note body, with spans, and the code lexer that keeps links in code out. The TypeScript twin of the parsing half of the Rust markdown-link crate, held to it by a shared case file. No dependencies.",
|
|
5
|
+
"license": "MIT OR Apache-2.0",
|
|
6
|
+
"author": "sksizer",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/osdlc/osdlc.git",
|
|
10
|
+
"directory": "packages/ts/markdown-link"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/osdlc/osdlc/tree/main/packages/ts/markdown-link#readme",
|
|
13
|
+
"bugs": "https://github.com/osdlc/osdlc/issues",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"markdown",
|
|
16
|
+
"wikilink",
|
|
17
|
+
"obsidian",
|
|
18
|
+
"links",
|
|
19
|
+
"embed",
|
|
20
|
+
"parser",
|
|
21
|
+
"typescript"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist/",
|
|
39
|
+
"README.md",
|
|
40
|
+
"CHANGELOG.md",
|
|
41
|
+
"LICENSE-MIT",
|
|
42
|
+
"LICENSE-APACHE",
|
|
43
|
+
"SECURITY.md",
|
|
44
|
+
"CONTRIBUTING.md"
|
|
45
|
+
],
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^26.1.0",
|
|
48
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
49
|
+
"oxlint": "^1.65.0",
|
|
50
|
+
"prettier": "3.9.6",
|
|
51
|
+
"typescript": "~6.0.3",
|
|
52
|
+
"vitest": "^5.0.5",
|
|
53
|
+
"@osdlc/prettier-config": "^0.3.1",
|
|
54
|
+
"@osdlc/tsconfig": "^0.4.1"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsc -p tsconfig.build.json",
|
|
58
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"coverage": "vitest run --coverage",
|
|
61
|
+
"smoke": "node tests/smoke.mjs",
|
|
62
|
+
"format": "prettier --write .",
|
|
63
|
+
"format:check": "prettier --check .",
|
|
64
|
+
"lint": "oxlint .",
|
|
65
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.build.json --noEmit"
|
|
66
|
+
}
|
|
6
67
|
}
|