@@ -352,6 +352,7 @@ angular.module('app.frontend')
|
|||||||
|
|
||||||
this.onNameBlur = function() {
|
this.onNameBlur = function() {
|
||||||
this.editingName = false;
|
this.editingName = false;
|
||||||
|
this.updateTagsFromTagsString()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.toggleFullScreen = function() {
|
this.toggleFullScreen = function() {
|
||||||
@@ -366,7 +367,8 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.deleteNote = function() {
|
this.deleteNote = function() {
|
||||||
if(confirm("Are you sure you want to delete this note?")) {
|
let title = this.note.safeTitle().length ? `'${this.note.title}'` : "this note";
|
||||||
|
if(confirm(`Are you sure you want to delete ${title}?`)) {
|
||||||
this.remove()(this.note);
|
this.remove()(this.note);
|
||||||
this.showMenu = false;
|
this.showMenu = false;
|
||||||
}
|
}
|
||||||
@@ -430,9 +432,7 @@ angular.module('app.frontend')
|
|||||||
this.loadTagsString();
|
this.loadTagsString();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateTagsFromTagsString = function($event) {
|
this.updateTagsFromTagsString = function() {
|
||||||
$event.target.blur();
|
|
||||||
|
|
||||||
var tags = this.tagsString.split("#");
|
var tags = this.tagsString.split("#");
|
||||||
tags = _.filter(tags, function(tag){
|
tags = _.filter(tags, function(tag){
|
||||||
return tag.length > 0;
|
return tag.length > 0;
|
||||||
|
|||||||
@@ -80,7 +80,9 @@ angular.module('app.frontend')
|
|||||||
this.showMenu = false;
|
this.showMenu = false;
|
||||||
|
|
||||||
if(this.selectedNote && this.selectedNote.dummy) {
|
if(this.selectedNote && this.selectedNote.dummy) {
|
||||||
_.remove(oldTag.notes, this.selectedNote);
|
if(oldTag) {
|
||||||
|
_.remove(oldTag.notes, this.selectedNote);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.noteFilter.text = "";
|
this.noteFilter.text = "";
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ class Item {
|
|||||||
reference.setDirty(true);
|
reference.setDirty(true);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addObserver(observer, callback) {
|
addObserver(observer, callback) {
|
||||||
if(!_.find(this.observers, observer)) {
|
if(!_.find(this.observers, observer)) {
|
||||||
this.observers.push({observer: observer, callback: callback});
|
this.observers.push({observer: observer, callback: callback});
|
||||||
@@ -116,7 +117,7 @@ class Item {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAllRelationships() {
|
removeAndDirtyAllRelationships() {
|
||||||
// must override
|
// must override
|
||||||
this.setDirty(true);
|
this.setDirty(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ class SyncAdapter extends Item {
|
|||||||
super(json_obj);
|
super(json_obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
mapContentToLocalProperties(contentObject) {
|
mapContentToLocalProperties(content) {
|
||||||
super.mapContentToLocalProperties(contentObject)
|
super.mapContentToLocalProperties(content)
|
||||||
this.url = contentObject.url;
|
this.url = content.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
structureParams() {
|
structureParams() {
|
||||||
|
|||||||
@@ -12,22 +12,22 @@ class Component extends Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mapContentToLocalProperties(contentObject) {
|
mapContentToLocalProperties(content) {
|
||||||
super.mapContentToLocalProperties(contentObject)
|
super.mapContentToLocalProperties(content)
|
||||||
this.url = contentObject.url;
|
this.url = content.url;
|
||||||
this.name = contentObject.name;
|
this.name = content.name;
|
||||||
|
|
||||||
// the location in the view this component is located in. Valid values are currently tags-list, note-tags, and editor-stack`
|
// the location in the view this component is located in. Valid values are currently tags-list, note-tags, and editor-stack`
|
||||||
this.area = contentObject.area;
|
this.area = content.area;
|
||||||
|
|
||||||
this.permissions = contentObject.permissions;
|
this.permissions = content.permissions;
|
||||||
this.active = contentObject.active;
|
this.active = content.active;
|
||||||
|
|
||||||
// custom data that a component can store in itself
|
// custom data that a component can store in itself
|
||||||
this.componentData = contentObject.componentData || {};
|
this.componentData = content.componentData || {};
|
||||||
|
|
||||||
// items that have requested a component to be disabled in its context
|
// items that have requested a component to be disabled in its context
|
||||||
this.disassociatedItemIds = contentObject.disassociatedItemIds || [];
|
this.disassociatedItemIds = content.disassociatedItemIds || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
structureParams() {
|
structureParams() {
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ class Editor extends Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mapContentToLocalProperties(contentObject) {
|
mapContentToLocalProperties(content) {
|
||||||
super.mapContentToLocalProperties(contentObject)
|
super.mapContentToLocalProperties(content)
|
||||||
this.url = contentObject.url;
|
this.url = content.url;
|
||||||
this.name = contentObject.name;
|
this.name = content.name;
|
||||||
this.data = contentObject.data || {};
|
this.data = content.data || {};
|
||||||
this.default = contentObject.default;
|
this.default = content.default;
|
||||||
this.systemEditor = contentObject.systemEditor;
|
this.systemEditor = content.systemEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
structureParams() {
|
structureParams() {
|
||||||
@@ -56,8 +56,8 @@ class Editor extends Item {
|
|||||||
super.removeItemAsRelationship(item);
|
super.removeItemAsRelationship(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAllRelationships() {
|
removeAndDirtyAllRelationships() {
|
||||||
super.removeAllRelationships();
|
super.removeAndDirtyAllRelationships();
|
||||||
this.notes = [];
|
this.notes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,16 +51,21 @@ class Action {
|
|||||||
class Extension extends Item {
|
class Extension extends Item {
|
||||||
constructor(json) {
|
constructor(json) {
|
||||||
super(json);
|
super(json);
|
||||||
_.merge(this, json);
|
|
||||||
|
|
||||||
this.encrypted = true;
|
if(this.encrypted === null || this.encrypted === undefined) {
|
||||||
this.content_type = "Extension";
|
// Default to encrypted on creation.
|
||||||
|
this.encrypted = true;
|
||||||
|
}
|
||||||
|
|
||||||
if(json.actions) {
|
if(json.actions) {
|
||||||
this.actions = json.actions.map(function(action){
|
this.actions = json.actions.map(function(action){
|
||||||
return new Action(action);
|
return new Action(action);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!this.actions) {
|
||||||
|
this.actions = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsInGlobalContext() {
|
actionsInGlobalContext() {
|
||||||
@@ -75,39 +80,42 @@ class Extension extends Item {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
mapContentToLocalProperties(contentObject) {
|
mapContentToLocalProperties(content) {
|
||||||
super.mapContentToLocalProperties(contentObject)
|
super.mapContentToLocalProperties(content)
|
||||||
this.name = contentObject.name;
|
this.name = content.name;
|
||||||
this.description = contentObject.description;
|
this.description = content.description;
|
||||||
this.url = contentObject.url;
|
this.url = content.url;
|
||||||
this.supported_types = contentObject.supported_types;
|
|
||||||
if(contentObject.actions) {
|
if(content.encrypted !== null && content.encrypted !== undefined) {
|
||||||
this.actions = contentObject.actions.map(function(action){
|
this.encrypted = content.encrypted;
|
||||||
|
} else {
|
||||||
|
this.encrypted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.supported_types = content.supported_types;
|
||||||
|
if(content.actions) {
|
||||||
|
this.actions = content.actions.map(function(action){
|
||||||
return new Action(action);
|
return new Action(action);
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
this.actions = [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFromExternalResponseItem(externalResponseItem) {
|
|
||||||
_.merge(this, externalResponseItem);
|
|
||||||
this.actions = externalResponseItem.actions.map(function(action){
|
|
||||||
return new Action(action);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
referenceParams() {
|
referenceParams() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get content_type() {
|
||||||
|
return "Extension";
|
||||||
|
}
|
||||||
|
|
||||||
structureParams() {
|
structureParams() {
|
||||||
var params = {
|
var params = {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
url: this.url,
|
url: this.url,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
actions: this.actions,
|
actions: this.actions,
|
||||||
supported_types: this.supported_types
|
supported_types: this.supported_types,
|
||||||
|
encrypted: this.encrypted
|
||||||
};
|
};
|
||||||
|
|
||||||
_.merge(params, super.structureParams());
|
_.merge(params, super.structureParams());
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ class Note extends Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mapContentToLocalProperties(contentObject) {
|
mapContentToLocalProperties(content) {
|
||||||
super.mapContentToLocalProperties(contentObject)
|
super.mapContentToLocalProperties(content)
|
||||||
this.title = contentObject.title;
|
this.title = content.title;
|
||||||
this.text = contentObject.text;
|
this.text = content.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
referenceParams() {
|
referenceParams() {
|
||||||
@@ -48,7 +48,7 @@ class Note extends Item {
|
|||||||
super.removeItemAsRelationship(item);
|
super.removeItemAsRelationship(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAllRelationships() {
|
removeAndDirtyAllRelationships() {
|
||||||
this.tags.forEach(function(tag){
|
this.tags.forEach(function(tag){
|
||||||
_.pull(tag.notes, this);
|
_.pull(tag.notes, this);
|
||||||
tag.setDirty(true);
|
tag.setDirty(true);
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ class Tag extends Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mapContentToLocalProperties(contentObject) {
|
mapContentToLocalProperties(content) {
|
||||||
super.mapContentToLocalProperties(contentObject)
|
super.mapContentToLocalProperties(content)
|
||||||
this.title = contentObject.title;
|
this.title = content.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
referenceParams() {
|
referenceParams() {
|
||||||
@@ -46,7 +46,7 @@ class Tag extends Item {
|
|||||||
super.removeItemAsRelationship(item);
|
super.removeItemAsRelationship(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAllRelationships() {
|
removeAndDirtyAllRelationships() {
|
||||||
this.notes.forEach(function(note){
|
this.notes.forEach(function(note){
|
||||||
_.pull(note.tags, this);
|
_.pull(note.tags, this);
|
||||||
note.setDirty(true);
|
note.setDirty(true);
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ class Theme extends Item {
|
|||||||
super(json_obj);
|
super(json_obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
mapContentToLocalProperties(contentObject) {
|
mapContentToLocalProperties(content) {
|
||||||
super.mapContentToLocalProperties(contentObject)
|
super.mapContentToLocalProperties(content)
|
||||||
this.url = contentObject.url;
|
this.url = content.url;
|
||||||
this.name = contentObject.name;
|
this.name = content.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
structureParams() {
|
structureParams() {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ class EncryptedStorage extends Item {
|
|||||||
super(json_obj);
|
super(json_obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
mapContentToLocalProperties(contentObject) {
|
mapContentToLocalProperties(content) {
|
||||||
super.mapContentToLocalProperties(contentObject)
|
super.mapContentToLocalProperties(content)
|
||||||
this.storage = contentObject.storage;
|
this.storage = content.storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
structureParams() {
|
structureParams() {
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ angular.module('app.frontend')
|
|||||||
storageManager.setModelStorageMode(StorageManager.Ephemeral);
|
storageManager.setModelStorageMode(StorageManager.Ephemeral);
|
||||||
storageManager.setItemsMode(storageManager.hasPasscode() ? StorageManager.FixedEncrypted : StorageManager.Ephemeral);
|
storageManager.setItemsMode(storageManager.hasPasscode() ? StorageManager.FixedEncrypted : StorageManager.Ephemeral);
|
||||||
} else {
|
} else {
|
||||||
|
storageManager.setModelStorageMode(StorageManager.Fixed);
|
||||||
|
storageManager.setItemsMode(storageManager.hasPasscode() ? StorageManager.FixedEncrypted : StorageManager.Fixed);
|
||||||
|
|
||||||
storageManager.setItem("ephemeral", JSON.stringify(false), StorageManager.Fixed);
|
storageManager.setItem("ephemeral", JSON.stringify(false), StorageManager.Fixed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,12 +153,8 @@ angular.module('app.frontend')
|
|||||||
var params = {password: keys.pw, email: email};
|
var params = {password: keys.pw, email: email};
|
||||||
httpManager.postAbsolute(requestUrl, params, function(response){
|
httpManager.postAbsolute(requestUrl, params, function(response){
|
||||||
this.setEphemeral(ephemeral);
|
this.setEphemeral(ephemeral);
|
||||||
|
|
||||||
this.handleAuthResponse(response, email, url, authParams, keys);
|
this.handleAuthResponse(response, email, url, authParams, keys);
|
||||||
storageManager.setModelStorageMode(ephemeral ? StorageManager.Ephemeral : StorageManager.Fixed);
|
|
||||||
|
|
||||||
this.checkForSecurityUpdate();
|
this.checkForSecurityUpdate();
|
||||||
|
|
||||||
callback(response);
|
callback(response);
|
||||||
}.bind(this), function(response){
|
}.bind(this), function(response){
|
||||||
console.error("Error logging in", response);
|
console.error("Error logging in", response);
|
||||||
@@ -199,11 +198,8 @@ angular.module('app.frontend')
|
|||||||
|
|
||||||
httpManager.postAbsolute(requestUrl, params, function(response){
|
httpManager.postAbsolute(requestUrl, params, function(response){
|
||||||
this.setEphemeral(ephemeral);
|
this.setEphemeral(ephemeral);
|
||||||
|
|
||||||
this.handleAuthResponse(response, email, url, authParams, keys);
|
this.handleAuthResponse(response, email, url, authParams, keys);
|
||||||
|
|
||||||
storageManager.setModelStorageMode(ephemeral ? StorageManager.Ephemeral : StorageManager.Fixed);
|
|
||||||
|
|
||||||
callback(response);
|
callback(response);
|
||||||
}.bind(this), function(response){
|
}.bind(this), function(response){
|
||||||
console.error("Registration error", response);
|
console.error("Registration error", response);
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ class ComponentManager {
|
|||||||
this.handleMessage(this.componentForSessionKey(event.data.sessionKey), event.data);
|
this.handleMessage(this.componentForSessionKey(event.data.sessionKey), event.data);
|
||||||
}.bind(this), false);
|
}.bind(this), false);
|
||||||
|
|
||||||
this.modelManager.addItemSyncObserver("component-manager", "*", function(items) {
|
this.modelManager.addItemSyncObserver("component-manager", "*", function(allItems, validItems, deletedItems) {
|
||||||
|
|
||||||
var syncedComponents = items.filter(function(item){return item.content_type === "SN|Component" });
|
var syncedComponents = allItems.filter(function(item){return item.content_type === "SN|Component" });
|
||||||
for(var component of syncedComponents) {
|
for(var component of syncedComponents) {
|
||||||
var activeComponent = _.find(this.activeComponents, {uuid: component.uuid});
|
var activeComponent = _.find(this.activeComponents, {uuid: component.uuid});
|
||||||
if(component.active && !activeComponent) {
|
if(component.active && !component.deleted && !activeComponent) {
|
||||||
this.activateComponent(component);
|
this.activateComponent(component);
|
||||||
} else if(!component.active && activeComponent) {
|
} else if(!component.active && activeComponent) {
|
||||||
this.deactivateComponent(component);
|
this.deactivateComponent(component);
|
||||||
@@ -41,7 +41,7 @@ class ComponentManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(let observer of this.streamObservers) {
|
for(let observer of this.streamObservers) {
|
||||||
var relevantItems = items.filter(function(item){
|
var relevantItems = allItems.filter(function(item){
|
||||||
return observer.contentTypes.indexOf(item.content_type) !== -1;
|
return observer.contentTypes.indexOf(item.content_type) !== -1;
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ class ComponentManager {
|
|||||||
}
|
}
|
||||||
var itemInContext = handler.contextRequestHandler(observer.component);
|
var itemInContext = handler.contextRequestHandler(observer.component);
|
||||||
if(itemInContext) {
|
if(itemInContext) {
|
||||||
var matchingItem = _.find(items, {uuid: itemInContext.uuid});
|
var matchingItem = _.find(allItems, {uuid: itemInContext.uuid});
|
||||||
if(matchingItem) {
|
if(matchingItem) {
|
||||||
this.sendContextItemInReply(observer.component, matchingItem, observer.originalMessage);
|
this.sendContextItemInReply(observer.component, matchingItem, observer.originalMessage);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class ContextualExtensionsMenu {
|
|||||||
|
|
||||||
$scope.isActionEnabled = function(action, extension) {
|
$scope.isActionEnabled = function(action, extension) {
|
||||||
if(action.access_type) {
|
if(action.access_type) {
|
||||||
var extEncryptedAccess = extensionManager.extensionUsesEncryptedData(extension);
|
var extEncryptedAccess = extension.encrypted;
|
||||||
if(action.access_type == "decrypted" && extEncryptedAccess) {
|
if(action.access_type == "decrypted" && extEncryptedAccess) {
|
||||||
return false;
|
return false;
|
||||||
} else if(action.access_type == "encrypted" && !extEncryptedAccess) {
|
} else if(action.access_type == "encrypted" && !extEncryptedAccess) {
|
||||||
@@ -77,7 +77,7 @@ class ContextualExtensionsMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scope.accessTypeForExtension = function(extension) {
|
$scope.accessTypeForExtension = function(extension) {
|
||||||
return extensionManager.extensionUsesEncryptedData(extension) ? "encrypted" : "decrypted";
|
return extension.encrypted ? "encrypted" : "decrypted";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ class GlobalExtensionsMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scope.changeExtensionEncryptionFormat = function(encrypted, extension) {
|
$scope.changeExtensionEncryptionFormat = function(encrypted, extension) {
|
||||||
extensionManager.changeExtensionEncryptionFormat(encrypted, extension);
|
extension.encrypted = encrypted;
|
||||||
|
extension.setDirty(true);
|
||||||
|
syncManager.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.deleteActionExtension = function(extension) {
|
$scope.deleteActionExtension = function(extension) {
|
||||||
@@ -55,6 +57,36 @@ class GlobalExtensionsMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.renameExtension = function(extension) {
|
||||||
|
extension.tempName = extension.name;
|
||||||
|
extension.rename = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.submitExtensionRename = function(extension) {
|
||||||
|
extension.name = extension.tempName;
|
||||||
|
extension.tempName = null;
|
||||||
|
extension.setDirty(true);
|
||||||
|
extension.rename = false;
|
||||||
|
syncManager.sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.clickedExtension = function(extension) {
|
||||||
|
if(extension.rename) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($scope.currentlyExpandedExtension && $scope.currentlyExpandedExtension !== extension) {
|
||||||
|
$scope.currentlyExpandedExtension.showDetails = false;
|
||||||
|
$scope.currentlyExpandedExtension.rename = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension.showDetails = !extension.showDetails;
|
||||||
|
|
||||||
|
if(extension.showDetails) {
|
||||||
|
$scope.currentlyExpandedExtension = extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Server extensions
|
// Server extensions
|
||||||
|
|
||||||
$scope.deleteServerExt = function(ext) {
|
$scope.deleteServerExt = function(ext) {
|
||||||
|
|||||||
@@ -5,15 +5,11 @@ class ExtensionManager {
|
|||||||
this.modelManager = modelManager;
|
this.modelManager = modelManager;
|
||||||
this.authManager = authManager;
|
this.authManager = authManager;
|
||||||
this.enabledRepeatActionUrls = JSON.parse(storageManager.getItem("enabledRepeatActionUrls")) || [];
|
this.enabledRepeatActionUrls = JSON.parse(storageManager.getItem("enabledRepeatActionUrls")) || [];
|
||||||
this.decryptedExtensions = JSON.parse(storageManager.getItem("decryptedExtensions")) || [];
|
|
||||||
this.syncManager = syncManager;
|
this.syncManager = syncManager;
|
||||||
this.storageManager = storageManager;
|
this.storageManager = storageManager;
|
||||||
|
|
||||||
modelManager.addItemSyncObserver("extensionManager", "Extension", function(items){
|
modelManager.addItemSyncObserver("extensionManager", "Extension", function(allItems, validItems, deletedItems){
|
||||||
for (var ext of items) {
|
for (var ext of validItems) {
|
||||||
|
|
||||||
ext.encrypted = this.extensionUsesEncryptedData(ext);
|
|
||||||
|
|
||||||
for (var action of ext.actions) {
|
for (var action of ext.actions) {
|
||||||
if(_.includes(this.enabledRepeatActionUrls, action.url)) {
|
if(_.includes(this.enabledRepeatActionUrls, action.url)) {
|
||||||
this.enableRepeatAction(action, ext);
|
this.enableRepeatAction(action, ext);
|
||||||
@@ -39,29 +35,12 @@ class ExtensionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extensionUsesEncryptedData(extension) {
|
|
||||||
return !_.includes(this.decryptedExtensions, extension.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
changeExtensionEncryptionFormat(encrypted, extension) {
|
|
||||||
if(encrypted) {
|
|
||||||
_.pull(this.decryptedExtensions, extension.url);
|
|
||||||
} else {
|
|
||||||
this.decryptedExtensions.push(extension.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.storageManager.setItem("decryptedExtensions", JSON.stringify(this.decryptedExtensions))
|
|
||||||
|
|
||||||
extension.encrypted = this.extensionUsesEncryptedData(extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
addExtension(url, callback) {
|
addExtension(url, callback) {
|
||||||
this.retrieveExtensionFromServer(url, callback);
|
this.retrieveExtensionFromServer(url, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteExtension(extension) {
|
deleteExtension(extension) {
|
||||||
for(var action of extension.actions) {
|
for(var action of extension.actions) {
|
||||||
_.pull(this.decryptedExtensions, extension);
|
|
||||||
if(action.repeat_mode) {
|
if(action.repeat_mode) {
|
||||||
if(this.isRepeatActionEnabled(action)) {
|
if(this.isRepeatActionEnabled(action)) {
|
||||||
this.disableRepeatAction(action);
|
this.disableRepeatAction(action);
|
||||||
@@ -80,18 +59,10 @@ class ExtensionManager {
|
|||||||
loadExtensionInContextOfItem(extension, item, callback) {
|
loadExtensionInContextOfItem(extension, item, callback) {
|
||||||
|
|
||||||
this.httpManager.getAbsolute(extension.url, {content_type: item.content_type, item_uuid: item.uuid}, function(response){
|
this.httpManager.getAbsolute(extension.url, {content_type: item.content_type, item_uuid: item.uuid}, function(response){
|
||||||
var scopedExtension = new Extension(response);
|
this.updateExtensionFromRemoteResponse(extension, response);
|
||||||
if(scopedExtension) {
|
callback && callback(extension);
|
||||||
_.merge(extension, scopedExtension);
|
|
||||||
extension.actions = scopedExtension.actions;
|
|
||||||
extension.encrypted = this.extensionUsesEncryptedData(extension);
|
|
||||||
}
|
|
||||||
if(callback) {
|
|
||||||
callback(scopedExtension);
|
|
||||||
}
|
|
||||||
}.bind(this), function(response){
|
}.bind(this), function(response){
|
||||||
console.log("Error loading extension", response);
|
console.log("Error loading extension", response);
|
||||||
extension.encrypted = this.extensionUsesEncryptedData(extension);
|
|
||||||
if(callback) {
|
if(callback) {
|
||||||
callback(null);
|
callback(null);
|
||||||
}
|
}
|
||||||
@@ -118,9 +89,13 @@ class ExtensionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleExtensionLoadExternalResponseItem(url, externalResponseItem) {
|
handleExtensionLoadExternalResponseItem(url, externalResponseItem) {
|
||||||
|
// Don't allow remote response to set these flags
|
||||||
|
delete externalResponseItem.encrypted;
|
||||||
|
delete externalResponseItem.uuid;
|
||||||
|
|
||||||
var extension = _.find(this.extensions, {url: url});
|
var extension = _.find(this.extensions, {url: url});
|
||||||
if(extension) {
|
if(extension) {
|
||||||
extension.updateFromExternalResponseItem(externalResponseItem);
|
this.updateExtensionFromRemoteResponse(extension, externalResponseItem);
|
||||||
} else {
|
} else {
|
||||||
extension = new Extension(externalResponseItem);
|
extension = new Extension(externalResponseItem);
|
||||||
extension.url = url;
|
extension.url = url;
|
||||||
@@ -132,6 +107,23 @@ class ExtensionManager {
|
|||||||
return extension;
|
return extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateExtensionFromRemoteResponse(extension, response) {
|
||||||
|
if(response.description) {
|
||||||
|
extension.description = response.description;
|
||||||
|
}
|
||||||
|
if(response.supported_types) {
|
||||||
|
extension.supported_types = response.supported_types;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(response.actions) {
|
||||||
|
extension.actions = response.actions.map(function(action){
|
||||||
|
return new Action(action);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
extension.actions = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
refreshExtensionsFromServer() {
|
refreshExtensionsFromServer() {
|
||||||
for (var url of this.enabledRepeatActionUrls) {
|
for (var url of this.enabledRepeatActionUrls) {
|
||||||
var action = this.actionWithURL(url);
|
var action = this.actionWithURL(url);
|
||||||
@@ -149,7 +141,7 @@ class ExtensionManager {
|
|||||||
|
|
||||||
executeAction(action, extension, item, callback) {
|
executeAction(action, extension, item, callback) {
|
||||||
|
|
||||||
if(this.extensionUsesEncryptedData(extension) && this.authManager.offline()) {
|
if(extension.encrypted && this.userManager.offline()) {
|
||||||
alert("To send data encrypted, you must have an encryption key, and must therefore be signed in.");
|
alert("To send data encrypted, you must have an encryption key, and must therefore be signed in.");
|
||||||
callback(null);
|
callback(null);
|
||||||
return;
|
return;
|
||||||
@@ -274,11 +266,9 @@ class ExtensionManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log("Successfully queued", action, this.actionQueue.length);
|
|
||||||
this.actionQueue.push(action);
|
this.actionQueue.push(action);
|
||||||
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
// console.log("Performing queued action", action);
|
|
||||||
this.triggerWatchAction(action, extension, changedItems);
|
this.triggerWatchAction(action, extension, changedItems);
|
||||||
_.pull(this.actionQueue, action);
|
_.pull(this.actionQueue, action);
|
||||||
}.bind(this), delay * 1000);
|
}.bind(this), delay * 1000);
|
||||||
@@ -297,8 +287,6 @@ class ExtensionManager {
|
|||||||
|
|
||||||
action.lastExecuted = new Date();
|
action.lastExecuted = new Date();
|
||||||
|
|
||||||
console.log("Performing action.");
|
|
||||||
|
|
||||||
if(action.verb == "post") {
|
if(action.verb == "post") {
|
||||||
var params = {};
|
var params = {};
|
||||||
params.items = changedItems.map(function(item){
|
params.items = changedItems.map(function(item){
|
||||||
@@ -316,8 +304,8 @@ class ExtensionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
outgoingParamsForItem(item, extension) {
|
outgoingParamsForItem(item, extension) {
|
||||||
var keys = this.authManager.keys();
|
var keys = this.userManager.keys();
|
||||||
if(!this.extensionUsesEncryptedData(extension)) {
|
if(!extension.encrypted) {
|
||||||
keys = null;
|
keys = null;
|
||||||
}
|
}
|
||||||
var itemParams = new ItemParams(item, keys, this.authManager.protocolVersion());
|
var itemParams = new ItemParams(item, keys, this.authManager.protocolVersion());
|
||||||
@@ -326,8 +314,8 @@ class ExtensionManager {
|
|||||||
|
|
||||||
performPost(action, extension, params, callback) {
|
performPost(action, extension, params, callback) {
|
||||||
|
|
||||||
if(this.extensionUsesEncryptedData(extension)) {
|
if(extension.encrypted) {
|
||||||
params.auth_params = this.authManager.getAuthParams();
|
params.auth_params = this.userManager.getAuthParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.httpManager.postAbsolute(action.url, params, function(response){
|
this.httpManager.postAbsolute(action.url, params, function(response){
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ class ModelManager {
|
|||||||
this.tags = [];
|
this.tags = [];
|
||||||
this.itemSyncObservers = [];
|
this.itemSyncObservers = [];
|
||||||
this.itemChangeObservers = [];
|
this.itemChangeObservers = [];
|
||||||
|
this.itemsPendingRemoval = [];
|
||||||
this.items = [];
|
this.items = [];
|
||||||
this._extensions = [];
|
this._extensions = [];
|
||||||
this.acceptableContentTypes = ["Note", "Tag", "Extension", "SN|Editor", "SN|Theme", "SN|Component", "SF|Extension"];
|
this.acceptableContentTypes = [
|
||||||
|
"Note", "Tag", "Extension", "SN|Editor", "SN|Theme",
|
||||||
|
"SN|Component", "SF|Extension", "SN|UserPreferences"
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
resetLocalMemory() {
|
resetLocalMemory() {
|
||||||
@@ -30,7 +34,7 @@ class ModelManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
alternateUUIDForItem(item, callback) {
|
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 mofidy uuid's in our indexeddb setup)
|
||||||
var newItem = this.createItem(item);
|
var newItem = this.createItem(item);
|
||||||
|
|
||||||
@@ -41,12 +45,20 @@ class ModelManager {
|
|||||||
|
|
||||||
this.informModelsOfUUIDChangeForItem(newItem, item.uuid, newItem.uuid);
|
this.informModelsOfUUIDChangeForItem(newItem, item.uuid, newItem.uuid);
|
||||||
|
|
||||||
this.removeItemLocally(item, function(){
|
var block = () => {
|
||||||
this.addItem(newItem);
|
this.addItem(newItem);
|
||||||
newItem.setDirty(true);
|
newItem.setDirty(true);
|
||||||
newItem.markAllReferencesDirty();
|
newItem.markAllReferencesDirty();
|
||||||
callback();
|
callback();
|
||||||
}.bind(this));
|
}
|
||||||
|
|
||||||
|
if(removeOriginal) {
|
||||||
|
this.removeItemLocally(item, function(){
|
||||||
|
block();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
block();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
informModelsOfUUIDChangeForItem(newItem, oldUUID, newUUID) {
|
informModelsOfUUIDChangeForItem(newItem, oldUUID, newUUID) {
|
||||||
@@ -89,23 +101,33 @@ class ModelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mapResponseItemsToLocalModelsOmittingFields(items, omitFields) {
|
mapResponseItemsToLocalModelsOmittingFields(items, omitFields) {
|
||||||
var models = [], processedObjects = [], allModels = [];
|
var models = [], processedObjects = [], modelsToNotifyObserversOf = [];
|
||||||
|
|
||||||
// first loop should add and process items
|
// first loop should add and process items
|
||||||
for (var json_obj of items) {
|
for (var json_obj of items) {
|
||||||
json_obj = _.omit(json_obj, omitFields || [])
|
if((!json_obj.content_type || !json_obj.content) && !json_obj.deleted) {
|
||||||
var item = this.findItem(json_obj["uuid"]);
|
// An item that is not deleted should never have empty content
|
||||||
|
console.error("Server response item is corrupt:", json_obj);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
_.omit(json_obj, omitFields);
|
json_obj = _.omit(json_obj, omitFields || [])
|
||||||
|
var item = this.findItem(json_obj.uuid);
|
||||||
|
|
||||||
if(item) {
|
if(item) {
|
||||||
item.updateFromJSON(json_obj);
|
item.updateFromJSON(json_obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(json_obj["deleted"] == true || !_.includes(this.acceptableContentTypes, json_obj["content_type"])) {
|
if(this.itemsPendingRemoval.includes(json_obj.uuid)) {
|
||||||
if(item) {
|
_.pull(this.itemsPendingRemoval, json_obj.uuid);
|
||||||
allModels.push(item);
|
continue;
|
||||||
this.removeItemLocally(item)
|
}
|
||||||
|
|
||||||
|
var unknownContentType = !_.includes(this.acceptableContentTypes, json_obj["content_type"]);
|
||||||
|
if(json_obj.deleted == true || unknownContentType) {
|
||||||
|
if(item && !unknownContentType) {
|
||||||
|
modelsToNotifyObserversOf.push(item);
|
||||||
|
this.removeItemLocally(item);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -116,7 +138,7 @@ class ModelManager {
|
|||||||
|
|
||||||
this.addItem(item);
|
this.addItem(item);
|
||||||
|
|
||||||
allModels.push(item);
|
modelsToNotifyObserversOf.push(item);
|
||||||
models.push(item);
|
models.push(item);
|
||||||
processedObjects.push(json_obj);
|
processedObjects.push(json_obj);
|
||||||
}
|
}
|
||||||
@@ -129,16 +151,25 @@ class ModelManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.notifySyncObserversOfModels(allModels);
|
this.notifySyncObserversOfModels(modelsToNotifyObserversOf);
|
||||||
|
|
||||||
return models;
|
return models;
|
||||||
}
|
}
|
||||||
|
|
||||||
notifySyncObserversOfModels(models) {
|
notifySyncObserversOfModels(models) {
|
||||||
for(var observer of this.itemSyncObservers) {
|
for(var observer of this.itemSyncObservers) {
|
||||||
var relevantItems = models.filter(function(item){return item.content_type == observer.type || observer.type == "*"});
|
var allRelevantItems = models.filter(function(item){return item.content_type == observer.type || observer.type == "*"});
|
||||||
if(relevantItems.length > 0) {
|
var validItems = [], deletedItems = [];
|
||||||
observer.callback(relevantItems);
|
for(var item of allRelevantItems) {
|
||||||
|
if(item.deleted) {
|
||||||
|
deletedItems.push(item);
|
||||||
|
} else {
|
||||||
|
validItems.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(allRelevantItems.length > 0) {
|
||||||
|
observer.callback(allRelevantItems, validItems, deletedItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,6 +215,12 @@ class ModelManager {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createDuplicateItem(itemResponse, sourceItem) {
|
||||||
|
var dup = this.createItem(itemResponse);
|
||||||
|
this.resolveReferencesForItem(dup);
|
||||||
|
return dup;
|
||||||
|
}
|
||||||
|
|
||||||
addItems(items) {
|
addItems(items) {
|
||||||
items.forEach(function(item){
|
items.forEach(function(item){
|
||||||
if(item.content_type == "Tag") {
|
if(item.content_type == "Tag") {
|
||||||
@@ -283,7 +320,7 @@ class ModelManager {
|
|||||||
if(!item.dummy) {
|
if(!item.dummy) {
|
||||||
item.setDirty(true);
|
item.setDirty(true);
|
||||||
}
|
}
|
||||||
item.removeAllRelationships();
|
item.removeAndDirtyAllRelationships();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Used when changing encryption key */
|
/* Used when changing encryption key */
|
||||||
@@ -302,6 +339,8 @@ class ModelManager {
|
|||||||
|
|
||||||
item.isBeingRemovedLocally();
|
item.isBeingRemovedLocally();
|
||||||
|
|
||||||
|
this.itemsPendingRemoval.push(item.uuid);
|
||||||
|
|
||||||
if(item.content_type == "Tag") {
|
if(item.content_type == "Tag") {
|
||||||
_.pull(this.tags, item);
|
_.pull(this.tags, item);
|
||||||
} else if(item.content_type == "Note") {
|
} else if(item.content_type == "Note") {
|
||||||
@@ -324,14 +363,6 @@ class ModelManager {
|
|||||||
itemOne.setDirty(true);
|
itemOne.setDirty(true);
|
||||||
itemTwo.setDirty(true);
|
itemTwo.setDirty(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeRelationshipBetweenItems(itemOne, itemTwo) {
|
|
||||||
itemOne.removeItemAsRelationship(itemTwo);
|
|
||||||
itemTwo.removeItemAsRelationship(itemOne);
|
|
||||||
|
|
||||||
itemOne.setDirty(true);
|
|
||||||
itemTwo.setDirty(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
angular.module('app.frontend').service('modelManager', ModelManager);
|
angular.module('app.frontend').service('modelManager', ModelManager);
|
||||||
|
|||||||
@@ -42,11 +42,14 @@ class StorageManager {
|
|||||||
if(hasPasscode) {
|
if(hasPasscode) {
|
||||||
// We don't want to save anything in fixed storage except for actual item data (in IndexedDB)
|
// We don't want to save anything in fixed storage except for actual item data (in IndexedDB)
|
||||||
this.storage = this.memoryStorage;
|
this.storage = this.memoryStorage;
|
||||||
|
this.itemsStorageMode = StorageManager.FixedEncrypted;
|
||||||
} else if(ephemeral) {
|
} else if(ephemeral) {
|
||||||
// We don't want to save anything in fixed storage as well as IndexedDB
|
// We don't want to save anything in fixed storage as well as IndexedDB
|
||||||
this.storage = this.memoryStorage;
|
this.storage = this.memoryStorage;
|
||||||
|
this.itemsStorageMode = StorageManager.Ephemeral;
|
||||||
} else {
|
} else {
|
||||||
this.storage = localStorage;
|
this.storage = localStorage;
|
||||||
|
this.itemsStorageMode = StorageManager.Fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.modelStorageMode = ephemeral ? StorageManager.Ephemeral : StorageManager.Fixed;
|
this.modelStorageMode = ephemeral ? StorageManager.Ephemeral : StorageManager.Fixed;
|
||||||
@@ -69,6 +72,7 @@ class StorageManager {
|
|||||||
newStorage.setItem(key, this.storage.getItem(key));
|
newStorage.setItem(key, this.storage.getItem(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.itemsStorageMode = mode;
|
||||||
this.storage.clear();
|
this.storage.clear();
|
||||||
this.storage = newStorage;
|
this.storage = newStorage;
|
||||||
|
|
||||||
@@ -83,25 +87,21 @@ class StorageManager {
|
|||||||
|
|
||||||
getVault(vaultKey) {
|
getVault(vaultKey) {
|
||||||
if(vaultKey) {
|
if(vaultKey) {
|
||||||
return this.storageForVault(vaultKey);
|
if(vaultKey == StorageManager.Ephemeral || vaultKey == StorageManager.FixedEncrypted) {
|
||||||
|
return this.memoryStorage;
|
||||||
|
} else {
|
||||||
|
return localStorage;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return this.storage;
|
return this.storage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
storageForVault(vault) {
|
setItem(key, value, vaultKey) {
|
||||||
if(vault == StorageManager.Ephemeral || vault == StorageManager.FixedEncrypted) {
|
var storage = this.getVault(vaultKey);
|
||||||
return this.memoryStorage;
|
|
||||||
} else {
|
|
||||||
return localStorage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setItem(key, value, vault) {
|
|
||||||
var storage = this.getVault(vault);
|
|
||||||
storage.setItem(key, value);
|
storage.setItem(key, value);
|
||||||
|
|
||||||
if(vault === StorageManager.FixedEncrypted) {
|
if(vaultKey === StorageManager.FixedEncrypted || (!vaultKey && this.itemsStorageMode === StorageManager.FixedEncrypted)) {
|
||||||
this.writeEncryptedStorageToDisk();
|
this.writeEncryptedStorageToDisk();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,6 +147,7 @@ class StorageManager {
|
|||||||
var encryptedStorage = new EncryptedStorage();
|
var encryptedStorage = new EncryptedStorage();
|
||||||
// Copy over totality of current storage
|
// Copy over totality of current storage
|
||||||
encryptedStorage.storage = this.storageAsHash();
|
encryptedStorage.storage = this.storageAsHash();
|
||||||
|
|
||||||
// Save new encrypted storage in Fixed storage
|
// Save new encrypted storage in Fixed storage
|
||||||
var params = new ItemParams(encryptedStorage, this.encryptedStorageKeys);
|
var params = new ItemParams(encryptedStorage, this.encryptedStorageKeys);
|
||||||
this.setItem("encryptedStorage", JSON.stringify(params.paramsForSync()), StorageManager.Fixed);
|
this.setItem("encryptedStorage", JSON.stringify(params.paramsForSync()), StorageManager.Fixed);
|
||||||
@@ -160,6 +161,7 @@ class StorageManager {
|
|||||||
for(var key of Object.keys(encryptedStorage.storage)) {
|
for(var key of Object.keys(encryptedStorage.storage)) {
|
||||||
this.setItem(key, encryptedStorage.storage[key]);
|
this.setItem(key, encryptedStorage.storage[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPasscode() {
|
hasPasscode() {
|
||||||
|
|||||||
@@ -75,13 +75,16 @@ class SyncManager {
|
|||||||
Alternating here forces us to to create duplicates of the items instead.
|
Alternating here forces us to to create duplicates of the items instead.
|
||||||
*/
|
*/
|
||||||
markAllItemsDirtyAndSaveOffline(callback, alternateUUIDs) {
|
markAllItemsDirtyAndSaveOffline(callback, alternateUUIDs) {
|
||||||
var originalItems = this.modelManager.allItems;
|
|
||||||
|
|
||||||
var block = (items) => {
|
// use a copy, as alternating uuid will affect array
|
||||||
for(var item of items) {
|
var originalItems = this.modelManager.allItems.slice();
|
||||||
|
|
||||||
|
var block = () => {
|
||||||
|
var allItems = this.modelManager.allItems;
|
||||||
|
for(var item of allItems) {
|
||||||
item.setDirty(true);
|
item.setDirty(true);
|
||||||
}
|
}
|
||||||
this.writeItemsToLocalStorage(items, false, callback);
|
this.writeItemsToLocalStorage(allItems, false, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(alternateUUIDs) {
|
if(alternateUUIDs) {
|
||||||
@@ -90,18 +93,26 @@ class SyncManager {
|
|||||||
let alternateNextItem = () => {
|
let alternateNextItem = () => {
|
||||||
if(index >= originalItems.length) {
|
if(index >= originalItems.length) {
|
||||||
// We don't use originalItems as altnerating UUID will have deleted them.
|
// We don't use originalItems as altnerating UUID will have deleted them.
|
||||||
block(this.modelManager.allItems);
|
block();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var item = originalItems[index];
|
var item = originalItems[index];
|
||||||
this.modelManager.alternateUUIDForItem(item, alternateNextItem);
|
index++;
|
||||||
++index;
|
|
||||||
|
// alternateUUIDForItem last param is a boolean that controls whether the original item
|
||||||
|
// should be removed locally after new item is created. We set this to true, since during sign in,
|
||||||
|
// all item ids are alternated, and we only want one final copy of the entire data set.
|
||||||
|
// Passing false can be desired sometimes, when for example the app has signed out the user,
|
||||||
|
// but for some reason retained their data (This happens in Firefox when using private mode).
|
||||||
|
// In this case, we should pass false so that both copies are kept. However, it's difficult to
|
||||||
|
// detect when the app has entered this state. We will just use true to remove original items for now.
|
||||||
|
this.modelManager.alternateUUIDForItem(item, alternateNextItem, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
alternateNextItem();
|
alternateNextItem();
|
||||||
} else {
|
} else {
|
||||||
block(originalItems);
|
block();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,16 +265,25 @@ class SyncManager {
|
|||||||
|
|
||||||
this.$rootScope.$broadcast("sync:updated_token", this.syncToken);
|
this.$rootScope.$broadcast("sync:updated_token", this.syncToken);
|
||||||
|
|
||||||
var retrieved = this.handleItemsResponse(response.retrieved_items, null);
|
// Map retrieved items to local data
|
||||||
|
var retrieved
|
||||||
|
= this.handleItemsResponse(response.retrieved_items, null);
|
||||||
|
|
||||||
|
// Append items to master list of retrieved items for this ongoing sync operation
|
||||||
this.allRetreivedItems = this.allRetreivedItems.concat(retrieved);
|
this.allRetreivedItems = this.allRetreivedItems.concat(retrieved);
|
||||||
|
|
||||||
// merge only metadata for saved items
|
// Merge only metadata for saved items
|
||||||
// we write saved items to disk now because it clears their dirty status then saves
|
// we write saved items to disk now because it clears their dirty status then saves
|
||||||
// if we saved items before completion, we had have to save them as dirty and save them again on success as clean
|
// if we saved items before completion, we had have to save them as dirty and save them again on success as clean
|
||||||
var omitFields = ["content", "auth_hash"];
|
var omitFields = ["content", "auth_hash"];
|
||||||
var saved = this.handleItemsResponse(response.saved_items, omitFields);
|
|
||||||
|
|
||||||
|
// Map saved items to local data
|
||||||
|
var saved =
|
||||||
|
this.handleItemsResponse(response.saved_items, omitFields);
|
||||||
|
|
||||||
|
// Create copies of items or alternate their uuids if neccessary
|
||||||
this.handleUnsavedItemsResponse(response.unsaved)
|
this.handleUnsavedItemsResponse(response.unsaved)
|
||||||
|
|
||||||
this.writeItemsToLocalStorage(saved, false, null);
|
this.writeItemsToLocalStorage(saved, false, null);
|
||||||
|
|
||||||
this.syncStatus.syncOpInProgress = false;
|
this.syncStatus.syncOpInProgress = false;
|
||||||
@@ -361,7 +381,7 @@ class SyncManager {
|
|||||||
// UUID conflicts can occur if a user attempts to
|
// UUID conflicts can occur if a user attempts to
|
||||||
// import an old data archive with uuids from the old account into a new account
|
// import an old data archive with uuids from the old account into a new account
|
||||||
handled = true;
|
handled = true;
|
||||||
this.modelManager.alternateUUIDForItem(item, handleNext);
|
this.modelManager.alternateUUIDForItem(item, handleNext, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if(error.tag === "sync_conflict") {
|
else if(error.tag === "sync_conflict") {
|
||||||
@@ -370,7 +390,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.createItem(itemResponse);
|
var dup = this.modelManager.createDuplicateItem(itemResponse, item);
|
||||||
if(!itemResponse.deleted && JSON.stringify(item.structureParams()) !== JSON.stringify(dup.structureParams())) {
|
if(!itemResponse.deleted && JSON.stringify(item.structureParams()) !== JSON.stringify(dup.structureParams())) {
|
||||||
this.modelManager.addItem(dup);
|
this.modelManager.addItem(dup);
|
||||||
dup.conflict_of = item.uuid;
|
dup.conflict_of = item.uuid;
|
||||||
|
|||||||
@@ -67,12 +67,15 @@
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
border-top: 1px solid $light-bg-color;
|
border-top: 1px solid $light-bg-color;
|
||||||
border-bottom: 1px solid $light-bg-color;
|
border-bottom: 1px solid $light-bg-color;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-top: 12px;
|
|
||||||
|
|
||||||
li {
|
li {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -49,11 +49,7 @@ h2 {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
strong {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,47 +16,55 @@
|
|||||||
%h3 Learn More
|
%h3 Learn More
|
||||||
|
|
||||||
%div{"ng-if" => "themeManager.themes.length > 0"}
|
%div{"ng-if" => "themeManager.themes.length > 0"}
|
||||||
.container.no-bottom.section-margin
|
.header.container.section-margin
|
||||||
%h2 Themes
|
%h2 Themes
|
||||||
%ul
|
%ul
|
||||||
%li{"ng-repeat" => "theme in themeManager.themes", "ng-click" => "theme.showDetails = !theme.showDetails"}
|
%li{"ng-repeat" => "theme in themeManager.themes | orderBy: 'name'", "ng-click" => "clickedExtension(theme)"}
|
||||||
.container
|
.container
|
||||||
%h3 {{theme.name}}
|
%h3
|
||||||
|
%input.bold{"ng-if" => "theme.rename", "ng-model" => "theme.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(theme);", "mb-autofocus" => "true", "should-focus" => "true"}
|
||||||
|
%span{"ng-if" => "!theme.rename"} {{theme.name}}
|
||||||
%a{"ng-if" => "!themeManager.isThemeActive(theme)", "ng-click" => "themeManager.activateTheme(theme); $event.stopPropagation();"} Activate
|
%a{"ng-if" => "!themeManager.isThemeActive(theme)", "ng-click" => "themeManager.activateTheme(theme); $event.stopPropagation();"} Activate
|
||||||
%a{"ng-if" => "themeManager.isThemeActive(theme)", "ng-click" => "themeManager.deactivateTheme(theme); $event.stopPropagation();"} Deactivate
|
%a{"ng-if" => "themeManager.isThemeActive(theme)", "ng-click" => "themeManager.deactivateTheme(theme); $event.stopPropagation();"} Deactivate
|
||||||
.mt-3{"ng-if" => "theme.showDetails"}
|
.mt-3{"ng-if" => "theme.showDetails"}
|
||||||
.link-group
|
.link-group
|
||||||
%a.red{"ng-click" => "deleteTheme(theme); $event.stopPropagation();"} Delete
|
%a{"ng-click" => "renameExtension(theme); $event.stopPropagation();"} Rename
|
||||||
%a{"ng-click" => "theme.showLink = !theme.showLink; $event.stopPropagation();"} Show Link
|
%a{"ng-click" => "theme.showLink = !theme.showLink; $event.stopPropagation();"} Show Link
|
||||||
|
%a.red{"ng-click" => "deleteTheme(theme); $event.stopPropagation();"} Delete
|
||||||
%p.small.selectable.wrap{"ng-if" => "theme.showLink"}
|
%p.small.selectable.wrap{"ng-if" => "theme.showLink"}
|
||||||
{{theme.url}}
|
{{theme.url}}
|
||||||
|
|
||||||
%div{"ng-if" => "editorManager.externalEditors.length > 0"}
|
%div{"ng-if" => "editorManager.externalEditors.length > 0"}
|
||||||
.container.no-bottom.section-margin
|
.header.container.section-margin
|
||||||
%h2 Editors
|
%h2 Editors
|
||||||
%p{"style" => "margin-top: 3px;"} Choose "Editor" in the note menu to use an editor for a specific note.
|
%p{"style" => "margin-top: 3px;"} Choose "Editor" in the note menu to use an editor for a specific note.
|
||||||
%ul
|
%ul
|
||||||
%li{"ng-repeat" => "editor in editorManager.externalEditors", "ng-click" => "editor.showDetails = !editor.showDetails"}
|
%li{"ng-repeat" => "editor in editorManager.externalEditors | orderBy: 'name'", "ng-click" => "clickedExtension(editor)"}
|
||||||
.container
|
.container
|
||||||
%strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy
|
%strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy
|
||||||
%h3 {{editor.name}}
|
%h3
|
||||||
|
%input.bold{"ng-if" => "editor.rename", "ng-model" => "editor.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(editor);", "mb-autofocus" => "true", "should-focus" => "true"}
|
||||||
|
%span{"ng-if" => "!editor.rename"} {{editor.name}}
|
||||||
%div.mt-5{"ng-if" => "editor.showDetails"}
|
%div.mt-5{"ng-if" => "editor.showDetails"}
|
||||||
.link-group
|
.link-group
|
||||||
%a{"ng-if" => "!editor.default", "ng-click" => "setDefaultEditor(editor); $event.stopPropagation();"} Make Default
|
%a{"ng-if" => "!editor.default", "ng-click" => "setDefaultEditor(editor); $event.stopPropagation();"} Make Default
|
||||||
%a.tinted{"ng-if" => "editor.default", "ng-click" => "removeDefaultEditor(editor); $event.stopPropagation();"} Remove as Default
|
%a.tinted{"ng-if" => "editor.default", "ng-click" => "removeDefaultEditor(editor); $event.stopPropagation();"} Remove as Default
|
||||||
|
%a{"ng-click" => "renameExtension(editor); $event.stopPropagation();"} Rename
|
||||||
%a{"ng-click" => "editor.showUrl = !editor.showUrl; $event.stopPropagation();"} Show Link
|
%a{"ng-click" => "editor.showUrl = !editor.showUrl; $event.stopPropagation();"} Show Link
|
||||||
%a.red{ "ng-click" => "deleteEditor(editor); $event.stopPropagation();"} Delete
|
%a.red{ "ng-click" => "deleteEditor(editor); $event.stopPropagation();"} Delete
|
||||||
.wrap.mt-5.selectable{"ng-if" => "editor.showUrl"} {{editor.url}}
|
.wrap.mt-5.selectable{"ng-if" => "editor.showUrl"} {{editor.url}}
|
||||||
|
|
||||||
%div{"ng-if" => "extensionManager.extensions.length"}
|
%div{"ng-if" => "extensionManager.extensions.length"}
|
||||||
.container.no-bottom.section-margin
|
.header.container.section-margin
|
||||||
%h2 Actions
|
%h2 Actions
|
||||||
%p{"style" => "margin-top: 3px;"} Choose "Actions" in the note editor to use installed actions.
|
%p{"style" => "margin-top: 3px;"} Choose "Actions" in the note editor to use installed actions.
|
||||||
|
|
||||||
%ul
|
%ul
|
||||||
%li{"ng-repeat" => "extension in extensionManager.extensions | orderBy: 'name'", "ng-init" => "extension.formData = {}", "ng-click" => "extension.showDetails = !extension.showDetails"}
|
%li{"ng-repeat" => "extension in extensionManager.extensions | orderBy: 'name'", "ng-init" => "extension.formData = {}", "ng-click" => "clickedExtension(extension)"}
|
||||||
.container
|
.container
|
||||||
%h3 {{extension.name}}
|
%h3
|
||||||
|
%input.bold{"ng-if" => "extension.rename", "ng-model" => "extension.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(extension);", "mb-autofocus" => "true", "should-focus" => "true"}
|
||||||
|
%span{"ng-if" => "!extension.rename"} {{extension.name}}
|
||||||
%p.small{"ng-if" => "extension.description"} {{extension.description}}
|
%p.small{"ng-if" => "extension.description"} {{extension.description}}
|
||||||
%div{"ng-if" => "extension.showDetails"}
|
%div{"ng-if" => "extension.showDetails"}
|
||||||
.mt-10
|
.mt-10
|
||||||
@@ -96,29 +104,33 @@
|
|||||||
%label.red{"ng-if" => "action.error"}
|
%label.red{"ng-if" => "action.error"}
|
||||||
Error performing action.
|
Error performing action.
|
||||||
|
|
||||||
|
%a.block.mt-5{"ng-click" => "renameExtension(extension); $event.stopPropagation();"} Rename
|
||||||
%a.block.mt-5{"ng-click" => "extension.showURL = !extension.showURL; $event.stopPropagation();"} Show Link
|
%a.block.mt-5{"ng-click" => "extension.showURL = !extension.showURL; $event.stopPropagation();"} Show Link
|
||||||
%p.wrap.selectable.small{"ng-if" => "extension.showURL"} {{extension.url}}
|
%p.wrap.selectable.small{"ng-if" => "extension.showURL"} {{extension.url}}
|
||||||
%a.block.mt-5{"ng-click" => "deleteActionExtension(extension); $event.stopPropagation();"} Remove extension
|
%a.block.mt-5{"ng-click" => "deleteActionExtension(extension); $event.stopPropagation();"} Delete
|
||||||
|
|
||||||
%div{"ng-if" => "componentManager.components.length > 0"}
|
%div{"ng-if" => "componentManager.components.length > 0"}
|
||||||
.container.no-bottom.section-margin
|
.header.container.section-margin
|
||||||
%h2 Components
|
%h2 Components
|
||||||
%ul
|
%ul
|
||||||
%li{"ng-repeat" => "component in componentManager.components", "ng-click" => "component.showDetails = !component.showDetails"}
|
%li{"ng-repeat" => "component in componentManager.components | orderBy: 'name'", "ng-click" => "clickedExtension(component)"}
|
||||||
.container
|
.container
|
||||||
%h3 {{component.name}}
|
%h3
|
||||||
|
%input.bold{"ng-if" => "component.rename", "ng-model" => "component.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(component);", "mb-autofocus" => "true", "should-focus" => "true"}
|
||||||
|
%span{"ng-if" => "!component.rename"} {{component.name}}
|
||||||
%a{"ng-if" => "!componentManager.isComponentActive(component)", "ng-click" => "componentManager.activateComponent(component); $event.stopPropagation();"} Activate
|
%a{"ng-if" => "!componentManager.isComponentActive(component)", "ng-click" => "componentManager.activateComponent(component); $event.stopPropagation();"} Activate
|
||||||
%a{"ng-if" => "componentManager.isComponentActive(component)", "ng-click" => "componentManager.deactivateComponent(component); $event.stopPropagation();"} Deactivate
|
%a{"ng-if" => "componentManager.isComponentActive(component)", "ng-click" => "componentManager.deactivateComponent(component); $event.stopPropagation();"} Deactivate
|
||||||
.mt-3{"ng-if" => "component.showDetails"}
|
.mt-3{"ng-if" => "component.showDetails"}
|
||||||
.link-group
|
.link-group
|
||||||
%a.red{"ng-click" => "deleteComponent(component); $event.stopPropagation();"} Delete
|
%a{"ng-click" => "renameExtension(component); $event.stopPropagation();"} Rename
|
||||||
%a{"ng-click" => "component.showLink = !component.showLink; $event.stopPropagation();"} Show Link
|
%a{"ng-click" => "component.showLink = !component.showLink; $event.stopPropagation();"} Show Link
|
||||||
%a{"ng-if" => "component.permissions.length", "ng-click" => "revokePermissions(component); $event.stopPropagation();"} Revoke Permissions
|
%a{"ng-if" => "component.permissions.length", "ng-click" => "revokePermissions(component); $event.stopPropagation();"} Revoke Permissions
|
||||||
|
%a.red{"ng-click" => "deleteComponent(component); $event.stopPropagation();"} Delete
|
||||||
%p.small.selectable.wrap{"ng-if" => "component.showLink"}
|
%p.small.selectable.wrap{"ng-if" => "component.showLink"}
|
||||||
{{component.url}}
|
{{component.url}}
|
||||||
|
|
||||||
%div{"ng-if" => "serverExtensions.length > 0"}
|
%div{"ng-if" => "serverExtensions.length > 0"}
|
||||||
.container.no-bottom.section-margin
|
.header.container.section-margin
|
||||||
%h2 Server Extensions
|
%h2 Server Extensions
|
||||||
%ul
|
%ul
|
||||||
%li{"ng-repeat" => "ext in serverExtensions", "ng-click" => "ext.showDetails = !ext.showDetails"}
|
%li{"ng-repeat" => "ext in serverExtensions", "ng-click" => "ext.showDetails = !ext.showDetails"}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
%iframe#editor-iframe{"ng-if" => "ctrl.editor && !ctrl.editor.systemEditor", "ng-src" => "{{ctrl.editor.url | trusted}}", "frameBorder" => "0", "style" => "width: 100%;"}
|
%iframe#editor-iframe{"ng-if" => "ctrl.editor && !ctrl.editor.systemEditor", "ng-src" => "{{ctrl.editor.url | trusted}}", "frameBorder" => "0", "style" => "width: 100%;"}
|
||||||
Loading
|
Loading
|
||||||
%textarea.editable#note-text-editor{"ng-if" => "!ctrl.editor || ctrl.editor.systemEditor", "ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-model" => "ctrl.note.text",
|
%textarea.editable#note-text-editor{"ng-if" => "!ctrl.editor || ctrl.editor.systemEditor", "ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-model" => "ctrl.note.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()", "dir" => "auto"}
|
||||||
{{ctrl.onSystemEditorLoad()}}
|
{{ctrl.onSystemEditorLoad()}}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
%span.tinted.normal New update downloaded. Installs on app restart.
|
%span.tinted.normal New update downloaded. Installs on app restart.
|
||||||
|
|
||||||
.footer-bar-link{"style" => "margin-right: 5px;"}
|
.footer-bar-link{"style" => "margin-right: 5px;"}
|
||||||
%div{"ng-if" => "ctrl.lastSyncDate", "style" => "float: left; font-weight: normal; margin-right: 8px;"}
|
%span{"ng-if" => "ctrl.lastSyncDate", "style" => "float: left; font-weight: normal; margin-right: 8px;"}
|
||||||
%span{"ng-if" => "!ctrl.isRefreshing"}
|
%span{"ng-if" => "!ctrl.isRefreshing"}
|
||||||
Last refreshed {{ctrl.lastSyncDate | appDateTime}}
|
Last refreshed {{ctrl.lastSyncDate | appDateTime}}
|
||||||
%span{"ng-if" => "ctrl.isRefreshing"}
|
%span{"ng-if" => "ctrl.isRefreshing"}
|
||||||
@@ -28,4 +28,4 @@
|
|||||||
%a{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"} Refresh
|
%a{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"} Refresh
|
||||||
|
|
||||||
%span{"ng-if" => "ctrl.hasPasscode()"}
|
%span{"ng-if" => "ctrl.hasPasscode()"}
|
||||||
%i.icon.ion-locked{"ng-click" => "ctrl.lockApp()"}
|
%i.icon.ion-locked{"ng-if" => "ctrl.hasPasscode()", "ng-click" => "ctrl.lockApp()"}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
.scrollable
|
.scrollable
|
||||||
.infinite-scroll{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"}
|
.infinite-scroll{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"}
|
||||||
.note{"ng-repeat" => "note in (ctrl.sortedNotes = (ctrl.tag.notes | filter: ctrl.filterNotes | sortBy: ctrl.sortBy| limitTo:ctrl.notesToDisplay))",
|
.note{"ng-repeat" => "note in (ctrl.sortedNotes = (ctrl.tag.notes | filter: ctrl.filterNotes | sortBy: ctrl.sortBy| limitTo:ctrl.notesToDisplay)) track by note.uuid",
|
||||||
"ng-click" => "ctrl.selectNote(note)", "ng-class" => "{'selected' : ctrl.selectedNote == note}"}
|
"ng-click" => "ctrl.selectNote(note)", "ng-class" => "{'selected' : ctrl.selectedNote == note}"}
|
||||||
%strong.red.medium{"ng-if" => "note.conflict_of"} Conflicted copy
|
%strong.red.medium{"ng-if" => "note.conflict_of"} Conflicted copy
|
||||||
%strong.red.medium{"ng-if" => "note.errorDecrypting"} Error decrypting
|
%strong.red.medium{"ng-if" => "note.errorDecrypting"} Error decrypting
|
||||||
|
|||||||
@@ -10,13 +10,16 @@
|
|||||||
.info
|
.info
|
||||||
%input.title{"ng-disabled" => "true", "ng-model" => "ctrl.allTag.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 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)", "mb-autofocus" => "true", "should-focus" => "ctrl.newTag || ctrl.editingTag == tag",
|
"ng-keyup" => "$event.keyCode == 13 && ctrl.saveTag($event, tag)", "mb-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)}}
|
||||||
|
|
||||||
.red.small.bold{"ng-if" => "tag.conflict_of"} Conflicted copy
|
.red.small.bold{"ng-if" => "tag.conflict_of"} Conflicted copy
|
||||||
|
.red.small.bold{"ng-if" => "tag.errorDecrypting"} Error decrypting
|
||||||
|
|
||||||
.menu{"ng-if" => "ctrl.selectedTag == tag"}
|
.menu{"ng-if" => "ctrl.selectedTag == tag"}
|
||||||
%a.item{"ng-click" => "ctrl.selectedRenameTag($event, tag)", "ng-if" => "!ctrl.editingTag"} Rename
|
%a.item{"ng-click" => "ctrl.selectedRenameTag($event, tag)", "ng-if" => "!ctrl.editingTag"} Rename
|
||||||
%a.item{"ng-click" => "ctrl.saveTag($event, tag)", "ng-if" => "ctrl.editingTag"} Save
|
%a.item{"ng-click" => "ctrl.saveTag($event, tag)", "ng-if" => "ctrl.editingTag"} Save
|
||||||
|
|||||||
Reference in New Issue
Block a user