diff --git a/dist/defaults.js b/dist/defaults.js
index 3679c67..868330e 100644
--- a/dist/defaults.js
+++ b/dist/defaults.js
@@ -42,6 +42,7 @@ exports.defaultTags = new tags.TagCollection([
new tags.Strikethrough(),
new tags.Code(),
new tags.Anchor(),
+ new tags.Image(),
new tags.HardBreak(),
new tags.FencedCode(),
new tags.HorizontalRule(),
diff --git a/dist/tags.d.ts b/dist/tags.d.ts
deleted file mode 100644
index c3b97e6..0000000
--- a/dist/tags.d.ts
+++ /dev/null
@@ -1,232 +0,0 @@
-import type { Token, TokenRole, TagType, TagCollectionType } from './types';
-/**
- * A single item in a parsed list, with optional nested sublist HTML.
- */
-export interface ListItem {
- text: string;
- sub: string;
-}
-/**
- * Result of parsing a list block: the generated HTML and the line
- * index where the list ends (so the caller can advance past it).
- */
-export interface ListResult {
- html: string;
- end: number;
-}
-export declare function childNodesToMarkdown(element: HTMLElement, callback: Function): string;
-export declare class Tag implements TagType {
- element: string[];
- opener: string[];
- closer: string;
- pattern: RegExp | null;
- precedence: number;
- isBlock: boolean;
- showDelimitersInEditorNodes: boolean;
- tokenize(role: TokenRole, sequence: string, source: string): Token | null;
- toHTML(token: Token, callback: Function): string;
- toMarkdown(element: HTMLElement, callback: Function): string;
- delimNodes(): [Node, Node];
- toEditorNode(token: Token, callback: Function): Node;
-}
-export declare class TagCollection implements TagCollectionType {
- tags: Record hello\nworld hello\nworld.
- *
- * converter.toHTML('```js\nlet x = 1;\n```')
- * //
- */
-export declare class FencedCode extends Tag {
- opener: string[];
- closer: string;
- pattern: RegExp;
- isBlock: boolean;
- element: string[];
- template: string;
- replaceSelection: boolean;
- toHTML(token: Token, callback: Function): string;
- toMarkdown(element: HTMLElement, callback: Function): string;
- toEditorNode(token: Token, callback: Function): Node;
-}
-/**
- * Three or more *, -, or _ on a line become let x = 1;
.
- *
- * converter.toHTML('---') // '
'
- */
-export declare class HorizontalRule extends Tag {
- element: string[];
- template: string;
- opener: string[];
- isBlock: boolean;
- replaceSelection: boolean;
- pattern: RegExp;
- precedence: number;
- toHTML(token: Token, callback: Function): string;
- toMarkdown(element: HTMLElement, callback: Function): string;
- toEditorNode(token: Token, callback: Function): Node;
-}
-export declare class Heading1 extends Tag {
- element: string[];
- pattern: RegExp;
- escapePattern: RegExp;
- opener: string[];
- closer: string;
- isBlock: boolean;
- toHTML(token: Token, callback: Function): string;
- toMarkdown(element: HTMLElement, callback: Function): string;
- toEditorNode(token: Token, callback: Function): Node;
- private anchorId;
-}
-export declare class Heading2 extends Heading1 {
- element: string[];
- opener: string[];
-}
-export declare class Heading3 extends Heading1 {
- element: string[];
- opener: string[];
-}
-export declare class Heading4 extends Heading1 {
- element: string[];
- opener: string[];
-}
-export declare class Heading5 extends Heading1 {
- element: string[];
- opener: string[];
-}
-export declare class Heading6 extends Heading1 {
- element: string[];
- opener: string[];
-}
-/**
- * Lines prefixed with > become blockquotes. Consecutive > lines
- * are merged into a single blockquote.
- *
- * converter.toHTML('> hello\n> world')
- * //
- */
-export declare class Blockquote extends Tag {
- element: string[];
- opener: string[];
- closer: string;
- isBlock: boolean;
- template: string;
- pattern: RegExp;
- private childToEditorNode;
- /**
- * Split a paragraph's content into lines at
elements.
- */
- private paragraphToLines;
- toHTML(token: Token, callback: Function): string;
- toMarkdown(element: HTMLElement, callback: Function): string;
- toEditorNode(token: Token, callback: Function): Node;
-}
-/**
- * Ordered and unordered lists with arbitrary nesting. Indentation
- * determines depth; mixed list types (ul inside ol) are supported.
- *
- * converter.toHTML('- one\n- two\n 1. nested')
- */
-declare class List extends Tag {
- pattern: RegExp;
- orderedOpenerPattern: RegExp;
- isBlock: boolean;
- toHTML(token: Token, callback: Function): string;
- toMarkdown(element: HTMLElement, callback: Function): string;
- toEditorNode(token: Token, callback: Function): Node;
- private childToEditorNode;
- private countListLines;
- /**
- * Recursively parse markdown list lines into HTML, handling nested
- * sublists at arbitrary depth and mixed list types (ul/ol).
- */
- private parseBlock;
- /**
- * Convert an HTML list back to markdown. Recurses into nested
- * sublists with 2-space indentation per depth level.
- */
- private nodeToMarkdown;
- private partitionListItem;
-}
-export declare class UnorderedList extends List {
- element: string[];
- opener: string[];
- template: string;
-}
-export declare class OrderedList extends List {
- element: string[];
- opener: string[];
- template: string;
-}
-/**
- * Pipe-delimited tables with optional column alignment.
- *
- * converter.toHTML('| A | B |\n|---|---|\n| 1 | 2 |')
- */
-export declare class Table extends Tag {
- opener: string[];
- element: string[];
- template: string;
- replaceSelection: boolean;
- pattern: RegExp;
- tableSeparatorPattern: RegExp;
- isBlock: boolean;
- showDelimitersInEditorNodes: boolean;
- parseBlock(token: Token): Record
'; }
- toMarkdown() { return ' \n'; }
-}
-exports.HardBreak = HardBreak;
-/**
- * Fenced code blocks: lines between ``` delimiters become .
- *
- * converter.toHTML('```js\nlet x = 1;\n```')
- * //
- */
-class FencedCode extends Tag {
- opener = ['```'];
- closer = '';
- pattern = /(?let x = 1;(.+\n)+)\k${(0, hopdown_1.escapeHtml)(val)}`;
- }
- toMarkdown(element, callback) {
- const lang = element.className.match(/language-(\S+)/)?.[1] || '';
- const content = element?.textContent || '';
- return '\n\n' + this.opener[0] + lang + content + this.opener[0] + '\n\n';
- }
- toEditorNode(token, callback) {
- const div = document.createElement('div');
- div.innerHTML = this.toHTML(token, callback);
- const [opener, closer] = this.delimNodes();
- const el = div.firstChild;
- if (el) {
- el.insertBefore(opener, el.firstChild);
- el.appendChild(closer);
- }
- return el;
- }
-}
-exports.FencedCode = FencedCode;
-/**
- * Three or more *, -, or _ on a line become
.
- *
- * converter.toHTML('---') // '
'
- */
-class HorizontalRule extends Tag {
- element = ['HR'];
- template = '***';
- opener = ['***', '___', '---'];
- isBlock = true;
- replaceSelection = false;
- pattern = /^(?
- */
-class Blockquote extends Tag {
- element = ['BLOCKQUOTE'];
- opener = ['> '];
- closer = '';
- isBlock = true;
- template = '> Quote\n> continues here';
- pattern = /(?
elements.
- */
- paragraphToLines(paragraph, callback) {
- const lines = [];
- let currentLine = '';
- for (const child of Array.from(paragraph.childNodes)) {
- if (child.nodeType === 1 && child.tagName === 'BR') {
- lines.push(currentLine);
- currentLine = '';
- }
- else {
- currentLine += callback(child);
- }
- }
- if (currentLine.trim()) {
- lines.push(currentLine);
- }
- return lines;
- }
- toHTML(token, callback) {
- var html = `<${this.element[0].toLowerCase()}>`;
- var inner = [];
- var pFlag = false;
- for (var line of token.value.split('\n')) {
- var text = line.replace(/^>\s*/, '');
- if (text) {
- html += callback(text) + "
";
- continue;
- }
- html += pFlag ? "
";
- pFlag = !pFlag;
- }
- html = html.replace(/\
$/g, pFlag ? '
inside the blockquote is a paragraph group. - // Within a
, bi a b c i* b
elements create line breaks.
- // Separate paragraphs get a blank > line between them.
- const paragraphs = [];
- for (const child of Array.from(element.childNodes)) {
- const el = child;
- if (child.nodeType === 1 && el.tagName === 'P') {
- const lines = this.paragraphToLines(el, callback);
- paragraphs.push(this.opener[0]);
- paragraphs.push(lines.map(line => '> ' + line).join('\n'));
- }
- else {
- const text = callback(child).trim();
- if (text) {
- paragraphs.push('> ' + text);
- }
- }
- }
- return '\n\n' + paragraphs.join('\n') + '\n\n';
- }
- toEditorNode(token, callback) {
- const [opener, closer] = this.delimNodes();
- opener.textContent = opener.textContent.replaceAll(' ', '');
- const bq = document.createElement('blockquote');
- for (const line of (token.value).split('\n')) {
- if (line === '') {
- continue;
- }
- const inner = callback(line.replace(/^>\s?/, ''));
- const children = this.childToEditorNode(inner, callback);
- const p = document.createElement('P');
- for (const c of children) {
- p.innerHTML += c.innerHTML;
- }
- p.insertBefore(opener.cloneNode(true), p.firstChild);
- bq.appendChild(p);
- }
- return bq;
- }
-}
-exports.Blockquote = Blockquote;
-/**
- * Ordered and unordered lists with arbitrary nesting. Indentation
- * determines depth; mixed list types (ul inside ol) are supported.
- *
- * converter.toHTML('- one\n- two\n 1. nested')
- */
-class List extends Tag {
- pattern = /(?'
- + block.headers.map((text, column) => cell('th', text, column)).join('')
- + ' ';
- const body = block.rows.map((row) => '' + row.map((text, column) => cell('td', text, column)).join('') + ' ').join('');
- return `${head}${body}
`;
- }
- toMarkdown(element, callback) {
- const rows = Array.from(element.querySelectorAll('tr'));
- if (!rows.length) {
- return '';
- }
- const headers = Array.from(rows[0].querySelectorAll('th,td'))
- .map(cell => callback(cell).trim());
- const separator = Array.from(element.querySelectorAll('th')).map(h => {
- return h.className == 'align-center' ? ':---:' :
- h.className == 'align-right' ? '---:' :
- '---';
- });
- const output = [
- '| ' + headers.join(' | ') + ' |',
- '| ' + separator.join(' | ') + ' |',
- ];
- for (const row of rows.slice(1)) {
- const cells = Array.from(row.querySelectorAll('td,th'))
- .map(cell => callback(cell).trim());
- output.push('| ' + cells.join(' | ') + ' |');
- }
- return '\n\n' + output.join('\n') + '\n\n';
- }
- parseRow(line) {
- return line
- .replace(/^\|/, '')
- .replace(/\|$/, '')
- .split('|')
- .map(cell => cell.trim());
- }
- parseAligns(line) {
- const centerAlign = /^:-+:$/;
- const rightAlign = /^-+:$/;
- return this.parseRow(line).map(cell => {
- if (centerAlign.test(cell)) {
- return 'center';
- }
- if (rightAlign.test(cell)) {
- return 'right';
- }
- return null;
- });
- }
-}
-exports.Table = Table;
diff --git a/dist/tags/base.d.ts b/dist/tags/base.d.ts
index 53144e9..d7d9b5a 100644
--- a/dist/tags/base.d.ts
+++ b/dist/tags/base.d.ts
@@ -3,6 +3,7 @@ export declare class Tag implements TagType {
element: string[];
opener: string[];
closer: string;
+ aliases: string[];
pattern: RegExp | null;
precedence: number;
isBlock: boolean;
diff --git a/dist/tags/base.js b/dist/tags/base.js
index e673f0d..4864ce7 100644
--- a/dist/tags/base.js
+++ b/dist/tags/base.js
@@ -5,6 +5,7 @@ class Tag {
element = [''];
opener = [''];
closer = '';
+ aliases = [];
pattern = null;
precedence = 50;
isBlock = false;
@@ -70,7 +71,6 @@ class Tag {
return [opener, closer];
}
toEditorNode(token, callback) {
- console.log(token);
if (['open', 'close', 'block'].indexOf(token.role) == -1) {
return document.createTextNode(token.value);
}
@@ -124,7 +124,7 @@ class TagCollection {
getByElementName(element) {
const el = element.toUpperCase();
return this.ordered.filter(t => {
- return t.element.includes(el);
+ return [...t.element, ...t.aliases ?? []].includes(el);
})[0] || undefined;
}
}
diff --git a/dist/tags/index.d.ts b/dist/tags/index.d.ts
index 2ceccd4..18fcb45 100644
--- a/dist/tags/index.d.ts
+++ b/dist/tags/index.d.ts
@@ -1,10 +1,11 @@
export * from "./base";
export * from "./anchor";
-export * from "./blockquote";
export * from "./fenced";
export * from "./hardbreak";
export * from "./heading";
export * from "./hr";
+export * from "./image";
+export * from "./blockquote";
export * from "./inline";
export * from "./list";
export * from "./table";
diff --git a/dist/tags/index.js b/dist/tags/index.js
index 96d7a52..ba08955 100644
--- a/dist/tags/index.js
+++ b/dist/tags/index.js
@@ -16,11 +16,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./base"), exports);
__exportStar(require("./anchor"), exports);
-__exportStar(require("./blockquote"), exports);
__exportStar(require("./fenced"), exports);
__exportStar(require("./hardbreak"), exports);
__exportStar(require("./heading"), exports);
__exportStar(require("./hr"), exports);
+__exportStar(require("./image"), exports);
+__exportStar(require("./blockquote"), exports);
__exportStar(require("./inline"), exports);
__exportStar(require("./list"), exports);
__exportStar(require("./table"), exports);
diff --git a/dist/tags/inline.d.ts b/dist/tags/inline.d.ts
index 9b35321..bb87c04 100644
--- a/dist/tags/inline.d.ts
+++ b/dist/tags/inline.d.ts
@@ -1,11 +1,13 @@
import { Tag } from "./base";
export declare class Bold extends Tag {
element: string[];
+ aliases: string[];
opener: string[];
precedence: number;
}
export declare class Italic extends Tag {
element: string[];
+ aliases: string[];
opener: string[];
precedence: number;
}
@@ -16,6 +18,7 @@ export declare class BoldItalic extends Tag {
}
export declare class Strikethrough extends Tag {
element: string[];
+ aliases: string[];
opener: string[];
}
export declare class Code extends Tag {
diff --git a/dist/tags/inline.js b/dist/tags/inline.js
index 9d094ce..8d04c94 100644
--- a/dist/tags/inline.js
+++ b/dist/tags/inline.js
@@ -3,13 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.Code = exports.Strikethrough = exports.BoldItalic = exports.Italic = exports.Bold = void 0;
const base_1 = require("./base");
class Bold extends base_1.Tag {
- element = ['STRONG', 'B'];
+ element = ['STRONG'];
+ aliases = ['B'];
opener = ['**', '__'];
precedence = 40;
}
exports.Bold = Bold;
class Italic extends base_1.Tag {
- element = ['EM', 'I'];
+ element = ['EM'];
+ aliases = ['I'];
opener = ['*', '_'];
precedence = 30;
}
@@ -21,7 +23,8 @@ class BoldItalic extends base_1.Tag {
}
exports.BoldItalic = BoldItalic;
class Strikethrough extends base_1.Tag {
- element = ['DEL', 'S', 'STRIKE'];
+ element = ['DEL'];
+ aliases = ['S', 'STRIKE'];
opener = ['~~'];
}
exports.Strikethrough = Strikethrough;
diff --git a/dist/types.d.ts b/dist/types.d.ts
index 84a99e2..18b5648 100644
--- a/dist/types.d.ts
+++ b/dist/types.d.ts
@@ -11,6 +11,7 @@ export interface Token {
}
export interface TagType {
element: string[];
+ aliases: string[];
opener: string[];
closer: string | null;
precedence: number;
diff --git a/src/ts/defaults.ts b/src/ts/defaults.ts
index 09eb32f..c16bb4a 100644
--- a/src/ts/defaults.ts
+++ b/src/ts/defaults.ts
@@ -8,6 +8,7 @@ export const defaultTags = new tags.TagCollection([
new tags.Strikethrough(),
new tags.Code(),
new tags.Anchor(),
+ new tags.Image(),
new tags.HardBreak(),
new tags.FencedCode(),
new tags.HorizontalRule(),
diff --git a/src/ts/tags/base.ts b/src/ts/tags/base.ts
index ff03842..819b723 100644
--- a/src/ts/tags/base.ts
+++ b/src/ts/tags/base.ts
@@ -4,6 +4,7 @@ export class Tag implements TagType {
element: string[] = [''];
opener: string[] = [''];
closer: string = '';
+ aliases: string[] = [];
pattern: RegExp | null = null;
precedence: number = 50;
isBlock: boolean = false;
@@ -74,7 +75,6 @@ export class Tag implements TagType {
}
toEditorNode(token: Token, callback: Function): Node {
- console.log(token);
if (['open', 'close', 'block'].indexOf(token.role) == -1) {
return document.createTextNode(token.value);
}
@@ -132,7 +132,7 @@ export class TagCollection implements TagCollectionType {
getByElementName(element: string): TagType | undefined {
const el = element.toUpperCase();
return this.ordered.filter(t => {
- return t.element.includes(el);
+ return [...t.element, ...t.aliases ?? []].includes(el);
})[0] || undefined;
}
}
diff --git a/src/ts/tags/index.ts b/src/ts/tags/index.ts
index 2ceccd4..18fcb45 100644
--- a/src/ts/tags/index.ts
+++ b/src/ts/tags/index.ts
@@ -1,10 +1,11 @@
export * from "./base";
export * from "./anchor";
-export * from "./blockquote";
export * from "./fenced";
export * from "./hardbreak";
export * from "./heading";
export * from "./hr";
+export * from "./image";
+export * from "./blockquote";
export * from "./inline";
export * from "./list";
export * from "./table";
diff --git a/src/ts/tags/inline.ts b/src/ts/tags/inline.ts
index ce56ff5..f02cf36 100644
--- a/src/ts/tags/inline.ts
+++ b/src/ts/tags/inline.ts
@@ -1,13 +1,15 @@
import { Tag } from "./base";
export class Bold extends Tag {
- element = ['STRONG', 'B'];
+ element = ['STRONG'];
+ aliases = ['B'];
opener = ['**', '__'];
precedence = 40;
}
export class Italic extends Tag {
- element = ['EM', 'I'];
+ element = ['EM'];
+ aliases = ['I'];
opener = ['*', '_'];
precedence = 30;
}
@@ -19,7 +21,8 @@ export class BoldItalic extends Tag {
}
export class Strikethrough extends Tag {
- element = ['DEL', 'S', 'STRIKE'];
+ element = ['DEL'];
+ aliases = ['S', 'STRIKE'];
opener = ['~~'];
}
diff --git a/src/ts/types.ts b/src/ts/types.ts
index c769f87..d8b7e0d 100644
--- a/src/ts/types.ts
+++ b/src/ts/types.ts
@@ -11,6 +11,7 @@ export interface Token {
export interface TagType {
element: string[];
+ aliases: string[];
opener: string[];
closer: string | null;
precedence: number;
diff --git a/test/hopdown.test.ts b/test/hopdown.test.ts
index 0e4c5ad..a690eb7 100644
--- a/test/hopdown.test.ts
+++ b/test/hopdown.test.ts
@@ -24,6 +24,7 @@ suite('Markdown to HTML Round-Trips', () => {
assert('inline code', '`code`', 'code
da b');
+ assertInline('image', '', '
');
+ });
+
});