chore: add clipper extension package (#2281)

This commit is contained in:
Aman Harwara
2023-04-11 22:14:02 +05:30
committed by GitHub
parent 0b0466c9fa
commit 4f5e634685
214 changed files with 3163 additions and 355 deletions

View File

@@ -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)
}
}

View File

@@ -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>
)
}

View File

@@ -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}