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/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
+ }