diff --git a/src/MystEditor.js b/src/MystEditor.js
index 051aa39..f19ff82 100644
--- a/src/MystEditor.js
+++ b/src/MystEditor.js
@@ -11,7 +11,6 @@ import { EditorTopbar } from "./components/Topbar";
import useCollaboration from "./hooks/useCollaboration";
import useComments from "./hooks/useComments";
import ResolvedComments from "./components/Resolved";
-import { handlePreviewFold } from "./hooks/markdownFoldButtons";
import { handlePreviewClickToScroll } from "./extensions/syncDualPane";
if (!window.myst_editor?.isFresh) {
@@ -233,7 +232,6 @@ const MystEditor = ({
ref=${preview}
mode=${mode}
onClick=${(ev) => {
- handlePreviewFold(ev, text.lineMap);
handlePreviewClickToScroll(ev, text.lineMap, preview);
}}
><${PreviewFocusHighlight} className="cm-previewFocus" />/
diff --git a/src/components/CodeMirror.js b/src/components/CodeMirror.js
index b1dc4d3..6bc49a9 100644
--- a/src/components/CodeMirror.js
+++ b/src/components/CodeMirror.js
@@ -3,7 +3,7 @@ import { html } from "htm/preact";
import { EditorView } from "codemirror";
import { EditorState } from "@codemirror/state";
import styled from "styled-components";
-import { ExtensionBuilder, skipAndFoldAll, folded } from "../extensions";
+import { ExtensionBuilder, skipAndFoldAll } from "../extensions";
import { YCommentsParent } from "../components/Comment";
import commentIcon from "../icons/comment.svg?raw";
import { customHighlighter } from "../extensions/customHighlights";
@@ -229,7 +229,7 @@ const CodeMirror = ({ text, id, root, mode, spellcheckOpts, highlights, collabor
.if(collaboration.opts.commentsEnabled, (b) =>
b.useComments({ ycomments: collaboration.ycomments }).useSuggestionPopup({ ycomments: collaboration.ycomments, editorMountpoint }),
)
- .addUpdateListener((update) => (update.docChanged || folded(update)) && text.set(view.state.doc.toString(), update))
+ .addUpdateListener((update) => update.docChanged && text.set(view.state.doc.toString(), update))
.useFixFoldingScroll(focusScroll)
.useMoveCursorAfterFold()
.useCursorIndicator({ lineMap: text.lineMap, preview })
diff --git a/src/components/Preview.js b/src/components/Preview.js
index 6d598d0..963b1dc 100644
--- a/src/components/Preview.js
+++ b/src/components/Preview.js
@@ -3,7 +3,6 @@ import styled from "styled-components";
const Preview = styled.div`
background-color: white;
padding: 20px;
- padding-left: 40px;
box-sizing: border-box;
height: 100%;
border: 1px solid var(--gray-400);
@@ -105,7 +104,6 @@ const Preview = styled.div`
pre {
white-space: pre-wrap;
padding: 16px;
- max-width: calc(100% - 40px) !important;
& > code {
padding: 0px;
}
@@ -116,7 +114,6 @@ const Preview = styled.div`
}
aside {
border-radius: var(--border-radius);
- max-width: 100% !important;
&.admonition {
border: var(--border-2) solid var(--green-500);
@@ -337,8 +334,6 @@ const Preview = styled.div`
.cm-previewFocus {
display: ${(props) => (props.mode === "Both" ? "block" : "none")};
- z-index: 1;
- pointer-events: none;
}
.mermaid {
@@ -347,67 +342,6 @@ const Preview = styled.div`
display: flex;
justify-content: center;
}
-
- *:has(.fold) {
- position: relative;
- max-width: max-content;
- }
-
- .fold-arrow {
- position: absolute;
- font-size: inherit;
- transform: translate(-25px);
- cursor: pointer;
- background: transparent;
- border: none;
- padding: 0;
- padding-right: 25px;
- opacity: 0;
-
- &:hover {
- opacity: 1;
- }
-
- span {
- font-size: initial;
- }
- }
-
- *:hover > .fold-arrow:first-child {
- opacity: 1;
- }
-
- .fold-arrow:has(*:hover) {
- opacity: 1;
- }
-
- li > .fold-arrow {
- transform: translate(-42px);
- }
-
- .fold-dots {
- background-color: rgb(238, 238, 238);
- border: 1px solid rgb(221, 221, 221);
- color: rgb(136, 136, 136);
- border-radius: 0.2rem;
- margin: 0;
- padding: 0 1px;
- cursor: pointer;
- position: absolute;
- right: -23px;
- top: 50%;
- transform: translateY(-50%);
- }
-
- .unfold {
- opacity: 1;
-
- span {
- rotate: -90deg;
- transform: translateY(-3px);
- display: inline-block;
- }
- }
`;
Preview.defaultProps = { className: "myst-preview" };
diff --git a/src/extensions/index.js b/src/extensions/index.js
index fb68d70..460c313 100644
--- a/src/extensions/index.js
+++ b/src/extensions/index.js
@@ -12,7 +12,6 @@ import { suggestionPopup } from "./suggestions";
import { foldEffect, unfoldEffect, foldable } from "@codemirror/language";
import { syncPreviewWithCursor } from "./syncDualPane";
import { cursorIndicator } from "./cursorIndicator";
-import { customCommonMark, fenceFold, headerIndent } from "./markdownLang";
const basicSetupWithoutHistory = basicSetup.filter((_, i) => i != 3);
const minimalSetupWithoutHistory = minimalSetup.filter((_, i) => i != 1);
@@ -50,14 +49,7 @@ export class ExtensionBuilder {
}
static defaultPlugins() {
- return [
- EditorView.lineWrapping,
- markdown({ base: customCommonMark }),
- highlightActiveLine(),
- headerIndent,
- fenceFold,
- keymap.of([indentWithTab, { key: "Mod-Z", run: redo }]),
- ];
+ return [EditorView.lineWrapping, markdown(), highlightActiveLine(), keymap.of([indentWithTab, { key: "Mod-Z", run: redo }])];
}
disable(keys) {
diff --git a/src/extensions/markdownLang.js b/src/extensions/markdownLang.js
deleted file mode 100644
index d38e5cd..0000000
--- a/src/extensions/markdownLang.js
+++ /dev/null
@@ -1,76 +0,0 @@
-// Taken from https://github.com/codemirror/lang-markdown/blob/main/src/markdown.ts
-import { parser } from "@lezer/markdown";
-import { NodeProp } from "@lezer/common";
-import { foldNodeProp, indentNodeProp, languageDataProp, defineLanguageFacet, Language, foldService, syntaxTree } from "@codemirror/language";
-
-const data = defineLanguageFacet({ commentTokens: { block: { open: "" } } });
-
-const headingProp = new NodeProp();
-const fenceProp = new NodeProp();
-
-// This is here to customize the markdown parser used by Codemirror, in particular the folding nodes.
-const commonmark = parser.configure({
- props: [
- foldNodeProp.add((type) => {
- return !type.is("Block") || type.is("FencedCode") || type.is("Document") || isHeading(type) != null
- ? undefined
- : (node, state) => {
- return { from: state.doc.lineAt(node.from).to, to: node.to };
- };
- }),
- headingProp.add(isHeading),
- fenceProp.add((type) => type.is("FencedCode")),
- indentNodeProp.add({
- Document: () => null,
- }),
- languageDataProp.add({
- Document: data,
- }),
- ],
-});
-
-function isHeading(type) {
- let match = /^(?:ATX|Setext)Heading(\d)$/.exec(type.name);
- return match ? +match[1] : undefined;
-}
-
-export const headerIndent = foldService.of((state, start, end) => {
- for (let node = syntaxTree(state).resolveInner(end, -1); node; node = node.parent) {
- if (node.from < start) break;
- let heading = node.type.prop(headingProp);
- if (heading == null) continue;
- let upto = findSectionEnd(node, heading);
- if (upto > end) return { from: end, to: upto };
- }
- return null;
-});
-
-function findSectionEnd(headerNode, level) {
- let last = headerNode;
- for (;;) {
- let next = last.nextSibling,
- heading;
- if (!next || ((heading = isHeading(next.type)) != null && heading <= level)) break;
- last = next;
- }
- return last.to;
-}
-
-// This foldService disables folding of certain fenced block types.
-const disabledBlocks = ["```{image", "```{figure", "```{list-table}"];
-export const fenceFold = foldService.of((state, start, end) => {
- for (let node = syntaxTree(state).resolveInner(end, -1); node; node = node.parent) {
- if (node.from < start) break;
- let fence = node.type.prop(fenceProp);
- if (fence == false) continue;
- const line = state.doc.lineAt(node.from);
- const to = state.doc.line(state.doc.lineAt(node.to).number - 1).to;
- for (const block of disabledBlocks) {
- if (line.text.includes(block)) return;
- }
- return { from: line.to, to };
- }
- return null;
-});
-
-export const customCommonMark = new Language(data, commonmark, [headerIndent, fenceFold], "markdown");
diff --git a/src/hooks/markdownFoldButtons.js b/src/hooks/markdownFoldButtons.js
deleted file mode 100644
index 6eb5ed3..0000000
--- a/src/hooks/markdownFoldButtons.js
+++ /dev/null
@@ -1,56 +0,0 @@
-import { getLineById, SRC_LINE_ID } from "./markdownSourceMap";
-import { toggleFold, foldable, foldedRanges } from "@codemirror/language";
-
-export function addFoldUI(/** @type {import("markdown-it").Token} */ token, /** @type {string} */ baseOutput, env) {
- const id = token.attrGet(SRC_LINE_ID);
- if (id) {
- const lineNumber = getLineById(env.lineMap.current, id);
- const line = window.myst_editor.main_editor.state.doc.line(lineNumber);
- const range = foldable(window.myst_editor.main_editor.state, line.from, line.to);
- if (range) {
- const ranges = foldedRanges(window.myst_editor.main_editor.state);
- let folded = false;
- ranges.between(range.from, range.to, (from, to) => {
- if (from === range.from && to === range.to) {
- folded = true;
- }
- });
-
- if (!folded) {
- return addFoldArrow(baseOutput, id);
- } else {
- return addUnfoldButtons(baseOutput, id);
- }
- }
- }
-
- return baseOutput;
-}
-
-const addFoldArrow = (baseOutput, id) => {
- return `` + baseOutput;
-};
-
-const addUnfoldButtons = (/** @type {string} */ baseOutput, id) => {
- const arrow = ``;
- const dots = ``;
- if (baseOutput.endsWith("\n")) {
- return arrow + baseOutput.slice(0, baseOutput.indexOf("")) + dots + "";
- } else {
- return arrow + baseOutput + dots;
- }
-};
-
-export function handlePreviewFold(/** @type {MouseEvent} */ ev, lineMap) {
- /** @type {HTMLElement} */
- let button = ev.target.classList.contains("fold") ? ev.target : ev.target.parentElement;
- if (!button.classList.contains("fold")) return;
-
- const lineId = button.getAttribute("data-btn-id");
- const lineNumber = getLineById(lineMap.current, lineId);
- const line = window.myst_editor.main_editor.state.doc.line(lineNumber);
- window.myst_editor.main_editor.dispatch({
- selection: { anchor: line.to, head: line.to },
- });
- toggleFold(window.myst_editor.main_editor);
-}
diff --git a/src/hooks/markdownSourceMap.js b/src/hooks/markdownSourceMap.js
index ecc3f82..d0cb9f7 100644
--- a/src/hooks/markdownSourceMap.js
+++ b/src/hooks/markdownSourceMap.js
@@ -1,21 +1,11 @@
import markdownIt from "markdown-it";
import { escapeHtml } from "markdown-it/lib/common/utils";
-export const SRC_LINE_ID = "data-line-id";
+const SRC_LINE_ID = "data-line-id";
const randomLineId = () => Math.random().toString().replace(".", "");
-function getLineForToken(token, env) {
- let line = token.map[0] + env.startLine - (env.chunkId !== 0);
- for (const range of env.foldedLines ?? []) {
- if (range.start > line) break;
-
- line += range.end - range.start + 1;
- }
- return line;
-}
-
/** @param {markdownIt} md */
-export default function markdownSourceMap(md, transform = (token, out, env) => out) {
+export default function markdownSourceMap(md) {
md.use(overrideDefaultDirectives);
md.use(overrideDefaultRole);
md.use(wrapTextInSpan);
@@ -33,11 +23,11 @@ export default function markdownSourceMap(md, transform = (token, out, env) => o
for (const rule of overrideRules) {
const temp = md.renderer.rules[rule];
- md.renderer.rules[rule] = addLineNumberToTokens(temp, transform);
+ md.renderer.rules[rule] = addLineNumberToTokens(temp);
}
}
-function addLineNumberToTokens(defaultRule, transform) {
+function addLineNumberToTokens(defaultRule) {
/**
* @param {import("markdown-it/index.js").Token[]} tokens
* @param {number} idx
@@ -69,7 +59,7 @@ function addLineNumberToTokens(defaultRule, transform) {
}
}
} else if (tokens[idx].map) {
- const line = getLineForToken(tokens[idx], env);
+ const line = tokens[idx].map[0] + env.startLine - (env.chunkId !== 0);
const id = randomLineId();
if (!env.lineMap.current.has(line)) {
env.lineMap.current.set(line, id);
@@ -77,7 +67,7 @@ function addLineNumberToTokens(defaultRule, transform) {
}
}
- return transform(tokens[idx], rule(tokens, idx, options, env, self), env);
+ return rule(tokens, idx, options, env, self);
};
}
@@ -132,18 +122,14 @@ function wrapFencedLinesInSpan(/** @type {markdownIt} */ md) {
}
const sanitizedContent = escapeHtml(token.content);
- const startLine = getLineForToken(token, env);
+ const startLine = token.map[0] + env.startLine - (env.chunkId !== 0);
let htmlContent = sanitizedContent
.split("\n")
.filter((_, i, lines) => i !== lines.length - 1)
.map((l, i) => {
const id = randomLineId();
- if (!env.lineMap.current.has(startLine + i + 1)) {
- env.lineMap.current.set(startLine + i + 1, id);
- return `${l}`;
- } else {
- return `${l}`;
- }
+ env.lineMap.current.set(startLine + i + 1, id);
+ return `${l}`;
})
.join("\n");
diff --git a/src/hooks/useText.js b/src/hooks/useText.js
index fddd2b4..5f70469 100644
--- a/src/hooks/useText.js
+++ b/src/hooks/useText.js
@@ -8,41 +8,8 @@ import { backslashLineBreakPlugin } from "./markdownLineBreak";
import markdownSourceMap from "./markdownSourceMap";
import { StateEffect } from "@codemirror/state";
import markdownMermaid from "./markdownMermaid";
-import { EditorView, ViewUpdate } from "@codemirror/view";
-import { addFoldUI } from "./markdownFoldButtons";
-import { foldedRanges } from "@codemirror/language";
const countOccurences = (str, pattern) => (str?.match(pattern) || []).length;
-const getUnfoldedMarkdown = (src, /** @type {EditorView} */ view) => {
- const folded = foldedRanges(view.state);
- const ranges = [];
- const cursor = folded.iter();
- for (let r = cursor; r.value != null; cursor.next()) {
- ranges.push({ from: r.from, to: r.to });
- }
-
- let unfoldedMarkdown = src;
- if (ranges.length > 0) {
- unfoldedMarkdown = ranges.reduce(
- (acc, { from, to }, idx) => {
- if (from > acc.lastPos) {
- acc.result += src.slice(acc.lastPos, from);
- }
- acc.lastPos = Math.max(acc.lastPos, to);
- if (idx == ranges.length - 1 && acc.lastPos < src.length) {
- // add remaining part
- acc.result += src.slice(acc.lastPos, src.length);
- }
- return acc;
- },
- { result: "", lastPos: 0 },
- ).result;
- }
-
- const foldedLines = ranges.map((r) => ({ start: view.state.doc.lineAt(r.from).number + 1, end: view.state.doc.lineAt(r.to).number }));
-
- return [unfoldedMarkdown, foldedLines];
-};
const exposeText = (text) => () => {
if (!window.myst_editor) {
@@ -87,7 +54,7 @@ export const useText = ({ initialText, transforms, customRoles, preview, backsla
*
* @type {[{ md: string, html: string }[], Dispatch<{newMarkdown: string, force: boolean }>]}
*/
- const [htmlChunks, updateHtmlChunks] = useReducer((oldChunks, { newMarkdown, force = false, view, foldedLines }) => {
+ const [htmlChunks, updateHtmlChunks] = useReducer((oldChunks, { newMarkdown, force = false, view }) => {
let htmlLookup = {};
if (!force) {
htmlLookup = oldChunks.reduce((lookup, { hash, html }) => {
@@ -95,7 +62,8 @@ export const useText = ({ initialText, transforms, customRoles, preview, backsla
return lookup;
}, {});
}
- const newChunks = splitIntoChunks(newMarkdown, htmlLookup, foldedLines);
+
+ const newChunks = splitIntoChunks(newMarkdown, htmlLookup);
if (newChunks.length !== oldChunks.length || force) {
// We can't infer which chunks were modified, so we update the entire document
@@ -122,8 +90,8 @@ export const useText = ({ initialText, transforms, customRoles, preview, backsla
.use(markdownitDocutils)
.use(markdownReplacer(transforms, parent))
.use(useCustomRoles(customRoles, parent))
- .use(markdownMermaid, { lineMap, parent })
- .use(markdownSourceMap, addFoldUI);
+ .use(markdownMermaid, { preview, lineMap, parent })
+ .use(markdownSourceMap);
if (backslashLineBreak) md.use(backslashLineBreakPlugin);
return md;
}, []);
@@ -159,7 +127,7 @@ export const useText = ({ initialText, transforms, customRoles, preview, backsla
/** Split and parse markdown into chunks of HTML. If `lookup` is not provided then every chunk will be parsed */
const splitIntoChunks = useCallback(
- (newMarkdown, lookup = {}, foldedLines) =>
+ (newMarkdown, lookup = {}) =>
newMarkdown
.split(/(?=\n#{1,3} )/g) // Perform a split without removing the delimeter
.reduce(
@@ -185,24 +153,14 @@ export const useText = ({ initialText, transforms, customRoles, preview, backsla
// Clear source mappings for chunk we are rerendering
if (!lookup[hash]) {
- let unfoldedStart = startLine;
- let unfoldedEnd = endLine;
- for (const range of foldedLines ?? []) {
- if (range.start < unfoldedStart) {
- unfoldedStart += range.end - range.start + 1;
- }
- if (range.start < unfoldedEnd) {
- unfoldedEnd += range.end - range.start + 1;
- }
- }
- for (let l = unfoldedStart; l <= unfoldedEnd; l++) {
+ for (let l = startLine; l <= endLine; l++) {
lineMap.current.delete(l);
}
}
const html =
lookup[hash] ||
- purify.sanitize(markdown.render(md, { chunkId: id, startLine, lineMap, foldedLines }), {
+ purify.sanitize(markdown.render(md, { chunkId: id, startLine, lineMap }), {
// Taken from Mermaid JS settings: https://github.com/mermaid-js/mermaid/blob/dd0304387e85fc57a9ebb666f89ef788c012c2c5/packages/mermaid/src/mermaidAPI.ts#L50
ADD_TAGS: ["foreignobject"],
ADD_ATTR: ["dominant-baseline"],
@@ -222,23 +180,17 @@ export const useText = ({ initialText, transforms, customRoles, preview, backsla
}, [syncText]);
return {
- set(newMarkdown, /** @type {ViewUpdate} */ update) {
+ set(newMarkdown, update) {
if (update) {
shiftLineMap(update);
}
- let unfoldedMarkdown = newMarkdown;
- let foldedLines = [];
- if (update?.state) {
- [unfoldedMarkdown, foldedLines] = getUnfoldedMarkdown(newMarkdown, update.view);
- }
-
- setText(unfoldedMarkdown);
+ setText(newMarkdown);
setTimeout(() => {
try {
- updateHtmlChunks({ newMarkdown: unfoldedMarkdown, view: update?.view, foldedLines });
+ updateHtmlChunks({ newMarkdown, view: update?.view });
} catch (e) {
console.warn(e);
- updateHtmlChunks({ newMarkdown: unfoldedMarkdown, force: true, view: update?.view, foldedLines });
+ updateHtmlChunks({ newMarkdown, force: true, view: update?.view });
}
});
},
@@ -249,8 +201,7 @@ export const useText = ({ initialText, transforms, customRoles, preview, backsla
setSyncText(true);
},
refresh() {
- const [unfoldedMarkdown, foldedLines] = getUnfoldedMarkdown(window.myst_editor.text, window.myst_editor.main_editor);
- updateHtmlChunks({ newMarkdown: unfoldedMarkdown, force: true, foldedLines });
+ updateHtmlChunks({ newMarkdown: window.myst_editor.text, force: true });
},
onSync(action) {
setOnSync({ action });
diff --git a/tests/myst-editor.spec.ts b/tests/myst-editor.spec.ts
index 6c16908..8f48c48 100644
--- a/tests/myst-editor.spec.ts
+++ b/tests/myst-editor.spec.ts
@@ -144,19 +144,6 @@ graph TD
(html) => expect(html).toContain("