diff --git a/packages/core/api/conflicts.js b/packages/core/api/conflicts.js new file mode 100644 index 000000000..42d85d3c8 --- /dev/null +++ b/packages/core/api/conflicts.js @@ -0,0 +1,18 @@ +import Database from "./index"; + +class Conflicts { + /** + * + * @param {Database} db + */ + constructor(db) { + this._db = db; + } + + recalculate = async () => { + if (this._db.notes.conflicted.length < 0) { + await this._db.context.write("hasConflicts", false); + } + }; +} +export default Conflicts; diff --git a/packages/core/api/index.js b/packages/core/api/index.js index 8520e8dff..e1c69e24e 100644 --- a/packages/core/api/index.js +++ b/packages/core/api/index.js @@ -7,6 +7,7 @@ import Sync from "./sync"; import Vault from "./vault"; import Lookup from "./lookup"; import Content from "../collections/content"; +import Conflict from "./conflicts"; class Database { constructor(context) { @@ -37,6 +38,7 @@ class Database { await this.trash.init(this.notes, this.notebooks, this.delta, this.text); this.syncer = new Sync(this); this.vault = new Vault(this, this.context); + this.conflicts = new Conflicts(this); this.lookup = new Lookup(this); } diff --git a/packages/core/api/sync.js b/packages/core/api/sync.js index e0641ac7d..31ed4f894 100644 --- a/packages/core/api/sync.js +++ b/packages/core/api/sync.js @@ -45,7 +45,7 @@ export default class Sync { let token = await this.db.user.token(); if (!token) throw new Error("You are not logged in"); let response = await fetch(`${HOST}sync?lst=${lastSyncedTimestamp}`, { - headers: { ...HEADERS, Authorization: `Bearer ${token}` } + headers: { ...HEADERS, Authorization: `Bearer ${token}` }, }); //TODO decrypt the response. return await response.json(); @@ -66,6 +66,7 @@ export default class Sync { let user = await this.db.user.get(); if (!user) throw new Error("You need to login to sync."); + await this.db.conflicts.recalculate(); await this.throwOnConflicts(); let lastSyncedTimestamp = user.lastSynced || 0; @@ -94,7 +95,7 @@ export default class Sync { let response = await fetch(`${HOST}sync`, { method: "POST", headers: { ...HEADERS, Authorization: `Bearer ${token}` }, - body: JSON.stringify(data) + body: JSON.stringify(data), }); return response.ok; } @@ -120,7 +121,7 @@ class Merger { async _mergeArray(array, get, set) { return Promise.all( - array.map(async item => await this._mergeItem(item, get, set)) + array.map(async (item) => await this._mergeItem(item, get, set)) ); } @@ -137,7 +138,7 @@ class Merger { async _mergeArrayWithConflicts(array, get, set, resolve) { return Promise.all( array.map( - async item => + async (item) => await this._mergeItemWithConflicts(item, get, set, resolve) ) ); @@ -152,26 +153,26 @@ class Merger { text, tags, colors, - trash + trash, } = serverResponse; if (synced || areAllEmpty(serverResponse)) return false; await this._mergeArray( notes, - id => this._db.notes.note(id), - item => this._db.notes.add(item) + (id) => this._db.notes.note(id), + (item) => this._db.notes.add(item) ); await this._mergeArray( notebooks, - id => this._db.notebooks.notebook(id), - item => this._db.notebooks.add(item) + (id) => this._db.notebooks.notebook(id), + (item) => this._db.notebooks.add(item) ); await this._mergeArrayWithConflicts( delta, - id => this._db.delta.raw(id), - item => this._db.delta.add(item), + (id) => this._db.delta.raw(id), + (item) => this._db.delta.add(item), async (local, remote) => { await this._db.delta.add({ ...local, conflicted: remote }); await this._db.notes.add({ id: local.noteId, conflicted: true }); @@ -181,26 +182,26 @@ class Merger { await this._mergeArray( text, - id => this._db.text.raw(id), - item => this._db.text.add(item) + (id) => this._db.text.raw(id), + (item) => this._db.text.add(item) ); await this._mergeArray( tags, - id => this._db.tags.tag(id), - item => this._db.tags.merge(item) + (id) => this._db.tags.tag(id), + (item) => this._db.tags.merge(item) ); await this._mergeArray( colors, - id => this._db.colors.tag(id), - item => this._db.colors.merge(item) + (id) => this._db.colors.tag(id), + (item) => this._db.colors.merge(item) ); await this._mergeArray( trash, () => undefined, - item => this._db.trash.add(item) + (item) => this._db.trash.add(item) ); return true; @@ -229,21 +230,21 @@ class Prepare { tags: this._prepareForServer(this._db.tags.raw), colors: this._prepareForServer(this._db.colors.raw), trash: this._prepareForServer(this._db.trash.raw), - lastSynced: Date.now() + lastSynced: Date.now(), }; } _prepareForServer(array) { return tfun - .filter(item => item.dateEdited > this._lastSyncedTimestamp) - .map(item => ({ + .filter((item) => item.dateEdited > this._lastSyncedTimestamp) + .map((item) => ({ id: item.id, - data: JSON.stringify(item) + data: JSON.stringify(item), }))(array); } } function areAllEmpty(obj) { - const arrays = Object.values(obj).filter(v => v.length !== undefined); - return arrays.every(array => array.length === 0); + const arrays = Object.values(obj).filter((v) => v.length !== undefined); + return arrays.every((array) => array.length === 0); } diff --git a/packages/core/collections/notes.js b/packages/core/collections/notes.js index c470bb10e..7c50a0f17 100644 --- a/packages/core/collections/notes.js +++ b/packages/core/collections/notes.js @@ -6,7 +6,7 @@ import { getWeekGroupFromTimestamp, months, getLastWeekTimestamp, - get7DayTimestamp + get7DayTimestamp, } from "../utils/date"; import Notebooks from "./notebooks"; import Note from "../models/note"; @@ -62,7 +62,7 @@ export default class Notes { let note = { ...oldNote, - ...noteArg + ...noteArg, }; if (isNoteEmpty(note)) { @@ -79,7 +79,7 @@ export default class Notes { deltaId = await this._deltaCollection.add({ noteId: id, id: deltaId, - data: delta + data: delta, }); } @@ -87,7 +87,7 @@ export default class Notes { textId = await this._textCollection.add({ noteId: id, id: textId, - data: text + data: text, }); note.title = getNoteTitle(note); note.headline = getNoteHeadline(note); @@ -106,7 +106,7 @@ export default class Notes { favorite: !!note.favorite, headline: note.headline, dateCreated: note.dateCreated, - conflicted: !!note.conflicted + conflicted: !!note.conflicted, }; if (!oldNote) { @@ -147,6 +147,10 @@ export default class Notes { return tfun.filter(".pinned === true")(this.all); } + get conflicted() { + return tfun.filter(".conflicted === true")(this.all); + } + get favorites() { return tfun.filter(".favorite === true")(this.all); } @@ -154,49 +158,49 @@ export default class Notes { tagged(tag) { return this._tagsCollection .notes(tag) - .map(id => this._collection.getItem(id)); + .map((id) => this._collection.getItem(id)); } colored(color) { return this._colorsCollection .notes(color) - .map(id => this._collection.getItem(id)); + .map((id) => this._collection.getItem(id)); } group(by, special = false) { let notes = !special ? tfun.filter(".pinned === false")(this.all) : this.all; - notes = sort(notes).desc(t => t.dateCreated); + notes = sort(notes).desc((t) => t.dateCreated); switch (by) { case "abc": - return groupBy(notes, note => note.title[0].toUpperCase(), special); + return groupBy(notes, (note) => note.title[0].toUpperCase(), special); case "month": return groupBy( notes, - note => months[new Date(note.dateCreated).getMonth()], + (note) => months[new Date(note.dateCreated).getMonth()], special ); case "week": return groupBy( notes, - note => getWeekGroupFromTimestamp(note.dateCreated), + (note) => getWeekGroupFromTimestamp(note.dateCreated), special ); case "year": return groupBy( notes, - note => new Date(note.dateCreated).getFullYear().toString(), + (note) => new Date(note.dateCreated).getFullYear().toString(), special ); default: let timestamps = { recent: getLastWeekTimestamp(7), - lastWeek: getLastWeekTimestamp(7) - get7DayTimestamp() //seven day timestamp value + lastWeek: getLastWeekTimestamp(7) - get7DayTimestamp(), //seven day timestamp value }; return groupBy( notes, - note => + (note) => note.dateCreated >= timestamps.recent ? "Recent" : note.dateCreated >= timestamps.lastWeek @@ -247,7 +251,7 @@ function isNoteEmpty(note) { const { title, content: { delta, text }, - locked + locked, } = note; const isTitleEmpty = !title || !title.trim().length; const isTextEmpty = !isHex(text) && (!text || !text.trim().length); @@ -265,9 +269,5 @@ function getNoteHeadline(note) { function getNoteTitle(note) { if (note.title && note.title.length > 0) return note.title.trim(); - return note.content.text - .split(" ") - .slice(0, 3) - .join(" ") - .trim(); + return note.content.text.split(" ").slice(0, 3).join(" ").trim(); }