Files
standardnotes-app-web/app/assets/javascripts/app/models/api/item.js
2018-01-27 09:40:48 -06:00

253 lines
5.4 KiB
JavaScript

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 = Neeto.crypto.generateUUID();
}
}
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 {
return JSON.parse(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();
}
if(json.content) {
this.mapContentToLocalProperties(this.contentObject);
} else if(json.deleted == true) {
this.handleDeletedContent();
}
}
/* Allows the item to handle the case where the item is deleted and the content is null */
handleDeletedContent() {
// Subclasses can override
}
setDirty(dirty) {
this.dirty = dirty;
if(dirty) {
this.notifyObserversOfChange();
}
}
markAllReferencesDirty() {
this.allReferencedObjects().forEach(function(reference){
reference.setDirty(true);
})
}
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);
}
}
mapContentToLocalProperties(contentObj) {
if(contentObj.appData) {
this.appData = contentObj.appData;
}
if(!this.appData) { this.appData = {}; }
}
createContentJSONFromProperties() {
return this.structureParams();
}
referenceParams() {
// must override
}
structureParams() {
return {
references: this.referenceParams(),
appData: this.appData
}
}
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
}
allReferencedObjects() {
// must override
return [];
}
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");
}
/*
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 [];
}
isItemContentEqualWith(otherItem) {
let omit = (obj, keys) => {
for(var key of keys) {
delete obj[key];
}
return obj;
}
var left = omit(this.structureParams(), this.keysToIgnoreWhenCheckingContentEquality());
var right = omit(otherItem.structureParams(), otherItem.keysToIgnoreWhenCheckingContentEquality());
return JSON.stringify(left) === JSON.stringify(right)
}
/*
Dates
*/
createdAtString() {
return this.dateToLocalizedString(this.created_at);
}
updatedAtString() {
return this.dateToLocalizedString(this.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();
}
}
}