chore: add clipper extension package (#2281)
This commit is contained in:
@@ -1,31 +1,19 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { noteTypeForEditorIdentifier } from '@standardnotes/features'
|
||||
import { InfoStrings } from '@standardnotes/services'
|
||||
import {
|
||||
NoteMutator,
|
||||
SNNote,
|
||||
SNTag,
|
||||
NoteContent,
|
||||
DecryptedItemInterface,
|
||||
PayloadEmitSource,
|
||||
PrefKey,
|
||||
} from '@standardnotes/models'
|
||||
import { SNNote, SNTag, NoteContent, DecryptedItemInterface, PayloadEmitSource, PrefKey } from '@standardnotes/models'
|
||||
import { UuidString } from '@standardnotes/snjs'
|
||||
import { removeFromArray, Deferred } from '@standardnotes/utils'
|
||||
import { removeFromArray } from '@standardnotes/utils'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ItemViewControllerInterface } from './ItemViewControllerInterface'
|
||||
import { TemplateNoteViewControllerOptions } from './TemplateNoteViewControllerOptions'
|
||||
import { EditorSaveTimeoutDebounce } from './EditorSaveTimeoutDebounce'
|
||||
import { log, LoggingDomain } from '@/Logging'
|
||||
import { NoteSaveFunctionParams, NoteSyncController } from '../../../Controllers/NoteSyncController'
|
||||
|
||||
export type EditorValues = {
|
||||
title: string
|
||||
text: string
|
||||
}
|
||||
|
||||
const StringEllipses = '...'
|
||||
const NotePreviewCharLimit = 160
|
||||
|
||||
export class NoteViewController implements ItemViewControllerInterface {
|
||||
public item!: SNNote
|
||||
public dealloced = false
|
||||
@@ -35,10 +23,10 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
|
||||
private innerValueChangeObservers: ((note: SNNote, source: PayloadEmitSource) => void)[] = []
|
||||
private disposers: (() => void)[] = []
|
||||
private saveTimeout?: ReturnType<typeof setTimeout>
|
||||
private defaultTagUuid: UuidString | undefined
|
||||
private defaultTag?: SNTag
|
||||
private savingLocallyPromise: ReturnType<typeof Deferred<void>> | null = null
|
||||
|
||||
private syncController: NoteSyncController
|
||||
|
||||
constructor(
|
||||
private application: WebApplication,
|
||||
@@ -56,15 +44,17 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
if (this.defaultTagUuid) {
|
||||
this.defaultTag = this.application.items.findItem(this.defaultTagUuid) as SNTag
|
||||
}
|
||||
|
||||
this.syncController = new NoteSyncController(this.application, this.item)
|
||||
}
|
||||
|
||||
deinit(): void {
|
||||
if (!this.savingLocallyPromise) {
|
||||
if (!this.syncController.savingLocallyPromise) {
|
||||
this.performDeinitSafely()
|
||||
return
|
||||
}
|
||||
|
||||
void this.savingLocallyPromise.promise.then(() => {
|
||||
void this.syncController.savingLocallyPromise.promise.then(() => {
|
||||
this.performDeinitSafely()
|
||||
})
|
||||
}
|
||||
@@ -80,8 +70,6 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
;(this.item as unknown) = undefined
|
||||
|
||||
this.innerValueChangeObservers.length = 0
|
||||
|
||||
this.saveTimeout = undefined
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
@@ -185,59 +173,11 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
}
|
||||
}
|
||||
|
||||
public async saveAndAwaitLocalPropagation(params: {
|
||||
title?: string
|
||||
text?: string
|
||||
isUserModified: boolean
|
||||
bypassDebouncer?: boolean
|
||||
dontGeneratePreviews?: boolean
|
||||
previews?: { previewPlain: string; previewHtml?: string }
|
||||
customMutate?: (mutator: NoteMutator) => void
|
||||
}): Promise<void> {
|
||||
public async saveAndAwaitLocalPropagation(params: NoteSaveFunctionParams): Promise<void> {
|
||||
if (this.needsInit) {
|
||||
throw Error('NoteViewController not initialized')
|
||||
}
|
||||
|
||||
this.savingLocallyPromise = Deferred<void>()
|
||||
|
||||
if (this.saveTimeout) {
|
||||
clearTimeout(this.saveTimeout)
|
||||
}
|
||||
|
||||
const noDebounce = params.bypassDebouncer || this.application.noAccount()
|
||||
|
||||
const syncDebouceMs = noDebounce
|
||||
? EditorSaveTimeoutDebounce.ImmediateChange
|
||||
: this.application.isNativeMobileWeb()
|
||||
? EditorSaveTimeoutDebounce.NativeMobileWeb
|
||||
: EditorSaveTimeoutDebounce.Desktop
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.saveTimeout = setTimeout(() => {
|
||||
void this.undebouncedSave({
|
||||
...params,
|
||||
onLocalPropagationComplete: () => {
|
||||
if (this.savingLocallyPromise) {
|
||||
this.savingLocallyPromise.resolve()
|
||||
}
|
||||
resolve()
|
||||
},
|
||||
})
|
||||
}, syncDebouceMs)
|
||||
})
|
||||
}
|
||||
|
||||
private async undebouncedSave(params: {
|
||||
title?: string
|
||||
text?: string
|
||||
bypassDebouncer?: boolean
|
||||
isUserModified?: boolean
|
||||
dontGeneratePreviews?: boolean
|
||||
previews?: { previewPlain: string; previewHtml?: string }
|
||||
customMutate?: (mutator: NoteMutator) => void
|
||||
onLocalPropagationComplete?: () => void
|
||||
onRemoteSyncComplete?: () => void
|
||||
}): Promise<void> {
|
||||
log(LoggingDomain.NoteView, 'Saving note', params)
|
||||
|
||||
const isTemplate = this.isTemplateNote
|
||||
@@ -246,46 +186,6 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
await this.insertTemplatedNote()
|
||||
}
|
||||
|
||||
if (!this.application.items.findItem(this.item.uuid)) {
|
||||
void this.application.alertService.alert(InfoStrings.InvalidNote)
|
||||
return
|
||||
}
|
||||
|
||||
await this.application.mutator.changeItem(
|
||||
this.item,
|
||||
(mutator) => {
|
||||
const noteMutator = mutator as NoteMutator
|
||||
if (params.customMutate) {
|
||||
params.customMutate(noteMutator)
|
||||
}
|
||||
|
||||
if (params.title != undefined) {
|
||||
noteMutator.title = params.title
|
||||
}
|
||||
|
||||
if (params.text != undefined) {
|
||||
noteMutator.text = params.text
|
||||
}
|
||||
|
||||
if (params.previews) {
|
||||
noteMutator.preview_plain = params.previews.previewPlain
|
||||
noteMutator.preview_html = params.previews.previewHtml
|
||||
} else if (!params.dontGeneratePreviews && params.text != undefined) {
|
||||
const noteText = params.text || ''
|
||||
const truncate = noteText.length > NotePreviewCharLimit
|
||||
const substring = noteText.substring(0, NotePreviewCharLimit)
|
||||
const previewPlain = substring + (truncate ? StringEllipses : '')
|
||||
noteMutator.preview_plain = previewPlain
|
||||
noteMutator.preview_html = undefined
|
||||
}
|
||||
},
|
||||
params.isUserModified,
|
||||
)
|
||||
|
||||
void this.application.sync.sync().then(() => {
|
||||
params.onRemoteSyncComplete?.()
|
||||
})
|
||||
|
||||
params.onLocalPropagationComplete?.()
|
||||
await this.syncController.saveAndAwaitLocalPropagation(params)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,37 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
import { FunctionComponent, useState } from 'react'
|
||||
import Icon from '../Icon/Icon'
|
||||
|
||||
type Props = {
|
||||
onMouseLeave: () => void
|
||||
onMouseOver: () => void
|
||||
onClick: () => void
|
||||
showLockedIcon: boolean
|
||||
lockText: string
|
||||
noteLocked: boolean
|
||||
}
|
||||
|
||||
const EditingDisabledBanner: FunctionComponent<Props> = ({
|
||||
onMouseLeave,
|
||||
onMouseOver,
|
||||
onClick,
|
||||
showLockedIcon,
|
||||
lockText,
|
||||
}) => {
|
||||
const background = showLockedIcon ? 'bg-warning-faded' : 'bg-info-faded'
|
||||
const iconColor = showLockedIcon ? 'text-accessory-tint-3' : 'text-accessory-tint-1'
|
||||
const textColor = showLockedIcon ? 'text-warning' : 'text-accessory-tint-1'
|
||||
const EditingDisabledBanner: FunctionComponent<Props> = ({ onClick, noteLocked }) => {
|
||||
const [showDisabledCopy, setShowDisabledCopy] = useState(() => noteLocked)
|
||||
|
||||
const background = showDisabledCopy ? 'bg-warning-faded' : 'bg-info-faded'
|
||||
const iconColor = showDisabledCopy ? 'text-accessory-tint-3' : 'text-accessory-tint-1'
|
||||
const textColor = showDisabledCopy ? 'text-warning' : 'text-accessory-tint-1'
|
||||
|
||||
const text = showDisabledCopy ? 'Note editing disabled.' : 'Enable editing'
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative flex items-center ${background} cursor-pointer px-3.5 py-2 text-sm`}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseOver={onMouseOver}
|
||||
onMouseLeave={() => {
|
||||
setShowDisabledCopy(true)
|
||||
}}
|
||||
onMouseOver={() => {
|
||||
setShowDisabledCopy(false)
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
{showLockedIcon ? (
|
||||
{showDisabledCopy ? (
|
||||
<Icon type="pencil-off" className={`${iconColor} mr-3 flex fill-current`} />
|
||||
) : (
|
||||
<Icon type="pencil" className={`${iconColor} mr-3 flex fill-current`} />
|
||||
)}
|
||||
<span className={textColor}>{lockText}</span>
|
||||
<span className={textColor}>{text}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ import { NoteViewController } from './Controller/NoteViewController'
|
||||
import { PlainEditor, PlainEditorInterface } from './PlainEditor/PlainEditor'
|
||||
|
||||
const MinimumStatusDuration = 400
|
||||
const NoteEditingDisabledText = 'Note editing disabled.'
|
||||
|
||||
function sortAlphabetically(array: SNComponent[]): SNComponent[] {
|
||||
return array.sort((a, b) => (a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1))
|
||||
@@ -59,12 +58,10 @@ type State = {
|
||||
editorStateDidLoad: boolean
|
||||
editorTitle: string
|
||||
isDesktop?: boolean
|
||||
lockText: string
|
||||
marginResizersEnabled?: boolean
|
||||
noteLocked: boolean
|
||||
noteStatus?: NoteStatus
|
||||
saveError?: boolean
|
||||
showLockedIcon: boolean
|
||||
showProtectedWarning: boolean
|
||||
spellcheck: boolean
|
||||
stackComponentViewers: ComponentViewerInterface[]
|
||||
@@ -116,10 +113,8 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
editorStateDidLoad: false,
|
||||
editorTitle: '',
|
||||
isDesktop: isDesktopApplication(),
|
||||
lockText: NoteEditingDisabledText,
|
||||
noteStatus: undefined,
|
||||
noteLocked: this.controller.item.locked,
|
||||
showLockedIcon: true,
|
||||
showProtectedWarning: false,
|
||||
spellcheck: true,
|
||||
stackComponentViewers: [],
|
||||
@@ -830,21 +825,8 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
|
||||
{this.state.noteLocked && (
|
||||
<EditingDisabledBanner
|
||||
onMouseLeave={() => {
|
||||
this.setState({
|
||||
lockText: NoteEditingDisabledText,
|
||||
showLockedIcon: true,
|
||||
})
|
||||
}}
|
||||
onMouseOver={() => {
|
||||
this.setState({
|
||||
lockText: 'Enable editing',
|
||||
showLockedIcon: false,
|
||||
})
|
||||
}}
|
||||
onClick={() => this.viewControllerManager.notesController.setLockSelectedNotes(!this.state.noteLocked)}
|
||||
showLockedIcon={this.state.showLockedIcon}
|
||||
lockText={this.state.lockText}
|
||||
noteLocked={this.state.noteLocked}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -886,7 +868,6 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
featuresController={this.viewControllerManager.featuresController}
|
||||
/>
|
||||
<ChangeEditorButton
|
||||
application={this.application}
|
||||
viewControllerManager={this.viewControllerManager}
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
@@ -895,7 +876,6 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||
/>
|
||||
<NotesOptionsPanel
|
||||
application={this.application}
|
||||
navigationController={this.viewControllerManager.navigationController}
|
||||
notesController={this.viewControllerManager.notesController}
|
||||
linkingController={this.viewControllerManager.linkingController}
|
||||
|
||||
Reference in New Issue
Block a user