diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index 041a6dc82..000000000 --- a/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "allow_root": true -} diff --git a/.gitignore b/.gitignore index 6f7b30833..4065e876d 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ /app/assets/templates/generated/ /node_modules +/dist /dist/javascripts/app.js /dist/javascripts/compiled.js /dist/javascripts/transpiled.js diff --git a/.npmignore b/.npmignore index 40c5f7c39..c0797a167 100644 --- a/.npmignore +++ b/.npmignore @@ -1,37 +1,40 @@ -# See https://help.github.com/articles/ignoring-files for more about ignoring files. -# -# If you find yourself ignoring temporary files generated by your text editor -# or operating system, you probably want to add a global ignore instead: -# git config --global core.excludesfile '~/.gitignore_global' - -# OS & IDE .DS_Store - -# Ignore bundler config. /.bundle -# Ignore all logfiles and tempfiles. +.git +/.sass-cache +/app +/bin +/config +/db +/lib +/log +/node_modules +/test +/vendor + /log/* !/log/.keep /tmp - -/config/cap.yml - -/app/assets/templates/generated/ - -/node_modules - -/.sass-cache - -# Ignore ENV variables config .env .ssh dump.rdb -# Ignore compiled assets /public/assets - -# Ignore user uploads /public/uploads/* !/public/uploads/.keep + +.babelrc +.gitignore +.gitmodules +.npmignore +Capfile +config.ru +Gemfile +Gemfile.lock +Gruntfile.js +package-lock.json +package.json +Rakefile +testing-server.js diff --git a/app/assets/javascripts/app/constants.js b/app/assets/javascripts/app/constants.js index 8916f877f..0ab05d291 100644 --- a/app/assets/javascripts/app/constants.js +++ b/app/assets/javascripts/app/constants.js @@ -1,5 +1,5 @@ angular.module('app') -.constant('appVersion', '3.0.9') +.constant('appVersion', '3.0.10') ; \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index f5eb8221a..6786d7ae0 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -14,7 +14,7 @@ angular.module('app') bindToController: true, link:function(scope, elem, attrs, ctrl) { - scope.$watch('ctrl.note', function(note, oldNote){ + scope.$watch('ctrl.note', (note, oldNote) => { if(note) { ctrl.noteDidChange(note, oldNote); } @@ -24,20 +24,43 @@ angular.module('app') }) .controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager, syncManager, modelManager, themeManager, componentManager, storageManager, sessionHistory, - privilegesManager, keyboardManager) { + privilegesManager, keyboardManager, desktopManager) { this.spellcheck = true; this.componentManager = componentManager; this.componentStack = []; this.isDesktop = isDesktopApplication(); - $rootScope.$on("sync:taking-too-long", function(){ - this.syncTakingTooLong = true; - }.bind(this)); + const MinimumStatusDurationMs = 400; - $rootScope.$on("sync:completed", function(){ - this.syncTakingTooLong = false; - }.bind(this)); + syncManager.addEventHandler((eventName, data) => { + if(!this.note) { + return; + } + + if(eventName == "sync:taking-too-long") { + this.syncTakingTooLong = true; + } + + else if(eventName == "sync:completed") { + this.syncTakingTooLong = false; + if(this.note.dirty) { + // if we're still dirty, don't change status, a sync is likely upcoming. + } else { + let savedItem = data.savedItems.find((item) => item.uuid == this.note.uuid); + let isInErrorState = this.saveError; + if(isInErrorState || savedItem) { + this.showAllChangesSavedStatus(); + } + } + } else if(eventName == "sync:error") { + // only show error status in editor if the note is dirty. Otherwise, it means the originating sync + // came from somewhere else and we don't want to display an error here. + if(this.note.dirty){ + this.showErrorStatus(); + } + } + }); // Right now this only handles offline saving status changes. this.syncStatusObserver = syncManager.registerSyncStatusObserver((status) => { @@ -111,6 +134,9 @@ angular.module('app') // Look through editors again and find the most proper one var editor = this.editorForNote(this.note); this.selectedEditor = editor; + if(!editor) { + this.reloadFont(); + } }); this.noteDidChange = function(note, oldNote) { @@ -158,7 +184,7 @@ angular.module('app') } if(oldNote && oldNote != note) { - if(oldNote.hasChanges) { + if(oldNote.dirty) { this.saveNote(oldNote); } else if(oldNote.dummy) { this.remove()(oldNote); @@ -198,14 +224,14 @@ angular.module('app') if(editor) { if(this.note.getAppDataItem("prefersPlainEditor") == true) { this.note.setAppDataItem("prefersPlainEditor", false); - this.note.setDirty(true); + modelManager.setItemDirty(this.note, true); } this.associateComponentWithCurrentNote(editor); } else { // Note prefers plain editor if(!this.note.getAppDataItem("prefersPlainEditor")) { this.note.setAppDataItem("prefersPlainEditor", true); - this.note.setDirty(true); + modelManager.setItemDirty(this.note, true); } $timeout(() => { this.reloadFont(); @@ -245,85 +271,60 @@ angular.module('app') this.showMenu = false; } - var statusTimeout; + this.EditorNgDebounce = 200; + const SyncDebouce = 1000; + const SyncNoDebounce = 100; - this.saveNote = function(note, callback, dontUpdateClientModified, dontUpdatePreviews) { - // We don't want to update the client modified date if toggling lock for note. - note.setDirty(true, dontUpdateClientModified); + this.saveNote = function({bypassDebouncer, updateClientModified, dontUpdatePreviews}) { + let note = this.note; + note.dummy = false; + + if(note.deleted) { + alert("The note you are attempting to edit has been deleted, and is awaiting sync. Changes you make will be disregarded."); + return; + } + + if(!modelManager.findItem(note.uuid)) { + alert("The note you are attempting to save can not be found or has been deleted. Changes you make will not be synced. Please copy this note's text and start a new note."); + return; + } + + this.showSavingStatus(); if(!dontUpdatePreviews) { let limit = 80; var text = note.text || ""; var truncate = text.length > limit; note.content.preview_plain = text.substring(0, limit) + (truncate ? "..." : ""); - // Clear dynamic previews if using plain editor note.content.preview_html = null; } - syncManager.sync().then((response) => { - if(response && response.error) { - if(!this.didShowErrorAlert) { + modelManager.setItemDirty(note, true, updateClientModified); + + if(this.saveTimeout) { + $timeout.cancel(this.saveTimeout); + } + + let syncDebouceMs; + if(authManager.offline() || bypassDebouncer) { + syncDebouceMs = SyncNoDebounce; + } else { + syncDebouceMs = SyncDebouce; + } + + this.saveTimeout = $timeout(() => { + syncManager.sync().then((response) => { + if(response && response.error && !this.didShowErrorAlert) { this.didShowErrorAlert = true; alert("There was an error saving your note. Please try again."); } - $timeout(() => { - callback && callback(false); - }) - } else { - note.hasChanges = false; - $timeout(() => { - callback && callback(true); - }); - } - }) - } - - let saveTimeout; - this.changesMade = function({bypassDebouncer, dontUpdateClientModified, dontUpdatePreviews} = {}) { - let note = this.note; - note.dummy = false; - - /* In the case of keystrokes, saving should go through a debouncer to avoid frequent calls. - In the case of deleting or archiving a note, it should happen immediately before the note is switched out - */ - let delay = bypassDebouncer ? 0 : 275; - - // In the case of archiving a note, the note is saved immediately, then switched to another note. - // Usually note.hasChanges is set back to false after the saving delay, but in this case, because there is no delay, - // we set it to false immediately so that it is not saved twice: once now, and the other on setNote in oldNote.hasChanges. - note.hasChanges = bypassDebouncer ? false : true; - - if(saveTimeout) $timeout.cancel(saveTimeout); - if(statusTimeout) $timeout.cancel(statusTimeout); - saveTimeout = $timeout(() => { - this.showSavingStatus(); - note.dummy = false; - // Make sure the note exists. A safety measure, as toggling between tags triggers deletes for dummy notes. - // Race conditions have been fixed, but we'll keep this here just in case. - if(!modelManager.findItem(note.uuid)) { - alert("The note you are attempting to save can not be found or has been deleted. Changes you make will not be synced. Please copy this note's text and start a new note."); - return; - } - - this.saveNote(note, (success) => { - if(success) { - if(statusTimeout) $timeout.cancel(statusTimeout); - statusTimeout = $timeout(() => { - this.showAllChangesSavedStatus(); - }, 200) - } else { - if(statusTimeout) $timeout.cancel(statusTimeout); - statusTimeout = $timeout(() => { - this.showErrorStatus(); - }, 200) - } - }, dontUpdateClientModified, dontUpdatePreviews); - }, delay) + }) + }, syncDebouceMs) } this.showSavingStatus = function() { - this.noteStatus = {message: "Saving..."}; + this.setStatus({message: "Saving..."}, false); } this.showAllChangesSavedStatus = function() { @@ -334,24 +335,40 @@ angular.module('app') if(authManager.offline()) { status += " (offline)"; } - this.noteStatus = {message: status}; + + this.setStatus({message: status}); } this.showErrorStatus = function(error) { if(!error) { error = { message: "Sync Unreachable", - desc: "All changes saved offline" + desc: "Changes saved offline" } } this.saveError = true; this.syncTakingTooLong = false; - this.noteStatus = error; + this.setStatus(error); + } + + this.setStatus = function(status, wait = true) { + // Keep every status up for a minimum duration so it doesnt flash crazily. + let waitForMs; + if(!this.noteStatus || !this.noteStatus.date) { + waitForMs = 0; + } else { + waitForMs = MinimumStatusDurationMs - (new Date() - this.noteStatus.date); + } + if(!wait || waitForMs < 0) {waitForMs = 0;} + if(this.statusTimeout) $timeout.cancel(this.statusTimeout); + this.statusTimeout = $timeout(() => { + status.date = new Date(); + this.noteStatus = status; + }, waitForMs) } this.contentChanged = function() { - // content changes should bypass manual debouncer as we use the built in ng-model-options debouncer - this.changesMade({bypassDebouncer: true}); + this.saveNote({updateClientModified: true}); } this.onTitleEnter = function($event) { @@ -361,7 +378,7 @@ angular.module('app') } this.onTitleChange = function() { - this.changesMade({dontUpdatePreviews: true}); + this.saveNote({dontUpdatePreviews: true, updateClientModified: true}); } this.onNameFocus = function() { @@ -398,7 +415,7 @@ angular.module('app') this.remove()(this.note); } else { this.note.content.trashed = true; - this.changesMade({bypassDebouncer: true, dontUpdateClientModified: true, dontUpdatePreviews: true}); + this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true}); } this.showMenu = false; } @@ -416,7 +433,7 @@ angular.module('app') this.restoreTrashedNote = function() { this.note.content.trashed = false; - this.changesMade({bypassDebouncer: true, dontUpdateClientModified: true, dontUpdatePreviews: true}); + this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true}); } this.deleteNotePermanantely = function() { @@ -437,17 +454,17 @@ angular.module('app') this.togglePin = function() { this.note.setAppDataItem("pinned", !this.note.pinned); - this.changesMade({bypassDebouncer: true, dontUpdatePreviews: true}); + this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true}); } this.toggleLockNote = function() { this.note.setAppDataItem("locked", !this.note.locked); - this.changesMade({bypassDebouncer: true, dontUpdateClientModified: true, dontUpdatePreviews: true}); + this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true}); } this.toggleProtectNote = function() { this.note.content.protected = !this.note.content.protected; - this.changesMade({bypassDebouncer: true, dontUpdateClientModified: true, dontUpdatePreviews: true}); + this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true}); // Show privilegesManager if Protection is not yet set up privilegesManager.actionHasPrivilegesConfigured(PrivilegesManager.ActionViewProtectedNotes).then((configured) => { @@ -459,12 +476,12 @@ angular.module('app') this.toggleNotePreview = function() { this.note.content.hidePreview = !this.note.content.hidePreview; - this.changesMade({bypassDebouncer: true, dontUpdateClientModified: true, dontUpdatePreviews: true}); + this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true}); } this.toggleArchiveNote = function() { this.note.setAppDataItem("archived", !this.note.archived); - this.changesMade({bypassDebouncer: true, dontUpdatePreviews: true}); + this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true}); $rootScope.$broadcast("noteArchived"); } @@ -627,6 +644,10 @@ angular.module('app') Components */ + this.onEditorLoad = function(editor) { + desktopManager.redoSearch(); + } + componentManager.registerHandler({identifier: "editor", areas: ["note-tags", "editor-stack", "editor-editor"], activationHandler: (component) => { if(component.area === "note-tags") { // Autocomplete Tags @@ -688,38 +709,17 @@ angular.module('app') if(data.item.content_type == "Tag") { var tag = modelManager.findItem(data.item.uuid); this.addTag(tag); - - // Currently extensions are not notified of association until a full server sync completes. - // We need a better system for this, but for now, we'll manually notify observers - modelManager.notifySyncObserversOfModels([tag], SFModelManager.MappingSourceLocalSaved); } } else if(action === "deassociate-item") { var tag = modelManager.findItem(data.item.uuid); this.removeTag(tag); - - // Currently extensions are not notified of association until a full server sync completes. - // We need a better system for this, but for now, we'll manually notify observers - modelManager.notifySyncObserversOfModels([tag], SFModelManager.MappingSourceLocalSaved); } - else if(action === "save-items" || action === "save-success" || action == "save-error") { + else if(action === "save-items") { if(data.items.map((item) => {return item.uuid}).includes(this.note.uuid)) { - - if(action == "save-items") { - if(this.componentSaveTimeout) $timeout.cancel(this.componentSaveTimeout); - this.componentSaveTimeout = $timeout(this.showSavingStatus.bind(this), 10); - } - - else { - if(this.componentStatusTimeout) $timeout.cancel(this.componentStatusTimeout); - if(action == "save-success") { - this.componentStatusTimeout = $timeout(this.showAllChangesSavedStatus.bind(this), 400); - } else { - this.componentStatusTimeout = $timeout(this.showErrorStatus.bind(this), 400); - } - } + this.showSavingStatus(); } } }}); @@ -785,7 +785,7 @@ angular.module('app') component.disassociatedItemIds.push(this.note.uuid); } - component.setDirty(true); + modelManager.setItemDirty(component, true); syncManager.sync(); } @@ -796,7 +796,7 @@ angular.module('app') component.associatedItemIds.push(this.note.uuid); } - component.setDirty(true); + modelManager.setItemDirty(component, true); syncManager.sync(); } @@ -887,7 +887,7 @@ angular.module('app') $timeout(() => { this.note.text = editor.value; - this.changesMade({bypassDebouncer: true}); + this.saveNote({bypassDebouncer: true}); }) }, }) diff --git a/app/assets/javascripts/app/controllers/home.js b/app/assets/javascripts/app/controllers/home.js index 49e37c873..69071bfa5 100644 --- a/app/assets/javascripts/app/controllers/home.js +++ b/app/assets/javascripts/app/controllers/home.js @@ -3,11 +3,6 @@ angular.module('app') dbManager, syncManager, authManager, themeManager, passcodeManager, storageManager, migrationManager, privilegesManager, statusManager) { - // Lock syncing until local data is loaded. Syncing may be called from a variety of places, - // such as when the window focuses, for example. We don't want sync to occur until all local items are loaded, - // otherwise, if sync happens first, then load, the load may override synced values. - syncManager.lockSyncing(); - storageManager.initialize(passcodeManager.hasPasscode(), authManager.isEphemeralSession()); $scope.platform = getPlatformString(); @@ -98,11 +93,7 @@ angular.module('app') this.syncStatus = statusManager.replaceStatusWithString(this.syncStatus, encryptionEnabled ? `Decrypting ${notesString}` : `Loading ${notesString}`); } - syncManager.loadLocalItems(incrementalCallback).then(() => { - - // First unlock after initially locked to wait for local data loaded. - syncManager.unlockSyncing(); - + syncManager.loadLocalItems({incrementalCallback}).then(() => { $timeout(() => { $rootScope.$broadcast("initial-data-loaded"); // This needs to be processed first before sync is called so that singletonManager observers function properly. // Perform integrity check on first sync @@ -172,9 +163,10 @@ angular.module('app') for(var tagToRemove of toRemove) { tagToRemove.removeItemAsRelationship(note); - tagToRemove.setDirty(true); } + modelManager.setItemsDirty(toRemove, true); + var tags = []; for(var tagString of stringTags) { var existingRelationship = _.find(note.tags, {title: tagString}); @@ -185,9 +177,10 @@ angular.module('app') for(var tag of tags) { tag.addItemAsRelationship(note); - tag.setDirty(true); } + modelManager.setItemsDirty(tags, true); + syncManager.sync(); } @@ -219,7 +212,8 @@ angular.module('app') $scope.removeTag(tag); return; } - tag.setDirty(true); + + modelManager.setItemDirty(tag, true); syncManager.sync().then(callback); modelManager.resortTag(tag); } @@ -244,10 +238,11 @@ angular.module('app') $scope.notesAddNew = function(note) { modelManager.addItem(note); + modelManager.setItemDirty(note); if(!$scope.selectedTag.isSmartTag()) { $scope.selectedTag.addItemAsRelationship(note); - $scope.selectedTag.setDirty(true); + modelManager.setItemDirty($scope.selectedTag, true); } } @@ -308,7 +303,7 @@ angular.module('app') window.addEventListener('drop', (event) => { event.preventDefault(); - alert("Please use FileSafe to attach images and files. Learn more at standardnotes.org/filesafe.") + alert("Please use FileSafe or the Bold Editor to attach images and files. Learn more at standardnotes.org/filesafe.") }, false) diff --git a/app/assets/javascripts/app/controllers/notes.js b/app/assets/javascripts/app/controllers/notes.js index b0959723f..74606dc72 100644 --- a/app/assets/javascripts/app/controllers/notes.js +++ b/app/assets/javascripts/app/controllers/notes.js @@ -40,14 +40,28 @@ angular.module('app') modelManager.removeItemLocally(this.selectedNote); _.pull(this.notes, this.selectedNote); this.selectedNote = null; + this.selectNote(null); + + // We now want to see if the user will download any items from the server. + // If the next sync completes and our notes are still 0, we need to create a dummy. + this.createDummyOnSynCompletionIfNoNotes = true; } } }) syncManager.addEventHandler((syncEvent, data) => { if(syncEvent == "local-data-loaded") { - this.localDataLoaded = true; - this.needsHandleDataLoad = true; + if(this.notes.length == 0) { + this.createNewNote(); + } + } else if(syncEvent == "sync:completed") { + // Pad with a timeout just to be extra patient + $timeout(() => { + if(this.createDummyOnSynCompletionIfNoNotes && this.notes.length == 0) { + this.createDummyOnSynCompletionIfNoNotes = false; + this.createNewNote(); + } + }, 100) } }); @@ -55,13 +69,6 @@ angular.module('app') // reload our notes this.reloadNotes(); - if(this.needsHandleDataLoad) { - this.needsHandleDataLoad = false; - if(this.tag && this.notes.length == 0) { - this.createNewNote(); - } - } - // Note has changed values, reset its flags let notes = allItems.filter((item) => item.content_type == "Note"); for(let note of notes) { @@ -230,6 +237,9 @@ angular.module('app') if(this.hidePinned) { base += " | – Pinned" } + if(this.sortReverse) { + base += " | Reversed" + } return base; } @@ -286,6 +296,13 @@ angular.module('app') }) } + if(note.deleted) { + flags.push({ + text: "Deletion Pending Sync", + class: "danger" + }) + } + note.flags = flags; return flags; @@ -302,13 +319,14 @@ angular.module('app') this.showMenu = false; - if(this.selectedNote && this.selectedNote.dummy) { - if(oldTag) { + if(this.selectedNote) { + if(this.selectedNote.dummy && oldTag) { _.remove(oldTag.notes, this.selectedNote); } } this.noteFilter.text = ""; + desktopManager.searchText(); this.setNotes(tag.notes); @@ -317,8 +335,14 @@ angular.module('app') if(this.notes.length > 0) { this.notes.forEach((note) => { note.visible = true; }) this.selectFirstNote(); - } else if(this.localDataLoaded) { - this.createNewNote(); + } else if(syncManager.initialDataLoaded()) { + if(!tag.isSmartTag()) { + this.createNewNote(); + } else { + if(this.selectedNote && !this.notes.includes(this.selectedNote)) { + this.selectNote(null); + } + } } }) } @@ -357,6 +381,7 @@ angular.module('app') this.selectNote = async function(note, viaClick = false) { if(!note) { + this.selectionMade()(null); return; } @@ -371,7 +396,7 @@ angular.module('app') this.selectedNote = note; if(note.content.conflict_of) { note.content.conflict_of = null; // clear conflict - note.setDirty(true, true); + modelManager.setItemDirty(note, true); syncManager.sync(); } this.selectionMade()(note); diff --git a/app/assets/javascripts/app/controllers/tags.js b/app/assets/javascripts/app/controllers/tags.js index a1a458ced..f72ff9df3 100644 --- a/app/assets/javascripts/app/controllers/tags.js +++ b/app/assets/javascripts/app/controllers/tags.js @@ -30,28 +30,30 @@ angular.module('app') || syncEvent == "local-data-incremental-load") { this.tags = modelManager.tags; this.smartTags = modelManager.getSmartTags(); + if(this.noteCountsNeedReload) { + this.noteCountsNeedReload = false; + this.reloadNoteCounts(); + } } }); - modelManager.addItemSyncObserver("note-list", "*", (allItems, validItems, deletedItems, source, sourceKey) => { - // recompute note counts - let tags = []; - if(this.tags) { - tags = tags.concat(this.tags); - } - if(this.smartTags) { - tags = tags.concat(this.smartTags); - } + modelManager.addItemSyncObserver("tags-list", "*", (allItems, validItems, deletedItems, source, sourceKey) => { + this.noteCountsNeedReload = true; + }); - for(let tag of tags) { - var validNotes = SNNote.filterDummyNotes(tag.notes).filter(function(note){ + this.reloadNoteCounts = function() { + let allTags = []; + if(this.tags) { allTags = allTags.concat(this.tags);} + if(this.smartTags) { allTags = allTags.concat(this.smartTags);} + + for(let tag of allTags) { + var validNotes = SNNote.filterDummyNotes(tag.notes).filter((note) => { return !note.archived && !note.content.trashed; }); tag.cachedNoteCount = validNotes.length; } - }); - + } this.panelController = {}; @@ -108,8 +110,8 @@ angular.module('app') } this.selectedTag = tag; if(tag.content.conflict_of) { - tag.content.conflict_of = null; // clear conflict - tag.setDirty(true, true); + tag.content.conflict_of = null; + modelManager.setItemDirty(tag, true); syncManager.sync(); } this.selectionMade()(tag); diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index cfbff6428..a613e2b69 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -301,7 +301,7 @@ class AccountMenu { if(item.content_type == "SN|Component") { item.active = false; } } - syncManager.sync({additionalFields: ["created_at", "updated_at"]}).then((response) => { + syncManager.sync().then((response) => { // Response can be null if syncing offline callback(response, errorCount); }); diff --git a/app/assets/javascripts/app/directives/views/actionsMenu.js b/app/assets/javascripts/app/directives/views/actionsMenu.js index 78f2cf90b..f79fb1a15 100644 --- a/app/assets/javascripts/app/directives/views/actionsMenu.js +++ b/app/assets/javascripts/app/directives/views/actionsMenu.js @@ -31,8 +31,13 @@ class ActionsMenu { } return; } + action.running = true; - actionsManager.executeAction(action, extension, $scope.item, function(response){ + actionsManager.executeAction(action, extension, $scope.item, (response, error) => { + if(error) { + return; + } + action.running = false; $scope.handleActionResponse(action, response); diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index e6aa0e3c7..97bda8d53 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -5,6 +5,7 @@ class ComponentView { this.templateUrl = "directives/component-view.html"; this.scope = { component: "=", + onLoad: "=", manualDealloc: "=" }; @@ -118,6 +119,7 @@ class ComponentView { $timeout(() => { $scope.loading = false; $scope.issueLoading = desktopError; /* Typically we'd just set this to false at this point, but we now account for desktopError */ + $scope.onLoad && $scope.onLoad($scope.component); }, 7) }) diff --git a/app/assets/javascripts/app/directives/views/editorMenu.js b/app/assets/javascripts/app/directives/views/editorMenu.js index c6bc0fd89..5bcc100f4 100644 --- a/app/assets/javascripts/app/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/directives/views/editorMenu.js @@ -10,7 +10,7 @@ class EditorMenu { }; } - controller($scope, componentManager, syncManager, $timeout) { + controller($scope, componentManager, syncManager, modelManager, $timeout) { 'ngInject'; $scope.formData = {}; @@ -27,7 +27,7 @@ class EditorMenu { if(component) { if(component.content.conflict_of) { component.content.conflict_of = null; // clear conflict if applicable - component.setDirty(true, true); + modelManager.setItemDirty(component, true); syncManager.sync(); } } @@ -52,11 +52,11 @@ class EditorMenu { var currentDefault = componentManager.componentsForArea("editor-editor").filter((e) => {return e.isDefaultEditor()})[0]; if(currentDefault) { currentDefault.setAppDataItem("defaultEditor", false); - currentDefault.setDirty(true); + modelManager.setItemDirty(currentDefault, true); } component.setAppDataItem("defaultEditor", true); - component.setDirty(true); + modelManager.setItemDirty(component, true); syncManager.sync(); $scope.defaultEditor = component; @@ -64,7 +64,7 @@ class EditorMenu { $scope.removeEditorDefault = function(component) { component.setAppDataItem("defaultEditor", false); - component.setDirty(true); + modelManager.setItemDirty(component, true); syncManager.sync(); $scope.defaultEditor = null; diff --git a/app/assets/javascripts/app/directives/views/revisionPreviewModal.js b/app/assets/javascripts/app/directives/views/revisionPreviewModal.js index 70891f354..27ca82858 100644 --- a/app/assets/javascripts/app/directives/views/revisionPreviewModal.js +++ b/app/assets/javascripts/app/directives/views/revisionPreviewModal.js @@ -75,10 +75,11 @@ class RevisionPreviewModal { var uuid = $scope.uuid; item = modelManager.findItem(uuid); item.content = Object.assign({}, $scope.content); + // mapResponseItemsToLocalModels is async, but we don't need to wait here. modelManager.mapResponseItemsToLocalModels([item], SFModelManager.MappingSourceRemoteActionRetrieved); } - item.setDirty(true); + modelManager.setItemDirty(item, true); syncManager.sync(); $scope.dismiss(); diff --git a/app/assets/javascripts/app/services/actionsManager.js b/app/assets/javascripts/app/services/actionsManager.js index 1443d8b06..4cddd4987 100644 --- a/app/assets/javascripts/app/services/actionsManager.js +++ b/app/assets/javascripts/app/services/actionsManager.js @@ -54,10 +54,10 @@ class ActionsManager { async executeAction(action, extension, item, callback) { - var customCallback = (response) => { + var customCallback = (response, error) => { action.running = false; this.$timeout(() => { - callback(response); + callback(response, error); }) } @@ -74,14 +74,14 @@ class ActionsManager { if(!item.errorDecrypting) { if(merge) { - var items = this.modelManager.mapResponseItemsToLocalModels([item], SFModelManager.MappingSourceRemoteActionRetrieved); + var items = await this.modelManager.mapResponseItemsToLocalModels([item], SFModelManager.MappingSourceRemoteActionRetrieved); for(var mappedItem of items) { - mappedItem.setDirty(true); + this.modelManager.setItemDirty(mappedItem, true); } this.syncManager.sync(); customCallback({item: item}); } else { - item = this.modelManager.createItem(item, true /* Dont notify observers */); + item = this.modelManager.createItem(item); customCallback({item: item}); } return true; @@ -127,11 +127,10 @@ class ActionsManager { action.error = false; handleResponseDecryption(response, await this.authManager.keys(), true); }, (response) => { - if(response && response.error) { - alert("An issue occurred while processing this action. Please try again."); - } + let error = (response && response.error) || {message: "An issue occurred while processing this action. Please try again."} + alert(error.message); action.error = true; - customCallback(null); + customCallback(null, error); }) } break; @@ -142,8 +141,10 @@ class ActionsManager { action.error = false; handleResponseDecryption(response, await this.authManager.keys(), false); }, (response) => { + let error = (response && response.error) || {message: "An issue occurred while processing this action. Please try again."} + alert(error.message); action.error = true; - customCallback(null); + customCallback(null, error); }) break; diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index ec54f1232..83fcabdf6 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -145,7 +145,7 @@ class AuthManager extends SFAuthManager { // Safe to create. Create and return object. var prefs = new SFItem({content_type: prefsContentType}); this.modelManager.addItem(prefs); - prefs.setDirty(true); + this.modelManager.setItemDirty(prefs, true); this.$rootScope.sync(); valueCallback(prefs); }); @@ -157,7 +157,7 @@ class AuthManager extends SFAuthManager { syncUserPreferences() { if(this.userPreferences) { - this.userPreferences.setDirty(true); + this.modelManager.setItemDirty(this.userPreferences, true); this.$rootScope.sync(); } } diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index e73ccdd1c..84de54717 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -12,6 +12,8 @@ class ComponentManager extends SNComponentManager { platform: getPlatformString() }); + // this.loggingEnabled = true; + this.$compile = $compile; this.$rootScope = $rootScope; } diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index cb691e058..eb1e6e866 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -70,9 +70,16 @@ class DesktopManager { if(!this.isDesktop) { return; } + this.lastSearchedText = text; this.searchHandler(text); } + redoSearch() { + if(this.lastSearchedText) { + this.searchText(this.lastSearchedText); + } + } + deregisterUpdateObserver(observer) { _.pull(this.updateObservers, observer); @@ -112,7 +119,8 @@ class DesktopManager { this.modelManager.notifySyncObserversOfModels([component], SFModelManager.MappingSourceDesktopInstalled); component.setAppDataItem("installError", null); } - component.setDirty(true); + + this.modelManager.setItemDirty(component, true); this.syncManager.sync(); this.timeout(() => { diff --git a/app/assets/javascripts/app/services/migrationManager.js b/app/assets/javascripts/app/services/migrationManager.js index 468669148..5bc659ade 100644 --- a/app/assets/javascripts/app/services/migrationManager.js +++ b/app/assets/javascripts/app/services/migrationManager.js @@ -38,8 +38,8 @@ class MigrationManager extends SFMigrationManager { } }) component.setAppDataItem("data", editor.data); - component.setDirty(true); this.modelManager.addItem(component); + this.modelManager.setItemDirty(component, true); } } @@ -76,7 +76,7 @@ class MigrationManager extends SFMigrationManager { if(clientData) { note.setDomainDataItem(component.uuid, clientData, ComponentManager.ClientDataDomain); note.setDomainDataItem(component.hosted_url, null, ComponentManager.ClientDataDomain); - note.setDirty(true, true); // dont update client date + this.modelManager.setItemDirty(note, true); hasChanges = true; } } @@ -126,14 +126,14 @@ class MigrationManager extends SFMigrationManager { let tag = this.modelManager.findItem(reference.uuid); if(tag && !tag.hasRelationshipWithItem(note)) { tag.addItemAsRelationship(note); - tag.setDirty(true, true); + this.modelManager.setItemDirty(tag, true); dirtyCount++; } } if(newReferences.length != references.length) { note.content.references = newReferences; - note.setDirty(true, true); + this.modelManager.setItemDirty(note, true); dirtyCount++; } } diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 168f551d9..218704a3f 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -49,8 +49,8 @@ class ModelManager extends SFModelManager { var tag = _.find(this.tags, {title: title}) if(!tag) { tag = this.createItem({content_type: "Tag", content: {title: title}}); - tag.setDirty(true); this.addItem(tag); + this.setItemDirty(tag, true); } return tag; } diff --git a/app/assets/javascripts/app/services/nativeExtManager.js b/app/assets/javascripts/app/services/nativeExtManager.js index 9cc69d7a8..489936c82 100644 --- a/app/assets/javascripts/app/services/nativeExtManager.js +++ b/app/assets/javascripts/app/services/nativeExtManager.js @@ -49,13 +49,13 @@ class NativeExtManager { } if(needsSync) { - resolvedSingleton.setDirty(true); + this.modelManager.setItemDirty(resolvedSingleton, true); this.syncManager.sync(); } }, (valueCallback) => { // Safe to create. Create and return object. let url = window._extensions_manager_location; - console.log("Installing Extensions Manager from URL", url); + // console.log("Installing Extensions Manager from URL", url); if(!url) { console.error("window._extensions_manager_location must be set."); return; @@ -93,7 +93,7 @@ class NativeExtManager { var component = this.modelManager.createItem(item); this.modelManager.addItem(component); - component.setDirty(true); + this.modelManager.setItemDirty(component, true); this.syncManager.sync(); this.systemExtensions.push(component.uuid); @@ -125,13 +125,13 @@ class NativeExtManager { } if(needsSync) { - resolvedSingleton.setDirty(true); + this.modelManager.setItemDirty(resolvedSingleton, true); this.syncManager.sync(); } }, (valueCallback) => { // Safe to create. Create and return object. let url = window._batch_manager_location; - console.log("Installing Batch Manager from URL", url); + // console.log("Installing Batch Manager from URL", url); if(!url) { console.error("window._batch_manager_location must be set."); return; @@ -171,7 +171,7 @@ class NativeExtManager { var component = this.modelManager.createItem(item); this.modelManager.addItem(component); - component.setDirty(true); + this.modelManager.setItemDirty(component, true); this.syncManager.sync(); this.systemExtensions.push(component.uuid); diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index 61b6949b4..41466a4cd 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -5,6 +5,8 @@ class SyncManager extends SFSyncManager { this.$rootScope = $rootScope; this.$compile = $compile; + // this.loggingEnabled = true; + // Content types appearing first are always mapped first this.contentTypeLoadPriority = [ "SN|UserPreferences", "SN|Privileges", diff --git a/app/assets/stylesheets/app/_editor.scss b/app/assets/stylesheets/app/_editor.scss index f27b638d9..3ae1c42cc 100644 --- a/app/assets/stylesheets/app/_editor.scss +++ b/app/assets/stylesheets/app/_editor.scss @@ -6,12 +6,13 @@ $heading-height: 75px; } } -.editor { +.section.editor { flex: 1 50%; display: flex; flex-direction: column; overflow-y: hidden; - background-color: var(--sn-stylekit-background-color); + background-color: var(--sn-stylekit-editor-background-color); + color: var(--sn-stylekit-editor-foreground-color); } #editor-title-bar { @@ -28,12 +29,15 @@ $heading-height: 75px; height: auto; overflow: visible; + $title-width: 70%; + $save-status-width: 30%; + > .title { font-size: var(--sn-stylekit-font-size-h1); font-weight: bold; padding-top: 0px; - width: 100%; - padding-right: 120px; /* make room for save status */ + width: $title-width; + padding-right: 20px; /* make room for save status */ > .input { float: left; @@ -43,17 +47,17 @@ $heading-height: 75px; border: none; outline: none; background-color: transparent; - color: var(--sn-stylekit-foreground-color); + color: var(--sn-stylekit-editor-foreground-color); &:disabled { - color: var(--sn-stylekit-foreground-color); + color: var(--sn-stylekit-editor-foreground-color); } } } #save-status { - width: 20%; + width: $save-status-width; float: right; position: absolute; @@ -63,6 +67,7 @@ $heading-height: 75px; font-weight: normal; margin-top: 4px; text-align: right; + white-space: nowrap; .desc, .message:not(.warning):not(.danger) { // color: var(--sn-stylekit-editor-foreground-color); @@ -122,8 +127,8 @@ $heading-height: 75px; font-family: monospace; overflow-y: scroll; width: 100%; - background-color: var(--sn-stylekit-background-color); - color: var(--sn-stylekit-foreground-color); + background-color: var(--sn-stylekit-editor-background-color); + color: var(--sn-stylekit-editor-foreground-color); border: none; outline: none; diff --git a/app/assets/stylesheets/app/_ionicons.scss b/app/assets/stylesheets/app/_ionicons.scss index 48b99d156..ae07f0906 100644 --- a/app/assets/stylesheets/app/_ionicons.scss +++ b/app/assets/stylesheets/app/_ionicons.scss @@ -15,8 +15,6 @@ .ion, .ionicons, .ion-locked:before, .ion-plus:before, -.ion-arrow-return-left:before, -.ion-arrow-return-right:before, { display: inline-block; font-family: "Ionicons"; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; text-rendering: auto; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } @@ -25,8 +23,4 @@ .ion-plus:before { content: "\f218"; } -.ion-arrow-return-left:before { content: "\f265"; } - -.ion-arrow-return-right:before { content: "\f266"; } - /*# sourceMappingURL=ionicons.css.map */ diff --git a/app/assets/stylesheets/app/_notes.scss b/app/assets/stylesheets/app/_notes.scss index d33621c5d..2b3f31354 100644 --- a/app/assets/stylesheets/app/_notes.scss +++ b/app/assets/stylesheets/app/_notes.scss @@ -23,7 +23,8 @@ font-size: var(--sn-stylekit-font-size-h1); .section-title-bar-header .title { - color: var(--sn-stylekit-neutral-color); + font-size: var(--sn-stylekit-font-size-h3); + font-weight: 600; width: calc(90% - 45px); } } @@ -33,21 +34,24 @@ margin-top: 14px; } + #notes-options-menu { + margin-left: 10px; + } + .filter-section { clear: left; - height: 29px; + height: 28px; margin-top: 14px; position: relative; .filter-bar { background-color: var(--sn-stylekit-contrast-background-color); - border-radius: 4px; + border-radius: var(--sn-stylekit-general-border-radius); height: 100%; color: #909090; text-align: center; font-weight: normal; font-size: var(--sn-stylekit-font-size-h3); - line-height: 35px; border: none; width: 100%; @@ -155,7 +159,7 @@ padding: 4px; padding-left: 6px; padding-right: 6px; - border-radius: 2px; + border-radius: var(--sn-stylekit-general-border-radius); margin-right: 4px; &.info { diff --git a/app/assets/stylesheets/app/_stylekit-sub.scss b/app/assets/stylesheets/app/_stylekit-sub.scss index fe5cd0a20..baf6fd293 100644 --- a/app/assets/stylesheets/app/_stylekit-sub.scss +++ b/app/assets/stylesheets/app/_stylekit-sub.scss @@ -51,6 +51,12 @@ } } +.sk-menu-panel-header { + a { + cursor: pointer; + } +} + #session-history-menu { .sk-menu-panel .sk-menu-panel-row .sk-sublabel.opaque { opacity: 1.0 diff --git a/app/assets/stylesheets/app/_ui.scss b/app/assets/stylesheets/app/_ui.scss index ebc2bcfa5..29ab8cb69 100644 --- a/app/assets/stylesheets/app/_ui.scss +++ b/app/assets/stylesheets/app/_ui.scss @@ -103,10 +103,6 @@ $screen-md-max: ($screen-lg-min - 1) !default; padding: 10px !important; } -.red { - color: red !important; -} - .bold { font-weight: bold !important; } diff --git a/app/assets/templates/directives/account-menu.html.haml b/app/assets/templates/directives/account-menu.html.haml index 356400cc2..6fc249814 100644 --- a/app/assets/templates/directives/account-menu.html.haml +++ b/app/assets/templates/directives/account-menu.html.haml @@ -22,7 +22,9 @@ %form.sk-panel-form{"ng-submit" => "submitAuthForm()"} .sk-panel-section - %input.sk-input.contrast{:placeholder => 'Email', "sn-autofocus" => 'true', "should-focus" => "true", :name => 'email', :required => true, :type => 'email', 'ng-model' => 'formData.email', 'spellcheck' => 'false'} + %input.sk-input.contrast{:placeholder => 'Email', "sn-autofocus" => 'true', + "should-focus" => "true", :name => 'email', :required => true, :type => 'email', + 'ng-model' => 'formData.email', 'spellcheck' => 'false', "ng-model-options"=> "{allowInvalid: true}"} %input.sk-input.contrast{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.user_password', 'sn-enter' => 'submitAuthForm()'} %input.sk-input.contrast{:placeholder => 'Confirm Password', "ng-if" => "formData.showRegister", :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.password_conf', 'sn-enter' => 'submitAuthForm()'} .sk-panel-row diff --git a/app/assets/templates/directives/session-history-menu.html.haml b/app/assets/templates/directives/session-history-menu.html.haml index da3633a74..b060d299e 100644 --- a/app/assets/templates/directives/session-history-menu.html.haml +++ b/app/assets/templates/directives/session-history-menu.html.haml @@ -1,10 +1,8 @@ .sn-component#session-history-menu .sk-menu-panel.dropdown-menu .sk-menu-panel-header - .sk-menu-panel-column - .sk-menu-panel-header-title {{history.entries.length || 'No'}} revisions - .sk-menu-panel-column{"ng-click" => "showOptions = !showOptions; $event.stopPropagation();"} - %a.sk-a.info.sk-menu-panel-header-title Options + .sk-menu-panel-header-title {{history.entries.length || 'No'}} revisions + %a.sk-a.info.sk-h5{"ng-click" => "showOptions = !showOptions; $event.stopPropagation();"} Options %div{"ng-if" => "showOptions"} %menu-row{"label" => "'Clear note local history'", "action" => "clearItemHistory()"} diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml index 609e11d3a..084927059 100644 --- a/app/assets/templates/editor.html.haml +++ b/app/assets/templates/editor.html.haml @@ -75,11 +75,12 @@ %panel-resizer.left{"ng-if" => "ctrl.marginResizersEnabled", "panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish","control" => "ctrl.leftResizeControl", "min-width" => 300, "property" => "'left'", "hoverable" => "true"} - %component-view.component-view{"ng-if" => "ctrl.selectedEditor", "component" => "ctrl.selectedEditor"} + %component-view.component-view{"ng-if" => "ctrl.selectedEditor", "component" => "ctrl.selectedEditor", "on-load" => "ctrl.onEditorLoad"} %textarea.editable#note-text-editor{"ng-if" => "!ctrl.selectedEditor", "ng-model" => "ctrl.note.text", "ng-readonly" => "ctrl.note.locked", "ng-change" => "ctrl.contentChanged()", "ng-trim" => "false", "ng-click" => "ctrl.clickedTextArea()", - "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto", "ng-attr-spellcheck" => "{{ctrl.spellcheck}}", "ng-model-options"=>"{ debounce: 300 }"} + "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto", "ng-attr-spellcheck" => "{{ctrl.spellcheck}}", + "ng-model-options"=>"{ debounce: ctrl.EditorNgDebounce }"} {{ctrl.onSystemEditorLoad()}} %panel-resizer{"ng-if" => "ctrl.marginResizersEnabled", "panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish", "control" => "ctrl.rightResizeControl", "min-width" => 300, "hoverable" => "true", "property" => "'right'"} diff --git a/app/assets/templates/footer.html.haml b/app/assets/templates/footer.html.haml index 441db5159..739242274 100644 --- a/app/assets/templates/footer.html.haml +++ b/app/assets/templates/footer.html.haml @@ -1,5 +1,5 @@ .sn-component - #footer-bar.sk-app-bar.no-edges + #footer-bar.sk-app-bar.no-edges.no-bottom-edge .left .sk-app-bar-item{"ng-click" => "ctrl.accountMenuPressed()", "click-outside" => "ctrl.clickOutsideAccountMenu()", "is-open" => "ctrl.showAccountMenu"} diff --git a/app/assets/templates/home.html.haml b/app/assets/templates/home.html.haml index 7b408e005..e0ba05513 100644 --- a/app/assets/templates/home.html.haml +++ b/app/assets/templates/home.html.haml @@ -3,8 +3,7 @@ %lock-screen{"ng-if" => "needsUnlock", "on-success" => "onSuccessfulUnlock"} .app#app{"ng-if" => "!needsUnlock", "ng-class" => "appClass"} - %tags-section{"save" => "tagsSave", "add-new" => "tagsAddNew", "selection-made" => "tagsSelectionMade", - "tags" => "tags", "remove-tag" => "removeTag"} + %tags-section{"save" => "tagsSave", "add-new" => "tagsAddNew", "selection-made" => "tagsSelectionMade", "remove-tag" => "removeTag"} %notes-section{"add-new" => "notesAddNew", "selection-made" => "notesSelectionMade", "tag" => "selectedTag"} %editor-section{"note" => "selectedNote", "remove" => "deleteNote", "update-tags" => "updateTagsForNote"} diff --git a/app/assets/templates/notes.html.haml b/app/assets/templates/notes.html.haml index fb2ea96e7..07886c1a0 100644 --- a/app/assets/templates/notes.html.haml +++ b/app/assets/templates/notes.html.haml @@ -22,12 +22,11 @@ .sk-app-bar-item-column .sk-sublabel {{ctrl.optionsSubtitle()}} - .sk-menu-panel.dropdown-menu{"ng-show" => "ctrl.showMenu"} + .sk-menu-panel.dropdown-menu#notes-options-menu{"ng-show" => "ctrl.showMenu"} .sk-menu-panel-header .sk-menu-panel-header-title Sort By - .sk-button.sk-secondary-contrast{"ng-click" => "ctrl.toggleReverseSort()"} - .sk-label - %i.icon{"ng-class" => "{'ion-arrow-return-left' : ctrl.sortReverse == false, 'ion-arrow-return-right' : ctrl.sortReverse == true }"} + %a.info.sk-h5{"ng-click" => "ctrl.toggleReverseSort()"} + {{ctrl.sortReverse === true ? 'Disable Reverse Sort' : 'Enable Reverse Sort'}} %menu-row{"label" => "'Date Added'", "circle" => "ctrl.sortBy == 'created_at' && 'success'", "action" => "ctrl.selectedMenuItem(); ctrl.selectedSortByCreated()", "desc" => "'Sort notes by newest first'"} %menu-row{"label" => "'Date Modified'", "circle" => "ctrl.sortBy == 'client_updated_at' && 'success'", "action" => "ctrl.selectedMenuItem(); ctrl.selectedSortByUpdated()", "desc" => "'Sort notes with the most recently updated first'"} diff --git a/app/assets/templates/tags.html.haml b/app/assets/templates/tags.html.haml index 33bca1c2e..e020fc0ea 100644 --- a/app/assets/templates/tags.html.haml +++ b/app/assets/templates/tags.html.haml @@ -32,8 +32,8 @@ "ng-change" => "ctrl.tagTitleDidChange(tag)", "ng-blur" => "ctrl.saveTag($event, tag)", "spellcheck" => "false"} .count {{tag.cachedNoteCount}} - .red.small-text.bold{"ng-show" => "tag.content.conflict_of"} Conflicted Copy - .red.small-text.bold{"ng-show" => "tag.errorDecrypting"} Missing Keys + .danger.small-text.bold{"ng-show" => "tag.content.conflict_of"} Conflicted Copy + .danger.small-text.bold{"ng-show" => "tag.errorDecrypting"} Missing Keys .menu{"ng-show" => "ctrl.selectedTag == tag"} %a.item{"ng-click" => "ctrl.selectedRenameTag($event, tag)", "ng-show" => "!ctrl.editingTag"} Rename diff --git a/circle.yml b/circle.yml deleted file mode 100644 index a8597534c..000000000 --- a/circle.yml +++ /dev/null @@ -1,19 +0,0 @@ -machine: - ruby: - version: - 2.3.1 - node: - version: - 6.1.0 - -dependencies: - pre: - - npm install -g grunt - override: - - bundle install - - npm install - - grunt - -test: - override: - - npm test diff --git a/index.html b/index.html new file mode 100644 index 000000000..6054b15f5 --- /dev/null +++ b/index.html @@ -0,0 +1,36 @@ + + +
+ + + + + + + + + + + + + + + +