diff --git a/app/assets/javascripts/app.ts b/app/assets/javascripts/app.ts index ff91d5a65..00924ffdb 100644 --- a/app/assets/javascripts/app.ts +++ b/app/assets/javascripts/app.ts @@ -45,7 +45,7 @@ import { PrivilegesAuthModal, PrivilegesManagementModal, RevisionPreviewModal, - SessionHistoryMenu, + HistoryMenu, SyncResolutionMenu } from './directives/views'; @@ -110,7 +110,7 @@ function startApplication(platform: Platform) { .directive('privilegesAuthModal', () => new PrivilegesAuthModal()) .directive('privilegesManagementModal', () => new PrivilegesManagementModal()) .directive('revisionPreviewModal', () => new RevisionPreviewModal()) - .directive('sessionHistoryMenu', () => new SessionHistoryMenu()) + .directive('historyMenu', () => new HistoryMenu()) .directive('syncResolutionMenu', () => new SyncResolutionMenu()); // Filters diff --git a/app/assets/javascripts/directives/views/historyMenu.ts b/app/assets/javascripts/directives/views/historyMenu.ts new file mode 100644 index 000000000..c33c01efb --- /dev/null +++ b/app/assets/javascripts/directives/views/historyMenu.ts @@ -0,0 +1,191 @@ +import { WebDirective } from '../../types'; +import { WebApplication } from '@/ui_models/application'; +import template from '%/directives/history-menu.pug'; +import { SNItem, ItemHistoryEntry } from '@node_modules/snjs/dist/@types'; +import { PureViewCtrl } from '@/views'; +import { ItemSessionHistory } from 'snjs/dist/@types/services/history/session/item_session_history'; +import { RemoteHistoryList, RemoteHistoryListEntry } from 'snjs/dist/@types/services/history/history_manager'; +import { confirmDialog } from '@/services/alertService'; + +type HistoryState = { + fetchingRemoteHistory: boolean +} + +interface HistoryScope { + application: WebApplication + item: SNItem +} + +class HistoryMenuCtrl extends PureViewCtrl<{}, HistoryState> implements HistoryScope { + + diskEnabled = false + autoOptimize = false + application!: WebApplication + item!: SNItem + sessionHistory?: ItemSessionHistory + remoteHistory?: RemoteHistoryList + + /* @ngInject */ + constructor( + $timeout: ng.ITimeoutService + ) { + super($timeout); + this.state = { + fetchingRemoteHistory: false + }; + } + + $onInit() { + super.$onInit(); + this.reloadSessionHistory(); + this.fetchRemoteHistory(); + this.diskEnabled = this.application.historyManager!.isDiskEnabled(); + this.autoOptimize = this.application.historyManager!.isAutoOptimizeEnabled(); + } + + reloadSessionHistory() { + this.sessionHistory = this.application.historyManager!.sessionHistoryForItem(this.item); + } + + get isfetchingRemoteHistory() { + return this.state.fetchingRemoteHistory; + } + + set fetchingRemoteHistory(value: boolean) { + this.setState({ + fetchingRemoteHistory: value + }); + } + + async fetchRemoteHistory() { + this.fetchingRemoteHistory = true; + this.remoteHistory = await this.application.historyManager!.remoteHistoryForItem(this.item) + .finally(() => { + this.fetchingRemoteHistory = false; + }); + } + + async openSessionRevision(revision: ItemHistoryEntry) { + this.application.presentRevisionPreviewModal( + revision.payload.uuid, + revision.payload.content + ); + } + + async openRemoteRevision(revision: RemoteHistoryListEntry) { + this.fetchingRemoteHistory = true; + const remoteRevision = await this.application.historyManager!.fetchRemoteRevision(this.item.uuid, revision); + this.fetchingRemoteHistory = false; + if (!remoteRevision) { + this.application.alertService!.alert("The remote revision could not be loaded. Please try again later."); + return; + } + this.application.presentRevisionPreviewModal( + remoteRevision.payload.uuid, + remoteRevision.payload.content + ); + } + + classForSessionRevision(revision: ItemHistoryEntry) { + const vector = revision.operationVector(); + if (vector === 0) { + return 'default'; + } else if (vector === 1) { + return 'success'; + } else if (vector === -1) { + return 'danger'; + } + } + + clearItemSessionHistory() { + confirmDialog({ + text: "Are you sure you want to delete the local session history for this note?", + confirmButtonStyle: "danger" + }).then((confirmed) => { + if (!confirmed) { + return; + } + this.application.historyManager!.clearHistoryForItem(this.item).then(() => { + this.$timeout(() => { + this.reloadSessionHistory(); + }); + }); + }); + } + + clearAllSessionHistory() { + confirmDialog({ + text: "Are you sure you want to delete the local session history for all notes?", + confirmButtonStyle: "danger" + }).then((confirmed) => { + if (!confirmed) { + return; + } + this.application.historyManager!.clearAllHistory().then(() => { + this.$timeout(() => { + this.reloadSessionHistory(); + }); + }); + }); + } + + get sessionHistoryEntries() { + return this.sessionHistory?.entries; + } + + get remoteHistoryEntries() { + return this.remoteHistory; + } + + toggleSessionHistoryDiskSaving() { + const run = () => { + this.application.historyManager!.toggleDiskSaving().then(() => { + this.$timeout(() => { + this.diskEnabled = this.application.historyManager!.isDiskEnabled(); + }); + }); + }; + if (!this.application.historyManager!.isDiskEnabled()) { + confirmDialog({ + text: "Are you sure you want to save history to disk? This will decrease general " + + "performance, especially as you type. You are advised to disable this feature " + + "if you experience any lagging.", + confirmButtonStyle: "danger" + }).then((confirmed) => { + if (confirmed) { + run(); + } + }); + } else { + run(); + } + } + + toggleSessionHistoryAutoOptimize() { + this.application.historyManager!.toggleAutoOptimize().then(() => { + this.$timeout(() => { + this.autoOptimize = this.application.historyManager!.autoOptimize; + }); + }); + } + + previewRemoteHistoryTitle(revision: RemoteHistoryListEntry) { + const createdAt = revision.created_at!; + return new Date(createdAt).toLocaleString(); + } +} + +export class HistoryMenu extends WebDirective { + constructor() { + super(); + this.restrict = 'E'; + this.template = template; + this.controller = HistoryMenuCtrl; + this.controllerAs = 'ctrl'; + this.bindToController = true; + this.scope = { + item: '=', + application: '=' + }; + } +} diff --git a/app/assets/javascripts/directives/views/index.ts b/app/assets/javascripts/directives/views/index.ts index 1f51d0023..adee8d90c 100644 --- a/app/assets/javascripts/directives/views/index.ts +++ b/app/assets/javascripts/directives/views/index.ts @@ -11,5 +11,5 @@ export { PermissionsModal } from './permissionsModal'; export { PrivilegesAuthModal } from './privilegesAuthModal'; export { PrivilegesManagementModal } from './privilegesManagementModal'; export { RevisionPreviewModal } from './revisionPreviewModal'; -export { SessionHistoryMenu } from './sessionHistoryMenu'; +export { HistoryMenu } from './historyMenu'; export { SyncResolutionMenu } from './syncResolutionMenu'; diff --git a/app/assets/javascripts/directives/views/revisionPreviewModal.ts b/app/assets/javascripts/directives/views/revisionPreviewModal.ts index b2a123ced..d98ed71c3 100644 --- a/app/assets/javascripts/directives/views/revisionPreviewModal.ts +++ b/app/assets/javascripts/directives/views/revisionPreviewModal.ts @@ -89,7 +89,7 @@ class RevisionPreviewModalCtrl implements RevisionPreviewScope { } } - async restore(asCopy: boolean) { + restore(asCopy: boolean) { const run = async () => { if (asCopy) { const contentCopy = Object.assign({}, this.content); @@ -110,12 +110,14 @@ class RevisionPreviewModalCtrl implements RevisionPreviewScope { }; if (!asCopy) { - if (await confirmDialog({ + confirmDialog({ text: "Are you sure you want to replace the current note's contents with what you see in this preview?", - confirmButtonStyle: 'danger', - })) { - run(); - } + confirmButtonStyle: "danger" + }).then((confirmed) => { + if (confirmed) { + run(); + } + }); } else { run(); } diff --git a/app/assets/javascripts/directives/views/sessionHistoryMenu.ts b/app/assets/javascripts/directives/views/sessionHistoryMenu.ts deleted file mode 100644 index c4f7f031e..000000000 --- a/app/assets/javascripts/directives/views/sessionHistoryMenu.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { WebDirective } from './../../types'; -import { WebApplication } from '@/ui_models/application'; -import template from '%/directives/session-history-menu.pug'; -import { SNItem, ItemHistoryEntry, ItemHistory } from '@node_modules/snjs/dist/@types'; -import { confirmDialog } from '@/services/alertService'; - -interface SessionHistoryScope { - application: WebApplication - item: SNItem -} - -class SessionHistoryMenuCtrl implements SessionHistoryScope { - - $timeout: ng.ITimeoutService - diskEnabled = false - autoOptimize = false - application!: WebApplication - item!: SNItem - entries!: ItemHistoryEntry[] - history!: ItemHistory - - /* @ngInject */ - constructor( - $timeout: ng.ITimeoutService - ) { - this.$timeout = $timeout; - } - - $onInit() { - this.reloadHistory(); - this.diskEnabled = this.application.historyManager!.isDiskEnabled(); - this.autoOptimize = this.application.historyManager!.isAutoOptimizeEnabled(); - } - - reloadHistory() { - const history = this.application.historyManager!.historyForItem(this.item); - this.entries = history.entries.slice(0).sort((a, b) => { - return a.payload.updated_at! < b.payload.updated_at! ? 1 : -1; - }); - this.history = history; - } - - openRevision(revision: ItemHistoryEntry) { - this.application.presentRevisionPreviewModal( - revision.payload.uuid, - revision.payload.content - ); - } - - classForRevision(revision: ItemHistoryEntry) { - const vector = revision.operationVector(); - if (vector === 0) { - return 'default'; - } else if (vector === 1) { - return 'success'; - } else if (vector === -1) { - return 'danger'; - } - } - - async clearItemHistory() { - if (await confirmDialog({ - text: "Are you sure you want to delete the local session history for this note?", - confirmButtonStyle: 'danger', - })) { - this.application.historyManager!.clearHistoryForItem(this.item).then(() => { - this.$timeout(() => { - this.reloadHistory(); - }); - }); - } - } - - clearAllHistory() { - if (confirmDialog({ - text: "Are you sure you want to delete the local session history for all notes?", - confirmButtonStyle: 'danger' - })) { - this.application.historyManager!.clearAllHistory().then(() => { - this.$timeout(() => { - this.reloadHistory(); - }); - }); - } - } - - async toggleDiskSaving() { - const run = () => { - this.application.historyManager!.toggleDiskSaving().then(() => { - this.$timeout(() => { - this.diskEnabled = this.application.historyManager!.isDiskEnabled(); - }); - }); - }; - if (!this.application.historyManager!.isDiskEnabled()) { - if (await confirmDialog({ - text: `Are you sure you want to save history to disk? This will decrease general - performance, especially as you type. You are advised to disable this feature - if you experience any lagging.`, - confirmButtonStyle: 'danger', - })) { - run(); - } - } else { - run(); - } - } - - toggleAutoOptimize() { - this.application.historyManager!.toggleAutoOptimize().then(() => { - this.$timeout(() => { - this.autoOptimize = this.application.historyManager!.autoOptimize; - }); - }); - } -} - -export class SessionHistoryMenu extends WebDirective { - constructor() { - super(); - this.restrict = 'E'; - this.template = template; - this.controller = SessionHistoryMenuCtrl; - this.controllerAs = 'ctrl'; - this.bindToController = true; - this.scope = { - item: '=', - application: '=' - }; - } -} diff --git a/app/assets/javascripts/views/editor/editor-view.pug b/app/assets/javascripts/views/editor/editor-view.pug index cec55b349..4eb651823 100644 --- a/app/assets/javascripts/views/editor/editor-view.pug +++ b/app/assets/javascripts/views/editor/editor-view.pug @@ -183,14 +183,14 @@ application='self.application' ) .sk-app-bar-item( - click-outside=`self.setMenuState('showSessionHistory', false)`, - is-open='self.state.showSessionHistory', - ng-click="self.toggleMenu('showSessionHistory')" + click-outside=`self.setMenuState('showHistory', false)`, + is-open='self.state.showHistory', + ng-click="self.toggleMenu('showHistory')" ) - .sk-label Session History - session-history-menu( + .sk-label History + history-menu( item='self.note', - ng-if='self.state.showSessionHistory', + ng-if='self.state.showHistory', application='self.application' ) #editor-content.editor-content(ng-if='!self.note.errorDecrypting') diff --git a/app/assets/templates/directives/history-menu.pug b/app/assets/templates/directives/history-menu.pug new file mode 100644 index 000000000..0a8bcacb7 --- /dev/null +++ b/app/assets/templates/directives/history-menu.pug @@ -0,0 +1,55 @@ +#history-menu.sn-component + .sk-menu-panel.dropdown-menu + .sk-menu-panel-header + .sk-menu-panel-header-title Session + .sk-menu-panel-header-subtitle {{ctrl.sessionHistoryEntries.length || 'No'}} revisions + a.sk-a.info.sk-h5( + ng-click='ctrl.showSessionOptions = !ctrl.showSessionOptions; $event.stopPropagation();' + ) Options + div(ng-if='ctrl.showSessionOptions') + menu-row( + action='ctrl.clearItemSessionHistory()' + label="'Clear note local history'" + ) + menu-row( + action='ctrl.clearAllSessionHistory()' + label="'Clear all local history'" + ) + menu-row( + action='ctrl.toggleSessionHistoryAutoOptimize()' + label="(ctrl.autoOptimize ? 'Disable' : 'Enable') + ' auto cleanup'") + .sk-sublabel + | Automatically cleans up small revisions to conserve space. + menu-row( + action='ctrl.toggleSessionHistoryDiskSaving()' + label="(ctrl.diskEnabled ? 'Disable' : 'Enable') + ' saving history to disk'" + ) + .sk-sublabel + | Saving to disk is not recommended. Decreases performance and increases app + | loading time and memory footprint. + menu-row( + ng-repeat='revision in ctrl.sessionHistoryEntries track by $index' + action='ctrl.openSessionRevision(revision);' + label='revision.previewTitle()' + ) + .sk-sublabel.opaque(ng-class='ctrl.classForSessionRevision(revision)') + | {{revision.previewSubTitle()}} + .sk-menu-panel-header + .sk-menu-panel-header-title Remote + .sk-menu-panel-header-subtitle {{ctrl.remoteHistoryEntries.length || 'No'}} revisions + a.sk-a.info.sk-h5( + ng-click='ctrl.showRemoteOptions = !ctrl.showRemoteOptions; $event.stopPropagation();' + ) Options + div(ng-if='ctrl.showRemoteOptions') + menu-row( + action='ctrl.fetchRemoteHistory()' + label="'Refresh'" + disabled="ctrl.isfetchingRemoteHistory" + spinner-class="ctrl.isfetchingRemoteHistory ? 'info' : null") + .sk-sublabel + | Fetch history from server. + menu-row( + ng-repeat='revision in ctrl.remoteHistoryEntries track by $index' + action='ctrl.openRemoteRevision(revision);' + label='ctrl.previewRemoteHistoryTitle(revision);' + ) diff --git a/app/assets/templates/directives/session-history-menu.pug b/app/assets/templates/directives/session-history-menu.pug deleted file mode 100644 index 36788db7a..000000000 --- a/app/assets/templates/directives/session-history-menu.pug +++ /dev/null @@ -1,35 +0,0 @@ -#session-history-menu.sn-component - .sk-menu-panel.dropdown-menu - .sk-menu-panel-header - .sk-menu-panel-header-title {{ctrl.history.entries.length || 'No'}} revisions - a.sk-a.info.sk-h5( - ng-click='ctrl.showOptions = !ctrl.showOptions; $event.stopPropagation();' - ) Options - div(ng-if='ctrl.showOptions') - menu-row( - action='ctrl.clearItemHistory()' - label="'Clear note local history'" - ) - menu-row( - action='ctrl.clearAllHistory()' - label="'Clear all local history'" - ) - menu-row( - action='ctrl.toggleAutoOptimize()' - label="(ctrl.autoOptimize ? 'Disable' : 'Enable') + ' auto cleanup'") - .sk-sublabel - | Automatically cleans up small revisions to conserve space. - menu-row( - action='ctrl.toggleDiskSaving()' - label="(ctrl.diskEnabled ? 'Disable' : 'Enable') + ' saving history to disk'" - ) - .sk-sublabel - | Saving to disk is not recommended. Decreases performance and increases app - | loading time and memory footprint. - menu-row( - ng-repeat='revision in ctrl.entries' - action='ctrl.openRevision(revision);' - label='revision.previewTitle()' - ) - .sk-sublabel.opaque(ng-class='ctrl.classForRevision(revision)') - | {{revision.previewSubTitle()}}