marked-abc 0.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/LICENSE +21 -0
- package/README.md +40 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.esm.js +204 -0
- package/lib/index.esm.js.map +7 -0
- package/lib/index.umd.js +207 -0
- package/lib/index.umd.js.map +7 -0
- package/package.json +64 -0
- package/src/index.ts +120 -0
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "marked-abc",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "marked extension template",
|
|
5
|
+
"main": "./lib/index.esm.js",
|
|
6
|
+
"module": "./lib/index.esm.js",
|
|
7
|
+
"browser": "./lib/index.umd.js",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"marked",
|
|
11
|
+
"extension"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
"lib/",
|
|
15
|
+
"src/"
|
|
16
|
+
],
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"typescript": "./src/index.ts",
|
|
20
|
+
"types": "./lib/index.d.ts",
|
|
21
|
+
"default": "./lib/index.esm.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "npm run build:esbuild && npm run build:types",
|
|
26
|
+
"build:esbuild": "node esbuild.config.js",
|
|
27
|
+
"build:types": "tsc && dts-bundle-generator --export-referenced-types --project tsconfig.json -o lib/index.d.ts src/index.ts",
|
|
28
|
+
"format": "eslint --fix",
|
|
29
|
+
"lint": "eslint",
|
|
30
|
+
"test": "npm run build:esbuild && node --experimental-transform-types ./spec/test.config.js",
|
|
31
|
+
"test:cover": "npm run build:esbuild && node --experimental-transform-types --experimental-test-coverage ./spec/test.config.js -- --cover",
|
|
32
|
+
"test:only": "npm run build:esbuild && node --experimental-transform-types ./spec/test.config.js -- --only",
|
|
33
|
+
"test:types": "npm run build:types && tsc --project tsconfig-test-types.json && attw -P --entrypoints . --profile esm-only",
|
|
34
|
+
"test:update": "npm run build:esbuild && node --experimental-transform-types --test-update-snapshots ./spec/test.config.js"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/MaddyGuthridge/marked-abc.git"
|
|
39
|
+
},
|
|
40
|
+
"author": "Maddy Guthridge <hello@maddyguthridge.com> (https://maddyguthridge.com)",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/MaddyGuthridge/marked-abc/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/MaddyGuthridge/marked-abc#readme",
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"marked": ">=17 <18"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@arethetypeswrong/cli": "^0.18.2",
|
|
51
|
+
"@markedjs/eslint-config": "^1.0.14",
|
|
52
|
+
"@types/dedent": "^0.7.2",
|
|
53
|
+
"dts-bundle-generator": "^9.5.1",
|
|
54
|
+
"esbuild": "^0.27.3",
|
|
55
|
+
"esbuild-plugin-umd-wrapper": "^3.0.0",
|
|
56
|
+
"eslint": "^10.0.2",
|
|
57
|
+
"marked": "^17.0.3",
|
|
58
|
+
"typescript": "^5.9.3"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"abcjs": "^6.6.2",
|
|
62
|
+
"dedent": "^1.7.1"
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { type MarkedExtension } from 'marked';
|
|
2
|
+
import * as abcjs from 'abcjs';
|
|
3
|
+
import dedent from 'dedent';
|
|
4
|
+
|
|
5
|
+
let scoreCounter = 0;
|
|
6
|
+
|
|
7
|
+
// Source - https://stackoverflow.com/a/31638198/6335363
|
|
8
|
+
// Posted by Dave Brown
|
|
9
|
+
// Retrieved 2026-02-27, License - CC BY-SA 3.0
|
|
10
|
+
/** Escape string for embedding in HTML */
|
|
11
|
+
function escape(r: string) {
|
|
12
|
+
return r.replace(/[\x26\x0A\<>'"]/g, r => '&#' + r.charCodeAt(0) + ';');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Requires browser to test
|
|
16
|
+
/* node:coverage disable */
|
|
17
|
+
// Source - https://stackoverflow.com/a/61511955/6335363
|
|
18
|
+
// Posted by Yong Wang, modified by community. See post 'Timeline' for change history
|
|
19
|
+
// Retrieved 2026-02-27, License - CC BY-SA 4.0
|
|
20
|
+
/**
|
|
21
|
+
* Wait for the given element selector to match, then return a reference to it.
|
|
22
|
+
*
|
|
23
|
+
* Requires `document` to exist, so will not work outside of a browser context.
|
|
24
|
+
*/
|
|
25
|
+
function waitForElement(selector: string): Promise<Element> {
|
|
26
|
+
return new Promise(resolve => {
|
|
27
|
+
const match = document.querySelector(selector);
|
|
28
|
+
if (match) {
|
|
29
|
+
return resolve(match);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const observer = new MutationObserver(mutations => {
|
|
33
|
+
const match = document.querySelector(selector);
|
|
34
|
+
if (match) {
|
|
35
|
+
observer.disconnect();
|
|
36
|
+
resolve(match);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// If you get "parameter 1 is not of type 'Node'" error, see
|
|
41
|
+
// https://stackoverflow.com/a/77855838/492336
|
|
42
|
+
observer.observe(document.body, {
|
|
43
|
+
childList: true,
|
|
44
|
+
subtree: true,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/* node:coverage enable */
|
|
49
|
+
|
|
50
|
+
/** Indent the given string with the given string for each line */
|
|
51
|
+
function indent(s: string, indent: string) {
|
|
52
|
+
return s.split('\n').map(line => indent + line).join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Match `<score lang="ABC">` */
|
|
56
|
+
const SCORE_OPEN = /<score\s+lang="(ABC|abc)"\s*>/;
|
|
57
|
+
|
|
58
|
+
/** Match `<score lang="ABC">`, only at beginning of string */
|
|
59
|
+
const SCORE_OPEN_BEGIN = new RegExp('^' + SCORE_OPEN.source);
|
|
60
|
+
|
|
61
|
+
/** Match `</score>` */
|
|
62
|
+
const SCORE_CLOSE = /<\/score>/;
|
|
63
|
+
|
|
64
|
+
export type MarkedAbcOptions = {
|
|
65
|
+
/** Options to pass to ABCjs */
|
|
66
|
+
abcOptions?: abcjs.AbcVisualParams,
|
|
67
|
+
/** Sanitizer function */
|
|
68
|
+
sanitizer?: (text: string) => string,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default function(options: MarkedAbcOptions = {}): MarkedExtension {
|
|
72
|
+
return {
|
|
73
|
+
extensions: [{
|
|
74
|
+
name: 'abcScore',
|
|
75
|
+
level: 'block',
|
|
76
|
+
// Marked never calls this and I do not know why
|
|
77
|
+
/* node:coverage disable */
|
|
78
|
+
start(src) { return SCORE_OPEN.exec(src)?.index; },
|
|
79
|
+
/* node:coverage enable */
|
|
80
|
+
tokenizer(src) {
|
|
81
|
+
if (!SCORE_OPEN_BEGIN.exec(src)) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const endMatch = SCORE_CLOSE.exec(src);
|
|
85
|
+
if (endMatch === null) {
|
|
86
|
+
throw Error('Unterminated <score lang="ABC"> block');
|
|
87
|
+
}
|
|
88
|
+
const end = endMatch.index + endMatch[0].length;
|
|
89
|
+
const raw = src.slice(0, end);
|
|
90
|
+
const abc = raw.replace(SCORE_OPEN_BEGIN, '').replace(SCORE_CLOSE, '');
|
|
91
|
+
return {
|
|
92
|
+
type: 'abcScore',
|
|
93
|
+
raw,
|
|
94
|
+
abc,
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
renderer(token) {
|
|
98
|
+
// Increment score counter for each score rendered to ensure ID uniqueness
|
|
99
|
+
const eleId = `abc-score-${++scoreCounter}`;
|
|
100
|
+
const sanitize = options.sanitizer ?? escape;
|
|
101
|
+
|
|
102
|
+
// Unreachable during tests due to missing DOM
|
|
103
|
+
// JS moment: the coverage ignore comment is not included in its own ignore meaning I need
|
|
104
|
+
// to place it before this if statement, meaning the coverage of the if statement itself is
|
|
105
|
+
// not measured.
|
|
106
|
+
/* node:coverage ignore next 6 */
|
|
107
|
+
if ('document' in globalThis) {
|
|
108
|
+
waitForElement(`#${eleId}`).then((ele: Element) => {
|
|
109
|
+
abcjs.renderAbc(ele, token.abc, options.abcOptions);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return dedent`<div class="abc-score" id="${eleId}">
|
|
114
|
+
${indent(sanitize(token.abc), ' ')}
|
|
115
|
+
</div>
|
|
116
|
+
`;
|
|
117
|
+
},
|
|
118
|
+
}],
|
|
119
|
+
};
|
|
120
|
+
}
|