Merge pull request #162 from standardnotes/2.0.2

2.0.2
This commit is contained in:
Mo Bitar
2017-11-04 19:02:20 -07:00
committed by GitHub
26 changed files with 294 additions and 200 deletions

View File

@@ -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;

View File

@@ -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 = "";

View File

@@ -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);
} }

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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 = [];
} }

View File

@@ -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());

View File

@@ -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);

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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);
} }

View File

@@ -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";
} }
} }

View File

@@ -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) {

View File

@@ -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){

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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;

View File

@@ -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;

View File

@@ -50,10 +50,6 @@ h2 {
margin-bottom: 4px; margin-bottom: 4px;
} }
strong {
display: block;
}
h1 { h1 {
font-size: 16px; font-size: 16px;
} }

View File

@@ -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"}

View File

@@ -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()}}

View File

@@ -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()"}

View File

@@ -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

View File

@@ -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