Merge branch 'master' of github.com:standardnotes/web into sfv3
This commit is contained in:
@@ -40,6 +40,33 @@ angular.module('app')
|
|||||||
this.loadTagsString();
|
this.loadTagsString();
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
|
modelManager.addItemSyncObserver("component-manager", "Note", (allItems, validItems, deletedItems, source) => {
|
||||||
|
if(!this.note) { return; }
|
||||||
|
|
||||||
|
// Before checking if isMappingSourceRetrieved, we check if this item was deleted via a local source,
|
||||||
|
// such as alternating uuids during sign in. Otherwise, we only want to make interface updates if it's a
|
||||||
|
// remote retrieved source.
|
||||||
|
if(this.note.deleted) {
|
||||||
|
$rootScope.notifyDelete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!ModelManager.isMappingSourceRetrieved(source)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchingNote = allItems.find((item) => {
|
||||||
|
return item.uuid == this.note.uuid;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!matchingNote) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update tags
|
||||||
|
this.loadTagsString();
|
||||||
|
});
|
||||||
|
|
||||||
this.noteDidChange = function(note, oldNote) {
|
this.noteDidChange = function(note, oldNote) {
|
||||||
this.setNote(note, oldNote);
|
this.setNote(note, oldNote);
|
||||||
this.reloadComponentContext();
|
this.reloadComponentContext();
|
||||||
|
|||||||
@@ -145,11 +145,11 @@ angular.module('app')
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scope.tagsSelectionMade = function(tag) {
|
$scope.tagsSelectionMade = function(tag) {
|
||||||
$scope.selectedTag = tag;
|
|
||||||
|
|
||||||
if($scope.selectedNote && $scope.selectedNote.dummy) {
|
if($scope.selectedNote && $scope.selectedNote.dummy) {
|
||||||
modelManager.removeItemLocally($scope.selectedNote);
|
modelManager.removeItemLocally($scope.selectedNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.selectedTag = tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.tagsAddNew = function(tag) {
|
$scope.tagsAddNew = function(tag) {
|
||||||
@@ -227,7 +227,7 @@ angular.module('app')
|
|||||||
this.$apply(fn);
|
this.$apply(fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.notifyDelete = function() {
|
$rootScope.notifyDelete = function() {
|
||||||
$timeout(function() {
|
$timeout(function() {
|
||||||
$rootScope.$broadcast("noteDeleted");
|
$rootScope.$broadcast("noteDeleted");
|
||||||
}.bind(this), 0);
|
}.bind(this), 0);
|
||||||
@@ -243,7 +243,7 @@ angular.module('app')
|
|||||||
|
|
||||||
if(note.dummy) {
|
if(note.dummy) {
|
||||||
modelManager.removeItemLocally(note);
|
modelManager.removeItemLocally(note);
|
||||||
$scope.notifyDelete();
|
$rootScope.notifyDelete();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,11 +251,11 @@ angular.module('app')
|
|||||||
if(authManager.offline()) {
|
if(authManager.offline()) {
|
||||||
// when deleting items while ofline, we need to explictly tell angular to refresh UI
|
// when deleting items while ofline, we need to explictly tell angular to refresh UI
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
$scope.notifyDelete();
|
$rootScope.notifyDelete();
|
||||||
$scope.safeApply();
|
$scope.safeApply();
|
||||||
}, 50);
|
}, 50);
|
||||||
} else {
|
} else {
|
||||||
$scope.notifyDelete();
|
$rootScope.notifyDelete();
|
||||||
}
|
}
|
||||||
}, null, "deleteNote");
|
}, null, "deleteNote");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ angular.module('app')
|
|||||||
this.onNoteRemoval = function() {
|
this.onNoteRemoval = function() {
|
||||||
let visibleNotes = this.visibleNotes();
|
let visibleNotes = this.visibleNotes();
|
||||||
if(this.selectedIndex < visibleNotes.length) {
|
if(this.selectedIndex < visibleNotes.length) {
|
||||||
this.selectNote(visibleNotes[this.selectedIndex]);
|
this.selectNote(visibleNotes[Math.max(this.selectedIndex, 0)]);
|
||||||
} else {
|
} else {
|
||||||
this.selectNote(visibleNotes[visibleNotes.length - 1]);
|
this.selectNote(visibleNotes[visibleNotes.length - 1]);
|
||||||
}
|
}
|
||||||
@@ -190,11 +190,14 @@ angular.module('app')
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.selectNote = function(note, viaClick = false) {
|
this.selectNote = function(note, viaClick = false) {
|
||||||
if(!note) { return; }
|
if(!note) {
|
||||||
|
this.createNewNote();
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.selectedNote = note;
|
this.selectedNote = note;
|
||||||
note.conflict_of = null; // clear conflict
|
note.conflict_of = null; // clear conflict
|
||||||
this.selectionMade()(note);
|
this.selectionMade()(note);
|
||||||
this.selectedIndex = this.visibleNotes().indexOf(note);
|
this.selectedIndex = Math.max(this.visibleNotes().indexOf(note), 0);
|
||||||
|
|
||||||
if(viaClick && this.isFiltering()) {
|
if(viaClick && this.isFiltering()) {
|
||||||
desktopManager.searchText(this.noteFilter.text);
|
desktopManager.searchText(this.noteFilter.text);
|
||||||
|
|||||||
@@ -129,10 +129,12 @@ angular.module('app')
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.save()(tag, function(savedTag){
|
this.save()(tag, (savedTag) => {
|
||||||
this.selectTag(tag);
|
$timeout(() => {
|
||||||
this.newTag = null;
|
this.selectTag(tag);
|
||||||
}.bind(this));
|
this.newTag = null;
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function inputElementForTag(tag) {
|
function inputElementForTag(tag) {
|
||||||
|
|||||||
@@ -105,11 +105,18 @@ class AccountMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scope.login = function(extraParams) {
|
$scope.login = function(extraParams) {
|
||||||
|
// Prevent a timed sync from occuring while signing in. There may be a race condition where when
|
||||||
|
// calling `markAllItemsDirtyAndSaveOffline` during sign in, if an authenticated sync happens to occur
|
||||||
|
// right before that's called, items retreived from that sync will be marked as dirty, then resynced, causing mass duplication.
|
||||||
|
// Unlock sync after all sign in processes are complete.
|
||||||
|
syncManager.lockSyncing();
|
||||||
|
|
||||||
$scope.formData.status = "Generating Login Keys...";
|
$scope.formData.status = "Generating Login Keys...";
|
||||||
$timeout(function(){
|
$timeout(function(){
|
||||||
authManager.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password, $scope.formData.ephemeral, extraParams,
|
authManager.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password, $scope.formData.ephemeral, extraParams,
|
||||||
(response) => {
|
(response) => {
|
||||||
if(!response || response.error) {
|
if(!response || response.error) {
|
||||||
|
syncManager.unlockSyncing();
|
||||||
$scope.formData.status = null;
|
$scope.formData.status = null;
|
||||||
var error = response ? response.error : {message: "An unknown error occured."}
|
var error = response ? response.error : {message: "An unknown error occured."}
|
||||||
|
|
||||||
@@ -133,7 +140,10 @@ class AccountMenu {
|
|||||||
|
|
||||||
// Success
|
// Success
|
||||||
else {
|
else {
|
||||||
$scope.onAuthSuccess();
|
$scope.onAuthSuccess(() => {
|
||||||
|
syncManager.unlockSyncing();
|
||||||
|
syncManager.sync("onLogin");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -156,7 +166,9 @@ class AccountMenu {
|
|||||||
var error = response ? response.error : {message: "An unknown error occured."}
|
var error = response ? response.error : {message: "An unknown error occured."}
|
||||||
alert(error.message);
|
alert(error.message);
|
||||||
} else {
|
} else {
|
||||||
$scope.onAuthSuccess();
|
$scope.onAuthSuccess(() => {
|
||||||
|
syncManager.sync("onRegister");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -170,12 +182,12 @@ class AccountMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.onAuthSuccess = function() {
|
$scope.onAuthSuccess = function(callback) {
|
||||||
var block = function() {
|
var block = function() {
|
||||||
$timeout(function(){
|
$timeout(function(){
|
||||||
$scope.onSuccessfulAuth()();
|
$scope.onSuccessfulAuth()();
|
||||||
syncManager.refreshErroredItems();
|
syncManager.refreshErroredItems();
|
||||||
syncManager.sync("onAuthSuccess");
|
callback && callback();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +208,7 @@ class AccountMenu {
|
|||||||
// clearAllModels will remove data from backing store, but not from working memory
|
// clearAllModels will remove data from backing store, but not from working memory
|
||||||
// See: https://github.com/standardnotes/desktop/issues/131
|
// See: https://github.com/standardnotes/desktop/issues/131
|
||||||
$scope.clearDatabaseAndRewriteAllItems = function(alternateUuids, callback) {
|
$scope.clearDatabaseAndRewriteAllItems = function(alternateUuids, callback) {
|
||||||
storageManager.clearAllModels(function(){
|
storageManager.clearAllModels(() => {
|
||||||
syncManager.markAllItemsDirtyAndSaveOffline(function(){
|
syncManager.markAllItemsDirtyAndSaveOffline(function(){
|
||||||
callback && callback();
|
callback && callback();
|
||||||
}, alternateUuids)
|
}, alternateUuids)
|
||||||
|
|||||||
@@ -55,6 +55,13 @@ class Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshContentObject() {
|
||||||
|
// Before an item can be duplicated or cloned, we must update this.content (if it is an object) with the object's
|
||||||
|
// current physical properties, because updateFromJSON, which is what all new items must go through,
|
||||||
|
// will call this.mapContentToLocalProperties(this.contentObject), which may have stale values if not explicitly updated.
|
||||||
|
|
||||||
|
this.content = this.structureParams();
|
||||||
|
}
|
||||||
|
|
||||||
/* Allows the item to handle the case where the item is deleted and the content is null */
|
/* Allows the item to handle the case where the item is deleted and the content is null */
|
||||||
handleDeletedContent() {
|
handleDeletedContent() {
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ class ModelManager {
|
|||||||
ModelManager.MappingSourceRemoteActionRetrieved = "MappingSourceRemoteActionRetrieved"; /* aciton-based Extensions like note history */
|
ModelManager.MappingSourceRemoteActionRetrieved = "MappingSourceRemoteActionRetrieved"; /* aciton-based Extensions like note history */
|
||||||
ModelManager.MappingSourceFileImport = "MappingSourceFileImport";
|
ModelManager.MappingSourceFileImport = "MappingSourceFileImport";
|
||||||
|
|
||||||
|
ModelManager.isMappingSourceRetrieved = (source) => {
|
||||||
|
return [
|
||||||
|
ModelManager.MappingSourceRemoteRetrieved,
|
||||||
|
ModelManager.MappingSourceComponentRetrieved,
|
||||||
|
ModelManager.MappingSourceRemoteActionRetrieved
|
||||||
|
].includes(source);
|
||||||
|
}
|
||||||
|
|
||||||
this.storageManager = storageManager;
|
this.storageManager = storageManager;
|
||||||
this.notes = [];
|
this.notes = [];
|
||||||
this.tags = [];
|
this.tags = [];
|
||||||
@@ -44,7 +52,11 @@ class ModelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
alternateUUIDForItem(item, callback, removeOriginal) {
|
alternateUUIDForItem(item, callback, removeOriginal) {
|
||||||
// we need to clone this item and give it a new uuid, then delete item with old uuid from db (you can't mofidy uuid's in our indexeddb setup)
|
// We need to clone this item and give it a new uuid, then delete item with old uuid from db (you can't modify uuid's in our indexeddb setup)
|
||||||
|
|
||||||
|
// Collapse in memory properties to item's content object, as the new item will be created based on the content object, and not the physical properties. (like note.text or note.title)
|
||||||
|
item.refreshContentObject();
|
||||||
|
|
||||||
var newItem = this.createItem(item);
|
var newItem = this.createItem(item);
|
||||||
|
|
||||||
newItem.uuid = SFJS.crypto.generateUUID();
|
newItem.uuid = SFJS.crypto.generateUUID();
|
||||||
@@ -54,6 +66,7 @@ class ModelManager {
|
|||||||
|
|
||||||
this.informModelsOfUUIDChangeForItem(newItem, item.uuid, newItem.uuid);
|
this.informModelsOfUUIDChangeForItem(newItem, item.uuid, newItem.uuid);
|
||||||
|
|
||||||
|
|
||||||
console.log(item.uuid, "-->", newItem.uuid);
|
console.log(item.uuid, "-->", newItem.uuid);
|
||||||
|
|
||||||
var block = () => {
|
var block = () => {
|
||||||
@@ -261,7 +274,14 @@ class ModelManager {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
createDuplicateItem(itemResponse, sourceItem) {
|
/*
|
||||||
|
Be sure itemResponse is a generic Javascript object, and not an Item.
|
||||||
|
An Item needs to collapse its properties into its content object before it can be duplicated.
|
||||||
|
Note: the reason we need this function is specificallty for the call to resolveReferencesForItem.
|
||||||
|
This method creates but does not add the item to the global inventory. It's used by syncManager
|
||||||
|
to check if this prospective duplicate item is identical to another item, including the references.
|
||||||
|
*/
|
||||||
|
createDuplicateItem(itemResponse) {
|
||||||
var dup = this.createItem(itemResponse, true);
|
var dup = this.createItem(itemResponse, true);
|
||||||
this.resolveReferencesForItem(dup);
|
this.resolveReferencesForItem(dup);
|
||||||
return dup;
|
return dup;
|
||||||
|
|||||||
@@ -193,8 +193,21 @@ class SyncManager {
|
|||||||
this.$interval.cancel(this.syncStatus.checker);
|
this.$interval.cancel(this.syncStatus.checker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lockSyncing() {
|
||||||
|
this.syncLocked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unlockSyncing() {
|
||||||
|
this.syncLocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
sync(callback, options = {}, source) {
|
sync(callback, options = {}, source) {
|
||||||
|
|
||||||
|
if(this.syncLocked) {
|
||||||
|
console.log("Sync Locked, Returning;");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(!options) options = {};
|
if(!options) options = {};
|
||||||
|
|
||||||
if(typeof callback == 'string') {
|
if(typeof callback == 'string') {
|
||||||
@@ -475,7 +488,7 @@ class SyncManager {
|
|||||||
// We want a new uuid for the new item. Note that this won't neccessarily adjust references.
|
// We want a new uuid for the new item. Note that this won't neccessarily adjust references.
|
||||||
itemResponse.uuid = null;
|
itemResponse.uuid = null;
|
||||||
|
|
||||||
var dup = this.modelManager.createDuplicateItem(itemResponse, item);
|
var dup = this.modelManager.createDuplicateItem(itemResponse);
|
||||||
if(!itemResponse.deleted && !item.isItemContentEqualWith(dup)) {
|
if(!itemResponse.deleted && !item.isItemContentEqualWith(dup)) {
|
||||||
this.modelManager.addItem(dup);
|
this.modelManager.addItem(dup);
|
||||||
dup.conflict_of = item.uuid;
|
dup.conflict_of = item.uuid;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
.tag{"ng-repeat" => "tag in ctrl.tags track by tag.uuid", "ng-click" => "ctrl.selectTag(tag)", "ng-class" => "{'selected' : ctrl.selectedTag == tag}"}
|
.tag{"ng-repeat" => "tag in ctrl.tags track by tag.uuid", "ng-click" => "ctrl.selectTag(tag)", "ng-class" => "{'selected' : ctrl.selectedTag == tag}"}
|
||||||
.info
|
.info
|
||||||
%input.title{"ng-attr-id" => "tag-{{tag.uuid}}", "ng-click" => "ctrl.selectTag(tag)", "ng-model" => "tag.title",
|
%input.title{"ng-attr-id" => "tag-{{tag.uuid}}", "ng-click" => "ctrl.selectTag(tag)", "ng-model" => "tag.title",
|
||||||
"ng-keyup" => "$event.keyCode == 13 && ctrl.saveTag($event, tag)", "sn-autofocus" => "true", "should-focus" => "ctrl.newTag || ctrl.editingTag == tag",
|
"ng-keyup" => "$event.keyCode == 13 && $event.target.blur()", "sn-autofocus" => "true", "should-focus" => "ctrl.newTag || ctrl.editingTag == tag",
|
||||||
"ng-change" => "ctrl.tagTitleDidChange(tag)", "ng-blur" => "ctrl.saveTag($event, tag)", "spellcheck" => "false"}
|
"ng-change" => "ctrl.tagTitleDidChange(tag)", "ng-blur" => "ctrl.saveTag($event, tag)", "spellcheck" => "false"}
|
||||||
.count {{ctrl.noteCount(tag)}}
|
.count {{ctrl.noteCount(tag)}}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user