From 1411a4bf6fb76a9cb2b35b770be20bb59b61c514 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Wed, 28 Jan 2026 11:08:28 +0000 Subject: [PATCH] WIP --- .../editor/components/link/link-view.tsx | 114 ++++++++++-------- .../editor/hooks/use-editor-scroll.ts | 2 +- 2 files changed, 62 insertions(+), 54 deletions(-) diff --git a/apps/client/src/features/editor/components/link/link-view.tsx b/apps/client/src/features/editor/components/link/link-view.tsx index 3bbdb2ed..ecab9411 100644 --- a/apps/client/src/features/editor/components/link/link-view.tsx +++ b/apps/client/src/features/editor/components/link/link-view.tsx @@ -1,14 +1,14 @@ -import { MarkViewContent, MarkViewProps } from '@tiptap/react'; -import { useNavigate, useLocation } from 'react-router-dom'; +import { MarkViewContent, MarkViewProps } from "@tiptap/react"; +import { useNavigate, useLocation } from "react-router-dom"; import { IconFileDescription, IconCopy, IconExternalLink, IconLinkOff, -} from '@tabler/icons-react'; -import { useState, useCallback, useRef, useEffect } from 'react'; -import { useLongPress } from '@/features/editor/hooks/use-long-press'; -import { notifications } from '@mantine/notifications'; +} from "@tabler/icons-react"; +import { useState, useCallback, useRef, useEffect } from "react"; +import { useLongPress } from "@/features/editor/hooks/use-long-press"; +import { notifications } from "@mantine/notifications"; import { Card, Group, @@ -19,15 +19,15 @@ import { Stack, CloseButton, Tooltip, -} from '@mantine/core'; -import classes from './link.module.css'; -import { useTranslation } from 'react-i18next'; -import { createPortal } from 'react-dom'; -import { INTERNAL_LINK_REGEX } from '@/lib/constants'; +} from "@mantine/core"; +import classes from "./link.module.css"; +import { useTranslation } from "react-i18next"; +import { createPortal } from "react-dom"; +import { INTERNAL_LINK_REGEX } from "@/lib/constants"; const isTouchDevice = () => { - if (typeof window === 'undefined') return false; - return 'ontouchstart' in window || navigator.maxTouchPoints > 0; + if (typeof window === "undefined") return false; + return "ontouchstart" in window || navigator.maxTouchPoints > 0; }; const isInternalLink = (href: string): boolean => { @@ -39,20 +39,20 @@ const isInternalLink = (href: string): boolean => { }; const extractLinkLabel = (href: string): string => { - if (!href) return ''; + if (!href) return ""; const match = INTERNAL_LINK_REGEX.exec(href); if (match) { const slug = match[5]; // Extract page name from slug (remove the ID suffix) - const namePart = slug.split('-').slice(0, -1).join('-'); + const namePart = slug.split("-").slice(0, -1).join("-"); return namePart || slug; } // For external links, show domain try { const url = new URL(href); - return url.hostname.replace('www.', ''); + return url.hostname.replace("www.", ""); } catch { return href.slice(0, 30); } @@ -68,7 +68,7 @@ export default function LinkView(props: MarkViewProps) { const [isHovered, setIsHovered] = useState(false); const [showEditPanel, setShowEditPanel] = useState(false); const [editUrl, setEditUrl] = useState(href); - const [editTitle, setEditTitle] = useState(''); + const [editTitle, setEditTitle] = useState(""); const wrapperRef = useRef(null); const hoverTimeoutRef = useRef | null>(null); const isTouch = isTouchDevice(); @@ -77,13 +77,13 @@ export default function LinkView(props: MarkViewProps) { const getLinkText = useCallback(() => { const { state } = editor; - let text = ''; + let text = ""; state.doc.descendants((node) => { const linkMark = node.marks.find( - (m) => m.type.name === 'link' && m.attrs.href === href + (m) => m.type.name === "link" && m.attrs.href === href, ); if (linkMark && node.isText) { - text = node.text || ''; + text = node.text || ""; return false; } }); @@ -119,7 +119,7 @@ export default function LinkView(props: MarkViewProps) { if (isInternal) { // Get pathname for navigation (handle both relative and absolute URLs) let targetPath = href; - let anchor = ''; + let anchor = ""; try { const url = new URL(href); @@ -127,8 +127,8 @@ export default function LinkView(props: MarkViewProps) { anchor = url.hash.slice(1); // Remove the # prefix } catch { // Relative URL - if (href.includes('#')) { - [targetPath, anchor] = href.split('#'); + if (href.includes("#")) { + [targetPath, anchor] = href.split("#"); } } @@ -138,7 +138,7 @@ export default function LinkView(props: MarkViewProps) { if (!targetPath || targetPath === currentPath) { const element = document.getElementById(anchor); if (element) { - element.scrollIntoView({ behavior: 'smooth', block: 'start' }); + element.scrollIntoView({ behavior: "smooth", block: "start" }); navigate(`${currentPath}#${anchor}`, { replace: true }); return; } @@ -147,7 +147,7 @@ export default function LinkView(props: MarkViewProps) { navigate(anchor ? `${targetPath}#${anchor}` : targetPath); } else { - window.open(href, '_blank', 'noopener,noreferrer'); + window.open(href, "_blank", "noopener,noreferrer"); } }, [href, navigate, location.pathname, isInternal]); @@ -159,7 +159,7 @@ export default function LinkView(props: MarkViewProps) { handleNavigate(); } }, - [handleNavigate, showEditPanel] + [handleNavigate, showEditPanel], ); const handleLongPress = useCallback(() => { @@ -176,7 +176,7 @@ export default function LinkView(props: MarkViewProps) { handleNavigate(); } }, - [handleNavigate, showEditPanel] + [handleNavigate, showEditPanel], ); const longPressHandlers = useLongPress({ @@ -204,12 +204,12 @@ export default function LinkView(props: MarkViewProps) { const fullUrl = isInternal ? `${window.location.origin}${href}` : href; navigator.clipboard.writeText(fullUrl); notifications.show({ - message: t('Link copied to clipboard'), - color: 'green', + message: t("Link copied to clipboard"), + color: "green", autoClose: 2000, }); }, - [href, isInternal, t] + [href, isInternal, t], ); const handleSave = useCallback(() => { @@ -221,7 +221,7 @@ export default function LinkView(props: MarkViewProps) { if (updated) return false; const linkMark = node.marks.find( - (m) => m.type.name === 'link' && m.attrs.href === href + (m) => m.type.name === "link" && m.attrs.href === href, ); if (linkMark && node.isText) { const from = pos; @@ -232,14 +232,14 @@ export default function LinkView(props: MarkViewProps) { tr.addMark(from, to, linkMark.type.create({ href: editUrl })); } - const currentText = node.text || ''; + const currentText = node.text || ""; if (editTitle && editTitle !== currentText) { tr.replaceWith( from, to, state.schema.text(editTitle, [ linkMark.type.create({ href: editUrl || href }), - ]) + ]), ); } @@ -256,7 +256,7 @@ export default function LinkView(props: MarkViewProps) { }, [editor, href, editUrl, editTitle]); const handleRemoveLink = useCallback(() => { - editor.chain().focus().extendMarkRange('link').unsetLink().run(); + editor.chain().focus().extendMarkRange("link").unsetLink().run(); setShowEditPanel(false); }, [editor]); @@ -265,15 +265,15 @@ export default function LinkView(props: MarkViewProps) { // Stop all keyboard events from bubbling to TipTap editor e.stopPropagation(); - if (e.key === 'Enter') { + if (e.key === "Enter") { e.preventDefault(); handleSave(); - } else if (e.key === 'Escape') { + } else if (e.key === "Escape") { e.preventDefault(); handleCloseEdit(); } }, - [handleSave, handleCloseEdit] + [handleSave, handleCloseEdit], ); const interactionProps = isTouch @@ -297,8 +297,8 @@ export default function LinkView(props: MarkViewProps) { href={href} className={classes.linkText} onClick={(e) => e.preventDefault()} - target={isInternal ? undefined : '_blank'} - rel={isInternal ? undefined : 'noopener noreferrer'} + target={isInternal ? undefined : "_blank"} + rel={isInternal ? undefined : "noopener noreferrer"} > @@ -316,7 +316,7 @@ export default function LinkView(props: MarkViewProps) { {isInternal ? ( @@ -329,20 +329,28 @@ export default function LinkView(props: MarkViewProps) { - - + + - - + + @@ -357,7 +365,7 @@ export default function LinkView(props: MarkViewProps) { className={classes.editPanelOverlay} onClick={handleCloseEdit} />, - document.body + document.body, )}
setEditUrl(e.target.value)} onKeyDown={handleKeyDown} rightSection={ editUrl && ( - setEditUrl('')} /> + setEditUrl("")} /> ) } autoFocus @@ -384,9 +392,9 @@ export default function LinkView(props: MarkViewProps) { /> setEditTitle(e.target.value)} onKeyDown={handleKeyDown} @@ -398,10 +406,10 @@ export default function LinkView(props: MarkViewProps) { onClick={handleCloseEdit} size="sm" > - {t('Cancel')} + {t("Cancel")} diff --git a/apps/client/src/features/editor/hooks/use-editor-scroll.ts b/apps/client/src/features/editor/hooks/use-editor-scroll.ts index 31c357a0..cfd5a692 100644 --- a/apps/client/src/features/editor/hooks/use-editor-scroll.ts +++ b/apps/client/src/features/editor/hooks/use-editor-scroll.ts @@ -42,7 +42,7 @@ export const useEditorScroll = ({ return; } - const dom = editor.view.dom.querySelector(`[id="${targetId}"]`); + const dom = editor.view.dom.querySelector(`[id="${targetId}"], [data-id="${targetId}"]`); if (dom) { dom.scrollIntoView({ behavior: 'smooth', block: 'start' }); resolve(true);