mirror of
https://github.com/docmost/docmost.git
synced 2026-05-20 00:14:10 +08:00
add nanoid by Vito0912
This commit is contained in:
@@ -16,32 +16,36 @@ const generateSlug = (text: string) =>
|
|||||||
|
|
||||||
export default function HeadingView({ node }: NodeViewProps) {
|
export default function HeadingView({ node }: NodeViewProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [slug, setSlug] = useState("");
|
const [combinedId, setCombinedId] = useState("");
|
||||||
const [url, setUrl] = useState("");
|
const [url, setUrl] = useState("");
|
||||||
const [showAnchorButton, setShowAnchorButton] = useState(false);
|
const [showAnchorButton, setShowAnchorButton] = useState(false);
|
||||||
|
|
||||||
const tag: ElementType = `h${node.attrs.level}` as ElementType;
|
const tag: ElementType = `h${node.attrs.level}` as ElementType;
|
||||||
|
const uid = node.attrs.uid;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const text = node.textContent || "";
|
if (uid) {
|
||||||
const generatedSlug = generateSlug(text);
|
const text = node.textContent || "";
|
||||||
setSlug(generatedSlug);
|
const textSlug = generateSlug(text);
|
||||||
|
const combined = textSlug ? `${textSlug}-${uid}` : uid;
|
||||||
const baseUrl = window.location.href.split("#")[0];
|
setCombinedId(combined);
|
||||||
setUrl(`${baseUrl}#${generatedSlug}`);
|
|
||||||
}, [node.content]);
|
const baseUrl = window.location.href.split("#")[0];
|
||||||
|
setUrl(`${baseUrl}#${combined}`);
|
||||||
|
}
|
||||||
|
}, [uid, node.content]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper
|
<NodeViewWrapper
|
||||||
as={tag}
|
as={tag}
|
||||||
id={slug}
|
id={combinedId}
|
||||||
className={classes.anchorScrollMargin}
|
className={classes.anchorScrollMargin}
|
||||||
onMouseEnter={() => setShowAnchorButton(true)}
|
onMouseEnter={() => setShowAnchorButton(true)}
|
||||||
onMouseLeave={() => setShowAnchorButton(false)}
|
onMouseLeave={() => setShowAnchorButton(false)}
|
||||||
>
|
>
|
||||||
<Flex gap="sm" justify="flex-start" align="center">
|
<Flex gap="sm" justify="flex-start" align="center">
|
||||||
<NodeViewContent as="span" />
|
<NodeViewContent as="span" />
|
||||||
{showAnchorButton && node.textContent && (
|
{showAnchorButton && uid && combinedId && node.textContent && (
|
||||||
<CopyButton value={url} timeout={2000}>
|
<CopyButton value={url} timeout={2000}>
|
||||||
{({ copied, copy }) => (
|
{({ copied, copy }) => (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
|||||||
@@ -9,11 +9,39 @@ export function useAnchorScroll(offset = 95, maxRetries = 10, retryDelay = 500)
|
|||||||
let retries = maxRetries;
|
let retries = maxRetries;
|
||||||
|
|
||||||
const tryScroll = () => {
|
const tryScroll = () => {
|
||||||
const el = document.getElementById(lastHash.current);
|
let el = document.getElementById(lastHash.current);
|
||||||
|
|
||||||
|
if (!el) {
|
||||||
|
const hash = lastHash.current;
|
||||||
|
|
||||||
|
if (hash.includes('-')) {
|
||||||
|
const parts = hash.split('-');
|
||||||
|
const possibleUid = parts[parts.length - 1];
|
||||||
|
|
||||||
|
const elements = document.querySelectorAll('[id]');
|
||||||
|
for (const element of elements) {
|
||||||
|
if (element.id.endsWith(`-${possibleUid}`)) {
|
||||||
|
el = element as HTMLElement;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!el) {
|
||||||
|
const elements = document.querySelectorAll('[id]');
|
||||||
|
for (const element of elements) {
|
||||||
|
if (element.id.endsWith(`-${hash}`) || element.id === hash) {
|
||||||
|
el = element as HTMLElement;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (el) {
|
if (el) {
|
||||||
const y = el.getBoundingClientRect().top + window.scrollY - offset;
|
const y = el.getBoundingClientRect().top + window.scrollY - offset;
|
||||||
window.scrollTo({ top: y, behavior: "smooth" });
|
window.scrollTo({ top: y, behavior: "smooth" });
|
||||||
window.history.replaceState(null, "", `#${lastHash.current}`);
|
window.history.replaceState(null, "", `#${el.id}`);
|
||||||
} else if (retries > 0) {
|
} else if (retries > 0) {
|
||||||
retries--;
|
retries--;
|
||||||
setTimeout(tryScroll, retryDelay);
|
setTimeout(tryScroll, retryDelay);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { Color } from "@tiptap/extension-color";
|
|||||||
import Table from "@tiptap/extension-table";
|
import Table from "@tiptap/extension-table";
|
||||||
import TableHeader from "@tiptap/extension-table-header";
|
import TableHeader from "@tiptap/extension-table-header";
|
||||||
import SlashCommand from "@/features/editor/extensions/slash-command";
|
import SlashCommand from "@/features/editor/extensions/slash-command";
|
||||||
import { Collaboration } from "@tiptap/extension-collaboration";
|
import { Collaboration, isChangeOrigin } from "@tiptap/extension-collaboration";
|
||||||
import { CollaborationCursor } from "@tiptap/extension-collaboration-cursor";
|
import { CollaborationCursor } from "@tiptap/extension-collaboration-cursor";
|
||||||
import { HocuspocusProvider } from "@hocuspocus/provider";
|
import { HocuspocusProvider } from "@hocuspocus/provider";
|
||||||
import {
|
import {
|
||||||
@@ -76,6 +76,8 @@ import { CharacterCount } from "@tiptap/extension-character-count";
|
|||||||
import Heading from "@tiptap/extension-heading";
|
import Heading from "@tiptap/extension-heading";
|
||||||
import HeadingView from "../components/heading/heading-view";
|
import HeadingView from "../components/heading/heading-view";
|
||||||
import { countWords } from "alfaaz";
|
import { countWords } from "alfaaz";
|
||||||
|
import UniqueID from '@tiptap/extension-unique-id';
|
||||||
|
import { generateSlugId } from "../utils/nanoid";
|
||||||
|
|
||||||
const lowlight = createLowlight(common);
|
const lowlight = createLowlight(common);
|
||||||
lowlight.register("mermaid", plaintext);
|
lowlight.register("mermaid", plaintext);
|
||||||
@@ -225,6 +227,12 @@ export const mainExtensions = [
|
|||||||
CharacterCount.configure({
|
CharacterCount.configure({
|
||||||
wordCounter: (text) => countWords(text),
|
wordCounter: (text) => countWords(text),
|
||||||
}),
|
}),
|
||||||
|
UniqueID.configure({
|
||||||
|
types: ['heading'],
|
||||||
|
attributeName: 'uid',
|
||||||
|
generateID: () => generateSlugId(),
|
||||||
|
filterTransaction: (transaction) => !isChangeOrigin(transaction),
|
||||||
|
}),
|
||||||
] as any;
|
] as any;
|
||||||
|
|
||||||
type CollabExtensions = (provider: HocuspocusProvider, user: IUser) => any[];
|
type CollabExtensions = (provider: HocuspocusProvider, user: IUser) => any[];
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { customAlphabet } from "nanoid";
|
||||||
|
|
||||||
|
const slugIdAlphabet =
|
||||||
|
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||||
|
export const generateSlugId = customAlphabet(slugIdAlphabet, 10);
|
||||||
@@ -54,6 +54,7 @@
|
|||||||
"@tiptap/extension-text-style": "^2.10.3",
|
"@tiptap/extension-text-style": "^2.10.3",
|
||||||
"@tiptap/extension-typography": "^2.10.3",
|
"@tiptap/extension-typography": "^2.10.3",
|
||||||
"@tiptap/extension-underline": "^2.10.3",
|
"@tiptap/extension-underline": "^2.10.3",
|
||||||
|
"@tiptap/extension-unique-id": "^2.23.0",
|
||||||
"@tiptap/extension-youtube": "^2.10.3",
|
"@tiptap/extension-youtube": "^2.10.3",
|
||||||
"@tiptap/html": "^2.10.3",
|
"@tiptap/html": "^2.10.3",
|
||||||
"@tiptap/pm": "^2.10.3",
|
"@tiptap/pm": "^2.10.3",
|
||||||
|
|||||||
Reference in New Issue
Block a user