@@ -352,6 +352,7 @@ angular.module('app.frontend')
|
||||
|
||||
this.onNameBlur = function() {
|
||||
this.editingName = false;
|
||||
this.updateTagsFromTagsString()
|
||||
}
|
||||
|
||||
this.toggleFullScreen = function() {
|
||||
@@ -366,7 +367,8 @@ angular.module('app.frontend')
|
||||
}
|
||||
|
||||
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.showMenu = false;
|
||||
}
|
||||
@@ -430,9 +432,7 @@ angular.module('app.frontend')
|
||||
this.loadTagsString();
|
||||
}
|
||||
|
||||
this.updateTagsFromTagsString = function($event) {
|
||||
$event.target.blur();
|
||||
|
||||
this.updateTagsFromTagsString = function() {
|
||||
var tags = this.tagsString.split("#");
|
||||
tags = _.filter(tags, function(tag){
|
||||
return tag.length > 0;
|
||||
|
||||
@@ -80,7 +80,9 @@ angular.module('app.frontend')
|
||||
this.showMenu = false;
|
||||
|
||||
if(this.selectedNote && this.selectedNote.dummy) {
|
||||
_.remove(oldTag.notes, this.selectedNote);
|
||||
if(oldTag) {
|
||||
_.remove(oldTag.notes, this.selectedNote);
|
||||
}
|
||||
}
|
||||
|
||||
this.noteFilter.text = "";
|
||||
|
||||
@@ -66,6 +66,7 @@ class Item {
|
||||
reference.setDirty(true);
|
||||
})
|
||||
}
|
||||
|
||||
addObserver(observer, callback) {
|
||||
if(!_.find(this.observers, observer)) {
|
||||
this.observers.push({observer: observer, callback: callback});
|
||||
@@ -116,7 +117,7 @@ class Item {
|
||||
|
||||
}
|
||||
|
||||
removeAllRelationships() {
|
||||
removeAndDirtyAllRelationships() {
|
||||
// must override
|
||||
this.setDirty(true);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ class SyncAdapter extends Item {
|
||||
super(json_obj);
|
||||
}
|
||||
|
||||
mapContentToLocalProperties(contentObject) {
|
||||
super.mapContentToLocalProperties(contentObject)
|
||||
this.url = contentObject.url;
|
||||
mapContentToLocalProperties(content) {
|
||||
super.mapContentToLocalProperties(content)
|
||||
this.url = content.url;
|
||||
}
|
||||
|
||||
structureParams() {
|
||||
|
||||
@@ -12,22 +12,22 @@ class Component extends Item {
|
||||
}
|
||||
}
|
||||
|
||||
mapContentToLocalProperties(contentObject) {
|
||||
super.mapContentToLocalProperties(contentObject)
|
||||
this.url = contentObject.url;
|
||||
this.name = contentObject.name;
|
||||
mapContentToLocalProperties(content) {
|
||||
super.mapContentToLocalProperties(content)
|
||||
this.url = content.url;
|
||||
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`
|
||||
this.area = contentObject.area;
|
||||
this.area = content.area;
|
||||
|
||||
this.permissions = contentObject.permissions;
|
||||
this.active = contentObject.active;
|
||||
this.permissions = content.permissions;
|
||||
this.active = content.active;
|
||||
|
||||
// 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
|
||||
this.disassociatedItemIds = contentObject.disassociatedItemIds || [];
|
||||
this.disassociatedItemIds = content.disassociatedItemIds || [];
|
||||
}
|
||||
|
||||
structureParams() {
|
||||
|
||||
@@ -10,13 +10,13 @@ class Editor extends Item {
|
||||
}
|
||||
}
|
||||
|
||||
mapContentToLocalProperties(contentObject) {
|
||||
super.mapContentToLocalProperties(contentObject)
|
||||
this.url = contentObject.url;
|
||||
this.name = contentObject.name;
|
||||
this.data = contentObject.data || {};
|
||||
this.default = contentObject.default;
|
||||
this.systemEditor = contentObject.systemEditor;
|
||||
mapContentToLocalProperties(content) {
|
||||
super.mapContentToLocalProperties(content)
|
||||
this.url = content.url;
|
||||
this.name = content.name;
|
||||
this.data = content.data || {};
|
||||
this.default = content.default;
|
||||
this.systemEditor = content.systemEditor;
|
||||
}
|
||||
|
||||
structureParams() {
|
||||
@@ -56,8 +56,8 @@ class Editor extends Item {
|
||||
super.removeItemAsRelationship(item);
|
||||
}
|
||||
|
||||
removeAllRelationships() {
|
||||
super.removeAllRelationships();
|
||||
removeAndDirtyAllRelationships() {
|
||||
super.removeAndDirtyAllRelationships();
|
||||
this.notes = [];
|
||||
}
|
||||
|
||||
|
||||
@@ -51,16 +51,21 @@ class Action {
|
||||
class Extension extends Item {
|
||||
constructor(json) {
|
||||
super(json);
|
||||
_.merge(this, json);
|
||||
|
||||
this.encrypted = true;
|
||||
this.content_type = "Extension";
|
||||
if(this.encrypted === null || this.encrypted === undefined) {
|
||||
// Default to encrypted on creation.
|
||||
this.encrypted = true;
|
||||
}
|
||||
|
||||
if(json.actions) {
|
||||
this.actions = json.actions.map(function(action){
|
||||
return new Action(action);
|
||||
})
|
||||
}
|
||||
|
||||
if(!this.actions) {
|
||||
this.actions = [];
|
||||
}
|
||||
}
|
||||
|
||||
actionsInGlobalContext() {
|
||||
@@ -75,39 +80,42 @@ class Extension extends Item {
|
||||
})
|
||||
}
|
||||
|
||||
mapContentToLocalProperties(contentObject) {
|
||||
super.mapContentToLocalProperties(contentObject)
|
||||
this.name = contentObject.name;
|
||||
this.description = contentObject.description;
|
||||
this.url = contentObject.url;
|
||||
this.supported_types = contentObject.supported_types;
|
||||
if(contentObject.actions) {
|
||||
this.actions = contentObject.actions.map(function(action){
|
||||
mapContentToLocalProperties(content) {
|
||||
super.mapContentToLocalProperties(content)
|
||||
this.name = content.name;
|
||||
this.description = content.description;
|
||||
this.url = content.url;
|
||||
|
||||
if(content.encrypted !== null && content.encrypted !== undefined) {
|
||||
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);
|
||||
})
|
||||
} else {
|
||||
this.actions = [];
|
||||
}
|
||||
}
|
||||
|
||||
updateFromExternalResponseItem(externalResponseItem) {
|
||||
_.merge(this, externalResponseItem);
|
||||
this.actions = externalResponseItem.actions.map(function(action){
|
||||
return new Action(action);
|
||||
})
|
||||
}
|
||||
|
||||
referenceParams() {
|
||||
return null;
|
||||
}
|
||||
|
||||
get content_type() {
|
||||
return "Extension";
|
||||
}
|
||||
|
||||
structureParams() {
|
||||
var params = {
|
||||
name: this.name,
|
||||
url: this.url,
|
||||
description: this.description,
|
||||
actions: this.actions,
|
||||
supported_types: this.supported_types
|
||||
supported_types: this.supported_types,
|
||||
encrypted: this.encrypted
|
||||
};
|
||||
|
||||
_.merge(params, super.structureParams());
|
||||
|
||||
@@ -8,10 +8,10 @@ class Note extends Item {
|
||||
}
|
||||
}
|
||||
|
||||
mapContentToLocalProperties(contentObject) {
|
||||
super.mapContentToLocalProperties(contentObject)
|
||||
this.title = contentObject.title;
|
||||
this.text = contentObject.text;
|
||||
mapContentToLocalProperties(content) {
|
||||
super.mapContentToLocalProperties(content)
|
||||
this.title = content.title;
|
||||
this.text = content.text;
|
||||
}
|
||||
|
||||
referenceParams() {
|
||||
@@ -48,7 +48,7 @@ class Note extends Item {
|
||||
super.removeItemAsRelationship(item);
|
||||
}
|
||||
|
||||
removeAllRelationships() {
|
||||
removeAndDirtyAllRelationships() {
|
||||
this.tags.forEach(function(tag){
|
||||
_.pull(tag.notes, this);
|
||||
tag.setDirty(true);
|
||||
|
||||
@@ -8,9 +8,9 @@ class Tag extends Item {
|
||||
}
|
||||
}
|
||||
|
||||
mapContentToLocalProperties(contentObject) {
|
||||
super.mapContentToLocalProperties(contentObject)
|
||||
this.title = contentObject.title;
|
||||
mapContentToLocalProperties(content) {
|
||||
super.mapContentToLocalProperties(content)
|
||||
this.title = content.title;
|
||||
}
|
||||
|
||||
referenceParams() {
|
||||
@@ -46,7 +46,7 @@ class Tag extends Item {
|
||||
super.removeItemAsRelationship(item);
|
||||
}
|
||||
|
||||
removeAllRelationships() {
|
||||
removeAndDirtyAllRelationships() {
|
||||
this.notes.forEach(function(note){
|
||||
_.pull(note.tags, this);
|
||||
note.setDirty(true);
|
||||
|
||||
@@ -4,10 +4,10 @@ class Theme extends Item {
|
||||
super(json_obj);
|
||||
}
|
||||
|
||||
mapContentToLocalProperties(contentObject) {
|
||||
super.mapContentToLocalProperties(contentObject)
|
||||
this.url = contentObject.url;
|
||||
this.name = contentObject.name;
|
||||
mapContentToLocalProperties(content) {
|
||||
super.mapContentToLocalProperties(content)
|
||||
this.url = content.url;
|
||||
this.name = content.name;
|
||||
}
|
||||
|
||||
structureParams() {
|
||||
|
||||
@@ -4,9 +4,9 @@ class EncryptedStorage extends Item {
|
||||
super(json_obj);
|
||||
}
|
||||
|
||||
mapContentToLocalProperties(contentObject) {
|
||||
super.mapContentToLocalProperties(contentObject)
|
||||
this.storage = contentObject.storage;
|
||||
mapContentToLocalProperties(content) {
|
||||
super.mapContentToLocalProperties(content)
|
||||
this.storage = content.storage;
|
||||
}
|
||||
|
||||
structureParams() {
|
||||
|
||||
@@ -45,6 +45,9 @@ angular.module('app.frontend')
|
||||
storageManager.setModelStorageMode(StorageManager.Ephemeral);
|
||||
storageManager.setItemsMode(storageManager.hasPasscode() ? StorageManager.FixedEncrypted : StorageManager.Ephemeral);
|
||||
} else {
|
||||
storageManager.setModelStorageMode(StorageManager.Fixed);
|
||||
storageManager.setItemsMode(storageManager.hasPasscode() ? StorageManager.FixedEncrypted : 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};
|
||||
httpManager.postAbsolute(requestUrl, params, function(response){
|
||||
this.setEphemeral(ephemeral);
|
||||
|
||||
this.handleAuthResponse(response, email, url, authParams, keys);
|
||||
storageManager.setModelStorageMode(ephemeral ? StorageManager.Ephemeral : StorageManager.Fixed);
|
||||
|
||||
this.checkForSecurityUpdate();
|
||||
|
||||
callback(response);
|
||||
}.bind(this), function(response){
|
||||
console.error("Error logging in", response);
|
||||
@@ -199,11 +198,8 @@ angular.module('app.frontend')
|
||||
|
||||
httpManager.postAbsolute(requestUrl, params, function(response){
|
||||
this.setEphemeral(ephemeral);
|
||||
|
||||
this.handleAuthResponse(response, email, url, authParams, keys);
|
||||
|
||||
storageManager.setModelStorageMode(ephemeral ? StorageManager.Ephemeral : StorageManager.Fixed);
|
||||
|
||||
callback(response);
|
||||
}.bind(this), function(response){
|
||||
console.error("Registration error", response);
|
||||
|
||||
@@ -28,12 +28,12 @@ class ComponentManager {
|
||||
this.handleMessage(this.componentForSessionKey(event.data.sessionKey), event.data);
|
||||
}.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) {
|
||||
var activeComponent = _.find(this.activeComponents, {uuid: component.uuid});
|
||||
if(component.active && !activeComponent) {
|
||||
if(component.active && !component.deleted && !activeComponent) {
|
||||
this.activateComponent(component);
|
||||
} else if(!component.active && activeComponent) {
|
||||
this.deactivateComponent(component);
|
||||
@@ -41,7 +41,7 @@ class ComponentManager {
|
||||
}
|
||||
|
||||
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;
|
||||
})
|
||||
|
||||
@@ -70,7 +70,7 @@ class ComponentManager {
|
||||
}
|
||||
var itemInContext = handler.contextRequestHandler(observer.component);
|
||||
if(itemInContext) {
|
||||
var matchingItem = _.find(items, {uuid: itemInContext.uuid});
|
||||
var matchingItem = _.find(allItems, {uuid: itemInContext.uuid});
|
||||
if(matchingItem) {
|
||||
this.sendContextItemInReply(observer.component, matchingItem, observer.originalMessage);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ class ContextualExtensionsMenu {
|
||||
|
||||
$scope.isActionEnabled = function(action, extension) {
|
||||
if(action.access_type) {
|
||||
var extEncryptedAccess = extensionManager.extensionUsesEncryptedData(extension);
|
||||
var extEncryptedAccess = extension.encrypted;
|
||||
if(action.access_type == "decrypted" && extEncryptedAccess) {
|
||||
return false;
|
||||
} else if(action.access_type == "encrypted" && !extEncryptedAccess) {
|
||||
@@ -77,7 +77,7 @@ class ContextualExtensionsMenu {
|
||||
}
|
||||
|
||||
$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) {
|
||||
extensionManager.changeExtensionEncryptionFormat(encrypted, extension);
|
||||
extension.encrypted = encrypted;
|
||||
extension.setDirty(true);
|
||||
syncManager.sync();
|
||||
}
|
||||
|
||||
$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
|
||||
|
||||
$scope.deleteServerExt = function(ext) {
|
||||
|
||||
@@ -5,15 +5,11 @@ class ExtensionManager {
|
||||
this.modelManager = modelManager;
|
||||
this.authManager = authManager;
|
||||
this.enabledRepeatActionUrls = JSON.parse(storageManager.getItem("enabledRepeatActionUrls")) || [];
|
||||
this.decryptedExtensions = JSON.parse(storageManager.getItem("decryptedExtensions")) || [];
|
||||
this.syncManager = syncManager;
|
||||
this.storageManager = storageManager;
|
||||
|
||||
modelManager.addItemSyncObserver("extensionManager", "Extension", function(items){
|
||||
for (var ext of items) {
|
||||
|
||||
ext.encrypted = this.extensionUsesEncryptedData(ext);
|
||||
|
||||
modelManager.addItemSyncObserver("extensionManager", "Extension", function(allItems, validItems, deletedItems){
|
||||
for (var ext of validItems) {
|
||||
for (var action of ext.actions) {
|
||||
if(_.includes(this.enabledRepeatActionUrls, action.url)) {
|
||||
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) {
|
||||
this.retrieveExtensionFromServer(url, callback);
|
||||
}
|
||||
|
||||
deleteExtension(extension) {
|
||||
for(var action of extension.actions) {
|
||||
_.pull(this.decryptedExtensions, extension);
|
||||
if(action.repeat_mode) {
|
||||
if(this.isRepeatActionEnabled(action)) {
|
||||
this.disableRepeatAction(action);
|
||||
@@ -80,18 +59,10 @@ class ExtensionManager {
|
||||
loadExtensionInContextOfItem(extension, item, callback) {
|
||||
|
||||
this.httpManager.getAbsolute(extension.url, {content_type: item.content_type, item_uuid: item.uuid}, function(response){
|
||||
var scopedExtension = new Extension(response);
|
||||
if(scopedExtension) {
|
||||
_.merge(extension, scopedExtension);
|
||||
extension.actions = scopedExtension.actions;
|
||||
extension.encrypted = this.extensionUsesEncryptedData(extension);
|
||||
}
|
||||
if(callback) {
|
||||
callback(scopedExtension);
|
||||
}
|
||||
this.updateExtensionFromRemoteResponse(extension, response);
|
||||
callback && callback(extension);
|
||||
}.bind(this), function(response){
|
||||
console.log("Error loading extension", response);
|
||||
extension.encrypted = this.extensionUsesEncryptedData(extension);
|
||||
if(callback) {
|
||||
callback(null);
|
||||
}
|
||||
@@ -118,9 +89,13 @@ class ExtensionManager {
|
||||
}
|
||||
|
||||
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});
|
||||
if(extension) {
|
||||
extension.updateFromExternalResponseItem(externalResponseItem);
|
||||
this.updateExtensionFromRemoteResponse(extension, externalResponseItem);
|
||||
} else {
|
||||
extension = new Extension(externalResponseItem);
|
||||
extension.url = url;
|
||||
@@ -132,6 +107,23 @@ class ExtensionManager {
|
||||
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() {
|
||||
for (var url of this.enabledRepeatActionUrls) {
|
||||
var action = this.actionWithURL(url);
|
||||
@@ -149,7 +141,7 @@ class ExtensionManager {
|
||||
|
||||
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.");
|
||||
callback(null);
|
||||
return;
|
||||
@@ -274,11 +266,9 @@ class ExtensionManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log("Successfully queued", action, this.actionQueue.length);
|
||||
this.actionQueue.push(action);
|
||||
|
||||
setTimeout(function () {
|
||||
// console.log("Performing queued action", action);
|
||||
this.triggerWatchAction(action, extension, changedItems);
|
||||
_.pull(this.actionQueue, action);
|
||||
}.bind(this), delay * 1000);
|
||||
@@ -297,8 +287,6 @@ class ExtensionManager {
|
||||
|
||||
action.lastExecuted = new Date();
|
||||
|
||||
console.log("Performing action.");
|
||||
|
||||
if(action.verb == "post") {
|
||||
var params = {};
|
||||
params.items = changedItems.map(function(item){
|
||||
@@ -316,8 +304,8 @@ class ExtensionManager {
|
||||
}
|
||||
|
||||
outgoingParamsForItem(item, extension) {
|
||||
var keys = this.authManager.keys();
|
||||
if(!this.extensionUsesEncryptedData(extension)) {
|
||||
var keys = this.userManager.keys();
|
||||
if(!extension.encrypted) {
|
||||
keys = null;
|
||||
}
|
||||
var itemParams = new ItemParams(item, keys, this.authManager.protocolVersion());
|
||||
@@ -326,8 +314,8 @@ class ExtensionManager {
|
||||
|
||||
performPost(action, extension, params, callback) {
|
||||
|
||||
if(this.extensionUsesEncryptedData(extension)) {
|
||||
params.auth_params = this.authManager.getAuthParams();
|
||||
if(extension.encrypted) {
|
||||
params.auth_params = this.userManager.getAuthParams();
|
||||
}
|
||||
|
||||
this.httpManager.postAbsolute(action.url, params, function(response){
|
||||
|
||||
@@ -6,9 +6,13 @@ class ModelManager {
|
||||
this.tags = [];
|
||||
this.itemSyncObservers = [];
|
||||
this.itemChangeObservers = [];
|
||||
this.itemsPendingRemoval = [];
|
||||
this.items = [];
|
||||
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() {
|
||||
@@ -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)
|
||||
var newItem = this.createItem(item);
|
||||
|
||||
@@ -41,12 +45,20 @@ class ModelManager {
|
||||
|
||||
this.informModelsOfUUIDChangeForItem(newItem, item.uuid, newItem.uuid);
|
||||
|
||||
this.removeItemLocally(item, function(){
|
||||
var block = () => {
|
||||
this.addItem(newItem);
|
||||
newItem.setDirty(true);
|
||||
newItem.markAllReferencesDirty();
|
||||
callback();
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
if(removeOriginal) {
|
||||
this.removeItemLocally(item, function(){
|
||||
block();
|
||||
});
|
||||
} else {
|
||||
block();
|
||||
}
|
||||
}
|
||||
|
||||
informModelsOfUUIDChangeForItem(newItem, oldUUID, newUUID) {
|
||||
@@ -89,23 +101,33 @@ class ModelManager {
|
||||
}
|
||||
|
||||
mapResponseItemsToLocalModelsOmittingFields(items, omitFields) {
|
||||
var models = [], processedObjects = [], allModels = [];
|
||||
var models = [], processedObjects = [], modelsToNotifyObserversOf = [];
|
||||
|
||||
// first loop should add and process items
|
||||
for (var json_obj of items) {
|
||||
json_obj = _.omit(json_obj, omitFields || [])
|
||||
var item = this.findItem(json_obj["uuid"]);
|
||||
if((!json_obj.content_type || !json_obj.content) && !json_obj.deleted) {
|
||||
// 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) {
|
||||
item.updateFromJSON(json_obj);
|
||||
}
|
||||
|
||||
if(json_obj["deleted"] == true || !_.includes(this.acceptableContentTypes, json_obj["content_type"])) {
|
||||
if(item) {
|
||||
allModels.push(item);
|
||||
this.removeItemLocally(item)
|
||||
if(this.itemsPendingRemoval.includes(json_obj.uuid)) {
|
||||
_.pull(this.itemsPendingRemoval, json_obj.uuid);
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -116,7 +138,7 @@ class ModelManager {
|
||||
|
||||
this.addItem(item);
|
||||
|
||||
allModels.push(item);
|
||||
modelsToNotifyObserversOf.push(item);
|
||||
models.push(item);
|
||||
processedObjects.push(json_obj);
|
||||
}
|
||||
@@ -129,16 +151,25 @@ class ModelManager {
|
||||
}
|
||||
}
|
||||
|
||||
this.notifySyncObserversOfModels(allModels);
|
||||
this.notifySyncObserversOfModels(modelsToNotifyObserversOf);
|
||||
|
||||
return models;
|
||||
}
|
||||
|
||||
notifySyncObserversOfModels(models) {
|
||||
for(var observer of this.itemSyncObservers) {
|
||||
var relevantItems = models.filter(function(item){return item.content_type == observer.type || observer.type == "*"});
|
||||
if(relevantItems.length > 0) {
|
||||
observer.callback(relevantItems);
|
||||
var allRelevantItems = models.filter(function(item){return item.content_type == observer.type || observer.type == "*"});
|
||||
var validItems = [], deletedItems = [];
|
||||
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;
|
||||
}
|
||||
|
||||
createDuplicateItem(itemResponse, sourceItem) {
|
||||
var dup = this.createItem(itemResponse);
|
||||
this.resolveReferencesForItem(dup);
|
||||
return dup;
|
||||
}
|
||||
|
||||
addItems(items) {
|
||||
items.forEach(function(item){
|
||||
if(item.content_type == "Tag") {
|
||||
@@ -283,7 +320,7 @@ class ModelManager {
|
||||
if(!item.dummy) {
|
||||
item.setDirty(true);
|
||||
}
|
||||
item.removeAllRelationships();
|
||||
item.removeAndDirtyAllRelationships();
|
||||
}
|
||||
|
||||
/* Used when changing encryption key */
|
||||
@@ -302,6 +339,8 @@ class ModelManager {
|
||||
|
||||
item.isBeingRemovedLocally();
|
||||
|
||||
this.itemsPendingRemoval.push(item.uuid);
|
||||
|
||||
if(item.content_type == "Tag") {
|
||||
_.pull(this.tags, item);
|
||||
} else if(item.content_type == "Note") {
|
||||
@@ -324,14 +363,6 @@ class ModelManager {
|
||||
itemOne.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);
|
||||
|
||||
@@ -42,11 +42,14 @@ class StorageManager {
|
||||
if(hasPasscode) {
|
||||
// We don't want to save anything in fixed storage except for actual item data (in IndexedDB)
|
||||
this.storage = this.memoryStorage;
|
||||
this.itemsStorageMode = StorageManager.FixedEncrypted;
|
||||
} else if(ephemeral) {
|
||||
// We don't want to save anything in fixed storage as well as IndexedDB
|
||||
this.storage = this.memoryStorage;
|
||||
this.itemsStorageMode = StorageManager.Ephemeral;
|
||||
} else {
|
||||
this.storage = localStorage;
|
||||
this.itemsStorageMode = StorageManager.Fixed;
|
||||
}
|
||||
|
||||
this.modelStorageMode = ephemeral ? StorageManager.Ephemeral : StorageManager.Fixed;
|
||||
@@ -69,6 +72,7 @@ class StorageManager {
|
||||
newStorage.setItem(key, this.storage.getItem(key));
|
||||
}
|
||||
|
||||
this.itemsStorageMode = mode;
|
||||
this.storage.clear();
|
||||
this.storage = newStorage;
|
||||
|
||||
@@ -83,25 +87,21 @@ class StorageManager {
|
||||
|
||||
getVault(vaultKey) {
|
||||
if(vaultKey) {
|
||||
return this.storageForVault(vaultKey);
|
||||
if(vaultKey == StorageManager.Ephemeral || vaultKey == StorageManager.FixedEncrypted) {
|
||||
return this.memoryStorage;
|
||||
} else {
|
||||
return localStorage;
|
||||
}
|
||||
} else {
|
||||
return this.storage;
|
||||
}
|
||||
}
|
||||
|
||||
storageForVault(vault) {
|
||||
if(vault == StorageManager.Ephemeral || vault == StorageManager.FixedEncrypted) {
|
||||
return this.memoryStorage;
|
||||
} else {
|
||||
return localStorage;
|
||||
}
|
||||
}
|
||||
|
||||
setItem(key, value, vault) {
|
||||
var storage = this.getVault(vault);
|
||||
setItem(key, value, vaultKey) {
|
||||
var storage = this.getVault(vaultKey);
|
||||
storage.setItem(key, value);
|
||||
|
||||
if(vault === StorageManager.FixedEncrypted) {
|
||||
if(vaultKey === StorageManager.FixedEncrypted || (!vaultKey && this.itemsStorageMode === StorageManager.FixedEncrypted)) {
|
||||
this.writeEncryptedStorageToDisk();
|
||||
}
|
||||
}
|
||||
@@ -147,6 +147,7 @@ class StorageManager {
|
||||
var encryptedStorage = new EncryptedStorage();
|
||||
// Copy over totality of current storage
|
||||
encryptedStorage.storage = this.storageAsHash();
|
||||
|
||||
// Save new encrypted storage in Fixed storage
|
||||
var params = new ItemParams(encryptedStorage, this.encryptedStorageKeys);
|
||||
this.setItem("encryptedStorage", JSON.stringify(params.paramsForSync()), StorageManager.Fixed);
|
||||
@@ -160,6 +161,7 @@ class StorageManager {
|
||||
for(var key of Object.keys(encryptedStorage.storage)) {
|
||||
this.setItem(key, encryptedStorage.storage[key]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
hasPasscode() {
|
||||
|
||||
@@ -75,13 +75,16 @@ class SyncManager {
|
||||
Alternating here forces us to to create duplicates of the items instead.
|
||||
*/
|
||||
markAllItemsDirtyAndSaveOffline(callback, alternateUUIDs) {
|
||||
var originalItems = this.modelManager.allItems;
|
||||
|
||||
var block = (items) => {
|
||||
for(var item of items) {
|
||||
// use a copy, as alternating uuid will affect array
|
||||
var originalItems = this.modelManager.allItems.slice();
|
||||
|
||||
var block = () => {
|
||||
var allItems = this.modelManager.allItems;
|
||||
for(var item of allItems) {
|
||||
item.setDirty(true);
|
||||
}
|
||||
this.writeItemsToLocalStorage(items, false, callback);
|
||||
this.writeItemsToLocalStorage(allItems, false, callback);
|
||||
}
|
||||
|
||||
if(alternateUUIDs) {
|
||||
@@ -90,18 +93,26 @@ class SyncManager {
|
||||
let alternateNextItem = () => {
|
||||
if(index >= originalItems.length) {
|
||||
// We don't use originalItems as altnerating UUID will have deleted them.
|
||||
block(this.modelManager.allItems);
|
||||
block();
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
} else {
|
||||
block(originalItems);
|
||||
block();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,16 +265,25 @@ class SyncManager {
|
||||
|
||||
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);
|
||||
|
||||
// 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
|
||||
// if we saved items before completion, we had have to save them as dirty and save them again on success as clean
|
||||
var omitFields = ["content", "auth_hash"];
|
||||
var saved = this.handleItemsResponse(response.saved_items, omitFields);
|
||||
|
||||
// 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.writeItemsToLocalStorage(saved, false, null);
|
||||
|
||||
this.syncStatus.syncOpInProgress = false;
|
||||
@@ -361,7 +381,7 @@ class SyncManager {
|
||||
// UUID conflicts can occur if a user attempts to
|
||||
// import an old data archive with uuids from the old account into a new account
|
||||
handled = true;
|
||||
this.modelManager.alternateUUIDForItem(item, handleNext);
|
||||
this.modelManager.alternateUUIDForItem(item, handleNext, true);
|
||||
}
|
||||
|
||||
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.
|
||||
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())) {
|
||||
this.modelManager.addItem(dup);
|
||||
dup.conflict_of = item.uuid;
|
||||
|
||||
@@ -67,12 +67,15 @@
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
ul {
|
||||
border-top: 1px solid $light-bg-color;
|
||||
border-bottom: 1px solid $light-bg-color;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-top: 12px;
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
|
||||
@@ -49,11 +49,7 @@ h2 {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
h1 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@@ -16,47 +16,55 @@
|
||||
%h3 Learn More
|
||||
|
||||
%div{"ng-if" => "themeManager.themes.length > 0"}
|
||||
.container.no-bottom.section-margin
|
||||
.header.container.section-margin
|
||||
%h2 Themes
|
||||
%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
|
||||
%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.deactivateTheme(theme); $event.stopPropagation();"} Deactivate
|
||||
.mt-3{"ng-if" => "theme.showDetails"}
|
||||
.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.red{"ng-click" => "deleteTheme(theme); $event.stopPropagation();"} Delete
|
||||
%p.small.selectable.wrap{"ng-if" => "theme.showLink"}
|
||||
{{theme.url}}
|
||||
|
||||
%div{"ng-if" => "editorManager.externalEditors.length > 0"}
|
||||
.container.no-bottom.section-margin
|
||||
.header.container.section-margin
|
||||
%h2 Editors
|
||||
%p{"style" => "margin-top: 3px;"} Choose "Editor" in the note menu to use an editor for a specific note.
|
||||
%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
|
||||
%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"}
|
||||
.link-group
|
||||
%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{"ng-click" => "renameExtension(editor); $event.stopPropagation();"} Rename
|
||||
%a{"ng-click" => "editor.showUrl = !editor.showUrl; $event.stopPropagation();"} Show Link
|
||||
%a.red{ "ng-click" => "deleteEditor(editor); $event.stopPropagation();"} Delete
|
||||
.wrap.mt-5.selectable{"ng-if" => "editor.showUrl"} {{editor.url}}
|
||||
|
||||
%div{"ng-if" => "extensionManager.extensions.length"}
|
||||
.container.no-bottom.section-margin
|
||||
.header.container.section-margin
|
||||
%h2 Actions
|
||||
%p{"style" => "margin-top: 3px;"} Choose "Actions" in the note editor to use installed actions.
|
||||
|
||||
%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
|
||||
%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}}
|
||||
%div{"ng-if" => "extension.showDetails"}
|
||||
.mt-10
|
||||
@@ -96,29 +104,33 @@
|
||||
%label.red{"ng-if" => "action.error"}
|
||||
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
|
||||
%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"}
|
||||
.container.no-bottom.section-margin
|
||||
.header.container.section-margin
|
||||
%h2 Components
|
||||
%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
|
||||
%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.deactivateComponent(component); $event.stopPropagation();"} Deactivate
|
||||
.mt-3{"ng-if" => "component.showDetails"}
|
||||
.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-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"}
|
||||
{{component.url}}
|
||||
|
||||
%div{"ng-if" => "serverExtensions.length > 0"}
|
||||
.container.no-bottom.section-margin
|
||||
.header.container.section-margin
|
||||
%h2 Server Extensions
|
||||
%ul
|
||||
%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%;"}
|
||||
Loading
|
||||
%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()}}
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
%span.tinted.normal New update downloaded. Installs on app restart.
|
||||
|
||||
.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"}
|
||||
Last refreshed {{ctrl.lastSyncDate | appDateTime}}
|
||||
%span{"ng-if" => "ctrl.isRefreshing"}
|
||||
@@ -28,4 +28,4 @@
|
||||
%a{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"} Refresh
|
||||
|
||||
%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
|
||||
.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}"}
|
||||
%strong.red.medium{"ng-if" => "note.conflict_of"} Conflicted copy
|
||||
%strong.red.medium{"ng-if" => "note.errorDecrypting"} Error decrypting
|
||||
|
||||
@@ -10,13 +10,16 @@
|
||||
.info
|
||||
%input.title{"ng-disabled" => "true", "ng-model" => "ctrl.allTag.title"}
|
||||
.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
|
||||
%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-change" => "ctrl.tagTitleDidChange(tag)", "ng-blur" => "ctrl.saveTag($event, tag)", "spellcheck" => "false"}
|
||||
.count {{ctrl.noteCount(tag)}}
|
||||
|
||||
.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"}
|
||||
%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
|
||||
|
||||
Reference in New Issue
Block a user