feat: Added "Table of contents" option to Super notes toolbar
This commit is contained in:
3
packages/icons/src/Icons/ic-toc.svg
Normal file
3
packages/icons/src/Icons/ic-toc.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M3 17v-2h14v2H3Zm0-4v-2h14v2H3Zm0-4V7h14v2H3Zm17 8q-.425 0-.713-.288T19 16q0-.425.288-.713T20 15q.425 0 .713.288T21 16q0 .425-.288.713T20 17Zm0-4q-.425 0-.713-.288T19 12q0-.425.288-.713T20 11q.425 0 .713.288T21 12q0 .425-.288.713T20 13Zm0-4q-.425 0-.713-.288T19 8q0-.425.288-.713T20 7q.425 0 .713.288T21 8q0 .425-.288.713T20 9Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 430 B |
@@ -214,6 +214,7 @@ import ViewIcon from './ic-view.svg'
|
|||||||
import WarningIcon from './ic-warning.svg'
|
import WarningIcon from './ic-warning.svg'
|
||||||
import WindowIcon from './ic-window.svg'
|
import WindowIcon from './ic-window.svg'
|
||||||
import DetailsBlockIcon from './ic-details-block.svg'
|
import DetailsBlockIcon from './ic-details-block.svg'
|
||||||
|
import TableOfContentsIcon from './ic-toc.svg'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AccessibilityIcon,
|
AccessibilityIcon,
|
||||||
@@ -408,6 +409,7 @@ export {
|
|||||||
SubtractIcon,
|
SubtractIcon,
|
||||||
SuperscriptIcon,
|
SuperscriptIcon,
|
||||||
SyncIcon,
|
SyncIcon,
|
||||||
|
TableOfContentsIcon,
|
||||||
TasksIcon,
|
TasksIcon,
|
||||||
TextCircleIcon,
|
TextCircleIcon,
|
||||||
TextIcon,
|
TextIcon,
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ export const IconNameToSvgMapping = {
|
|||||||
themes: icons.ThemesIcon,
|
themes: icons.ThemesIcon,
|
||||||
trash: icons.TrashIcon,
|
trash: icons.TrashIcon,
|
||||||
tune: icons.TuneIcon,
|
tune: icons.TuneIcon,
|
||||||
|
toc: icons.TableOfContentsIcon,
|
||||||
unarchive: icons.UnarchiveIcon,
|
unarchive: icons.UnarchiveIcon,
|
||||||
underline: icons.UnderlineIcon,
|
underline: icons.UnderlineIcon,
|
||||||
undo: icons.UndoIcon,
|
undo: icons.UndoIcon,
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ const StyledTooltip = ({
|
|||||||
const clickProps = isMobile
|
const clickProps = isMobile
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
onClick: () => setForceOpen(false),
|
onClick: () => tooltip.hide(),
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { mergeRegister, $findMatchingParent, $getNearestNodeOfType } from '@lexi
|
|||||||
import { $isLinkNode, $isAutoLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
|
import { $isLinkNode, $isAutoLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
|
||||||
import { $isListNode, ListNode } from '@lexical/list'
|
import { $isListNode, ListNode } from '@lexical/list'
|
||||||
import { $isHeadingNode } from '@lexical/rich-text'
|
import { $isHeadingNode } from '@lexical/rich-text'
|
||||||
import { ComponentPropsWithoutRef, useCallback, useEffect, useRef, useState } from 'react'
|
import { ComponentPropsWithoutRef, ForwardedRef, forwardRef, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { CenterAlignBlock, JustifyAlignBlock, LeftAlignBlock, RightAlignBlock } from '../Blocks/Alignment'
|
import { CenterAlignBlock, JustifyAlignBlock, LeftAlignBlock, RightAlignBlock } from '../Blocks/Alignment'
|
||||||
import { BulletedListBlock, ChecklistBlock, NumberedListBlock } from '../Blocks/List'
|
import { BulletedListBlock, ChecklistBlock, NumberedListBlock } from '../Blocks/List'
|
||||||
import { CodeBlock } from '../Blocks/Code'
|
import { CodeBlock } from '../Blocks/Code'
|
||||||
@@ -46,6 +46,10 @@ import { PasswordBlock } from '../Blocks/Password'
|
|||||||
import LinkEditor from './ToolbarLinkEditor'
|
import LinkEditor from './ToolbarLinkEditor'
|
||||||
import { FOCUSABLE_BUT_NOT_TABBABLE, URL_REGEX } from '@/Constants/Constants'
|
import { FOCUSABLE_BUT_NOT_TABBABLE, URL_REGEX } from '@/Constants/Constants'
|
||||||
import LinkTextEditor, { $isLinkTextNode } from './ToolbarLinkTextEditor'
|
import LinkTextEditor, { $isLinkTextNode } from './ToolbarLinkTextEditor'
|
||||||
|
import Popover from '@/Components/Popover/Popover'
|
||||||
|
import LexicalTableOfContents from '@lexical/react/LexicalTableOfContents'
|
||||||
|
import Menu from '@/Components/Menu/Menu'
|
||||||
|
import MenuItem from '@/Components/Menu/MenuItem'
|
||||||
|
|
||||||
const TOGGLE_LINK_AND_EDIT_COMMAND = createCommand<string | null>('TOGGLE_LINK_AND_EDIT_COMMAND')
|
const TOGGLE_LINK_AND_EDIT_COMMAND = createCommand<string | null>('TOGGLE_LINK_AND_EDIT_COMMAND')
|
||||||
|
|
||||||
@@ -71,36 +75,42 @@ interface ToolbarButtonProps extends ComponentPropsWithoutRef<'button'> {
|
|||||||
onSelect: () => void
|
onSelect: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ToolbarButton = ({ name, active, iconName, onSelect, disabled, ...props }: ToolbarButtonProps) => {
|
const ToolbarButton = forwardRef(
|
||||||
const [editor] = useLexicalComposerContext()
|
(
|
||||||
|
{ name, active, iconName, onSelect, disabled, ...props }: ToolbarButtonProps,
|
||||||
|
ref: ForwardedRef<HTMLButtonElement>,
|
||||||
|
) => {
|
||||||
|
const [editor] = useLexicalComposerContext()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTooltip showOnMobile showOnHover label={name} side="top">
|
<StyledTooltip showOnMobile showOnHover label={name} side="top">
|
||||||
<ToolbarItem
|
<ToolbarItem
|
||||||
className="flex select-none items-center justify-center rounded p-0.5 focus:shadow-none focus:outline-none enabled:hover:bg-default enabled:focus-visible:bg-default disabled:opacity-50 md:border md:border-transparent enabled:hover:md:translucent-ui:border-[--popover-border-color]"
|
className="flex select-none items-center justify-center rounded p-0.5 focus:shadow-none focus:outline-none enabled:hover:bg-default enabled:focus-visible:bg-default disabled:opacity-50 md:border md:border-transparent enabled:hover:md:translucent-ui:border-[--popover-border-color]"
|
||||||
onMouseDown={(event) => {
|
onMouseDown={(event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
onSelect()
|
onSelect()
|
||||||
}}
|
}}
|
||||||
onContextMenu={(event) => {
|
onContextMenu={(event) => {
|
||||||
editor.focus()
|
editor.focus()
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}}
|
}}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
{...props}
|
ref={ref}
|
||||||
>
|
{...props}
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
'flex items-center justify-center rounded p-2 transition-colors duration-75',
|
|
||||||
active && 'bg-info text-info-contrast',
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<Icon type={iconName} size="medium" className="!text-current [&>path]:!text-current" />
|
<div
|
||||||
</div>
|
className={classNames(
|
||||||
</ToolbarItem>
|
'flex items-center justify-center rounded p-2 transition-colors duration-75',
|
||||||
</StyledTooltip>
|
active && 'bg-info text-info-contrast',
|
||||||
)
|
)}
|
||||||
}
|
>
|
||||||
|
<Icon type={iconName} size="medium" className="!text-current [&>path]:!text-current" />
|
||||||
|
</div>
|
||||||
|
</ToolbarItem>
|
||||||
|
</StyledTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const ToolbarPlugin = () => {
|
const ToolbarPlugin = () => {
|
||||||
const application = useApplication()
|
const application = useApplication()
|
||||||
@@ -132,6 +142,9 @@ const ToolbarPlugin = () => {
|
|||||||
const [linkText, setLinkText] = useState<string>('')
|
const [linkText, setLinkText] = useState<string>('')
|
||||||
const [linkUrl, setLinkUrl] = useState<string>('')
|
const [linkUrl, setLinkUrl] = useState<string>('')
|
||||||
|
|
||||||
|
const [isTOCOpen, setIsTOCOpen] = useState(false)
|
||||||
|
const tocAnchorRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
const [canUndo, setCanUndo] = useState(false)
|
const [canUndo, setCanUndo] = useState(false)
|
||||||
const [canRedo, setCanRedo] = useState(false)
|
const [canRedo, setCanRedo] = useState(false)
|
||||||
|
|
||||||
@@ -451,6 +464,13 @@ const ToolbarPlugin = () => {
|
|||||||
ref={toolbarRef}
|
ref={toolbarRef}
|
||||||
store={toolbarStore}
|
store={toolbarStore}
|
||||||
>
|
>
|
||||||
|
<ToolbarButton
|
||||||
|
name="Table of Contents"
|
||||||
|
iconName="toc"
|
||||||
|
active={isTOCOpen}
|
||||||
|
onSelect={() => setIsTOCOpen(!isTOCOpen)}
|
||||||
|
ref={tocAnchorRef}
|
||||||
|
/>
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
name="Undo"
|
name="Undo"
|
||||||
iconName="undo"
|
iconName="undo"
|
||||||
@@ -646,6 +666,51 @@ const ToolbarPlugin = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Popover
|
||||||
|
title="Table of contents"
|
||||||
|
anchorElement={tocAnchorRef}
|
||||||
|
open={isTOCOpen}
|
||||||
|
togglePopover={() => setIsTOCOpen(!isTOCOpen)}
|
||||||
|
side="top"
|
||||||
|
align="center"
|
||||||
|
className="py-1"
|
||||||
|
disableMobileFullscreenTakeover
|
||||||
|
>
|
||||||
|
<div className="mb-1.5 mt-1 px-3 text-sm font-semibold uppercase text-text">Table of Contents</div>
|
||||||
|
<LexicalTableOfContents>
|
||||||
|
{(tableOfContents) => {
|
||||||
|
if (!tableOfContents.length) {
|
||||||
|
return <div className="py-2 text-center">No headings found</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu a11yLabel="Table of contents" isOpen>
|
||||||
|
{tableOfContents.map(([key, text, tag]) => (
|
||||||
|
<MenuItem
|
||||||
|
key={key}
|
||||||
|
className="overflow-hidden md:py-2"
|
||||||
|
onClick={() => {
|
||||||
|
setIsTOCOpen(false)
|
||||||
|
editor.getEditorState().read(() => {
|
||||||
|
const domElement = editor.getElementByKey(key)
|
||||||
|
if (!domElement) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
|
domElement.scrollIntoView({ behavior: reducedMotion ? 'auto' : 'smooth', block: 'nearest' })
|
||||||
|
editor.focus()
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type={tag} className="-mt-px mr-2.5 flex-shrink-0" />
|
||||||
|
<span className="overflow-hidden text-ellipsis whitespace-nowrap">{text}</span>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</LexicalTableOfContents>
|
||||||
|
</Popover>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user