diff --git a/_minutes/export-minutes.html b/_minutes/export-minutes.html
index 8fd27563..c43251f2 100644
--- a/_minutes/export-minutes.html
+++ b/_minutes/export-minutes.html
@@ -22,7 +22,8 @@
}
#extraInfoOutput {
white-space: pre-wrap;
- height: 7em;
+ height: 8em;
+ overflow-y: auto;
}
#input, #output {
flex: 1;
@@ -75,6 +76,10 @@
- Issues: ${serializeIssues(issues)}
- PRs: ${serializeIssues(prs)}
- Mentioned issues without link to issue: ${serializeIssues(mentionedWithoutLink)}`;
+ if (markdownText.includes("```")) {
+ extraInfoOutput.textContent += `
+WARNING: ${markdownText.match(/```/g).length / 2} code blocks (\`\`\`) found. You should verify the rendered output!`;
+ }
};
/**
@@ -86,7 +91,7 @@
- Replace boldfaced with **xx**
- Replace italic with _xx_
- Replace links with [text](anchor)
-- Replace h1, h2, h3 with #, ## and ###
+- Replace h1, h2, h3, h4 with #, ##, ### and ####
- Format h1 header for consistency.
- Replace ol,ul and li with correctly indented list items.
- Fixup whitespace.
@@ -95,12 +100,9 @@
let root = elemRootInput.cloneNode(true);
// Apply code formatting first, before escaping characters.
- for (let c of root.querySelectorAll(`span[style*="font-family:'Courier New'"]`)) {
- c.prepend("`");
- c.append("`");
- // replaceAllInTextNodes skips ` only if they are in the same text node.
- c.normalize();
- }
+ // To avoid interference by transformations below, the code is replaced
+ // with placeholders, which we should restore in the end.
+ const { finalRestoreCodeBlocks } = replaceAllCodeBlocks(root);
// Escape < to avoid rendering as HTML.
replaceAllInTextNodes(root, "<", "<");
@@ -148,6 +150,9 @@
for (let h of root.querySelectorAll("h3")) {
h.prepend(`\n### `);
}
+ for (let h of root.querySelectorAll("h4")) {
+ h.prepend(`\n#### `);
+ }
for (let li of root.querySelectorAll("li")) {
let level = 0;
@@ -190,7 +195,7 @@
elem.after("\n");
}
// Blank line after every header.
- for (let elem of root.querySelectorAll("h1,h2,h3")) {
+ for (let elem of root.querySelectorAll("h1,h2,h3,h4")) {
elem.after("\n\n");
}
@@ -218,6 +223,8 @@
// Trim leading whitespace.
textContent = textContent.trim();
+ textContent = finalRestoreCodeBlocks(textContent);
+
return textContent;
}
@@ -248,6 +255,92 @@
node.parentNode.replaceChild(document.createTextNode(proposed), node);
}
}
+
+// Replaces code elements in |root| with.
+function replaceAllCodeBlocks(root, getPlaceholder) {
+ // To prevent code blocks from being affected by text-based transformations
+ // in the end, replace the text with placeholders.
+ const codeTexts = new Map();
+ let nextCodeId = 1000;
+ function getPlaceholder(txt) {
+ // Assuming that minutes will never contain MINUTE_PLACEHOLDER_.
+ let placeholder = `^^^MINUTE_PLACEHOLDER_${nextCodeId++}===`;
+ codeTexts.set(placeholder, txt);
+ return placeholder;
+ }
+ function restorePlaceholders(txt) {
+ return txt.replace(
+ /\^\^\^MINUTE_PLACEHOLDER_\d+===/g,
+ placeholder => codeTexts.get(placeholder)
+ );
+ }
+
+ // First pass: Detect code lines (possibly multiline code) and inline code.
+ for (let c of root.querySelectorAll(`span[style*="font-family"][style*="monospace"]`)) {
+ if (c.style.fontFamily.includes("monospace")) {
+ if (c.closest("[this_is_really_a_code_block]")) {
+ // Already processed (determined that parent is code block).
+ continue;
+ }
+ if (
+ c.parentNode.tagName === "P" &&
+ !c.parentNode.querySelector(`span[style*="font-family"]:not([style*="monospace"])`)
+ ) {
+ // Part of code block.
+ c.parentNode.setAttribute("this_is_really_a_code_block", "");
+ } else {
+ // Has siblings that is not code.
+ c.setAttribute("this_is_really_a_code_block", "");
+ }
+ }
+ }
+ // Second pass: Collapse multiline code with ```, use ` otherwise.
+ for (let c of root.querySelectorAll("[this_is_really_a_code_block]")) {
+ if (!root.contains(c)) {
+ // Already processed and remove()d below.
+ continue;
+ }
+ let codeNodes = [];
+ for (
+ let nod = c;
+ nod?.matches?.("[this_is_really_a_code_block],br");
+ nod = nod.nextSibling
+ ) {
+ codeNodes.push(nod);
+ }
+ let codeText = "";
+ for (let nod of codeNodes) {
+ // br can be top-level, sole child of p, or wrapped in span.
+ for (let br of nod.querySelectorAll("br")) {
+ br.replaceWith("\n");
+ }
+ codeText += nod.textContent;
+ if (nod.tagName === "P" || nod.tagName === "BR") {
+ codeText += "\n";
+ }
+ }
+ codeText = codeText.replace(/\n+$/, "");
+
+ // Replace actual content with placeholder to prevent other logic such as
+ // the link wrapping / text replacement logic from mangling the code block.
+ c.textContent = getPlaceholder(codeText);
+
+ if (codeText.trim().includes("\n")) {
+ c.textContent = "```\n" + codeText + "\n```";
+ } else {
+ c.textContent = "`" + codeText + "`";
+ }
+ // codeNodes[0] === c; remove all except c.
+ codeNodes.slice(1).forEach(nod => nod.remove());
+ }
+
+ function finalRestoreCodeBlocks(textContent) {
+ textContent = restorePlaceholders(textContent);
+ return textContent;
+ }
+
+ return { finalRestoreCodeBlocks };
+}