diff --git a/Gruntfile.js b/Gruntfile.js index 39a79854f..f3afa8c4a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -72,6 +72,7 @@ module.exports = function(grunt) { src: [ 'node_modules/sn-models/dist/sn-models.js', 'app/assets/javascripts/app/*.js', + 'app/assets/javascripts/app/models/**/*.js', 'app/assets/javascripts/app/controllers/**/*.js', 'app/assets/javascripts/app/services/**/*.js', 'app/assets/javascripts/app/filters/**/*.js', diff --git a/app/assets/javascripts/app/directives/views/actionsMenu.js b/app/assets/javascripts/app/directives/views/actionsMenu.js index 7e97d868f..62cfece78 100644 --- a/app/assets/javascripts/app/directives/views/actionsMenu.js +++ b/app/assets/javascripts/app/directives/views/actionsMenu.js @@ -49,7 +49,7 @@ class ActionsMenu { switch (action.verb) { case "render": { var item = response.item; - actionsManager.presentRevisionPreviewModal(item); + actionsManager.presentRevisionPreviewModal(item.uuid, item.content); } } } diff --git a/app/assets/javascripts/app/directives/views/revisionPreviewModal.js b/app/assets/javascripts/app/directives/views/revisionPreviewModal.js index 40cf3034b..d5d6313d3 100644 --- a/app/assets/javascripts/app/directives/views/revisionPreviewModal.js +++ b/app/assets/javascripts/app/directives/views/revisionPreviewModal.js @@ -4,9 +4,8 @@ class RevisionPreviewModal { this.restrict = "E"; this.templateUrl = "directives/revision-preview-modal.html"; this.scope = { - revision: "=", - show: "=", - callback: "=" + uuid: "=", + content: "=" }; } @@ -27,25 +26,23 @@ class RevisionPreviewModal { var item; if(asCopy) { - var contentCopy = Object.assign({}, $scope.revision.content); + var contentCopy = Object.assign({}, $scope.content); if(contentCopy.title) { contentCopy.title += " (copy)"; } item = modelManager.createItem({content_type: "Note", content: contentCopy}); modelManager.addItem(item); } else { - // revision can be an ItemRevision revision object or just a plain SFItem - var uuid = $scope.revision.uuid; + var uuid = $scope.uuid; item = modelManager.findItem(uuid); - item.content = Object.assign({}, $scope.revision.content); + item.content = Object.assign({}, $scope.content); modelManager.mapResponseItemsToLocalModels([item], SFModelManager.MappingSourceRemoteActionRetrieved); } + item.setDirty(true); syncManager.sync(); $scope.dismiss(); } - } - } angular.module('app').directive('revisionPreviewModal', () => new RevisionPreviewModal); diff --git a/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js b/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js index 64e94bc5a..4522b9abb 100644 --- a/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js +++ b/app/assets/javascripts/app/directives/views/sessionHistoryMenu.js @@ -20,7 +20,7 @@ class SessionHistoryMenu { $scope.reloadHistory(); $scope.openRevision = function(revision) { - actionsManager.presentRevisionPreviewModal(revision); + actionsManager.presentRevisionPreviewModal(revision.item.uuid, revision.item.content); } $scope.classForRevision = function(revision) { diff --git a/app/assets/javascripts/app/models/noteHistoryEntry.js b/app/assets/javascripts/app/models/noteHistoryEntry.js new file mode 100644 index 000000000..922388b86 --- /dev/null +++ b/app/assets/javascripts/app/models/noteHistoryEntry.js @@ -0,0 +1,37 @@ +class NoteHistoryEntry extends SFItemHistoryEntry { + + setPreviousEntry(previousEntry) { + super.setPreviousEntry(previousEntry); + if(previousEntry) { + this.textCharDiffLength = this.item.content.text.length - previousEntry.item.content.text.length; + } else { + this.textCharDiffLength = this.item.content.text.length; + } + } + + previewTitle() { + return this.item.updated_at.toLocaleString(); + } + + operationVector() { + if(!this.hasPreviousEntry || this.textCharDiffLength == 0) { + return 0; + } else if(this.textCharDiffLength < 0) { + return -1; + } else { + return 1; + } + } + + previewSubTitle() { + if(!this.hasPreviousEntry) { + return `${this.textCharDiffLength} characters loaded` + } else if(this.textCharDiffLength < 0) { + return `${this.textCharDiffLength * -1} characters removed` + } else if(this.textCharDiffLength > 0) { + return `${this.textCharDiffLength} characters added` + } else { + return "Title changed" + } + } +} diff --git a/app/assets/javascripts/app/services/actionsManager.js b/app/assets/javascripts/app/services/actionsManager.js index 7790262cf..1953907c3 100644 --- a/app/assets/javascripts/app/services/actionsManager.js +++ b/app/assets/javascripts/app/services/actionsManager.js @@ -198,10 +198,11 @@ class ActionsManager { }) } - presentRevisionPreviewModal(revision) { + presentRevisionPreviewModal(uuid, content) { var scope = this.$rootScope.$new(true); - scope.revision = revision; - var el = this.$compile( "" )(scope); + scope.uuid = uuid; + scope.content = content; + var el = this.$compile( "" )(scope); angular.element(document.body).append(el); } diff --git a/app/assets/javascripts/app/services/sessionHistory.js b/app/assets/javascripts/app/services/sessionHistory.js index e20114fe9..d64dec37f 100644 --- a/app/assets/javascripts/app/services/sessionHistory.js +++ b/app/assets/javascripts/app/services/sessionHistory.js @@ -1,280 +1,24 @@ -const SessionHistoryPersistKey = "sessionHistory_persist"; -const SessionHistoryRevisionsKey = "sessionHistory_revisions"; -const SessionHistoryAutoOptimizeKey = "sessionHistory_autoOptimize"; - -class SessionHistory { +class SessionHistory extends SFSessionHistoryManager { constructor(modelManager, storageManager, authManager, passcodeManager, $timeout) { - this.modelManager = modelManager; - this.storageManager = storageManager; - this.authManager = authManager; - this.passcodeManager = passcodeManager; - this.$timeout = $timeout; - this.loadFromDisk().then(() => { - this.modelManager.addItemSyncObserver("session-history", "Note", (allItems, validItems, deletedItems, source, sourceKey) => { - for(let item of allItems) { - this.addRevision(item); - } - }); - }) - } - - async encryptionParams() { - let offline = this.authManager.offline(); - let auth_params = offline ? this.passcodeManager.passcodeAuthParams() : await this.authManager.getAuthParams(); - let keys = offline ? this.passcodeManager.keys() : await this.authManager.keys(); - return {keys, auth_params}; - } - - addRevision(item) { - var added = this.historyContainer.addRevision(item); - - if(added) { - if(this.diskTimeout) {this.$timeout.cancel(this.diskTimeout)}; - this.diskTimeout = this.$timeout(() => { - this.saveToDisk(); - }, 1000) - } - } - - historyForItem(item) { - return this.historyContainer.historyForItem(item); - } - - async clearItemHistory(item) { - delete this.historyContainer.clearItemHistory(item); - return this.saveToDisk(); - } - - async clearAllHistory() { - this.historyContainer.clearAllHistory(); - return this.storageManager.removeItem(SessionHistoryRevisionsKey); - } - - async toggleDiskSaving() { - this.diskEnabled = !this.diskEnabled; - - if(this.diskEnabled) { - this.storageManager.setItem(SessionHistoryPersistKey, JSON.stringify(true)); - this.saveToDisk(); - } else { - this.storageManager.setItem(SessionHistoryPersistKey, JSON.stringify(false)); - return this.storageManager.removeItem(SessionHistoryRevisionsKey); - } - } - - get autoOptimize() { - return this.historyContainer.autoOptimize; - } - - async toggleAutoOptimize() { - this.historyContainer.autoOptimize = !this.historyContainer.autoOptimize; - - if(this.historyContainer.autoOptimize) { - this.storageManager.setItem(SessionHistoryAutoOptimizeKey, JSON.stringify(true)); - } else { - this.storageManager.setItem(SessionHistoryAutoOptimizeKey, JSON.stringify(false)); - } - } - - async saveToDisk() { - if(!this.diskEnabled) { - return; - } - let encryptionParams = await this.encryptionParams(); - var itemParams = new SFItemParams(this.historyContainer, encryptionParams.keys, encryptionParams.auth_params); - itemParams.paramsForSync().then((syncParams) => { - // console.log("Saving to disk", syncParams); - this.storageManager.setItem(SessionHistoryRevisionsKey, JSON.stringify(syncParams)); - }) - } - - async loadFromDisk() { - var diskValue = await this.storageManager.getItem(SessionHistoryPersistKey); - if(diskValue) { - this.diskEnabled = JSON.parse(diskValue); + SFItemHistory.HistoryEntryClassMapping = { + "Note" : NoteHistoryEntry } - var historyValue = await this.storageManager.getItem(SessionHistoryRevisionsKey); - if(historyValue) { - historyValue = JSON.parse(historyValue); - let encryptionParams = await this.encryptionParams(); - await SFJS.itemTransformer.decryptItem(historyValue, encryptionParams.keys); - var historyContainer = new HistoryContainer(historyValue); - this.historyContainer = historyContainer; - } else { - this.historyContainer = new HistoryContainer(); - } - - var autoOptimizeValue = await this.storageManager.getItem(SessionHistoryAutoOptimizeKey); - if(autoOptimizeValue) { - this.historyContainer.autoOptimize = JSON.parse(autoOptimizeValue); - } else { - // default value is true - this.historyContainer.autoOptimize = true; - } - } - - async optimize() { - return this.historyContainer.optimize(); - } -} - -class HistoryContainer extends SFItem { - constructor(json_obj) { - super(json_obj); - - if(!this.content.itemsDictionary) { - this.content.itemsDictionary = {}; - } - - var objectKeys = Object.keys(this.content.itemsDictionary); - objectKeys.forEach((key) => { - var value = this.content.itemsDictionary[key]; - this.content.itemsDictionary[key] = new ItemHistory(value); - }); - } - - addRevision(item) { - if(!this.content.itemsDictionary[item.uuid]) { - this.content.itemsDictionary[item.uuid] = new ItemHistory(); - } - var itemHistory = this.content.itemsDictionary[item.uuid]; - return itemHistory.addRevision(item, this.autoOptimize); - } - - historyForItem(item) { - return this.content.itemsDictionary[item.uuid]; - } - - clearItemHistory(item) { - delete this.content.itemsDictionary[item.uuid]; - } - - clearAllHistory() { - this.content.itemsDictionary = {}; - } - - optimize() { - var objectKeys = Object.keys(this.content.itemsDictionary); - objectKeys.forEach((key) => { - var itemHistory = this.content.itemsDictionary[key]; - itemHistory.optimize(); - }); - } -} - -class ItemHistory { - - constructor(json_obj = {}) { - if(!this.revisions) { - this.revisions = []; - } - - if(json_obj.revisions) { - for(var revision of json_obj.revisions) { - this.revisions.push(new NoteRevision(revision, this.revisions[this.revisions.length - 1], revision.date)); + var keyRequestHandler = async () => { + let offline = authManager.offline(); + let auth_params = offline ? passcodeManager.passcodeAuthParams() : await authManager.getAuthParams(); + let keys = offline ? passcodeManager.keys() : await authManager.keys(); + return { + keys: keys, + offline: offline, + auth_params: auth_params } } - } - addRevision(item, autoOptimize) { - var previousRevision = this.revisions[this.revisions.length - 1]; - var prospectiveRevision = new NoteRevision(item, previousRevision, item.updated_at); - - // Don't add first revision if text length is 0, as this means it's a new note. - // Actually, we'll skip this. If we do this, the first character added to a new note - // will be displayed as "1 characters loaded" - // if(!previousRevision && prospectiveRevision.textCharDiffLength == 0) { - // return; - // } - - // Don't add if text is the same - if(prospectiveRevision.isSameAsRevision(previousRevision)) { - return; - } - - this.revisions.push(prospectiveRevision); - - // Clean up if there are too many revisions - const LargeRevisionAmount = 100; - if(autoOptimize && this.revisions.length > LargeRevisionAmount) { - this.optimize(); - } - - return prospectiveRevision; - } - - optimize() { - const SmallRevisionLength = 15; - this.revisions = this.revisions.filter((revision, index) => { - // Keep only first and last item and items whos diff length is greater than the small revision length. - var isFirst = index == 0; - var isLast = index == this.revisions.length - 1; - var isSmallRevision = Math.abs(revision.textCharDiffLength) < SmallRevisionLength; - return isFirst || isLast || !isSmallRevision; - }) - } - -} - -class ItemRevision { - - constructor(item, previousRevision, date) { - if(typeof(date) == "string") { - this.date = new Date(date); - } else { - this.date = date; - } - this.uuid = item.uuid; - this.hasPreviousRevision = previousRevision != null; - this.content = Object.assign({}, item.content); - } - - isSameAsRevision(revision) { - if(!revision) { - return false; - } - return JSON.stringify(this.content) === JSON.stringify(revision.content); - } - -} - -class NoteRevision extends ItemRevision { - constructor(item, previousRevision, date) { - super(item, previousRevision, date); - if(previousRevision) { - this.textCharDiffLength = this.content.text.length - previousRevision.content.text.length; - } else { - this.textCharDiffLength = this.content.text.length; - } - } - - previewTitle() { - return this.date.toLocaleString(); - } - - operationVector() { - if(!this.hasPreviousRevision || this.textCharDiffLength == 0) { - return 0; - } else if(this.textCharDiffLength < 0) { - return -1; - } else { - return 1; - } - } - - previewSubTitle() { - if(!this.hasPreviousRevision) { - return `${this.textCharDiffLength} characters loaded` - } else if(this.textCharDiffLength < 0) { - return `${this.textCharDiffLength * -1} characters removed` - } else if(this.textCharDiffLength > 0) { - return `${this.textCharDiffLength} characters added` - } else { - return "Title changed" - } + var contentTypes = ["Note"]; + super(modelManager, storageManager, keyRequestHandler, contentTypes, $timeout); } } diff --git a/app/assets/templates/directives/revision-preview-modal.html.haml b/app/assets/templates/directives/revision-preview-modal.html.haml index 1e9b5f107..df37fb182 100644 --- a/app/assets/templates/directives/revision-preview-modal.html.haml +++ b/app/assets/templates/directives/revision-preview-modal.html.haml @@ -9,5 +9,5 @@ %a.close-button.info{"ng-click" => "restore(true)"} Restore as copy %a.close-button.info{"ng-click" => "dismiss(); $event.stopPropagation()"} Close .content.selectable - %h2 {{revision.content.title}} - %p.normal{"style" => "white-space: pre-wrap; font-size: 16px;"} {{revision.content.text}} + %h2 {{content.title}} + %p.normal{"style" => "white-space: pre-wrap; font-size: 16px;"} {{content.text}} diff --git a/app/assets/templates/directives/session-history-menu.html.haml b/app/assets/templates/directives/session-history-menu.html.haml index d4556896c..244ee9ba6 100644 --- a/app/assets/templates/directives/session-history-menu.html.haml +++ b/app/assets/templates/directives/session-history-menu.html.haml @@ -2,7 +2,7 @@ .menu-panel.dropdown-menu .header .column - %h4.title {{history.revisions.length || 'No'}} revisions + %h4.title {{history.entries.length || 'No'}} revisions %h4{"ng-click" => "showOptions = !showOptions; $event.stopPropagation();"} %a Options @@ -16,7 +16,7 @@ .sublabel Saving to disk may increase app loading time and memory footprint. - %menu-row{"ng-repeat" => "revision in history.revisions", + %menu-row{"ng-repeat" => "revision in history.entries", "ng-click" => "openRevision(revision); $event.stopPropagation();", "label" => "revision.previewTitle()"} .sublabel.opaque{"ng-class" => "classForRevision(revision)"} diff --git a/package.json b/package.json index 0b66c8a4b..e0081141c 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "karma-jasmine": "^1.1.0", "karma-phantomjs-launcher": "^1.0.2", "sn-stylekit": "1.0.15", - "standard-file-js": "0.3.2", + "standard-file-js": "file:~/Desktop/sf/sfjs", "sn-models": "0.1.0", "connect": "^3.6.6", "mocha": "^5.2.0",