merged master

This commit is contained in:
Mo Bitar
2017-03-01 17:52:37 -06:00
16 changed files with 239 additions and 79 deletions

View File

@@ -15,30 +15,9 @@ angular.module('app.frontend')
bindToController: true,
link:function(scope, elem, attrs, ctrl) {
var handler = function(event) {
if (event.ctrlKey || event.metaKey) {
switch (String.fromCharCode(event.which).toLowerCase()) {
case 'o':
event.preventDefault();
$timeout(function(){
ctrl.toggleFullScreen();
})
break;
}
}
};
window.addEventListener('keydown', handler);
scope.$on('$destroy', function(){
window.removeEventListener('keydown', handler);
})
scope.$watch('ctrl.note', function(note, oldNote){
if(note) {
ctrl.setNote(note, oldNote);
} else {
ctrl.note = {};
}
});
}
@@ -47,27 +26,49 @@ angular.module('app.frontend')
.controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, extensionManager, syncManager, modelManager) {
window.addEventListener("message", function(event){
// console.log("App received message:", event);
if(event.data.status) {
this.postNoteToExternalEditor();
} else {
var id = event.data.id;
var text = event.data.text;
if(this.note.uuid == id) {
var data = event.data.data;
if(this.note.uuid === id) {
this.note.text = text;
if(data) {
var changesMade = this.customEditor.setData(id, data);
if(changesMade) {
this.customEditor.setDirty(true);
}
}
this.changesMade();
}
}
}.bind(this), false);
this.setNote = function(note, oldNote) {
var currentEditor = this.customEditor;
this.customEditor = null;
this.showExtensions = false;
this.showMenu = false;
this.loadTagsString();
if(note.editorUrl) {
this.customEditor = this.editorForUrl(note.editorUrl);
var setEditor = function(editor) {
this.customEditor = editor;
this.postNoteToExternalEditor();
}.bind(this)
var editor = this.editorForNote(note);
if(editor) {
if(currentEditor !== editor) {
// switch after timeout, so that note data isnt posted to current editor
$timeout(function(){
setEditor(editor);
}.bind(this));
} else {
// switch immediately
setEditor(editor);
}
} else {
this.customEditor = null;
}
@@ -87,23 +88,35 @@ angular.module('app.frontend')
this.selectedEditor = function(editor) {
this.showEditorMenu = false;
if(this.customEditor && editor !== this.customEditor) {
this.customEditor.removeItemAsRelationship(this.note);
this.customEditor.setDirty(true);
}
if(editor.default) {
this.customEditor = null;
} else {
this.customEditor = editor;
this.customEditor.addItemAsRelationship(this.note);
this.customEditor.setDirty(true);
}
this.note.editorUrl = editor.url;
}.bind(this)
this.editorForUrl = function(url) {
this.editorForNote = function(note) {
var editors = modelManager.itemsForContentType("SN|Editor");
return editors.filter(function(editor){return editor.url == url})[0];
for(var editor of editors) {
if(_.includes(editor.notes, note)) {
return editor;
}
}
return null;
}
this.postNoteToExternalEditor = function() {
var externalEditorElement = document.getElementById("editor-iframe");
if(externalEditorElement) {
externalEditorElement.contentWindow.postMessage({text: this.note.text, id: this.note.uuid}, '*');
externalEditorElement.contentWindow.postMessage({text: this.note.text, data: this.customEditor.dataForKey(this.note.uuid), id: this.note.uuid}, '*');
}
}
@@ -214,6 +227,11 @@ angular.module('app.frontend')
}
}
this.clickedEditNote = function() {
this.editorMode = 'edit';
this.focusEditor(100);
}
/* Tags */
this.loadTagsString = function() {

View File

@@ -1,7 +1,31 @@
angular.module('app.frontend')
.controller('HomeCtrl', function ($scope, $rootScope, $timeout, modelManager, syncManager, authManager) {
.controller('HomeCtrl', function ($scope, $stateParams, $rootScope, $timeout, modelManager, syncManager, authManager) {
function autoSignInFromParams() {
if(!authManager.offline()) {
// check if current account
if(syncManager.serverURL == $stateParams.server && authManager.user.email == $stateParams.email) {
// already signed in, return
return;
} else {
// sign out
syncManager.destroyLocalData(function(){
window.location.reload();
})
}
} else {
authManager.login($stateParams.server, $stateParams.email, $stateParams.pw, function(response){
window.location.reload();
})
}
}
if($stateParams.server && $stateParams.email) {
autoSignInFromParams();
}
syncManager.loadLocalItems(function(items) {
$scope.allTag.didLoad = true;
$scope.$apply();
syncManager.sync(null);
@@ -11,7 +35,9 @@ angular.module('app.frontend')
}, 30000);
});
$scope.allTag = new Tag({all: true});
var allTag = new Tag({all: true});
allTag.needsLoad = true;
$scope.allTag = allTag;
$scope.allTag.title = "All";
$scope.tags = modelManager.tags;
$scope.allTag.notes = modelManager.notes;
@@ -127,6 +153,12 @@ angular.module('app.frontend')
this.$apply(fn);
};
$scope.notifyDelete = function() {
$timeout(function() {
$rootScope.$broadcast("noteDeleted");
}.bind(this), 0);
}
$scope.deleteNote = function(note) {
modelManager.setItemToBeDeleted(note);
@@ -137,6 +169,7 @@ angular.module('app.frontend')
if(note.dummy) {
modelManager.removeItemLocally(note);
$scope.notifyDelete();
return;
}
@@ -144,8 +177,11 @@ angular.module('app.frontend')
if(authManager.offline()) {
// when deleting items while ofline, we need to explictly tell angular to refresh UI
setTimeout(function () {
$scope.notifyDelete();
$scope.safeApply();
}, 50);
} else {
$scope.notifyDelete();
}
});
}

View File

@@ -4,7 +4,6 @@ angular.module('app.frontend')
scope: {
addNew: "&",
selectionMade: "&",
remove: "&",
tag: "=",
removeTag: "&"
},
@@ -18,7 +17,16 @@ angular.module('app.frontend')
link:function(scope, elem, attrs, ctrl) {
scope.$watch('ctrl.tag', function(tag, oldTag){
if(tag) {
ctrl.tagDidChange(tag, oldTag);
if(tag.needsLoad) {
scope.$watch('ctrl.tag.didLoad', function(didLoad){
if(didLoad) {
tag.needsLoad = false;
ctrl.tagDidChange(tag, oldTag);
}
});
} else {
ctrl.tagDidChange(tag, oldTag);
}
}
});
}
@@ -32,7 +40,9 @@ angular.module('app.frontend')
this.showMenu = false;
}.bind(this))
var isFirstLoad = true;
$rootScope.$on("noteDeleted", function() {
this.selectFirstNote(false);
}.bind(this))
this.notesToDisplay = 20;
this.paginate = function() {
@@ -47,20 +57,16 @@ angular.module('app.frontend')
}
this.noteFilter.text = "";
this.setNotes(tag.notes);
}
tag.notes.forEach(function(note){
this.setNotes = function(notes) {
notes.forEach(function(note){
note.visible = true;
})
this.selectFirstNote(false);
if(isFirstLoad) {
$timeout(function(){
this.createNewNote();
isFirstLoad = false;
}.bind(this))
} else if(tag.notes.length == 0) {
this.createNewNote();
}
var createNew = notes.length == 0;
this.selectFirstNote(createNew);
}
this.selectedTagDelete = function() {
@@ -69,7 +75,7 @@ angular.module('app.frontend')
}
this.selectFirstNote = function(createNew) {
var visibleNotes = this.tag.notes.filter(function(note){
var visibleNotes = this.sortedNotes.filter(function(note){
return note.visible;
});

View File

@@ -2,24 +2,70 @@ class Editor extends Item {
constructor(json_obj) {
super(json_obj);
if(!this.notes) {
this.notes = [];
}
if(!this.data) {
this.data = {};
}
}
mapContentToLocalProperties(contentObject) {
super.mapContentToLocalProperties(contentObject)
this.url = contentObject.url;
this.name = contentObject.name;
this.data = contentObject.data || {};
}
structureParams() {
var params = {
url: this.url,
name: this.name
name: this.name,
data: this.data
};
_.merge(params, super.structureParams());
return params;
}
referenceParams() {
var references = _.map(this.notes, function(note){
return {uuid: note.uuid, content_type: note.content_type};
})
return references;
}
addItemAsRelationship(item) {
if(item.content_type == "Note") {
if(!_.find(this.notes, item)) {
this.notes.push(item);
}
}
super.addItemAsRelationship(item);
}
removeItemAsRelationship(item) {
if(item.content_type == "Note") {
_.pull(this.notes, item);
}
super.removeItemAsRelationship(item);
}
removeAllRelationships() {
super.removeAllRelationships();
this.notes = [];
}
locallyClearAllReferences() {
super.locallyClearAllReferences();
this.notes = [];
}
allReferencedObjects() {
return this.notes;
}
toJSON() {
return {uuid: this.uuid}
}
@@ -27,4 +73,17 @@ class Editor extends Item {
get content_type() {
return "SN|Editor";
}
setData(key, value) {
var dataHasChanged = JSON.stringify(this.data[key]) !== JSON.stringify(value);
if(dataHasChanged) {
this.data[key] = value;
return true;
}
return false;
}
dataForKey(key) {
return this.data[key] || {};
}
}

View File

@@ -62,7 +62,7 @@ class Note extends Item {
_.pull(tag.notes, this);
}.bind(this))
this.tags = [];
}
}
isBeingRemovedLocally() {
this.tags.forEach(function(tag){

View File

@@ -16,7 +16,7 @@ class ItemParams {
}
paramsForLocalStorage() {
this.additionalFields = ["updated_at", "dirty", "editorUrl"];
this.additionalFields = ["updated_at", "dirty"];
this.forExportFile = true;
return this.__params();
}

View File

@@ -7,7 +7,7 @@ angular.module('app.frontend')
})
.state('home', {
url: '/',
url: '/?server&email&pw',
parent: 'base',
views: {
'content@' : {

View File

@@ -78,7 +78,7 @@ angular.module('app.frontend')
callback(response);
}.bind(this), function(response){
console.error("Error logging in", response);
callback(null);
callback(response);
})
}.bind(this));
@@ -106,8 +106,8 @@ angular.module('app.frontend')
callback(response);
}.bind(this), function(response){
console.error("Registration error", response);
callback(null);
})
callback(response);
}.bind(this))
}.bind(this));
}
@@ -120,7 +120,7 @@ angular.module('app.frontend')
this.handleAuthResponse(response, email, null, authParams, keys.mk, keys.pw);
callback(response);
}.bind(this), function(response){
var error = response.data;
var error = response;
if(!error) {
error = {message: "Something went wrong while changing your password. Your password was not changed. Please try again."}
}

View File

@@ -6,10 +6,10 @@ class AccountMenu {
this.scope = {};
}
controller($scope, authManager, modelManager, syncManager, $timeout) {
controller($scope, authManager, modelManager, syncManager, dbManager, $timeout) {
'ngInject';
$scope.formData = {url: syncManager.serverURL};
$scope.formData = {mergeLocal: true, url: syncManager.serverURL};
$scope.user = authManager.user;
$scope.server = syncManager.serverURL;
@@ -82,7 +82,6 @@ class AccountMenu {
$scope.loginSubmitPressed = function() {
$scope.formData.status = "Generating Login Keys...";
console.log("logging in with url", $scope.formData.url);
$timeout(function(){
authManager.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password, function(response){
if(!response || response.error) {
@@ -99,6 +98,11 @@ class AccountMenu {
}
$scope.submitRegistrationForm = function() {
var confirmation = prompt("Please confirm your password. Note that because your notes are encrypted using your password, Standard Notes does not have a password reset option. You cannot forget your password.")
if(confirmation !== $scope.formData.user_password) {
alert("The two passwords you entered do not match. Please try again.");
return;
}
$scope.formData.status = "Generating Account Keys...";
$timeout(function(){
@@ -114,10 +118,32 @@ class AccountMenu {
})
}
$scope.localNotesCount = function() {
return modelManager.filteredNotes.length;
}
$scope.mergeLocalChanged = function() {
if(!$scope.formData.mergeLocal) {
if(!confirm("Unchecking this option means any of the notes you have written while you were signed out will be deleted. Are you sure you want to discard these notes?")) {
$scope.formData.mergeLocal = true;
}
}
}
$scope.onAuthSuccess = function() {
syncManager.markAllItemsDirtyAndSaveOffline(function(){
var block = function() {
window.location.reload();
})
}
if($scope.formData.mergeLocal) {
syncManager.markAllItemsDirtyAndSaveOffline(function(){
block();
})
} else {
dbManager.clearAllItems(function(){
block();
})
}
}
$scope.destroyLocalData = function() {

View File

@@ -59,7 +59,9 @@ class ModelManager {
}
mapResponseItemsToLocalModelsOmittingFields(items, omitFields) {
var models = [];
var models = [], processedObjects = [];
// first loop should add and process items
for (var json_obj of items) {
json_obj = _.omit(json_obj, omitFields || [])
var item = this.findItem(json_obj["uuid"]);
@@ -80,11 +82,16 @@ class ModelManager {
this.addItem(item);
if(json_obj.content) {
this.resolveReferencesForItem(item);
}
models.push(item);
processedObjects.push(json_obj);
}
// second loop should process references
for (var index in processedObjects) {
var json_obj = processedObjects[index];
if(json_obj.content) {
this.resolveReferencesForItem(models[index]);
}
}
this.notifySyncObserversOfModels(models);
@@ -174,6 +181,7 @@ class ModelManager {
return;
}
for(var reference of contentObject.references) {
var referencedItem = this.findItem(reference.uuid);
if(referencedItem) {

View File

@@ -187,7 +187,6 @@ class SyncManager {
var saved = this.handleItemsResponse(response.saved_items, omitFields);
this.handleUnsavedItemsResponse(response.unsaved)
this.writeItemsToLocalStorage(saved, false, null);
this.writeItemsToLocalStorage(retrieved, false, null);
@@ -213,7 +212,7 @@ class SyncManager {
}.bind(this), function(response){
console.log("Sync error: ", response);
var error = response.data ? response.data.error : {message: "Could not connect to server."};
var error = response ? response.error : {message: "Could not connect to server."};
this.syncStatus.syncOpInProgress = false;
this.syncStatus.error = error;

View File

@@ -8,6 +8,10 @@
%input.form-control{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'formData.url'}
%input.form-control{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'formData.email'}
%input.form-control{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.user_password'}
.checkbox{"ng-if" => "localNotesCount() > 0"}
%label
%input{"type" => "checkbox", "ng-model" => "formData.mergeLocal", "ng-bind" => "true", "ng-change" => "mergeLocalChanged()"}
Merge local notes ({{localNotesCount()}} notes)
%div{"ng-if" => "!formData.status"}
%button.btn.dark-button.half-button{"ng-click" => "loginSubmitPressed()", "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
@@ -94,4 +98,4 @@
.spinner.mt-10{"ng-if" => "importData.loading"}
%a.block.mt-25.red{"ng-click" => "destroyLocalData()"} Destroy all local data
%a.block.mt-25.red{"ng-click" => "destroyLocalData()"} {{ user ? "Sign out and clear local data" : "Clear all local data" }}

View File

@@ -1,5 +1,5 @@
.section.editor{"ng-class" => "{'fullscreen' : ctrl.fullscreen}"}
.section-title-bar.editor-heading{"ng-class" => "{'fullscreen' : ctrl.fullscreen }"}
.section-title-bar.editor-heading{"ng-if" => "ctrl.note", "ng-class" => "{'fullscreen' : ctrl.fullscreen }"}
.title
%input.input#note-title-editor{"ng-model" => "ctrl.note.title", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTitle($event)",
"ng-change" => "ctrl.nameChanged()", "ng-focus" => "ctrl.onNameFocus()",
@@ -8,20 +8,19 @@
.tags
%input.tags-input{"type" => "text", "ng-keyup" => "$event.keyCode == 13 && $event.target.blur();",
"ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)"}
.section-menu
.section-menu{"ng-if" => "ctrl.note"}
%ul.nav
%li.dropdown.pull-left.mr-10{"click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"}
%a.dropdown-toggle{"ng-click" => "ctrl.showMenu = !ctrl.showMenu; ctrl.showExtensions = false;"}
File
Menu
%span.caret
%span.sr-only
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu"}
%li{"ng-click" => "ctrl.selectedMenuItem(); ctrl.toggleFullScreen()"}
.text Toggle Fullscreen
.shortcut Cmd + O
%li{"ng-click" => "ctrl.deleteNote()"}
.text Delete
.text Delete Note
%li.sep
%li.dropdown.pull-left.mr-10{"click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"}

View File

@@ -4,8 +4,8 @@
"tags" => "tags"}
%notes-section{"remove-tag" => "notesRemoveTag", "add-new" => "notesAddNew", "selection-made" => "notesSelectionMade",
"tag" => "selectedTag", "remove" => "deleteNote"}
"tag" => "selectedTag"}
%editor-section{"ng-if" => "selectedNote", "note" => "selectedNote", "remove" => "deleteNote", "save" => "saveNote", "update-tags" => "updateTagsForNote"}
%editor-section{"note" => "selectedNote", "remove" => "deleteNote", "save" => "saveNote", "update-tags" => "updateTagsForNote"}
%header

View File

@@ -10,13 +10,11 @@
%ul.nav.nav-pills
%li.dropdown
%a.dropdown-toggle{"ng-click" => "ctrl.showMenu = !ctrl.showMenu"}
Tag options
Menu
%span.caret
%span.sr-only
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu"}
%li
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagDelete()"} Delete Tag
%li
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedSortByCreated()"}
%span.top.mt-5.mr-5{"ng-if" => "ctrl.sortBy == 'created_at'"} ✓
@@ -25,10 +23,12 @@
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedSortByUpdated()"}
%span.top.mt-5.mr-5{"ng-if" => "ctrl.sortBy == 'updated_at'"} ✓
Sort by date updated
%li
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagDelete()"} Delete Tag
.scrollable
.infinite-scroll{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"}
.note{"ng-repeat" => "note in ctrl.tag.notes | filter: ctrl.filterNotes | orderBy: ctrl.sortBy:true | limitTo:ctrl.notesToDisplay",
.note{"ng-repeat" => "note in (ctrl.sortedNotes = (ctrl.tag.notes | filter: ctrl.filterNotes | orderBy: ctrl.sortBy:true | limitTo:ctrl.notesToDisplay))",
"ng-click" => "ctrl.selectNote(note)", "ng-class" => "{'selected' : ctrl.selectedNote == note}"}
.name{"ng-if" => "note.title"}
{{note.title}}

View File

@@ -1,10 +1,10 @@
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :null_session
after_action :set_csrf_cookie
after_action :allow_iframe
layout :false
def frontend
@@ -13,8 +13,13 @@ class ApplicationController < ActionController::Base
rescue_from ActionView::MissingTemplate do |exception|
end
protected
def allow_iframe
response.headers.except! 'X-Frame-Options'
end
def set_app_domain
@appDomain = request.domain
@appDomain << ':' + request.port.to_s unless request.port.blank?