mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
autojoiner
This commit is contained in:
@@ -0,0 +1,105 @@
|
|||||||
|
// https://github.com/NiclasDev63/tiptap-extension-auto-joiner - MIT
|
||||||
|
import { Extension } from "@tiptap/core";
|
||||||
|
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
||||||
|
import { canJoin } from "@tiptap/pm/transform";
|
||||||
|
import { getNodeType } from "@tiptap/react";
|
||||||
|
import { NodeType } from "@tiptap/pm/model";
|
||||||
|
import { Transaction } from "@tiptap/pm/state";
|
||||||
|
|
||||||
|
// https://discuss.prosemirror.net/t/how-to-autojoin-all-the-time/2957/4
|
||||||
|
// Adapted from prosemirror-commands wrapDispatchForJoin
|
||||||
|
function autoJoin(
|
||||||
|
transactions: readonly Transaction[],
|
||||||
|
newTr: Transaction,
|
||||||
|
nodeTypes: NodeType[]
|
||||||
|
) {
|
||||||
|
// Collect changed ranges across all transactions, mapping earlier ranges
|
||||||
|
// forward through later mappings so every position lands in newTr.doc space.
|
||||||
|
let ranges: number[] = [];
|
||||||
|
for (const tr of transactions) {
|
||||||
|
for (let i = 0; i < tr.mapping.maps.length; i++) {
|
||||||
|
let map = tr.mapping.maps[i];
|
||||||
|
if (!map) continue;
|
||||||
|
for (let j = 0; j < ranges.length; j++) ranges[j] = map.map(ranges[j]!);
|
||||||
|
map.forEach((_s, _e, from, to) => ranges.push(from, to));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out which joinable points exist inside those ranges,
|
||||||
|
// by checking all node boundaries in their parent nodes.
|
||||||
|
// Resolve against newTr.doc — the same document we will join on.
|
||||||
|
let joinable: number[] = [];
|
||||||
|
for (let i = 0; i < ranges.length; i += 2) {
|
||||||
|
let from = ranges[i]!,
|
||||||
|
to = ranges[i + 1]!;
|
||||||
|
let $from = newTr.doc.resolve(from),
|
||||||
|
depth = $from.sharedDepth(to),
|
||||||
|
parent = $from.node(depth);
|
||||||
|
for (
|
||||||
|
let index = $from.indexAfter(depth), pos = $from.after(depth + 1);
|
||||||
|
pos <= to;
|
||||||
|
++index
|
||||||
|
) {
|
||||||
|
let after = parent.maybeChild(index);
|
||||||
|
if (!after) break;
|
||||||
|
if (index && joinable.indexOf(pos) == -1) {
|
||||||
|
let before = parent.child(index - 1);
|
||||||
|
if (before.type == after.type && nodeTypes.includes(before.type))
|
||||||
|
joinable.push(pos);
|
||||||
|
}
|
||||||
|
pos += after.nodeSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join the joinable points (reverse order to preserve earlier positions)
|
||||||
|
let joined = false;
|
||||||
|
joinable.sort((a, b) => a - b);
|
||||||
|
for (let i = joinable.length - 1; i >= 0; i--) {
|
||||||
|
if (canJoin(newTr.doc, joinable[i]!)) {
|
||||||
|
newTr.join(joinable[i]!);
|
||||||
|
joined = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return joined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AutoJoinerOptions {
|
||||||
|
elementsToJoin: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const AutoJoiner = Extension.create<AutoJoinerOptions>({
|
||||||
|
name: "autoJoiner",
|
||||||
|
|
||||||
|
addOptions() {
|
||||||
|
return {
|
||||||
|
elementsToJoin: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
addProseMirrorPlugins() {
|
||||||
|
const plugin = new PluginKey(this.name);
|
||||||
|
const joinableNodes = [
|
||||||
|
this.editor.schema.nodes.bulletList,
|
||||||
|
this.editor.schema.nodes.orderedList,
|
||||||
|
];
|
||||||
|
this.options.elementsToJoin.forEach((element) => {
|
||||||
|
const nodeTyp = getNodeType(element, this.editor.schema);
|
||||||
|
joinableNodes.push(nodeTyp);
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
|
new Plugin({
|
||||||
|
key: plugin,
|
||||||
|
appendTransaction(transactions, _, newState) {
|
||||||
|
let newTr = newState.tr;
|
||||||
|
if (autoJoin(transactions, newTr, joinableNodes as NodeType[])) {
|
||||||
|
return newTr;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default AutoJoiner;
|
||||||
@@ -49,7 +49,7 @@ import {
|
|||||||
SharedStorage,
|
SharedStorage,
|
||||||
Columns,
|
Columns,
|
||||||
Column,
|
Column,
|
||||||
Status
|
Status,
|
||||||
} from "@docmost/editor-ext";
|
} from "@docmost/editor-ext";
|
||||||
import {
|
import {
|
||||||
randomElement,
|
randomElement,
|
||||||
@@ -97,6 +97,7 @@ import i18n from "@/i18n.ts";
|
|||||||
import { MarkdownClipboard } from "@/features/editor/extensions/markdown-clipboard.ts";
|
import { MarkdownClipboard } from "@/features/editor/extensions/markdown-clipboard.ts";
|
||||||
import EmojiCommand from "./emoji-command";
|
import EmojiCommand from "./emoji-command";
|
||||||
import { countWords } from "alfaaz";
|
import { countWords } from "alfaaz";
|
||||||
|
import AutoJoiner from "@/features/editor/extensions/autojoiner.ts";
|
||||||
|
|
||||||
const lowlight = createLowlight(common);
|
const lowlight = createLowlight(common);
|
||||||
lowlight.register("mermaid", plaintext);
|
lowlight.register("mermaid", plaintext);
|
||||||
@@ -353,6 +354,9 @@ export const mainExtensions = [
|
|||||||
}).configure(),
|
}).configure(),
|
||||||
Columns,
|
Columns,
|
||||||
Column,
|
Column,
|
||||||
|
AutoJoiner.configure({
|
||||||
|
elementsToJoin: [],
|
||||||
|
}),
|
||||||
] as any;
|
] as any;
|
||||||
|
|
||||||
type CollabExtensions = (provider: HocuspocusProvider, user: IUser) => any[];
|
type CollabExtensions = (provider: HocuspocusProvider, user: IUser) => any[];
|
||||||
|
|||||||
Reference in New Issue
Block a user