253 lines
5.4 KiB
JavaScript
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();
|
|
}
|
|
}
|
|
|
|
}
|