diff --git a/Gruntfile.js b/Gruntfile.js index 01020118a..197c493ce 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -86,7 +86,6 @@ module.exports = function(grunt) { 'vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.js', 'vendor/assets/bower_components/lodash/dist/lodash.min.js', 'vendor/assets/bower_components/restangular/dist/restangular.js', - 'vendor/assets/bower_components/marked/lib/marked.js', 'vendor/assets/javascripts/crypto/*.js' ], dest: 'vendor/assets/javascripts/lib.js', diff --git a/app/assets/javascripts/app/frontend/controllers/editor.js b/app/assets/javascripts/app/frontend/controllers/editor.js index 6834360eb..c9e8c9016 100644 --- a/app/assets/javascripts/app/frontend/controllers/editor.js +++ b/app/assets/javascripts/app/frontend/controllers/editor.js @@ -15,87 +15,64 @@ angular.module('app.frontend') bindToController: true, link:function(scope, elem, attrs, ctrl) { - - /** - * Insert 4 spaces when a tab key is pressed, - * only used when inside of the text editor. - * If the shift key is pressed first, this event is - * not fired. - */ - var handleTab = function (event) { - if (!event.shiftKey && event.which == 9) { - event.preventDefault(); - var start = this.selectionStart; - var end = this.selectionEnd; - var spaces = " "; - - // Insert 4 spaces - this.value = this.value.substring(0, start) - + spaces + this.value.substring(end); - - // Place cursor 4 spaces away from where - // the tab key was pressed - this.selectionStart = this.selectionEnd = start + 4; - } - } - - var handler = function(event) { - if (event.ctrlKey || event.metaKey) { - switch (String.fromCharCode(event.which).toLowerCase()) { - case 's': - event.preventDefault(); - $timeout(function(){ - ctrl.saveNote(event); - }); - break; - case 'e': - event.preventDefault(); - $timeout(function(){ - ctrl.clickedEditNote(); - }) - break; - case 'm': - event.preventDefault(); - $timeout(function(){ - ctrl.toggleMarkdown(); - }) - break; - case 'o': - event.preventDefault(); - $timeout(function(){ - ctrl.toggleFullScreen(); - }) - break; - } - } - }; - - window.addEventListener('keydown', handler); - var element = document.getElementById("note-text-editor"); - element.addEventListener('keydown', handleTab); - - scope.$on('$destroy', function(){ - window.removeEventListener('keydown', handler); - }) - scope.$watch('ctrl.note', function(note, oldNote){ if(note) { ctrl.setNote(note, oldNote); - } else { - ctrl.note = {}; } }); } } }) - .controller('EditorCtrl', function ($sce, $timeout, authManager, markdownRenderer, $rootScope, extensionManager, syncManager) { + .controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, extensionManager, syncManager, modelManager) { + + window.addEventListener("message", function(event){ + if(event.data.status) { + this.postNoteToExternalEditor(); + } else { + var id = event.data.id; + var text = event.data.text; + var data = event.data.data; + + if(this.note.uuid === id) { + this.note.text = text; + if(data) { + var changesMade = this.customEditor.setData(id, data); + if(changesMade) { + this.customEditor.setDirty(true); + } + } + this.changesMade(); + } + } + }.bind(this), false); this.setNote = function(note, oldNote) { - this.editorMode = 'edit'; + var currentEditor = this.customEditor; + this.customEditor = null; this.showExtensions = false; this.showMenu = false; this.loadTagsString(); + var setEditor = function(editor) { + this.customEditor = editor; + this.postNoteToExternalEditor(); + }.bind(this) + + var editor = this.editorForNote(note); + if(editor) { + if(currentEditor !== editor) { + // switch after timeout, so that note data isnt posted to current editor + $timeout(function(){ + setEditor(editor); + }.bind(this)); + } else { + // switch immediately + setEditor(editor); + } + } else { + this.customEditor = null; + } + if(note.safeText().length == 0 && note.dummy) { this.focusTitle(100); } @@ -109,19 +86,50 @@ angular.module('app.frontend') } } - this.hasAvailableExtensions = function() { - return extensionManager.extensionsInContextOfItem(this.note).length > 0; + this.selectedEditor = function(editor) { + this.showEditorMenu = false; + + if(this.customEditor && editor !== this.customEditor) { + this.customEditor.removeItemAsRelationship(this.note); + this.customEditor.setDirty(true); + } + + if(editor.default) { + this.customEditor = null; + } else { + this.customEditor = editor; + this.customEditor.addItemAsRelationship(this.note); + this.customEditor.setDirty(true); + } + }.bind(this) + + this.editorForNote = function(note) { + var editors = modelManager.itemsForContentType("SN|Editor"); + for(var editor of editors) { + if(_.includes(editor.notes, note)) { + return editor; + } + } + return null; } - this.onPreviewDoubleClick = function() { - this.editorMode = 'edit'; - this.focusEditor(100); + this.postNoteToExternalEditor = function() { + var externalEditorElement = document.getElementById("editor-iframe"); + if(externalEditorElement) { + externalEditorElement.contentWindow.postMessage({text: this.note.text, data: this.customEditor.dataForKey(this.note.uuid), id: this.note.uuid}, '*'); + } + } + + this.hasAvailableExtensions = function() { + return extensionManager.extensionsInContextOfItem(this.note).length > 0; } this.focusEditor = function(delay) { setTimeout(function(){ var element = document.getElementById("note-text-editor"); - element.focus(); + if(element) { + element.focus(); + } }, delay) } @@ -135,10 +143,6 @@ angular.module('app.frontend') this.showMenu = false; } - this.renderedContent = function() { - return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText(this.note.safeText())); - } - var statusTimeout; this.saveNote = function($event) { @@ -198,7 +202,6 @@ angular.module('app.frontend') } this.onContentFocus = function() { - this.showSampler = false; $rootScope.$broadcast("editorFocused"); } @@ -209,12 +212,7 @@ angular.module('app.frontend') this.toggleFullScreen = function() { this.fullscreen = !this.fullscreen; if(this.fullscreen) { - if(this.editorMode == 'edit') { - // refocus - this.focusEditor(0); - } - } else { - + this.focusEditor(0); } } @@ -222,19 +220,6 @@ angular.module('app.frontend') this.showMenu = false; } - this.toggleMarkdown = function() { - if(this.editorMode == 'preview') { - this.editorMode = 'edit'; - this.focusEditor(0); - } else { - this.editorMode = 'preview'; - } - } - - this.clickedMenu = function() { - this.showMenu = !this.showMenu; - } - this.deleteNote = function() { if(confirm("Are you sure you want to delete this note?")) { this.remove()(this.note); diff --git a/app/assets/javascripts/app/frontend/controllers/home.js b/app/assets/javascripts/app/frontend/controllers/home.js index 507f3c931..550827f6b 100644 --- a/app/assets/javascripts/app/frontend/controllers/home.js +++ b/app/assets/javascripts/app/frontend/controllers/home.js @@ -1,8 +1,31 @@ angular.module('app.frontend') -.controller('HomeCtrl', function ($scope, $rootScope, $timeout, modelManager, syncManager, authManager) { - $rootScope.bodyClass = "app-body-class"; +.controller('HomeCtrl', function ($scope, $stateParams, $rootScope, $timeout, modelManager, syncManager, authManager) { + + function autoSignInFromParams() { + if(!authManager.offline()) { + // check if current account + if(syncManager.serverURL == $stateParams.server && authManager.user.email == $stateParams.email) { + // already signed in, return + return; + } else { + // sign out + syncManager.destroyLocalData(function(){ + window.location.reload(); + }) + } + } else { + authManager.login($stateParams.server, $stateParams.email, $stateParams.pw, function(response){ + window.location.reload(); + }) + } + } + + if($stateParams.server && $stateParams.email) { + autoSignInFromParams(); + } syncManager.loadLocalItems(function(items) { + $scope.allTag.didLoad = true; $scope.$apply(); syncManager.sync(null); @@ -12,7 +35,9 @@ angular.module('app.frontend') }, 30000); }); - $scope.allTag = new Tag({all: true}); + var allTag = new Tag({all: true}); + allTag.needsLoad = true; + $scope.allTag = allTag; $scope.allTag.title = "All"; $scope.tags = modelManager.tags; $scope.allTag.notes = modelManager.notes; @@ -31,6 +56,7 @@ angular.module('app.frontend') modelManager.createRelationshipBetweenItems(note, tag); } + note.setDirty(true); syncManager.sync(); } diff --git a/app/assets/javascripts/app/frontend/controllers/notes.js b/app/assets/javascripts/app/frontend/controllers/notes.js index fc6bc70e0..579d3e1d0 100644 --- a/app/assets/javascripts/app/frontend/controllers/notes.js +++ b/app/assets/javascripts/app/frontend/controllers/notes.js @@ -18,7 +18,16 @@ angular.module('app.frontend') link:function(scope, elem, attrs, ctrl) { scope.$watch('ctrl.tag', function(tag, oldTag){ if(tag) { - ctrl.tagDidChange(tag, oldTag); + if(tag.needsLoad) { + scope.$watch('ctrl.tag.didLoad', function(didLoad){ + if(didLoad) { + tag.needsLoad = false; + ctrl.tagDidChange(tag, oldTag); + } + }); + } else { + ctrl.tagDidChange(tag, oldTag); + } } }); } @@ -26,6 +35,8 @@ angular.module('app.frontend') }) .controller('NotesCtrl', function (authManager, $timeout, $rootScope, modelManager) { + this.sortBy = localStorage.getItem("sortBy") || "created_at"; + $rootScope.$on("editorFocused", function(){ this.showMenu = false; }.bind(this)) @@ -34,8 +45,6 @@ angular.module('app.frontend') this.selectFirstNote(false); }.bind(this)) - var isFirstLoad = true; - this.notesToDisplay = 20; this.paginate = function() { this.notesToDisplay += 20 @@ -49,20 +58,16 @@ angular.module('app.frontend') } this.noteFilter.text = ""; + this.setNotes(tag.notes); + } - tag.notes.forEach(function(note){ + this.setNotes = function(notes) { + notes.forEach(function(note){ note.visible = true; }) - this.selectFirstNote(false); - if(isFirstLoad) { - $timeout(function(){ - this.createNewNote(); - isFirstLoad = false; - }.bind(this)) - } else if(tag.notes.length == 0) { - this.createNewNote(); - } + var createNew = notes.length == 0; + this.selectFirstNote(createNew); } this.selectedTagDelete = function() { @@ -71,7 +76,7 @@ angular.module('app.frontend') } this.selectFirstNote = function(createNew) { - var visibleNotes = this.tag.notes.filter(function(note){ + var visibleNotes = this.sortedNotes.filter(function(note){ return note.visible; }); @@ -114,4 +119,22 @@ angular.module('app.frontend') } }.bind(this), 100) } + + this.selectedMenuItem = function() { + this.showMenu = false; + } + + this.selectedSortByCreated = function() { + this.setSortBy("created_at"); + } + + this.selectedSortByUpdated = function() { + this.setSortBy("updated_at"); + } + + this.setSortBy = function(type) { + this.sortBy = type; + localStorage.setItem("sortBy", type); + } + }); diff --git a/app/assets/javascripts/app/frontend/models/api/item.js b/app/assets/javascripts/app/frontend/models/api/item.js index f6129f5a0..4cc38b8fc 100644 --- a/app/assets/javascripts/app/frontend/models/api/item.js +++ b/app/assets/javascripts/app/frontend/models/api/item.js @@ -37,6 +37,7 @@ class Item { updateFromJSON(json) { _.merge(this, json); + if(this.created_at) { this.created_at = new Date(this.created_at); this.updated_at = new Date(this.updated_at); @@ -112,6 +113,10 @@ class Item { this.setDirty(true); } + locallyClearAllReferences() { + + } + mergeMetadataFromItem(item) { _.merge(this, _.omit(item, ["content"])); } diff --git a/app/assets/javascripts/app/frontend/models/app/editor.js b/app/assets/javascripts/app/frontend/models/app/editor.js new file mode 100644 index 000000000..00a09cc0f --- /dev/null +++ b/app/assets/javascripts/app/frontend/models/app/editor.js @@ -0,0 +1,89 @@ +class Editor extends Item { + + constructor(json_obj) { + super(json_obj); + if(!this.notes) { + this.notes = []; + } + if(!this.data) { + this.data = {}; + } + } + + mapContentToLocalProperties(contentObject) { + super.mapContentToLocalProperties(contentObject) + this.url = contentObject.url; + this.name = contentObject.name; + this.data = contentObject.data || {}; + } + + structureParams() { + var params = { + url: this.url, + name: this.name, + data: this.data + }; + + _.merge(params, super.structureParams()); + return params; + } + + referenceParams() { + var references = _.map(this.notes, function(note){ + return {uuid: note.uuid, content_type: note.content_type}; + }) + + return references; + } + + addItemAsRelationship(item) { + if(item.content_type == "Note") { + if(!_.find(this.notes, item)) { + this.notes.push(item); + } + } + super.addItemAsRelationship(item); + } + + removeItemAsRelationship(item) { + if(item.content_type == "Note") { + _.pull(this.notes, item); + } + super.removeItemAsRelationship(item); + } + + removeAllRelationships() { + super.removeAllRelationships(); + this.notes = []; + } + + locallyClearAllReferences() { + super.locallyClearAllReferences(); + this.notes = []; + } + + allReferencedObjects() { + return this.notes; + } + + toJSON() { + return {uuid: this.uuid} + } + + get content_type() { + return "SN|Editor"; + } + + setData(key, value) { + var dataHasChanged = JSON.stringify(this.data[key]) !== JSON.stringify(value); + if(dataHasChanged) { + this.data[key] = value; + return true; + } + return false; + } + + dataForKey(key) { + return this.data[key] || {}; + } +} diff --git a/app/assets/javascripts/app/frontend/models/app/extension.js b/app/assets/javascripts/app/frontend/models/app/extension.js index 97a2576f0..fd45b3444 100644 --- a/app/assets/javascripts/app/frontend/models/app/extension.js +++ b/app/assets/javascripts/app/frontend/models/app/extension.js @@ -79,6 +79,7 @@ class Extension extends Item { super.mapContentToLocalProperties(contentObject) this.name = contentObject.name; this.url = contentObject.url; + this.supported_types = contentObject.supported_types; this.actions = contentObject.actions.map(function(action){ return new Action(action); }) @@ -99,7 +100,8 @@ class Extension extends Item { var params = { name: this.name, url: this.url, - actions: this.actions + actions: this.actions, + supported_types: this.supported_types }; _.merge(params, super.structureParams()); diff --git a/app/assets/javascripts/app/frontend/models/app/note.js b/app/assets/javascripts/app/frontend/models/app/note.js index b7b4a6c06..58cd84292 100644 --- a/app/assets/javascripts/app/frontend/models/app/note.js +++ b/app/assets/javascripts/app/frontend/models/app/note.js @@ -56,6 +56,14 @@ class Note extends Item { this.tags = []; } + locallyClearAllReferences() { + super.locallyClearAllReferences(); + this.tags.forEach(function(tag){ + _.pull(tag.notes, this); + }.bind(this)) + this.tags = []; + } + isBeingRemovedLocally() { this.tags.forEach(function(tag){ _.pull(tag.notes, this); diff --git a/app/assets/javascripts/app/frontend/models/app/tag.js b/app/assets/javascripts/app/frontend/models/app/tag.js index f8361ca5a..be36360c9 100644 --- a/app/assets/javascripts/app/frontend/models/app/tag.js +++ b/app/assets/javascripts/app/frontend/models/app/tag.js @@ -55,6 +55,15 @@ class Tag extends Item { this.notes = []; } + locallyClearAllReferences() { + super.locallyClearAllReferences(); + this.notes.forEach(function(note){ + _.pull(note.tags, this); + }.bind(this)) + + this.notes = []; + } + isBeingRemovedLocally() { this.notes.forEach(function(note){ _.pull(note.tags, this); diff --git a/app/assets/javascripts/app/frontend/routes.js b/app/assets/javascripts/app/frontend/routes.js index f1f9322fa..687d4cab9 100644 --- a/app/assets/javascripts/app/frontend/routes.js +++ b/app/assets/javascripts/app/frontend/routes.js @@ -7,7 +7,7 @@ angular.module('app.frontend') }) .state('home', { - url: '/', + url: '/?server&email&pw', parent: 'base', views: { 'content@' : { diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index fcc3b4d92..5a9d1bde2 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -73,13 +73,12 @@ angular.module('app.frontend') } Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, authParams), function(keys){ - var mk = keys.mk; var requestUrl = url + "/auth/sign_in"; var request = Restangular.oneUrl(requestUrl, requestUrl); var params = {password: keys.pw, email: email}; _.merge(request, params); request.post().then(function(response){ - this.handleAuthResponse(response, email, url, authParams, mk, keys.pw); + this.handleAuthResponse(response, email, url, authParams, keys.mk, keys.pw); callback(response); }.bind(this)) .catch(function(response){ @@ -91,7 +90,9 @@ angular.module('app.frontend') } this.handleAuthResponse = function(response, email, url, authParams, mk, pw) { - localStorage.setItem("server", url); + if(url) { + localStorage.setItem("server", url); + } localStorage.setItem("user", JSON.stringify(response.plain().user)); localStorage.setItem("auth_params", JSON.stringify(_.omit(authParams, ["pw_nonce"]))); localStorage.setItem("mk", mk); @@ -101,13 +102,12 @@ angular.module('app.frontend') this.register = function(url, email, password, callback) { Neeto.crypto.generateInitialEncryptionKeysForUser({password: password, email: email}, function(keys, authParams){ - var mk = keys.mk; var requestUrl = url + "/auth"; var request = Restangular.oneUrl(requestUrl, requestUrl); var params = _.merge({password: keys.pw, email: email}, authParams); _.merge(request, params); request.post().then(function(response){ - this.handleAuthResponse(response, email, url, authParams, mk, keys.pw); + this.handleAuthResponse(response, email, url, authParams, keys.mk, keys.pw); callback(response); }.bind(this)) .catch(function(response){ @@ -117,55 +117,26 @@ angular.module('app.frontend') }.bind(this)); } - // this.changePassword = function(current_password, new_password) { - // this.getAuthParamsForEmail(email, function(authParams){ - // if(!authParams) { - // callback(null); - // return; - // } - // Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: current_password, email: user.email}, authParams), function(currentKeys) { - // Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: new_password, email: user.email}, authParams), function(newKeys){ - // var data = {}; - // data.current_password = currentKeys.pw; - // data.password = newKeys.pw; - // data.password_confirmation = newKeys.pw; - // - // var user = this.user; - // - // this._performPasswordChange(currentKeys, newKeys, function(response){ - // if(response && !response.error) { - // // this.showNewPasswordForm = false; - // // reencrypt data with new mk - // this.reencryptAllItemsAndSave(user, newKeys.mk, currentKeys.mk, function(success){ - // if(success) { - // this.setMk(newKeys.mk); - // alert("Your password has been changed and your data re-encrypted."); - // } else { - // // rollback password - // this._performPasswordChange(newKeys, currentKeys, function(response){ - // alert("There was an error changing your password. Your password has been rolled back."); - // window.location.reload(); - // }) - // } - // }.bind(this)); - // } else { - // // this.showNewPasswordForm = false; - // alert("There was an error changing your password. Please try again."); - // } - // }.bind(this)) - // }.bind(this)); - // }.bind(this)); - // }.bind(this)); - // } + this.changePassword = function(email, new_password, callback) { + Neeto.crypto.generateInitialEncryptionKeysForUser({password: new_password, email: email}, function(keys, authParams){ + var requestUrl = localStorage.getItem("server") + "/auth/change_pw"; + var request = Restangular.oneUrl(requestUrl, requestUrl); + var params = _.merge({new_password: keys.pw}, authParams); + _.merge(request, params); - this._performPasswordChange = function(url, email, current_keys, new_keys, callback) { - var requestUrl = url + "/auth"; - var request = Restangular.oneUrl(requestUrl, requestUrl); - var params = {password: new_keys.pw, password_confirmation: new_keys.pw, current_password: current_keys.pw, email: email}; - _.merge(request, params); - request.patch().then(function(response){ - callback(response); - }) + request.post().then(function(response){ + this.handleAuthResponse(response, email, null, authParams, keys.mk, keys.pw); + callback(response.plain()); + }.bind(this)) + .catch(function(response){ + var error = response.data; + if(!error) { + error = {message: "Something went wrong while changing your password. Your password was not changed. Please try again."} + } + console.log("Change pw error", response); + callback({error: error}); + }) + }.bind(this)); } this.staticifyObject = function(object) { diff --git a/app/assets/javascripts/app/services/dbManager.js b/app/assets/javascripts/app/services/dbManager.js index d7e3edabb..90d529ec3 100644 --- a/app/assets/javascripts/app/services/dbManager.js +++ b/app/assets/javascripts/app/services/dbManager.js @@ -102,7 +102,6 @@ class DBManager { this.openDatabase((db) => { var request = db.transaction("items", "readwrite").objectStore("items").delete(item.uuid); request.onsuccess = function(event) { - console.log("Successfully deleted item", item.uuid); if(callback) { callback(true); } diff --git a/app/assets/javascripts/app/services/directives/functional/click-outside.js b/app/assets/javascripts/app/services/directives/functional/click-outside.js new file mode 100644 index 000000000..10e40e1da --- /dev/null +++ b/app/assets/javascripts/app/services/directives/functional/click-outside.js @@ -0,0 +1,27 @@ +angular + .module('app.frontend') + .directive('clickOutside', ['$document', function($document) { + return { + restrict: 'A', + replace: false, + link : function($scope, $element, attrs) { + + var didApplyClickOutside = false; + + $element.bind('click', function(e) { + didApplyClickOutside = false; + if (attrs.isOpen) { + e.stopPropagation(); + } + }); + + $document.bind('click', function() { + if(!didApplyClickOutside) { + $scope.$apply(attrs.clickOutside); + didApplyClickOutside = true; + } + }) + + } + } + }]); diff --git a/app/assets/javascripts/app/services/directives/functional/delay-hide.js b/app/assets/javascripts/app/services/directives/functional/delay-hide.js index 7467208f4..6e381ed3f 100644 --- a/app/assets/javascripts/app/services/directives/functional/delay-hide.js +++ b/app/assets/javascripts/app/services/directives/functional/delay-hide.js @@ -20,11 +20,15 @@ angular }); function showSpinner() { + if(scope.hidePromise) { + $timeout.cancel(scope.hidePromise); + scope.hidePromise = null; + } showElement(true); } function hideSpinner() { - $timeout(showElement.bind(this, false), getDelay()); + scope.hidePromise = $timeout(showElement.bind(this, false), getDelay()); } function showElement(show) { diff --git a/app/assets/javascripts/app/services/directives/views/accountMenu.js b/app/assets/javascripts/app/services/directives/views/accountMenu.js index 27c491280..7fd9ca4c1 100644 --- a/app/assets/javascripts/app/services/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/services/directives/views/accountMenu.js @@ -6,19 +6,15 @@ class AccountMenu { this.scope = {}; } - controller($scope, authManager, modelManager, syncManager, $timeout) { + controller($scope, authManager, modelManager, syncManager, dbManager, $timeout) { 'ngInject'; - $scope.formData = {url: syncManager.serverURL}; + $scope.formData = {mergeLocal: true, url: syncManager.serverURL}; $scope.user = authManager.user; $scope.server = syncManager.serverURL; $scope.syncStatus = syncManager.syncStatus; - $scope.changePasswordPressed = function() { - $scope.showNewPasswordForm = !$scope.showNewPasswordForm; - } - $scope.encryptionKey = function() { return syncManager.masterKey; } @@ -31,25 +27,61 @@ class AccountMenu { return `${$scope.server}/dashboard/?server=${$scope.server}&id=${$scope.user.email}&pw=${$scope.serverPassword()}`; } + $scope.newPasswordData = {}; + + $scope.showPasswordChangeForm = function() { + $scope.newPasswordData.showForm = true; + } + $scope.submitPasswordChange = function() { - $scope.passwordChangeData.status = "Generating New Keys..."; - $timeout(function(){ - if(data.password != data.password_confirmation) { - alert("Your new password does not match its confirmation."); - return; - } + if($scope.newPasswordData.newPassword != $scope.newPasswordData.newPasswordConfirmation) { + alert("Your new password does not match its confirmation."); + $scope.newPasswordData.status = null; + return; + } - authManager.changePassword($scope.passwordChangeData.current_password, $scope.passwordChangeData.new_password, function(response){ + var email = $scope.user.email; + if(!email) { + alert("We don't have your email stored. Please log out then log back in to fix this issue."); + $scope.newPasswordData.status = null; + return; + } + $scope.newPasswordData.status = "Generating New Keys..."; + $scope.newPasswordData.showForm = false; + + // perform a sync beforehand to pull in any last minutes changes before we change the encryption key (and thus cant decrypt new changes) + syncManager.sync(function(response){ + authManager.changePassword(email, $scope.newPasswordData.newPassword, function(response){ + if(response.error) { + alert("There was an error changing your password. Please try again."); + $scope.newPasswordData.status = null; + return; + } + + // re-encrypt all items + $scope.newPasswordData.status = "Re-encrypting all items with your new key..."; + + modelManager.setAllItemsDirty(); + syncManager.sync(function(response){ + if(response.error) { + alert("There was an error re-encrypting your items. Your password was changed, but not all your items were properly re-encrypted and synced. You should try syncing again. If all else fails, you should restore your notes from backup.") + return; + } + $scope.newPasswordData.status = "Successfully changed password and re-encrypted all items."; + $timeout(function(){ + alert("Your password has been changed, and your items successfully re-encrypted and synced. You must sign out of all other signed in applications and sign in again, or else you may corrupt your data.") + $scope.newPasswordData = {}; + }, 1000) + }); }) - }) + } $scope.loginSubmitPressed = function() { $scope.formData.status = "Generating Login Keys..."; - console.log("logging in with url", $scope.formData.url); $timeout(function(){ authManager.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password, function(response){ if(!response || response.error) { @@ -66,6 +98,11 @@ class AccountMenu { } $scope.submitRegistrationForm = function() { + var confirmation = prompt("Please confirm your password. Note that because your notes are encrypted using your password, Standard Notes does not have a password reset option. You cannot forget your password.") + if(confirmation !== $scope.formData.user_password) { + alert("The two passwords you entered do not match. Please try again."); + return; + } $scope.formData.status = "Generating Account Keys..."; $timeout(function(){ @@ -81,10 +118,32 @@ class AccountMenu { }) } + $scope.localNotesCount = function() { + return modelManager.filteredNotes.length; + } + + $scope.mergeLocalChanged = function() { + if(!$scope.formData.mergeLocal) { + if(!confirm("Unchecking this option means any of the notes you have written while you were signed out will be deleted. Are you sure you want to discard these notes?")) { + $scope.formData.mergeLocal = true; + } + } + } + $scope.onAuthSuccess = function() { - syncManager.markAllItemsDirtyAndSaveOffline(function(){ + var block = function() { window.location.reload(); - }) + } + + if($scope.formData.mergeLocal) { + syncManager.markAllItemsDirtyAndSaveOffline(function(){ + block(); + }) + } else { + dbManager.clearAllItems(function(){ + block(); + }) + } } $scope.destroyLocalData = function() { @@ -116,6 +175,8 @@ class AccountMenu { $scope.importData = null; if(!response) { alert("There was an error importing your data. Please try again."); + } else { + alert("Your data was successfully imported.") } }) }) @@ -149,12 +210,11 @@ class AccountMenu { } $scope.importJSONData = function(data, password, callback) { - console.log("Importing data", data); - var onDataReady = function() { var items = modelManager.mapResponseItemsToLocalModels(data.items); items.forEach(function(item){ item.setDirty(true); + item.deleted = false; item.markAllReferencesDirty(); }) diff --git a/app/assets/javascripts/app/services/directives/views/contextualExtensionsMenu.js b/app/assets/javascripts/app/services/directives/views/contextualExtensionsMenu.js index a0ed0e97d..f9a57b1b6 100644 --- a/app/assets/javascripts/app/services/directives/views/contextualExtensionsMenu.js +++ b/app/assets/javascripts/app/services/directives/views/contextualExtensionsMenu.js @@ -11,6 +11,8 @@ class ContextualExtensionsMenu { controller($scope, modelManager, extensionManager) { 'ngInject'; + $scope.renderData = {}; + $scope.extensions = _.map(extensionManager.extensionsInContextOfItem($scope.item), function(ext){ return _.cloneDeep(ext); }); @@ -27,12 +29,30 @@ class ContextualExtensionsMenu { } $scope.executeAction = function(action, extension) { + if(action.verb == "nested") { + action.showNestedActions = !action.showNestedActions; + return; + } action.running = true; extensionManager.executeAction(action, extension, $scope.item, function(response){ action.running = false; + $scope.handleActionResponse(action, response); }) } + $scope.handleActionResponse = function(action, response) { + switch (action.verb) { + case "render": { + var item = response.item; + if(item.content_type == "Note") { + $scope.renderData.title = item.title; + $scope.renderData.text = item.text; + $scope.renderData.showRenderModal = true; + } + } + } + } + $scope.accessTypeForExtension = function(extension) { return extensionManager.extensionUsesEncryptedData(extension) ? "encrypted" : "decrypted"; } diff --git a/app/assets/javascripts/app/services/directives/views/editorMenu.js b/app/assets/javascripts/app/services/directives/views/editorMenu.js new file mode 100644 index 000000000..63aab1e70 --- /dev/null +++ b/app/assets/javascripts/app/services/directives/views/editorMenu.js @@ -0,0 +1,73 @@ +class EditorMenu { + + constructor() { + this.restrict = "E"; + this.templateUrl = "frontend/directives/editor-menu.html"; + this.scope = { + callback: "&", + selectedEditor: "=" + }; + } + + controller($scope, modelManager, extensionManager, syncManager) { + 'ngInject'; + + $scope.formData = {}; + + let editorContentType = "SN|Editor"; + + let defaultEditor = { + default: true, + name: "Plain" + } + + $scope.sysEditors = [defaultEditor]; + $scope.editors = modelManager.itemsForContentType(editorContentType); + + $scope.editorForUrl = function(url) { + return $scope.editors.filter(function(editor){return editor.url == url})[0]; + } + + $scope.selectEditor = function(editor) { + $scope.callback()(editor); + } + + $scope.deleteEditor = function(editor) { + if(confirm("Are you sure you want to delete this editor?")) { + modelManager.setItemToBeDeleted(editor); + syncManager.sync(); + _.pull($scope.editors, editor); + } + } + + $scope.submitNewEditorRequest = function() { + var editor = createEditor($scope.formData.url); + modelManager.addItem(editor); + editor.setDirty(true); + syncManager.sync(); + $scope.editors.push(editor); + $scope.formData = {}; + } + + function createEditor(url) { + var name = getParameterByName("name", url); + return modelManager.createItem({ + content_type: editorContentType, + url: url, + name: name + }) + } + + function getParameterByName(name, url) { + name = name.replace(/[\[\]]/g, "\\$&"); + var regex = new RegExp("[?&]" + name + "(=([^]*)|&|#|$)"), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, " ")); + } + } + +} + +angular.module('app.frontend').directive('editorMenu', () => new EditorMenu); diff --git a/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js b/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js index 9945ac81c..8365a67ab 100644 --- a/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js +++ b/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js @@ -21,7 +21,14 @@ class GlobalExtensionsMenu { if($scope.newExtensionData.url) { extensionManager.addExtension($scope.newExtensionData.url, function(response){ if(!response) { - alert("Unable to register this extension. Make sure the link is valid and try again."); + if($scope.newExtensionData.url.indexOf("type=sf") != -1) { + alert("Unable to register this extension. You are attempting to register a Standard File extension in Standard Notes. You should instead open your Standard File Dashboard and register this extension there."); + } else if($scope.newExtensionData.url.indexOf("name=") != -1) { + // user is mistakenly trying to register editor extension, most likely + alert("Unable to register this extension. It looks like you may be trying to install an editor extension. To do that, click 'Editor' under the current note's title."); + } else { + alert("Unable to register this extension. Make sure the link is valid and try again."); + } } else { $scope.newExtensionData.url = ""; $scope.showNewExtensionForm = false; diff --git a/app/assets/javascripts/app/services/extensionManager.js b/app/assets/javascripts/app/services/extensionManager.js index d18cf868c..20b21de82 100644 --- a/app/assets/javascripts/app/services/extensionManager.js +++ b/app/assets/javascripts/app/services/extensionManager.js @@ -28,7 +28,7 @@ class ExtensionManager { extensionsInContextOfItem(item) { return this.extensions.filter(function(ext){ - return ext.actionsWithContextForItem(item).length > 0; + return _.includes(ext.supported_types, item.content_type) || ext.actionsWithContextForItem(item).length > 0; }) } @@ -152,9 +152,29 @@ class ExtensionManager { case "get": { this.Restangular.oneUrl(action.url, action.url).get().then(function(response){ action.error = false; - var items = response.items; - this.modelManager.mapResponseItemsToLocalModels(items); - customCallback(items); + var items = response.items || [response.item]; + EncryptionHelper.decryptMultipleItems(items, localStorage.getItem("mk")); + items = this.modelManager.mapResponseItemsToLocalModels(items); + for(var item of items) { + item.setDirty(true); + } + this.syncManager.sync(null); + customCallback({items: items}); + }.bind(this)) + .catch(function(response){ + action.error = true; + customCallback(null); + }) + + break; + } + + case "render": { + this.Restangular.oneUrl(action.url, action.url).get().then(function(response){ + action.error = false; + EncryptionHelper.decryptItem(response.item, localStorage.getItem("mk")); + var item = this.modelManager.createItem(response.item); + customCallback({item: item}); }.bind(this)) .catch(function(response){ action.error = true; @@ -281,7 +301,11 @@ class ExtensionManager { } outgoingParamsForItem(item, extension) { - var itemParams = new ItemParams(item, this.syncManager.masterKey); + var ek = this.syncManager.masterKey; + if(!this.extensionUsesEncryptedData(extension)) { + ek = null; + } + var itemParams = new ItemParams(item, ek); return itemParams.paramsForExtension(); } diff --git a/app/assets/javascripts/app/services/filters/startFrom.js b/app/assets/javascripts/app/services/filters/startFrom.js index 2ebd72e42..3cf824c4c 100644 --- a/app/assets/javascripts/app/services/filters/startFrom.js +++ b/app/assets/javascripts/app/services/filters/startFrom.js @@ -1,4 +1,3 @@ -// Start from filter angular.module('app.frontend').filter('startFrom', function() { return function(input, start) { return input.slice(start); diff --git a/app/assets/javascripts/app/services/filters/trusted.js b/app/assets/javascripts/app/services/filters/trusted.js new file mode 100644 index 000000000..7bb57b301 --- /dev/null +++ b/app/assets/javascripts/app/services/filters/trusted.js @@ -0,0 +1,5 @@ +angular.module('app.frontend').filter('trusted', ['$sce', function ($sce) { + return function(url) { + return $sce.trustAsResourceUrl(url); + }; +}]); diff --git a/app/assets/javascripts/app/services/helpers/crypto.js b/app/assets/javascripts/app/services/helpers/crypto.js index e93b9017c..ab09b41e8 100644 --- a/app/assets/javascripts/app/services/helpers/crypto.js +++ b/app/assets/javascripts/app/services/helpers/crypto.js @@ -60,11 +60,13 @@ class SNCrypto { } base64(text) { - return CryptoJS.enc.Utf8.parse(text).toString(CryptoJS.enc.Base64) + // return CryptoJS.enc.Utf8.parse(text).toString(CryptoJS.enc.Base64) + return window.btoa(text); } base64Decode(base64String) { - return CryptoJS.enc.Base64.parse(base64String).toString(CryptoJS.enc.Utf8) + // return CryptoJS.enc.Base64.parse(base64String).toString(CryptoJS.enc.Utf8) + return window.atob(base64String); } sha256(text) { diff --git a/app/assets/javascripts/app/services/helpers/encryptionHelper.js b/app/assets/javascripts/app/services/helpers/encryptionHelper.js index 7cf011ac8..e0e171af5 100644 --- a/app/assets/javascripts/app/services/helpers/encryptionHelper.js +++ b/app/assets/javascripts/app/services/helpers/encryptionHelper.js @@ -1,14 +1,8 @@ class EncryptionHelper { static encryptItem(item, key) { - var item_key = null; - if(item.enc_item_key) { - // we reuse the key, but this is optional - item_key = Neeto.crypto.decryptText(item.enc_item_key, key); - } else { - item_key = Neeto.crypto.generateRandomEncryptionKey(); - item.enc_item_key = Neeto.crypto.encryptText(item_key, key); - } + var item_key = Neeto.crypto.generateRandomEncryptionKey(); + item.enc_item_key = Neeto.crypto.encryptText(item_key, key); var ek = Neeto.crypto.firstHalfOfKey(item_key); var ak = Neeto.crypto.secondHalfOfKey(item_key); diff --git a/app/assets/javascripts/app/services/markdownRenderer.js b/app/assets/javascripts/app/services/markdownRenderer.js deleted file mode 100644 index b6fd7a749..000000000 --- a/app/assets/javascripts/app/services/markdownRenderer.js +++ /dev/null @@ -1,21 +0,0 @@ -angular.module('app.frontend') - .service('markdownRenderer', function ($sce) { - - marked.setOptions({ - breaks: true, - sanitize: true - }); - - this.renderedContentForText = function(text) { - if(!text || text.length == 0) { - return ""; - } - return marked(text); - } - - this.renderHtml = function(html_code) { - return $sce.trustAsHtml(html_code); - }; - - - }); diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index aa436b6e2..f5b5c04ac 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -8,6 +8,7 @@ class ModelManager { this.itemChangeObservers = []; this.items = []; this._extensions = []; + this.acceptableContentTypes = ["Note", "Tag", "Extension", "SN|Editor"]; } get allItems() { @@ -58,15 +59,17 @@ class ModelManager { } mapResponseItemsToLocalModelsOmittingFields(items, omitFields) { - var models = []; + var models = [], processedObjects = []; + + // first loop should add and process items for (var json_obj of items) { json_obj = _.omit(json_obj, omitFields || []) var item = this.findItem(json_obj["uuid"]); - if(json_obj["deleted"] == true) { - if(item) { - this.removeItemLocally(item) - } - continue; + if(json_obj["deleted"] == true || !_.includes(this.acceptableContentTypes, json_obj["content_type"])) { + if(item) { + this.removeItemLocally(item) + } + continue; } _.omit(json_obj, omitFields); @@ -79,11 +82,16 @@ class ModelManager { this.addItem(item); - if(json_obj.content) { - this.resolveReferencesForItem(item); - } - models.push(item); + processedObjects.push(json_obj); + } + + // second loop should process references + for (var index in processedObjects) { + var json_obj = processedObjects[index]; + if(json_obj.content) { + this.resolveReferencesForItem(models[index]); + } } this.notifySyncObserversOfModels(models); @@ -120,6 +128,8 @@ class ModelManager { item = new Tag(json_obj); } else if(json_obj.content_type == "Extension") { item = new Extension(json_obj); + } else if(json_obj.content_type == "SN|Editor") { + item = new Editor(json_obj); } else { item = new Item(json_obj); } @@ -144,9 +154,7 @@ class ModelManager { } } else if(item.content_type == "Note") { if(!_.find(this.notes, {uuid: item.uuid})) { - this.notes.splice(_.sortedLastIndexBy(this.notes, item, function(item){ - return -item.created_at; - }), 0, item); + this.notes.unshift(item); } } else if(item.content_type == "Extension") { if(!_.find(this._extensions, {uuid: item.uuid})) { @@ -167,11 +175,13 @@ class ModelManager { } resolveReferencesForItem(item) { + item.locallyClearAllReferences(); var contentObject = item.contentObject; if(!contentObject.references) { return; } + for(var reference of contentObject.references) { var referencedItem = this.findItem(reference.uuid); if(referencedItem) { @@ -225,6 +235,17 @@ class ModelManager { item.removeAllRelationships(); } + /* Used when changing encryption key */ + setAllItemsDirty() { + var relevantItems = this.allItems.filter(function(item){ + return _.includes(this.acceptableContentTypes, item.content_type); + }.bind(this)); + + for(var item of relevantItems) { + item.setDirty(true); + } + } + removeItemLocally(item, callback) { _.pull(this.items, item); diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index 66344b15a..e8ebb82df 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -98,10 +98,37 @@ class SyncManager { return this._cursorToken; } + get queuedCallbacks() { + if(!this._queuedCallbacks) { + this._queuedCallbacks = []; + } + return this._queuedCallbacks; + } + + clearQueuedCallbacks() { + this._queuedCallbacks = []; + } + + callQueuedCallbacksAndCurrent(currentCallback, response) { + var allCallbacks = this.queuedCallbacks; + if(currentCallback) { + allCallbacks.push(currentCallback); + } + if(allCallbacks.length) { + for(var eachCallback of allCallbacks) { + eachCallback(response); + } + this.clearQueuedCallbacks(); + } + } + sync(callback, options = {}) { if(this.syncStatus.syncOpInProgress) { this.repeatOnCompletion = true; + if(callback) { + this.queuedCallbacks.push(callback); + } console.log("Sync op in progress; returning."); return; } @@ -116,18 +143,17 @@ class SyncManager { return; } - var isContinuationSync = this.needsMoreSync; + var isContinuationSync = this.syncStatus.needsMoreSync; - this.repeatOnCompletion = false; this.syncStatus.syncOpInProgress = true; let submitLimit = 100; var subItems = allDirtyItems.slice(0, submitLimit); if(subItems.length < allDirtyItems.length) { // more items left to be synced, repeat - this.needsMoreSync = true; + this.syncStatus.needsMoreSync = true; } else { - this.needsMoreSync = false; + this.syncStatus.needsMoreSync = false; } if(!isContinuationSync) { @@ -153,12 +179,14 @@ class SyncManager { this.$rootScope.$broadcast("sync:updated_token", this.syncToken); var retrieved = this.handleItemsResponse(response.retrieved_items, null); + // merge only metadata for saved items + // we write saved items to disk now because it clears their dirty status then saves + // if we saved items before completion, we had have to save them as dirty and save them again on success as clean var omitFields = ["content", "auth_hash"]; var saved = this.handleItemsResponse(response.saved_items, omitFields); this.handleUnsavedItemsResponse(response.unsaved) - this.writeItemsToLocalStorage(saved, false, null); this.writeItemsToLocalStorage(retrieved, false, null); @@ -169,14 +197,17 @@ class SyncManager { this.syncToken = response.sync_token; this.cursorToken = response.cursor_token; - if(this.cursorToken || this.repeatOnCompletion || this.needsMoreSync) { + if(this.cursorToken || this.syncStatus.needsMoreSync) { + setTimeout(function () { + this.sync(callback, options); + }.bind(this), 10); // wait 10ms to allow UI to update + } else if(this.repeatOnCompletion) { + this.repeatOnCompletion = false; setTimeout(function () { this.sync(callback, options); }.bind(this), 10); // wait 10ms to allow UI to update } else { - if(callback) { - callback(response); - } + this.callQueuedCallbacksAndCurrent(callback, response); } }.bind(this)) @@ -190,9 +221,7 @@ class SyncManager { this.$rootScope.$broadcast("sync:error", error); - if(callback) { - callback({error: "Sync error"}); - } + this.callQueuedCallbacksAndCurrent(callback, {error: "Sync error"}); }.bind(this)) } @@ -211,7 +240,7 @@ class SyncManager { var item = this.modelManager.findItem(itemResponse.uuid); var error = mapping.error; if(error.tag == "uuid_conflict") { - // uuid conflicts can occur if a user attempts to import an old data archive with uuids form the old account into a new account + // uuid conflicts can occur if a user attempts to import an old data archive with uuids from the old account into a new account this.modelManager.alternateUUIDForItem(item, handleNext); } ++i; diff --git a/app/assets/stylesheets/app/_common.scss b/app/assets/stylesheets/app/_common.scss index ea9cb0895..87f8bbdb0 100644 --- a/app/assets/stylesheets/app/_common.scss +++ b/app/assets/stylesheets/app/_common.scss @@ -72,45 +72,3 @@ } } } - - -.section-menu { - padding-top: 0px; - width: 100%; - position: absolute; - padding-left: inherit; - padding-right: inherit; - left: 0; - right: 0; - bottom: 0px; - background-color: #f1f1f1; - color: $selected-text-color; - height: 28px; - cursor: default; - - ol, ul { - margin-top: 5px; - margin-bottom: 10px; - - &.dropdown-menu { - margin-top: 10px; - } - } - - ul { - li { - text-align: left; - - &.sep { - margin: 6px; - display: block; - } - - a { - font-size: 13px; - font-weight: bold; - padding: 0 0; - } - } - } -} diff --git a/app/assets/stylesheets/app/_directives.scss b/app/assets/stylesheets/app/_directives.scss deleted file mode 100644 index 39eb623dc..000000000 --- a/app/assets/stylesheets/app/_directives.scss +++ /dev/null @@ -1,70 +0,0 @@ -.dropdown-menu.contextual-menu { - .extension { - - &:not(:first-child) { - margin-top: 18px; - } - - .ext-header { - background-color: #ededed; - border-bottom: 1px solid #d3d3d3; - padding-top: 12px; - padding-left: 10px; - padding-bottom: 10px; - position: relative; - - > .name { - font-size: 14px; - } - - > .access { - font-size: 12px; - opacity: 0.5; - font-weight: normal; - margin-top: 2px; - } - - > .loading { - position: absolute; - height: 15px; - width: 15px; - right: 10px; - top: 20px; - } - } - - ul { - margin-top: 0px; - margin-bottom: 0px; - list-style:none; - padding-left:0; - - li { - cursor: pointer; - height: auto; - - &.action { - padding: 10px; - border-bottom: 1px solid rgba(black, 0.1); - background-color: rgba(white, 0.9); - - &:hover { - background-color: rgba(gray, 0.05); - } - - > .name { - font-weight: bold; - font-size: 14px; - } - - > .desc { - font-weight: normal; - opacity: 0.5; - margin-top: 1px; - font-size: 12px; - } - } - } - } - } -} diff --git a/app/assets/stylesheets/app/_editor.scss b/app/assets/stylesheets/app/_editor.scss index 6404713d5..cd2c35a9a 100644 --- a/app/assets/stylesheets/app/_editor.scss +++ b/app/assets/stylesheets/app/_editor.scss @@ -1,5 +1,10 @@ +$heading-height: 75px; .editor { - width: 60%; + flex: 1 50%; + min-width: 300px; + display: flex; + flex-direction: column; + overflow-y: hidden; &.fullscreen { width: 100%; @@ -12,253 +17,130 @@ .section-title-bar { border-bottom: none !important; - - &.fullscreen { - opacity: 0.0; - -webkit-transition: all 300ms ease-in-out; - -moz-transition: all 300ms ease-in-out; - -ms-transition: all 300ms ease-in-out; - -o-transition: all 300ms ease-in-out; - transition: all 300ms ease-in-out; - - &:hover { - opacity: 1.0; - } - - // z-index: -1; - } + height: $heading-height !important; } - $heading-height: 100px; + .section-menu { + flex: 1 0 28px; + max-height: 28px; + } +} - .editor-heading { +.editor-heading { - position: absolute; - width: 100%; - padding: 15px; + width: 100%; + padding: 15px; + padding-top: 0px; + background-color: white; + + min-height: $heading-height; + + padding-right: 10px; + + &.fullscreen { + position: relative; + } + + > .title { + font-size: 18px; + font-weight: bold; padding-top: 0px; - background-color: white; - - min-height: $heading-height; - width: 100%; - padding-right: 10px; - > .title { - font-size: 18px; + > .input { + float: left; + text-overflow:ellipsis; + width: 90%; font-weight: bold; - padding-top: 0px; - width: 100%; - - > .input { - float: left; - text-overflow:ellipsis; - width: 90%; - font-weight: bold; - border: none; - outline: none; - background-color: transparent; - - &:disabled { - color: black; - } - } - - } - .save-status { - width: 20% !important; - float: right; - position: absolute; - - right: 20px; - font-size: 12px; - text-transform: none; - font-weight: normal; - margin-top: 4px; - width: 120px; - text-align: right; - color: rgba(black, 0.23); - } - - .tags { - clear: left; - width: 100%; - height: 25px; - - .tags-input { - background-color: transparent; - width: 100%; - border: none; - } - } - } - - .fullscreen-ghost-bar { - - position: absolute; - width: 20%; - height: 200px; - z-index: 100; - } - - - .editor-content { - max-height: 100%; - - height: 100%; - clear: both; - min-width: 0; - padding-left: 10px; - padding-right: 10px; - overflow: auto; - text-align: center; // centers children div horizontally - z-index: 10; - padding-top: $heading-height; - - &.fullscreen { - padding-top: 0px; - } - - .sampler-container { - margin-top: 10px; - padding: 15px; - padding-top: 17px; - font-size: 17px; - // opacity: 0.5; - } - - .sampler { - // opacity: 0.5; - color: rgba(black, 0.3); - } - - .editable { - font-family: monospace; - max-height: 100%; - height: 100%; - width: 100%; border: none; outline: none; - padding: 15px; - padding-top: 17px; - font-size: 17px; - resize: none; - - &.fullscreen { - padding: 85px 10%; - max-width: 1200px; - display: inline-block;; - } - } - - .preview { - // font-family: monospace; - max-height: 100%; - height: 100%; - line-height: 23px; - overflow-y: scroll; - padding: 0px 15px; - text-align: left; - - &.fullscreen { - padding: 85px 10%; - max-width: 1200px; - display: inline-block;; - } - } - } -} - - -.markdown { - margin-left: 15px; - float: right; - text-align: right; - right: 0; -} - -.full-screen-button { - cursor: pointer; - position: absolute; - top: 25px; - right: 20px; - z-index: 100; -} - - -ol { - list-style-type: decimal; - list-style-position: inside; - -webkit-margin-before: 1em; - -webkit-margin-after: 1em; - -webkit-margin-start: 0px; - -webkit-margin-end: 0px; - -webkit-padding-start: 0px; -} - - - -.nav-tabs { - a { - color: black; - text-decoration: none; - } - a { background-color: transparent; + + &:disabled { + color: black; + } + } + + } + + .save-status { + width: 20% !important; + float: right; + position: absolute; + + right: 20px; + font-size: 12px; + text-transform: none; + font-weight: normal; + margin-top: 4px; + width: 120px; + text-align: right; + color: rgba(black, 0.23); + } + + .tags { + clear: left; + width: 100%; + height: 25px; + + .tags-input { + background-color: transparent; + width: 100%; + border: none; + } } } + +.editor-content { + flex: 1; + z-index: 10; + overflow-y: hidden; + height: 100%; + display: flex; + background-color: white; + + &.fullscreen { + padding-top: 0px; + } + + #editor-iframe { + flex: 1; + width: 100%; + } + + .editable { + font-family: monospace; + flex: 1; + overflow-y: scroll; + width: 100%; + + border: none; + outline: none; + padding: 15px; + padding-top: 11px; + font-size: 17px; + resize: none; + + &.fullscreen { + padding: 85px 10%; + max-width: 1200px; + display: inline-block; + } + } +} + .nav { padding-left: 0; margin-bottom: 0; list-style: none; + ul { + font-weight: bold; + } } -.nav-pills>li { - float: left; -} -.nav>li { - position: relative; - display: block; -} - -.nav-pills>li.active>a, .nav-pills>li.active>a:focus, .nav-pills>li.active>a:hover { - color: #fff; - background-color: #337ab7; -} -.nav-pills>li>a { - border-radius: 4px; -} .nav>li>a { position: relative; display: block; - padding: 10px 15px; -} - -.nav-tabs>li { - float: left; - margin-bottom: -1px; -} -.nav>li { - position: relative; - display: block; -} - -.nav-tabs>li.active>a, .nav-tabs>li.active>a:focus, .nav-tabs>li.active>a:hover { - color: black; - cursor: pointer; - background-color: #fff; - border: 1px solid #ddd; - border-bottom-color: transparent; -} -.nav-tabs>li>a { - margin-right: 2px; - line-height: 1.42857143; - border: 1px solid transparent; - // border-radius: 4px 4px 0 0; - cursor: pointer; -} -.nav>li>a { - position: relative; - display: block; - padding: 10px 15px; + font-weight: bold; + user-select: none; } diff --git a/app/assets/stylesheets/app/_extensions.scss b/app/assets/stylesheets/app/_extensions.scss new file mode 100644 index 000000000..c5cf82fd2 --- /dev/null +++ b/app/assets/stylesheets/app/_extensions.scss @@ -0,0 +1,112 @@ +.extension-render-modal { + position: fixed; + margin-left: auto; + margin-right: auto; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 10000; + width: 100vw; + height: 100vh; + background-color: rgba(gray, 0.3); + + .content { + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-color: white; + width: 700px; + height: 500px; + margin: auto; + padding: 25px; + position: absolute; + top: 0; left: 0; bottom: 0; right: 0; + overflow-y: scroll; + } +} + +.menu-section-footer { + background-color: #ededed; + border-top: 1px solid #d3d3d3; + position: relative; + padding: 10px; +} + +.menu-section-header { + background-color: #ededed; + border-bottom: 1px solid #d3d3d3; + position: relative; + padding-top: 12px; + padding-left: 10px; + padding-bottom: 10px; + + + > .title { + font-size: 14px; + font-weight: bold; + } + + > .subtitle { + font-size: 12px; + opacity: 0.5; + font-weight: normal; + margin-top: 2px; + } + + > .loading { + position: absolute; + height: 15px; + width: 15px; + right: 10px; + top: 20px; + } +} + +.dropdown-menu.editor-menu { + overflow-y: scroll; + max-height: 85vh; + + &:not(:first-child) { + margin-top: 18px; + } + + ul { + margin-top: 0px; + margin-bottom: 0px; + list-style:none; + padding-left:0; + + li { + cursor: pointer; + height: auto; + + &.menu-item { + padding: 10px; + border-bottom: 1px solid rgba(black, 0.1); + background-color: rgba(white, 0.9); + + &:hover { + background-color: rgba(gray, 0.05); + } + + &.nonactive { + cursor: default; + &:hover { + background-color: rgba(white, 0.9) !important; + } + } + + .menu-item-title { + font-weight: bold; + font-size: 14px; + } + + .menu-item-subtitle { + font-weight: normal; + opacity: 0.5; + margin-top: 1px; + font-size: 12px; + } + } + } + } +} diff --git a/app/assets/stylesheets/app/_fonts.scss b/app/assets/stylesheets/app/_fonts.scss deleted file mode 100644 index 2d5708715..000000000 --- a/app/assets/stylesheets/app/_fonts.scss +++ /dev/null @@ -1,92 +0,0 @@ -@font-face { - font-family: ProximaNova; - src: font-url('ProximaNova/ProximaNova-Regular.eot'); - src: local('☺'), - font-url('ProximaNova/ProximaNova-Regular.woff') format('woff'), - font-url('ProximaNova/ProximaNova-Regular.ttf') format('truetype'), - font-url('ProximaNova/ProximaNova-Regular.svg') format('svg'); -} - -@font-face { - font-family: ProximaNova; - font-style: italic; - src: font-url('ProximaNova/ProximaNova-Regular-Italic.eot'); - src: local('☺'), - font-url('ProximaNova/ProximaNova-Regular-Italic.woff') format('woff'), - font-url('ProximaNova/ProximaNova-Regular-Italic.ttf') format('truetype'), - font-url('ProximaNova/ProximaNova-Regular-Italic.svg') format('svg'); -} - -@font-face { - font-family: ProximaNova; - font-weight: 600; - src: font-url('ProximaNova/ProximaNova-Semibold.eot'); - src: local('☺'), - font-url('ProximaNova/ProximaNova-Semibold.woff') format('woff'), - font-url('ProximaNova/ProximaNova-Semibold.ttf') format('truetype'), - font-url('ProximaNova/ProximaNova-Semibold.svg') format('svg'); -} - -@font-face { - font-family: ProximaNova; - font-weight: 600; - font-style: italic; - src: font-url('ProximaNova/ProximaNova-Semibold-Italic.eot'); - src: local('☺'), - font-url('ProximaNova/ProximaNova-Semibold-Italic.woff') format('woff'), - font-url('ProximaNova/ProximaNova-Semibold-Italic.ttf') format('truetype'), - font-url('ProximaNova/ProximaNova-Semibold-Italic.svg') format('svg'); -} - -@font-face { - font-family: ProximaNova; - font-weight: bold; - src: font-url('ProximaNova/ProximaNova-Bold.eot'); - src: local('☺'), - font-url('ProximaNova/ProximaNova-Bold.woff') format('woff'), - font-url('ProximaNova/ProximaNova-Bold.ttf') format('truetype'), - font-url('ProximaNova/ProximaNova-Bold.svg') format('svg'); -} - -@font-face { - font-family: ProximaNova; - font-weight: bold; - font-style: italic; - src: font-url('ProximaNova/ProximaNova-Bold-Italic.eot'); - src: local('☺'), - font-url('ProximaNova/ProximaNova-Bold-Italic.woff') format('woff'), - font-url('ProximaNova/ProximaNova-Bold-Italic.ttf') format('truetype'), - font-url('ProximaNova/ProximaNova-Bold-Italic.svg') format('svg'); -} - - -@font-face { - font-family: ProximaNova; - font-weight: 100; - src: font-url('fonts/ProximaNova-Thin.eot'); - src: local('☺'), - font-url('ProximaNova/ProximaNova-Thin.woff') format('woff'), - font-url('ProximaNova/ProximaNova-Thin.ttf') format('truetype'), - font-url('ProximaNova/ProximaNova-Thin.svg') format('svg'); -} - -@font-face { - font-family: ProximaNova; - font-weight: 100; - font-style: italic; - src: font-url('fonts/ProximaNova-Thin-Italic.eot'); - src: local('☺'), - font-url('ProximaNova/ProximaNova-Thin-Italic.woff') format('woff'), - font-url('ProximaNova/ProximaNova-Thin-Italic.ttf') format('truetype'), - font-url('ProximaNova/ProximaNova-Thin-Italic.svg') format('svg'); -} - -@font-face { - font-family: ProximaNova; - font-weight: 900; - src: font-url('ProximaNova/ProximaNova-Extrabold.eot'); - src: local('☺'), - font-url('ProximaNova/ProximaNova-Extrabold.woff') format('woff'), - font-url('ProximaNova/ProximaNova-Extrabold.ttf') format('truetype'), - font-url('ProximaNova/ProximaNova-Extrabold.svg') format('svg'); -} diff --git a/app/assets/stylesheets/app/_header.scss b/app/assets/stylesheets/app/_header.scss index 74efb241a..fdeb83a11 100644 --- a/app/assets/stylesheets/app/_header.scss +++ b/app/assets/stylesheets/app/_header.scss @@ -1,141 +1,3 @@ -.pull-left { - float: left !important; -} - -.pull-right { - float: right !important; -} - -.mt-1 { - margin-top: 1px !important; -} - -.mt-2 { - margin-top: 2px !important; -} - -.mt-5 { - margin-top: 5px !important; -} - -.mt-10 { - margin-top: 10px !important; -} - -.mt-15 { - margin-top: 15px !important; -} - -.mt-25 { - margin-top: 25px !important; -} - -.mb-0 { - margin-bottom: 0px !important; -} - -.mb-5 { - margin-bottom: 5px !important; -} - -.mb-10 { - margin-bottom: 10px !important; -} - -.mr-5 { - margin-right: 5px; -} - -.faded { - opacity: 0.5; -} - -.center-align { - text-align: center !important; -} - -.center { - margin-left: auto !important; - margin-right: auto !important; -} - -.block { - display: block !important; -} - -.wrap { - word-wrap: break-word; -} - -.one-line-overflow { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} - -.small-v-space { - height: 6px; - display: block; -} - -.medium-v-space { - height: 12px; - display: block; -} - -.large-v-space { - height: 24px; - display: block; -} - -.small-padding { - padding: 5px !important; -} - -.medium-padding { - padding: 10px !important; -} - -.pb-4 { - padding-bottom: 4px !important; -} - -.pb-6 { - padding-bottom: 6px !important; -} - -.pb-10 { - padding-bottom: 10px !important; -} - -.large-padding { - padding: 22px !important; -} - -.red { - color: red !important; -} - -.blue { - color: $blue-color; -} - -.bold { - font-weight: bold !important; -} - -.normal { - font-weight: normal !important; -} - -.small { - font-size: 10px !important; -} - -.inline { - display: inline-block; -} - .fake-link { font-weight: bold; cursor: pointer; @@ -146,6 +8,11 @@ } } +h2 { + margin-bottom: 0px; + margin-top: 0px; +} + .footer-bar { position: relative; width: 100%; @@ -190,10 +57,6 @@ display: block; } - h2 { - margin-bottom: 0px; - margin-top: 0px; - } h3 { font-size: 14px !important; diff --git a/app/assets/stylesheets/app/_main.scss b/app/assets/stylesheets/app/_main.scss index b71e01bac..621d97678 100644 --- a/app/assets/stylesheets/app/_main.scss +++ b/app/assets/stylesheets/app/_main.scss @@ -29,8 +29,8 @@ $blue-color: #086dd6; } } -[ng\:cloak], [ng-cloak], .ng-cloak { - display: none !important; +.blue { + color: $blue-color; } html, @@ -45,6 +45,7 @@ body { height: 100%; font-size: 20px; margin: 0; + background-color: $bg-color; } .dark-button { @@ -72,19 +73,6 @@ a { } } -pre { - padding: 16px; - overflow: auto; - line-height: 1.45; - background-color: #f7f7f7; - border-radius: 3px; -} - -code { - word-wrap: break-word; - line-height: 1.45; -} - p { overflow: auto; } @@ -93,35 +81,21 @@ p { min-height: 100vh; height: 100vh; overflow: auto; -} - -.app-body-class { - height: 100%; background-color: $bg-color; - min-width: 100px; - min-width: 900px; } $header-height: 25px; -.app-container { - display: table; - background-color: $bg-color; - width: 100%; - height: calc(100% - #{$header-height}); - padding-top: 0px; - font-size: 0; - margin-top: 0; -} - $section-header-height: 70px; .app { - height: 100%; + // height: 100%; + height: calc(100% - #{$header-height}); width: 100%; - display: table-row; + display: flex; vertical-align: top; overflow: hidden; + position: relative; .light-button { background-color: $bg-color; @@ -140,15 +114,8 @@ $section-header-height: 70px; .section { padding-bottom: 0px; - - display: block; height: 100%; max-height: calc(100vh - #{$header-height}); - float: left; - overflow-y: auto; - overflow-x: hidden; - min-width: 0; - font-size: 17px; .scrollable { @@ -161,7 +128,6 @@ $section-header-height: 70px; max-height: 100%; background-color: white; position: relative; - box-shadow: 0px 0px 2px rgba(gray, 0.3); } .section-title-bar { @@ -198,3 +164,41 @@ $section-header-height: 70px; } } } + +.section-menu { + width: 100%; + padding-top: 0px; + padding-left: 21px; + padding-right: 21px; + + background-color: #f1f1f1; + color: $selected-text-color; + height: 28px; + cursor: default; + + ol, ul { + margin-top: 5px; + margin-bottom: 10px; + + &.dropdown-menu { + margin-top: 10px; + } + } + + ul { + li { + text-align: left; + + &.sep { + margin: 6px; + display: block; + } + + a { + font-size: 13px; + font-weight: bold; + padding: 0 0; + } + } + } +} diff --git a/app/assets/stylesheets/app/_mostrap.scss b/app/assets/stylesheets/app/_mostrap.scss index 7b540f5b0..2776256a1 100644 --- a/app/assets/stylesheets/app/_mostrap.scss +++ b/app/assets/stylesheets/app/_mostrap.scss @@ -7,15 +7,11 @@ $screen-xs: 480px !default; //** Deprecated `$screen-xs-min` as of v3.2.0 $screen-xs-min: $screen-xs !default; -//** Deprecated `$screen-phone` as of v3.0.1 -$screen-phone: $screen-xs-min !default; // Small screen / tablet //** Deprecated `$screen-sm` as of v3.0.1 $screen-sm: 768px !default; $screen-sm-min: $screen-sm !default; -//** Deprecated `$screen-tablet` as of v3.0.1 -$screen-tablet: $screen-sm-min !default; // Medium screen / desktop //** Deprecated `$screen-md` as of v3.0.1 @@ -67,111 +63,6 @@ $screen-md-max: ($screen-lg-min - 1) !default; *:focus {outline:0;} -.navbar { - min-height: 0px !important; - background-color: white; - height: 80px; - margin-bottom: 0px; - padding-top: 10px; - border-radius: 0px; -} -@media (min-width: 768px) { - .navbar { - border-radius: 4px; - } -} - -.navbar { - position: relative; - min-height: 50px; - margin-bottom: 20px; - border: 1px solid transparent; -} - -@media (min-width: 768px) { - .navbar-header { - float: left; - } -} - -.container > .navbar-header, .container > .navbar-collapse, .container-fluid > .navbar-header, .container-fluid > .navbar-collapse { - margin-right: 0; - margin-left: 0; -} - -@media (min-width: 768px) { - .navbar > .container .navbar-brand, .navbar > .container-fluid .navbar-brand { - // margin-left: -15px; - } -} - -.navbar-brand { - float: left; - padding: 15px 15px; - font-size: 18px; - line-height: 20px; - height: 50px; -} - -@media (min-width: 768px) { - .container > .navbar-header, .container > .navbar-collapse, .container-fluid > .navbar-header, .container-fluid > .navbar-collapse { - // margin-right: 0; - // margin-left: 0; - } -} - -.container > .navbar-header, .container > .navbar-collapse, .container-fluid > .navbar-header, .container-fluid > .navbar-collapse { - // margin-right: -15px; - // margin-left: -15px; -} - -@media (min-width: 768px) { - .navbar-collapse.collapse { - display: block !important; - height: auto !important; - padding-bottom: 0; - overflow: visible !important; - } -} -@media (min-width: 768px) { - .navbar-collapse { - width: auto; - border-top: 0; - box-shadow: none; - } -} - -.navbar-collapse { - overflow-x: visible; - padding-right: 15px; - padding-left: 15px; - border-top: 1px solid transparent; - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); - -webkit-overflow-scrolling: touch; -} - -.collapse { - display: none; -} - -@media (min-width: 768px) { - .navbar-right { - float: right !important; - // margin-right: -15px; - } -} -@media (min-width: 768px) { - .navbar-text { - float: left; - margin-left: 15px; - margin-right: 15px; - } -} -.navbar-text { - margin-top: 15px; - margin-bottom: 15px; -} - .dropup, .dropdown { position: relative; } @@ -232,10 +123,6 @@ button:focus {outline:0;} right: 0; } -.open > .dropdown-menu { - display: block; -} - .btn { display: inline-block; margin-bottom: 0; @@ -262,23 +149,6 @@ button:focus {outline:0;} width: 100%; } -// ul, menu, dir { -// display: block; -// list-style-type: disc; -// -webkit-margin-before: 1em; -// -webkit-margin-after: 1em; -// -webkit-margin-start: 0px; -// -webkit-margin-end: 0px; -// -webkit-padding-start: 40px; -// } - -.dropdown-menu .divider { - height: 1px; - margin: 9px 0; - overflow: hidden; - background-color: #e5e5e5; -} - .panel { position: absolute; right: 0px; @@ -290,59 +160,35 @@ button:focus {outline:0;} background-color: white; } -.panel-top { - bottom: 0px; -} - -.panel-left { - left: -50px; -} - .panel-right { left: 0px; } -.panel-centered { - position: relative; - width: 400px; - margin: 0 auto; - padding: 10px; - text-align: center; -} - .panel-body { padding: 15px; } .form-control { - display: block; - width: 100%; - height: 34px; - padding: 6px 12px; - font-size: 14px; - line-height: 1.42857; - color: #555555; - background-color: #fff; - background-image: none; - border: 1px solid #ccc; - border-radius: 4px; - box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); - -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; - transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857; + color: #555555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); + -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; } input, button, select, textarea { - font-family: inherit; - font-size: inherit; - line-height: inherit; -} - -.has-feedback { - position: relative; -} - -.form-tag { - margin-bottom: 15px; + font-family: inherit; + font-size: inherit; + line-height: inherit; } .checkbox { @@ -352,63 +198,3 @@ input, button, select, textarea { margin-right: auto; margin-bottom: 10px; } - -.btn-link { - background-color: transparent; - -webkit-box-shadow: none; - box-shadow: none; - text-decoration: none; -} - -.btn-link:hover, .btn-link:focus { - color: #23527c; - text-decoration: underline; - background-color: transparent; -} - -.animated { - -webkit-animation-duration: 1s; - animation-duration: 1s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; -} - -.animated-fast { - -webkit-animation-duration: 0.5s; - animation-duration: 0.5s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; -} - -.fadeInDown { - -webkit-animation-name: fadeInDown; - animation-name: fadeInDown; -} - -.fadeIn { - -webkit-animation-name: fadeIn; - animation-name: fadeIn; -} - -@keyframes fadeIn { - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } -} - -@keyframes fadeInDown { - 0% { - opacity: 0; - -webkit-transform: translate3d(0,-100%,0); - transform: translate3d(0,-100%,0); - } - 100% { - opacity: 1; - -webkit-transform: none; - transform: none; - } -} diff --git a/app/assets/stylesheets/app/_notes.scss b/app/assets/stylesheets/app/_notes.scss index 923e7cf57..4294ff6de 100644 --- a/app/assets/stylesheets/app/_notes.scss +++ b/app/assets/stylesheets/app/_notes.scss @@ -1,8 +1,11 @@ .notes { - width: 25%; border-left: 1px solid #dddddd; border-right: 1px solid #dddddd; + flex: 1 20%; + max-width: 350px; + min-width: 170px; + $notes-title-bar-height: 130px; .notes-title-bar { diff --git a/app/assets/stylesheets/app/_standard.scss b/app/assets/stylesheets/app/_standard.scss new file mode 100644 index 000000000..60cc3601a --- /dev/null +++ b/app/assets/stylesheets/app/_standard.scss @@ -0,0 +1,270 @@ +.pull-left { + float: left !important; +} + +.pull-right { + float: right !important; +} + +.mt-1 { + margin-top: 1px !important; +} + +.mt-2 { + margin-top: 2px !important; +} + +.mt-5 { + margin-top: 5px !important; +} + +.mt-10 { + margin-top: 10px !important; +} + +.mt-15 { + margin-top: 15px !important; +} + +.mt-20 { + margin-top: 20px !important; +} + +.mt-25 { + margin-top: 25px !important; +} + +.mt-50 { + margin-top: 50px !important; +} + +.mt-100 { + margin-top: 100px !important; +} + +.mb-0 { + margin-bottom: 0px !important; +} + +.mb-5 { + margin-bottom: 5px !important; +} + +.mb-10 { + margin-bottom: 10px !important; +} + +.mr-5 { + margin-right: 5px; +} + +.mr-10 { + margin-right: 10px; +} + +.mr-20 { + margin-right: 20px; +} + +.pb-0 { + padding-bottom: 0px !important; +} + +.faded { + opacity: 0.5; +} + +.center-align { + text-align: center !important; +} + +.center { + margin-left: auto !important; + margin-right: auto !important; +} + +.block { + display: block !important; +} + +.wrap { + word-wrap: break-word; + word-break: break-all; +} + +.one-line-overflow { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.small-v-space { + height: 6px; + display: block; +} + +.medium-v-space { + height: 12px; + display: block; +} + +.large-v-space { + height: 24px; + display: block; +} + +.small-padding { + padding: 5px !important; +} + +.medium-padding { + padding: 10px !important; +} + +.pb-4 { + padding-bottom: 4px !important; +} + +.pb-6 { + padding-bottom: 6px !important; +} + +.pb-10 { + padding-bottom: 10px !important; +} + +.large-padding { + padding: 22px !important; +} + +.red { + color: red !important; +} + +.bold { + font-weight: bold !important; +} + +.normal { + font-weight: normal !important; +} + +.small { + font-size: 10px !important; +} + +.medium { + font-size: 14px !important; +} + +.inline { + display: inline-block !important; + + &.top { + vertical-align: top; + } + + &.middle { + vertical-align: middle; + } +} + +button { + cursor: pointer; +} + +input.form-control { + margin-bottom: 10px; + border-radius: 0px; + min-height: 39px; + font-size: 14px; + padding-left: 6px; +} + +button { + border: none; + + @mixin wide-button() { + font-weight: bold; + text-align: center; + padding: 10px; + font-size: 16px; + // min-width: 200px; + + &:hover { + text-decoration: underline; + } + } + + &.black { + @include wide-button(); + background-color: black; + color: white; + } + + &.white { + @include wide-button(); + background-color: white; + color: black; + border: 1px solid rgba(gray, 0.2); + } +} + + +.gray-bg { + background-color: #f6f6f6; + border: 1px solid #f2f2f2; +} + +.white-bg { + background-color: white; + border: 1px solid rgba(gray, 0.2); +} + +.col-container { + // white-space: nowrap; +} + +@mixin col() { + display: inline-block; + vertical-align: top; + white-space: normal; +} + +.col-10 { + width: 10%; + @include col(); +} + +.col-15 { + width: 15%; + @include col(); +} + +.col-20 { + width: 20%; + @include col(); +} + +.col-45 { + width: 45%; + @include col(); +} + +.col-50 { + width: 50%; + @include col(); +} + +.col-80 { + width: 80%; + @include col(); +} + +.relative { + position: relative !important; +} + +.absolute { + position: absolute !important; +} diff --git a/app/assets/stylesheets/app/_tags.scss b/app/assets/stylesheets/app/_tags.scss index 7d3ce9e34..ba45efd89 100644 --- a/app/assets/stylesheets/app/_tags.scss +++ b/app/assets/stylesheets/app/_tags.scss @@ -1,5 +1,8 @@ .tags { - width: 15%; + // width: 15%; + flex: 1 10%; + max-width: 180px; + min-width: 100px; $tags-title-bar-height: 55px; diff --git a/app/assets/stylesheets/frontend.css.scss b/app/assets/stylesheets/frontend.css.scss index bfe290297..904cb614a 100644 --- a/app/assets/stylesheets/frontend.css.scss +++ b/app/assets/stylesheets/frontend.css.scss @@ -1,5 +1,6 @@ $dark-gray: #2e2e2e; +@import "app/standard"; @import "app/mostrap"; @import "app/main"; @import "app/common"; @@ -7,41 +8,4 @@ $dark-gray: #2e2e2e; @import "app/tags"; @import "app/notes"; @import "app/editor"; -@import "app/directives"; - -@font-face { - font-family: 'icomoon'; - src: url('icomoon/icomoon.eot'); - src: url('icomoon/icomoon.eot') format('embedded-opentype'), - url('icomoon/icomoon.ttf') format('truetype'), - url('icomoon/icomoon.woff') format('woff'), - url('icomoon/icomoon.svg') format('svg'); - font-weight: normal; - font-style: normal; -} - -[class^="icon-"], [class*=" icon-"] { - /* use !important to prevent issues with browser extensions that change fonts */ - font-family: 'icomoon' !important; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - // line-height: 1; - - /* Better Font Rendering =========== */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - - line-height: 10px; -} - -.inline-icon { - display: inline-block; - margin-left: 2px; -} - -.icon-markdown:before { - content: "\e901"; -} +@import "app/extensions"; diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/frontend/directives/account-menu.html.haml index 62ffe0db9..3c93889a5 100644 --- a/app/assets/templates/frontend/directives/account-menu.html.haml +++ b/app/assets/templates/frontend/directives/account-menu.html.haml @@ -8,6 +8,10 @@ %input.form-control{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'formData.url'} %input.form-control{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'formData.email'} %input.form-control{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.user_password'} + .checkbox{"ng-if" => "localNotesCount() > 0"} + %label + %input{"type" => "checkbox", "ng-model" => "formData.mergeLocal", "ng-bind" => "true", "ng-change" => "mergeLocalChanged()"} + Merge local notes ({{localNotesCount()}} notes) %div{"ng-if" => "!formData.status"} %button.btn.dark-button.half-button{"ng-click" => "loginSubmitPressed()", "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"} @@ -28,6 +32,13 @@ %div{"ng-if" => "user"} %h2 {{user.email}} %p {{server}} + %div.bold.mt-10.blue{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"} + .spinner.inline.mr-5.blue + {{"Syncing" + (syncStatus.total > 0 ? ":" : "")}} + %span{"ng-if" => "syncStatus.total > 0"} {{syncStatus.current}}/{{syncStatus.total}} + %p.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}} + + %a.block.mt-15{"href" => "{{dashboardURL()}}", "target" => "_blank"} → Standard File Dashboard %a.block.mt-5{"ng-click" => "showCredentials = !showCredentials"} Show Credentials %section.gray-bg.mt-10.medium-padding{"ng-if" => "showCredentials"} %label.block @@ -36,13 +47,23 @@ %label.block.mt-5.mb-0 Server password: .wrap.normal.mt-1 {{serverPassword() ? serverPassword() : 'Not available. Sign out then sign back in to compute.'}} - %a.block.mt-5{"href" => "{{dashboardURL()}}", "target" => "_blank"} Standard File Dashboard - %div.bold.mt-10.blue{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress", "delay" => "1000"} - .spinner.inline.mr-5.blue - Syncing - %span{"ng-if" => "syncStatus.total > 0"}: {{syncStatus.current}}/{{syncStatus.total}} - %p.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}} + %a.block.mt-5{"ng-click" => "newPasswordData.changePassword = !newPasswordData.changePassword"} Change Password + %section.gray-bg.mt-10.medium-padding{"ng-if" => "newPasswordData.changePassword"} + %p.bold Change Password (Beta) + %p.mt-10 Since your encryption key is based on your password, changing your password requires all your notes and tags to be re-encrypted using your new key. + %p.mt-5 If you have thousands of items, this can take several minutes — you must keep the application window open during this process. + %p.mt-5 After changing your password, you must log out of all other applications currently signed in to your account. + %p.bold.mt-5 It is highly recommended you download a backup of your data before proceeding. + %div.mt-10{"ng-if" => "!newPasswordData.status"} + %a.red.mr-5{"ng-if" => "!newPasswordData.showForm", "ng-click" => "showPasswordChangeForm()"} Continue + %a{"ng-click" => "newPasswordData.changePassword = false; newPasswordData.showForm = false"} Cancel + %div.mt-10{"ng-if" => "newPasswordData.showForm"} + %form + %input.form-control{"type" => "text", "ng-model" => "newPasswordData.newPassword", "placeholder" => "Enter new password"} + %input.form-control{"type" => "text", "ng-model" => "newPasswordData.newPasswordConfirmation", "placeholder" => "Confirm new password"} + %button.btn.dark-button.btn-block{"ng-click" => "submitPasswordChange()"} Submit + %p.italic.mt-10{"ng-if" => "newPasswordData.status"} {{newPasswordData.status}} .medium-v-space @@ -77,4 +98,4 @@ .spinner.mt-10{"ng-if" => "importData.loading"} - %a.block.mt-25.red{"ng-click" => "destroyLocalData()"} Destroy all local data + %a.block.mt-25.red{"ng-click" => "destroyLocalData()"} {{ user ? "Sign out and clear local data" : "Clear all local data" }} diff --git a/app/assets/templates/frontend/directives/contextual-menu.html.haml b/app/assets/templates/frontend/directives/contextual-menu.html.haml index c30f5da5e..6fbfaa9aa 100644 --- a/app/assets/templates/frontend/directives/contextual-menu.html.haml +++ b/app/assets/templates/frontend/directives/contextual-menu.html.haml @@ -1,14 +1,28 @@ -%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark.contextual-menu +%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark.editor-menu .extension{"ng-repeat" => "extension in extensions"} - .ext-header - .name {{extension.name}} - .access + .menu-section-header + .title {{extension.name}} + .subtitle Can access your data %strong {{accessTypeForExtension(extension)}} .spinner.loading{"ng-if" => "extension.loading"} %ul - %li.action{"ng-repeat" => "action in extension.actionsWithContextForItem(item)", "ng-click" => "executeAction(action, extension)"} - .name {{action.label}} - .desc {{action.desc}} + %li.menu-item{"ng-repeat" => "action in extension.actionsWithContextForItem(item)", "ng-click" => "executeAction(action, extension)"} + .menu-item-title {{action.label}} + .menu-item-subtitle {{action.desc}} + + %div{"ng-if" => "action.showNestedActions"} + %ul.mt-10 + %li.menu-item.white-bg{"ng-repeat" => "subaction in action.subactions", "ng-click" => "executeAction(subaction, extension); $event.stopPropagation()", "style" => "margin-top: -1px;"} + .menu-item-title {{subaction.label}} + .menu-item-subtitle {{subaction.desc}} + %span{"ng-if" => "subaction.running"} + .spinner{"style" => "margin-top: 3px;"} + %span{"ng-if" => "action.running"} .spinner{"style" => "margin-top: 3px;"} + +.extension-render-modal{"ng-if" => "renderData.showRenderModal", "ng-click" => "renderData.showRenderModal = false"} + .content + %h2 {{renderData.title}} + %p.normal{"style" => "white-space: pre-wrap; font-family: monospace; font-size: 16px;"} {{renderData.text}} diff --git a/app/assets/templates/frontend/directives/editor-menu.html.haml b/app/assets/templates/frontend/directives/editor-menu.html.haml new file mode 100644 index 000000000..374e58918 --- /dev/null +++ b/app/assets/templates/frontend/directives/editor-menu.html.haml @@ -0,0 +1,23 @@ +%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark.editor-menu + .menu-section-header + .title System Editors + %ul + %li.menu-item{"ng-repeat" => "editor in sysEditors", "ng-click" => "selectEditor(editor)"} + %span.pull-left.mr-10{"ng-if" => "!selectedEditor"} ✓ + .menu-item-title.pull-left {{editor.name}} + + %div{"ng-if" => "editors.length > 0"} + .menu-section-header + .title External Editors + .subtitle Can access your current note decrypted. + %ul + %li.menu-item{"ng-repeat" => "editor in editors", "ng-click" => "selectEditor(editor)"} + %span.pull-left.mr-10{"ng-if" => "selectedEditor == editor"} ✓ + .pull-left{"style" => "width: 60%"} + .menu-item-title {{editor.name}} + .menu-item-subtitle.wrap {{editor.url}} + .pull-right + %button.white.medium.inline.top{"style" => "width: 50px; height: 40px;", "ng-click" => "deleteEditor(editor); $event.stopPropagation();"} ☓ + .menu-section-footer.mt-10 + %input.form-control{"ng-model" => "formData.url", "placeholder" => "Add new editor via URL", "ng-keyup" => "$event.keyCode == 13 && submitNewEditorRequest()"} + %a.block.blue{"href" => "https://standardnotes.org/extensions", "target" => "_blank"} Available Editors diff --git a/app/assets/templates/frontend/directives/global-extensions-menu.html.haml b/app/assets/templates/frontend/directives/global-extensions-menu.html.haml index 2dd5df9d1..a28067719 100644 --- a/app/assets/templates/frontend/directives/global-extensions-menu.html.haml +++ b/app/assets/templates/frontend/directives/global-extensions-menu.html.haml @@ -1,8 +1,8 @@ .panel.panel-default.account-panel.panel-right .panel-body - %div{"style" => "font-size: 18px;", "ng-if" => "!extensionManager.extensions.length"} No extensions installed + %div{"style" => "font-size: 15px;", "ng-if" => "!extensionManager.extensions.length"} No extensions installed %div{"ng-if" => "extensionManager.extensions.length"} - %section.gray-bg.inline-h.mb-10.medium-padding{"ng-repeat" => "extension in extensionManager.extensions", "ng-init" => "extension.formData = {}"} + %section.gray-bg.inline-h.mb-10.medium-padding{"ng-repeat" => "extension in extensionManager.extensions | orderBy: 'name'", "ng-init" => "extension.formData = {}"} %h3.center-align {{extension.name}} .center-align.centered.mt-10 %label.block.normal Send data: diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index af7635754..933e00c99 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -1,46 +1,44 @@ .section.editor{"ng-class" => "{'fullscreen' : ctrl.fullscreen}"} - .content - .section-title-bar.editor-heading{"ng-class" => "{'fullscreen' : ctrl.fullscreen }"} - .title - %input.input#note-title-editor{"ng-model" => "ctrl.note.title", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTitle($event)", - "ng-change" => "ctrl.nameChanged()", "ng-focus" => "ctrl.onNameFocus()", - "select-on-click" => "true"} - .save-status{"ng-class" => "{'red bold': ctrl.saveError}", "ng-bind-html" => "ctrl.noteStatus"} - .tags - %input.tags-input{"type" => "text", "ng-keyup" => "$event.keyCode == 13 && ctrl.updateTagsFromTagsString($event, ctrl.tagsString)", - "ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)"} - .section-menu - %ul.nav.nav-pills - %li.dropdown - %a.dropdown-toggle{"ng-click" => "ctrl.clickedMenu(); ctrl.showExtensions = false"} - File - %span.caret - %span.sr-only + .section-title-bar.editor-heading{"ng-if" => "ctrl.note", "ng-class" => "{'fullscreen' : ctrl.fullscreen }"} + .title + %input.input#note-title-editor{"ng-model" => "ctrl.note.title", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTitle($event)", + "ng-change" => "ctrl.nameChanged()", "ng-focus" => "ctrl.onNameFocus()", + "select-on-click" => "true"} + .save-status{"ng-class" => "{'red bold': ctrl.saveError}", "ng-bind-html" => "ctrl.noteStatus"} + .tags + %input.tags-input{"type" => "text", "ng-keyup" => "$event.keyCode == 13 && $event.target.blur();", + "ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)"} + .section-menu{"ng-if" => "ctrl.note"} + %ul.nav + %li.dropdown.pull-left.mr-10{"click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"} + %a.dropdown-toggle{"ng-click" => "ctrl.showMenu = !ctrl.showMenu; ctrl.showExtensions = false;"} + Menu + %span.caret + %span.sr-only - %ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu"} - %li{"ng-click" => "ctrl.selectedMenuItem(); ctrl.toggleFullScreen()"} - .text Toggle Fullscreen - .shortcut Cmd + O - %li{"ng-click" => "ctrl.selectedMenuItem(); ctrl.toggleMarkdown()"} - .text Toggle Markdown Preview - .shortcut Cmd + M - %li{"ng-click" => "ctrl.deleteNote()"} - .text Delete - %li.sep - %li.dropdown{"ng-if" => "ctrl.hasAvailableExtensions()"} - %a.dropdown-toggle{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false"} - Extensions - %span.caret - %span.sr-only - %contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"} + %ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu"} + %li{"ng-click" => "ctrl.selectedMenuItem(); ctrl.toggleFullScreen()"} + .text Toggle Fullscreen + %li{"ng-click" => "ctrl.deleteNote()"} + .text Delete Note - .markdown.icon{"ng-if" => "ctrl.editorMode == 'preview'", "ng-click" => "ctrl.showMarkdown = !ctrl.showMarkdown"} - .icon-markdown - .panel.panel-default.info-panel{"ng-if" => "ctrl.showMarkdown"} - .panel-body{"style" => "text-align: center; color: black;"} - This editor is Markdown enabled. + %li.sep + %li.dropdown.pull-left.mr-10{"click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"} + %a.dropdown-toggle{"ng-click" => "ctrl.showEditorMenu = !ctrl.showEditorMenu; ctrl.showMenu = false;"} + Editor + %span.caret + %span.sr-only + %editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.selectedEditor", "selected-editor" => "ctrl.customEditor"} - .editor-content{"ng-class" => "{'fullscreen' : ctrl.fullscreen }"} - %textarea.editable#note-text-editor{"ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-show" => "ctrl.editorMode == 'edit'", "ng-model" => "ctrl.note.text", - "ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()"} - .preview{"ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-if" => "ctrl.editorMode == 'preview'", "ng-bind-html" => "ctrl.renderedContent()", "ng-dblclick" => "ctrl.onPreviewDoubleClick()"} + %li.sep + %li.dropdown.pull-left{"ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"} + %a.dropdown-toggle{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false;"} + Extensions + %span.caret + %span.sr-only + %contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"} + + .editor-content{"ng-class" => "{'fullscreen' : ctrl.fullscreen }"} + %iframe#editor-iframe{"ng-if" => "ctrl.customEditor", "ng-src" => "{{ctrl.customEditor.url | trusted}}", "frameBorder" => "0", "style" => "width: 100%;"} + %textarea.editable#note-text-editor{"ng-if" => "!ctrl.customEditor", "ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-model" => "ctrl.note.text", + "ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()"} diff --git a/app/assets/templates/frontend/header.html.haml b/app/assets/templates/frontend/header.html.haml index f385f293c..972825eaf 100644 --- a/app/assets/templates/frontend/header.html.haml +++ b/app/assets/templates/frontend/header.html.haml @@ -1,10 +1,10 @@ .footer-bar .pull-left - .footer-bar-link + .footer-bar-link{"click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"} %a{"ng-click" => "ctrl.accountMenuPressed()", "ng-class" => "{red: ctrl.error}"} Account %account-menu{"ng-if" => "ctrl.showAccountMenu"} - .footer-bar-link + .footer-bar-link{"click-outside" => "ctrl.showExtensionsMenu = false;", "is-open" => "ctrl.showExtensionsMenu"} %a{"ng-click" => "ctrl.toggleExtensions()"} Extensions %global-extensions-menu{"ng-if" => "ctrl.showExtensionsMenu"} diff --git a/app/assets/templates/frontend/home.html.haml b/app/assets/templates/frontend/home.html.haml index f7057c955..61203afcd 100644 --- a/app/assets/templates/frontend/home.html.haml +++ b/app/assets/templates/frontend/home.html.haml @@ -1,12 +1,11 @@ .main-ui-view - .app-container - .app - %tags-section{"save" => "tagsSave", "add-new" => "tagsAddNew", "will-select" => "tagsWillMakeSelection", "selection-made" => "tagsSelectionMade", "all-tag" => "allTag", - "tags" => "tags"} + .app + %tags-section{"save" => "tagsSave", "add-new" => "tagsAddNew", "will-select" => "tagsWillMakeSelection", "selection-made" => "tagsSelectionMade", "all-tag" => "allTag", + "tags" => "tags"} - %notes-section{"remove-tag" => "notesRemoveTag", "add-new" => "notesAddNew", "selection-made" => "notesSelectionMade", - "tag" => "selectedTag", "remove" => "deleteNote"} + %notes-section{"remove-tag" => "notesRemoveTag", "add-new" => "notesAddNew", "selection-made" => "notesSelectionMade", + "tag" => "selectedTag", "remove" => "deleteNote"} - %editor-section{"ng-if" => "selectedNote", "note" => "selectedNote", "remove" => "deleteNote", "save" => "saveNote", "update-tags" => "updateTagsForNote"} + %editor-section{"note" => "selectedNote", "remove" => "deleteNote", "save" => "saveNote", "update-tags" => "updateTagsForNote"} %header diff --git a/app/assets/templates/frontend/layouts/about.html.haml b/app/assets/templates/frontend/layouts/about.html.haml deleted file mode 100644 index c274dd210..000000000 --- a/app/assets/templates/frontend/layouts/about.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -.about.animated.fadeIn - .title About - .summary Namewhale helps you find a unique name for your startup. Using an intelligent, seed-based algorithm, names are generated based on the sound, style, and feel of the seed words you chose. - .links - %a{"href" => "https://itunes.apple.com/us/app/namewhale/id1028881375?ls=1&mt=8", "target" => "_blank"} Namewhale on the AppStore - %a{"href" => "https://twitter.com/namewhale", "target" => "_blank"} @namewhale diff --git a/app/assets/templates/frontend/layouts/footer.html.haml b/app/assets/templates/frontend/layouts/footer.html.haml deleted file mode 100644 index f378522db..000000000 --- a/app/assets/templates/frontend/layouts/footer.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%footer.footer{"ng-class" => "footerClass"} - .container - .row - .footer-about-section diff --git a/app/assets/templates/frontend/notes.html.haml b/app/assets/templates/frontend/notes.html.haml index 94b9e9dec..63a42b7f0 100644 --- a/app/assets/templates/frontend/notes.html.haml +++ b/app/assets/templates/frontend/notes.html.haml @@ -10,17 +10,25 @@ %ul.nav.nav-pills %li.dropdown %a.dropdown-toggle{"ng-click" => "ctrl.showMenu = !ctrl.showMenu"} - Tag options + Menu %span.caret %span.sr-only %ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu"} + %li + %a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedSortByCreated()"} + %span.top.mt-5.mr-5{"ng-if" => "ctrl.sortBy == 'created_at'"} ✓ + Sort by date created + %li + %a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedSortByUpdated()"} + %span.top.mt-5.mr-5{"ng-if" => "ctrl.sortBy == 'updated_at'"} ✓ + Sort by date updated %li %a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagDelete()"} Delete Tag .scrollable .infinite-scroll{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"} - .note{"ng-repeat" => "note in ctrl.tag.notes | filter: ctrl.filterNotes | limitTo:ctrl.notesToDisplay", + .note{"ng-repeat" => "note in (ctrl.sortedNotes = (ctrl.tag.notes | filter: ctrl.filterNotes | orderBy: ctrl.sortBy:true | limitTo:ctrl.notesToDisplay))", "ng-click" => "ctrl.selectNote(note)", "ng-class" => "{'selected' : ctrl.selectedNote == note}"} .name{"ng-if" => "note.title"} {{note.title}} diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2d6b04dea..43b6f0688 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,10 +1,10 @@ class ApplicationController < ActionController::Base - # Prevent CSRF attacks by raising an exception. - # For APIs, you may want to use :null_session instead. protect_from_forgery with: :null_session after_action :set_csrf_cookie + after_action :allow_iframe + layout :false def frontend @@ -13,8 +13,13 @@ class ApplicationController < ActionController::Base rescue_from ActionView::MissingTemplate do |exception| end + protected + def allow_iframe + response.headers.except! 'X-Frame-Options' + end + def set_app_domain @appDomain = request.domain @appDomain << ':' + request.port.to_s unless request.port.blank? diff --git a/app/views/application/frontend.html.erb b/app/views/application/frontend.html.erb index 5e965dc17..e60481af3 100644 --- a/app/views/application/frontend.html.erb +++ b/app/views/application/frontend.html.erb @@ -37,7 +37,7 @@ -
+ diff --git a/bower.json b/bower.json index dd0074806..fa8d65b8f 100644 --- a/bower.json +++ b/bower.json @@ -9,8 +9,7 @@ "dependencies": { "angular": "1.6.1", "angular-ui-router": "^0.3.2", - "restangular": "^1.6.1", - "marked": "0.3.6" + "restangular": "^1.6.1" }, "resolutions": { "angular": "1.6.1" diff --git a/public/favicon/android-chrome-192x192.png b/public/favicon/android-chrome-192x192.png index 419b3202a..2f4f3ba3c 100644 Binary files a/public/favicon/android-chrome-192x192.png and b/public/favicon/android-chrome-192x192.png differ diff --git a/public/favicon/android-chrome-384x384.png b/public/favicon/android-chrome-384x384.png index d3f4c81a1..2bd6810d7 100644 Binary files a/public/favicon/android-chrome-384x384.png and b/public/favicon/android-chrome-384x384.png differ diff --git a/public/favicon/apple-touch-icon.png b/public/favicon/apple-touch-icon.png index edd53c010..620497103 100644 Binary files a/public/favicon/apple-touch-icon.png and b/public/favicon/apple-touch-icon.png differ diff --git a/public/favicon/browserconfig.xml b/public/favicon/browserconfig.xml index 03c04ac40..b3930d0f0 100644 --- a/public/favicon/browserconfig.xml +++ b/public/favicon/browserconfig.xml @@ -1,9 +1,9 @@ - -