Skip to content

Commit

Permalink
fix: Multiline asides and blurbs
Browse files Browse the repository at this point in the history
  • Loading branch information
nzakas committed Mar 17, 2022
1 parent 634e715 commit ebef7bf
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 103 deletions.
231 changes: 132 additions & 99 deletions src/markua-aside.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,128 +55,161 @@ function renderBlurb(tokens, idx, _options, env, slf) {
return slf.renderToken(tokens, idx, _options, env, slf);
}

//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------

/**
* The aside plugin.
* @param {MarkdownIt} md The MarkdownIt instance to
* attach to.
* @returns {boolean} True if the function handled an aside or blurb,
* false if not.
*/
export function asidePlugin(md) {
function getLine(state, lineNumber) {

const start = state.bMarks[lineNumber] + state.tShift[lineNumber];
const end = state.eMarks[lineNumber];
const text = state.src.slice(start, end);

function aside(state, startLine, endLine, silent) {
var nextLine, token,
originalParent, originalLineMax,
start = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
return {
start,
end,
text
};

let src = state.src;
let isClosingTag = false;
let tagStart = start;
}

if (src.charAt(start) !== "{") {
return false;
}
function findStartTag(state, lineNumber) {

tagStart++;
const {
text,
start: lineStart,
end: lineEnd
} = getLine(state, lineNumber);
const src = state.src;

if (src.charAt(tagStart) === "/") {
isClosingTag = true;
tagStart++;
}
// tags must start at start of line
if (text[0] !== "{") {
return undefined;
}

let lastCharPos = tagStart + 5;
let tagName = src.slice(tagStart, lastCharPos);
// parse the tag name
let tagName = "";

// exit early for unknown tags
if (!TAGS.has(tagName)) {
return false;
}
for (let i = 1; /[a-z]/i.test(text[i]) && i < text.length; i++) {
tagName += text[i];
}

// exit early for unknown tags
if (!TAGS.has(tagName)) {
return undefined;
}

let className = "";
let expectedClosingCurlyPos = 6;
let className = "";

// check for, e.g. {blurb, class: warning}
if (src.charAt(lastCharPos) === ",") {
let pos = tagStart + 6;
pos = state.skipSpaces(pos);
// blurbs allow class names
if (tagName === "blurb") {

// 6 = curly brace plus tag name
let pos = state.skipSpaces(lineStart + 6);

if (src[pos] === ",") {

pos = state.skipSpaces(pos + 1);

if (src.slice(pos, pos + 6) !== "class:") {
return false;
return undefined;
}

pos = state.skipSpaces(pos + 6);

// find closing curly
let i = pos;
for (; src.charAt(i) !== "}" && i < max; i++) {
for (; src.charAt(i) !== "}" && i < lineEnd; i++) {
className += src.charAt(i);
}

if (!BLURB_CLASSES.has(className)) {
console.warn("Unknown blurb class detected:", className);
}

lastCharPos = i;
expectedClosingCurlyPos = i - lineStart;
}
}

if (text[expectedClosingCurlyPos] !== "}") {
return undefined;
}

const start = lineStart;
const end = start + expectedClosingCurlyPos + 1;

return {
tagName,
className,
start,
end,
lineNumber,
alone: start === lineStart && end === lineEnd
};
}

function findEndTag(state, lineNumber, tagName) {

const {
text,
start: lineStart,
end: lineEnd
} = getLine(state, lineNumber);
const closingTagText = `{/${tagName}}`;

let openCurlyPos = text.indexOf(closingTagText);
if (openCurlyPos === -1) {
return undefined;
}

const start = lineStart + openCurlyPos;
const end = start + closingTagText.length;

return {
tagName,
start,
end,
lineNumber,
alone: start === lineStart && end === lineEnd
};
}

const hasClosingCurly = src.charAt(lastCharPos) === "}";

// not an aside or blurb
if (!hasClosingCurly) {
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------

/**
* The aside plugin.
* @param {MarkdownIt} md The MarkdownIt instance to
* attach to.
* @returns {boolean} True if the function handled an aside or blurb,
* false if not.
*/
export function asidePlugin(md) {

function aside(state, startLine, endLine, silent) {
let nextLine, token,
originalParent, originalLineMax;

let isSingleLine = false;

// try to find start tag on this line
const startTag = findStartTag(state, startLine);
if (!startTag) {
return false;
}

// Since start is found, we can report success here in validation mode
//
if (silent) {
return true;
}
// try to find end tag on this line
let endTag = findEndTag(state, startLine, startTag.tagName);
if (endTag) {
isSingleLine = true;

// closing tags are always ignored and we just go to the next line
if (isClosingTag) {
state.line = startLine + 1;
return true;
// validation complete
if (silent) {
return true;
}
}

// Only dealing with opening tags here

// TODO: Improper blocks
/*
* {aside}foo
*
* instead of
*
* {aside}
* foo
*/
// let textAfterOpen = false;

/*
* Search from the position after the last curly until the end of the line
* looking for additional characters.
*/
// for (let i = lastCharPos + 1; i < max; i++) {

// // if there's anything other than whitespace, flag it
// if (!/\s/.test(src[i])) {
// textAfterOpen = true;
// break;
// }
// }

/*
* Try to detect asides/blurbs on one line, such as:
* {blurb}Foo{/blurb}.
*/
const closingTagPos = src.slice(lastCharPos + 1, max).indexOf(`{/${tagName}}`);
const singleLine = closingTagPos > -1;

if (singleLine) {
if (isSingleLine) {
nextLine = startLine + 1;
} else {
// Search for the end of the block
Expand All @@ -192,18 +225,16 @@ export function asidePlugin(md) {
break;
}

start = state.bMarks[nextLine] + state.tShift[nextLine];
max = state.eMarks[nextLine];

// possible blurb end
if (state.src.slice(start, max).includes(`{/${tagName}}`)) {
endTag = findEndTag(state, nextLine, startTag.tagName);
if (endTag) {
break;
}

}

}

let tagName = startTag.tagName;
originalParent = state.parentType;
originalLineMax = state.lineMax;
state.parentType = tagName;
Expand All @@ -213,21 +244,23 @@ export function asidePlugin(md) {
token = state.push(tagName + "_open", tagName, 1);
token.markup = `{${tagName}}`;
token.block = true;
token.info = tagName === "blurb" ? { className } : null;
token.info = tagName === "blurb" ? { className: startTag.className } : null;
token.map = [startLine, startLine + 1];

let originalBMark = state.bMarks[startLine];
let originalEMark = state.eMarks[startLine];
let lineToTokenize = startLine + 1;

// for single line we need to adjust the state
if (singleLine) {
state.bMarks[startLine] = lastCharPos + 1;
state.eMarks[startLine] = lastCharPos + 1 + closingTagPos;
if (!startTag.alone) {
state.bMarks[startTag.lineNumber] = startTag.end;
lineToTokenize = startLine;
}

state.md.block.tokenize(state, lineToTokenize, nextLine);
if (!endTag.alone) {
state.eMarks[endTag.lineNumber] = endTag.start;
}

state.md.block.tokenize(state, lineToTokenize, lineToTokenize + 1);

state.bMarks[startLine] = originalBMark;
state.eMarks[startLine] = originalEMark;
Expand All @@ -239,7 +272,7 @@ export function asidePlugin(md) {

state.parentType = originalParent;
state.lineMax = originalLineMax;
state.line = nextLine;
state.line = nextLine + 1;

return true;
}
Expand Down
22 changes: 19 additions & 3 deletions tests/fixtures/aside-block.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Aside tags on separate lines

.
{aside}
Hello world!
Expand All @@ -9,12 +9,28 @@ Hello world!
</aside>
.


Aside tags on a single line
.
{aside}Hello world!{/aside}
.
<aside>
<p>Hello world!</p>
</aside>
.

.
{aside}Hello world!
{/aside}
.
<aside>
<p>Hello world!</p>
</aside>
.

.
{aside}
Hello world!{/aside}
.
<aside>
<p>Hello world!</p>
</aside>
.
18 changes: 18 additions & 0 deletions tests/fixtures/blurb-block.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,21 @@ Hello world!
<p>Hello world!</p>
</aside>
.

.
{blurb}Hello world!
{/blurb}
.
<aside class="blurb">
<p>Hello world!</p>
</aside>
.

.
{blurb}
Hello world!{/blurb}
.
<aside class="blurb">
<p>Hello world!</p>
</aside>
.
4 changes: 3 additions & 1 deletion tests/markua-aside.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ md.use(asidePlugin, "aside");
describe("Markua Aside Plugin", () => {

for (const filename of filenames) {
generate(filename, md);
describe(path.basename(filename), () => {
generate(filename, md);
});
}
});

0 comments on commit ebef7bf

Please sign in to comment.