fix: improve plaintext editor performance on mobile (#1855)
This commit is contained in:
@@ -1,34 +0,0 @@
|
|||||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
|
||||||
import { ComponentPropsWithoutRef, ForwardedRef, forwardRef } from 'react'
|
|
||||||
|
|
||||||
// Based on: https://css-tricks.com/auto-growing-inputs-textareas/#aa-other-ideas
|
|
||||||
const AutoresizingNoteViewTextarea = forwardRef(
|
|
||||||
(
|
|
||||||
{ value, className, ...textareaProps }: ComponentPropsWithoutRef<'textarea'>,
|
|
||||||
ref: ForwardedRef<HTMLTextAreaElement>,
|
|
||||||
) => {
|
|
||||||
return (
|
|
||||||
<div className="relative inline-grid min-h-[75vh] w-full grid-rows-1 items-stretch md:block md:flex-grow">
|
|
||||||
<pre
|
|
||||||
id="textarea-mobile-resizer"
|
|
||||||
className={classNames(
|
|
||||||
'editable font-editor break-word whitespace-pre-wrap',
|
|
||||||
'invisible [grid-area:1_/_1] md:hidden',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
aria-hidden
|
|
||||||
>
|
|
||||||
{value}{' '}
|
|
||||||
</pre>
|
|
||||||
<textarea
|
|
||||||
value={value}
|
|
||||||
className={classNames('editable font-editor [grid-area:1_/_1] md:h-full md:min-h-0', className)}
|
|
||||||
{...textareaProps}
|
|
||||||
ref={ref}
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
export default AutoresizingNoteViewTextarea
|
|
||||||
@@ -13,12 +13,11 @@ import {
|
|||||||
NoteViewController,
|
NoteViewController,
|
||||||
PayloadEmitSource,
|
PayloadEmitSource,
|
||||||
WebAppEvent,
|
WebAppEvent,
|
||||||
Platform,
|
|
||||||
EditorLineHeight,
|
EditorLineHeight,
|
||||||
EditorFontSize,
|
EditorFontSize,
|
||||||
NoteType,
|
NoteType,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { debounce, isDesktopApplication, isIOS } from '@/Utils'
|
import { debounce, isDesktopApplication } from '@/Utils'
|
||||||
import { EditorEventSource } from '../../Types/EditorEventSource'
|
import { EditorEventSource } from '../../Types/EditorEventSource'
|
||||||
import { confirmDialog, KeyboardModifier, KeyboardKey } from '@standardnotes/ui-services'
|
import { confirmDialog, KeyboardModifier, KeyboardKey } from '@standardnotes/ui-services'
|
||||||
import { STRING_DELETE_PLACEHOLDER_ATTEMPT, STRING_DELETE_LOCKED_ATTEMPT, StringDeleteNote } from '@/Constants/Strings'
|
import { STRING_DELETE_PLACEHOLDER_ATTEMPT, STRING_DELETE_LOCKED_ATTEMPT, StringDeleteNote } from '@/Constants/Strings'
|
||||||
@@ -39,7 +38,6 @@ import { reloadFont } from './FontFunctions'
|
|||||||
import { NoteViewProps } from './NoteViewProps'
|
import { NoteViewProps } from './NoteViewProps'
|
||||||
import IndicatorCircle from '../IndicatorCircle/IndicatorCircle'
|
import IndicatorCircle from '../IndicatorCircle/IndicatorCircle'
|
||||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||||
import AutoresizingNoteViewTextarea from './AutoresizingTextarea'
|
|
||||||
import MobileItemsListButton from '../NoteGroupView/MobileItemsListButton'
|
import MobileItemsListButton from '../NoteGroupView/MobileItemsListButton'
|
||||||
import LinkedItemBubblesContainer from '../LinkedItems/LinkedItemBubblesContainer'
|
import LinkedItemBubblesContainer from '../LinkedItems/LinkedItemBubblesContainer'
|
||||||
import NoteStatusIndicator, { NoteStatus } from './NoteStatusIndicator'
|
import NoteStatusIndicator, { NoteStatus } from './NoteStatusIndicator'
|
||||||
@@ -50,7 +48,6 @@ import NoteViewFileDropTarget from './NoteViewFileDropTarget'
|
|||||||
const MinimumStatusDuration = 400
|
const MinimumStatusDuration = 400
|
||||||
const TextareaDebounce = 100
|
const TextareaDebounce = 100
|
||||||
const NoteEditingDisabledText = 'Note editing disabled.'
|
const NoteEditingDisabledText = 'Note editing disabled.'
|
||||||
const StickyHeaderScrollThresholdInPx = 20
|
|
||||||
|
|
||||||
function sortAlphabetically(array: SNComponent[]): SNComponent[] {
|
function sortAlphabetically(array: SNComponent[]): SNComponent[] {
|
||||||
return array.sort((a, b) => (a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1))
|
return array.sort((a, b) => (a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1))
|
||||||
@@ -83,8 +80,6 @@ type State = {
|
|||||||
rightResizerWidth: number
|
rightResizerWidth: number
|
||||||
rightResizerOffset: number
|
rightResizerOffset: number
|
||||||
|
|
||||||
shouldStickyHeader: boolean
|
|
||||||
|
|
||||||
monospaceFont?: boolean
|
monospaceFont?: boolean
|
||||||
lineHeight?: EditorLineHeight
|
lineHeight?: EditorLineHeight
|
||||||
fontSize?: EditorFontSize
|
fontSize?: EditorFontSize
|
||||||
@@ -134,8 +129,6 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
|
|
||||||
this.textAreaChangeDebounceSave = debounce(this.textAreaChangeDebounceSave, TextareaDebounce)
|
this.textAreaChangeDebounceSave = debounce(this.textAreaChangeDebounceSave, TextareaDebounce)
|
||||||
|
|
||||||
this.handleWindowScroll = debounce(this.handleWindowScroll, 10)
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
availableStackComponents: [],
|
availableStackComponents: [],
|
||||||
editorStateDidLoad: false,
|
editorStateDidLoad: false,
|
||||||
@@ -155,23 +148,18 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
leftResizerOffset: 0,
|
leftResizerOffset: 0,
|
||||||
rightResizerWidth: 0,
|
rightResizerWidth: 0,
|
||||||
rightResizerOffset: 0,
|
rightResizerOffset: 0,
|
||||||
shouldStickyHeader: false,
|
|
||||||
editorFeatureIdentifier: this.controller.item.editorIdentifier,
|
editorFeatureIdentifier: this.controller.item.editorIdentifier,
|
||||||
noteType: this.controller.item.noteType,
|
noteType: this.controller.item.noteType,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.noteViewElementRef = createRef<HTMLDivElement>()
|
this.noteViewElementRef = createRef<HTMLDivElement>()
|
||||||
this.editorContentRef = createRef<HTMLDivElement>()
|
this.editorContentRef = createRef<HTMLDivElement>()
|
||||||
|
|
||||||
window.addEventListener('scroll', this.handleWindowScroll)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override deinit() {
|
override deinit() {
|
||||||
super.deinit()
|
super.deinit()
|
||||||
;(this.controller as unknown) = undefined
|
;(this.controller as unknown) = undefined
|
||||||
|
|
||||||
window.removeEventListener('scroll', this.handleWindowScroll)
|
|
||||||
|
|
||||||
this.removeComponentStreamObserver?.()
|
this.removeComponentStreamObserver?.()
|
||||||
;(this.removeComponentStreamObserver as unknown) = undefined
|
;(this.removeComponentStreamObserver as unknown) = undefined
|
||||||
|
|
||||||
@@ -932,12 +920,6 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleWindowScroll = () => {
|
|
||||||
this.setState({
|
|
||||||
shouldStickyHeader: window.scrollY > StickyHeaderScrollThresholdInPx,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
if (this.state.showProtectedWarning || !this.application.isAuthorizedToRenderItem(this.note)) {
|
if (this.state.showProtectedWarning || !this.application.isAuthorizedToRenderItem(this.note)) {
|
||||||
return (
|
return (
|
||||||
@@ -951,7 +933,11 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div aria-label="Note" className="section editor sn-component" ref={this.noteViewElementRef}>
|
<div
|
||||||
|
aria-label="Note"
|
||||||
|
className="section editor sn-component max-h-screen md:max-h-full"
|
||||||
|
ref={this.noteViewElementRef}
|
||||||
|
>
|
||||||
{this.note && (
|
{this.note && (
|
||||||
<NoteViewFileDropTarget
|
<NoteViewFileDropTarget
|
||||||
note={this.note}
|
note={this.note}
|
||||||
@@ -983,15 +969,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
{this.note && (
|
{this.note && (
|
||||||
<div
|
<div
|
||||||
id="editor-title-bar"
|
id="editor-title-bar"
|
||||||
className={classNames(
|
className="content-title-bar section-title-bar z-editor-title-bar w-full bg-default pt-4"
|
||||||
'content-title-bar section-title-bar z-editor-title-bar w-full bg-default',
|
|
||||||
this.state.shouldStickyHeader && 'fixed top-0',
|
|
||||||
this.state.shouldStickyHeader
|
|
||||||
? isIOS() || this.application.platform === Platform.Ios
|
|
||||||
? 'pt-safe-top'
|
|
||||||
: 'pt-4'
|
|
||||||
: '',
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<div className="mb-2 flex flex-wrap items-start justify-between gap-2 md:mb-0 md:flex-nowrap md:gap-4 xl:items-center">
|
<div className="mb-2 flex flex-wrap items-start justify-between gap-2 md:mb-0 md:flex-nowrap md:gap-4 xl:items-center">
|
||||||
<div className={classNames(this.state.noteLocked && 'locked', 'flex flex-grow items-center')}>
|
<div className={classNames(this.state.noteLocked && 'locked', 'flex flex-grow items-center')}>
|
||||||
@@ -1017,36 +995,32 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
updateSavingIndicator={this.state.updateSavingIndicator}
|
updateSavingIndicator={this.state.updateSavingIndicator}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!this.state.shouldStickyHeader && (
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex items-center gap-3">
|
<LinkedItemsButton
|
||||||
<LinkedItemsButton
|
filesController={this.viewControllerManager.filesController}
|
||||||
filesController={this.viewControllerManager.filesController}
|
linkingController={this.viewControllerManager.linkingController}
|
||||||
linkingController={this.viewControllerManager.linkingController}
|
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
/>
|
||||||
/>
|
<ChangeEditorButton
|
||||||
<ChangeEditorButton
|
application={this.application}
|
||||||
application={this.application}
|
viewControllerManager={this.viewControllerManager}
|
||||||
viewControllerManager={this.viewControllerManager}
|
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
/>
|
||||||
/>
|
<PinNoteButton
|
||||||
<PinNoteButton
|
notesController={this.viewControllerManager.notesController}
|
||||||
notesController={this.viewControllerManager.notesController}
|
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
/>
|
||||||
/>
|
<NotesOptionsPanel
|
||||||
<NotesOptionsPanel
|
application={this.application}
|
||||||
application={this.application}
|
navigationController={this.viewControllerManager.navigationController}
|
||||||
navigationController={this.viewControllerManager.navigationController}
|
notesController={this.viewControllerManager.notesController}
|
||||||
notesController={this.viewControllerManager.notesController}
|
linkingController={this.viewControllerManager.linkingController}
|
||||||
linkingController={this.viewControllerManager.linkingController}
|
historyModalController={this.viewControllerManager.historyModalController}
|
||||||
historyModalController={this.viewControllerManager.historyModalController}
|
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{!this.state.shouldStickyHeader && (
|
<LinkedItemBubblesContainer linkingController={this.viewControllerManager.linkingController} />
|
||||||
<LinkedItemBubblesContainer linkingController={this.viewControllerManager.linkingController} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -1082,12 +1056,8 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{this.state.editorStateDidLoad && !this.state.editorComponentViewer && !this.state.textareaUnloading && (
|
{this.state.editorStateDidLoad && !this.state.editorComponentViewer && !this.state.textareaUnloading && (
|
||||||
<AutoresizingNoteViewTextarea
|
<textarea
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
className={classNames(
|
|
||||||
this.state.lineHeight && `leading-${this.state.lineHeight.toLowerCase()}`,
|
|
||||||
this.state.fontSize && PlaintextFontSizeMapping[this.state.fontSize],
|
|
||||||
)}
|
|
||||||
dir="auto"
|
dir="auto"
|
||||||
id={ElementIds.NoteTextEditor}
|
id={ElementIds.NoteTextEditor}
|
||||||
onChange={this.onTextAreaChange}
|
onChange={this.onTextAreaChange}
|
||||||
@@ -1096,7 +1066,12 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
ref={(ref) => ref && this.onSystemEditorLoad(ref)}
|
ref={(ref) => ref && this.onSystemEditorLoad(ref)}
|
||||||
spellCheck={this.state.spellcheck}
|
spellCheck={this.state.spellcheck}
|
||||||
value={this.state.editorText}
|
value={this.state.editorText}
|
||||||
/>
|
className={classNames(
|
||||||
|
'editable font-editor flex-grow',
|
||||||
|
this.state.lineHeight && `leading-${this.state.lineHeight.toLowerCase()}`,
|
||||||
|
this.state.fontSize && PlaintextFontSizeMapping[this.state.fontSize],
|
||||||
|
)}
|
||||||
|
></textarea>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.state.marginResizersEnabled && this.editorContentRef.current ? (
|
{this.state.marginResizersEnabled && this.editorContentRef.current ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user