mirror of
https://github.com/docmost/docmost.git
synced 2025-11-21 22:51:10 +10:00
feat: internal page links and mentions (#604)
* Work on mentions * fix: properly parse page slug * fix editor suggestion bugs * mentions must start with whitespace * add icon to page mention render * feat: backlinks - WIP * UI - WIP * permissions check * use FTS for page suggestion * cleanup * WIP * page title fallback * feat: handle internal link paste * link styling * WIP * Switch back to LIKE operator for search suggestion * WIP * scope to workspaceId * still create link for pages not found * select necessary columns * cleanups
This commit is contained in:
@ -0,0 +1,74 @@
|
||||
import { EditorView } from "@tiptap/pm/view";
|
||||
import { getPageById } from "@/features/page/services/page-service.ts";
|
||||
import { IPage } from "@/features/page/types/page.types.ts";
|
||||
import { v7 } from "uuid";
|
||||
import { extractPageSlugId } from "@/lib";
|
||||
|
||||
export type LinkFn = (
|
||||
url: string,
|
||||
view: EditorView,
|
||||
pos: number,
|
||||
creatorId: string,
|
||||
) => void;
|
||||
|
||||
export interface InternalLinkOptions {
|
||||
validateFn: (url: string, view: EditorView) => boolean;
|
||||
onResolveLink: (linkedPageId: string, creatorId: string) => Promise<any>;
|
||||
}
|
||||
|
||||
export const handleInternalLink =
|
||||
({ validateFn, onResolveLink }: InternalLinkOptions): LinkFn =>
|
||||
async (url: string, view, pos, creatorId) => {
|
||||
const validated = validateFn(url, view);
|
||||
if (!validated) return;
|
||||
|
||||
const linkedPageId = extractPageSlugId(url);
|
||||
|
||||
await onResolveLink(linkedPageId, creatorId).then(
|
||||
(page: IPage) => {
|
||||
const { schema } = view.state;
|
||||
|
||||
const node = schema.nodes.mention.create({
|
||||
id: v7(),
|
||||
label: page.title || "Untitled",
|
||||
entityType: "page",
|
||||
entityId: page.id,
|
||||
slugId: page.slugId,
|
||||
creatorId: creatorId,
|
||||
});
|
||||
|
||||
if (!node) return;
|
||||
|
||||
const transaction = view.state.tr.replaceWith(pos, pos, node);
|
||||
view.dispatch(transaction);
|
||||
},
|
||||
() => {
|
||||
// on failure, insert as normal link
|
||||
const { schema } = view.state;
|
||||
|
||||
const transaction = view.state.tr.insertText(url, pos);
|
||||
transaction.addMark(
|
||||
pos,
|
||||
pos + url.length,
|
||||
schema.marks.link.create({ href: url }),
|
||||
);
|
||||
|
||||
view.dispatch(transaction);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const createMentionAction = handleInternalLink({
|
||||
onResolveLink: async (linkedPageId: string): Promise<any> => {
|
||||
// eslint-disable-next-line no-useless-catch
|
||||
try {
|
||||
return await getPageById({ pageId: linkedPageId });
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
validateFn: (url: string, view: EditorView) => {
|
||||
// validation is already done on the paste handler
|
||||
return true;
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user