internal: incomplete vault systems behind feature flag (#2340)
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { FunctionComponent } from 'react'
|
||||
import Icon from '../Icon/Icon'
|
||||
import { useApplication } from '../ApplicationProvider'
|
||||
import { DecryptedItemInterface } from '@standardnotes/snjs'
|
||||
import { featureTrunkVaultsEnabled } from '@/FeatureTrunk'
|
||||
|
||||
type Props = {
|
||||
item: DecryptedItemInterface
|
||||
}
|
||||
|
||||
const CollaborationInfoHUD: FunctionComponent<Props> = ({ item }) => {
|
||||
const application = useApplication()
|
||||
|
||||
if (!featureTrunkVaultsEnabled()) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (application.items.isTemplateItem(item)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const vault = application.vaults.getItemVault(item)
|
||||
if (!vault) {
|
||||
return null
|
||||
}
|
||||
|
||||
const lastEditedBy = application.sharedVaults.getItemLastEditedBy(item)
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-start gap-2">
|
||||
<div title="Vault name" className={'flex rounded bg-success py-1 px-1.5 text-success-contrast'}>
|
||||
<Icon ariaLabel="Shared in vault" type="safe-square" className="mr-1 text-info-contrast" size="medium" />
|
||||
<span className="mr-auto overflow-hidden text-ellipsis text-xs">{vault.name}</span>
|
||||
</div>
|
||||
|
||||
{lastEditedBy && (
|
||||
<div title="Last edited by" className={'flex rounded bg-info py-1 px-1.5 text-info-contrast'}>
|
||||
<Icon ariaLabel="Shared by" type="pencil" className="mr-1 text-info-contrast" size="medium" />
|
||||
<span className="mr-auto overflow-hidden text-ellipsis text-xs">{lastEditedBy?.name}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CollaborationInfoHUD
|
||||
@@ -5,10 +5,11 @@ import {
|
||||
SNComponentManager,
|
||||
SNComponent,
|
||||
SNTag,
|
||||
ItemsClientInterface,
|
||||
SNNote,
|
||||
Deferred,
|
||||
SyncServiceInterface,
|
||||
ItemManagerInterface,
|
||||
MutatorClientInterface,
|
||||
} from '@standardnotes/snjs'
|
||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||
import { NoteViewController } from './NoteViewController'
|
||||
@@ -24,7 +25,9 @@ describe('note view controller', () => {
|
||||
application.noAccount = jest.fn().mockReturnValue(false)
|
||||
application.isNativeMobileWeb = jest.fn().mockReturnValue(false)
|
||||
|
||||
Object.defineProperty(application, 'items', { value: {} as jest.Mocked<ItemsClientInterface> })
|
||||
const items = {} as jest.Mocked<ItemManagerInterface>
|
||||
items.createTemplateItem = jest.fn().mockReturnValue({} as SNNote)
|
||||
Object.defineProperty(application, 'items', { value: items })
|
||||
|
||||
Object.defineProperty(application, 'sync', { value: {} as jest.Mocked<SyncServiceInterface> })
|
||||
application.sync.sync = jest.fn().mockReturnValue(Promise.resolve())
|
||||
@@ -33,8 +36,7 @@ describe('note view controller', () => {
|
||||
componentManager.legacyGetDefaultEditor = jest.fn()
|
||||
Object.defineProperty(application, 'componentManager', { value: componentManager })
|
||||
|
||||
const mutator = {} as jest.Mocked<MutatorService>
|
||||
mutator.createTemplateItem = jest.fn().mockReturnValue({} as SNNote)
|
||||
const mutator = {} as jest.Mocked<MutatorClientInterface>
|
||||
Object.defineProperty(application, 'mutator', { value: mutator })
|
||||
})
|
||||
|
||||
@@ -44,7 +46,7 @@ describe('note view controller', () => {
|
||||
const controller = new NoteViewController(application)
|
||||
await controller.initialize()
|
||||
|
||||
expect(application.mutator.createTemplateItem).toHaveBeenCalledWith(
|
||||
expect(application.items.createTemplateItem).toHaveBeenCalledWith(
|
||||
ContentType.Note,
|
||||
expect.objectContaining({ noteType: NoteType.Plain }),
|
||||
expect.anything(),
|
||||
@@ -65,7 +67,7 @@ describe('note view controller', () => {
|
||||
const controller = new NoteViewController(application)
|
||||
await controller.initialize()
|
||||
|
||||
expect(application.mutator.createTemplateItem).toHaveBeenCalledWith(
|
||||
expect(application.items.createTemplateItem).toHaveBeenCalledWith(
|
||||
ContentType.Note,
|
||||
expect.objectContaining({ noteType: NoteType.Markdown }),
|
||||
expect.anything(),
|
||||
@@ -80,13 +82,13 @@ describe('note view controller', () => {
|
||||
} as jest.Mocked<SNTag>
|
||||
|
||||
application.items.findItem = jest.fn().mockReturnValue(tag)
|
||||
application.items.addTagToNote = jest.fn()
|
||||
application.mutator.addTagToNote = jest.fn()
|
||||
|
||||
const controller = new NoteViewController(application, undefined, { tag: tag.uuid })
|
||||
await controller.initialize()
|
||||
|
||||
expect(controller['defaultTag']).toEqual(tag)
|
||||
expect(application.items.addTagToNote).toHaveBeenCalledWith(expect.anything(), tag, expect.anything())
|
||||
expect(application.mutator.addTagToNote).toHaveBeenCalledWith(expect.anything(), tag, expect.anything())
|
||||
})
|
||||
|
||||
it('should wait until item finishes saving locally before deiniting', async () => {
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { WebApplication } from '@/Application/WebApplication'
|
||||
import { noteTypeForEditorIdentifier } from '@standardnotes/features'
|
||||
import { SNNote, SNTag, NoteContent, DecryptedItemInterface, PayloadEmitSource, PrefKey } from '@standardnotes/models'
|
||||
import {
|
||||
SNNote,
|
||||
SNTag,
|
||||
NoteContent,
|
||||
DecryptedItemInterface,
|
||||
PayloadEmitSource,
|
||||
PrefKey,
|
||||
PayloadVaultOverrides,
|
||||
} from '@standardnotes/models'
|
||||
import { UuidString } from '@standardnotes/snjs'
|
||||
import { removeFromArray } from '@standardnotes/utils'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
@@ -90,7 +98,7 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
|
||||
const noteType = noteTypeForEditorIdentifier(editorIdentifier)
|
||||
|
||||
const note = this.application.mutator.createTemplateItem<NoteContent, SNNote>(
|
||||
const note = this.application.items.createTemplateItem<NoteContent, SNNote>(
|
||||
ContentType.Note,
|
||||
{
|
||||
text: '',
|
||||
@@ -101,6 +109,7 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
},
|
||||
{
|
||||
created_at: this.templateNoteOptions?.createdAt || new Date(),
|
||||
...PayloadVaultOverrides(this.templateNoteOptions?.vault),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -110,7 +119,7 @@ export class NoteViewController implements ItemViewControllerInterface {
|
||||
|
||||
if (this.defaultTagUuid) {
|
||||
const tag = this.application.items.findItem(this.defaultTagUuid) as SNTag
|
||||
await this.application.items.addTagToNote(note, tag, addTagHierarchy)
|
||||
await this.application.mutator.addTagToNote(note, tag, addTagHierarchy)
|
||||
}
|
||||
|
||||
this.notifyObservers(this.item, PayloadEmitSource.InitialObserverRegistrationPush)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { UuidString } from '@standardnotes/snjs'
|
||||
import { UuidString, VaultListingInterface } from '@standardnotes/snjs'
|
||||
|
||||
export type TemplateNoteViewControllerOptions = {
|
||||
title?: string
|
||||
tag?: UuidString
|
||||
vault?: VaultListingInterface
|
||||
createdAt?: Date
|
||||
autofocusBehavior?: TemplateNoteViewAutofocusBehavior
|
||||
}
|
||||
|
||||
@@ -65,12 +65,13 @@ const NoteConflictResolutionModal = ({
|
||||
async (note: SNNote) => {
|
||||
await application.mutator
|
||||
.deleteItem(note)
|
||||
.then(() => application.sync.sync())
|
||||
.catch(console.error)
|
||||
.then(() => {
|
||||
setSelectedVersions([allVersions[0].uuid])
|
||||
})
|
||||
},
|
||||
[allVersions, application.mutator],
|
||||
[allVersions, application.mutator, application.sync],
|
||||
)
|
||||
|
||||
const [selectedAction, setSelectionAction] = useState<ConflictAction>('move-to-trash')
|
||||
|
||||
@@ -45,6 +45,7 @@ import { SuperEditorContentId } from '../SuperEditor/Constants'
|
||||
import { NoteViewController } from './Controller/NoteViewController'
|
||||
import { PlainEditor, PlainEditorInterface } from './PlainEditor/PlainEditor'
|
||||
import { EditorMargins, EditorMaxWidths } from '../EditorWidthSelectionModal/EditorWidths'
|
||||
import CollaborationInfoHUD from './CollaborationInfoHUD'
|
||||
import Button from '../Button/Button'
|
||||
import ModalOverlay from '../Modal/ModalOverlay'
|
||||
import NoteConflictResolutionModal from './NoteConflictResolutionModal/NoteConflictResolutionModal'
|
||||
@@ -74,7 +75,7 @@ type State = {
|
||||
monospaceFont?: boolean
|
||||
plainEditorFocused?: boolean
|
||||
paneGestureEnabled?: boolean
|
||||
|
||||
noteLastEditedByUuid?: string
|
||||
updateSavingIndicator?: boolean
|
||||
editorFeatureIdentifier?: string
|
||||
noteType?: NoteType
|
||||
@@ -270,6 +271,12 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
})
|
||||
}
|
||||
|
||||
if (note.last_edited_by_uuid !== this.state.noteLastEditedByUuid) {
|
||||
this.setState({
|
||||
noteLastEditedByUuid: note.last_edited_by_uuid,
|
||||
})
|
||||
}
|
||||
|
||||
if (note.locked !== this.state.noteLocked) {
|
||||
this.setState({
|
||||
noteLocked: note.locked,
|
||||
@@ -651,7 +658,10 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
}
|
||||
|
||||
performNoteDeletion(note: SNNote) {
|
||||
this.application.mutator.deleteItem(note).catch(console.error)
|
||||
this.application.mutator
|
||||
.deleteItem(note)
|
||||
.then(() => this.application.sync.sync())
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
onPanelResizeFinish = async (width: number, left: number, isMaxWidth: boolean) => {
|
||||
@@ -897,6 +907,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
)}
|
||||
{renderHeaderOptions && (
|
||||
<div className="note-view-options-buttons flex items-center gap-3">
|
||||
<CollaborationInfoHUD item={this.note} />
|
||||
<LinkedItemsButton
|
||||
filesController={this.viewControllerManager.filesController}
|
||||
linkingController={this.viewControllerManager.linkingController}
|
||||
|
||||
@@ -24,7 +24,7 @@ const NoteViewFileDropTarget = ({ note, linkingController, noteViewElement, file
|
||||
tooltipText: 'Drop your files to upload and link them to the current note',
|
||||
callback: async (uploadedFile) => {
|
||||
await linkingController.linkItems(note, uploadedFile)
|
||||
void application.mutator.changeAndSaveItem(uploadedFile, (mutator) => {
|
||||
void application.changeAndSaveItem(uploadedFile, (mutator) => {
|
||||
mutator.protected = note.protected
|
||||
})
|
||||
filesController.notifyObserversOfUploadedFileLinkingToCurrentNote(uploadedFile.uuid)
|
||||
@@ -37,7 +37,7 @@ const NoteViewFileDropTarget = ({ note, linkingController, noteViewElement, file
|
||||
removeDragTarget(target)
|
||||
}
|
||||
}
|
||||
}, [addDragTarget, linkingController, note, noteViewElement, removeDragTarget, filesController, application.mutator])
|
||||
}, [addDragTarget, linkingController, note, noteViewElement, removeDragTarget, filesController, application])
|
||||
|
||||
return isDraggingFiles ? (
|
||||
// Required to block drag events to editor iframe
|
||||
|
||||
@@ -36,14 +36,14 @@ export const ReadonlyNoteContent = ({
|
||||
return undefined
|
||||
}
|
||||
|
||||
const templateNoteForRevision = application.mutator.createTemplateItem(ContentType.Note, note.content) as SNNote
|
||||
const templateNoteForRevision = application.items.createTemplateItem(ContentType.Note, note.content) as SNNote
|
||||
|
||||
const componentViewer = application.componentManager.createComponentViewer(editorForCurrentNote)
|
||||
componentViewer.setReadonly(true)
|
||||
componentViewer.lockReadonly = true
|
||||
componentViewer.overrideContextItem = templateNoteForRevision
|
||||
return componentViewer
|
||||
}, [application.componentManager, application.mutator, note])
|
||||
}, [application.componentManager, application.items, note])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
||||
Reference in New Issue
Block a user