948 lines
19 KiB
JavaScript
948 lines
19 KiB
JavaScript
class LineFeed {
|
|
originalText = undefined;
|
|
/**
|
|
* @type {string[]}
|
|
*/
|
|
feed = undefined;
|
|
/**
|
|
*
|
|
* @param {string} markdown
|
|
*/
|
|
constructor(markdown) {
|
|
this.originalText = markdown;
|
|
// TODO: Need to identify line endings
|
|
this.feed = markdown.split("\n");
|
|
}
|
|
|
|
/**
|
|
* @returns {string}
|
|
*/
|
|
peek() {
|
|
return this.feed[0] ?? undefined;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @returns {string}
|
|
*/
|
|
claim() {
|
|
const line = this.feed.shift();
|
|
if (line === undefined) throw new Error("Feed is empty");
|
|
return line;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} line
|
|
*/
|
|
push(line) {
|
|
if (line === undefined || line === null) return;
|
|
this.feed = [line, ...this.feed];
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @returns {boolean}
|
|
*/
|
|
isEmpty() {
|
|
return this.feed.length === 0;
|
|
}
|
|
}
|
|
|
|
class Symbol {
|
|
static canParse(line) {
|
|
throw new Error("Not implemented");
|
|
}
|
|
static create(lineFeed, assetDirectory) {
|
|
throw new Error("Not implemented");
|
|
}
|
|
/**
|
|
*
|
|
* @param {string} assetDirectory
|
|
* @returns {void}
|
|
*/
|
|
process(assetDirectory) {
|
|
throw new Error("Not Implemented");
|
|
}
|
|
render() {
|
|
throw new Error("Not implemented");
|
|
}
|
|
}
|
|
|
|
class Heading extends Symbol {
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
text = "";
|
|
/**
|
|
* @type {number}
|
|
*/
|
|
level = 1;
|
|
/**
|
|
*
|
|
* @param {string} line
|
|
* @returns {boolean}
|
|
*/
|
|
static canParse(line) {
|
|
return line.trim().startsWith("#");
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {LineFeed} lineFeed
|
|
* @returns {Symbol}
|
|
*/
|
|
static create(lineFeed) {
|
|
const instance = new Heading();
|
|
const line = lineFeed.claim();
|
|
instance.text = line.replaceAll("#", "").trim();
|
|
instance.level = line.split("").reduce((aggregate, current) => {
|
|
return current === "#" ? aggregate + 1 : aggregate;
|
|
}, 0);
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} assetDirectory
|
|
* @returns {void}
|
|
*/
|
|
process(assetDirectory) {
|
|
return;
|
|
}
|
|
|
|
render() {
|
|
return `<h${this.level}>${this.text}</h${this.level}>`;
|
|
}
|
|
}
|
|
|
|
class JustALineBreak extends Symbol {
|
|
/**
|
|
*
|
|
* @param {string} line
|
|
* @returns {boolean}
|
|
*/
|
|
static canParse(line) {
|
|
return line === "";
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {LineFeed} lineFeed
|
|
* @returns {Symbol}
|
|
*/
|
|
static create(lineFeed) {
|
|
const instance = new JustALineBreak();
|
|
lineFeed.claim();
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} assetDirectory
|
|
* @returns {void}
|
|
*/
|
|
process(assetDirectory) {
|
|
return;
|
|
}
|
|
|
|
render() {
|
|
throw new Error("Symbol cannot be rendered directly");
|
|
}
|
|
}
|
|
|
|
class Paragraph extends Symbol {
|
|
/**
|
|
* @type {string[]}
|
|
*/
|
|
lines = [];
|
|
/**
|
|
* @type {Symbol[]}
|
|
*/
|
|
children = [];
|
|
/**
|
|
*
|
|
* @param {string} line
|
|
* @returns {boolean}
|
|
*/
|
|
static canParse(line) {
|
|
return line === "";
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {LineFeed} lineFeed
|
|
* @returns {Symbol}
|
|
*/
|
|
static create(lineFeed) {
|
|
const instance = new Paragraph();
|
|
lineFeed.claim();
|
|
|
|
const lines = [];
|
|
let endOfParagraph = false;
|
|
while (!endOfParagraph && !lineFeed.isEmpty()) {
|
|
const line = lineFeed.peek();
|
|
if (line === "") {
|
|
endOfParagraph = true;
|
|
continue;
|
|
}
|
|
lines.push(lineFeed.claim());
|
|
}
|
|
|
|
instance.lines = lines;
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} assetDirectory
|
|
* @returns {void}
|
|
*/
|
|
process(assetDirectory) {
|
|
return;
|
|
}
|
|
|
|
render() {
|
|
return `<p>${this.children.map((c) => c.render()).join("")}</p>`;
|
|
}
|
|
}
|
|
|
|
class UnorderedListItem extends Symbol {
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
text = "";
|
|
/**
|
|
* @type {number}
|
|
*/
|
|
level = 0;
|
|
/**
|
|
*
|
|
* @param {string} line
|
|
* @returns {boolean}
|
|
*/
|
|
static canParse(line) {
|
|
return line.trim().startsWith("- ");
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {LineFeed} lineFeed
|
|
* @returns {Symbol}
|
|
*/
|
|
static create(lineFeed) {
|
|
const instance = new UnorderedListItem();
|
|
const line = lineFeed.claim();
|
|
instance.text = line.replaceAll("-", "").trim();
|
|
instance.level = getAmountOfTokenInBeginningOfFile(" ", line);
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} assetDirectory
|
|
* @returns {void}
|
|
*/
|
|
process(assetDirectory) {
|
|
return;
|
|
}
|
|
|
|
render() {
|
|
return `<li class="indent-${this.level}">${this.text} indentation level ${this.level}</li>`;
|
|
}
|
|
}
|
|
|
|
class OrderedListItem extends Symbol {
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
text = "";
|
|
/**
|
|
* @type {number}
|
|
*/
|
|
level = 0;
|
|
/**
|
|
*
|
|
* @param {string} line
|
|
* @returns {boolean}
|
|
*/
|
|
static canParse(line) {
|
|
return new RegExp(/^\d\. /).test(line.trim());
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {LineFeed} lineFeed
|
|
* @returns {Symbol}
|
|
*/
|
|
static create(lineFeed) {
|
|
const instance = new UnorderedListItem();
|
|
const line = lineFeed.claim();
|
|
instance.text = line.trim().replace(/^\d\. /, "");
|
|
instance.level = getAmountOfTokenInBeginningOfFile(" ", line);
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} assetDirectory
|
|
* @returns {void}
|
|
*/
|
|
process(assetDirectory) {
|
|
return;
|
|
}
|
|
|
|
render() {
|
|
return `<li class="indent-${this.level}">${this.text} indentation level ${this.level}</li>`;
|
|
}
|
|
}
|
|
class Link extends Symbol {
|
|
/**
|
|
* @type {RegExp}
|
|
*/
|
|
static canParseRegExp = new RegExp(/^\[.*\]\(.*\)/);
|
|
/**
|
|
* @type {RegExp}
|
|
*/
|
|
static textAndLinkRegExp = new RegExp(/\[(?<text>.*)\]\((?<link>.*)\)/);
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
text = "";
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
link = "";
|
|
/**
|
|
*
|
|
* @param {string} line
|
|
* @returns {boolean}
|
|
*/
|
|
static canParse(line) {
|
|
return Link.canParseRegExp.test(line.trim());
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {LineFeed} lineFeed
|
|
* @returns {Symbol}
|
|
*/
|
|
static create(lineFeed) {
|
|
const instance = new Link();
|
|
const line = lineFeed.claim().trim();
|
|
const [linkLine, rest] = extractTokenAndRest(Link.textAndLinkRegExp, line);
|
|
lineFeed.push(rest);
|
|
const { text, link } = Link.textAndLinkRegExp.exec(linkLine)?.groups ?? {};
|
|
instance.link = link ?? "";
|
|
instance.text = text ?? "";
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} assetDirectory
|
|
* @returns {void}
|
|
*/
|
|
process(assetDirectory) {
|
|
return;
|
|
}
|
|
|
|
render() {
|
|
return `<a href="${this.link}">${this.text}</a>`;
|
|
}
|
|
}
|
|
|
|
class ImageLink extends Symbol {
|
|
/**
|
|
* @type {RegExp}
|
|
*/
|
|
static canParseRegExp = new RegExp(/^!\[.*\]\(.*\)/);
|
|
/**
|
|
* @type {RegExp}
|
|
*/
|
|
static textAndLinkRegExp = new RegExp(/!\[(?<text>.*)\]\((?<link>.*)\)/);
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
static assetDirectoryToken = "@asset/";
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
alt = "";
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
link = "";
|
|
/**
|
|
*
|
|
* @param {string} line
|
|
* @returns {boolean}
|
|
*/
|
|
static canParse(line) {
|
|
return ImageLink.canParseRegExp.test(line.trim());
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {LineFeed} lineFeed
|
|
* @returns {Symbol}
|
|
*/
|
|
static create(lineFeed, assetDirectory) {
|
|
const instance = new ImageLink();
|
|
const line = lineFeed.claim().trim();
|
|
const [linkLine, rest] = extractTokenAndRest(
|
|
ImageLink.textAndLinkRegExp,
|
|
line
|
|
);
|
|
lineFeed.push(rest);
|
|
const { text, link } =
|
|
ImageLink.textAndLinkRegExp.exec(linkLine)?.groups ?? {};
|
|
instance.link =
|
|
link.replace(ImageLink.assetDirectoryToken, assetDirectory) ?? "";
|
|
instance.alt = text ?? "";
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} assetDirectory
|
|
* @returns {void}
|
|
*/
|
|
process(assetDirectory) {
|
|
return;
|
|
}
|
|
|
|
render() {
|
|
return `<img src="${this.link}" alt="${this.alt}"/>`;
|
|
}
|
|
}
|
|
|
|
class Italic extends Symbol {
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
text = "";
|
|
/**
|
|
*
|
|
* @param {string} line
|
|
* @returns {boolean}
|
|
*/
|
|
static canParse(line) {
|
|
return line.startsWith("_");
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {LineFeed} lineFeed
|
|
* @returns {Symbol}
|
|
*/
|
|
static create(lineFeed) {
|
|
const instance = new Italic();
|
|
const line = lineFeed.claim().trim();
|
|
const lineWithoutStartingUnderscore = line.slice(1, line.length);
|
|
const lineEndsWithUnderscore = lineWithoutStartingUnderscore.endsWith("_");
|
|
if (lineEndsWithUnderscore) {
|
|
const lineWithoutUnderscores = lineWithoutStartingUnderscore.slice(
|
|
0,
|
|
lineWithoutStartingUnderscore.length - 1
|
|
);
|
|
instance.text = lineWithoutUnderscores;
|
|
return instance;
|
|
}
|
|
const endOfItalicString = lineWithoutStartingUnderscore.indexOf("_");
|
|
const lineWithoutUnderscores = lineWithoutStartingUnderscore.slice(
|
|
0,
|
|
endOfItalicString
|
|
);
|
|
const rest = lineWithoutStartingUnderscore.slice(endOfItalicString + 1);
|
|
lineFeed.push(rest);
|
|
instance.text = lineWithoutUnderscores;
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} assetDirectory
|
|
* @returns {void}
|
|
*/
|
|
process(assetDirectory) {
|
|
return;
|
|
}
|
|
|
|
render() {
|
|
return `<i>${this.text}</i>`;
|
|
}
|
|
}
|
|
|
|
class Bold extends Symbol {
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
text = "";
|
|
/**
|
|
*
|
|
* @param {string} line
|
|
* @returns {boolean}
|
|
*/
|
|
static canParse(line) {
|
|
return line.startsWith("**");
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {LineFeed} lineFeed
|
|
* @returns {Symbol}
|
|
*/
|
|
static create(lineFeed) {
|
|
const instance = new Bold();
|
|
const line = lineFeed.claim().trim();
|
|
const lineWithoutStartingUnderscore = line.slice(2, line.length);
|
|
const lineEndsWithStars = lineWithoutStartingUnderscore.endsWith("**");
|
|
if (lineEndsWithStars) {
|
|
const lineWithoutUnderscores = lineWithoutStartingUnderscore.slice(
|
|
0,
|
|
lineWithoutStartingUnderscore.length - 2
|
|
);
|
|
instance.text = lineWithoutUnderscores;
|
|
return instance;
|
|
}
|
|
const endOfBoldString = lineWithoutStartingUnderscore.indexOf("**");
|
|
const lineWithoutUnderscores = lineWithoutStartingUnderscore.slice(
|
|
0,
|
|
endOfBoldString
|
|
);
|
|
const rest = lineWithoutStartingUnderscore.slice(endOfBoldString + 2);
|
|
lineFeed.push(rest);
|
|
instance.text = lineWithoutUnderscores;
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} assetDirectory
|
|
* @returns {void}
|
|
*/
|
|
process(assetDirectory) {
|
|
return;
|
|
}
|
|
|
|
render() {
|
|
return `<b>${this.text}</b>`;
|
|
}
|
|
}
|
|
|
|
class SingleLineCode extends Symbol {
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
text = "";
|
|
/**
|
|
*
|
|
* @param {string} line
|
|
* @returns {boolean}
|
|
*/
|
|
static canParse(line) {
|
|
const trimmedLine = line.trim();
|
|
return trimmedLine.startsWith("`");
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {LineFeed} lineFeed
|
|
* @returns {Symbol}
|
|
*/
|
|
static create(lineFeed) {
|
|
const instance = new SingleLineCode();
|
|
const line = lineFeed.claim().trim();
|
|
const lineWithoutStartingUnderscore = line.slice(1, line.length);
|
|
const lineEndsWithUnderscore = lineWithoutStartingUnderscore.endsWith("`");
|
|
if (lineEndsWithUnderscore) {
|
|
const lineWithoutUnderscores = lineWithoutStartingUnderscore.slice(
|
|
0,
|
|
lineWithoutStartingUnderscore.length - 1
|
|
);
|
|
instance.text = lineWithoutUnderscores;
|
|
return instance;
|
|
}
|
|
const endOfItalicString = lineWithoutStartingUnderscore.indexOf("`");
|
|
const lineWithoutUnderscores = lineWithoutStartingUnderscore.slice(
|
|
0,
|
|
endOfItalicString
|
|
);
|
|
const rest = lineWithoutStartingUnderscore.slice(endOfItalicString + 1);
|
|
lineFeed.push(rest);
|
|
instance.text = lineWithoutUnderscores;
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} assetDirectory
|
|
* @returns {void}
|
|
*/
|
|
process(assetDirectory) {
|
|
return;
|
|
}
|
|
|
|
render() {
|
|
return `<code>${this.text}</code>`;
|
|
}
|
|
}
|
|
|
|
class MultiLineCode extends Symbol {
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
text = "";
|
|
/**
|
|
*
|
|
* @param {string} line
|
|
* @returns {boolean}
|
|
*/
|
|
static canParse(line) {
|
|
const trimmedLine = line.trim();
|
|
return trimmedLine.startsWith("```");
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {LineFeed} lineFeed
|
|
* @returns {Symbol}
|
|
*/
|
|
static create(lineFeed) {
|
|
const instance = new MultiLineCode();
|
|
const line = lineFeed.claim().trim();
|
|
const lineWithoutStartTag = line.slice(3, line.length);
|
|
|
|
const sameLineContainsEnding = lineWithoutStartTag.includes("```");
|
|
if (sameLineContainsEnding) {
|
|
const endsWithTag = lineWithoutStartTag.endsWith("```");
|
|
if (endsWithTag) {
|
|
instance.text = lineWithoutStartTag.slice(
|
|
0,
|
|
lineWithoutStartTag.length - 3
|
|
);
|
|
return instance;
|
|
}
|
|
const [text, rest] = splitStringAtToken("```", lineWithoutStartTag);
|
|
lineFeed.push(rest);
|
|
instance.text = text;
|
|
return instance;
|
|
}
|
|
const lines = [lineWithoutStartTag];
|
|
|
|
let endTokenFound = false;
|
|
while (!endTokenFound && !lineFeed.isEmpty()) {
|
|
const nextLine = lineFeed.claim();
|
|
if (!nextLine.includes("```")) {
|
|
lines.push(nextLine);
|
|
continue;
|
|
}
|
|
if (nextLine.endsWith("```")) {
|
|
lines.push(nextLine.slice(0, nextLine.length - 3));
|
|
break;
|
|
}
|
|
const [lastLine, rest] = splitStringAtToken("```", nextLine);
|
|
lineFeed.push(rest);
|
|
lines.push(lastLine);
|
|
break;
|
|
}
|
|
|
|
instance.text = lines.join("\n");
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} assetDirectory
|
|
* @returns {void}
|
|
*/
|
|
process(assetDirectory) {
|
|
return;
|
|
}
|
|
|
|
render() {
|
|
return `<pre><code>${this.text}</code></pre>`;
|
|
}
|
|
}
|
|
|
|
class TextRow extends Symbol {
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
text = "";
|
|
/**
|
|
* @type {Symbol[]}
|
|
*/
|
|
children = [];
|
|
/**
|
|
* @type {Symbol[]}
|
|
*/
|
|
allowedChildren = [
|
|
Bold,
|
|
Italic,
|
|
ImageLink,
|
|
Link,
|
|
MultiLineCode,
|
|
SingleLineCode,
|
|
TextRow,
|
|
];
|
|
/**
|
|
*
|
|
* @param {string} line
|
|
* @returns {boolean}
|
|
*/
|
|
static canParse(line) {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {LineFeed} lineFeed
|
|
* @returns {Symbol}
|
|
*/
|
|
static create(lineFeed) {
|
|
const instance = new TextRow();
|
|
instance.text = lineFeed.claim();
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} assetDirectory
|
|
* @returns {void}
|
|
*/
|
|
process(assetDirectory) {
|
|
for (let i = 0; i < this.text.length; i++) {
|
|
if (i === this.text.length - 1) {
|
|
const plainText = new PlainText();
|
|
plainText.text = this.text;
|
|
this.children = [plainText];
|
|
break;
|
|
}
|
|
const line = this.text.slice(i);
|
|
const symbols = toSymbols(line, assetDirectory, this.allowedChildren);
|
|
const symbolsIsOnlyOneTextRow =
|
|
symbols.length === 1 && symbols[0] instanceof TextRow;
|
|
if (!symbols.length || symbolsIsOnlyOneTextRow) {
|
|
continue;
|
|
}
|
|
const plainText = new PlainText();
|
|
plainText.text = this.text.slice(0, i);
|
|
this.children = [plainText, ...symbols];
|
|
break;
|
|
}
|
|
this.children.forEach((c) => c.process(assetDirectory));
|
|
}
|
|
|
|
render() {
|
|
return this.children.map((c) => c.render()).join("");
|
|
}
|
|
}
|
|
|
|
class PlainText extends Symbol {
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
text = "";
|
|
/**
|
|
* @type {Symbol[]}
|
|
*/
|
|
children = [];
|
|
/**
|
|
*
|
|
* @param {string} line
|
|
* @returns {boolean}
|
|
*/
|
|
static canParse(line) {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {LineFeed} lineFeed
|
|
* @returns {Symbol}
|
|
*/
|
|
static create(lineFeed) {
|
|
const instance = new PlainText();
|
|
instance.text = lineFeed.claim();
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} assetDirectory
|
|
* @returns {void}
|
|
*/
|
|
process(assetDirectory) {
|
|
return;
|
|
}
|
|
|
|
render() {
|
|
return `<span>${this.text}</span>`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string|RexExp} token
|
|
* @param {string} string
|
|
* @returns {string[]}
|
|
*/
|
|
const splitStringAtToken = (token, string) => {
|
|
let index = 0;
|
|
let length = 0;
|
|
if (typeof token?.exec === "function") {
|
|
const exp = new RegExp(token);
|
|
const match = exp.exec(string);
|
|
index = match?.index ?? -1;
|
|
length = match?.[0].length ?? 0;
|
|
} else {
|
|
index = string.indexOf(token);
|
|
length = token.length;
|
|
}
|
|
|
|
const splitTokenNotFound = index === -1;
|
|
if (splitTokenNotFound) return [string, ""];
|
|
return [string.slice(0, index), string.slice(index + length)];
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @param {string|RexExp} token
|
|
* @param {string} string
|
|
* @returns {string[]}
|
|
*/
|
|
const extractTokenAndRest = (token, string) => {
|
|
let index = 0;
|
|
let length = 0;
|
|
if (typeof token?.exec === "function") {
|
|
const exp = new RegExp(token);
|
|
const match = exp.exec(string);
|
|
index = match?.index ?? -1;
|
|
length = match?.[0].length ?? 0;
|
|
} else {
|
|
index = string.indexOf(token);
|
|
length = token.length;
|
|
}
|
|
|
|
const splitTokenNotFound = index === -1;
|
|
if (splitTokenNotFound) return [string, ""];
|
|
return [string.slice(0, index + length), string.slice(index + length)];
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @param {string} token
|
|
* @param {string} string
|
|
*/
|
|
const getAmountOfTokenInBeginningOfFile = (token, string) => {
|
|
let count = 0;
|
|
let searchIndex = 0;
|
|
while (true) {
|
|
if (searchIndex + (token.length - 1) > string.length) return count;
|
|
|
|
const index = string.slice(searchIndex).indexOf(token);
|
|
const indexNotAtStartOfString = index !== 0;
|
|
if (indexNotAtStartOfString) return count;
|
|
|
|
count++;
|
|
searchIndex += token.length;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @type {Symbol[]}
|
|
*/
|
|
const AllSymbols = [
|
|
JustALineBreak,
|
|
Heading,
|
|
UnorderedListItem,
|
|
OrderedListItem,
|
|
Bold,
|
|
Italic,
|
|
ImageLink,
|
|
Link,
|
|
MultiLineCode,
|
|
SingleLineCode,
|
|
TextRow,
|
|
];
|
|
|
|
/**
|
|
*
|
|
* @param {string} markdown
|
|
* @param {string} assetDirectory
|
|
* @returns {string}
|
|
*/
|
|
export const toHtml = (markdown, assetDirectory) => {
|
|
// Stage one, markdown to symbols
|
|
const symbols = toSymbols(markdown, assetDirectory);
|
|
|
|
// Stage two, expanding symbols
|
|
symbols.forEach((s) => s.process(assetDirectory));
|
|
|
|
// Stage three, operations based on symbol relationship
|
|
const stageThree = stageThreeProcessing(symbols);
|
|
// Stage four, rendering
|
|
const html = stageThree
|
|
// .filter((s) => !(s instanceof JustALineBreak))
|
|
.map((s) => s.render())
|
|
.join("");
|
|
return `<div>${html}</div>`;
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @param {Symbol[]} symbols
|
|
* @returns {Symbol[]}
|
|
*/
|
|
const stageThreeProcessing = (symbols) => {
|
|
const localSymbols = [...symbols];
|
|
// Turn line break pairs into paragraphs
|
|
while (localSymbols.filter((s) => s instanceof JustALineBreak).length !== 0) {
|
|
const startIndex = localSymbols.findIndex(
|
|
(s) => s instanceof JustALineBreak
|
|
);
|
|
const subEndIndex = localSymbols
|
|
.slice(startIndex + 1)
|
|
.findIndex((s) => s instanceof JustALineBreak);
|
|
const endIndex =
|
|
subEndIndex === -1
|
|
? localSymbols.length - 1
|
|
: startIndex + 1 + subEndIndex;
|
|
const lastItemIsNotLineBreak = subEndIndex === -1;
|
|
const children = localSymbols.slice(
|
|
startIndex + 1,
|
|
lastItemIsNotLineBreak ? localSymbols.length : endIndex
|
|
);
|
|
const paragraph = new Paragraph();
|
|
paragraph.children = children;
|
|
console.log(children, startIndex, endIndex);
|
|
localSymbols.splice(startIndex, children.length + 1, paragraph);
|
|
console.log(localSymbols.filter((s) => s instanceof JustALineBreak).length);
|
|
}
|
|
// Fix indentation of bullet elements
|
|
return localSymbols;
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @param {string} markdown
|
|
* @param {string} assetDirectory
|
|
* @param {Symbol[]} allowedSymbols
|
|
* @returns {Symbol[]}
|
|
*/
|
|
const toSymbols = (markdown, assetDirectory, allowedSymbols = AllSymbols) => {
|
|
const lineFeed = new LineFeed(markdown);
|
|
const symbols = [];
|
|
|
|
while (!lineFeed.isEmpty()) {
|
|
const line = lineFeed.peek();
|
|
const factory = allowedSymbols.find((factory) => factory.canParse(line));
|
|
const symbol = factory.create(lineFeed, assetDirectory);
|
|
symbols.push(symbol);
|
|
}
|
|
|
|
return symbols;
|
|
};
|