Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions src/markdown/marpRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,22 @@ export async function loadMarp() {
*/
export function cleanMarpHtml(html: string): { cleaned: string; scripts: string[] } {
const scripts: string[] = [];
const cleaned = html.replace(
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
(match) => {
const content = match.replace(/^<script.*?>/i, "").replace(/<\/script>$/i, "");
if (content.trim()) {
scripts.push(content);
}
return "";
},
);

let fullyCleaned = cleaned.replace(
/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi,
"",
);
const scriptRegex = /<\s*script\b[^>]*>([\s\S]*?)<\/\s*script\b[^>]*>/gi;
let m: RegExpExecArray | null;
while ((m = scriptRegex.exec(html)) !== null) {
const content = m[1].trim();
if (content) {
scripts.push(content);
}
}

let fullyCleaned = html;
let prevCleaned: string;
const tagRemoveRegex = /<\s*(?:script|style)\b[^>]*>[\s\S]*?<\/\s*(?:script|style)\b[^>]*>/gi;
do {
prevCleaned = fullyCleaned;
fullyCleaned = fullyCleaned.replace(tagRemoveRegex, "");
} while (fullyCleaned !== prevCleaned);

// Strip data-line from SVGs and Sections to fix Marp slide offsets and Quick Edit targeting
fullyCleaned = fullyCleaned.replace(/<(svg|section)\b[^>]*\sdata-line="[^"]*"[^>]*>/gi, (match) => {
Expand Down
48 changes: 38 additions & 10 deletions src/markdown/structuralDiff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1373,7 +1373,7 @@ export function refineBlockDiffs(
);

const footnoteBundleRegex =
/<del[^>]*>\s*((?:<li[^>]*>[\s\S]*?<\/li>\s*)+)<\/del>\s*<ins[^>]*>\s*((?:<li[^>]*>[\s\S]*?<\/li>\s*)+)<\/ins>/gi;
/<del\b([^>]*)>\s*([\s\S]*?)\s*<\/del>\s*<ins\b([^>]*)>\s*([\s\S]*?)\s*<\/ins>/gi;
const footnoteItemRegex = /<li[^>]*>[\s\S]*?<\/li>/gi;
const getFootnoteId = (itemHtml: string) => {
const id = itemHtml.match(/\bid=["']([^"']+)["']/i)?.[1] ?? null;
Expand All @@ -1382,7 +1382,27 @@ export function refineBlockDiffs(

resultHtml = resultHtml.replace(
footnoteBundleRegex,
(match, oldBundle, newBundle) => {
(match, delAttrs, oldBundle, insAttrs, newBundle) => {
// Must not match list container change diffs or non-li bundles
if (
delAttrs.includes("diff-list-container-change") ||
insAttrs.includes("diff-list-container-change")
) {
return match;
}
const trimmedOld = oldBundle.trim();
const trimmedNew = newBundle.trim();
if (
!trimmedOld.startsWith("<li") ||
!trimmedOld.endsWith("</li>") ||
!trimmedNew.startsWith("<li") ||
!trimmedNew.endsWith("</li>") ||
/<(?:ul|ol|dl|table|pre|h[1-6])\b/i.test(oldBundle) ||
/<(?:ul|ol|dl|table|pre|h[1-6])\b/i.test(newBundle)
) {
return match;
}

const oldFootnotes = oldBundle.match(footnoteItemRegex) || [];
const newFootnotes = newBundle.match(footnoteItemRegex) || [];

Expand Down Expand Up @@ -1967,19 +1987,27 @@ export function stripDataLineAttributes(html: string): string {

export function wrapHeadingPrefixes(html: string): string {
return html.replace(
/(<h[1-6][^>]*>)((?:\s*(?:<(?:del|ins)[^>]*>)?\s*[\d\.\[\]]+\s*(?:<\/(?:del|ins)>)?\s*)+(?:\]\s*)?(?=\S))/gi,
(match, tag, prefix) => {
if (!/<(ins|del)\b/.test(prefix)) {
/(<h[1-6][^>]*>)([\s\S]*?)(<\/h[1-6]>)/gi,
(match, openTag, innerContent, closeTag) => {
const prefixMatch = innerContent.match(
/^((?:[0-9.\[\]\s]|<(?:del|ins)\b[^>]*>[0-9.\[\]\s]*<\/(?:del|ins)\b[^>]*>)+)(?=\S)/i,
);
if (!prefixMatch) {
return match;
}
const prefix = prefixMatch[1];
if (!/<(?:ins|del)\b/i.test(prefix)) {
return match;
}
const openIns = (prefix.match(/<ins\b/g) || []).length;
const closeIns = (prefix.match(/<\/ins>/g) || []).length;
const openDel = (prefix.match(/<del\b/g) || []).length;
const closeDel = (prefix.match(/<\/del>/g) || []).length;
const openIns = (prefix.match(/<ins\b/gi) || []).length;
const closeIns = (prefix.match(/<\/ins>/gi) || []).length;
const openDel = (prefix.match(/<del\b/gi) || []).length;
const closeDel = (prefix.match(/<\/del>/gi) || []).length;
if (openIns !== closeIns || openDel !== closeDel) {
return match;
}
return tag + '<span class="heading-prefix">' + prefix + "</span>";
const rest = innerContent.slice(prefix.length);
return `${openTag}<span class="heading-prefix">${prefix}</span>${rest}${closeTag}`;
},
);
}
Expand Down
3 changes: 3 additions & 0 deletions src/test/runTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ async function main() {
await fs.mkdir(userDataDir, { recursive: true });
await fs.mkdir(extensionsDir, { recursive: true });

// Prevent Electron from running as Node CLI when running inside VS Code / sub-shells
delete process.env.ELECTRON_RUN_AS_NODE;

// Download VS Code, unzip it and run the integration test
await runTests({
extensionDevelopmentPath,
Expand Down
2 changes: 1 addition & 1 deletion src/test/unit/markdownDiff.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ describe("MarkdownDiffProvider", () => {

const scriptBlocks = Array.from(
webviewContent.matchAll(
/<script(?: nonce="[^"]*")?>([\s\S]*?)<\/script>/g,
/<\s*script\b[^>]*>([\s\S]*?)<\/\s*script\b[^>]*>/gi,
),
(match) => match[1],
);
Expand Down