Files
docmost/apps/client/src/features/editor/components/link/internal-link-paste.ts
T
Philip Okugbe d2629afff2 feat: anchor links (#1765)
* feat: add heading extension with unique ID support and scroll functionality
* Added unique id for heading
* remove baseUrl heading storage
* move heading to extensions package
* WIP
* support anchors in mentions
* enhance scrolling functionality
* nodeId function
* fix nanoid import
* Bring unique-id extension local
* fixes
* fix internal link scroll in public pages
* add unique id server side
* rename mention anchor to anchorId
* capture first anchorId on paste

---------

Co-authored-by: Romik <40670677+RomikMakavana@users.noreply.github.com>
2025-12-06 14:46:54 +00:00

77 lines
2.1 KiB
TypeScript

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,
anchorId?: 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, anchorId) => {
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,
anchorId: anchorId,
});
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;
},
});