improve markdown import and export

This commit is contained in:
Salihu
2026-08-09 20:40:03 +01:00
parent b77b84602d
commit 9ba93c87c0
3 changed files with 34 additions and 17 deletions
@@ -13,7 +13,8 @@ interface TabbedToken {
activeTabIndex: number;
}
const HEADER_RE = /^===([!+])?\s*["'“”‘’]([^"'“”‘’\n]+)["'“”‘’]\s*$/gm;
const HEADER_RE =
/^===([!+])?\s*["'“”‘’]((?:\\.|[^"'“”‘’\n])+?)["'“”‘’]\s*$/gm;
export const tabsExtension = {
name: 'tabbed',
@@ -38,7 +39,7 @@ export const tabsExtension = {
index: m.index,
raw: m[0],
marker: m[1] ?? '',
label: m[2].trim(),
label: unescapeTabLabel(m[2].trim()),
});
}
@@ -87,7 +88,7 @@ export const tabsExtension = {
if (body.length > 0) {
body = body
.split('\n')
.map((line) => line.replace(/^\s{2,4}/, ''))
.map((line) => line.replace(/^(?:\t| {2,4})/, ''))
.join('\n');
}
@@ -137,6 +138,10 @@ export const tabsExtension = {
},
};
function unescapeTabLabel(value: string): string {
return value.replace(/\\(["\\])/g, '$1');
}
function escapeHtml(value: string): string {
return value
.replace(/&/g, '&')
@@ -69,7 +69,7 @@ function tabs(turndownService: _TurndownService) {
const isNestedTabsNode =
node.parentElement?.closest('div[data-type="tabs"]') !== null;
const tabBlocks = tabNodes.map((tabNode, index) => {
const labelNode = tabNode.querySelector(
':scope > div[data-type="tabLabel"]',
@@ -279,15 +279,16 @@ function video(turndownService: _TurndownService) {
function sanitizeTabLabel(value: string): string {
return value
.replace(/[\r\n]+/g, ' ')
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.trim();
}
function indentMarkdownBlock(content: string): string {
if (!content) return ' ';
if (!content) return '\t';
return content
.split('\n')
.map((line) => (line.trim() ? ` ${line}` : ''))
.map((line) => (line.trim() ? `\t${line}` : ''))
.join('\n');
}
+22 -11
View File
@@ -15,7 +15,7 @@ export interface TabsOptions {
view: ComponentType<ReactNodeViewProps<HTMLElement>> | null;
}
const TAB_INPUT_REGEX = /^\s*===\s*["'“”‘’]([^"'“”‘’\n]+)["'“”‘’]\s+$/;
const TAB_INPUT_REGEX = /^\s*===\s*["'“”‘’]((?:\\.|[^"'“”‘’\n])+?)["'“”‘’]\s+$/;
declare module '@tiptap/core' {
interface Commands<ReturnType> {
@@ -85,9 +85,8 @@ export const Tabs = Node.create<TabsOptions>({
new InputRule({
find: TAB_INPUT_REGEX,
handler: ({ range, match }) => {
const label = (
typeof match[1] === 'string' ? match[1] : 'Tab 1'
).trim();
const rawLabel = typeof match[1] === 'string' ? match[1] : 'Tab 1';
const label = rawLabel.replace(/\\(["\\])/g, '$1').trim();
this.editor.commands.insertTabs(label, range);
},
@@ -222,7 +221,10 @@ export const Tabs = Node.create<TabsOptions>({
(pos: 'left' | 'right') =>
({ state, tr, dispatch }) => {
const { $from } = state.selection;
const tabs = findParentNode((node) => node.type.name === this.name, $from);
const tabs = findParentNode(
(node) => node.type.name === this.name,
$from,
);
if (!tabs || tabs.node.childCount <= 0) return false;
const currentTabIndex = clampIndex(
@@ -265,7 +267,10 @@ export const Tabs = Node.create<TabsOptions>({
(pos: 'left' | 'right') =>
({ state, tr, dispatch }) => {
const { $from } = state.selection;
const tabs = findParentNode((node) => node.type.name === this.name, $from);
const tabs = findParentNode(
(node) => node.type.name === this.name,
$from,
);
if (!tabs || tabs.node.childCount <= 1) return false;
const currentTabIndex = clampIndex(
@@ -356,7 +361,10 @@ export const Tabs = Node.create<TabsOptions>({
() =>
({ state, tr, dispatch }) => {
const { $from } = state.selection;
const tabs = findParentNode((node) => node.type.name === this.name, $from);
const tabs = findParentNode(
(node) => node.type.name === this.name,
$from,
);
if (!tabs) return false;
if (tabs.node.childCount < 2) {
@@ -398,8 +406,11 @@ export const Tabs = Node.create<TabsOptions>({
() =>
({ state, tr, dispatch }) => {
const { $from } = state.selection;
const tabs = findParentNode((node) => node.type.name === this.name, $from);
if (!tabs || tabs.node.childCount < 0) return false;
const tabs = findParentNode(
(node) => node.type.name === this.name,
$from,
);
if (tabs?.node.childCount <= 0) return false;
tr.delete(tabs.pos, tabs.pos + tabs.node.nodeSize);
@@ -417,12 +428,12 @@ export const Tabs = Node.create<TabsOptions>({
if (!empty) return false;
if ($from.parent.content.size > 0) return false;
const tabsNode = findParentNode(
(node) => node.type.name === this.name,
$from,
);
if (!tabsNode) return false;
return editor
.chain()