mirror of
https://github.com/docmost/docmost.git
synced 2026-09-11 07:56:54 +08:00
Compare commits
1
Commits
main
...
fix/gap-cursor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34e5744850 |
@@ -0,0 +1,112 @@
|
||||
import { Editor, Node } from "@tiptap/core";
|
||||
import { GapCursor } from "@tiptap/pm/gapcursor";
|
||||
import { NodeSelection, TextSelection } from "@tiptap/pm/state";
|
||||
import { StarterKit } from "@tiptap/starter-kit";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { TiptapDocument } from "./document";
|
||||
|
||||
const Footnotes = Node.create({
|
||||
name: "footnotes",
|
||||
group: "",
|
||||
content: "paragraph*",
|
||||
isolating: true,
|
||||
renderHTML() {
|
||||
return ["ol", { class: "footnotes" }, 0];
|
||||
},
|
||||
});
|
||||
|
||||
const AtomBlock = Node.create({
|
||||
name: "atomBlock",
|
||||
group: "block",
|
||||
atom: true,
|
||||
renderHTML() {
|
||||
return ["div", { "data-atom-block": "" }];
|
||||
},
|
||||
});
|
||||
|
||||
const IsolatingBlock = Node.create({
|
||||
name: "isolatingBlock",
|
||||
group: "block",
|
||||
content: "paragraph+",
|
||||
isolating: true,
|
||||
renderHTML() {
|
||||
return ["div", { "data-isolating-block": "" }, 0];
|
||||
},
|
||||
});
|
||||
|
||||
function createEditor(content: object[]) {
|
||||
const element = document.createElement("div");
|
||||
document.body.appendChild(element);
|
||||
|
||||
return new Editor({
|
||||
element,
|
||||
extensions: [
|
||||
TiptapDocument,
|
||||
StarterKit.configure({ document: false }),
|
||||
Footnotes,
|
||||
AtomBlock,
|
||||
IsolatingBlock,
|
||||
],
|
||||
content: { type: "doc", content },
|
||||
});
|
||||
}
|
||||
|
||||
function pressKey(editor: Editor, key: string, keyCode: number) {
|
||||
editor.view.dom.dispatchEvent(
|
||||
new KeyboardEvent("keydown", {
|
||||
key,
|
||||
keyCode,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
describe("TiptapDocument", () => {
|
||||
it("stops on the gap when arrowing down from a selected block node", () => {
|
||||
const editor = createEditor([
|
||||
{ type: "atomBlock" },
|
||||
{ type: "atomBlock" },
|
||||
{ type: "paragraph" },
|
||||
]);
|
||||
const gapPos = editor.state.doc.child(0).nodeSize;
|
||||
editor.view.dispatch(
|
||||
editor.state.tr.setSelection(
|
||||
NodeSelection.create(editor.state.doc, 0),
|
||||
),
|
||||
);
|
||||
|
||||
pressKey(editor, "ArrowDown", 40);
|
||||
|
||||
expect(editor.state.selection).toBeInstanceOf(GapCursor);
|
||||
expect(editor.state.selection.head).toBe(gapPos);
|
||||
|
||||
editor.destroy();
|
||||
});
|
||||
|
||||
it("stops on the gap when arrowing right out of an isolating block", () => {
|
||||
const paragraph = (text: string) => ({
|
||||
type: "paragraph",
|
||||
content: [{ type: "text", text }],
|
||||
});
|
||||
const editor = createEditor([
|
||||
{ type: "isolatingBlock", content: [paragraph("a")] },
|
||||
{ type: "isolatingBlock", content: [paragraph("b")] },
|
||||
{ type: "paragraph" },
|
||||
]);
|
||||
const gapPos = editor.state.doc.child(0).nodeSize;
|
||||
const endOfFirstText = gapPos - 2;
|
||||
editor.view.dispatch(
|
||||
editor.state.tr.setSelection(
|
||||
TextSelection.create(editor.state.doc, endOfFirstText),
|
||||
),
|
||||
);
|
||||
|
||||
pressKey(editor, "ArrowRight", 39);
|
||||
|
||||
expect(editor.state.selection).toBeInstanceOf(GapCursor);
|
||||
expect(editor.state.selection.head).toBe(gapPos);
|
||||
|
||||
editor.destroy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Document } from "@tiptap/extension-document";
|
||||
|
||||
// With `block+ footnotes?`, ProseMirror's defaultType after the first block is
|
||||
// `footnotes` (not a textblock), so GapCursor.valid() rejects every top-level gap.
|
||||
export const TiptapDocument = Document.extend({
|
||||
content: "block+ footnotes?",
|
||||
allowGapCursor: true,
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { markInputRule } from "@tiptap/core";
|
||||
import { StarterKit } from "@tiptap/starter-kit";
|
||||
import { Document } from "@tiptap/extension-document";
|
||||
import { TiptapDocument } from "@/features/editor/extensions/document";
|
||||
import { Code } from "@tiptap/extension-code";
|
||||
import { TextAlign } from "@tiptap/extension-text-align";
|
||||
import { TaskList, TaskItem } from "@tiptap/extension-list";
|
||||
@@ -148,9 +148,7 @@ export const mainExtensions = [
|
||||
codeBlock: false,
|
||||
code: false,
|
||||
}),
|
||||
Document.extend({
|
||||
content: "block+ footnotes?",
|
||||
}),
|
||||
TiptapDocument,
|
||||
// Override TipTap's Code extension to fix the inline code input rule.
|
||||
// The upstream regex /(^|[^`])`([^`]+)`(?!`)$/ captures the character
|
||||
// before the opening backtick as part of the match, causing markInputRule
|
||||
|
||||
Reference in New Issue
Block a user