feat(snjs): add revisions api v2 (#2154)
* feat(snjs): add revisions api v2 * fix(snjs): reference listing and getting revisions in specs * fix(snjs): revisions specs * fix(web): usage of revision metadata * fix(snjs): add specs for decryption revision * fix(snjs): issue with building mocked specs * fix(snjs): adjust revision creation delay
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { NoteHistoryController } from '@/Controllers/NoteHistory/NoteHistoryController'
|
||||
import { RevisionListEntry } from '@standardnotes/snjs'
|
||||
import { RevisionMetadata } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useCallback, useState } from 'react'
|
||||
import Button from '@/Components/Button/Button'
|
||||
@@ -36,7 +36,7 @@ const HistoryModalFooter = ({ dismissModal, noteHistoryController }: Props) => {
|
||||
}
|
||||
|
||||
setIsDeletingRevision(true)
|
||||
await deleteRemoteRevision(selectedEntry as RevisionListEntry)
|
||||
await deleteRemoteRevision(selectedEntry as RevisionMetadata)
|
||||
setIsDeletingRevision(false)
|
||||
}, [deleteRemoteRevision, selectedEntry])
|
||||
|
||||
@@ -45,13 +45,13 @@ const HistoryModalFooter = ({ dismissModal, noteHistoryController }: Props) => {
|
||||
<Button className="py-1.35" label="Close" onClick={dismissModal} />
|
||||
{selectedRevision && (
|
||||
<>
|
||||
{(selectedEntry as RevisionListEntry).uuid && (
|
||||
{(selectedEntry as RevisionMetadata).uuid && (
|
||||
<Button className="md:ml-auto" onClick={deleteSelectedRevision}>
|
||||
{isDeletingRevision ? <Spinner className="my-1 h-3 w-3" /> : 'Delete this revision'}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className={!(selectedEntry as RevisionListEntry).uuid ? 'md:ml-auto' : ''}
|
||||
className={!(selectedEntry as RevisionMetadata).uuid ? 'md:ml-auto' : ''}
|
||||
label="Restore as a copy"
|
||||
onClick={restoreAsCopy}
|
||||
/>
|
||||
|
||||
@@ -4,7 +4,7 @@ import Icon from '@/Components/Icon/Icon'
|
||||
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
|
||||
import HistoryListItem from './HistoryListItem'
|
||||
import { previewHistoryEntryTitle } from './utils'
|
||||
import { FeaturesClientInterface, RevisionListEntry } from '@standardnotes/snjs'
|
||||
import { FeaturesClientInterface, RevisionMetadata, RoleName } from '@standardnotes/snjs'
|
||||
import { NoteHistoryController } from '@/Controllers/NoteHistory/NoteHistoryController'
|
||||
import Spinner from '@/Components/Spinner/Spinner'
|
||||
import { PremiumFeatureIconClass, PremiumFeatureIconName } from '../Icon/PremiumFeatureIcon'
|
||||
@@ -46,7 +46,7 @@ const RemoteHistoryList: FunctionComponent<RemoteHistoryListProps> = ({
|
||||
{group.entries.map((entry) => (
|
||||
<HistoryListItem
|
||||
key={entry.uuid}
|
||||
isSelected={(selectedEntry as RevisionListEntry)?.uuid === entry.uuid}
|
||||
isSelected={(selectedEntry as RevisionMetadata)?.uuid === entry.uuid}
|
||||
onClick={() => {
|
||||
void selectRemoteRevision(entry)
|
||||
onSelectRevision()
|
||||
@@ -54,7 +54,7 @@ const RemoteHistoryList: FunctionComponent<RemoteHistoryListProps> = ({
|
||||
>
|
||||
<div className="flex flex-grow items-center justify-between">
|
||||
<div>{previewHistoryEntryTitle(entry)}</div>
|
||||
{!features.hasMinimumRole(entry.required_role) && (
|
||||
{!features.hasMinimumRole(entry.required_role as RoleName) && (
|
||||
<Icon type={PremiumFeatureIconName} className={PremiumFeatureIconClass} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DAYS_IN_A_WEEK, DAYS_IN_A_YEAR } from '@/Constants/Constants'
|
||||
import { HistoryEntry, NoteHistoryEntry, RevisionListEntry } from '@standardnotes/snjs'
|
||||
import { HistoryEntry, NoteHistoryEntry, RevisionMetadata } from '@standardnotes/snjs'
|
||||
import { calculateDifferenceBetweenDatesInDays } from '../../Utils/CalculateDifferenceBetweenDatesInDays'
|
||||
|
||||
export type HistoryModalMobileTab = 'Content' | 'List'
|
||||
@@ -9,14 +9,14 @@ export type LegacyHistoryEntry = {
|
||||
created_at: string
|
||||
}
|
||||
|
||||
type RevisionEntry = RevisionListEntry | NoteHistoryEntry | LegacyHistoryEntry
|
||||
type RevisionEntry = RevisionMetadata | NoteHistoryEntry | LegacyHistoryEntry
|
||||
|
||||
export type ListGroup<EntryType extends RevisionEntry> = {
|
||||
title: string
|
||||
entries: EntryType[] | undefined
|
||||
}
|
||||
|
||||
export type RemoteRevisionListGroup = ListGroup<RevisionListEntry>
|
||||
export type RemoteRevisionListGroup = ListGroup<RevisionMetadata>
|
||||
export type SessionRevisionListGroup = ListGroup<NoteHistoryEntry>
|
||||
|
||||
export const formatDateAsMonthYearString = (date: Date) => {
|
||||
@@ -28,7 +28,7 @@ export const formatDateAsMonthYearString = (date: Date) => {
|
||||
|
||||
export const getGroupIndexForEntry = (entry: RevisionEntry, groups: ListGroup<RevisionEntry>[]) => {
|
||||
const todayAsDate = new Date()
|
||||
const entryDate = new Date((entry as RevisionListEntry).created_at ?? (entry as NoteHistoryEntry).payload.updated_at)
|
||||
const entryDate = new Date((entry as RevisionMetadata).created_at ?? (entry as NoteHistoryEntry).payload.updated_at)
|
||||
|
||||
const differenceBetweenDatesInDays = calculateDifferenceBetweenDatesInDays(todayAsDate, entryDate)
|
||||
|
||||
@@ -81,7 +81,7 @@ export const sortRevisionListIntoGroups = <EntryType extends RevisionEntry>(revi
|
||||
} else {
|
||||
addBeforeLastGroup({
|
||||
title: formatDateAsMonthYearString(
|
||||
new Date((entry as RevisionListEntry).created_at ?? (entry as NoteHistoryEntry).payload.updated_at),
|
||||
new Date((entry as RevisionMetadata).created_at ?? (entry as NoteHistoryEntry).payload.updated_at),
|
||||
),
|
||||
entries: [entry],
|
||||
})
|
||||
@@ -91,6 +91,6 @@ export const sortRevisionListIntoGroups = <EntryType extends RevisionEntry>(revi
|
||||
return sortedGroups
|
||||
}
|
||||
|
||||
export const previewHistoryEntryTitle = (revision: RevisionListEntry | LegacyHistoryEntry) => {
|
||||
export const previewHistoryEntryTitle = (revision: RevisionMetadata | LegacyHistoryEntry) => {
|
||||
return new Date(revision.created_at).toLocaleString()
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ import {
|
||||
HistoryEntry,
|
||||
NoteHistoryEntry,
|
||||
PayloadEmitSource,
|
||||
RevisionListEntry,
|
||||
RevisionMetadata,
|
||||
RoleName,
|
||||
SNNote,
|
||||
} from '@standardnotes/snjs'
|
||||
import { makeObservable, observable, action } from 'mobx'
|
||||
@@ -29,7 +30,7 @@ type LegacyHistory = Action[]
|
||||
|
||||
type SelectedRevision = HistoryEntry | LegacyHistoryEntry | undefined
|
||||
|
||||
type SelectedEntry = RevisionListEntry | NoteHistoryEntry | Action | undefined
|
||||
type SelectedEntry = RevisionMetadata | NoteHistoryEntry | Action | undefined
|
||||
|
||||
export enum RevisionContentState {
|
||||
Idle,
|
||||
@@ -114,12 +115,12 @@ export class NoteHistoryController {
|
||||
this.contentState = contentState
|
||||
}
|
||||
|
||||
selectRemoteRevision = async (entry: RevisionListEntry) => {
|
||||
selectRemoteRevision = async (entry: RevisionMetadata) => {
|
||||
if (!this.note) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.application.features.hasMinimumRole(entry.required_role)) {
|
||||
if (!this.application.features.hasMinimumRole(entry.required_role as RoleName)) {
|
||||
this.setContentState(RevisionContentState.NotEntitled)
|
||||
this.setSelectedRevision(undefined)
|
||||
return
|
||||
@@ -130,7 +131,14 @@ export class NoteHistoryController {
|
||||
|
||||
try {
|
||||
this.setSelectedEntry(entry)
|
||||
const remoteRevision = await this.application.historyManager.fetchRemoteRevision(this.note, entry)
|
||||
const remoteRevisionOrError = await this.application.getRevision.execute({
|
||||
itemUuid: this.note.uuid,
|
||||
revisionUuid: entry.uuid,
|
||||
})
|
||||
if (remoteRevisionOrError.isFailed()) {
|
||||
throw new Error(remoteRevisionOrError.getError())
|
||||
}
|
||||
const remoteRevision = remoteRevisionOrError.getValue()
|
||||
this.setSelectedRevision(remoteRevision)
|
||||
} catch (err) {
|
||||
this.clearSelection()
|
||||
@@ -211,7 +219,7 @@ export class NoteHistoryController {
|
||||
}
|
||||
}
|
||||
|
||||
selectPrevOrNextRemoteRevision = (revisionEntry: RevisionListEntry) => {
|
||||
selectPrevOrNextRemoteRevision = (revisionEntry: RevisionMetadata) => {
|
||||
const currentIndex = this.flattenedRemoteHistory.findIndex((entry) => entry?.uuid === revisionEntry.uuid)
|
||||
|
||||
const previousEntry = this.flattenedRemoteHistory[currentIndex - 1]
|
||||
@@ -234,9 +242,13 @@ export class NoteHistoryController {
|
||||
if (this.note) {
|
||||
this.setIsFetchingRemoteHistory(true)
|
||||
try {
|
||||
const initialRemoteHistory = await this.application.historyManager.remoteHistoryForItem(this.note)
|
||||
const revisionsListOrError = await this.application.listRevisions.execute({ itemUuid: this.note.uuid })
|
||||
if (revisionsListOrError.isFailed()) {
|
||||
throw new Error(revisionsListOrError.getError())
|
||||
}
|
||||
const revisionsList = revisionsListOrError.getValue()
|
||||
|
||||
this.setRemoteHistory(sortRevisionListIntoGroups<RevisionListEntry>(initialRemoteHistory))
|
||||
this.setRemoteHistory(sortRevisionListIntoGroups<RevisionMetadata>(revisionsList))
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
@@ -341,7 +353,7 @@ export class NoteHistoryController {
|
||||
this.selectionController.selectItem(duplicatedItem.uuid).catch(console.error)
|
||||
}
|
||||
|
||||
deleteRemoteRevision = async (revisionEntry: RevisionListEntry) => {
|
||||
deleteRemoteRevision = async (revisionEntry: RevisionMetadata) => {
|
||||
const shouldDelete = await this.application.alertService.confirm(
|
||||
'Are you sure you want to delete this revision?',
|
||||
'Delete revision?',
|
||||
@@ -354,10 +366,12 @@ export class NoteHistoryController {
|
||||
return
|
||||
}
|
||||
|
||||
const response = await this.application.historyManager.deleteRemoteRevision(this.note, revisionEntry)
|
||||
|
||||
if (response.error?.message) {
|
||||
throw new Error(response.error.message)
|
||||
const deleteRevisionOrError = await this.application.deleteRevision.execute({
|
||||
itemUuid: this.note.uuid,
|
||||
revisionUuid: revisionEntry.uuid,
|
||||
})
|
||||
if (deleteRevisionOrError.isFailed()) {
|
||||
throw new Error(deleteRevisionOrError.getError())
|
||||
}
|
||||
|
||||
this.clearSelection()
|
||||
|
||||
Reference in New Issue
Block a user