chore: show non-editable link bar to allow opening auto-links
This commit is contained in:
@@ -6,6 +6,7 @@ import { sanitizeUrl } from '../../Lexical/Utils/sanitizeUrl'
|
|||||||
import { TOGGLE_LINK_COMMAND } from '@lexical/link'
|
import { TOGGLE_LINK_COMMAND } from '@lexical/link'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { GridSelection, LexicalEditor, NodeSelection, RangeSelection } from 'lexical'
|
import { GridSelection, LexicalEditor, NodeSelection, RangeSelection } from 'lexical'
|
||||||
|
import { classNames } from '@standardnotes/snjs'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
linkUrl: string
|
linkUrl: string
|
||||||
@@ -13,9 +14,10 @@ type Props = {
|
|||||||
setEditMode: (isEditMode: boolean) => void
|
setEditMode: (isEditMode: boolean) => void
|
||||||
editor: LexicalEditor
|
editor: LexicalEditor
|
||||||
lastSelection: RangeSelection | GridSelection | NodeSelection | null
|
lastSelection: RangeSelection | GridSelection | NodeSelection | null
|
||||||
|
isAutoLink: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const LinkEditor = ({ linkUrl, isEditMode, setEditMode, editor, lastSelection }: Props) => {
|
const LinkEditor = ({ linkUrl, isEditMode, setEditMode, editor, lastSelection, isAutoLink }: Props) => {
|
||||||
const [editedLinkUrl, setEditedLinkUrl] = useState('')
|
const [editedLinkUrl, setEditedLinkUrl] = useState('')
|
||||||
|
|
||||||
const handleLinkSubmission = () => {
|
const handleLinkSubmission = () => {
|
||||||
@@ -80,7 +82,10 @@ const LinkEditor = ({ linkUrl, isEditMode, setEditMode, editor, lastSelection }:
|
|||||||
) : (
|
) : (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<a
|
<a
|
||||||
className="mr-1 flex flex-grow items-center gap-2 overflow-hidden whitespace-nowrap underline"
|
className={classNames(
|
||||||
|
'mr-1 flex flex-grow items-center gap-2 overflow-hidden whitespace-nowrap underline',
|
||||||
|
isAutoLink && 'py-2.5',
|
||||||
|
)}
|
||||||
href={linkUrl}
|
href={linkUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
@@ -88,31 +93,35 @@ const LinkEditor = ({ linkUrl, isEditMode, setEditMode, editor, lastSelection }:
|
|||||||
<Icon type="open-in" className="ml-1 flex-shrink-0" />
|
<Icon type="open-in" className="ml-1 flex-shrink-0" />
|
||||||
<div className="max-w-[35ch] overflow-hidden text-ellipsis">{linkUrl}</div>
|
<div className="max-w-[35ch] overflow-hidden text-ellipsis">{linkUrl}</div>
|
||||||
</a>
|
</a>
|
||||||
<button
|
{!isAutoLink && (
|
||||||
className="flex rounded-lg p-3 hover:bg-contrast hover:text-text disabled:cursor-not-allowed"
|
<>
|
||||||
onClick={() => {
|
<button
|
||||||
setEditedLinkUrl(linkUrl)
|
className="flex rounded-lg p-3 hover:bg-contrast hover:text-text disabled:cursor-not-allowed"
|
||||||
setEditMode(true)
|
onClick={() => {
|
||||||
}}
|
setEditedLinkUrl(linkUrl)
|
||||||
aria-label="Edit link"
|
setEditMode(true)
|
||||||
onMouseDown={(event) => event.preventDefault()}
|
}}
|
||||||
>
|
aria-label="Edit link"
|
||||||
<IconComponent size={15}>
|
onMouseDown={(event) => event.preventDefault()}
|
||||||
<PencilFilledIcon />
|
>
|
||||||
</IconComponent>
|
<IconComponent size={15}>
|
||||||
</button>
|
<PencilFilledIcon />
|
||||||
<button
|
</IconComponent>
|
||||||
className="flex rounded-lg p-3 hover:bg-contrast hover:text-text disabled:cursor-not-allowed"
|
</button>
|
||||||
onClick={() => {
|
<button
|
||||||
editor.dispatchCommand(TOGGLE_LINK_COMMAND, null)
|
className="flex rounded-lg p-3 hover:bg-contrast hover:text-text disabled:cursor-not-allowed"
|
||||||
}}
|
onClick={() => {
|
||||||
aria-label="Remove link"
|
editor.dispatchCommand(TOGGLE_LINK_COMMAND, null)
|
||||||
onMouseDown={(event) => event.preventDefault()}
|
}}
|
||||||
>
|
aria-label="Remove link"
|
||||||
<IconComponent size={15}>
|
onMouseDown={(event) => event.preventDefault()}
|
||||||
<TrashFilledIcon />
|
>
|
||||||
</IconComponent>
|
<IconComponent size={15}>
|
||||||
</button>
|
<TrashFilledIcon />
|
||||||
|
</IconComponent>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,15 @@ import { getPositionedPopoverStyles } from '@/Components/Popover/GetPositionedPo
|
|||||||
import { getAdjustedStylesForNonPortalPopover } from '@/Components/Popover/Utils/getAdjustedStylesForNonPortal'
|
import { getAdjustedStylesForNonPortalPopover } from '@/Components/Popover/Utils/getAdjustedStylesForNonPortal'
|
||||||
import LinkEditor from './LinkEditor'
|
import LinkEditor from './LinkEditor'
|
||||||
|
|
||||||
function FloatingLinkEditor({ editor, anchorElem }: { editor: LexicalEditor; anchorElem: HTMLElement }): JSX.Element {
|
function FloatingLinkEditor({
|
||||||
|
editor,
|
||||||
|
anchorElem,
|
||||||
|
isAutoLink,
|
||||||
|
}: {
|
||||||
|
editor: LexicalEditor
|
||||||
|
anchorElem: HTMLElement
|
||||||
|
isAutoLink: boolean
|
||||||
|
}): JSX.Element {
|
||||||
const editorRef = useRef<HTMLDivElement | null>(null)
|
const editorRef = useRef<HTMLDivElement | null>(null)
|
||||||
const [linkUrl, setLinkUrl] = useState('')
|
const [linkUrl, setLinkUrl] = useState('')
|
||||||
const [isEditMode, setEditMode] = useState(false)
|
const [isEditMode, setEditMode] = useState(false)
|
||||||
@@ -154,6 +162,7 @@ function FloatingLinkEditor({ editor, anchorElem }: { editor: LexicalEditor; anc
|
|||||||
setEditMode={setEditMode}
|
setEditMode={setEditMode}
|
||||||
editor={editor}
|
editor={editor}
|
||||||
lastSelection={lastSelection}
|
lastSelection={lastSelection}
|
||||||
|
isAutoLink={isAutoLink}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -162,6 +171,7 @@ function FloatingLinkEditor({ editor, anchorElem }: { editor: LexicalEditor; anc
|
|||||||
function useFloatingLinkEditorToolbar(editor: LexicalEditor, anchorElem: HTMLElement): JSX.Element | null {
|
function useFloatingLinkEditorToolbar(editor: LexicalEditor, anchorElem: HTMLElement): JSX.Element | null {
|
||||||
const [activeEditor, setActiveEditor] = useState(editor)
|
const [activeEditor, setActiveEditor] = useState(editor)
|
||||||
const [isLink, setIsLink] = useState(false)
|
const [isLink, setIsLink] = useState(false)
|
||||||
|
const [isAutoLink, setIsAutoLink] = useState(false)
|
||||||
|
|
||||||
const updateToolbar = useCallback(() => {
|
const updateToolbar = useCallback(() => {
|
||||||
const selection = $getSelection()
|
const selection = $getSelection()
|
||||||
@@ -170,11 +180,17 @@ function useFloatingLinkEditorToolbar(editor: LexicalEditor, anchorElem: HTMLEle
|
|||||||
const linkParent = $findMatchingParent(node, $isLinkNode)
|
const linkParent = $findMatchingParent(node, $isLinkNode)
|
||||||
const autoLinkParent = $findMatchingParent(node, $isAutoLinkNode)
|
const autoLinkParent = $findMatchingParent(node, $isAutoLinkNode)
|
||||||
|
|
||||||
if (linkParent != null && autoLinkParent == null) {
|
if (linkParent != null) {
|
||||||
setIsLink(true)
|
setIsLink(true)
|
||||||
} else {
|
} else {
|
||||||
setIsLink(false)
|
setIsLink(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (autoLinkParent != null) {
|
||||||
|
setIsAutoLink(true)
|
||||||
|
} else {
|
||||||
|
setIsAutoLink(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -197,7 +213,12 @@ function useFloatingLinkEditorToolbar(editor: LexicalEditor, anchorElem: HTMLEle
|
|||||||
)
|
)
|
||||||
}, [editor, updateToolbar])
|
}, [editor, updateToolbar])
|
||||||
|
|
||||||
return isLink ? createPortal(<FloatingLinkEditor editor={activeEditor} anchorElem={anchorElem} />, anchorElem) : null
|
return isLink
|
||||||
|
? createPortal(
|
||||||
|
<FloatingLinkEditor editor={activeEditor} anchorElem={anchorElem} isAutoLink={isAutoLink} />,
|
||||||
|
anchorElem,
|
||||||
|
)
|
||||||
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FloatingLinkEditorPlugin({
|
export default function FloatingLinkEditorPlugin({
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { $isCodeHighlightNode } from '@lexical/code'
|
import { $isCodeHighlightNode } from '@lexical/code'
|
||||||
import { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
|
import { $isLinkNode, $isAutoLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||||
import { mergeRegister, $findMatchingParent, $getNearestNodeOfType } from '@lexical/utils'
|
import { mergeRegister, $findMatchingParent, $getNearestNodeOfType } from '@lexical/utils'
|
||||||
import {
|
import {
|
||||||
@@ -92,6 +92,7 @@ function TextFormatFloatingToolbar({
|
|||||||
anchorElem,
|
anchorElem,
|
||||||
isText,
|
isText,
|
||||||
isLink,
|
isLink,
|
||||||
|
isAutoLink,
|
||||||
isBold,
|
isBold,
|
||||||
isItalic,
|
isItalic,
|
||||||
isUnderline,
|
isUnderline,
|
||||||
@@ -109,6 +110,7 @@ function TextFormatFloatingToolbar({
|
|||||||
isCode: boolean
|
isCode: boolean
|
||||||
isItalic: boolean
|
isItalic: boolean
|
||||||
isLink: boolean
|
isLink: boolean
|
||||||
|
isAutoLink: boolean
|
||||||
isStrikethrough: boolean
|
isStrikethrough: boolean
|
||||||
isSubscript: boolean
|
isSubscript: boolean
|
||||||
isSuperscript: boolean
|
isSuperscript: boolean
|
||||||
@@ -276,6 +278,7 @@ function TextFormatFloatingToolbar({
|
|||||||
linkUrl={linkUrl}
|
linkUrl={linkUrl}
|
||||||
isEditMode={isLinkEditMode}
|
isEditMode={isLinkEditMode}
|
||||||
setEditMode={setIsLinkEditMode}
|
setEditMode={setIsLinkEditMode}
|
||||||
|
isAutoLink={isAutoLink}
|
||||||
editor={editor}
|
editor={editor}
|
||||||
lastSelection={lastSelection}
|
lastSelection={lastSelection}
|
||||||
/>
|
/>
|
||||||
@@ -387,6 +390,7 @@ function useFloatingTextFormatToolbar(editor: LexicalEditor, anchorElem: HTMLEle
|
|||||||
const [activeEditor, setActiveEditor] = useState(editor)
|
const [activeEditor, setActiveEditor] = useState(editor)
|
||||||
const [isText, setIsText] = useState(false)
|
const [isText, setIsText] = useState(false)
|
||||||
const [isLink, setIsLink] = useState(false)
|
const [isLink, setIsLink] = useState(false)
|
||||||
|
const [isAutoLink, setIsAutoLink] = useState(false)
|
||||||
const [isBold, setIsBold] = useState(false)
|
const [isBold, setIsBold] = useState(false)
|
||||||
const [isItalic, setIsItalic] = useState(false)
|
const [isItalic, setIsItalic] = useState(false)
|
||||||
const [isUnderline, setIsUnderline] = useState(false)
|
const [isUnderline, setIsUnderline] = useState(false)
|
||||||
@@ -471,6 +475,11 @@ function useFloatingTextFormatToolbar(editor: LexicalEditor, anchorElem: HTMLEle
|
|||||||
} else {
|
} else {
|
||||||
setIsLink(false)
|
setIsLink(false)
|
||||||
}
|
}
|
||||||
|
if ($isAutoLinkNode(parent) || $isAutoLinkNode(node)) {
|
||||||
|
setIsAutoLink(true)
|
||||||
|
} else {
|
||||||
|
setIsAutoLink(false)
|
||||||
|
}
|
||||||
|
|
||||||
if (!$isCodeHighlightNode(selection.anchor.getNode()) && selection.getTextContent() !== '') {
|
if (!$isCodeHighlightNode(selection.anchor.getNode()) && selection.getTextContent() !== '') {
|
||||||
setIsText($isTextNode(node))
|
setIsText($isTextNode(node))
|
||||||
@@ -515,6 +524,7 @@ function useFloatingTextFormatToolbar(editor: LexicalEditor, anchorElem: HTMLEle
|
|||||||
anchorElem={anchorElem}
|
anchorElem={anchorElem}
|
||||||
isText={isText}
|
isText={isText}
|
||||||
isLink={isLink}
|
isLink={isLink}
|
||||||
|
isAutoLink={isAutoLink}
|
||||||
isBold={isBold}
|
isBold={isBold}
|
||||||
isItalic={isItalic}
|
isItalic={isItalic}
|
||||||
isStrikethrough={isStrikethrough}
|
isStrikethrough={isStrikethrough}
|
||||||
|
|||||||
Reference in New Issue
Block a user