Single ribbit namespace instead of window globals
Use esbuild --global-name=ribbit to expose a single namespace.
This commit is contained in:
parent
f76ebbf2e5
commit
df49ce7545
|
|
@ -11,8 +11,8 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "mkdir -p dist/ribbit && npm run build:check && npm run build:js && npm run build:min && npm run build:css",
|
"build": "mkdir -p dist/ribbit && npm run build:check && npm run build:js && npm run build:min && npm run build:css",
|
||||||
"build:check": "tsc --noEmit",
|
"build:check": "tsc --noEmit",
|
||||||
"build:js": "esbuild src/ts/ribbit-editor.ts --bundle --format=iife --sourcemap --outfile=dist/ribbit/ribbit.js",
|
"build:js": "esbuild src/ts/ribbit-editor.ts --bundle --format=iife --global-name=ribbit --sourcemap --outfile=dist/ribbit/ribbit.js",
|
||||||
"build:min": "esbuild src/ts/ribbit-editor.ts --bundle --format=iife --minify --outfile=dist/ribbit/ribbit.min.js",
|
"build:min": "esbuild src/ts/ribbit-editor.ts --bundle --format=iife --global-name=ribbit --minify --outfile=dist/ribbit/ribbit.min.js",
|
||||||
"build:css": "cp src/static/ribbit-core.css dist/ribbit/ && cp -r src/static/themes dist/ribbit/",
|
"build:css": "cp src/static/ribbit-core.css dist/ribbit/ && cp -r src/static/themes dist/ribbit/",
|
||||||
"test": "npm run build && node test/test_hopdown.js"
|
"test": "npm run build && node test/test_hopdown.js"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -102,16 +102,12 @@ export class RibbitEditor extends Ribbit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach public API to window for <script> tag usage.
|
// Public API — accessed as ribbit.Editor, ribbit.HopDown, etc.
|
||||||
(window as any).HopDown = HopDown;
|
export { RibbitEditor as Editor };
|
||||||
(window as any).inlineTag = inlineTag;
|
export { Ribbit as Viewer };
|
||||||
(window as any).defaultTags = defaultTags;
|
export { RibbitPlugin as Plugin };
|
||||||
(window as any).defaultBlockTags = defaultBlockTags;
|
export { HopDown };
|
||||||
(window as any).defaultInlineTags = defaultInlineTags;
|
export { inlineTag };
|
||||||
(window as any).defaultTheme = defaultTheme;
|
export { defaultTags, defaultBlockTags, defaultInlineTags };
|
||||||
(window as any).Ribbit = Ribbit;
|
export { defaultTheme };
|
||||||
(window as any).RibbitEditor = RibbitEditor;
|
export { camelCase, decodeHtmlEntities, encodeHtmlEntities };
|
||||||
(window as any).RibbitPlugin = RibbitPlugin;
|
|
||||||
(window as any).camelCase = camelCase;
|
|
||||||
(window as any).decodeHtmlEntities = decodeHtmlEntities;
|
|
||||||
(window as any).encodeHtmlEntities = encodeHtmlEntities;
|
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,12 @@ global.document = dom.window.document;
|
||||||
global.HTMLElement = dom.window.HTMLElement;
|
global.HTMLElement = dom.window.HTMLElement;
|
||||||
global.Node = dom.window.Node;
|
global.Node = dom.window.Node;
|
||||||
|
|
||||||
// Load the compiled bundle (attaches globals to window)
|
// Load the compiled bundle — esbuild IIFE assigns to var ribbit,
|
||||||
|
// but eval in jsdom doesn't attach vars to window, so we patch it.
|
||||||
const bundle = fs.readFileSync(path.join(__dirname, '..', 'dist', 'ribbit', 'ribbit.js'), 'utf8');
|
const bundle = fs.readFileSync(path.join(__dirname, '..', 'dist', 'ribbit', 'ribbit.js'), 'utf8');
|
||||||
dom.window.eval(bundle);
|
dom.window.eval(bundle.replace('var ribbit =', 'window.ribbit ='));
|
||||||
|
|
||||||
const hopdown = new dom.window.HopDown();
|
const hopdown = new dom.window.ribbit.HopDown();
|
||||||
const H = hopdown.toHTML.bind(hopdown);
|
const H = hopdown.toHTML.bind(hopdown);
|
||||||
const M = hopdown.toMarkdown.bind(hopdown);
|
const M = hopdown.toMarkdown.bind(hopdown);
|
||||||
function rt(md) { return M(H(md)); }
|
function rt(md) { return M(H(md)); }
|
||||||
|
|
@ -287,22 +288,22 @@ eq('td link>bold rt', rt('| h |\n|---|\n| [**t**](u) |'), '| h |\n| --- |\n| [**
|
||||||
eq('multi-cell rt', rt('| **a** | *b* |\n|---|---|\n| `c` | [d](e) |'), '| **a** | *b* |\n| --- | --- |\n| `c` | [d](e) |');
|
eq('multi-cell rt', rt('| **a** | *b* |\n|---|---|\n| `c` | [d](e) |'), '| **a** | *b* |\n| --- | --- |\n| `c` | [d](e) |');
|
||||||
|
|
||||||
// ── 18. inlineTag() factory ─────────────────────────────
|
// ── 18. inlineTag() factory ─────────────────────────────
|
||||||
const strikethrough = dom.window.inlineTag({
|
const strikethrough = dom.window.ribbit.inlineTag({
|
||||||
name: 'strikethrough',
|
name: 'strikethrough',
|
||||||
delimiter: '~~',
|
delimiter: '~~',
|
||||||
htmlTag: 'del',
|
htmlTag: 'del',
|
||||||
aliases: 'S,STRIKE',
|
aliases: 'S,STRIKE',
|
||||||
precedence: 45,
|
precedence: 45,
|
||||||
});
|
});
|
||||||
const customInline = new dom.window.HopDown({
|
const customInline = new dom.window.ribbit.HopDown({
|
||||||
tags: { ...dom.window.defaultTags, 'DEL,S,STRIKE': strikethrough },
|
tags: { ...dom.window.ribbit.defaultTags, 'DEL,S,STRIKE': strikethrough },
|
||||||
});
|
});
|
||||||
eq('factory: md→html', customInline.toHTML('~~struck~~'), '<p><del>struck</del></p>');
|
eq('factory: md→html', customInline.toHTML('~~struck~~'), '<p><del>struck</del></p>');
|
||||||
has('factory: html→md', customInline.toMarkdown('<p><del>struck</del></p>'), '~~struck~~');
|
has('factory: html→md', customInline.toMarkdown('<p><del>struck</del></p>'), '~~struck~~');
|
||||||
eq('factory: round-trip', customInline.toMarkdown(customInline.toHTML('~~struck~~')), '~~struck~~');
|
eq('factory: round-trip', customInline.toMarkdown(customInline.toHTML('~~struck~~')), '~~struck~~');
|
||||||
has('factory: mixed with bold', customInline.toHTML('**bold** and ~~struck~~'), '<del>struck</del>');
|
has('factory: mixed with bold', customInline.toHTML('**bold** and ~~struck~~'), '<del>struck</del>');
|
||||||
has('factory: mixed with bold', customInline.toHTML('**bold** and ~~struck~~'), '<strong>bold</strong>');
|
has('factory: mixed with bold', customInline.toHTML('**bold** and ~~struck~~'), '<strong>bold</strong>');
|
||||||
eq('factory: non-recursive', dom.window.inlineTag({
|
eq('factory: non-recursive', dom.window.ribbit.inlineTag({
|
||||||
name: 'test',
|
name: 'test',
|
||||||
delimiter: '%%',
|
delimiter: '%%',
|
||||||
htmlTag: 'mark',
|
htmlTag: 'mark',
|
||||||
|
|
@ -324,8 +325,8 @@ const spoiler = {
|
||||||
selector: 'DETAILS',
|
selector: 'DETAILS',
|
||||||
toMarkdown: (element, convert) => '\n\n|||\n' + convert.children(element).trim() + '\n|||\n\n',
|
toMarkdown: (element, convert) => '\n\n|||\n' + convert.children(element).trim() + '\n|||\n\n',
|
||||||
};
|
};
|
||||||
const customBlock = new dom.window.HopDown({
|
const customBlock = new dom.window.ribbit.HopDown({
|
||||||
tags: { 'DETAILS': spoiler, ...dom.window.defaultTags },
|
tags: { 'DETAILS': spoiler, ...dom.window.ribbit.defaultTags },
|
||||||
});
|
});
|
||||||
has('custom block: md→html', customBlock.toHTML('|||\nhidden\n|||'), '<details>');
|
has('custom block: md→html', customBlock.toHTML('|||\nhidden\n|||'), '<details>');
|
||||||
has('custom block: content', customBlock.toHTML('|||\nhidden\n|||'), 'hidden');
|
has('custom block: content', customBlock.toHTML('|||\nhidden\n|||'), 'hidden');
|
||||||
|
|
@ -333,22 +334,22 @@ has('custom block: html→md', customBlock.toMarkdown('<details><summary>Spoiler
|
||||||
has('custom block: nested md', customBlock.toHTML('|||\n**bold** inside\n|||'), '<strong>bold</strong>');
|
has('custom block: nested md', customBlock.toHTML('|||\n**bold** inside\n|||'), '<strong>bold</strong>');
|
||||||
|
|
||||||
// ── 20. HopDown({ exclude }) ────────────────────────────
|
// ── 20. HopDown({ exclude }) ────────────────────────────
|
||||||
const noTables = new dom.window.HopDown({ exclude: ['table'] });
|
const noTables = new dom.window.ribbit.HopDown({ exclude: ['table'] });
|
||||||
// With table excluded, pipe lines fall through to paragraph but isBlockStart
|
// With table excluded, pipe lines fall through to paragraph but isBlockStart
|
||||||
// still detects table-like patterns, so lines are split across paragraphs.
|
// still detects table-like patterns, so lines are split across paragraphs.
|
||||||
has('exclude: table not rendered', noTables.toHTML('| a | b |\n|---|---|\n| 1 | 2 |'), '<p>');
|
has('exclude: table not rendered', noTables.toHTML('| a | b |\n|---|---|\n| 1 | 2 |'), '<p>');
|
||||||
not('exclude: no table tag', noTables.toHTML('| a | b |\n|---|---|\n| 1 | 2 |'), '<table>');
|
not('exclude: no table tag', noTables.toHTML('| a | b |\n|---|---|\n| 1 | 2 |'), '<table>');
|
||||||
has('exclude: bold still works', noTables.toHTML('**bold**'), '<strong>bold</strong>');
|
has('exclude: bold still works', noTables.toHTML('**bold**'), '<strong>bold</strong>');
|
||||||
|
|
||||||
const noCode = new dom.window.HopDown({ exclude: ['code'] });
|
const noCode = new dom.window.ribbit.HopDown({ exclude: ['code'] });
|
||||||
eq('exclude: code not processed', noCode.toHTML('`code`'), '<p>`code`</p>');
|
eq('exclude: code not processed', noCode.toHTML('`code`'), '<p>`code`</p>');
|
||||||
has('exclude: bold still works', noCode.toHTML('**bold**'), '<strong>bold</strong>');
|
has('exclude: bold still works', noCode.toHTML('**bold**'), '<strong>bold</strong>');
|
||||||
|
|
||||||
// ── 21. Collision detection: delimiter ───────────────────
|
// ── 21. Collision detection: delimiter ───────────────────
|
||||||
let threw = false;
|
let threw = false;
|
||||||
try {
|
try {
|
||||||
const bad = dom.window.inlineTag({ name: 'bad', delimiter: '*', htmlTag: 'span', precedence: 10 });
|
const bad = dom.window.ribbit.inlineTag({ name: 'bad', delimiter: '*', htmlTag: 'span', precedence: 10 });
|
||||||
new dom.window.HopDown({ tags: { ...dom.window.defaultTags, 'SPAN': bad } });
|
new dom.window.ribbit.HopDown({ tags: { ...dom.window.ribbit.defaultTags, 'SPAN': bad } });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
threw = true;
|
threw = true;
|
||||||
}
|
}
|
||||||
|
|
@ -357,8 +358,8 @@ eq('delimiter collision throws', String(threw), 'true');
|
||||||
threw = false;
|
threw = false;
|
||||||
try {
|
try {
|
||||||
// Same delimiter, higher precedence than existing — should throw
|
// Same delimiter, higher precedence than existing — should throw
|
||||||
const bad = dom.window.inlineTag({ name: 'bad', delimiter: '**', htmlTag: 'span', precedence: 60 });
|
const bad = dom.window.ribbit.inlineTag({ name: 'bad', delimiter: '**', htmlTag: 'span', precedence: 60 });
|
||||||
new dom.window.HopDown({ tags: { ...dom.window.defaultTags, 'SPAN': bad } });
|
new dom.window.ribbit.HopDown({ tags: { ...dom.window.ribbit.defaultTags, 'SPAN': bad } });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
threw = true;
|
threw = true;
|
||||||
}
|
}
|
||||||
|
|
@ -368,7 +369,7 @@ eq('duplicate delimiter collision throws', String(threw), 'true');
|
||||||
threw = false;
|
threw = false;
|
||||||
try {
|
try {
|
||||||
const dup = { name: 'dup', match: () => null, toHTML: () => '', selector: 'STRONG', toMarkdown: () => '' };
|
const dup = { name: 'dup', match: () => null, toHTML: () => '', selector: 'STRONG', toMarkdown: () => '' };
|
||||||
new dom.window.HopDown({ tags: { ...dom.window.defaultTags, 'STRONG': dup } });
|
new dom.window.ribbit.HopDown({ tags: { ...dom.window.ribbit.defaultTags, 'STRONG': dup } });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
threw = true;
|
threw = true;
|
||||||
}
|
}
|
||||||
|
|
@ -376,10 +377,10 @@ eq('selector collision throws', String(threw), 'true');
|
||||||
|
|
||||||
// ── 23. Precedence ordering ─────────────────────────────
|
// ── 23. Precedence ordering ─────────────────────────────
|
||||||
// Longer delimiter with lower precedence should win
|
// Longer delimiter with lower precedence should win
|
||||||
const tilde = dom.window.inlineTag({ name: 'tilde', delimiter: '~', htmlTag: 's', precedence: 45 });
|
const tilde = dom.window.ribbit.inlineTag({ name: 'tilde', delimiter: '~', htmlTag: 's', precedence: 45 });
|
||||||
const doubleTilde = dom.window.inlineTag({ name: 'doubleTilde', delimiter: '~~', htmlTag: 'del', precedence: 35 });
|
const doubleTilde = dom.window.ribbit.inlineTag({ name: 'doubleTilde', delimiter: '~~', htmlTag: 'del', precedence: 35 });
|
||||||
const precTest = new dom.window.HopDown({
|
const precTest = new dom.window.ribbit.HopDown({
|
||||||
tags: { ...dom.window.defaultTags, 'S': tilde, 'DEL': doubleTilde },
|
tags: { ...dom.window.ribbit.defaultTags, 'S': tilde, 'DEL': doubleTilde },
|
||||||
});
|
});
|
||||||
has('precedence: ~~ matches before ~', precTest.toHTML('~~struck~~'), '<del>struck</del>');
|
has('precedence: ~~ matches before ~', precTest.toHTML('~~struck~~'), '<del>struck</del>');
|
||||||
has('precedence: ~ still works', precTest.toHTML('~light~'), '<s>light</s>');
|
has('precedence: ~ still works', precTest.toHTML('~light~'), '<s>light</s>');
|
||||||
|
|
@ -387,9 +388,9 @@ has('precedence: ~ still works', precTest.toHTML('~light~'), '<s>light</s>');
|
||||||
// Valid: longer delimiter has lower precedence
|
// Valid: longer delimiter has lower precedence
|
||||||
threw = false;
|
threw = false;
|
||||||
try {
|
try {
|
||||||
const short = dom.window.inlineTag({ name: 'short', delimiter: '~', htmlTag: 's', precedence: 50 });
|
const short = dom.window.ribbit.inlineTag({ name: 'short', delimiter: '~', htmlTag: 's', precedence: 50 });
|
||||||
const long = dom.window.inlineTag({ name: 'long', delimiter: '~~', htmlTag: 'del', precedence: 40 });
|
const long = dom.window.ribbit.inlineTag({ name: 'long', delimiter: '~~', htmlTag: 'del', precedence: 40 });
|
||||||
new dom.window.HopDown({ tags: { ...dom.window.defaultTags, 'S': short, 'DEL': long } });
|
new dom.window.ribbit.HopDown({ tags: { ...dom.window.ribbit.defaultTags, 'S': short, 'DEL': long } });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
threw = true;
|
threw = true;
|
||||||
}
|
}
|
||||||
|
|
@ -398,9 +399,9 @@ eq('valid precedence does not throw', String(threw), 'false');
|
||||||
// Invalid: longer delimiter has higher precedence
|
// Invalid: longer delimiter has higher precedence
|
||||||
threw = false;
|
threw = false;
|
||||||
try {
|
try {
|
||||||
const short = dom.window.inlineTag({ name: 'short', delimiter: '~', htmlTag: 's', precedence: 30 });
|
const short = dom.window.ribbit.inlineTag({ name: 'short', delimiter: '~', htmlTag: 's', precedence: 30 });
|
||||||
const long = dom.window.inlineTag({ name: 'long', delimiter: '~~', htmlTag: 'del', precedence: 50 });
|
const long = dom.window.ribbit.inlineTag({ name: 'long', delimiter: '~~', htmlTag: 'del', precedence: 50 });
|
||||||
new dom.window.HopDown({ tags: { ...dom.window.defaultTags, 'S': short, 'DEL': long } });
|
new dom.window.ribbit.HopDown({ tags: { ...dom.window.ribbit.defaultTags, 'S': short, 'DEL': long } });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
threw = true;
|
threw = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user