feat: Added preference to toggle Super note toolbar visibility. When toggled off, the toolbar will only be visible when text is selected as a floating toolbar. [skip e2e]

This commit is contained in:
Aman Harwara
2023-10-26 01:17:33 +05:30
parent 5f61244ec8
commit a616750aea
4 changed files with 266 additions and 120 deletions

View File

@@ -45,6 +45,7 @@ export const PrefDefaults = {
[PrefKey.ComponentPreferences]: {}, [PrefKey.ComponentPreferences]: {},
[PrefKey.ActiveThemes]: [], [PrefKey.ActiveThemes]: [],
[PrefKey.ActiveComponents]: [], [PrefKey.ActiveComponents]: [],
[PrefKey.AlwaysShowSuperToolbar]: true,
} satisfies { } satisfies {
[key in PrefKey]: PrefValue[key] [key in PrefKey]: PrefValue[key]
} }

View File

@@ -46,6 +46,7 @@ export enum PrefKey {
ComponentPreferences = 'componentPreferences', ComponentPreferences = 'componentPreferences',
ActiveThemes = 'activeThemes', ActiveThemes = 'activeThemes',
ActiveComponents = 'activeComponents', ActiveComponents = 'activeComponents',
AlwaysShowSuperToolbar = 'alwaysShowSuperToolbar',
} }
export type PrefValue = { export type PrefValue = {
@@ -87,4 +88,5 @@ export type PrefValue = {
[PrefKey.ComponentPreferences]: AllComponentPreferences [PrefKey.ComponentPreferences]: AllComponentPreferences
[PrefKey.ActiveThemes]: string[] [PrefKey.ActiveThemes]: string[]
[PrefKey.ActiveComponents]: string[] [PrefKey.ActiveComponents]: string[]
[PrefKey.AlwaysShowSuperToolbar]: boolean
} }

View File

@@ -1,4 +1,4 @@
import { PrefKey, Platform, PrefDefaults } from '@standardnotes/snjs' import { PrefKey, Platform } from '@standardnotes/snjs'
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content' import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { WebApplication } from '@/Application/WebApplication' import { WebApplication } from '@/Application/WebApplication'
import { FunctionComponent, useState } from 'react' import { FunctionComponent, useState } from 'react'
@@ -6,6 +6,8 @@ import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import Switch from '@/Components/Switch/Switch' import Switch from '@/Components/Switch/Switch'
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup' import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment' import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
import usePreference from '@/Hooks/usePreference'
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
type Props = { type Props = {
application: WebApplication application: WebApplication
@@ -18,16 +20,15 @@ const Defaults: FunctionComponent<Props> = ({ application }) => {
() => (application.getValue(AndroidConfirmBeforeExitKey) as boolean) ?? true, () => (application.getValue(AndroidConfirmBeforeExitKey) as boolean) ?? true,
) )
const [spellcheck, setSpellcheck] = useState(() => const isMobile = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
application.getPreference(PrefKey.EditorSpellcheck, PrefDefaults[PrefKey.EditorSpellcheck]),
)
const [addNoteToParentFolders, setAddNoteToParentFolders] = useState(() => const spellcheck = usePreference(PrefKey.EditorSpellcheck)
application.getPreference(PrefKey.NoteAddToParentFolders, PrefDefaults[PrefKey.NoteAddToParentFolders]),
) const addNoteToParentFolders = usePreference(PrefKey.NoteAddToParentFolders)
const alwaysShowSuperToolbar = usePreference(PrefKey.AlwaysShowSuperToolbar)
const toggleSpellcheck = () => { const toggleSpellcheck = () => {
setSpellcheck(!spellcheck)
application.toggleGlobalSpellcheck().catch(console.error) application.toggleGlobalSpellcheck().catch(console.error)
} }
@@ -72,11 +73,29 @@ const Defaults: FunctionComponent<Props> = ({ application }) => {
<Switch <Switch
onChange={() => { onChange={() => {
application.setPreference(PrefKey.NoteAddToParentFolders, !addNoteToParentFolders).catch(console.error) application.setPreference(PrefKey.NoteAddToParentFolders, !addNoteToParentFolders).catch(console.error)
setAddNoteToParentFolders(!addNoteToParentFolders)
}} }}
checked={addNoteToParentFolders} checked={addNoteToParentFolders}
/> />
</div> </div>
<HorizontalSeparator classes="my-4" />
{!isMobile && (
<div className="flex justify-between gap-2 md:items-center">
<div className="flex flex-col">
<Subtitle>Use always-visible toolbar in Super notes</Subtitle>
<Text>
When enabled, the Super toolbar will always be shown at the top of the note. It can be temporarily
toggled using Cmd/Ctrl+Shift+K. When disabled, the Super toolbar will only be shown as a floating
toolbar when text is selected.
</Text>
</div>
<Switch
onChange={() => {
application.setPreference(PrefKey.AlwaysShowSuperToolbar, !alwaysShowSuperToolbar).catch(console.error)
}}
checked={alwaysShowSuperToolbar}
/>
</div>
)}
</PreferencesSegment> </PreferencesSegment>
</PreferencesGroup> </PreferencesGroup>
) )

View File

@@ -45,7 +45,7 @@ import { IndentBlock, OutdentBlock } from '../Blocks/IndentOutdent'
import { ParagraphBlock } from '../Blocks/Paragraph' import { ParagraphBlock } from '../Blocks/Paragraph'
import { QuoteBlock } from '../Blocks/Quote' import { QuoteBlock } from '../Blocks/Quote'
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery' import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
import { classNames } from '@standardnotes/snjs' import { PrefKey, classNames } from '@standardnotes/snjs'
import { SUPER_TOGGLE_SEARCH, SUPER_TOGGLE_TOOLBAR } from '@standardnotes/ui-services' import { SUPER_TOGGLE_SEARCH, SUPER_TOGGLE_TOOLBAR } from '@standardnotes/ui-services'
import { useApplication } from '@/Components/ApplicationProvider' import { useApplication } from '@/Components/ApplicationProvider'
import { InsertRemoteImageDialog } from '../RemoteImagePlugin/RemoteImagePlugin' import { InsertRemoteImageDialog } from '../RemoteImagePlugin/RemoteImagePlugin'
@@ -58,9 +58,13 @@ import Popover from '@/Components/Popover/Popover'
import LexicalTableOfContents from '@lexical/react/LexicalTableOfContents' import LexicalTableOfContents from '@lexical/react/LexicalTableOfContents'
import Menu from '@/Components/Menu/Menu' import Menu from '@/Components/Menu/Menu'
import MenuItem, { MenuItemProps } from '@/Components/Menu/MenuItem' import MenuItem, { MenuItemProps } from '@/Components/Menu/MenuItem'
import { remToPx } from '@/Utils' import { debounce, remToPx } from '@/Utils'
import FloatingLinkEditor from './FloatingLinkEditor' import FloatingLinkEditor from './FloatingLinkEditor'
import MenuItemSeparator from '@/Components/Menu/MenuItemSeparator' import MenuItemSeparator from '@/Components/Menu/MenuItemSeparator'
import { useStateRef } from '@/Hooks/useStateRef'
import { getDOMRangeRect } from '../../Lexical/Utils/getDOMRangeRect'
import { getPositionedPopoverStyles } from '@/Components/Popover/GetPositionedPopoverStyles'
import usePreference from '@/Hooks/usePreference'
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')
@@ -215,9 +219,74 @@ const ToolbarPlugin = () => {
const [canUndo, setCanUndo] = useState(false) const [canUndo, setCanUndo] = useState(false)
const [canRedo, setCanRedo] = useState(false) const [canRedo, setCanRedo] = useState(false)
const containerRef = useRef<HTMLDivElement>(null)
const alwaysShowToolbar = usePreference(PrefKey.AlwaysShowSuperToolbar)
const [isToolbarFixedToTop, setIsToolbarFixedToTop] = useState(alwaysShowToolbar)
const isToolbarFixedRef = useStateRef(isToolbarFixedToTop)
const updateToolbarFloatingPosition = useCallback(() => {
const selection = $getSelection()
if (!$isRangeSelection(selection)) {
return
}
if (isMobile) {
return
}
if (isToolbarFixedRef.current) {
return
}
const containerElement = containerRef.current
if (!containerElement) {
return
}
if (selection.getTextContent() === '') {
containerElement.style.removeProperty('opacity')
return
}
const nativeSelection = window.getSelection()
const rootElement = activeEditor.getRootElement()
if (nativeSelection !== null && rootElement !== null && rootElement.contains(nativeSelection.anchorNode)) {
const rangeRect = getDOMRangeRect(nativeSelection, rootElement)
const containerRect = containerElement.getBoundingClientRect()
const rootRect = rootElement.getBoundingClientRect()
const calculatedStyles = getPositionedPopoverStyles({
align: 'start',
side: 'top',
anchorRect: rangeRect,
popoverRect: containerRect,
documentRect: rootRect,
offset: 8,
maxHeightFunction: () => 'none',
})
if (calculatedStyles) {
Object.entries(calculatedStyles).forEach(([key, value]) => {
if (key === 'transform') {
return
}
containerElement.style.setProperty(key, value)
})
containerElement.style.setProperty('opacity', '1')
}
}
}, [activeEditor, isMobile, isToolbarFixedRef])
const $updateToolbar = useCallback(() => { const $updateToolbar = useCallback(() => {
const selection = $getSelection() const selection = $getSelection()
if ($isRangeSelection(selection)) { if (!$isRangeSelection(selection)) {
return
}
const anchorNode = selection.anchor.getNode() const anchorNode = selection.anchor.getNode()
let element = let element =
anchorNode.getKey() === 'root' anchorNode.getKey() === 'root'
@@ -280,8 +349,44 @@ const ToolbarPlugin = () => {
} }
setElementFormat(($isElementNode(node) ? node.getFormatType() : parent?.getFormatType()) || 'left') setElementFormat(($isElementNode(node) ? node.getFormatType() : parent?.getFormatType()) || 'left')
updateToolbarFloatingPosition()
}, [activeEditor, updateToolbarFloatingPosition])
const clearContainerFloatingStyles = useCallback(() => {
const containerElement = containerRef.current
if (!containerElement) {
return
} }
}, [activeEditor]) containerElement.style.removeProperty('--translate-x')
containerElement.style.removeProperty('--translate-y')
containerElement.style.removeProperty('transform')
containerElement.style.removeProperty('transform-origin')
containerElement.style.removeProperty('opacity')
}, [])
useEffect(() => {
const scrollerElem = activeEditor.getRootElement()
const update = () => {
activeEditor.getEditorState().read(() => {
updateToolbarFloatingPosition()
})
}
const debouncedUpdate = debounce(update, 50)
window.addEventListener('resize', debouncedUpdate)
if (scrollerElem) {
scrollerElem.addEventListener('scroll', debouncedUpdate)
}
return () => {
window.removeEventListener('resize', debouncedUpdate)
if (scrollerElem) {
scrollerElem.removeEventListener('scroll', debouncedUpdate)
}
}
}, [activeEditor, updateToolbarFloatingPosition])
useEffect(() => { useEffect(() => {
return mergeRegister( return mergeRegister(
@@ -379,14 +484,12 @@ const ToolbarPlugin = () => {
) )
}, [activeEditor, isLink]) }, [activeEditor, isLink])
const containerRef = useRef<HTMLDivElement>(null)
const dismissButtonRef = useRef<HTMLButtonElement>(null) const dismissButtonRef = useRef<HTMLButtonElement>(null)
const [isFocusInEditor, setIsFocusInEditor] = useState(false) const [isFocusInEditor, setIsFocusInEditor] = useState(false)
const [isFocusInToolbar, setIsFocusInToolbar] = useState(false) const [isFocusInToolbar, setIsFocusInToolbar] = useState(false)
const isFocusInEditorOrToolbar = isFocusInEditor || isFocusInToolbar const canShowToolbarOnMobile = isFocusInEditor || isFocusInToolbar
const [isToolbarVisible, setIsToolbarVisible] = useState(true) const canShowAllItems = isMobile || isToolbarFixedToTop
const canShowToolbar = isMobile ? isFocusInEditorOrToolbar : isToolbarVisible
useEffect(() => { useEffect(() => {
const container = containerRef.current const container = containerRef.current
@@ -438,24 +541,32 @@ const ToolbarPlugin = () => {
if (isMobile) { if (isMobile) {
return return
} }
event.preventDefault() if (!alwaysShowToolbar) {
if (!isToolbarVisible) {
setIsToolbarVisible(true)
toolbarStore.move(toolbarStore.first())
return return
} }
const isFocusInContainer = containerRef.current?.contains(document.activeElement) event.preventDefault()
if (isFocusInContainer) {
setIsToolbarVisible(false) if (!isToolbarFixedToTop) {
editor.focus() setIsToolbarFixedToTop(true)
} else { clearContainerFloatingStyles()
toolbarStore.move(toolbarStore.first()) toolbarStore.move(toolbarStore.first())
return
} else {
setIsToolbarFixedToTop(false)
editor.focus()
} }
}, },
}) })
}, [application.keyboardService, editor, isMobile, isToolbarVisible, toolbarStore]) }, [
alwaysShowToolbar,
application.keyboardService,
clearContainerFloatingStyles,
editor,
isMobile,
isToolbarFixedToTop,
toolbarStore,
])
return ( return (
<> <>
@@ -463,8 +574,15 @@ const ToolbarPlugin = () => {
<div <div
className={classNames( className={classNames(
'bg-contrast', 'bg-contrast',
'md:w-full md:border-b md:border-border md:px-1 md:py-1 md:translucent-ui:border-[--popover-border-color] md:translucent-ui:bg-[--popover-background-color] md:translucent-ui:[backdrop-filter:var(--popover-backdrop-filter)]', !isEditable ? 'hidden opacity-0' : '',
!canShowToolbar || !isEditable ? 'hidden' : '', isMobile && !canShowToolbarOnMobile ? 'hidden' : '',
!isMobile &&
'border-b border-border translucent-ui:border-[--popover-border-color] translucent-ui:bg-[--popover-background-color] translucent-ui:[backdrop-filter:var(--popover-backdrop-filter)]',
!isMobile
? !isToolbarFixedToTop
? 'fixed left-0 top-0 z-tooltip translate-x-[--translate-x] translate-y-[--translate-y] rounded border py-0.5 opacity-0'
: 'w-full px-1 py-1'
: '',
)} )}
id="super-mobile-toolbar" id="super-mobile-toolbar"
ref={containerRef} ref={containerRef}
@@ -487,6 +605,8 @@ const ToolbarPlugin = () => {
ref={toolbarRef} ref={toolbarRef}
store={toolbarStore} store={toolbarStore}
> >
{canShowAllItems && (
<>
<ToolbarButton <ToolbarButton
name="Table of Contents" name="Table of Contents"
iconName="toc" iconName="toc"
@@ -511,6 +631,8 @@ const ToolbarPlugin = () => {
disabled={!canRedo} disabled={!canRedo}
onSelect={() => editor.dispatchCommand(REDO_COMMAND, undefined)} onSelect={() => editor.dispatchCommand(REDO_COMMAND, undefined)}
/> />
</>
)}
<ToolbarButton <ToolbarButton
name="Bold" name="Bold"
iconName="bold" iconName="bold"
@@ -586,6 +708,7 @@ const ToolbarPlugin = () => {
iconName={OutdentBlock.iconName} iconName={OutdentBlock.iconName}
onSelect={() => OutdentBlock.onSelect(editor)} onSelect={() => OutdentBlock.onSelect(editor)}
/> />
{canShowAllItems && (
<ToolbarButton <ToolbarButton
name="Insert" name="Insert"
onSelect={() => { onSelect={() => {
@@ -597,6 +720,7 @@ const ToolbarPlugin = () => {
<Icon type="add" size="custom" className="h-4 w-4 md:h-3.5 md:w-3.5" /> <Icon type="add" size="custom" className="h-4 w-4 md:h-3.5 md:w-3.5" />
<Icon type="chevron-down" size="custom" className="ml-2 h-4 w-4 md:h-3.5 md:w-3.5" /> <Icon type="chevron-down" size="custom" className="ml-2 h-4 w-4 md:h-3.5 md:w-3.5" />
</ToolbarButton> </ToolbarButton>
)}
</Toolbar> </Toolbar>
{isMobile && ( {isMobile && (
<button <button