feat: sticky header on mobile if plaintext note is scrolled (#1645)

This commit is contained in:
Aman Harwara
2022-09-26 21:15:11 +05:30
committed by GitHub
parent 58cd919267
commit f8f6a5212b

View File

@@ -14,7 +14,7 @@ import {
PayloadEmitSource, PayloadEmitSource,
WebAppEvent, WebAppEvent,
} from '@standardnotes/snjs' } from '@standardnotes/snjs'
import { debounce, isDesktopApplication } from '@/Utils' import { debounce, isDesktopApplication, isIOS } 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'
@@ -22,7 +22,6 @@ import { PureComponent } from '@/Components/Abstract/PureComponent'
import ProtectedItemOverlay from '@/Components/ProtectedItemOverlay/ProtectedItemOverlay' import ProtectedItemOverlay from '@/Components/ProtectedItemOverlay/ProtectedItemOverlay'
import PinNoteButton from '@/Components/PinNoteButton/PinNoteButton' import PinNoteButton from '@/Components/PinNoteButton/PinNoteButton'
import NotesOptionsPanel from '@/Components/NotesOptions/NotesOptionsPanel' import NotesOptionsPanel from '@/Components/NotesOptions/NotesOptionsPanel'
import NoteTagsContainer from '@/Components/NoteTags/NoteTagsContainer'
import ComponentView from '@/Components/ComponentView/ComponentView' import ComponentView from '@/Components/ComponentView/ComponentView'
import PanelResizer, { PanelSide, PanelResizeType } from '@/Components/PanelResizer/PanelResizer' import PanelResizer, { PanelSide, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
import { ElementIds } from '@/Constants/ElementIDs' import { ElementIds } from '@/Constants/ElementIDs'
@@ -41,9 +40,10 @@ import AutoresizingNoteViewTextarea from './AutoresizingTextarea'
import MobileItemsListButton from '../NoteGroupView/MobileItemsListButton' import MobileItemsListButton from '../NoteGroupView/MobileItemsListButton'
import NoteTagsPanel from '../NoteTags/NoteTagsPanel' import NoteTagsPanel from '../NoteTags/NoteTagsPanel'
const MINIMUM_STATUS_DURATION = 400 const MinimumStatusDuration = 400
const TEXTAREA_DEBOUNCE = 100 const TextareaDebounce = 100
const NOTE_EDITING_DISABLED_TEXT = 'Note editing disabled.' const NoteEditingDisabledText = 'Note editing disabled.'
const StickyHeaderScrollThresholdInPx = 20
type NoteStatus = { type NoteStatus = {
message?: string message?: string
@@ -81,6 +81,8 @@ type State = {
leftResizerOffset: number leftResizerOffset: number
rightResizerWidth: number rightResizerWidth: number
rightResizerOffset: number rightResizerOffset: number
shouldStickyHeader: boolean
} }
class NoteView extends PureComponent<NoteViewProps, State> { class NoteView extends PureComponent<NoteViewProps, State> {
@@ -112,7 +114,9 @@ class NoteView extends PureComponent<NoteViewProps, State> {
this.debounceReloadEditorComponent = debounce(this.debounceReloadEditorComponent.bind(this), 25) this.debounceReloadEditorComponent = debounce(this.debounceReloadEditorComponent.bind(this), 25)
this.textAreaChangeDebounceSave = debounce(this.textAreaChangeDebounceSave, TEXTAREA_DEBOUNCE) this.textAreaChangeDebounceSave = debounce(this.textAreaChangeDebounceSave, TextareaDebounce)
this.handleWindowScroll = debounce(this.handleWindowScroll, 10)
this.state = { this.state = {
availableStackComponents: [], availableStackComponents: [],
@@ -120,7 +124,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
editorText: '', editorText: '',
editorTitle: '', editorTitle: '',
isDesktop: isDesktopApplication(), isDesktop: isDesktopApplication(),
lockText: NOTE_EDITING_DISABLED_TEXT, lockText: NoteEditingDisabledText,
noteStatus: undefined, noteStatus: undefined,
noteLocked: this.controller.item.locked, noteLocked: this.controller.item.locked,
showLockedIcon: true, showLockedIcon: true,
@@ -133,12 +137,17 @@ class NoteView extends PureComponent<NoteViewProps, State> {
leftResizerOffset: 0, leftResizerOffset: 0,
rightResizerWidth: 0, rightResizerWidth: 0,
rightResizerOffset: 0, rightResizerOffset: 0,
shouldStickyHeader: false,
} }
this.editorContentRef = createRef<HTMLDivElement>() this.editorContentRef = createRef<HTMLDivElement>()
window.addEventListener('scroll', this.handleWindowScroll)
} }
override deinit() { override deinit() {
window.removeEventListener('scroll', this.handleWindowScroll)
this.removeComponentStreamObserver?.() this.removeComponentStreamObserver?.()
;(this.removeComponentStreamObserver as unknown) = undefined ;(this.removeComponentStreamObserver as unknown) = undefined
@@ -522,7 +531,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
this.setState({ this.setState({
noteStatus: status, noteStatus: status,
}) })
}, MINIMUM_STATUS_DURATION) }, MinimumStatusDuration)
} else { } else {
this.setState({ this.setState({
noteStatus: status, noteStatus: status,
@@ -872,6 +881,12 @@ class NoteView extends PureComponent<NoteViewProps, State> {
} }
} }
handleWindowScroll = () => {
this.setState({
shouldStickyHeader: window.scrollY > StickyHeaderScrollThresholdInPx,
})
}
override render() { override render() {
if (this.state.showProtectedWarning) { if (this.state.showProtectedWarning) {
return ( return (
@@ -890,7 +905,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
<EditingDisabledBanner <EditingDisabledBanner
onMouseLeave={() => { onMouseLeave={() => {
this.setState({ this.setState({
lockText: NOTE_EDITING_DISABLED_TEXT, lockText: NoteEditingDisabledText,
showLockedIcon: true, showLockedIcon: true,
}) })
}} }}
@@ -907,7 +922,14 @@ class NoteView extends PureComponent<NoteViewProps, State> {
)} )}
{this.note && ( {this.note && (
<div id="editor-title-bar" className="content-title-bar section-title-bar z-editor-title-bar w-full"> <div
id="editor-title-bar"
className={classNames(
'content-title-bar section-title-bar z-editor-title-bar w-full bg-default',
this.state.shouldStickyHeader && 'fixed top-0',
this.state.shouldStickyHeader ? (isIOS() ? '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-0 xl:items-center"> <div className="mb-2 flex flex-wrap items-start justify-between gap-2 md:mb-0 md:flex-nowrap md:gap-0 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')}>
<MobileItemsListButton /> <MobileItemsListButton />
@@ -927,62 +949,62 @@ class NoteView extends PureComponent<NoteViewProps, State> {
/> />
</div> </div>
</div> </div>
<div className="flex flex-row-reverse items-center gap-3 md:flex-col-reverse md:items-end xl:flex-row xl:flex-nowrap xl:items-center"> {!this.state.shouldStickyHeader && (
{this.state.noteStatus?.message?.length && ( <div className="flex flex-row-reverse items-center gap-3 md:flex-col-reverse md:items-end xl:flex-row xl:flex-nowrap xl:items-center">
<div id="save-status-container" className={'xl:mr-5 xl:max-w-[16ch]'}> {this.state.noteStatus?.message?.length && (
<div id="save-status"> <div id="save-status-container" className={'xl:mr-5 xl:max-w-[16ch]'}>
<div <div id="save-status">
className={ <div
(this.state.syncTakingTooLong ? 'font-bold text-warning ' : '') + className={
(this.state.saveError ? 'font-bold text-danger ' : '') + (this.state.syncTakingTooLong ? 'font-bold text-warning ' : '') +
'message text-xs' (this.state.saveError ? 'font-bold text-danger ' : '') +
} 'message text-xs'
> }
{this.state.noteStatus?.message} >
{this.state.noteStatus?.message}
</div>
{this.state.noteStatus?.desc && (
<div className="desc text-xs">{this.state.noteStatus.desc}</div>
)}
</div> </div>
{this.state.noteStatus?.desc && <div className="desc text-xs">{this.state.noteStatus.desc}</div>}
</div> </div>
)}
<div className="flex items-center gap-3">
<NoteTagsPanel
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
noteTagsController={this.viewControllerManager.noteTagsController}
/>
<AttachedFilesButton
application={this.application}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
featuresController={this.viewControllerManager.featuresController}
filePreviewModalController={this.viewControllerManager.filePreviewModalController}
filesController={this.viewControllerManager.filesController}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
selectionController={this.viewControllerManager.selectionController}
/>
<ChangeEditorButton
application={this.application}
viewControllerManager={this.viewControllerManager}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
<PinNoteButton
notesController={this.viewControllerManager.notesController}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
<NotesOptionsPanel
application={this.application}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
noteTagsController={this.viewControllerManager.noteTagsController}
historyModalController={this.viewControllerManager.historyModalController}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
</div> </div>
)}
<div className="flex items-center gap-3">
<NoteTagsPanel
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
noteTagsController={this.viewControllerManager.noteTagsController}
/>
<AttachedFilesButton
application={this.application}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
featuresController={this.viewControllerManager.featuresController}
filePreviewModalController={this.viewControllerManager.filePreviewModalController}
filesController={this.viewControllerManager.filesController}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
selectionController={this.viewControllerManager.selectionController}
/>
<ChangeEditorButton
application={this.application}
viewControllerManager={this.viewControllerManager}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
<PinNoteButton
notesController={this.viewControllerManager.notesController}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
<NotesOptionsPanel
application={this.application}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
noteTagsController={this.viewControllerManager.noteTagsController}
historyModalController={this.viewControllerManager.historyModalController}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
</div> </div>
</div> )}
</div> </div>
<NoteTagsContainer
noteTagsController={this.viewControllerManager.noteTagsController}
navigationController={this.viewControllerManager.navigationController}
/>
</div> </div>
)} )}