fix: Added backspace button on Super toolbar on Android as a workaround for backspace issue
This commit is contained in:
4
packages/icons/src/Icons/ic-backspace.svg
Normal file
4
packages/icons/src/Icons/ic-backspace.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="m11.4 16l2.6-2.6l2.6 2.6l1.4-1.4l-2.6-2.6L18 9.4L16.6 8L14 10.6L11.4 8L10 9.4l2.6 2.6l-2.6 2.6l1.4 1.4ZM3 12l4.35-6.15q.275-.4.713-.625T9 5h10q.825 0 1.413.588T21 7v10q0 .825-.588 1.413T19 19H9q-.5 0-.938-.225t-.712-.625L3 12Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 330 B |
@@ -6,6 +6,7 @@ import AccountVariantIcon from './ic-account-variant.svg'
|
|||||||
import AddBoldIcon from './ic-add-bold.svg'
|
import AddBoldIcon from './ic-add-bold.svg'
|
||||||
import AddIcon from './ic-add.svg'
|
import AddIcon from './ic-add.svg'
|
||||||
import AddTextIcon from './ic-add-text.svg'
|
import AddTextIcon from './ic-add-text.svg'
|
||||||
|
import AegisIcon from './ic-aegis.svg'
|
||||||
import ArchiveIcon from './ic-archive.svg'
|
import ArchiveIcon from './ic-archive.svg'
|
||||||
import ArrowDownCheckmarkIcon from './arrow-down-checkmark.svg'
|
import ArrowDownCheckmarkIcon from './arrow-down-checkmark.svg'
|
||||||
import ArrowDownIcon from './ic-arrow-down.svg'
|
import ArrowDownIcon from './ic-arrow-down.svg'
|
||||||
@@ -21,6 +22,7 @@ import AttachmentFileIcon from './ic-attachment-file.svg'
|
|||||||
import AuthenticatorIcon from './ic-authenticator.svg'
|
import AuthenticatorIcon from './ic-authenticator.svg'
|
||||||
import AuthenticatorVariantIcon from './ic-authenticator-variant.svg'
|
import AuthenticatorVariantIcon from './ic-authenticator-variant.svg'
|
||||||
import BackIosIcon from './ic-back-ios.svg'
|
import BackIosIcon from './ic-back-ios.svg'
|
||||||
|
import BackspaceIcon from './ic-backspace.svg'
|
||||||
import BlockIcon from './ic-block.svg'
|
import BlockIcon from './ic-block.svg'
|
||||||
import BlueDotIcon from './blue-dot.svg'
|
import BlueDotIcon from './blue-dot.svg'
|
||||||
import BoldIcon from './ic-bold.svg'
|
import BoldIcon from './ic-bold.svg'
|
||||||
@@ -57,6 +59,7 @@ import EditorIcon from './ic-editor.svg'
|
|||||||
import EmailFilledIcon from './ic-email-filled.svg'
|
import EmailFilledIcon from './ic-email-filled.svg'
|
||||||
import EmailIcon from './ic-email.svg'
|
import EmailIcon from './ic-email.svg'
|
||||||
import EnterIcon from './ic-enter.svg'
|
import EnterIcon from './ic-enter.svg'
|
||||||
|
import EvernoteIcon from './ic-evernote.svg'
|
||||||
import EyeFilledIcon from './ic-eye-filled.svg'
|
import EyeFilledIcon from './ic-eye-filled.svg'
|
||||||
import EyeIcon from './ic-eye.svg'
|
import EyeIcon from './ic-eye.svg'
|
||||||
import EyeOffFilledIcon from './ic-eye-off-filled.svg'
|
import EyeOffFilledIcon from './ic-eye-off-filled.svg'
|
||||||
@@ -87,6 +90,7 @@ import ForwardIosIcon from './ic-forward-ios.svg'
|
|||||||
import FullscreenExitIcon from './ic-fullscreen-exit.svg'
|
import FullscreenExitIcon from './ic-fullscreen-exit.svg'
|
||||||
import FullscreenIcon from './ic-fullscreen.svg'
|
import FullscreenIcon from './ic-fullscreen.svg'
|
||||||
import GiftOutlineIcon from './ic-gift-outline.svg'
|
import GiftOutlineIcon from './ic-gift-outline.svg'
|
||||||
|
import GoogleKeepIcon from './ic-gkeep.svg'
|
||||||
import HashtagFilledIcon from './ic-hashtag-filled.svg'
|
import HashtagFilledIcon from './ic-hashtag-filled.svg'
|
||||||
import HashtagIcon from './ic-hashtag.svg'
|
import HashtagIcon from './ic-hashtag.svg'
|
||||||
import HashtagOffIcon from './ic-hashtag-off.svg'
|
import HashtagOffIcon from './ic-hashtag-off.svg'
|
||||||
@@ -112,9 +116,9 @@ import LineWidthIcon from './ic-line-width.svg'
|
|||||||
import LinkIcon from './ic-link.svg'
|
import LinkIcon from './ic-link.svg'
|
||||||
import LinkOffIcon from './ic-link-off.svg'
|
import LinkOffIcon from './ic-link-off.svg'
|
||||||
import ListBulleted from './ic-list-bulleted.svg'
|
import ListBulleted from './ic-list-bulleted.svg'
|
||||||
import ListNumbered from './ic-list-numbered.svg'
|
|
||||||
import ListedFilledIcon from './ic-listed-filled.svg'
|
import ListedFilledIcon from './ic-listed-filled.svg'
|
||||||
import ListedIcon from './ic-listed.svg'
|
import ListedIcon from './ic-listed.svg'
|
||||||
|
import ListNumbered from './ic-list-numbered.svg'
|
||||||
import LockFilledIcon from './ic-lock-filled.svg'
|
import LockFilledIcon from './ic-lock-filled.svg'
|
||||||
import LockIcon from './ic-lock.svg'
|
import LockIcon from './ic-lock.svg'
|
||||||
import MarkdownIcon from './ic-markdown.svg'
|
import MarkdownIcon from './ic-markdown.svg'
|
||||||
@@ -146,6 +150,8 @@ import PrintIcon from './ic-print.svg'
|
|||||||
import ProtectedIllustration from './il-protected.svg'
|
import ProtectedIllustration from './il-protected.svg'
|
||||||
import RedoIcon from './ic-redo.svg'
|
import RedoIcon from './ic-redo.svg'
|
||||||
import ReorderIcon from './ic-reorder.svg'
|
import ReorderIcon from './ic-reorder.svg'
|
||||||
|
import ReplaceAllIcon from './ic-replace-all.svg'
|
||||||
|
import ReplaceIcon from './ic-replace.svg'
|
||||||
import RestoreIcon from './ic-restore.svg'
|
import RestoreIcon from './ic-restore.svg'
|
||||||
import RichTextIcon from './ic-text-rich.svg'
|
import RichTextIcon from './ic-text-rich.svg'
|
||||||
import SafeIcon from './ic-safe.svg'
|
import SafeIcon from './ic-safe.svg'
|
||||||
@@ -164,6 +170,7 @@ import ShareIcon from './ic-share.svg'
|
|||||||
import ShortcutButtonIcon from './ic-shortcut-button.svg'
|
import ShortcutButtonIcon from './ic-shortcut-button.svg'
|
||||||
import SignInIcon from './ic-signin.svg'
|
import SignInIcon from './ic-signin.svg'
|
||||||
import SignOutIcon from './ic-signout.svg'
|
import SignOutIcon from './ic-signout.svg'
|
||||||
|
import SimplenoteIcon from './ic-simplenote.svg'
|
||||||
import SNLogoAltIcon from './ic-standard-notes-2.svg'
|
import SNLogoAltIcon from './ic-standard-notes-2.svg'
|
||||||
import SNLogoFull from './ic-sn-logo-full.svg'
|
import SNLogoFull from './ic-sn-logo-full.svg'
|
||||||
import SNLogoIcon from './ic-standard-notes.svg'
|
import SNLogoIcon from './ic-standard-notes.svg'
|
||||||
@@ -201,12 +208,6 @@ import UserSwitch from './ic-user-switch.svg'
|
|||||||
import ViewIcon from './ic-view.svg'
|
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 EvernoteIcon from './ic-evernote.svg'
|
|
||||||
import GoogleKeepIcon from './ic-gkeep.svg'
|
|
||||||
import SimplenoteIcon from './ic-simplenote.svg'
|
|
||||||
import AegisIcon from './ic-aegis.svg'
|
|
||||||
import ReplaceIcon from './ic-replace.svg'
|
|
||||||
import ReplaceAllIcon from './ic-replace-all.svg'
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AccessibilityIcon,
|
AccessibilityIcon,
|
||||||
@@ -232,6 +233,7 @@ export {
|
|||||||
AuthenticatorIcon,
|
AuthenticatorIcon,
|
||||||
AuthenticatorVariantIcon,
|
AuthenticatorVariantIcon,
|
||||||
BackIosIcon,
|
BackIosIcon,
|
||||||
|
BackspaceIcon,
|
||||||
BlockIcon,
|
BlockIcon,
|
||||||
BlueDotIcon,
|
BlueDotIcon,
|
||||||
BoldIcon,
|
BoldIcon,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const getColorsForPrimaryVariant = (style: AlertButtonStyle) => {
|
|||||||
type AlertButton = {
|
type AlertButton = {
|
||||||
text: string
|
text: string
|
||||||
style: AlertButtonStyle
|
style: AlertButtonStyle
|
||||||
action: () => void
|
action?: () => void
|
||||||
primary?: boolean
|
primary?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +168,9 @@ export class SKAlert {
|
|||||||
this.buttons.forEach((buttonDesc, index) => {
|
this.buttons.forEach((buttonDesc, index) => {
|
||||||
const buttonElem = this.element.querySelector(`#button-${index}`) as HTMLButtonElement
|
const buttonElem = this.element.querySelector(`#button-${index}`) as HTMLButtonElement
|
||||||
buttonElem.onclick = () => {
|
buttonElem.onclick = () => {
|
||||||
buttonDesc.action && buttonDesc.action()
|
if (buttonDesc.action) {
|
||||||
|
buttonDesc.action()
|
||||||
|
}
|
||||||
this.dismiss()
|
this.dismiss()
|
||||||
}
|
}
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export const IconNameToSvgMapping = {
|
|||||||
archive: icons.ArchiveIcon,
|
archive: icons.ArchiveIcon,
|
||||||
asterisk: icons.AsteriskIcon,
|
asterisk: icons.AsteriskIcon,
|
||||||
authenticator: icons.AuthenticatorIcon,
|
authenticator: icons.AuthenticatorIcon,
|
||||||
|
backspace: icons.BackspaceIcon,
|
||||||
bold: icons.BoldIcon,
|
bold: icons.BoldIcon,
|
||||||
camera: icons.CameraIcon,
|
camera: icons.CameraIcon,
|
||||||
check: icons.CheckIcon,
|
check: icons.CheckIcon,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import useModal from '../../Lexical/Hooks/useModal'
|
|||||||
import { InsertTableDialog } from '../../Plugins/TablePlugin'
|
import { InsertTableDialog } from '../../Plugins/TablePlugin'
|
||||||
import { getSelectedNode } from '../../Lexical/Utils/getSelectedNode'
|
import { getSelectedNode } from '../../Lexical/Utils/getSelectedNode'
|
||||||
import { sanitizeUrl } from '../../Lexical/Utils/sanitizeUrl'
|
import { sanitizeUrl } from '../../Lexical/Utils/sanitizeUrl'
|
||||||
import { $getSelection, $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical'
|
import { $getSelection, $isRangeSelection, DELETE_CHARACTER_COMMAND, FORMAT_TEXT_COMMAND } from 'lexical'
|
||||||
import { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
|
import { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { GetAlignmentBlocks } from '../Blocks/Alignment'
|
import { GetAlignmentBlocks } from '../Blocks/Alignment'
|
||||||
@@ -23,11 +23,14 @@ import { GetPasswordBlock } from '../Blocks/Password'
|
|||||||
import { GetQuoteBlock } from '../Blocks/Quote'
|
import { GetQuoteBlock } from '../Blocks/Quote'
|
||||||
import { GetTableBlock } from '../Blocks/Table'
|
import { GetTableBlock } from '../Blocks/Table'
|
||||||
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||||
import { classNames } from '@standardnotes/snjs'
|
import { classNames, Platform } from '@standardnotes/snjs'
|
||||||
import { SUPER_TOGGLE_SEARCH } from '@standardnotes/ui-services'
|
import { SUPER_TOGGLE_SEARCH } from '@standardnotes/ui-services'
|
||||||
import { useApplication } from '@/Components/ApplicationProvider'
|
import { useApplication } from '@/Components/ApplicationProvider'
|
||||||
import { GetRemoteImageBlock } from '../Blocks/RemoteImage'
|
import { GetRemoteImageBlock } from '../Blocks/RemoteImage'
|
||||||
import { InsertRemoteImageDialog } from '../RemoteImagePlugin/RemoteImagePlugin'
|
import { InsertRemoteImageDialog } from '../RemoteImagePlugin/RemoteImagePlugin'
|
||||||
|
import { SKAlert } from '@standardnotes/styles'
|
||||||
|
|
||||||
|
const DontShowSuperAndroidBackspaceAlertKey = 'dontShowSuperAndroidBackspaceAlert'
|
||||||
|
|
||||||
const MobileToolbarPlugin = () => {
|
const MobileToolbarPlugin = () => {
|
||||||
const application = useApplication()
|
const application = useApplication()
|
||||||
@@ -37,8 +40,10 @@ const MobileToolbarPlugin = () => {
|
|||||||
const [isInEditor, setIsInEditor] = useState(false)
|
const [isInEditor, setIsInEditor] = useState(false)
|
||||||
const [isInToolbar, setIsInToolbar] = useState(false)
|
const [isInToolbar, setIsInToolbar] = useState(false)
|
||||||
const isMobile = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
const isMobile = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||||
|
const isAndroid = application.platform === Platform.Android
|
||||||
|
|
||||||
const toolbarRef = useRef<HTMLDivElement>(null)
|
const toolbarRef = useRef<HTMLDivElement>(null)
|
||||||
|
const backspaceButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
const insertLink = useCallback(() => {
|
const insertLink = useCallback(() => {
|
||||||
const selection = $getSelection()
|
const selection = $getSelection()
|
||||||
@@ -156,7 +161,8 @@ const MobileToolbarPlugin = () => {
|
|||||||
|
|
||||||
const handleFocus = () => setIsInEditor(true)
|
const handleFocus = () => setIsInEditor(true)
|
||||||
const handleBlur = (event: FocusEvent) => {
|
const handleBlur = (event: FocusEvent) => {
|
||||||
if (toolbarRef.current?.contains(event.relatedTarget as Node)) {
|
const elementToBeFocused = event.relatedTarget as Node
|
||||||
|
if (toolbarRef.current?.contains(elementToBeFocused) || elementToBeFocused === backspaceButtonRef.current) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setIsInEditor(false)
|
setIsInEditor(false)
|
||||||
@@ -179,7 +185,13 @@ const MobileToolbarPlugin = () => {
|
|||||||
const toolbar = toolbarRef.current
|
const toolbar = toolbarRef.current
|
||||||
|
|
||||||
const handleFocus = () => setIsInToolbar(true)
|
const handleFocus = () => setIsInToolbar(true)
|
||||||
const handleBlur = () => setIsInToolbar(false)
|
const handleBlur = (event: FocusEvent) => {
|
||||||
|
const elementToBeFocused = event.relatedTarget as Node
|
||||||
|
if (elementToBeFocused === backspaceButtonRef.current) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setIsInToolbar(false)
|
||||||
|
}
|
||||||
|
|
||||||
toolbar.addEventListener('focus', handleFocus)
|
toolbar.addEventListener('focus', handleFocus)
|
||||||
toolbar.addEventListener('blur', handleBlur)
|
toolbar.addEventListener('blur', handleBlur)
|
||||||
@@ -190,6 +202,38 @@ const MobileToolbarPlugin = () => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAndroid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const dontShowAgain = application.getValue(DontShowSuperAndroidBackspaceAlertKey)
|
||||||
|
|
||||||
|
if (dontShowAgain) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const alert = new SKAlert({
|
||||||
|
title: 'Android backspace issue',
|
||||||
|
text: 'There is a known issue with Super on Android where pressing the backspace will also delete the character after the cursor. We are working on a fix for this. Please use the backspace button in the toolbar as a workaround.',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: 'OK',
|
||||||
|
style: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Don't show again",
|
||||||
|
style: 'default',
|
||||||
|
action: () => {
|
||||||
|
application.setValue(DontShowSuperAndroidBackspaceAlertKey, true)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
alert.present()
|
||||||
|
}, [application, isAndroid])
|
||||||
|
|
||||||
const isFocusInEditorOrToolbar = isInEditor || isInToolbar
|
const isFocusInEditorOrToolbar = isInEditor || isInToolbar
|
||||||
if (!isMobile || !isFocusInEditorOrToolbar) {
|
if (!isMobile || !isFocusInEditorOrToolbar) {
|
||||||
return null
|
return null
|
||||||
@@ -223,6 +267,20 @@ const MobileToolbarPlugin = () => {
|
|||||||
>
|
>
|
||||||
<Icon type="keyboard-close" size="medium" />
|
<Icon type="keyboard-close" size="medium" />
|
||||||
</button>
|
</button>
|
||||||
|
{isAndroid && (
|
||||||
|
<button
|
||||||
|
className="flex flex-shrink-0 items-center justify-center rounded border-l border-border py-3 px-3"
|
||||||
|
aria-label="Backspace"
|
||||||
|
ref={backspaceButtonRef}
|
||||||
|
onClick={() => {
|
||||||
|
editor.update(() => {
|
||||||
|
editor.dispatchCommand(DELETE_CHARACTER_COMMAND, true)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="backspace" size="medium" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user