-
Notifications
You must be signed in to change notification settings - Fork 64
Expand file tree
/
Copy pathcodeFence.ts
More file actions
80 lines (72 loc) · 2.85 KB
/
Copy pathcodeFence.ts
File metadata and controls
80 lines (72 loc) · 2.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import { TextSelection } from "@tiptap/pm/state";
import type { EditorView } from "@tiptap/pm/view";
export function inCodeBlock(view: EditorView): boolean {
return view.state.selection.$from.parent.type.spec.code === true;
}
interface CodeFenceLine {
language: string;
/** Doc position where the fence text starts, including the preceding hard break. */
deleteFrom: number;
/** The fence follows a Shift+Enter hard break rather than opening the paragraph. */
afterHardBreak: boolean;
}
// Caret at the end of a paragraph whose current line (after the last hard
// break, or the whole paragraph) is a ``` fence opener.
export function findCodeFenceLine(view: EditorView): CodeFenceLine | null {
const { $from, empty } = view.state.selection;
if (!empty) return null;
const parent = $from.parent;
if (parent.type.name !== "paragraph") return null;
if ($from.parentOffset !== parent.content.size) return null;
let lineStartOffset = 0;
let afterHardBreak = false;
parent.forEach((child, offset) => {
if (child.type.name === "hardBreak") {
lineStartOffset = offset + child.nodeSize;
afterHardBreak = true;
}
});
// Atoms (mention chips) on the line show up as the replacement char and
// fail the match, so a chip-bearing line is never treated as a fence.
const lineText = parent.textBetween(
lineStartOffset,
parent.content.size,
undefined,
"",
);
const match = /^```(\w*)$/.exec(lineText);
if (!match) return null;
const lineStartPos = $from.start() + lineStartOffset;
return {
language: match[1],
deleteFrom: afterHardBreak ? lineStartPos - 1 : lineStartPos,
afterHardBreak,
};
}
// Shift+Enter on a ``` fence line converts it to a code block. The stock
// input rules are disabled (they fire on "``` " with a space or Enter, and
// only match a fence that opens the paragraph), so both cases are handled
// here: a paragraph-opening fence converts in place, and a fence typed after
// a hard break strips the break + fence and opens a code block right after
// the paragraph.
export function convertFenceLine(view: EditorView): boolean {
const fence = findCodeFenceLine(view);
if (!fence) return false;
const codeBlockType = view.state.schema.nodes.codeBlock;
if (!codeBlockType) return false;
const attrs = { language: fence.language || null };
const { $from } = view.state.selection;
const tr = view.state.tr.delete(fence.deleteFrom, $from.pos);
if (fence.afterHardBreak) {
const codeBlock = codeBlockType.createAndFill(attrs);
if (!codeBlock) return false;
const afterParagraph = tr.mapping.map($from.after());
tr.insert(afterParagraph, codeBlock);
tr.setSelection(TextSelection.create(tr.doc, afterParagraph + 1));
} else {
tr.setBlockType(fence.deleteFrom, fence.deleteFrom, codeBlockType, attrs);
}
tr.scrollIntoView();
view.dispatch(tr);
return true;
}