Integrate SFJS model management
This commit is contained in:
@@ -64,7 +64,7 @@ angular.module('app')
|
||||
return;
|
||||
}
|
||||
|
||||
if(!ModelManager.isMappingSourceRetrieved(source)) {
|
||||
if(!SFModelManager.isMappingSourceRetrieved(source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ angular.module('app')
|
||||
|
||||
// Currently extensions are not notified of association until a full server sync completes.
|
||||
// We need a better system for this, but for now, we'll manually notify observers
|
||||
modelManager.notifySyncObserversOfModels([this.note], ModelManager.MappingSourceLocalSaved);
|
||||
modelManager.notifySyncObserversOfModels([this.note], SFModelManager.MappingSourceLocalSaved);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,7 +564,7 @@ angular.module('app')
|
||||
|
||||
// Currently extensions are not notified of association until a full server sync completes.
|
||||
// We need a better system for this, but for now, we'll manually notify observers
|
||||
modelManager.notifySyncObserversOfModels([this.note], ModelManager.MappingSourceLocalSaved);
|
||||
modelManager.notifySyncObserversOfModels([this.note], SFModelManager.MappingSourceLocalSaved);
|
||||
}
|
||||
|
||||
else if(action === "save-items" || action === "save-success" || action == "save-error") {
|
||||
|
||||
@@ -87,7 +87,8 @@ angular.module('app')
|
||||
}
|
||||
|
||||
function loadAllTag() {
|
||||
var allTag = new Tag({all: true, title: "All"});
|
||||
var allTag = new Tag({content: {title: "All"}});
|
||||
allTag.all = true;
|
||||
allTag.needsLoad = true;
|
||||
$scope.allTag = allTag;
|
||||
$scope.tags = modelManager.tags;
|
||||
@@ -95,7 +96,8 @@ angular.module('app')
|
||||
}
|
||||
|
||||
function loadArchivedTag() {
|
||||
var archiveTag = new Tag({archiveTag: true, title: "Archived"});
|
||||
var archiveTag = new Tag({content: {title: "Archived"}});
|
||||
archiveTag.archiveTag = true;
|
||||
$scope.archiveTag = archiveTag;
|
||||
$scope.archiveTag.notes = modelManager.notes;
|
||||
}
|
||||
@@ -115,8 +117,6 @@ angular.module('app')
|
||||
|
||||
for(var tagToRemove of toRemove) {
|
||||
note.removeItemAsRelationship(tagToRemove);
|
||||
tagToRemove.removeItemAsRelationship(note);
|
||||
tagToRemove.setDirty(true);
|
||||
}
|
||||
|
||||
var tags = [];
|
||||
@@ -128,7 +128,7 @@ angular.module('app')
|
||||
}
|
||||
|
||||
for(var tag of tags) {
|
||||
modelManager.createRelationshipBetweenItems(note, tag);
|
||||
note.addItemAsRelationship(tag);
|
||||
}
|
||||
|
||||
note.setDirty(true);
|
||||
@@ -190,7 +190,8 @@ angular.module('app')
|
||||
modelManager.addItem(note);
|
||||
|
||||
if(!$scope.selectedTag.all && !$scope.selectedTag.archiveTag) {
|
||||
modelManager.createRelationshipBetweenItems($scope.selectedTag, note);
|
||||
note.addItemAsRelationship($scope.selectedTag);
|
||||
note.setDirty(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -217,8 +217,9 @@ angular.module('app')
|
||||
|
||||
this.createNewNote = function() {
|
||||
var title = "New Note" + (this.tag.notes ? (" " + (this.tag.notes.length + 1)) : "");
|
||||
this.newNote = modelManager.createItem({content_type: "Note", dummy: true, text: ""});
|
||||
this.newNote.title = title;
|
||||
let newNote = modelManager.createItem({content_type: "Note", content: {text: "", title: title}});
|
||||
newNote.dummy = true;
|
||||
this.newNote = newNote;
|
||||
this.selectNote(this.newNote);
|
||||
this.addNew()(this.newNote);
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ class AccountMenu {
|
||||
|
||||
$scope.importJSONData = function(data, password, callback) {
|
||||
var onDataReady = function(errorCount) {
|
||||
var items = modelManager.mapResponseItemsToLocalModels(data.items, ModelManager.MappingSourceFileImport);
|
||||
var items = modelManager.mapResponseItemsToLocalModels(data.items, SFModelManager.MappingSourceFileImport);
|
||||
items.forEach(function(item){
|
||||
item.setDirty(true, true);
|
||||
item.deleted = false;
|
||||
|
||||
@@ -1,314 +0,0 @@
|
||||
let AppDomain = "org.standardnotes.sn";
|
||||
var dateFormatter;
|
||||
|
||||
class Item {
|
||||
|
||||
constructor(json_obj = {}) {
|
||||
this.appData = {};
|
||||
this.updateFromJSON(json_obj);
|
||||
this.observers = [];
|
||||
|
||||
if(!this.uuid) {
|
||||
this.uuid = SFJS.crypto.generateUUIDSync();
|
||||
}
|
||||
}
|
||||
|
||||
static sortItemsByDate(items) {
|
||||
items.sort(function(a,b){
|
||||
return new Date(b.created_at) - new Date(a.created_at);
|
||||
});
|
||||
}
|
||||
|
||||
get contentObject() {
|
||||
if(!this.content) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if(this.content !== null && typeof this.content === 'object') {
|
||||
// this is the case when mapping localStorage content, in which case the content is already parsed
|
||||
return this.content;
|
||||
}
|
||||
|
||||
try {
|
||||
// console.log("Parsing json", this.content);
|
||||
this.content = JSON.parse(this.content);
|
||||
return this.content;
|
||||
} catch (e) {
|
||||
console.log("Error parsing json", e, this);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
updateFromJSON(json) {
|
||||
_.merge(this, json);
|
||||
|
||||
if(this.created_at) {
|
||||
this.created_at = new Date(this.created_at);
|
||||
this.updated_at = new Date(this.updated_at);
|
||||
} else {
|
||||
this.created_at = new Date();
|
||||
this.updated_at = new Date();
|
||||
}
|
||||
|
||||
// Allows the getter to be re-invoked
|
||||
this._client_updated_at = null;
|
||||
|
||||
if(json.content) {
|
||||
this.mapContentToLocalProperties(this.contentObject);
|
||||
} else if(json.deleted == true) {
|
||||
this.handleDeletedContent();
|
||||
}
|
||||
}
|
||||
|
||||
mapContentToLocalProperties(contentObj) {
|
||||
if(contentObj.appData) {
|
||||
this.appData = contentObj.appData;
|
||||
}
|
||||
if(!this.appData) { this.appData = {}; }
|
||||
}
|
||||
|
||||
createContentJSONFromProperties() {
|
||||
return this.structureParams();
|
||||
}
|
||||
|
||||
referenceParams() {
|
||||
// subclasses can override
|
||||
return this.contentObject.references || [];
|
||||
}
|
||||
|
||||
structureParams() {
|
||||
var params = this.contentObject;
|
||||
params.appData = this.appData;
|
||||
params.references = this.referenceParams();
|
||||
return params;
|
||||
}
|
||||
|
||||
refreshContentObject() {
|
||||
// Before an item can be duplicated or cloned, we must update this.content (if it is an object) with the object's
|
||||
// current physical properties, because updateFromJSON, which is what all new items must go through,
|
||||
// will call this.mapContentToLocalProperties(this.contentObject), which may have stale values if not explicitly updated.
|
||||
|
||||
this.content = this.structureParams();
|
||||
}
|
||||
|
||||
/* Allows the item to handle the case where the item is deleted and the content is null */
|
||||
handleDeletedContent() {
|
||||
// Subclasses can override
|
||||
}
|
||||
|
||||
setDirty(dirty, dontUpdateClientDate) {
|
||||
this.dirty = dirty;
|
||||
|
||||
// Allows the syncManager to check if an item has been marked dirty after a sync has been started
|
||||
// This prevents it from clearing it as a dirty item after sync completion, if someone else has marked it dirty
|
||||
// again after an ongoing sync.
|
||||
if(!this.dirtyCount) { this.dirtyCount = 0; }
|
||||
if(dirty) {
|
||||
this.dirtyCount++;
|
||||
} else {
|
||||
this.dirtyCount = 0;
|
||||
}
|
||||
|
||||
if(dirty && !dontUpdateClientDate) {
|
||||
// Set the client modified date to now if marking the item as dirty
|
||||
this.client_updated_at = new Date();
|
||||
} else if(!this.hasRawClientUpdatedAtValue()) {
|
||||
// copy updated_at
|
||||
this.client_updated_at = new Date(this.updated_at);
|
||||
}
|
||||
|
||||
if(dirty) {
|
||||
this.notifyObserversOfChange();
|
||||
}
|
||||
}
|
||||
|
||||
addObserver(observer, callback) {
|
||||
if(!_.find(this.observers, observer)) {
|
||||
this.observers.push({observer: observer, callback: callback});
|
||||
}
|
||||
}
|
||||
|
||||
removeObserver(observer) {
|
||||
_.remove(this.observers, {observer: observer})
|
||||
}
|
||||
|
||||
notifyObserversOfChange() {
|
||||
for(var observer of this.observers) {
|
||||
observer.callback(this);
|
||||
}
|
||||
}
|
||||
|
||||
addItemAsRelationship(item) {
|
||||
// must override
|
||||
}
|
||||
|
||||
removeItemAsRelationship(item) {
|
||||
// must override
|
||||
}
|
||||
|
||||
isBeingRemovedLocally() {
|
||||
|
||||
}
|
||||
|
||||
removeAndDirtyAllRelationships() {
|
||||
// must override
|
||||
this.setDirty(true);
|
||||
}
|
||||
|
||||
removeReferencesNotPresentIn(references) {
|
||||
|
||||
}
|
||||
|
||||
mergeMetadataFromItem(item) {
|
||||
_.merge(this, _.omit(item, ["content"]));
|
||||
}
|
||||
|
||||
informReferencesOfUUIDChange(oldUUID, newUUID) {
|
||||
// optional override
|
||||
}
|
||||
|
||||
potentialItemOfInterestHasChangedItsUUID(newItem, oldUUID, newUUID) {
|
||||
// optional override
|
||||
for(var reference of this.content.references) {
|
||||
if(reference.uuid == oldUUID) {
|
||||
reference.uuid = newUUID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doNotEncrypt() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
App Data
|
||||
*/
|
||||
|
||||
setDomainDataItem(key, value, domain) {
|
||||
var data = this.appData[domain];
|
||||
if(!data) {
|
||||
data = {}
|
||||
}
|
||||
data[key] = value;
|
||||
this.appData[domain] = data;
|
||||
}
|
||||
|
||||
getDomainDataItem(key, domain) {
|
||||
var data = this.appData[domain];
|
||||
if(data) {
|
||||
return data[key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
setAppDataItem(key, value) {
|
||||
this.setDomainDataItem(key, value, AppDomain);
|
||||
}
|
||||
|
||||
getAppDataItem(key) {
|
||||
return this.getDomainDataItem(key, AppDomain);
|
||||
}
|
||||
|
||||
get pinned() {
|
||||
return this.getAppDataItem("pinned");
|
||||
}
|
||||
|
||||
get archived() {
|
||||
return this.getAppDataItem("archived");
|
||||
}
|
||||
|
||||
get locked() {
|
||||
return this.getAppDataItem("locked");
|
||||
}
|
||||
|
||||
hasRawClientUpdatedAtValue() {
|
||||
return this.getAppDataItem("client_updated_at") != null;
|
||||
}
|
||||
|
||||
get client_updated_at() {
|
||||
if(!this._client_updated_at) {
|
||||
var saved = this.getAppDataItem("client_updated_at");
|
||||
if(saved) {
|
||||
this._client_updated_at = new Date(saved);
|
||||
} else {
|
||||
this._client_updated_at = new Date(this.updated_at);
|
||||
}
|
||||
}
|
||||
return this._client_updated_at;
|
||||
}
|
||||
|
||||
set client_updated_at(date) {
|
||||
this._client_updated_at = date;
|
||||
|
||||
this.setAppDataItem("client_updated_at", date);
|
||||
}
|
||||
|
||||
/*
|
||||
During sync conflicts, when determing whether to create a duplicate for an item, we can omit keys that have no
|
||||
meaningful weight and can be ignored. For example, if one component has active = true and another component has active = false,
|
||||
it would be silly to duplicate them, so instead we ignore this.
|
||||
*/
|
||||
keysToIgnoreWhenCheckingContentEquality() {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Same as above, but keys inside appData[AppDomain]
|
||||
appDataKeysToIgnoreWhenCheckingContentEquality() {
|
||||
return ["client_updated_at"];
|
||||
}
|
||||
|
||||
isItemContentEqualWith(otherItem) {
|
||||
let omit = (obj, keys) => {
|
||||
for(var key of keys) {
|
||||
delete obj[key];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
var left = this.structureParams();
|
||||
left.appData[AppDomain] = omit(left.appData[AppDomain], this.appDataKeysToIgnoreWhenCheckingContentEquality());
|
||||
left = omit(left, this.keysToIgnoreWhenCheckingContentEquality());
|
||||
|
||||
var right = otherItem.structureParams();
|
||||
right.appData[AppDomain] = omit(right.appData[AppDomain], otherItem.appDataKeysToIgnoreWhenCheckingContentEquality());
|
||||
right = omit(right, otherItem.keysToIgnoreWhenCheckingContentEquality());
|
||||
|
||||
return JSON.stringify(left) === JSON.stringify(right)
|
||||
}
|
||||
|
||||
/*
|
||||
Dates
|
||||
*/
|
||||
|
||||
createdAtString() {
|
||||
return this.dateToLocalizedString(this.created_at);
|
||||
}
|
||||
|
||||
updatedAtString() {
|
||||
return this.dateToLocalizedString(this.client_updated_at);
|
||||
}
|
||||
|
||||
dateToLocalizedString(date) {
|
||||
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
|
||||
if (!dateFormatter) {
|
||||
var locale = (navigator.languages && navigator.languages.length) ? navigator.languages[0] : navigator.language;
|
||||
dateFormatter = new Intl.DateTimeFormat(locale, {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
weekday: 'long',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
}
|
||||
return dateFormatter.format(date);
|
||||
} else {
|
||||
// IE < 11, Safari <= 9.0.
|
||||
// In English, this generates the string most similar to
|
||||
// the toLocaleDateString() result above.
|
||||
return date.toDateString() + ' ' + date.toLocaleTimeString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
class Mfa extends Item {
|
||||
class Mfa extends SFItem {
|
||||
|
||||
constructor(json_obj) {
|
||||
super(json_obj);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class ServerExtension extends Item {
|
||||
class ServerExtension extends SFItem {
|
||||
|
||||
constructor(json_obj) {
|
||||
super(json_obj);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Component extends Item {
|
||||
class Component extends SFItem {
|
||||
|
||||
constructor(json_obj) {
|
||||
// If making a copy of an existing component (usually during sign in if you have a component active in the session),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Editor extends Item {
|
||||
class Editor extends SFItem {
|
||||
|
||||
constructor(json_obj) {
|
||||
super(json_obj);
|
||||
@@ -68,18 +68,18 @@ class Editor extends Item {
|
||||
var uuids = references.map(function(ref){return ref.uuid});
|
||||
this.notes.forEach(function(note){
|
||||
if(!uuids.includes(note.uuid)) {
|
||||
_.pull(this.notes, note);
|
||||
_.remove(this.notes, {uuid: note.uuid});
|
||||
}
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
potentialItemOfInterestHasChangedItsUUID(newItem, oldUUID, newUUID) {
|
||||
if(newItem.content_type === "Note" && _.find(this.notes, {uuid: oldUUID})) {
|
||||
_.pull(this.notes, {uuid: oldUUID});
|
||||
_.remove(this.notes, {uuid: oldUUID});
|
||||
this.notes.push(newItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
toJSON() {
|
||||
return {uuid: this.uuid}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Note extends Item {
|
||||
export class Note extends SFItem {
|
||||
|
||||
constructor(json_obj) {
|
||||
super(json_obj);
|
||||
@@ -21,14 +21,6 @@ class Note extends Item {
|
||||
this.text = content.text;
|
||||
}
|
||||
|
||||
referenceParams() {
|
||||
var references = _.map(this.tags, function(tag){
|
||||
return {uuid: tag.uuid, content_type: tag.content_type};
|
||||
})
|
||||
|
||||
return references;
|
||||
}
|
||||
|
||||
structureParams() {
|
||||
var params = {
|
||||
title: this.title,
|
||||
@@ -44,8 +36,9 @@ class Note extends Item {
|
||||
this.savedTagsString = null;
|
||||
|
||||
if(item.content_type == "Tag") {
|
||||
if(!_.find(this.tags, item)) {
|
||||
if(!_.find(this.tags, {uuid: item.uuid})) {
|
||||
this.tags.push(item);
|
||||
item.notes.push(this);
|
||||
}
|
||||
}
|
||||
super.addItemAsRelationship(item);
|
||||
@@ -55,38 +48,29 @@ class Note extends Item {
|
||||
this.savedTagsString = null;
|
||||
|
||||
if(item.content_type == "Tag") {
|
||||
_.pull(this.tags, item);
|
||||
_.remove(this.tags, {uuid: item.uuid});
|
||||
_.remove(item.notes, {uuid: this.uuid});
|
||||
}
|
||||
super.removeItemAsRelationship(item);
|
||||
}
|
||||
|
||||
removeAndDirtyAllRelationships() {
|
||||
updateLocalRelationships() {
|
||||
this.savedTagsString = null;
|
||||
|
||||
this.tags.forEach(function(tag){
|
||||
_.pull(tag.notes, this);
|
||||
tag.setDirty(true);
|
||||
}.bind(this))
|
||||
this.tags = [];
|
||||
}
|
||||
|
||||
removeReferencesNotPresentIn(references) {
|
||||
this.savedTagsString = null;
|
||||
|
||||
super.removeReferencesNotPresentIn(references);
|
||||
var references = this.content.references;
|
||||
|
||||
var uuids = references.map(function(ref){return ref.uuid});
|
||||
this.tags.slice().forEach(function(tag){
|
||||
if(!uuids.includes(tag.uuid)) {
|
||||
_.pull(tag.notes, this);
|
||||
_.pull(this.tags, tag);
|
||||
_.remove(tag.notes, {uuid: this.uuid});
|
||||
_.remove(this.tags, {uuid: tag.uuid});
|
||||
}
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
isBeingRemovedLocally() {
|
||||
this.tags.forEach(function(tag){
|
||||
_.pull(tag.notes, this);
|
||||
_.remove(tag.notes, {uuid: this.uuid});
|
||||
}.bind(this))
|
||||
super.isBeingRemovedLocally();
|
||||
}
|
||||
@@ -99,7 +83,7 @@ class Note extends Item {
|
||||
informReferencesOfUUIDChange(oldUUID, newUUID) {
|
||||
super.informReferencesOfUUIDChange();
|
||||
for(var tag of this.tags) {
|
||||
_.pull(tag.notes, {uuid: oldUUID});
|
||||
_.remove(tag.notes, {uuid: oldUUID});
|
||||
tag.notes.push(this);
|
||||
}
|
||||
}
|
||||
@@ -116,10 +100,6 @@ class Note extends Item {
|
||||
return {uuid: this.uuid}
|
||||
}
|
||||
|
||||
get content_type() {
|
||||
return "Note";
|
||||
}
|
||||
|
||||
tagsString() {
|
||||
this.savedTagsString = Tag.arrayToDisplayString(this.tags);
|
||||
return this.savedTagsString;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Tag extends Item {
|
||||
export class Tag extends SFItem {
|
||||
|
||||
constructor(json_obj) {
|
||||
super(json_obj);
|
||||
@@ -13,14 +13,6 @@ class Tag extends Item {
|
||||
this.title = content.title;
|
||||
}
|
||||
|
||||
referenceParams() {
|
||||
var references = _.map(this.notes, function(note){
|
||||
return {uuid: note.uuid, content_type: note.content_type};
|
||||
})
|
||||
|
||||
return references;
|
||||
}
|
||||
|
||||
structureParams() {
|
||||
var params = {
|
||||
title: this.title
|
||||
@@ -31,59 +23,20 @@ class Tag extends Item {
|
||||
return superParams;
|
||||
}
|
||||
|
||||
addItemAsRelationship(item) {
|
||||
if(item.content_type == "Note") {
|
||||
if(!_.find(this.notes, item)) {
|
||||
this.notes.unshift(item);
|
||||
}
|
||||
}
|
||||
super.addItemAsRelationship(item);
|
||||
}
|
||||
|
||||
removeItemAsRelationship(item) {
|
||||
if(item.content_type == "Note") {
|
||||
_.pull(this.notes, item);
|
||||
}
|
||||
super.removeItemAsRelationship(item);
|
||||
}
|
||||
|
||||
removeAndDirtyAllRelationships() {
|
||||
this.notes.forEach(function(note){
|
||||
_.pull(note.tags, this);
|
||||
note.setDirty(true);
|
||||
}.bind(this))
|
||||
|
||||
this.notes = [];
|
||||
}
|
||||
|
||||
removeReferencesNotPresentIn(references) {
|
||||
var uuids = references.map(function(ref){return ref.uuid});
|
||||
this.notes.slice().forEach(function(note){
|
||||
if(!uuids.includes(note.uuid)) {
|
||||
_.pull(note.tags, this);
|
||||
_.pull(this.notes, note);
|
||||
}
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
isBeingRemovedLocally() {
|
||||
this.notes.forEach(function(note){
|
||||
_.pull(note.tags, this);
|
||||
_.remove(note.tags, {uuid: this.uuid});
|
||||
}.bind(this))
|
||||
super.isBeingRemovedLocally();
|
||||
}
|
||||
|
||||
informReferencesOfUUIDChange(oldUUID, newUUID) {
|
||||
for(var note of this.notes) {
|
||||
_.pull(note.tags, {uuid: oldUUID});
|
||||
_.remove(note.tags, {uuid: oldUUID});
|
||||
note.tags.push(this);
|
||||
}
|
||||
}
|
||||
|
||||
get content_type() {
|
||||
return "Tag";
|
||||
}
|
||||
|
||||
static arrayToDisplayString(tags) {
|
||||
return tags.sort((a, b) => {return a.title > b.title}).map(function(tag, i){
|
||||
return "#" + tag.title;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class EncryptedStorage extends Item {
|
||||
class EncryptedStorage extends SFItem {
|
||||
|
||||
constructor(json_obj) {
|
||||
super(json_obj);
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
class ItemParams {
|
||||
|
||||
constructor(item, keys, version) {
|
||||
this.item = item;
|
||||
this.keys = keys;
|
||||
this.version = version || SFJS.version();
|
||||
}
|
||||
|
||||
async paramsForExportFile(includeDeleted) {
|
||||
this.additionalFields = ["updated_at"];
|
||||
this.forExportFile = true;
|
||||
if(includeDeleted) {
|
||||
return this.__params();
|
||||
} else {
|
||||
var result = await this.__params();
|
||||
return _.omit(result, ["deleted"]);
|
||||
}
|
||||
}
|
||||
|
||||
async paramsForExtension() {
|
||||
return this.paramsForExportFile();
|
||||
}
|
||||
|
||||
async paramsForLocalStorage() {
|
||||
this.additionalFields = ["updated_at", "dirty", "errorDecrypting"];
|
||||
this.forExportFile = true;
|
||||
return this.__params();
|
||||
}
|
||||
|
||||
async paramsForSync() {
|
||||
return this.__params();
|
||||
}
|
||||
|
||||
async __params() {
|
||||
|
||||
console.assert(!this.item.dummy, "Item is dummy, should not have gotten here.", this.item.dummy)
|
||||
|
||||
var params = {uuid: this.item.uuid, content_type: this.item.content_type, deleted: this.item.deleted, created_at: this.item.created_at};
|
||||
if(!this.item.errorDecrypting) {
|
||||
// Items should always be encrypted for export files. Only respect item.doNotEncrypt for remote sync params.
|
||||
var doNotEncrypt = this.item.doNotEncrypt() && !this.forExportFile;
|
||||
if(this.keys && !doNotEncrypt) {
|
||||
var encryptedParams = await SFJS.itemTransformer.encryptItem(this.item, this.keys, this.version);
|
||||
_.merge(params, encryptedParams);
|
||||
|
||||
if(this.version !== "001") {
|
||||
params.auth_hash = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
params.content = this.forExportFile ? this.item.createContentJSONFromProperties() : "000" + await SFJS.crypto.base64(JSON.stringify(this.item.createContentJSONFromProperties()));
|
||||
if(!this.forExportFile) {
|
||||
params.enc_item_key = null;
|
||||
params.auth_hash = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Error decrypting, keep "content" and related fields as is (and do not try to encrypt, otherwise that would be undefined behavior)
|
||||
params.content = this.item.content;
|
||||
params.enc_item_key = this.item.enc_item_key;
|
||||
params.auth_hash = this.item.auth_hash;
|
||||
}
|
||||
|
||||
if(this.additionalFields) {
|
||||
_.merge(params, _.pick(this.item, this.additionalFields));
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -74,7 +74,7 @@ class ActionsManager {
|
||||
|
||||
if(!item.errorDecrypting) {
|
||||
if(merge) {
|
||||
var items = this.modelManager.mapResponseItemsToLocalModels([item], ModelManager.MappingSourceRemoteActionRetrieved);
|
||||
var items = this.modelManager.mapResponseItemsToLocalModels([item], SFModelManager.MappingSourceRemoteActionRetrieved);
|
||||
for(var mappedItem of items) {
|
||||
mappedItem.setDirty(true);
|
||||
}
|
||||
@@ -179,7 +179,7 @@ class ActionsManager {
|
||||
if(decrypted) {
|
||||
keys = null;
|
||||
}
|
||||
var itemParams = new ItemParams(item, keys, this.authManager.protocolVersion());
|
||||
var itemParams = new SFItemParams(item, keys, this.authManager.protocolVersion());
|
||||
return itemParams.paramsForExtension();
|
||||
}
|
||||
|
||||
|
||||
@@ -296,7 +296,7 @@ angular.module('app')
|
||||
this.userPreferencesDidChange();
|
||||
}, (valueCallback) => {
|
||||
// Safe to create. Create and return object.
|
||||
var prefs = new Item({content_type: prefsContentType});
|
||||
var prefs = new SFItem({content_type: prefsContentType});
|
||||
modelManager.addItem(prefs);
|
||||
prefs.setDirty(true);
|
||||
$rootScope.sync("authManager singletonCreate");
|
||||
|
||||
@@ -53,13 +53,13 @@ class ComponentManager {
|
||||
|
||||
/* If the source of these new or updated items is from a Component itself saving items, we don't need to notify
|
||||
components again of the same item. Regarding notifying other components than the issuing component, other mapping sources
|
||||
will take care of that, like ModelManager.MappingSourceRemoteSaved
|
||||
will take care of that, like SFModelManager.MappingSourceRemoteSaved
|
||||
|
||||
Update: We will now check sourceKey to determine whether the incoming change should be sent to
|
||||
a component. If sourceKey == component.uuid, it will be skipped. This way, if one component triggers a change,
|
||||
it's sent to other components.
|
||||
*/
|
||||
// if(source == ModelManager.MappingSourceComponentRetrieved) {
|
||||
// if(source == SFModelManager.MappingSourceComponentRetrieved) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
@@ -70,7 +70,7 @@ class ComponentManager {
|
||||
/* We only want to sync if the item source is Retrieved, not MappingSourceRemoteSaved to avoid
|
||||
recursion caused by the component being modified and saved after it is updated.
|
||||
*/
|
||||
if(syncedComponents.length > 0 && source != ModelManager.MappingSourceRemoteSaved) {
|
||||
if(syncedComponents.length > 0 && source != SFModelManager.MappingSourceRemoteSaved) {
|
||||
// Ensure any component in our data is installed by the system
|
||||
this.desktopManager.syncComponentsInstallation(syncedComponents);
|
||||
}
|
||||
@@ -195,11 +195,11 @@ class ComponentManager {
|
||||
/* This means the this function is being triggered through a remote Saving response, which should not update
|
||||
actual local content values. The reason is, Save responses may be delayed, and a user may have changed some values
|
||||
in between the Save was initiated, and the time it completes. So we only want to update actual content values (and not just metadata)
|
||||
when its another source, like ModelManager.MappingSourceRemoteRetrieved.
|
||||
when its another source, like SFModelManager.MappingSourceRemoteRetrieved.
|
||||
|
||||
3/7/18: Add MappingSourceLocalSaved as well to handle fully offline saving. github.com/standardnotes/forum/issues/169
|
||||
*/
|
||||
if(source && (source == ModelManager.MappingSourceRemoteSaved || source == ModelManager.MappingSourceLocalSaved)) {
|
||||
if(source && (source == SFModelManager.MappingSourceRemoteSaved || source == SFModelManager.MappingSourceLocalSaved)) {
|
||||
params.isMetadataUpdate = true;
|
||||
}
|
||||
this.removePrivatePropertiesFromResponseItems([params], component);
|
||||
@@ -468,7 +468,7 @@ class ComponentManager {
|
||||
We map the items here because modelManager is what updates the UI. If you were to instead get the items directly,
|
||||
this would update them server side via sync, but would never make its way back to the UI.
|
||||
*/
|
||||
var localItems = this.modelManager.mapResponseItemsToLocalModels(responseItems, ModelManager.MappingSourceComponentRetrieved, component.uuid);
|
||||
var localItems = this.modelManager.mapResponseItemsToLocalModels(responseItems, SFModelManager.MappingSourceComponentRetrieved, component.uuid);
|
||||
|
||||
for(var item of localItems) {
|
||||
var responseItem = _.find(responseItems, {uuid: item.uuid});
|
||||
|
||||
@@ -37,7 +37,7 @@ class DesktopManager {
|
||||
Keys are not passed into ItemParams, so the result is not encrypted
|
||||
*/
|
||||
async convertComponentForTransmission(component) {
|
||||
return new ItemParams(component).paramsForExportFile(true);
|
||||
return new SFItemParams(component).paramsForExportFile(true);
|
||||
}
|
||||
|
||||
// All `components` should be installed
|
||||
@@ -96,7 +96,7 @@ class DesktopManager {
|
||||
for(var key of permissableKeys) {
|
||||
component[key] = componentData.content[key];
|
||||
}
|
||||
this.modelManager.notifySyncObserversOfModels([component], ModelManager.MappingSourceDesktopInstalled);
|
||||
this.modelManager.notifySyncObserversOfModels([component], SFModelManager.MappingSourceDesktopInstalled);
|
||||
component.setAppDataItem("installError", null);
|
||||
}
|
||||
component.setDirty(true);
|
||||
|
||||
@@ -36,9 +36,11 @@ class MigrationManager {
|
||||
if(editor.url && !this.componentManager.componentForUrl(editor.url)) {
|
||||
var component = this.modelManager.createItem({
|
||||
content_type: "SN|Component",
|
||||
url: editor.url,
|
||||
name: editor.name,
|
||||
area: "editor-editor"
|
||||
content: {
|
||||
url: editor.url,
|
||||
name: editor.name,
|
||||
area: "editor-editor"
|
||||
}
|
||||
})
|
||||
component.setAppDataItem("data", editor.data);
|
||||
component.setDirty(true);
|
||||
|
||||
@@ -1,10 +1,25 @@
|
||||
class ModelManager {
|
||||
SFModelManager.ContentTypeClassMapping = {
|
||||
"Note" : Note,
|
||||
"Tag" : Tag,
|
||||
"Extension" : Extension,
|
||||
"SN|Editor" : Editor,
|
||||
"SN|Theme" : Theme,
|
||||
"SN|Component" : Component,
|
||||
"SF|Extension" : ServerExtension,
|
||||
"SF|MFA" : Mfa
|
||||
};
|
||||
|
||||
SFItem.AppDomain = "org.standardnotes.sn";
|
||||
|
||||
class ModelManager extends SFModelManager {
|
||||
|
||||
constructor(storageManager) {
|
||||
super(storageManager);
|
||||
super();
|
||||
this.notes = [];
|
||||
this.tags = [];
|
||||
this._extensions = [];
|
||||
|
||||
this.storageManager = storageManager;
|
||||
}
|
||||
|
||||
resetLocalMemory() {
|
||||
@@ -17,7 +32,7 @@ class ModelManager {
|
||||
findOrCreateTagByTitle(title) {
|
||||
var tag = _.find(this.tags, {title: title})
|
||||
if(!tag) {
|
||||
tag = this.createItem({content_type: "Tag", title: title});
|
||||
tag = this.createItem({content_type: "Tag", content: {title: title}});
|
||||
this.addItem(tag);
|
||||
}
|
||||
return tag;
|
||||
@@ -86,6 +101,8 @@ class ModelManager {
|
||||
} else if(item.content_type == "Extension") {
|
||||
_.pull(this._extensions, item);
|
||||
}
|
||||
|
||||
this.storageManager.deleteModel(item, callback);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -153,7 +153,7 @@ class StorageManager {
|
||||
encryptedStorage.storage = this.storageAsHash();
|
||||
|
||||
// Save new encrypted storage in Fixed storage
|
||||
var params = new ItemParams(encryptedStorage, this.encryptedStorageKeys, this.encryptedStorageAuthParams.version);
|
||||
var params = new SFItemParams(encryptedStorage, this.encryptedStorageKeys, this.encryptedStorageAuthParams.version);
|
||||
params.paramsForSync().then((syncParams) => {
|
||||
this.setItem("encryptedStorage", JSON.stringify(syncParams), StorageManager.Fixed);
|
||||
})
|
||||
|
||||
@@ -48,7 +48,7 @@ class SyncManager {
|
||||
var keys = this.authManager.offline() ? this.passcodeManager.keys() : this.authManager.keys();
|
||||
|
||||
Promise.all(items.map(async (item) => {
|
||||
var itemParams = new ItemParams(item, keys, version);
|
||||
var itemParams = new SFItemParams(item, keys, version);
|
||||
itemParams = await itemParams.paramsForLocalStorage();
|
||||
if(offlineOnly) {
|
||||
delete itemParams.dirty;
|
||||
@@ -79,13 +79,13 @@ class SyncManager {
|
||||
var processed = [];
|
||||
|
||||
var completion = () => {
|
||||
Item.sortItemsByDate(processed);
|
||||
SFItem.sortItemsByDate(processed);
|
||||
callback(processed);
|
||||
}
|
||||
|
||||
var decryptNext = async () => {
|
||||
var subitems = items.slice(current, current + iteration);
|
||||
var processedSubitems = await this.handleItemsResponse(subitems, null, ModelManager.MappingSourceLocalRetrieved);
|
||||
var processedSubitems = await this.handleItemsResponse(subitems, null, SFModelManager.MappingSourceLocalRetrieved);
|
||||
processed.push(processedSubitems);
|
||||
|
||||
current += subitems.length;
|
||||
@@ -93,6 +93,7 @@ class SyncManager {
|
||||
if(current < total) {
|
||||
this.$timeout(() => { decryptNext(); });
|
||||
} else {
|
||||
// this.modelManager.resolveReferencesForAllItems()
|
||||
completion();
|
||||
}
|
||||
}
|
||||
@@ -341,7 +342,7 @@ class SyncManager {
|
||||
params.limit = 150;
|
||||
|
||||
await Promise.all(subItems.map((item) => {
|
||||
var itemParams = new ItemParams(item, keys, version);
|
||||
var itemParams = new SFItemParams(item, keys, version);
|
||||
itemParams.additionalFields = options.additionalFields;
|
||||
return itemParams.paramsForSync();
|
||||
})).then((itemsParams) => {
|
||||
@@ -386,7 +387,7 @@ class SyncManager {
|
||||
|
||||
// Map retrieved items to local data
|
||||
// Note that deleted items will not be returned
|
||||
var retrieved = await this.handleItemsResponse(response.retrieved_items, null, ModelManager.MappingSourceRemoteRetrieved);
|
||||
var retrieved = await this.handleItemsResponse(response.retrieved_items, null, SFModelManager.MappingSourceRemoteRetrieved);
|
||||
|
||||
// Append items to master list of retrieved items for this ongoing sync operation
|
||||
this.allRetreivedItems = this.allRetreivedItems.concat(retrieved);
|
||||
@@ -397,7 +398,7 @@ class SyncManager {
|
||||
var omitFields = ["content", "auth_hash"];
|
||||
|
||||
// Map saved items to local data
|
||||
var saved = await this.handleItemsResponse(response.saved_items, omitFields, ModelManager.MappingSourceRemoteSaved);
|
||||
var saved = await this.handleItemsResponse(response.saved_items, omitFields, SFModelManager.MappingSourceRemoteSaved);
|
||||
|
||||
// Append items to master list of saved items for this ongoing sync operation
|
||||
this.allSavedItems = this.allSavedItems.concat(saved);
|
||||
@@ -504,7 +505,7 @@ class SyncManager {
|
||||
refreshErroredItems() {
|
||||
var erroredItems = this.modelManager.allItems.filter((item) => {return item.errorDecrypting == true});
|
||||
if(erroredItems.length > 0) {
|
||||
this.handleItemsResponse(erroredItems, null, ModelManager.MappingSourceLocalRetrieved);
|
||||
this.handleItemsResponse(erroredItems, null, SFModelManager.MappingSourceLocalRetrieved);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user