4
Gemfile
4
Gemfile
@@ -9,16 +9,12 @@ gem "non-stupid-digest-assets"
|
|||||||
|
|
||||||
gem 'uglifier'
|
gem 'uglifier'
|
||||||
|
|
||||||
gem 'activemodel-serializers-xml'
|
|
||||||
|
|
||||||
gem 'rack-cors', :require => 'rack/cors'
|
gem 'rack-cors', :require => 'rack/cors'
|
||||||
|
|
||||||
gem 'dotenv-rails', '~> 2.1.1'
|
gem 'dotenv-rails', '~> 2.1.1'
|
||||||
|
|
||||||
gem 'bower-rails', '~> 0.10.0'
|
gem 'bower-rails', '~> 0.10.0'
|
||||||
|
|
||||||
gem 'devise_token_auth', '~> 0.1.39'
|
|
||||||
|
|
||||||
# bundle exec rake doc:rails generates the API under doc/api.
|
# bundle exec rake doc:rails generates the API under doc/api.
|
||||||
gem 'sdoc', '~> 0.4.0', group: :doc
|
gem 'sdoc', '~> 0.4.0', group: :doc
|
||||||
|
|
||||||
|
|||||||
20
Gemfile.lock
20
Gemfile.lock
@@ -29,11 +29,6 @@ GEM
|
|||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (5.0.0.1)
|
activemodel (5.0.0.1)
|
||||||
activesupport (= 5.0.0.1)
|
activesupport (= 5.0.0.1)
|
||||||
activemodel-serializers-xml (1.0.1)
|
|
||||||
activemodel (> 5.x)
|
|
||||||
activerecord (> 5.x)
|
|
||||||
activesupport (> 5.x)
|
|
||||||
builder (~> 3.1)
|
|
||||||
activerecord (5.0.0.1)
|
activerecord (5.0.0.1)
|
||||||
activemodel (= 5.0.0.1)
|
activemodel (= 5.0.0.1)
|
||||||
activesupport (= 5.0.0.1)
|
activesupport (= 5.0.0.1)
|
||||||
@@ -46,7 +41,6 @@ GEM
|
|||||||
airbrussh (1.1.1)
|
airbrussh (1.1.1)
|
||||||
sshkit (>= 1.6.1, != 1.7.0)
|
sshkit (>= 1.6.1, != 1.7.0)
|
||||||
arel (7.1.4)
|
arel (7.1.4)
|
||||||
bcrypt (3.1.11)
|
|
||||||
binding_of_caller (0.7.2)
|
binding_of_caller (0.7.2)
|
||||||
debug_inspector (>= 0.0.1)
|
debug_inspector (>= 0.0.1)
|
||||||
bower-rails (0.10.0)
|
bower-rails (0.10.0)
|
||||||
@@ -78,15 +72,6 @@ GEM
|
|||||||
concurrent-ruby (1.0.2)
|
concurrent-ruby (1.0.2)
|
||||||
connection_pool (2.2.1)
|
connection_pool (2.2.1)
|
||||||
debug_inspector (0.0.2)
|
debug_inspector (0.0.2)
|
||||||
devise (4.2.0)
|
|
||||||
bcrypt (~> 3.0)
|
|
||||||
orm_adapter (~> 0.1)
|
|
||||||
railties (>= 4.1.0, < 5.1)
|
|
||||||
responders
|
|
||||||
warden (~> 1.2.3)
|
|
||||||
devise_token_auth (0.1.39)
|
|
||||||
devise (> 3.5.2, <= 4.2)
|
|
||||||
rails (< 6)
|
|
||||||
dotenv (2.1.1)
|
dotenv (2.1.1)
|
||||||
dotenv-rails (2.1.1)
|
dotenv-rails (2.1.1)
|
||||||
dotenv (= 2.1.1)
|
dotenv (= 2.1.1)
|
||||||
@@ -115,7 +100,6 @@ GEM
|
|||||||
mini_portile2 (~> 2.1.0)
|
mini_portile2 (~> 2.1.0)
|
||||||
non-stupid-digest-assets (1.0.9)
|
non-stupid-digest-assets (1.0.9)
|
||||||
sprockets (>= 2.0)
|
sprockets (>= 2.0)
|
||||||
orm_adapter (0.5.0)
|
|
||||||
puma (3.6.2)
|
puma (3.6.2)
|
||||||
rack (2.0.1)
|
rack (2.0.1)
|
||||||
rack-cors (0.4.0)
|
rack-cors (0.4.0)
|
||||||
@@ -178,8 +162,6 @@ GEM
|
|||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
uglifier (3.0.3)
|
uglifier (3.0.3)
|
||||||
execjs (>= 0.3.0, < 3)
|
execjs (>= 0.3.0, < 3)
|
||||||
warden (1.2.6)
|
|
||||||
rack (>= 1.0)
|
|
||||||
web-console (2.3.0)
|
web-console (2.3.0)
|
||||||
activemodel (>= 4.0)
|
activemodel (>= 4.0)
|
||||||
binding_of_caller (>= 0.7.2)
|
binding_of_caller (>= 0.7.2)
|
||||||
@@ -193,7 +175,6 @@ PLATFORMS
|
|||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
activemodel-serializers-xml
|
|
||||||
bower-rails (~> 0.10.0)
|
bower-rails (~> 0.10.0)
|
||||||
byebug
|
byebug
|
||||||
capistrano
|
capistrano
|
||||||
@@ -203,7 +184,6 @@ DEPENDENCIES
|
|||||||
capistrano-rails
|
capistrano-rails
|
||||||
capistrano-rvm
|
capistrano-rvm
|
||||||
capistrano-sidekiq
|
capistrano-sidekiq
|
||||||
devise_token_auth (~> 0.1.39)
|
|
||||||
dotenv-rails (~> 2.1.1)
|
dotenv-rails (~> 2.1.1)
|
||||||
non-stupid-digest-assets
|
non-stupid-digest-assets
|
||||||
puma
|
puma
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ module.exports = function(grunt) {
|
|||||||
'app/assets/javascripts/app/frontend/controllers/*.js',
|
'app/assets/javascripts/app/frontend/controllers/*.js',
|
||||||
'app/assets/javascripts/app/frontend/models/**/*.js',
|
'app/assets/javascripts/app/frontend/models/**/*.js',
|
||||||
'app/assets/javascripts/app/services/**/*.js',
|
'app/assets/javascripts/app/services/**/*.js',
|
||||||
// 'app/assets/javascripts/app/services/directives/*.js',
|
|
||||||
],
|
],
|
||||||
dest: 'vendor/assets/javascripts/app.js',
|
dest: 'vendor/assets/javascripts/app.js',
|
||||||
},
|
},
|
||||||
@@ -85,13 +84,9 @@ module.exports = function(grunt) {
|
|||||||
src: [
|
src: [
|
||||||
'vendor/assets/bower_components/angular/angular.js',
|
'vendor/assets/bower_components/angular/angular.js',
|
||||||
'vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.js',
|
'vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.js',
|
||||||
'vendor/assets/bower_components/ng-token-auth/dist/ng-token-auth.js',
|
|
||||||
'vendor/assets/bower_components/angular-cookie/angular-cookie.js',
|
|
||||||
'vendor/assets/bower_components/lodash/dist/lodash.min.js',
|
'vendor/assets/bower_components/lodash/dist/lodash.min.js',
|
||||||
'vendor/assets/bower_components/restangular/dist/restangular.js',
|
'vendor/assets/bower_components/restangular/dist/restangular.js',
|
||||||
'vendor/assets/bower_components/marked/lib/marked.js',
|
'vendor/assets/bower_components/marked/lib/marked.js',
|
||||||
'vendor/assets/bower_components/oclazyload/dist/ocLazyLoad.js',
|
|
||||||
'vendor/assets/bower_components/angular-lazy-img/release/angular-lazy-img.js',
|
|
||||||
'vendor/assets/bower_components/ng-dialog/js/ngDialog.min.js',
|
'vendor/assets/bower_components/ng-dialog/js/ngDialog.min.js',
|
||||||
'vendor/assets/javascripts/crypto/*.js'
|
'vendor/assets/javascripts/crypto/*.js'
|
||||||
],
|
],
|
||||||
@@ -108,7 +103,6 @@ module.exports = function(grunt) {
|
|||||||
'vendor/assets/stylesheets/app.css',
|
'vendor/assets/stylesheets/app.css',
|
||||||
'vendor/assets/bower_components/ng-dialog/css/ngDialog.css',
|
'vendor/assets/bower_components/ng-dialog/css/ngDialog.css',
|
||||||
'vendor/assets/bower_components/ng-dialog/css/ngDialog-theme-default.css',
|
'vendor/assets/bower_components/ng-dialog/css/ngDialog-theme-default.css',
|
||||||
'vendor/assets/bower_components/angular-typewrite/dist/angular-typewrite.css',
|
|
||||||
],
|
],
|
||||||
dest: 'vendor/assets/stylesheets/app.css'
|
dest: 'vendor/assets/stylesheets/app.css'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ if(window.crypto.subtle) {
|
|||||||
angular.module('app.frontend', [
|
angular.module('app.frontend', [
|
||||||
'ui.router',
|
'ui.router',
|
||||||
'restangular',
|
'restangular',
|
||||||
'oc.lazyLoad',
|
|
||||||
'angularLazyImg',
|
|
||||||
'ngDialog'
|
'ngDialog'
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -63,12 +63,12 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.controller('EditorCtrl', function ($sce, $timeout, apiController, modelManager, markdownRenderer, $rootScope) {
|
.controller('EditorCtrl', function ($sce, $timeout, apiController, markdownRenderer, $rootScope, extensionManager) {
|
||||||
|
|
||||||
this.setNote = function(note, oldNote) {
|
this.setNote = function(note, oldNote) {
|
||||||
this.editorMode = 'edit';
|
this.editorMode = 'edit';
|
||||||
|
|
||||||
if(note.content.text.length == 0 && note.dummy) {
|
if(note.safeText().length == 0 && note.dummy) {
|
||||||
this.focusTitle(100);
|
this.focusTitle(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +81,10 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.hasAvailableExtensions = function() {
|
||||||
|
return extensionManager.extensionsInContextOfItem(this.note).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
this.onPreviewDoubleClick = function() {
|
this.onPreviewDoubleClick = function() {
|
||||||
this.editorMode = 'edit';
|
this.editorMode = 'edit';
|
||||||
this.focusEditor(100);
|
this.focusEditor(100);
|
||||||
@@ -104,7 +108,7 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.renderedContent = function() {
|
this.renderedContent = function() {
|
||||||
return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText(this.note.content.text));
|
return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText(this.note.safeText()));
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusTimeout;
|
var statusTimeout;
|
||||||
@@ -207,10 +211,10 @@ angular.module('app.frontend')
|
|||||||
|
|
||||||
var original = this.note.presentation_name;
|
var original = this.note.presentation_name;
|
||||||
this.note.presentation_name = this.url.token;
|
this.note.presentation_name = this.url.token;
|
||||||
modelManager.addDirtyItems([this.note]);
|
this.note.setDirty(true);
|
||||||
|
|
||||||
apiController.sync(function(response){
|
apiController.sync(function(response){
|
||||||
if(!response) {
|
if(response && response.error) {
|
||||||
this.note.presentation_name = original;
|
this.note.presentation_name = original;
|
||||||
this.url.token = original;
|
this.url.token = original;
|
||||||
alert("This URL is not available.");
|
alert("This URL is not available.");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
angular.module('app.frontend')
|
angular.module('app.frontend')
|
||||||
.directive("header", function(apiController){
|
.directive("header", function(apiController, extensionManager){
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
scope: {
|
scope: {
|
||||||
@@ -19,7 +19,9 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.controller('HeaderCtrl', function ($state, apiController, modelManager, serverSideValidation, $timeout) {
|
.controller('HeaderCtrl', function ($state, apiController, modelManager, serverSideValidation, $timeout, extensionManager) {
|
||||||
|
|
||||||
|
this.extensionManager = extensionManager;
|
||||||
|
|
||||||
this.changePasswordPressed = function() {
|
this.changePasswordPressed = function() {
|
||||||
this.showNewPasswordForm = !this.showNewPasswordForm;
|
this.showNewPasswordForm = !this.showNewPasswordForm;
|
||||||
@@ -30,6 +32,56 @@ angular.module('app.frontend')
|
|||||||
this.showAccountMenu = !this.showAccountMenu;
|
this.showAccountMenu = !this.showAccountMenu;
|
||||||
this.showFaq = false;
|
this.showFaq = false;
|
||||||
this.showNewPasswordForm = false;
|
this.showNewPasswordForm = false;
|
||||||
|
this.showExtensionsMenu = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggleExtensions = function() {
|
||||||
|
this.showAccountMenu = false;
|
||||||
|
this.showExtensionsMenu = !this.showExtensionsMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggleExtensionForm = function() {
|
||||||
|
this.newExtensionData = {};
|
||||||
|
this.showNewExtensionForm = !this.showNewExtensionForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.submitNewExtensionForm = function() {
|
||||||
|
if(this.newExtensionData.url) {
|
||||||
|
extensionManager.addExtension(this.newExtensionData.url, function(response){
|
||||||
|
if(!response) {
|
||||||
|
alert("Unable to register this extension. Make sure the link is valid and try again.");
|
||||||
|
} else {
|
||||||
|
this.newExtensionData.url = "";
|
||||||
|
this.showNewExtensionForm = false;
|
||||||
|
}
|
||||||
|
}.bind(this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedAction = function(action, extension) {
|
||||||
|
action.running = true;
|
||||||
|
extensionManager.executeAction(action, extension, null, function(response){
|
||||||
|
action.running = false;
|
||||||
|
if(response && response.error) {
|
||||||
|
action.error = true;
|
||||||
|
alert("There was an error performing this action. Please try again.");
|
||||||
|
} else {
|
||||||
|
action.error = false;
|
||||||
|
apiController.sync(null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deleteExtension = function(extension) {
|
||||||
|
if(confirm("Are you sure you want to delete this extension?")) {
|
||||||
|
extensionManager.deleteExtension(extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadExtensionsPressed = function() {
|
||||||
|
if(confirm("For your security, reloading extensions will disable any currently enabled repeat actions.")) {
|
||||||
|
extensionManager.refreshExtensionsFromServer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.changeServer = function() {
|
this.changeServer = function() {
|
||||||
@@ -77,7 +129,7 @@ angular.module('app.frontend')
|
|||||||
$timeout(function(){
|
$timeout(function(){
|
||||||
this.isRefreshing = false;
|
this.isRefreshing = false;
|
||||||
}.bind(this), 200)
|
}.bind(this), 200)
|
||||||
if(!response) {
|
if(response && response.error) {
|
||||||
alert("There was an error syncing. Please try again. If all else fails, log out and log back in.");
|
alert("There was an error syncing. Please try again. If all else fails, log out and log back in.");
|
||||||
} else {
|
} else {
|
||||||
this.syncUpdated();
|
this.syncUpdated();
|
||||||
@@ -120,18 +172,6 @@ angular.module('app.frontend')
|
|||||||
}.bind(this))
|
}.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
this.forgotPasswordSubmit = function() {
|
|
||||||
// $auth.requestPasswordReset(this.resetData)
|
|
||||||
// .then(function(resp) {
|
|
||||||
// this.resetData.response = "Success";
|
|
||||||
// // handle success response
|
|
||||||
// }.bind(this))
|
|
||||||
// .catch(function(resp) {
|
|
||||||
// // handle error response
|
|
||||||
// this.resetData.response = "Error";
|
|
||||||
// }.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.encryptionStatusForNotes = function() {
|
this.encryptionStatusForNotes = function() {
|
||||||
var allNotes = modelManager.filteredNotes;
|
var allNotes = modelManager.filteredNotes;
|
||||||
var countEncrypted = 0;
|
var countEncrypted = 0;
|
||||||
@@ -144,10 +184,12 @@ angular.module('app.frontend')
|
|||||||
return countEncrypted + "/" + allNotes.length + " notes encrypted";
|
return countEncrypted + "/" + allNotes.length + " notes encrypted";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.archiveEncryptionFormat = {encrypted: true};
|
||||||
|
|
||||||
this.downloadDataArchive = function() {
|
this.downloadDataArchive = function() {
|
||||||
var link = document.createElement('a');
|
var link = document.createElement('a');
|
||||||
link.setAttribute('download', 'notes.json');
|
link.setAttribute('download', 'notes.json');
|
||||||
link.href = apiController.itemsDataFile();
|
link.href = apiController.itemsDataFile(this.archiveEncryptionFormat.encrypted);
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ angular.module('app.frontend')
|
|||||||
var onUserSet = function() {
|
var onUserSet = function() {
|
||||||
apiController.setUser($scope.defaultUser);
|
apiController.setUser($scope.defaultUser);
|
||||||
$scope.allTag = new Tag({all: true});
|
$scope.allTag = new Tag({all: true});
|
||||||
$scope.allTag.content.title = "All";
|
$scope.allTag.title = "All";
|
||||||
$scope.tags = modelManager.tags;
|
$scope.tags = modelManager.tags;
|
||||||
$scope.allTag.notes = modelManager.notes;
|
$scope.allTag.notes = modelManager.notes;
|
||||||
|
|
||||||
@@ -14,8 +14,6 @@ angular.module('app.frontend')
|
|||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
apiController.sync(null);
|
apiController.sync(null);
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|
||||||
// apiController.verifyEncryptionStatusOfAllItems($scope.defaultUser, function(success){});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apiController.getCurrentUser(function(user){
|
apiController.getCurrentUser(function(user){
|
||||||
@@ -45,14 +43,18 @@ angular.module('app.frontend')
|
|||||||
|
|
||||||
$scope.tagsSelectionMade = function(tag) {
|
$scope.tagsSelectionMade = function(tag) {
|
||||||
$scope.selectedTag = tag;
|
$scope.selectedTag = tag;
|
||||||
|
|
||||||
|
if($scope.selectedNote && $scope.selectedNote.dummy) {
|
||||||
|
modelManager.removeItemLocally($scope.selectedNote);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.tagsAddNew = function(tag) {
|
$scope.tagsAddNew = function(tag) {
|
||||||
modelManager.addTag(tag);
|
modelManager.addItem(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.tagsSave = function(tag, callback) {
|
$scope.tagsSave = function(tag, callback) {
|
||||||
modelManager.addDirtyItems([tag]);
|
tag.setDirty(true);
|
||||||
apiController.sync(callback);
|
apiController.sync(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +66,7 @@ angular.module('app.frontend')
|
|||||||
|
|
||||||
var originalNote = _.find(modelManager.notes, {uuid: noteCopy.uuid});
|
var originalNote = _.find(modelManager.notes, {uuid: noteCopy.uuid});
|
||||||
if(!newTag.all) {
|
if(!newTag.all) {
|
||||||
modelManager.addTagToNote(newTag, originalNote);
|
modelManager.createRelationshipBetweenItems(newTag, originalNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
apiController.sync(function(){});
|
apiController.sync(function(){});
|
||||||
@@ -77,9 +79,9 @@ angular.module('app.frontend')
|
|||||||
$scope.notesRemoveTag = function(tag) {
|
$scope.notesRemoveTag = function(tag) {
|
||||||
var validNotes = Note.filterDummyNotes(tag.notes);
|
var validNotes = Note.filterDummyNotes(tag.notes);
|
||||||
if(validNotes == 0) {
|
if(validNotes == 0) {
|
||||||
modelManager.deleteTag(tag);
|
modelManager.setItemToBeDeleted(tag);
|
||||||
// if no more notes, delete tag
|
// if no more notes, delete tag
|
||||||
apiController.deleteItem(tag, function(){
|
apiController.sync(function(){
|
||||||
// force scope tags to update on sub directives
|
// force scope tags to update on sub directives
|
||||||
$scope.tags = [];
|
$scope.tags = [];
|
||||||
$timeout(function(){
|
$timeout(function(){
|
||||||
@@ -96,10 +98,10 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scope.notesAddNew = function(note) {
|
$scope.notesAddNew = function(note) {
|
||||||
modelManager.addNote(note);
|
modelManager.addItem(note);
|
||||||
|
|
||||||
if(!$scope.selectedTag.all) {
|
if(!$scope.selectedTag.all) {
|
||||||
modelManager.addTagToNote($scope.selectedTag, note);
|
modelManager.createRelationshipBetweenItems($scope.selectedTag, note);
|
||||||
$scope.updateAllTag();
|
$scope.updateAllTag();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,30 +111,35 @@ angular.module('app.frontend')
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
$scope.saveNote = function(note, callback) {
|
$scope.saveNote = function(note, callback) {
|
||||||
modelManager.addDirtyItems(note);
|
note.setDirty(true);
|
||||||
|
|
||||||
apiController.sync(function(){
|
apiController.sync(function(response){
|
||||||
note.hasChanges = false;
|
if(response && response.error) {
|
||||||
|
alert("There was an error saving your note. Please try again.");
|
||||||
if(callback) {
|
callback(false);
|
||||||
callback(true);
|
} else {
|
||||||
|
note.hasChanges = false;
|
||||||
|
if(callback) {
|
||||||
|
callback(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.deleteNote = function(note) {
|
$scope.deleteNote = function(note) {
|
||||||
|
|
||||||
modelManager.deleteNote(note);
|
modelManager.setItemToBeDeleted(note);
|
||||||
|
|
||||||
if(note == $scope.selectedNote) {
|
if(note == $scope.selectedNote) {
|
||||||
$scope.selectedNote = null;
|
$scope.selectedNote = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(note.dummy) {
|
if(note.dummy) {
|
||||||
|
modelManager.removeItemLocally(note);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
apiController.deleteItem(note, function(success){})
|
apiController.sync(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.controller('NotesCtrl', function (apiController, $timeout, $rootScope) {
|
.controller('NotesCtrl', function (apiController, $timeout, $rootScope, modelManager) {
|
||||||
|
|
||||||
$rootScope.$on("editorFocused", function(){
|
$rootScope.$on("editorFocused", function(){
|
||||||
this.showMenu = false;
|
this.showMenu = false;
|
||||||
@@ -110,8 +110,8 @@ angular.module('app.frontend')
|
|||||||
|
|
||||||
this.createNewNote = function() {
|
this.createNewNote = function() {
|
||||||
var title = "New Note" + (this.tag.notes ? (" " + (this.tag.notes.length + 1)) : "");
|
var title = "New Note" + (this.tag.notes ? (" " + (this.tag.notes.length + 1)) : "");
|
||||||
this.newNote = new Note({dummy: true});
|
this.newNote = modelManager.createItem({content_type: "Note", dummy: true, text: ""});
|
||||||
this.newNote.content.title = title;
|
this.newNote.title = title;
|
||||||
this.selectNote(this.newNote);
|
this.selectNote(this.newNote);
|
||||||
this.addNew()(this.newNote);
|
this.addNew()(this.newNote);
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ angular.module('app.frontend')
|
|||||||
if(this.noteFilter.text.length == 0) {
|
if(this.noteFilter.text.length == 0) {
|
||||||
note.visible = true;
|
note.visible = true;
|
||||||
} else {
|
} else {
|
||||||
note.visible = note.content.title.toLowerCase().includes(this.noteFilter.text) || note.content.text.toLowerCase().includes(this.noteFilter.text);
|
note.visible = note.title.toLowerCase().includes(this.noteFilter.text) || note.text.toLowerCase().includes(this.noteFilter.text);
|
||||||
}
|
}
|
||||||
return note.visible;
|
return note.visible;
|
||||||
}.bind(this)
|
}.bind(this)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.controller('TagsCtrl', function () {
|
.controller('TagsCtrl', function (modelManager) {
|
||||||
|
|
||||||
var initialLoad = true;
|
var initialLoad = true;
|
||||||
|
|
||||||
@@ -62,8 +62,8 @@ angular.module('app.frontend')
|
|||||||
if(this.editingTag) {
|
if(this.editingTag) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.newTag = new Tag();
|
this.newTag = modelManager.createItem({content_type: "Tag"});
|
||||||
this.selectedTag = this.newTag;
|
this.selectedTag = this.newTag;
|
||||||
this.editingTag = this.newTag;
|
this.editingTag = this.newTag;
|
||||||
this.addNew()(this.newTag);
|
this.addNew()(this.newTag);
|
||||||
@@ -71,7 +71,7 @@ angular.module('app.frontend')
|
|||||||
|
|
||||||
var originalTagName = "";
|
var originalTagName = "";
|
||||||
this.onTagTitleFocus = function(tag) {
|
this.onTagTitleFocus = function(tag) {
|
||||||
originalTagName = tag.content.title;
|
originalTagName = tag.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tagTitleDidChange = function(tag) {
|
this.tagTitleDidChange = function(tag) {
|
||||||
@@ -80,14 +80,14 @@ angular.module('app.frontend')
|
|||||||
|
|
||||||
this.saveTag = function($event, tag) {
|
this.saveTag = function($event, tag) {
|
||||||
this.editingTag = null;
|
this.editingTag = null;
|
||||||
if(tag.content.title.length == 0) {
|
if(tag.title.length == 0) {
|
||||||
tag.content.title = originalTagName;
|
tag.title = originalTagName;
|
||||||
originalTagName = "";
|
originalTagName = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$event.target.blur();
|
$event.target.blur();
|
||||||
if(!tag.content.title || tag.content.title.length == 0) {
|
if(!tag.title || tag.title.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +1,14 @@
|
|||||||
class Item {
|
class Item {
|
||||||
|
|
||||||
constructor(json_obj) {
|
constructor(json_obj) {
|
||||||
|
|
||||||
var content;
|
this.updateFromJSON(json_obj);
|
||||||
|
|
||||||
Object.defineProperty(this, "content", {
|
this.observers = [];
|
||||||
get: function() {
|
|
||||||
return content;
|
|
||||||
},
|
|
||||||
set: function(value) {
|
|
||||||
var finalValue = value;
|
|
||||||
|
|
||||||
if(typeof value === 'string') {
|
|
||||||
try {
|
|
||||||
var decodedValue = JSON.parse(value);
|
|
||||||
finalValue = decodedValue;
|
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
finalValue = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
content = finalValue;
|
|
||||||
},
|
|
||||||
enumerable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
_.merge(this, json_obj);
|
|
||||||
|
|
||||||
if(this.created_at) {
|
|
||||||
this.created_at = new Date(this.created_at);
|
|
||||||
this.updated_at = new Date(this.updated_at);
|
|
||||||
} else {
|
|
||||||
this.created_at = new Date();
|
|
||||||
this.updated_at = new Date();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!this.uuid) {
|
if(!this.uuid) {
|
||||||
this.uuid = Neeto.crypto.generateUUID();
|
this.uuid = Neeto.crypto.generateUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setContentRaw = function(rawContent) {
|
|
||||||
content = rawContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!this.content) {
|
|
||||||
this.content = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!this.content.references) {
|
|
||||||
this.content.references = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static sortItemsByDate(items) {
|
static sortItemsByDate(items) {
|
||||||
@@ -57,31 +17,90 @@ class Item {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addReference(reference) {
|
get contentObject() {
|
||||||
this.content.references.push(reference);
|
if(!this.content) {
|
||||||
this.content.references = _.uniq(this.content.references);
|
return {};
|
||||||
this.updateReferencesLocalMapping();
|
}
|
||||||
|
|
||||||
|
if(this.content !== null && typeof this.content === 'object') {
|
||||||
|
// this is the case when mapping localStorage content, in which case the content is already parsed
|
||||||
|
return this.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(this.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeReference(reference) {
|
updateFromJSON(json) {
|
||||||
_.remove(this.content.references, _.find(this.content.references, {uuid: reference.uuid}));
|
_.merge(this, json);
|
||||||
this.updateReferencesLocalMapping();
|
if(this.created_at) {
|
||||||
|
this.created_at = new Date(this.created_at);
|
||||||
|
this.updated_at = new Date(this.updated_at);
|
||||||
|
} else {
|
||||||
|
this.created_at = new Date();
|
||||||
|
this.updated_at = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(json.content) {
|
||||||
|
this.mapContentToLocalProperties(this.contentObject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
referencesMatchingContentType(contentType) {
|
setDirty(dirty) {
|
||||||
return this.content.references.filter(function(reference){
|
this.dirty = dirty;
|
||||||
return reference.content_type == contentType;
|
|
||||||
});
|
if(dirty) {
|
||||||
|
this.notifyObserversOfChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addObserver(observer, callback) {
|
||||||
|
if(!_.find(this.observers, observer)) {
|
||||||
|
this.observers.push({observer: observer, callback: callback});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeObserver(observer) {
|
||||||
|
_.remove(this.observers, {observer: observer})
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyObserversOfChange() {
|
||||||
|
for(var observer of this.observers) {
|
||||||
|
observer.callback(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapContentToLocalProperties(contentObj) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
createContentJSONFromProperties() {
|
||||||
|
return this.structureParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
referenceParams() {
|
||||||
|
// must override
|
||||||
|
}
|
||||||
|
|
||||||
|
structureParams() {
|
||||||
|
return {references: this.referenceParams()}
|
||||||
|
}
|
||||||
|
|
||||||
|
addItemAsRelationship(item) {
|
||||||
|
// must override
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItemAsRelationship(item) {
|
||||||
|
// must override
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAllRelationships() {
|
||||||
|
// must override
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeMetadataFromItem(item) {
|
mergeMetadataFromItem(item) {
|
||||||
_.merge(this, _.omit(item, ["content"]));
|
_.merge(this, _.omit(item, ["content"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateReferencesLocalMapping() {
|
|
||||||
// should be overriden to manage local properties
|
|
||||||
}
|
|
||||||
|
|
||||||
referencesAffectedBySharingChange() {
|
referencesAffectedBySharingChange() {
|
||||||
// should be overriden to determine which references should be decrypted/encrypted
|
// should be overriden to determine which references should be decrypted/encrypted
|
||||||
return null;
|
return null;
|
||||||
@@ -92,7 +111,7 @@ class Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isEncrypted() {
|
isEncrypted() {
|
||||||
return this.encryptionEnabled() && typeof this.content === 'string' ? true : false;
|
return this.encryptionEnabled() && this.content.substring(0, 3) === '001' ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptionEnabled() {
|
encryptionEnabled() {
|
||||||
|
|||||||
102
app/assets/javascripts/app/frontend/models/app/extension.js
Normal file
102
app/assets/javascripts/app/frontend/models/app/extension.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
class Action {
|
||||||
|
constructor(json) {
|
||||||
|
_.merge(this, json);
|
||||||
|
this.running = false; // in case running=true was synced with server since model is uploaded nondiscriminatory
|
||||||
|
this.error = false;
|
||||||
|
if(this.lastExecuted) {
|
||||||
|
// is string
|
||||||
|
this.lastExecuted = new Date(this.lastExecuted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get permissionsString() {
|
||||||
|
if(!this.permissions) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
var permission = this.permissions.charAt(0).toUpperCase() + this.permissions.slice(1); // capitalize first letter
|
||||||
|
permission += ": ";
|
||||||
|
for(var contentType of this.content_types) {
|
||||||
|
if(contentType == "*") {
|
||||||
|
permission += "All items";
|
||||||
|
} else {
|
||||||
|
permission += contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
permission += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
return permission;
|
||||||
|
}
|
||||||
|
|
||||||
|
get encryptionModeString() {
|
||||||
|
if(this.verb != "post") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var encryptionMode = "This action accepts data ";
|
||||||
|
if(this.accepts_encrypted && this.accepts_decrypted) {
|
||||||
|
encryptionMode += "encrypted or decrypted.";
|
||||||
|
} else {
|
||||||
|
if(this.accepts_encrypted) {
|
||||||
|
encryptionMode += "encrypted.";
|
||||||
|
} else {
|
||||||
|
encryptionMode += "decrypted.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return encryptionMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Extension extends Item {
|
||||||
|
constructor(json) {
|
||||||
|
super(json);
|
||||||
|
_.merge(this, json);
|
||||||
|
|
||||||
|
this.encrypted = true;
|
||||||
|
this.content_type = "Extension";
|
||||||
|
}
|
||||||
|
|
||||||
|
actionsInGlobalContext() {
|
||||||
|
return this.actions.filter(function(action){
|
||||||
|
return action.context == "global";
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
actionsWithContextForItem(item) {
|
||||||
|
return this.actions.filter(function(action){
|
||||||
|
return action.context == item.content_type || action.context == "Item";
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
mapContentToLocalProperties(contentObject) {
|
||||||
|
super.mapContentToLocalProperties(contentObject)
|
||||||
|
this.name = contentObject.name;
|
||||||
|
this.url = contentObject.url;
|
||||||
|
this.actions = contentObject.actions.map(function(action){
|
||||||
|
return new Action(action);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFromExternalResponseItem(externalResponseItem) {
|
||||||
|
_.merge(this, externalResponseItem);
|
||||||
|
this.actions = externalResponseItem.actions.map(function(action){
|
||||||
|
return new Action(action);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
referenceParams() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
structureParams() {
|
||||||
|
var params = {
|
||||||
|
name: this.name,
|
||||||
|
url: this.url,
|
||||||
|
actions: this.actions
|
||||||
|
};
|
||||||
|
|
||||||
|
_.merge(params, super.structureParams());
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,14 +6,54 @@ class Note extends Item {
|
|||||||
if(!this.tags) {
|
if(!this.tags) {
|
||||||
this.tags = [];
|
this.tags = [];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!this.content.title) {
|
mapContentToLocalProperties(contentObject) {
|
||||||
this.content.title = "";
|
super.mapContentToLocalProperties(contentObject)
|
||||||
}
|
this.title = contentObject.title;
|
||||||
|
this.text = contentObject.text;
|
||||||
|
}
|
||||||
|
|
||||||
if(!this.content.text) {
|
referenceParams() {
|
||||||
this.content.text = "";
|
var references = _.map(this.tags, function(tag){
|
||||||
|
return {uuid: tag.uuid, content_type: tag.content_type};
|
||||||
|
})
|
||||||
|
|
||||||
|
return references;
|
||||||
|
}
|
||||||
|
|
||||||
|
structureParams() {
|
||||||
|
var params = {
|
||||||
|
title: this.title,
|
||||||
|
text: this.text
|
||||||
|
};
|
||||||
|
|
||||||
|
_.merge(params, super.structureParams());
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
addItemAsRelationship(item) {
|
||||||
|
if(item.content_type == "Tag") {
|
||||||
|
if(!_.find(this.tags, item)) {
|
||||||
|
this.tags.push(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
super.addItemAsRelationship(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItemAsRelationship(item) {
|
||||||
|
if(item.content_type == "Tag") {
|
||||||
|
_.pull(this.tags, item);
|
||||||
|
}
|
||||||
|
super.removeItemAsRelationship(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAllRelationships() {
|
||||||
|
this.tags.forEach(function(tag){
|
||||||
|
_.pull(tag.notes, this);
|
||||||
|
tag.setDirty(true);
|
||||||
|
}.bind(this))
|
||||||
|
this.tags = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
static filterDummyNotes(notes) {
|
static filterDummyNotes(notes) {
|
||||||
@@ -21,11 +61,6 @@ class Note extends Item {
|
|||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateReferencesLocalMapping() {
|
|
||||||
super.updateReferencesLocalMapping();
|
|
||||||
this.tags = this.referencesMatchingContentType("Tag");
|
|
||||||
}
|
|
||||||
|
|
||||||
referencesAffectedBySharingChange() {
|
referencesAffectedBySharingChange() {
|
||||||
return super.referencesAffectedBySharingChange();
|
return super.referencesAffectedBySharingChange();
|
||||||
}
|
}
|
||||||
@@ -39,6 +74,14 @@ class Note extends Item {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
safeText() {
|
||||||
|
return this.text || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
safeTitle() {
|
||||||
|
return this.title || "";
|
||||||
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {uuid: this.uuid}
|
return {uuid: this.uuid}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,22 +6,60 @@ class Tag extends Item {
|
|||||||
if(!this.notes) {
|
if(!this.notes) {
|
||||||
this.notes = [];
|
this.notes = [];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!this.content.title) {
|
mapContentToLocalProperties(contentObject) {
|
||||||
this.content.title = "";
|
super.mapContentToLocalProperties(contentObject)
|
||||||
|
this.title = contentObject.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
referenceParams() {
|
||||||
|
var references = _.map(this.notes, function(note){
|
||||||
|
return {uuid: note.uuid, content_type: note.content_type};
|
||||||
|
})
|
||||||
|
|
||||||
|
return references;
|
||||||
|
}
|
||||||
|
|
||||||
|
structureParams() {
|
||||||
|
var params = {
|
||||||
|
title: this.title
|
||||||
|
};
|
||||||
|
|
||||||
|
_.merge(params, super.structureParams());
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
addItemAsRelationship(item) {
|
||||||
|
if(item.content_type == "Note") {
|
||||||
|
if(!_.find(this.notes, item)) {
|
||||||
|
this.notes.unshift(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
super.addItemAsRelationship(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItemAsRelationship(item) {
|
||||||
|
if(item.content_type == "Note") {
|
||||||
|
_.pull(this.notes, item);
|
||||||
|
}
|
||||||
|
super.removeItemAsRelationship(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAllRelationships() {
|
||||||
|
this.notes.forEach(function(note){
|
||||||
|
_.pull(note.tags, this);
|
||||||
|
note.setDirty(true);
|
||||||
|
}.bind(this))
|
||||||
|
|
||||||
|
this.notes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
get content_type() {
|
get content_type() {
|
||||||
return "Tag";
|
return "Tag";
|
||||||
}
|
}
|
||||||
|
|
||||||
updateReferencesLocalMapping() {
|
|
||||||
super.updateReferencesLocalMapping();
|
|
||||||
this.notes = this.referencesMatchingContentType("Note");
|
|
||||||
}
|
|
||||||
|
|
||||||
referencesAffectedBySharingChange() {
|
referencesAffectedBySharingChange() {
|
||||||
return this.referencesMatchingContentType("Note");
|
return this.notes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ angular.module('app.frontend')
|
|||||||
Auth
|
Auth
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
this.isUserSignedIn = function() {
|
||||||
|
return this.user.email && this.retrieveMk();
|
||||||
|
}
|
||||||
|
|
||||||
this.getAuthParamsForEmail = function(email, callback) {
|
this.getAuthParamsForEmail = function(email, callback) {
|
||||||
var request = Restangular.one("auth", "params");
|
var request = Restangular.one("auth", "params");
|
||||||
request.get({email: email}).then(function(response){
|
request.get({email: email}).then(function(response){
|
||||||
@@ -218,19 +222,23 @@ angular.module('app.frontend')
|
|||||||
|
|
||||||
this.syncWithOptions = function(callback, options = {}) {
|
this.syncWithOptions = function(callback, options = {}) {
|
||||||
if(!this.user.uuid) {
|
if(!this.user.uuid) {
|
||||||
this.writeItemsToLocalStorage();
|
this.writeItemsToLocalStorage(function(responseItems){
|
||||||
if(callback) {
|
modelManager.clearDirtyItems();
|
||||||
callback();
|
if(callback) {
|
||||||
}
|
callback();
|
||||||
|
}
|
||||||
|
}.bind(this))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dirtyItems = modelManager.dirtyItems;
|
var dirtyItems = modelManager.getDirtyItems();
|
||||||
var request = Restangular.one("items/sync");
|
var request = Restangular.one("items/sync");
|
||||||
request.items = _.map(dirtyItems, function(item){
|
request.items = _.map(dirtyItems, function(item){
|
||||||
return this.createRequestParamsForItem(item, options.additionalFields);
|
return this.createRequestParamsForItem(item, options.additionalFields);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
|
// console.log("syncing items", request.items);
|
||||||
|
|
||||||
if(this.syncToken) {
|
if(this.syncToken) {
|
||||||
request.sync_token = this.syncToken;
|
request.sync_token = this.syncToken;
|
||||||
}
|
}
|
||||||
@@ -251,7 +259,7 @@ angular.module('app.frontend')
|
|||||||
}.bind(this))
|
}.bind(this))
|
||||||
.catch(function(response){
|
.catch(function(response){
|
||||||
console.log("Sync error: ", response);
|
console.log("Sync error: ", response);
|
||||||
callback(null);
|
callback({error: "Sync error"});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,14 +276,21 @@ angular.module('app.frontend')
|
|||||||
return this.paramsForItem(item, !item.isPublic(), additionalFields, false);
|
return this.paramsForItem(item, !item.isPublic(), additionalFields, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.paramsForExportFile = function(item, encrypted) {
|
||||||
|
return _.omit(this.paramsForItem(item, encrypted, ["created_at", "updated_at"], true), ["deleted"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.paramsForExtension = function(item, encrypted) {
|
||||||
|
return _.omit(this.paramsForItem(item, encrypted, ["created_at", "updated_at"], true), ["deleted"]);
|
||||||
|
}
|
||||||
|
|
||||||
this.paramsForItem = function(item, encrypted, additionalFields, forExportFile) {
|
this.paramsForItem = function(item, encrypted, additionalFields, forExportFile) {
|
||||||
var itemCopy = _.cloneDeep(item);
|
var itemCopy = _.cloneDeep(item);
|
||||||
|
|
||||||
var params = {uuid: item.uuid, content_type: item.content_type, presentation_name: item.presentation_name, deleted: item.deleted};
|
console.assert(!item.dummy, "Item is dummy, should not have gotten here.", item.dummy)
|
||||||
|
|
||||||
itemCopy.content.references = _.map(itemCopy.content.references, function(reference){
|
var params = {uuid: item.uuid, content_type: item.content_type,
|
||||||
return {uuid: reference.uuid, content_type: reference.content_type};
|
presentation_name: item.presentation_name, deleted: item.deleted};
|
||||||
})
|
|
||||||
|
|
||||||
if(encrypted) {
|
if(encrypted) {
|
||||||
this.encryptSingleItem(itemCopy, this.retrieveMk());
|
this.encryptSingleItem(itemCopy, this.retrieveMk());
|
||||||
@@ -284,7 +299,7 @@ angular.module('app.frontend')
|
|||||||
params.auth_hash = itemCopy.auth_hash;
|
params.auth_hash = itemCopy.auth_hash;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
params.content = forExportFile ? itemCopy.content : "000" + Neeto.crypto.base64(JSON.stringify(itemCopy.content));
|
params.content = forExportFile ? itemCopy.createContentJSONFromProperties() : "000" + Neeto.crypto.base64(JSON.stringify(itemCopy.createContentJSONFromProperties()));
|
||||||
if(!forExportFile) {
|
if(!forExportFile) {
|
||||||
params.enc_item_key = null;
|
params.enc_item_key = null;
|
||||||
params.auth_hash = null;
|
params.auth_hash = null;
|
||||||
@@ -298,13 +313,6 @@ angular.module('app.frontend')
|
|||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.deleteItem = function(item, callback) {
|
|
||||||
item.deleted = true;
|
|
||||||
modelManager.addDirtyItems([item]);
|
|
||||||
this.sync(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.shareItem = function(item, callback) {
|
this.shareItem = function(item, callback) {
|
||||||
if(!this.user.uuid) {
|
if(!this.user.uuid) {
|
||||||
alert("You must be signed in to share.");
|
alert("You must be signed in to share.");
|
||||||
@@ -314,7 +322,9 @@ angular.module('app.frontend')
|
|||||||
var shareFn = function() {
|
var shareFn = function() {
|
||||||
item.presentation_name = "_auto_";
|
item.presentation_name = "_auto_";
|
||||||
var needsUpdate = [item].concat(item.referencesAffectedBySharingChange() || []);
|
var needsUpdate = [item].concat(item.referencesAffectedBySharingChange() || []);
|
||||||
modelManager.addDirtyItems(needsUpdate);
|
needsUpdate.forEach(function(needingUpdate){
|
||||||
|
needingUpdate.setDirty(true);
|
||||||
|
})
|
||||||
this.sync();
|
this.sync();
|
||||||
}.bind(this)
|
}.bind(this)
|
||||||
|
|
||||||
@@ -339,7 +349,9 @@ angular.module('app.frontend')
|
|||||||
this.unshareItem = function(item, callback) {
|
this.unshareItem = function(item, callback) {
|
||||||
item.presentation_name = null;
|
item.presentation_name = null;
|
||||||
var needsUpdate = [item].concat(item.referencesAffectedBySharingChange() || []);
|
var needsUpdate = [item].concat(item.referencesAffectedBySharingChange() || []);
|
||||||
modelManager.addDirtyItems(needsUpdate);
|
needsUpdate.forEach(function(needingUpdate){
|
||||||
|
needingUpdate.setDirty(true);
|
||||||
|
})
|
||||||
this.sync(null);
|
this.sync(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,8 +361,12 @@ angular.module('app.frontend')
|
|||||||
|
|
||||||
this.importJSONData = function(jsonString, callback) {
|
this.importJSONData = function(jsonString, callback) {
|
||||||
var data = JSON.parse(jsonString);
|
var data = JSON.parse(jsonString);
|
||||||
|
console.log("importing data", data);
|
||||||
|
this.decryptItems(data.items);
|
||||||
modelManager.mapResponseItemsToLocalModels(data.items);
|
modelManager.mapResponseItemsToLocalModels(data.items);
|
||||||
modelManager.addDirtyItems(modelManager.items);
|
modelManager.allItems.forEach(function(item){
|
||||||
|
item.setDirty(true);
|
||||||
|
})
|
||||||
this.syncWithOptions(callback, {additionalFields: ["created_at", "updated_at"]});
|
this.syncWithOptions(callback, {additionalFields: ["created_at", "updated_at"]});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,7 +374,7 @@ angular.module('app.frontend')
|
|||||||
Export
|
Export
|
||||||
*/
|
*/
|
||||||
|
|
||||||
this.itemsDataFile = function() {
|
this.itemsDataFile = function(encrypted) {
|
||||||
var textFile = null;
|
var textFile = null;
|
||||||
var makeTextFile = function (text) {
|
var makeTextFile = function (text) {
|
||||||
var data = new Blob([text], {type: 'text/json'});
|
var data = new Blob([text], {type: 'text/json'});
|
||||||
@@ -375,8 +391,8 @@ angular.module('app.frontend')
|
|||||||
return textFile;
|
return textFile;
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
var items = _.map(modelManager.items, function(item){
|
var items = _.map(modelManager.allItemsMatchingTypes(["Tag", "Note"]), function(item){
|
||||||
return _.omit(this.paramsForItem(item, false, ["created_at", "updated_at"], true), ["deleted"]);
|
return this.paramsForExportFile(item, encrypted);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
@@ -398,7 +414,7 @@ angular.module('app.frontend')
|
|||||||
request.items.forEach(function(item){
|
request.items.forEach(function(item){
|
||||||
if(item.tag_id) {
|
if(item.tag_id) {
|
||||||
var tag = tags.filter(function(tag){return tag.uuid == item.tag_id})[0];
|
var tag = tags.filter(function(tag){return tag.uuid == item.tag_id})[0];
|
||||||
item.tag_name = tag.content.title;
|
item.tag_name = tag.title;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
request.post().then(function(response){
|
request.post().then(function(response){
|
||||||
@@ -416,11 +432,13 @@ angular.module('app.frontend')
|
|||||||
return JSON.parse(JSON.stringify(object));
|
return JSON.parse(JSON.stringify(object));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.writeItemsToLocalStorage = function() {
|
this.writeItemsToLocalStorage = function(callback) {
|
||||||
var items = _.map(modelManager.items, function(item){
|
var items = _.map(modelManager.allItems, function(item){
|
||||||
return this.paramsForItem(item, false, ["created_at", "updated_at"], true)
|
return this.paramsForItem(item, false, ["created_at", "updated_at"], false)
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
console.log("Writing items to local", items);
|
||||||
this.writeToLocalStorage('items', items);
|
this.writeToLocalStorage('items', items);
|
||||||
|
callback(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.writeToLocalStorage = function(key, value) {
|
this.writeToLocalStorage = function(key, value) {
|
||||||
@@ -430,7 +448,7 @@ angular.module('app.frontend')
|
|||||||
this.loadLocalItemsAndUser = function() {
|
this.loadLocalItemsAndUser = function() {
|
||||||
var user = {};
|
var user = {};
|
||||||
var items = JSON.parse(localStorage.getItem('items')) || [];
|
var items = JSON.parse(localStorage.getItem('items')) || [];
|
||||||
items = modelManager.mapResponseItemsToLocalModels(items);
|
items = this.handleItemsResponse(items, null);
|
||||||
Item.sortItemsByDate(items);
|
Item.sortItemsByDate(items);
|
||||||
user.items = items;
|
user.items = items;
|
||||||
user.shouldMerge = true;
|
user.shouldMerge = true;
|
||||||
@@ -454,7 +472,8 @@ angular.module('app.frontend')
|
|||||||
if(!draftString || draftString == 'undefined') {
|
if(!draftString || draftString == 'undefined') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new Note(JSON.parse(draftString));
|
var jsonObj = _.merge({content_type: "Note"}, JSON.parse(draftString));
|
||||||
|
return modelManager.createItem(jsonObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -489,7 +508,7 @@ angular.module('app.frontend')
|
|||||||
|
|
||||||
var ek = Neeto.crypto.firstHalfOfKey(item_key);
|
var ek = Neeto.crypto.firstHalfOfKey(item_key);
|
||||||
var ak = Neeto.crypto.secondHalfOfKey(item_key);
|
var ak = Neeto.crypto.secondHalfOfKey(item_key);
|
||||||
var encryptedContent = "001" + Neeto.crypto.encryptText(JSON.stringify(item.content), ek);
|
var encryptedContent = "001" + Neeto.crypto.encryptText(JSON.stringify(item.createContentJSONFromProperties()), ek);
|
||||||
var authHash = Neeto.crypto.hmac256(encryptedContent, ak);
|
var authHash = Neeto.crypto.hmac256(encryptedContent, ak);
|
||||||
|
|
||||||
item.content = encryptedContent;
|
item.content = encryptedContent;
|
||||||
@@ -518,13 +537,15 @@ angular.module('app.frontend')
|
|||||||
if(item.deleted == true) {
|
if(item.deleted == true) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
var isString = typeof item.content === 'string' || item.content instanceof String;
|
||||||
if(item.content.substring(0, 3) == "001" && item.enc_item_key) {
|
if(isString) {
|
||||||
// is encrypted
|
if(item.content.substring(0, 3) == "001" && item.enc_item_key) {
|
||||||
this.decryptSingleItem(item, masterKey);
|
// is encrypted
|
||||||
} else {
|
this.decryptSingleItem(item, masterKey);
|
||||||
// is base64 encoded
|
} else {
|
||||||
item.content = Neeto.crypto.base64Decode(item.content.substring(3, item.content.length))
|
// is base64 encoded
|
||||||
|
item.content = Neeto.crypto.base64Decode(item.content.substring(3, item.content.length))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
class ContextualExtensionsMenu {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.restrict = "E";
|
||||||
|
this.templateUrl = "frontend/directives/contextual-menu.html";
|
||||||
|
this.scope = {
|
||||||
|
item: "="
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
controller($scope, modelManager, extensionManager) {
|
||||||
|
$scope.extensions = extensionManager.extensionsInContextOfItem($scope.item);
|
||||||
|
|
||||||
|
$scope.executeAction = function(action, extension) {
|
||||||
|
action.running = true;
|
||||||
|
extensionManager.executeAction(action, extension, $scope.item, function(response){
|
||||||
|
action.running = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.accessTypeForExtension = function(extension) {
|
||||||
|
return extensionManager.extensionUsesEncryptedData(extension) ? "encrypted" : "decrypted";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module('app.frontend').directive('contextualExtensionsMenu', () => new ContextualExtensionsMenu);
|
||||||
285
app/assets/javascripts/app/services/extensionManager.js
Normal file
285
app/assets/javascripts/app/services/extensionManager.js
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
class ExtensionManager {
|
||||||
|
|
||||||
|
constructor(Restangular, modelManager, apiController) {
|
||||||
|
this.Restangular = Restangular;
|
||||||
|
this.modelManager = modelManager;
|
||||||
|
this.apiController = apiController;
|
||||||
|
this.enabledRepeatActionUrls = JSON.parse(localStorage.getItem("enabledRepeatActionUrls")) || [];
|
||||||
|
this.decryptedExtensions = JSON.parse(localStorage.getItem("decryptedExtensions")) || [];
|
||||||
|
|
||||||
|
modelManager.addItemSyncObserver("extensionManager", "Extension", function(items){
|
||||||
|
for (var ext of items) {
|
||||||
|
|
||||||
|
ext.encrypted = this.extensionUsesEncryptedData(ext);
|
||||||
|
|
||||||
|
for (var action of ext.actions) {
|
||||||
|
if(this.enabledRepeatActionUrls.includes(action.url)) {
|
||||||
|
this.enableRepeatAction(action, ext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
get extensions() {
|
||||||
|
return this.modelManager.extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
extensionsInContextOfItem(item) {
|
||||||
|
return this.extensions.filter(function(ext){
|
||||||
|
return ext.actionsWithContextForItem(item).length > 0;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
actionWithURL(url) {
|
||||||
|
for (var extension of this.extensions) {
|
||||||
|
return _.find(extension.actions, {url: url})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extensionUsesEncryptedData(extension) {
|
||||||
|
return !this.decryptedExtensions.includes(extension.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeExtensionEncryptionFormat(encrypted, extension) {
|
||||||
|
if(encrypted) {
|
||||||
|
_.pull(this.decryptedExtensions, extension.url);
|
||||||
|
} else {
|
||||||
|
this.decryptedExtensions.push(extension.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("decryptedExtensions", JSON.stringify(this.decryptedExtensions))
|
||||||
|
|
||||||
|
extension.encrypted = this.extensionUsesEncryptedData(extension);
|
||||||
|
|
||||||
|
console.log("ext with dec", this.decryptedExtensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
addExtension(url, callback) {
|
||||||
|
this.retrieveExtensionFromServer(url, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteExtension(extension) {
|
||||||
|
for(var action of extension.actions) {
|
||||||
|
_.pull(this.decryptedExtensions, extension);
|
||||||
|
if(action.repeat_mode) {
|
||||||
|
if(this.isRepeatActionEnabled(action)) {
|
||||||
|
this.disableRepeatAction(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.modelManager.setItemToBeDeleted(extension);
|
||||||
|
this.apiController.sync(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieveExtensionFromServer(url, callback) {
|
||||||
|
console.log("Registering URL", url);
|
||||||
|
this.Restangular.oneUrl(url, url).get().then(function(response){
|
||||||
|
var ext = this.handleExtensionLoadExternalResponseItem(url, response.plain());
|
||||||
|
if(callback) {
|
||||||
|
callback(ext);
|
||||||
|
}
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function(response){
|
||||||
|
console.log("Error registering extension", response);
|
||||||
|
callback(null);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleExtensionLoadExternalResponseItem(url, externalResponseItem) {
|
||||||
|
var extension = _.find(this.extensions, {url: url});
|
||||||
|
if(extension) {
|
||||||
|
extension.updateFromExternalResponseItem(externalResponseItem);
|
||||||
|
} else {
|
||||||
|
extension = new Extension(externalResponseItem);
|
||||||
|
extension.url = url;
|
||||||
|
extension.setDirty(true);
|
||||||
|
this.modelManager.addItem(extension);
|
||||||
|
this.apiController.sync(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshExtensionsFromServer() {
|
||||||
|
for (var url of this.enabledRepeatActionUrls) {
|
||||||
|
var action = this.actionWithURL(url);
|
||||||
|
if(action) {
|
||||||
|
this.disableRepeatAction(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var ext of this.extensions) {
|
||||||
|
this.retrieveExtensionFromServer(ext.url, function(extension){
|
||||||
|
extension.setDirty(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executeAction(action, extension, item, callback) {
|
||||||
|
|
||||||
|
if(this.extensionUsesEncryptedData(extension) && !this.apiController.isUserSignedIn()) {
|
||||||
|
alert("To send data encrypted, you must have an encryption key, and must therefore be signed in.");
|
||||||
|
callback(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action.verb) {
|
||||||
|
case "get": {
|
||||||
|
this.Restangular.oneUrl(action.url, action.url).get().then(function(response){
|
||||||
|
console.log("Execute action response", response);
|
||||||
|
action.error = false;
|
||||||
|
var items = response.items;
|
||||||
|
this.modelManager.mapResponseItemsToLocalModels(items);
|
||||||
|
callback(items);
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function(response){
|
||||||
|
action.error = true;
|
||||||
|
})
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "show": {
|
||||||
|
var win = window.open(action.url, '_blank');
|
||||||
|
win.focus();
|
||||||
|
callback();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "post": {
|
||||||
|
var params = {};
|
||||||
|
|
||||||
|
if(action.all) {
|
||||||
|
var items = this.modelManager.allItemsMatchingTypes(action.content_types);
|
||||||
|
params.items = items.map(function(item){
|
||||||
|
var params = this.outgoingParamsForItem(item, extension);
|
||||||
|
return params;
|
||||||
|
}.bind(this))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
params.item = this.outgoingParamsForItem(item, extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.performPost(action, extension, params, function(response){
|
||||||
|
callback(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action.lastExecuted = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
isRepeatActionEnabled(action) {
|
||||||
|
return this.enabledRepeatActionUrls.includes(action.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
disableRepeatAction(action, extension) {
|
||||||
|
console.log("Disabling action", action);
|
||||||
|
|
||||||
|
_.pull(this.enabledRepeatActionUrls, action.url);
|
||||||
|
localStorage.setItem("enabledRepeatActionUrls", JSON.stringify(this.enabledRepeatActionUrls));
|
||||||
|
this.modelManager.removeItemChangeObserver(action.url);
|
||||||
|
|
||||||
|
console.assert(this.isRepeatActionEnabled(action) == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
enableRepeatAction(action, extension) {
|
||||||
|
// console.log("Enabling repeat action", action);
|
||||||
|
|
||||||
|
if(!_.find(this.enabledRepeatActionUrls, action.url)) {
|
||||||
|
this.enabledRepeatActionUrls.push(action.url);
|
||||||
|
localStorage.setItem("enabledRepeatActionUrls", JSON.stringify(this.enabledRepeatActionUrls));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(action.repeat_mode) {
|
||||||
|
|
||||||
|
if(action.repeat_mode == "watch") {
|
||||||
|
this.modelManager.addItemChangeObserver(action.url, action.content_types, function(changedItems){
|
||||||
|
this.triggerWatchAction(action, extension, changedItems);
|
||||||
|
}.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
if(action.repeat_mode == "loop") {
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queueAction(action, extension, delay, changedItems) {
|
||||||
|
this.actionQueue = this.actionQueue || [];
|
||||||
|
if(_.find(this.actionQueue, {url: action.url})) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Successfully queued", action, this.actionQueue.length);
|
||||||
|
this.actionQueue.push(action);
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
console.log("Performing queued action", action);
|
||||||
|
this.triggerWatchAction(action, extension, changedItems);
|
||||||
|
_.pull(this.actionQueue, action);
|
||||||
|
}.bind(this), delay * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerWatchAction(action, extension, changedItems) {
|
||||||
|
if(action.repeat_timeout > 0) {
|
||||||
|
var lastExecuted = action.lastExecuted;
|
||||||
|
var diffInSeconds = (new Date() - lastExecuted)/1000;
|
||||||
|
if(diffInSeconds < action.repeat_timeout) {
|
||||||
|
var delay = action.repeat_timeout - diffInSeconds;
|
||||||
|
this.queueAction(action, extension, delay, changedItems);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action.lastExecuted = new Date();
|
||||||
|
|
||||||
|
console.log("Performing action immediately", action);
|
||||||
|
|
||||||
|
if(action.verb == "post") {
|
||||||
|
var params = {};
|
||||||
|
params.items = changedItems.map(function(item){
|
||||||
|
var params = this.outgoingParamsForItem(item, extension);
|
||||||
|
return params;
|
||||||
|
}.bind(this))
|
||||||
|
this.performPost(action, extension, params, null);
|
||||||
|
} else {
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outgoingParamsForItem(item, extension) {
|
||||||
|
return this.apiController.paramsForExtension(item, this.extensionUsesEncryptedData(extension));
|
||||||
|
}
|
||||||
|
|
||||||
|
performPost(action, extension, params, callback) {
|
||||||
|
var request = this.Restangular.oneUrl(action.url, action.url);
|
||||||
|
_.merge(request, params);
|
||||||
|
|
||||||
|
request.post().then(function(response){
|
||||||
|
action.error = false;
|
||||||
|
if(callback) {
|
||||||
|
callback(response.plain());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(response){
|
||||||
|
action.error = true;
|
||||||
|
console.log("Action error response:", response);
|
||||||
|
if(callback) {
|
||||||
|
callback({error: "Request error"});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module('app.frontend').service('extensionManager', ExtensionManager);
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
class ItemManager {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this._items = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
get items() {
|
|
||||||
return this._items;
|
|
||||||
}
|
|
||||||
|
|
||||||
findItem(itemId) {
|
|
||||||
return _.find(this.items, {uuid: itemId});
|
|
||||||
}
|
|
||||||
|
|
||||||
addItems(items) {
|
|
||||||
this._items = _.uniq(this.items.concat(items));
|
|
||||||
}
|
|
||||||
|
|
||||||
mapResponseItemsToLocalModels(items) {
|
|
||||||
return this.mapResponseItemsToLocalModelsOmittingFields(items, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
mapResponseItemsToLocalModelsOmittingFields(items, omitFields) {
|
|
||||||
var models = []
|
|
||||||
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.deleteItem(item)
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(item) {
|
|
||||||
_.merge(item, json_obj);
|
|
||||||
} else {
|
|
||||||
item = this.createItem(json_obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
models.push(item)
|
|
||||||
}
|
|
||||||
this.addItems(models)
|
|
||||||
this.resolveReferences()
|
|
||||||
return models;
|
|
||||||
}
|
|
||||||
|
|
||||||
createItem(json_obj) {
|
|
||||||
if(json_obj.content_type == "Note") {
|
|
||||||
return new Note(json_obj);
|
|
||||||
} else if(json_obj.content_type == "Tag") {
|
|
||||||
return new Tag(json_obj);
|
|
||||||
} else {
|
|
||||||
return new Item(json_obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveReferences() {
|
|
||||||
this.items.forEach(function(item){
|
|
||||||
// build out references, safely handle broken references
|
|
||||||
item.content.references = _.reduce(item.content.references, function(accumulator, reference){
|
|
||||||
var item = this.findItem(reference.uuid);
|
|
||||||
if(item) {
|
|
||||||
accumulator.push(item);
|
|
||||||
}
|
|
||||||
return accumulator;
|
|
||||||
}.bind(this), []);
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
itemsForContentType(contentType) {
|
|
||||||
return this.items.filter(function(item){
|
|
||||||
return item.content_type == contentType;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addItem(item) {
|
|
||||||
if(!this.findItem(item.uuid)) {
|
|
||||||
this.items.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns dirty item references that need saving
|
|
||||||
deleteItem(item) {
|
|
||||||
var dirty = [];
|
|
||||||
_.remove(this.items, item);
|
|
||||||
var length = item.content.references.length;
|
|
||||||
// note that references are deleted in this for loop, so you have to be careful how you iterate
|
|
||||||
for(var i = 0; i < length; i++) {
|
|
||||||
var referencedItem = item.content.references[0];
|
|
||||||
// console.log("removing references between items", referencedItem, item);
|
|
||||||
var _dirty = this.removeReferencesBetweenItems(referencedItem, item);
|
|
||||||
dirty = dirty.concat(_dirty);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dirty;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeReferencesBetweenItems(itemOne, itemTwo) {
|
|
||||||
itemOne.removeReference(itemTwo);
|
|
||||||
itemTwo.removeReference(itemOne);
|
|
||||||
return [itemOne, itemTwo];
|
|
||||||
}
|
|
||||||
|
|
||||||
createReferencesBetweenItems(itemOne, itemTwo) {
|
|
||||||
itemOne.addReference(itemTwo);
|
|
||||||
itemTwo.addReference(itemOne);
|
|
||||||
return [itemOne, itemTwo];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
angular.module('app.frontend').service('itemManager', ItemManager);
|
|
||||||
@@ -1,101 +1,239 @@
|
|||||||
class ModelManager extends ItemManager {
|
class ModelManager {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
|
||||||
this.notes = [];
|
this.notes = [];
|
||||||
this.tags = [];
|
this.tags = [];
|
||||||
this.dirtyItems = [];
|
this.itemSyncObservers = [];
|
||||||
|
this.itemChangeObservers = [];
|
||||||
|
this.items = [];
|
||||||
|
this._extensions = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveReferences() {
|
get allItems() {
|
||||||
super.resolveReferences()
|
return this.items.filter(function(item){
|
||||||
|
return !item.dummy;
|
||||||
this.notes.push.apply(this.notes, _.difference(this.itemsForContentType("Note"), this.notes));
|
|
||||||
Item.sortItemsByDate(this.notes);
|
|
||||||
this.notes.forEach(function(note){
|
|
||||||
note.updateReferencesLocalMapping();
|
|
||||||
})
|
|
||||||
|
|
||||||
this.tags.push.apply(this.tags, _.difference(this.itemsForContentType("Tag"), this.tags));
|
|
||||||
this.tags.forEach(function(tag){
|
|
||||||
tag.updateReferencesLocalMapping();
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addDirtyItems(items) {
|
get extensions() {
|
||||||
if(!(items instanceof Array)) {
|
return this._extensions.filter(function(ext){
|
||||||
items = [items];
|
return !ext.deleted;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
allItemsMatchingTypes(contentTypes) {
|
||||||
|
return this.items.filter(function(item){
|
||||||
|
return (contentTypes.includes(item.content_type) || contentTypes.includes("*")) && !item.dummy;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
findItem(itemId) {
|
||||||
|
return _.find(this.items, {uuid: itemId});
|
||||||
|
}
|
||||||
|
|
||||||
|
mapResponseItemsToLocalModels(items) {
|
||||||
|
return this.mapResponseItemsToLocalModelsOmittingFields(items, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
mapResponseItemsToLocalModelsOmittingFields(items, omitFields) {
|
||||||
|
var models = []
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
_.omit(json_obj, omitFields);
|
||||||
|
|
||||||
|
if(!item) {
|
||||||
|
item = this.createItem(json_obj);
|
||||||
|
} else {
|
||||||
|
item.updateFromJSON(json_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addItem(item);
|
||||||
|
|
||||||
|
if(json_obj.content) {
|
||||||
|
this.resolveReferencesForItem(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
models.push(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dirtyItems = this.dirtyItems.concat(items);
|
this.notifySyncObserversOfModels(models);
|
||||||
this.dirtyItems = _.uniq(this.dirtyItems);
|
|
||||||
|
this.sortItems();
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySyncObserversOfModels(models) {
|
||||||
|
for(var observer of this.itemSyncObservers) {
|
||||||
|
var relevantItems = models.filter(function(item){return item.content_type == observer.type});
|
||||||
|
if(relevantItems.length > 0) {
|
||||||
|
observer.callback(relevantItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyItemChangeObserversOfModels(models) {
|
||||||
|
for(var observer of this.itemChangeObservers) {
|
||||||
|
var relevantItems = models.filter(function(item){
|
||||||
|
return observer.content_types.includes(item.content_type) || observer.content_types.includes("*");
|
||||||
|
});
|
||||||
|
|
||||||
|
if(relevantItems.length > 0) {
|
||||||
|
observer.callback(relevantItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createItem(json_obj) {
|
||||||
|
var item;
|
||||||
|
if(json_obj.content_type == "Note") {
|
||||||
|
item = new Note(json_obj);
|
||||||
|
} else if(json_obj.content_type == "Tag") {
|
||||||
|
item = new Tag(json_obj);
|
||||||
|
} else if(json_obj.content_type == "Extension") {
|
||||||
|
item = new Extension(json_obj);
|
||||||
|
} else {
|
||||||
|
item = new Item(json_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.addObserver(this, function(changedItem){
|
||||||
|
this.notifyItemChangeObserversOfModels([changedItem]);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
addItems(items) {
|
||||||
|
this.items = _.uniq(this.items.concat(items));
|
||||||
|
|
||||||
|
items.forEach(function(item){
|
||||||
|
if(item.content_type == "Tag") {
|
||||||
|
if(!_.find(this.tags, {uuid: item.uuid})) {
|
||||||
|
this.tags.unshift(item);
|
||||||
|
}
|
||||||
|
} else if(item.content_type == "Note") {
|
||||||
|
if(!_.find(this.notes, {uuid: item.uuid})) {
|
||||||
|
this.notes.unshift(item);
|
||||||
|
}
|
||||||
|
} else if(item.content_type == "Extension") {
|
||||||
|
if(!_.find(this._extensions, {uuid: item.uuid})) {
|
||||||
|
this._extensions.unshift(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
addItem(item) {
|
||||||
|
this.addItems([item])
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsForContentType(contentType) {
|
||||||
|
return this.items.filter(function(item){
|
||||||
|
return item.content_type == contentType;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveReferencesForItem(item) {
|
||||||
|
var contentObject = item.contentObject;
|
||||||
|
if(!contentObject.references) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var reference of contentObject.references) {
|
||||||
|
var referencedItem = this.findItem(reference.uuid);
|
||||||
|
if(referencedItem) {
|
||||||
|
item.addItemAsRelationship(referencedItem);
|
||||||
|
referencedItem.addItemAsRelationship(item);
|
||||||
|
} else {
|
||||||
|
// console.log("Unable to find item:", reference.uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortItems() {
|
||||||
|
Item.sortItemsByDate(this.notes);
|
||||||
|
|
||||||
|
this.tags.forEach(function(tag){
|
||||||
|
Item.sortItemsByDate(tag.notes);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
addItemSyncObserver(id, type, callback) {
|
||||||
|
this.itemSyncObservers.push({id: id, type: type, callback: callback});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItemSyncObserver(id) {
|
||||||
|
_.remove(this.itemSyncObservers, _.find(this.itemSyncObservers, {id: id}));
|
||||||
|
}
|
||||||
|
|
||||||
|
addItemChangeObserver(id, content_types, callback) {
|
||||||
|
this.itemChangeObservers.push({id: id, content_types: content_types, callback: callback});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItemChangeObserver(id) {
|
||||||
|
_.remove(this.itemChangeObservers, _.find(this.itemChangeObservers, {id: id}));
|
||||||
}
|
}
|
||||||
|
|
||||||
get filteredNotes() {
|
get filteredNotes() {
|
||||||
return Note.filterDummyNotes(this.notes);
|
return Note.filterDummyNotes(this.notes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDirtyItems() {
|
||||||
|
return this.items.filter(function(item){return item.dirty == true && !item.dummy})
|
||||||
|
}
|
||||||
|
|
||||||
clearDirtyItems() {
|
clearDirtyItems() {
|
||||||
this.dirtyItems = [];
|
this.getDirtyItems().forEach(function(item){
|
||||||
|
item.setDirty(false);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addNote(note) {
|
setItemToBeDeleted(item) {
|
||||||
if(!_.find(this.notes, {uuid: note.uuid})) {
|
item.deleted = true;
|
||||||
this.notes.unshift(note);
|
if(!item.dummy) {
|
||||||
this.addItem(note);
|
item.setDirty(true);
|
||||||
|
}
|
||||||
|
item.removeAllRelationships();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItemLocally(item) {
|
||||||
|
_.pull(this.items, item);
|
||||||
|
|
||||||
|
if(item.content_type == "Tag") {
|
||||||
|
_.pull(this.tags, item);
|
||||||
|
} else if(item.content_type == "Note") {
|
||||||
|
_.pull(this.notes, item);
|
||||||
|
} else if(item.content_type == "Extension") {
|
||||||
|
_.pull(this._extensions, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addTag(tag) {
|
/*
|
||||||
this.tags.unshift(tag);
|
Relationships
|
||||||
this.addItem(tag);
|
*/
|
||||||
|
|
||||||
|
createRelationshipBetweenItems(itemOne, itemTwo) {
|
||||||
|
itemOne.addItemAsRelationship(itemTwo);
|
||||||
|
itemTwo.addItemAsRelationship(itemOne);
|
||||||
|
|
||||||
|
itemOne.setDirty(true);
|
||||||
|
itemTwo.setDirty(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
addTagToNote(tag, note) {
|
removeRelationshipBetweenItems(itemOne, itemTwo) {
|
||||||
var dirty = this.createReferencesBetweenItems(tag, note);
|
itemOne.removeItemAsRelationship(itemTwo);
|
||||||
this.refreshRelationshipsForTag(tag);
|
itemTwo.removeItemAsRelationship(itemOne);
|
||||||
this.refreshRelationshipsForNote(note);
|
|
||||||
this.addDirtyItems(dirty);
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshRelationshipsForTag(tag) {
|
itemOne.setDirty(true);
|
||||||
tag.notes = tag.referencesMatchingContentType("Note");
|
itemTwo.setDirty(true);
|
||||||
Item.sortItemsByDate(tag.notes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshRelationshipsForNote(note) {
|
|
||||||
note.tags = note.referencesMatchingContentType("Tag");
|
|
||||||
}
|
|
||||||
|
|
||||||
removeTagFromNote(tag, note) {
|
|
||||||
var dirty = this.removeReferencesBetweenItems(tag, note);
|
|
||||||
this.addDirtyItems(dirty);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteItem(item) {
|
|
||||||
var dirty = super.deleteItem(item);
|
|
||||||
if(item.content_type == "Note") {
|
|
||||||
_.remove(this.notes, item);
|
|
||||||
} else if(item.content_type == "Tag") {
|
|
||||||
_.remove(this.tags, item);
|
|
||||||
}
|
|
||||||
return dirty;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteNote(note) {
|
|
||||||
var dirty = this.deleteItem(note);
|
|
||||||
_.remove(this.notes, note);
|
|
||||||
if(!note.dummy) {
|
|
||||||
this.addDirtyItems(dirty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteTag(tag) {
|
|
||||||
var dirty = this.deleteItem(tag);
|
|
||||||
_.remove(this.tags, tag);
|
|
||||||
this.addDirtyItems(dirty);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
angular.module('app.frontend').service('modelManager', ModelManager);
|
angular.module('app.frontend').service('modelManager', ModelManager);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nt-dropdown-menu.dark {
|
.nt-dropdown-menu.dark {
|
||||||
background-color: $selection-color;
|
background-color: white;
|
||||||
color: $selected-text-color;
|
color: $selected-text-color;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
@@ -85,18 +85,27 @@
|
|||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
background-color: #f1f1f1;
|
background-color: #f1f1f1;
|
||||||
color: $selected-text-color;
|
color: $selected-text-color;
|
||||||
// padding-top: 5px;
|
|
||||||
height: 28px;
|
height: 28px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
ol, ul {
|
ol, ul {
|
||||||
margin-top: 7px;
|
margin-top: 5px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
&.dropdown-menu {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
li {
|
li {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
|
&.sep {
|
||||||
|
margin: 6px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -128,6 +137,7 @@
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
color: black;
|
||||||
|
|
||||||
.url {
|
.url {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|||||||
64
app/assets/stylesheets/app/_directives.scss
Normal file
64
app/assets/stylesheets/app/_directives.scss
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
.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;
|
||||||
|
|
||||||
|
> .name {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .access {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.5;
|
||||||
|
font-weight: normal;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,21 @@
|
|||||||
|
|
||||||
.section-title-bar {
|
.section-title-bar {
|
||||||
border-bottom: none !important;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$heading-height: 90px;
|
$heading-height: 90px;
|
||||||
@@ -65,6 +80,26 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fullscreen-ghost-bar {
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
width: 20%;
|
||||||
|
height: 200px;
|
||||||
|
z-index: 100;
|
||||||
|
//
|
||||||
|
// &:hover + .section-title-bar {
|
||||||
|
// // show section title bar when hover over ghost bar
|
||||||
|
// opacity: 1.0;
|
||||||
|
// z-index: 100;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// &:not(:hover) + .section-title-bar {
|
||||||
|
// // display: none;
|
||||||
|
// opacity: 1.0;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.editor-content {
|
.editor-content {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
|
|
||||||
@@ -74,9 +109,14 @@
|
|||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
text-align: center; // centers children div horizontally
|
||||||
|
z-index: 10;
|
||||||
padding-top: $heading-height;
|
padding-top: $heading-height;
|
||||||
|
|
||||||
|
&.fullscreen {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.sampler-container {
|
.sampler-container {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
@@ -101,6 +141,12 @@
|
|||||||
padding-top: 17px;
|
padding-top: 17px;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
resize: none;
|
resize: none;
|
||||||
|
|
||||||
|
&.fullscreen {
|
||||||
|
padding: 85px 10%;
|
||||||
|
max-width: 1200px;
|
||||||
|
display: inline-block;;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
@@ -110,6 +156,13 @@
|
|||||||
line-height: 23px;
|
line-height: 23px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
padding: 0px 15px;
|
padding: 0px 15px;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&.fullscreen {
|
||||||
|
padding: 85px 10%;
|
||||||
|
max-width: 1200px;
|
||||||
|
display: inline-block;;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,10 @@
|
|||||||
.email {
|
.email {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,3 +264,131 @@ a.disabled {
|
|||||||
0% { transform: rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
100% { transform: rotate(360deg); }
|
100% { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Extensions
|
||||||
|
*/
|
||||||
|
|
||||||
|
.extensions-panel {
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.extension-link {
|
||||||
|
margin-top: 6px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.extension-form {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.registered-extensions {
|
||||||
|
|
||||||
|
|
||||||
|
.extension {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
border: 1px solid #f2f2f2;
|
||||||
|
padding: 14px 6px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $blue-color !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .name {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.encryption-format {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
> .title {
|
||||||
|
font-size: 13px;
|
||||||
|
// font-weight: bold;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .actions {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.action {
|
||||||
|
padding: 13px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background-color: rgba(white, 0.9);
|
||||||
|
border: 1px solid rgba(gray, 0.15);
|
||||||
|
|
||||||
|
> .name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .permissions {
|
||||||
|
margin-top: 2px;
|
||||||
|
a {
|
||||||
|
font-weight: normal !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .execute {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
font-size: 12px;
|
||||||
|
height: 30px;
|
||||||
|
padding-top: 7px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 6px;
|
||||||
|
border: 1px solid rgba(gray, 0.15);
|
||||||
|
cursor: pointer;
|
||||||
|
color: $blue-color;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(gray, 0.10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.execution-spinner {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .execute-type {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .error {
|
||||||
|
color: red;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .last-run {
|
||||||
|
opacity: 0.5;
|
||||||
|
font-size: 11px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ $secondary-text-color: rgba($main-text-color, 0.8);
|
|||||||
$bg-color: #e3e3e3;
|
$bg-color: #e3e3e3;
|
||||||
$selection-color: $bg-color;
|
$selection-color: $bg-color;
|
||||||
$selected-text-color: black;
|
$selected-text-color: black;
|
||||||
|
$blue-color: #086dd6;
|
||||||
|
|
||||||
@mixin MQ-Small() {
|
@mixin MQ-Small() {
|
||||||
@media (max-width: $screen-xs-max) {
|
@media (max-width: $screen-xs-max) {
|
||||||
|
|||||||
@@ -53,8 +53,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$note-selection-color: #086dd6;
|
|
||||||
|
|
||||||
.note {
|
.note {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
@@ -75,13 +73,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
background-color: $note-selection-color;
|
background-color: $blue-color;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
// &:hover:not(.selected) {
|
|
||||||
// background-color: $note-selection-color;
|
|
||||||
// color: white;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ $dark-gray: #2e2e2e;
|
|||||||
@import "app/tags";
|
@import "app/tags";
|
||||||
@import "app/notes";
|
@import "app/notes";
|
||||||
@import "app/editor";
|
@import "app/editor";
|
||||||
|
@import "app/directives";
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'icomoon';
|
font-family: 'icomoon';
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
.panel.panel-default
|
|
||||||
.panel-heading
|
|
||||||
%h3.panel-title Forgot Your Password?
|
|
||||||
.panel-body
|
|
||||||
%p We'll send reset instructions to your email.
|
|
||||||
%form{'ng-submit' => 'requestPasswordReset(forgotData)', 'ng-init' => 'forgotData = {}'}
|
|
||||||
.form-tag.has-feedback
|
|
||||||
%input.form-control{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'forgotData.email'}/
|
|
||||||
%span.glyphicon.glyphicon-envelope.form-control-feedback
|
|
||||||
.row
|
|
||||||
.col-sm-8.hidden-xs
|
|
||||||
%a.btn.btn-link{'ui-sref' => 'auth.login'} Go back to the login page
|
|
||||||
.col-sm-4
|
|
||||||
%button.btn.btn-main.btn-block.btn-flat{:type => 'submit'} Reset Password
|
|
||||||
.col-xs-12.visible-xs
|
|
||||||
%a.btn.btn-link.btn-block{'ui-sref' => 'auth.login'} Go back to the login page
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
.panel.panel-default
|
|
||||||
.panel-heading
|
|
||||||
%h3.panel-title Sign in to start your session
|
|
||||||
.panel-body
|
|
||||||
%form{'ng-submit' => 'submitLogin(loginData)', 'ng-init' => 'loginData = {}'}
|
|
||||||
.form-tag.has-feedback
|
|
||||||
%input.form-control{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Username', :required => true, :type => 'email', 'ng-model' => 'loginData.email'}/
|
|
||||||
%span.glyphicon.glyphicon-user.form-control-feedback
|
|
||||||
.form-tag.has-feedback
|
|
||||||
%input.form-control{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'loginData.password'}/
|
|
||||||
%span.glyphicon.glyphicon-lock.form-control-feedback
|
|
||||||
.row
|
|
||||||
.col-sm-8.hidden-xs
|
|
||||||
%a.btn.btn-link{'ui-sref' => 'auth.forgot'} I forgot my password
|
|
||||||
.col-sm-4
|
|
||||||
%button.btn.btn-main.btn-block.btn-flat{:type => 'submit'} Sign in
|
|
||||||
.col-xs-12.visible-xs
|
|
||||||
%a.btn.btn-link.btn-block{'ui-sref' => 'auth.forgot'} I forgot my password
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
.panel.panel-default.panel-centered
|
|
||||||
.panel-heading
|
|
||||||
%h3.panel-title Reset Password
|
|
||||||
.panel-body
|
|
||||||
%p Type your new password.
|
|
||||||
%form{'ng-submit' => 'resetPasswordSubmit()'}
|
|
||||||
.form-tag.has-feedback
|
|
||||||
%input.form-control{:placeholder => 'New password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'resetData.password'}/
|
|
||||||
%span.glyphicon.glyphicon-lock.form-control-feedback
|
|
||||||
.form-tag.has-feedback
|
|
||||||
%input.form-control{:placeholder => 'Password confirmation', :name => 'password_confirmation', :required => true, :type => 'password', 'ng-model' => 'resetData.password_confirmation'}/
|
|
||||||
%span.glyphicon.glyphicon-lock.form-control-feedback
|
|
||||||
.row
|
|
||||||
.col-sm-4.col-sm-offset-8
|
|
||||||
%button.btn.btn-main.btn-block.btn-flat{:type => 'submit'} Update Password
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
.login-box.margin-auto{"style" => "margin-top: 150px;"}
|
|
||||||
|
|
||||||
%uib-alert{:type => '{{data.authAlert.type}}', :close => 'data.authAlert = null', 'ng-if' => 'data.authAlert'}
|
|
||||||
{{data.authAlert.msg}}
|
|
||||||
|
|
||||||
%ui-view
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark.contextual-menu
|
||||||
|
.extension{"ng-repeat" => "extension in extensions"}
|
||||||
|
.ext-header
|
||||||
|
.name {{extension.name}}
|
||||||
|
.access
|
||||||
|
Can access your data
|
||||||
|
%strong {{accessTypeForExtension(extension)}}
|
||||||
|
%ul
|
||||||
|
%li.action{"ng-repeat" => "action in extension.actionsWithContextForItem(item)", "ng-click" => "executeAction(action, extension)"}
|
||||||
|
.name {{action.label}}
|
||||||
|
.desc {{action.desc}}
|
||||||
|
%span{"ng-if" => "action.running"}
|
||||||
|
.spinner{"style" => "margin-top: 3px;"}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
.section.editor{"ng-class" => "{'fullscreen' : ctrl.fullscreen}"}
|
.section.editor{"ng-class" => "{'fullscreen' : ctrl.fullscreen}"}
|
||||||
.content
|
.content
|
||||||
.section-title-bar.editor-heading{"ng-class" => "{'shared' : ctrl.note.isPublic() }"}
|
-# %span.fullscreen-ghost-bar{"ng-if" => "ctrl.fullscreen"}
|
||||||
|
.section-title-bar.editor-heading{"ng-class" => "{'shared' : ctrl.note.isPublic(), 'fullscreen' : ctrl.fullscreen }"}
|
||||||
.title
|
.title
|
||||||
%input.input#note-title-editor{"ng-model" => "ctrl.note.content.title", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTitle($event)",
|
%input.input#note-title-editor{"ng-model" => "ctrl.note.title", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTitle($event)",
|
||||||
"ng-disabled" => "ctrl.note.locked", "ng-change" => "ctrl.nameChanged()", "ng-focus" => "ctrl.onNameFocus()",
|
"ng-disabled" => "ctrl.note.locked", "ng-change" => "ctrl.nameChanged()", "ng-focus" => "ctrl.onNameFocus()",
|
||||||
"select-on-click" => "true"}
|
"select-on-click" => "true"}
|
||||||
.save-status {{ctrl.noteStatus}}
|
.save-status {{ctrl.noteStatus}}
|
||||||
.section-menu
|
.section-menu
|
||||||
%ul.nav.nav-pills
|
%ul.nav.nav-pills
|
||||||
%li.dropdown
|
%li.dropdown
|
||||||
%a.dropdown-toggle{"ng-click" => "ctrl.clickedMenu()"}
|
%a.dropdown-toggle{"ng-click" => "ctrl.clickedMenu(); ctrl.showExtensions = false"}
|
||||||
File
|
File
|
||||||
%span.caret{"ng-if" => "!ctrl.note.locked"}
|
%span.caret{"ng-if" => "!ctrl.note.locked"}
|
||||||
%span{"ng-if" => " ctrl.note.locked"}
|
%span{"ng-if" => " ctrl.note.locked"}
|
||||||
@@ -17,20 +18,28 @@
|
|||||||
%span.sr-only
|
%span.sr-only
|
||||||
|
|
||||||
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu && !ctrl.note.locked"}
|
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu && !ctrl.note.locked"}
|
||||||
%li
|
%li{"ng-click" => "ctrl.selectedMenuItem(); ctrl.toggleFullScreen()"}
|
||||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.toggleFullScreen()"} Toggle Fullscreen
|
.text Toggle Fullscreen
|
||||||
.shortcut Cmd + O
|
.shortcut Cmd + O
|
||||||
%li
|
%li{"ng-click" => "ctrl.selectedMenuItem(); ctrl.toggleMarkdown()"}
|
||||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.toggleMarkdown()"} Toggle Markdown Preview
|
.text Toggle Markdown Preview
|
||||||
.shortcut Cmd + M
|
.shortcut Cmd + M
|
||||||
%li{"ng-if" => "!ctrl.note.isSharedIndividually()"}
|
%li{"ng-if" => "!ctrl.note.isSharedIndividually()", "ng-click" => "ctrl.selectedMenuItem(); ctrl.shareNote()"}
|
||||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.shareNote()"} Share
|
.text Share
|
||||||
%li{"ng-if" => "ctrl.note.isSharedIndividually()"}
|
%li{"ng-if" => "ctrl.note.isSharedIndividually()", "ng-click" => "ctrl.selectedMenuItem(); ctrl.editUrlPressed()"}
|
||||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.editUrlPressed()"} Edit URL
|
.text Edit URL
|
||||||
%li{"ng-if" => "ctrl.note.isSharedIndividually()"}
|
%li{"ng-if" => "ctrl.note.isSharedIndividually()", "ng-click" => "ctrl.selectedMenuItem(); ctrl.unshareNote()"}
|
||||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.unshareNote()"} Unshare
|
.text Unshare
|
||||||
%li
|
%li{"ng-click" => "ctrl.deleteNote()"}
|
||||||
%a.text{"ng-click" => "ctrl.deleteNote()"} Delete
|
.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"}
|
||||||
|
|
||||||
.markdown.icon{"ng-if" => "ctrl.editorMode == 'preview'", "ng-click" => "ctrl.showMarkdown = !ctrl.showMarkdown"}
|
.markdown.icon{"ng-if" => "ctrl.editorMode == 'preview'", "ng-click" => "ctrl.showMarkdown = !ctrl.showMarkdown"}
|
||||||
.icon-markdown
|
.icon-markdown
|
||||||
.panel.panel-default.info-panel{"ng-if" => "ctrl.showMarkdown"}
|
.panel.panel-default.info-panel{"ng-if" => "ctrl.showMarkdown"}
|
||||||
@@ -47,12 +56,10 @@
|
|||||||
"ng-disabled" => "ctrl.note.locked", "ng-change" => "ctrl.urlChanged()", "ng-focus" => "ctrl.onUrlFocus()",
|
"ng-disabled" => "ctrl.note.locked", "ng-change" => "ctrl.urlChanged()", "ng-focus" => "ctrl.onUrlFocus()",
|
||||||
"select-on-click" => "true", "autofocus" => "true"}
|
"select-on-click" => "true", "autofocus" => "true"}
|
||||||
|
|
||||||
.editor-content{"ng-class" => "{'shared' : ctrl.note.isPublic() }"}
|
.editor-content{"ng-class" => "{'shared' : ctrl.note.isPublic(), 'fullscreen' : ctrl.fullscreen }"}
|
||||||
.sampler-container{"ng-if" => "ctrl.showSampler", "ng-click" => "ctrl.focusEditor()"}
|
.sampler-container{"ng-if" => "ctrl.showSampler", "ng-click" => "ctrl.focusEditor()"}
|
||||||
%strong.name-sampler.sampler{"typewrite" => "true", "text" => "ctrl.demoNoteNames", "type-delay" => "30", "initial-delay" => "1.5s",
|
%strong.name-sampler.sampler{"typewrite" => "true", "text" => "ctrl.demoNoteNames", "type-delay" => "30", "initial-delay" => "1.5s",
|
||||||
"iteration-callback" => "ctrl.callback", "prebegin-fn" => "ctrl.prebeginFn", "iteration-delay" => "2000", "cursor" => ""}
|
"iteration-callback" => "ctrl.callback", "prebegin-fn" => "ctrl.prebeginFn", "iteration-delay" => "2000", "cursor" => ""}
|
||||||
%code{"ng-if" => "ctrl.currentDemoContent.text"}
|
%textarea.editable#note-text-editor{"ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-disabled" => "ctrl.note.locked", "ng-show" => "ctrl.editorMode == 'edit'", "ng-model" => "ctrl.note.text",
|
||||||
.content-sampler.sampler{"typewrite" => "true", "text" => "ctrl.currentDemoContent.text", "type-delay" => "10", "iteration-callback" => "ctrl.contentCallback"}
|
|
||||||
%textarea.editable#note-text-editor{"ng-disabled" => "ctrl.note.locked", "ng-show" => "ctrl.editorMode == 'edit'", "ng-model" => "ctrl.note.content.text",
|
|
||||||
"ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()"}
|
"ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()"}
|
||||||
.preview{"ng-if" => "ctrl.editorMode == 'preview'", "ng-bind-html" => "ctrl.renderedContent()", "ng-dblclick" => "ctrl.onPreviewDoubleClick()"}
|
.preview{"ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-if" => "ctrl.editorMode == 'preview'", "ng-bind-html" => "ctrl.renderedContent()", "ng-dblclick" => "ctrl.onPreviewDoubleClick()"}
|
||||||
|
|||||||
@@ -49,20 +49,21 @@
|
|||||||
|
|
||||||
.account-item{"ng-if" => "ctrl.user.email"}
|
.account-item{"ng-if" => "ctrl.user.email"}
|
||||||
.email {{ctrl.user.email}}
|
.email {{ctrl.user.email}}
|
||||||
|
.server {{ctrl.serverData.url}}
|
||||||
.links{"ng-if" => "ctrl.user.email"}
|
.links{"ng-if" => "ctrl.user.email"}
|
||||||
.link-item
|
-# .link-item
|
||||||
%a{"ng-click" => "ctrl.changePasswordPressed()"} Change Password
|
-# %a{"ng-click" => "ctrl.changePasswordPressed()"} Change Password
|
||||||
%form.account-form{"ng-if" => "ctrl.showNewPasswordForm", 'ng-submit' => 'ctrl.submitPasswordChange()', 'name' => "passwordChangeForm"}
|
-# %form.account-form{"ng-if" => "ctrl.showNewPasswordForm", 'ng-submit' => 'ctrl.submitPasswordChange()', 'name' => "passwordChangeForm"}
|
||||||
.form-tag.has-feedback
|
-# .form-tag.has-feedback
|
||||||
%input.form-control.login-input{:autofocus => 'autofocus', :name => 'current', :placeholder => 'Current password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.current_password'}
|
-# %input.form-control.login-input{:autofocus => 'autofocus', :name => 'current', :placeholder => 'Current password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.current_password'}
|
||||||
.form-tag.has-feedback
|
-# .form-tag.has-feedback
|
||||||
%input.form-control.login-input{:placeholder => 'New password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.new_password', "autocomplete" => "new-password"}
|
-# %input.form-control.login-input{:placeholder => 'New password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.new_password', "autocomplete" => "new-password"}
|
||||||
.form-tag.has-feedback
|
-# .form-tag.has-feedback
|
||||||
%input.form-control.login-input{:placeholder => 'Confirm password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.new_password_confirmation', "autocomplete" => "new-password"}
|
-# %input.form-control.login-input{:placeholder => 'Confirm password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.new_password_confirmation', "autocomplete" => "new-password"}
|
||||||
%button.btn.dark-button.btn-block{:type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
|
-# %button.btn.dark-button.btn-block{:type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
|
||||||
%span.ladda-label Change Password
|
-# %span.ladda-label Change Password
|
||||||
.panel-status-text{"ng-if" => "ctrl.passwordChangeData.status", "style" => "font-size: 14px;"}
|
-# .panel-status-text{"ng-if" => "ctrl.passwordChangeData.status", "style" => "font-size: 14px;"}
|
||||||
{{ctrl.passwordChangeData.status}}
|
-# {{ctrl.passwordChangeData.status}}
|
||||||
.link-item
|
.link-item
|
||||||
%a{"ng-click" => "ctrl.signOutPressed()"} Sign Out
|
%a{"ng-click" => "ctrl.signOutPressed()"} Sign Out
|
||||||
.meta-container
|
.meta-container
|
||||||
@@ -74,9 +75,15 @@
|
|||||||
.account-item{"ng-if" => "ctrl.user.email"}
|
.account-item{"ng-if" => "ctrl.user.email"}
|
||||||
.meta-container
|
.meta-container
|
||||||
.title Data Archives
|
.title Data Archives
|
||||||
.desc Note: data archives that you download using the link below are decrypted before save. You should take care to store them in a safe location.
|
.options{"style" => "font-size: 12px; margin-top: 4px;"}
|
||||||
|
%label
|
||||||
|
%input{"type" => "radio", "ng-model" => "ctrl.archiveEncryptionFormat.encrypted", "ng-value" => "true", "ng-change" => "ctrl.archiveEncryptionFormat.encrypted = true"}
|
||||||
|
Encrypted
|
||||||
|
%label
|
||||||
|
%input{"type" => "radio", "ng-model" => "ctrl.archiveEncryptionFormat.encrypted", "ng-value" => "false", "ng-change" => "ctrl.archiveEncryptionFormat.encrypted = false"}
|
||||||
|
Decrypted
|
||||||
.action-container
|
.action-container
|
||||||
%a{"ng-click" => "ctrl.downloadDataArchive()"} Download Latest Data Archive
|
%a{"ng-click" => "ctrl.downloadDataArchive()"} Download Data Archive
|
||||||
%br
|
%br
|
||||||
%label#import-archive
|
%label#import-archive
|
||||||
%input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "ctrl.importFileSelected(files)"}
|
%input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "ctrl.importFileSelected(files)"}
|
||||||
@@ -84,6 +91,61 @@
|
|||||||
%span
|
%span
|
||||||
Import Data from Archive
|
Import Data from Archive
|
||||||
|
|
||||||
|
.item
|
||||||
|
%a{"ng-click" => "ctrl.toggleExtensions()"} Extensions
|
||||||
|
.panel.panel-default.account-panel.panel-right.extensions-panel{"ng-if" => "ctrl.showExtensionsMenu"}
|
||||||
|
.panel-body
|
||||||
|
.registered-extensions{"ng-if" => "ctrl.extensionManager.extensions.length"}
|
||||||
|
.extension{"ng-repeat" => "extension in ctrl.extensionManager.extensions"}
|
||||||
|
.name {{extension.name}}
|
||||||
|
.encryption-format
|
||||||
|
.title Send data:
|
||||||
|
%label
|
||||||
|
%input{"type" => "radio", "ng-model" => "extension.encrypted", "ng-value" => "true", "ng-change" => "ctrl.extensionManager.changeExtensionEncryptionFormat(true, extension)"}
|
||||||
|
Encrypted
|
||||||
|
%label
|
||||||
|
%input{"type" => "radio", "ng-model" => "extension.encrypted", "ng-value" => "false", "ng-change" => "ctrl.extensionManager.changeExtensionEncryptionFormat(false, extension)"}
|
||||||
|
Decrypted
|
||||||
|
.actions
|
||||||
|
.action{"ng-repeat" => "action in extension.actionsInGlobalContext()"}
|
||||||
|
.name {{action.label}}
|
||||||
|
.desc{"style" => "font-style: italic;"} {{action.desc}}
|
||||||
|
.execute-type{"ng-if" => "action.repeat_mode == 'watch'"}
|
||||||
|
Repeats when a change is made to your items.
|
||||||
|
.execute-type{"ng-if" => "action.repeat_mode == 'loop'"}
|
||||||
|
Repeats at most once every {{action.repeat_timeout}} seconds
|
||||||
|
.permissions
|
||||||
|
%a{"ng-click" => "action.showPermissions = !action.showPermissions"} {{action.showPermissions ? "Hide permissions" : "Show permissions"}}
|
||||||
|
%div{"ng-if" => "action.showPermissions"}
|
||||||
|
{{action.permissionsString}}
|
||||||
|
.encryption-type
|
||||||
|
%span {{action.encryptionModeString}}
|
||||||
|
.execute
|
||||||
|
%div{"ng-if" => "action.repeat_mode"}
|
||||||
|
%div{"ng-if" => "ctrl.extensionManager.isRepeatActionEnabled(action)", "ng-click" => "ctrl.extensionManager.disableRepeatAction(action, extension)"} Disable
|
||||||
|
%div{"ng-if" => "!ctrl.extensionManager.isRepeatActionEnabled(action)", "ng-click" => "ctrl.extensionManager.enableRepeatAction(action, extension)"} Enable
|
||||||
|
%div{"ng-if" => "!action.repeat_mode", "ng-click" => "ctrl.selectedAction(action, extension)"}
|
||||||
|
%div{"ng-if" => "!action.running"}
|
||||||
|
Perform Action
|
||||||
|
%div{"ng-if" => "action.running"}
|
||||||
|
.spinner.execution-spinner
|
||||||
|
.last-run{"ng-if" => "!action.error && action.lastExecuted && !action.running"}
|
||||||
|
Last run {{action.lastExecuted | appDateTime}}
|
||||||
|
.error{"ng-if" => "action.error"}
|
||||||
|
Error performing action.
|
||||||
|
%a{"ng-click" => "ctrl.deleteExtension(extension)", "style" => "margin-top: 22px; display: block;"} Remove extension
|
||||||
|
|
||||||
|
.extension-link
|
||||||
|
%a{"ng-click" => "ctrl.toggleExtensionForm()"} Add new extension
|
||||||
|
|
||||||
|
%form.extension-form{"ng-if" => "ctrl.showNewExtensionForm"}
|
||||||
|
.form-tag.has-feedback
|
||||||
|
%input.form-control{:autofocus => 'autofocus', :name => 'url', :placeholder => 'Extension URL', :required => true, :type => 'url', 'ng-model' => 'ctrl.newExtensionData.url'}
|
||||||
|
%button.btn.dark-button.btn-block{"ng-click" => "ctrl.submitNewExtensionForm()", :type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
|
||||||
|
%span.ladda-label Add Extension
|
||||||
|
|
||||||
|
.extension-link
|
||||||
|
%a{"ng-click" => "ctrl.reloadExtensionsPressed()", "ng-if" => "ctrl.extensionManager.extensions.length > 0"} Reload all extensions
|
||||||
|
|
||||||
.item
|
.item
|
||||||
%a{"href" => "https://standardnotes.org", "target" => "_blank"}
|
%a{"href" => "https://standardnotes.org", "target" => "_blank"}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.section.notes
|
.section.notes
|
||||||
.content
|
.content
|
||||||
.section-title-bar.notes-title-bar
|
.section-title-bar.notes-title-bar
|
||||||
.title {{ctrl.tag.content.title}} notes
|
.title {{ctrl.tag.title}} notes
|
||||||
.add-button{"ng-click" => "ctrl.createNewNote()"} +
|
.add-button{"ng-click" => "ctrl.createNewNote()"} +
|
||||||
%br
|
%br
|
||||||
.filter-section
|
.filter-section
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
File
|
File
|
||||||
%span.caret
|
%span.caret
|
||||||
%span.sr-only
|
%span.sr-only
|
||||||
|
|
||||||
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu"}
|
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu"}
|
||||||
%li{"ng-if" => "!ctrl.tag.isPublic()"}
|
%li{"ng-if" => "!ctrl.tag.isPublic()"}
|
||||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagShare($event)"} Share Tag
|
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagShare($event)"} Share Tag
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagUnshare()"} Unshare Tag
|
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagUnshare()"} Unshare Tag
|
||||||
%li{"ng-if" => "!ctrl.tag.all"}
|
%li{"ng-if" => "!ctrl.tag.all"}
|
||||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagDelete()"} Delete Tag
|
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagDelete()"} Delete Tag
|
||||||
|
|
||||||
.menu-right-container
|
.menu-right-container
|
||||||
.public-link{"ng-if" => "ctrl.tag.isPublic()"}
|
.public-link{"ng-if" => "ctrl.tag.isPublic()"}
|
||||||
%a.url{"ng-if" => "!ctrl.editingUrl", "href" => "{{ctrl.tag.presentationURL()}}", "target" => "_blank"}
|
%a.url{"ng-if" => "!ctrl.editingUrl", "href" => "{{ctrl.tag.presentationURL()}}", "target" => "_blank"}
|
||||||
@@ -34,5 +36,5 @@
|
|||||||
"ng-click" => "ctrl.selectNote(note)", "ng-class" => "{'selected' : ctrl.selectedNote == note}",
|
"ng-click" => "ctrl.selectNote(note)", "ng-class" => "{'selected' : ctrl.selectedNote == note}",
|
||||||
"ng-attr-draggable" => "{{note.dummy ? undefined : 'true'}}", "note" => "note"}
|
"ng-attr-draggable" => "{{note.dummy ? undefined : 'true'}}", "note" => "note"}
|
||||||
.name
|
.name
|
||||||
{{note.content.title}}
|
{{note.title}}
|
||||||
.date {{(note.created_at | appDateTime) || 'Now'}}
|
.date {{(note.created_at | appDateTime) || 'Now'}}
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
{{ctrl.test}}
|
{{ctrl.test}}
|
||||||
.tag{"ng-if" => "ctrl.allTag", "ng-click" => "ctrl.selectTag(ctrl.allTag)", "ng-class" => "{'selected' : ctrl.selectedTag == ctrl.allTag}",
|
.tag{"ng-if" => "ctrl.allTag", "ng-click" => "ctrl.selectTag(ctrl.allTag)", "ng-class" => "{'selected' : ctrl.selectedTag == ctrl.allTag}",
|
||||||
"droppable" => true, "drop" => "ctrl.handleDrop", "tag" => "ctrl.allTag"}
|
"droppable" => true, "drop" => "ctrl.handleDrop", "tag" => "ctrl.allTag"}
|
||||||
%input.title{"ng-disabled" => "true", "ng-model" => "ctrl.allTag.content.title"}
|
%input.title{"ng-disabled" => "true", "ng-model" => "ctrl.allTag.title"}
|
||||||
.count {{ctrl.noteCount(ctrl.allTag)}}
|
.count {{ctrl.noteCount(ctrl.allTag)}}
|
||||||
|
|
||||||
.tag{"ng-repeat" => "tag in ctrl.tags", "ng-click" => "ctrl.selectTag(tag)", "ng-class" => "{'selected' : ctrl.selectedTag == tag}",
|
.tag{"ng-repeat" => "tag in ctrl.tags", "ng-click" => "ctrl.selectTag(tag)", "ng-class" => "{'selected' : ctrl.selectedTag == tag}",
|
||||||
"droppable" => true, "drop" => "ctrl.handleDrop", "tag" => "tag"}
|
"droppable" => true, "drop" => "ctrl.handleDrop", "tag" => "tag"}
|
||||||
.icon.icon-rss{"ng-if" => "tag.isPublic()"}
|
.icon.icon-rss{"ng-if" => "tag.isPublic()"}
|
||||||
%input.title{"ng-disabled" => "tag != ctrl.selectedTag", "ng-model" => "tag.content.title",
|
%input.title{"ng-disabled" => "tag != ctrl.selectedTag", "ng-model" => "tag.title",
|
||||||
"ng-keyup" => "$event.keyCode == 13 && ctrl.saveTag($event, tag)", "mb-autofocus" => "true", "should-focus" => "ctrl.newTag",
|
"ng-keyup" => "$event.keyCode == 13 && ctrl.saveTag($event, tag)", "mb-autofocus" => "true", "should-focus" => "ctrl.newTag",
|
||||||
"ng-change" => "ctrl.tagTitleDidChange(tag)", "ng-focus" => "ctrl.onTagTitleFocus(tag)"}
|
"ng-change" => "ctrl.tagTitleDidChange(tag)", "ng-focus" => "ctrl.onTagTitleFocus(tag)"}
|
||||||
.count {{ctrl.noteCount(tag)}}
|
.count {{ctrl.noteCount(tag)}}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
include DeviseTokenAuth::Concerns::SetUserByToken
|
|
||||||
# Prevent CSRF attacks by raising an exception.
|
# Prevent CSRF attacks by raising an exception.
|
||||||
# For APIs, you may want to use :null_session instead.
|
# For APIs, you may want to use :null_session instead.
|
||||||
|
|
||||||
|
|||||||
15
bower.json
15
bower.json
@@ -7,17 +7,14 @@
|
|||||||
"vendor": {
|
"vendor": {
|
||||||
"name": "bower-rails generated vendor assets",
|
"name": "bower-rails generated vendor assets",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"angular": "1.6.0",
|
"angular": "1.6.1",
|
||||||
"angular-ui-router": "^0.2.18",
|
"angular-ui-router": "^0.3.2",
|
||||||
"restangular": "^1.5.2",
|
"restangular": "^1.6.1",
|
||||||
"oclazyload": "^1.0.9",
|
"marked": "0.3.6",
|
||||||
"font-awesome": "^4.6.2",
|
"ng-dialog" : "0.6.6"
|
||||||
"marked": "0.3.4",
|
|
||||||
"angular-lazy-img": "1.1.0",
|
|
||||||
"ng-dialog" : "0.6.4"
|
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"angular": "1.6.0"
|
"angular": "1.6.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Rails.application.configure do
|
|||||||
config.eager_load = true
|
config.eager_load = true
|
||||||
|
|
||||||
# Full error reports are disabled and caching is turned on.
|
# Full error reports are disabled and caching is turned on.
|
||||||
config.consider_all_requests_local = true
|
config.consider_all_requests_local = false
|
||||||
config.action_controller.perform_caching = true
|
config.action_controller.perform_caching = true
|
||||||
|
|
||||||
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
|
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
DeviseTokenAuth.setup do |config|
|
|
||||||
# By default the authorization headers will change after each request. The
|
|
||||||
# client is responsible for keeping track of the changing tokens. Change
|
|
||||||
# this to false to prevent the Authorization header from changing after
|
|
||||||
# each request.
|
|
||||||
# config.change_headers_on_each_request = true
|
|
||||||
|
|
||||||
# By default, users will need to re-authenticate after 2 weeks. This setting
|
|
||||||
# determines how long tokens will remain valid after they are issued.
|
|
||||||
# config.token_lifespan = 2.weeks
|
|
||||||
|
|
||||||
# Sets the max number of concurrent devices per user, which is 10 by default.
|
|
||||||
# After this limit is reached, the oldest tokens will be removed.
|
|
||||||
# config.max_number_of_devices = 10
|
|
||||||
|
|
||||||
# Sometimes it's necessary to make several requests to the API at the same
|
|
||||||
# time. In this case, each request in the batch will need to share the same
|
|
||||||
# auth token. This setting determines how far apart the requests can be while
|
|
||||||
# still using the same auth token.
|
|
||||||
# config.batch_request_buffer_throttle = 5.seconds
|
|
||||||
|
|
||||||
# This route will be the prefix for all oauth2 redirect callbacks. For
|
|
||||||
# example, using the default '/omniauth', the github oauth2 provider will
|
|
||||||
# redirect successful authentications to '/omniauth/github/callback'
|
|
||||||
# config.omniauth_prefix = "/omniauth"
|
|
||||||
|
|
||||||
# By default sending current password is not needed for the password update.
|
|
||||||
# Uncomment to enforce current_password param to be checked before all
|
|
||||||
# attribute updates. Set it to :password if you want it to be checked only if
|
|
||||||
# password is updated.
|
|
||||||
config.check_current_password_before_update = :password
|
|
||||||
|
|
||||||
# By default we will use callbacks for single omniauth.
|
|
||||||
# It depends on fields like email, provider and uid.
|
|
||||||
# config.default_callbacks = true
|
|
||||||
|
|
||||||
# Makes it possible to change the headers names
|
|
||||||
# config.headers_names = {:'access-token' => 'access-token',
|
|
||||||
# :'client' => 'client',
|
|
||||||
# :'expiry' => 'expiry',
|
|
||||||
# :'uid' => 'uid',
|
|
||||||
# :'token-type' => 'token-type' }
|
|
||||||
|
|
||||||
# By default, only Bearer Token authentication is implemented out of the box.
|
|
||||||
# If, however, you wish to integrate with legacy Devise authentication, you can
|
|
||||||
# do so by enabling this flag. NOTE: This feature is highly experimental!
|
|
||||||
# config.enable_standard_devise_support = false
|
|
||||||
end
|
|
||||||
0
vendor/assets/javascripts/.keep
vendored
0
vendor/assets/javascripts/.keep
vendored
1
vendor/assets/javascripts/app.js.map
vendored
1
vendor/assets/javascripts/app.js.map
vendored
File diff suppressed because one or more lines are too long
1
vendor/assets/javascripts/compiled.js.map
vendored
1
vendor/assets/javascripts/compiled.js.map
vendored
File diff suppressed because one or more lines are too long
1799
vendor/assets/javascripts/transpiled.js
vendored
1799
vendor/assets/javascripts/transpiled.js
vendored
File diff suppressed because it is too large
Load Diff
2
vendor/assets/javascripts/transpiled.js.map
vendored
2
vendor/assets/javascripts/transpiled.js.map
vendored
File diff suppressed because one or more lines are too long
0
vendor/assets/stylesheets/.keep
vendored
0
vendor/assets/stylesheets/.keep
vendored
Reference in New Issue
Block a user