mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 11:47:54 +01:00
core: migrate settings to its own collection
This commit is contained in:
@@ -78,7 +78,13 @@ module.exports = {
|
|||||||
semi: ["error", "always"],
|
semi: ["error", "always"],
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
"react/prop-types": "off",
|
"react/prop-types": "off",
|
||||||
"header/header": ["error", "block", LICENSE, 1]
|
"header/header": ["error", "block", LICENSE, 1],
|
||||||
|
"@typescript-eslint/no-empty-interface": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
allowSingleExtends: true
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { TEST_NOTE, databaseTest, loginFakeUser, notebookTest } from "./utils";
|
import { TEST_NOTE, loginFakeUser, notebookTest } from "./utils";
|
||||||
import v52Backup from "./__fixtures__/backup.v5.2.json";
|
// import v52Backup from "./__fixtures__/backup.v5.2.json";
|
||||||
import v52BackupCopy from "./__fixtures__/backup.v5.2.copy.json";
|
// import v52BackupCopy from "./__fixtures__/backup.v5.2.copy.json";
|
||||||
import v56BackupCopy from "./__fixtures__/backup.v5.6.json";
|
// import v56BackupCopy from "./__fixtures__/backup.v5.6.json";
|
||||||
import v58BackupCopy from "./__fixtures__/backup.v5.8.json";
|
// import v58BackupCopy from "./__fixtures__/backup.v5.8.json";
|
||||||
import qclone from "qclone";
|
// import qclone from "qclone";
|
||||||
import { test, expect, describe } from "vitest";
|
import { test, expect } from "vitest";
|
||||||
import { getId, makeId } from "../src/utils/id";
|
// import { getId, makeId } from "../src/utils/id";
|
||||||
|
|
||||||
test("export backup", () =>
|
test("export backup", () =>
|
||||||
notebookTest().then(async ({ db }) => {
|
notebookTest().then(async ({ db }) => {
|
||||||
@@ -114,103 +114,103 @@ test("import tempered backup", () =>
|
|||||||
await expect(db.backup.import(backup)).rejects.toThrow(/tempered/);
|
await expect(db.backup.import(backup)).rejects.toThrow(/tempered/);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe.each([
|
// describe.each([
|
||||||
["v5.2", v52Backup],
|
// ["v5.2", v52Backup],
|
||||||
["v5.2 copy", v52BackupCopy],
|
// ["v5.2 copy", v52BackupCopy],
|
||||||
["v5.6", v56BackupCopy],
|
// ["v5.6", v56BackupCopy],
|
||||||
["v5.8", v58BackupCopy]
|
// ["v5.8", v58BackupCopy]
|
||||||
])("testing backup version: %s", (version, data) => {
|
// ])("testing backup version: %s", (version, data) => {
|
||||||
test(`import ${version} backup`, () => {
|
// test(`import ${version} backup`, () => {
|
||||||
return databaseTest().then(async (db) => {
|
// return databaseTest().then(async (db) => {
|
||||||
await db.backup.import(qclone(data));
|
// await db.backup.import(qclone(data));
|
||||||
|
|
||||||
expect(db.settings.raw.id).toBeDefined();
|
// expect(db.settings.raw.id).toBeDefined();
|
||||||
expect(db.settings.raw.dateModified).toBeDefined();
|
// expect(db.settings.raw.dateModified).toBeDefined();
|
||||||
expect(db.settings.raw.dateEdited).toBeUndefined();
|
// expect(db.settings.raw.dateEdited).toBeUndefined();
|
||||||
expect(db.settings.raw.pins).toBeUndefined();
|
// expect(db.settings.raw.pins).toBeUndefined();
|
||||||
|
|
||||||
expect(
|
// expect(
|
||||||
db.notes.all.every((v) => {
|
// db.notes.all.every((v) => {
|
||||||
const doesNotHaveContent = !v.content;
|
// const doesNotHaveContent = !v.content;
|
||||||
const doesNotHaveColors = !v.colors; // && (!v.color || v.color.length);
|
// const doesNotHaveColors = !v.colors; // && (!v.color || v.color.length);
|
||||||
const hasTopicsInAllNotebooks =
|
// const hasTopicsInAllNotebooks =
|
||||||
!v.notebooks ||
|
// !v.notebooks ||
|
||||||
v.notebooks.every((nb) => !!nb.id && !!nb.topics && !nb.topic);
|
// v.notebooks.every((nb) => !!nb.id && !!nb.topics && !nb.topic);
|
||||||
const hasDateModified = v.dateModified > 0;
|
// const hasDateModified = v.dateModified > 0;
|
||||||
const doesNotHaveTags = !v.tags;
|
// const doesNotHaveTags = !v.tags;
|
||||||
const doesNotHaveColor = !v.color;
|
// const doesNotHaveColor = !v.color;
|
||||||
if (!doesNotHaveTags) console.log(v);
|
// if (!doesNotHaveTags) console.log(v);
|
||||||
return (
|
// return (
|
||||||
doesNotHaveTags &&
|
// doesNotHaveTags &&
|
||||||
doesNotHaveColor &&
|
// doesNotHaveColor &&
|
||||||
doesNotHaveContent &&
|
// doesNotHaveContent &&
|
||||||
!v.notebook &&
|
// !v.notebook &&
|
||||||
hasTopicsInAllNotebooks &&
|
// hasTopicsInAllNotebooks &&
|
||||||
doesNotHaveColors &&
|
// doesNotHaveColors &&
|
||||||
hasDateModified
|
// hasDateModified
|
||||||
);
|
// );
|
||||||
})
|
// })
|
||||||
).toBeTruthy();
|
// ).toBeTruthy();
|
||||||
|
|
||||||
expect(
|
// expect(
|
||||||
db.tags.all.every((t) => makeId(t.title) !== t.id && !t.noteIds)
|
// db.tags.all.every((t) => makeId(t.title) !== t.id && !t.noteIds)
|
||||||
).toBeTruthy();
|
// ).toBeTruthy();
|
||||||
|
|
||||||
expect(
|
// expect(
|
||||||
db.colors.all.every(
|
// db.colors.all.every(
|
||||||
(t) => makeId(t.title) !== t.id && !t.noteIds && !!t.colorCode
|
// (t) => makeId(t.title) !== t.id && !t.noteIds && !!t.colorCode
|
||||||
)
|
// )
|
||||||
).toBeTruthy();
|
// ).toBeTruthy();
|
||||||
|
|
||||||
expect(
|
// expect(
|
||||||
db.notebooks.all.every((v) => v.title != null && v.dateModified > 0)
|
// db.notebooks.all.every((v) => v.title != null && v.dateModified > 0)
|
||||||
).toBeTruthy();
|
// ).toBeTruthy();
|
||||||
|
|
||||||
expect(db.notebooks.all.every((v) => !v.topics)).toBeTruthy();
|
// expect(db.notebooks.all.every((v) => !v.topics)).toBeTruthy();
|
||||||
|
|
||||||
expect(
|
// expect(
|
||||||
db.attachments.all.every((v) => v.dateModified > 0 && !v.dateEdited)
|
// db.attachments.all.every((v) => v.dateModified > 0 && !v.dateEdited)
|
||||||
).toBeTruthy();
|
// ).toBeTruthy();
|
||||||
|
|
||||||
expect(db.attachments.all.every((a) => !a.noteIds)).toBeTruthy();
|
// expect(db.attachments.all.every((a) => !a.noteIds)).toBeTruthy();
|
||||||
|
|
||||||
if (data.data.settings.pins)
|
// if (data.data.settings.pins)
|
||||||
expect(db.shortcuts.all).toHaveLength(data.data.settings.pins.length);
|
// expect(db.shortcuts.all).toHaveLength(data.data.settings.pins.length);
|
||||||
|
|
||||||
const allContent = await db.content.all();
|
// const allContent = await db.content.all();
|
||||||
expect(
|
// expect(
|
||||||
allContent.every((v) => v.type === "tiptap" || v.deleted)
|
// allContent.every((v) => v.type === "tiptap" || v.deleted)
|
||||||
).toBeTruthy();
|
// ).toBeTruthy();
|
||||||
expect(allContent.every((v) => !v.persistDateEdited)).toBeTruthy();
|
// expect(allContent.every((v) => !v.persistDateEdited)).toBeTruthy();
|
||||||
expect(allContent.every((v) => v.dateModified > 0)).toBeTruthy();
|
// expect(allContent.every((v) => v.dateModified > 0)).toBeTruthy();
|
||||||
|
|
||||||
expect(
|
// expect(
|
||||||
allContent.every(
|
// allContent.every(
|
||||||
(v) =>
|
// (v) =>
|
||||||
!v.data.includes("tox-checklist") &&
|
// !v.data.includes("tox-checklist") &&
|
||||||
!v.data.includes("tox-checklist--checked")
|
// !v.data.includes("tox-checklist--checked")
|
||||||
)
|
// )
|
||||||
).toBeTruthy();
|
// ).toBeTruthy();
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
test(`verify indices of ${version} backup`, () => {
|
// test(`verify indices of ${version} backup`, () => {
|
||||||
return databaseTest().then(async (db) => {
|
// return databaseTest().then(async (db) => {
|
||||||
await db.backup.import(qclone(data));
|
// await db.backup.import(qclone(data));
|
||||||
|
|
||||||
const keys = await db.storage().getAllKeys();
|
// const keys = await db.storage().getAllKeys();
|
||||||
for (let key in data.data) {
|
// for (let key in data.data) {
|
||||||
const item = data.data[key];
|
// const item = data.data[key];
|
||||||
if (item && !item.type && item.deleted) continue;
|
// if (item && !item.type && item.deleted) continue;
|
||||||
if (
|
// if (
|
||||||
key.startsWith("_uk_") ||
|
// key.startsWith("_uk_") ||
|
||||||
key === "hasConflicts" ||
|
// key === "hasConflicts" ||
|
||||||
key === "monographs" ||
|
// key === "monographs" ||
|
||||||
key === "token"
|
// key === "token"
|
||||||
)
|
// )
|
||||||
continue;
|
// continue;
|
||||||
expect(keys.some((k) => k.startsWith(key))).toBeTruthy();
|
// expect(keys.some((k) => k.startsWith(key))).toBeTruthy();
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|||||||
@@ -20,23 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import { databaseTest } from "./utils";
|
import { databaseTest } from "./utils";
|
||||||
import { test, expect } from "vitest";
|
import { test, expect } from "vitest";
|
||||||
|
|
||||||
test("settings' dateModified should not update on init", () =>
|
|
||||||
databaseTest().then(async (db) => {
|
|
||||||
const beforeDateModified = db.settings.raw.dateModified;
|
|
||||||
await db.settings.init();
|
|
||||||
const afterDateModified = db.settings.raw.dateModified;
|
|
||||||
expect(beforeDateModified).toBe(afterDateModified);
|
|
||||||
}));
|
|
||||||
|
|
||||||
test("settings' dateModified should update after merge conflict resolve", () =>
|
|
||||||
databaseTest().then(async (db) => {
|
|
||||||
// await db.storage.write("lastSynced", 0);
|
|
||||||
const beforeDateModified = (db.settings.raw.dateModified = 1);
|
|
||||||
await db.settings.merge({ groupOptions: {}, aliases: {} }, 0);
|
|
||||||
const afterDateModified = db.settings.raw.dateModified;
|
|
||||||
expect(afterDateModified).toBeGreaterThan(beforeDateModified);
|
|
||||||
}));
|
|
||||||
|
|
||||||
test("save group options", () =>
|
test("save group options", () =>
|
||||||
databaseTest().then(async (db) => {
|
databaseTest().then(async (db) => {
|
||||||
const groupOptions = {
|
const groupOptions = {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import Backup from "../database/backup";
|
|||||||
import Session from "./session";
|
import Session from "./session";
|
||||||
import Hosts from "../utils/constants";
|
import Hosts from "../utils/constants";
|
||||||
import { EV, EVENTS } from "../common";
|
import { EV, EVENTS } from "../common";
|
||||||
import Settings from "../collections/settings";
|
import { LegacySettings } from "../collections/legacy-settings";
|
||||||
import Migrations from "./migrations";
|
import Migrations from "./migrations";
|
||||||
import UserManager from "./user-manager";
|
import UserManager from "./user-manager";
|
||||||
import http from "../utils/http";
|
import http from "../utils/http";
|
||||||
@@ -59,6 +59,7 @@ import {
|
|||||||
} from "../interfaces";
|
} from "../interfaces";
|
||||||
import TokenManager from "./token-manager";
|
import TokenManager from "./token-manager";
|
||||||
import { Attachment } from "../types";
|
import { Attachment } from "../types";
|
||||||
|
import { Settings } from "../collections/settings";
|
||||||
|
|
||||||
type EventSourceConstructor = new (
|
type EventSourceConstructor = new (
|
||||||
uri: string,
|
uri: string,
|
||||||
@@ -127,6 +128,7 @@ class Database {
|
|||||||
vault = new Vault(this);
|
vault = new Vault(this);
|
||||||
lookup = new Lookup(this);
|
lookup = new Lookup(this);
|
||||||
backup = new Backup(this);
|
backup = new Backup(this);
|
||||||
|
legacySettings = new LegacySettings(this);
|
||||||
settings = new Settings(this);
|
settings = new Settings(this);
|
||||||
migrations = new Migrations(this);
|
migrations = new Migrations(this);
|
||||||
monographs = new Monographs(this);
|
monographs = new Monographs(this);
|
||||||
@@ -189,9 +191,10 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async initCollections() {
|
async initCollections() {
|
||||||
await this.settings.init();
|
await this.legacySettings.init();
|
||||||
// collections
|
// collections
|
||||||
|
|
||||||
|
await this.settings.init();
|
||||||
await this.notebooks.init();
|
await this.notebooks.init();
|
||||||
await this.tags.init();
|
await this.tags.init();
|
||||||
await this.colors.init();
|
await this.colors.init();
|
||||||
|
|||||||
@@ -42,14 +42,6 @@ class Collector {
|
|||||||
throw new Error("User encryption key not generated. Please relogin.");
|
throw new Error("User encryption key not generated. Please relogin.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = await this.prepareChunk(
|
|
||||||
[this.db.settings.raw],
|
|
||||||
lastSyncedTimestamp,
|
|
||||||
isForceSync,
|
|
||||||
key
|
|
||||||
);
|
|
||||||
if (settings) yield { items: settings, type: "settings" };
|
|
||||||
|
|
||||||
const attachments = await this.prepareChunk(
|
const attachments = await this.prepareChunk(
|
||||||
this.db.attachments.syncable,
|
this.db.attachments.syncable,
|
||||||
lastSyncedTimestamp,
|
lastSyncedTimestamp,
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import {
|
|||||||
MaybeDeletedItem,
|
MaybeDeletedItem,
|
||||||
Note,
|
Note,
|
||||||
Notebook,
|
Notebook,
|
||||||
SettingsItem,
|
|
||||||
TrashOrItem,
|
TrashOrItem,
|
||||||
isDeleted
|
isDeleted
|
||||||
} from "../../types";
|
} from "../../types";
|
||||||
@@ -94,7 +93,8 @@ class Merger {
|
|||||||
case "color":
|
case "color":
|
||||||
case "note":
|
case "note":
|
||||||
case "relation":
|
case "relation":
|
||||||
case "notebook": {
|
case "notebook":
|
||||||
|
case "settingitem": {
|
||||||
const localItem = this.db[SYNC_COLLECTIONS_MAP[type]].collection.getRaw(
|
const localItem = this.db[SYNC_COLLECTIONS_MAP[type]].collection.getRaw(
|
||||||
remoteItem.id
|
remoteItem.id
|
||||||
);
|
);
|
||||||
@@ -158,23 +158,11 @@ class Merger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async mergeItem(
|
async mergeItem(
|
||||||
remoteItem: SettingsItem | MaybeDeletedItem<Attachment>,
|
remoteItem: MaybeDeletedItem<Attachment>,
|
||||||
type: "settings" | "attachment",
|
type: "settings" | "attachment",
|
||||||
lastSynced: number
|
_lastSynced: number
|
||||||
) {
|
) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "settings": {
|
|
||||||
if (isDeleted(remoteItem) || remoteItem.type !== "settings") return;
|
|
||||||
|
|
||||||
const localItem = this.db.settings.raw;
|
|
||||||
if (
|
|
||||||
!localItem ||
|
|
||||||
this.isConflicted(localItem, remoteItem, lastSynced, 1000)
|
|
||||||
) {
|
|
||||||
await this.db.settings.merge(remoteItem, lastSynced);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "attachment": {
|
case "attachment": {
|
||||||
if (isDeleted(remoteItem)) return remoteItem;
|
if (isDeleted(remoteItem)) return remoteItem;
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ export type SyncableItemType =
|
|||||||
| "relation"
|
| "relation"
|
||||||
| "color"
|
| "color"
|
||||||
| "tag"
|
| "tag"
|
||||||
| "settings";
|
| "settings"
|
||||||
|
| "settingitem";
|
||||||
|
|
||||||
export type SyncItem = {
|
export type SyncItem = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -43,7 +44,8 @@ export const SYNC_COLLECTIONS_MAP = {
|
|||||||
reminder: "reminders",
|
reminder: "reminders",
|
||||||
relation: "relations",
|
relation: "relations",
|
||||||
tag: "tags",
|
tag: "tags",
|
||||||
color: "colors"
|
color: "colors",
|
||||||
|
settingitem: "settings"
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type SyncTransferItem = {
|
export type SyncTransferItem = {
|
||||||
|
|||||||
55
packages/core/src/collections/legacy-settings.ts
Normal file
55
packages/core/src/collections/legacy-settings.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the Notesnook project (https://notesnook.com/)
|
||||||
|
|
||||||
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getId } from "../utils/id";
|
||||||
|
import Database from "../api";
|
||||||
|
import { LegacySettingsItem } from "../types";
|
||||||
|
import { ICollection } from "./collection";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated only kept here for migration purposes
|
||||||
|
*/
|
||||||
|
export class LegacySettings implements ICollection {
|
||||||
|
name = "legacy-settings";
|
||||||
|
private settings: LegacySettingsItem = {
|
||||||
|
type: "settings",
|
||||||
|
dateModified: 0,
|
||||||
|
dateCreated: 0,
|
||||||
|
id: getId()
|
||||||
|
};
|
||||||
|
constructor(private readonly db: Database) {}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
const settings = await this.db
|
||||||
|
.storage()
|
||||||
|
.read<LegacySettingsItem>("settings");
|
||||||
|
if (settings) this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
get raw() {
|
||||||
|
return this.settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated only kept here for migration purposes
|
||||||
|
*/
|
||||||
|
getAlias(id: string) {
|
||||||
|
return this.settings.aliases && this.settings.aliases[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,84 +17,24 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EV, EVENTS } from "../common";
|
import { makeId } from "../utils/id";
|
||||||
import { getId } from "../utils/id";
|
|
||||||
import Database from "../api";
|
import Database from "../api";
|
||||||
import {
|
import {
|
||||||
DefaultNotebook,
|
DefaultNotebook,
|
||||||
GroupOptions,
|
GroupOptions,
|
||||||
GroupingKey,
|
GroupingKey,
|
||||||
SettingsItem,
|
SettingItem,
|
||||||
|
SettingItemMap,
|
||||||
ToolbarConfig,
|
ToolbarConfig,
|
||||||
TrashCleanupInterval,
|
ToolbarConfigPlatforms,
|
||||||
isDeleted
|
TrashCleanupInterval
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import { ICollection } from "./collection";
|
import { ICollection } from "./collection";
|
||||||
|
import { CachedCollection } from "../database/cached-collection";
|
||||||
import { TimeFormat } from "../utils/date";
|
import { TimeFormat } from "../utils/date";
|
||||||
|
|
||||||
class Settings implements ICollection {
|
const DEFAULT_GROUP_OPTIONS = (key: GroupingKey) =>
|
||||||
name = "settings";
|
({
|
||||||
private settings: SettingsItem = {
|
|
||||||
type: "settings",
|
|
||||||
dateModified: 0,
|
|
||||||
dateCreated: 0,
|
|
||||||
id: getId()
|
|
||||||
};
|
|
||||||
constructor(private readonly db: Database) {}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
const settings = await this.db.storage().read<SettingsItem>("settings");
|
|
||||||
this.reset(settings);
|
|
||||||
await this.save(false);
|
|
||||||
|
|
||||||
EV.subscribe(EVENTS.userLoggedOut, async () => {
|
|
||||||
this.reset();
|
|
||||||
await this.save(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get raw() {
|
|
||||||
return this.settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
async merge(remoteItem: SettingsItem, lastSynced: number) {
|
|
||||||
if (this.settings.dateModified > lastSynced) {
|
|
||||||
this.settings = {
|
|
||||||
...this.settings,
|
|
||||||
...(isDeleted(remoteItem)
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
...remoteItem,
|
|
||||||
groupOptions: {
|
|
||||||
...this.settings.groupOptions,
|
|
||||||
...remoteItem.groupOptions
|
|
||||||
},
|
|
||||||
toolbarConfig: {
|
|
||||||
...this.settings.toolbarConfig,
|
|
||||||
...remoteItem.toolbarConfig
|
|
||||||
},
|
|
||||||
aliases: {
|
|
||||||
...this.settings.aliases,
|
|
||||||
...remoteItem.aliases
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
this.settings.dateModified = Date.now();
|
|
||||||
} else {
|
|
||||||
this.reset(remoteItem);
|
|
||||||
}
|
|
||||||
await this.save(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setGroupOptions(key: GroupingKey, groupOptions: GroupOptions) {
|
|
||||||
if (!this.settings.groupOptions) this.settings.groupOptions = {};
|
|
||||||
this.settings.groupOptions[key] = groupOptions;
|
|
||||||
await this.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
getGroupOptions(key: GroupingKey) {
|
|
||||||
return (
|
|
||||||
(this.settings.groupOptions && this.settings.groupOptions[key]) || {
|
|
||||||
groupBy: "default",
|
groupBy: "default",
|
||||||
sortBy:
|
sortBy:
|
||||||
key === "trash"
|
key === "trash"
|
||||||
@@ -105,104 +45,128 @@ class Settings implements ICollection {
|
|||||||
? "dueDate"
|
? "dueDate"
|
||||||
: "dateEdited",
|
: "dateEdited",
|
||||||
sortDirection: key === "reminders" ? "asc" : "desc"
|
sortDirection: key === "reminders" ? "asc" : "desc"
|
||||||
}
|
} satisfies GroupOptions);
|
||||||
|
|
||||||
|
const defaultSettings: SettingItemMap = {
|
||||||
|
timeFormat: "12-hour",
|
||||||
|
dateFormat: "DD-MM-YYYY",
|
||||||
|
titleFormat: "Note $date$ $time$",
|
||||||
|
defaultNotebook: undefined,
|
||||||
|
trashCleanupInterval: 7,
|
||||||
|
|
||||||
|
"groupOptions:trash": DEFAULT_GROUP_OPTIONS("trash"),
|
||||||
|
"groupOptions:tags": DEFAULT_GROUP_OPTIONS("tags"),
|
||||||
|
"groupOptions:notes": DEFAULT_GROUP_OPTIONS("notes"),
|
||||||
|
"groupOptions:notebooks": DEFAULT_GROUP_OPTIONS("notebooks"),
|
||||||
|
"groupOptions:favorites": DEFAULT_GROUP_OPTIONS("favorites"),
|
||||||
|
"groupOptions:home": DEFAULT_GROUP_OPTIONS("home"),
|
||||||
|
"groupOptions:reminders": DEFAULT_GROUP_OPTIONS("reminders"),
|
||||||
|
|
||||||
|
"toolbarConfig:desktop": undefined,
|
||||||
|
"toolbarConfig:mobile": undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Settings implements ICollection {
|
||||||
|
name = "settingsv2";
|
||||||
|
readonly collection: CachedCollection<"settingsv2", SettingItem>;
|
||||||
|
constructor(db: Database) {
|
||||||
|
this.collection = new CachedCollection(
|
||||||
|
db.storage,
|
||||||
|
"settingsv2",
|
||||||
|
db.eventManager
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setToolbarConfig(key: string, config: ToolbarConfig) {
|
init() {
|
||||||
if (!this.settings.toolbarConfig) this.settings.toolbarConfig = {};
|
return this.collection.init();
|
||||||
this.settings.toolbarConfig[key] = config;
|
|
||||||
await this.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getToolbarConfig(key: string) {
|
get raw() {
|
||||||
return this.settings.toolbarConfig && this.settings.toolbarConfig[key];
|
return this.collection.raw();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private async set<TKey extends keyof SettingItemMap>(
|
||||||
* Setting to -1 means never clear trash.
|
key: TKey,
|
||||||
*/
|
value: SettingItemMap[TKey]
|
||||||
async setTrashCleanupInterval(interval: TrashCleanupInterval) {
|
) {
|
||||||
this.settings.trashCleanupInterval = interval;
|
const id = makeId(key);
|
||||||
await this.save();
|
const oldItem = this.collection.get(id);
|
||||||
|
if (oldItem && oldItem.key !== key) throw new Error("Key conflict.");
|
||||||
|
|
||||||
|
await this.collection.add({
|
||||||
|
id,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
type: "settingitem",
|
||||||
|
dateCreated: oldItem?.dateCreated || Date.now(),
|
||||||
|
dateModified: oldItem?.dateCreated || Date.now()
|
||||||
|
});
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get<TKey extends keyof SettingItemMap>(
|
||||||
|
key: TKey
|
||||||
|
): SettingItemMap[TKey] {
|
||||||
|
const item = this.collection.get(makeId(key)) as
|
||||||
|
| SettingItem<TKey>
|
||||||
|
| undefined;
|
||||||
|
if (!item || item.key !== key) return defaultSettings[key];
|
||||||
|
return item.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
getGroupOptions(key: GroupingKey) {
|
||||||
|
return this.get(`groupOptions:${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setGroupOptions(key: GroupingKey, groupOptions: GroupOptions) {
|
||||||
|
return this.set(`groupOptions:${key}`, groupOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
setToolbarConfig(platform: ToolbarConfigPlatforms, config: ToolbarConfig) {
|
||||||
|
return this.set(`toolbarConfig:${platform}`, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
getToolbarConfig(platform: ToolbarConfigPlatforms) {
|
||||||
|
return this.get(`toolbarConfig:${platform}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTrashCleanupInterval(interval: TrashCleanupInterval) {
|
||||||
|
return this.set("trashCleanupInterval", interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTrashCleanupInterval() {
|
getTrashCleanupInterval() {
|
||||||
return this.settings.trashCleanupInterval || 7;
|
return this.get("trashCleanupInterval");
|
||||||
}
|
}
|
||||||
|
|
||||||
async setDefaultNotebook(item: DefaultNotebook | undefined) {
|
setDefaultNotebook(item: DefaultNotebook | undefined) {
|
||||||
this.settings.defaultNotebook = !item
|
return this.set("defaultNotebook", item);
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
id: item.id,
|
|
||||||
topic: item.topic
|
|
||||||
};
|
|
||||||
await this.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultNotebook() {
|
getDefaultNotebook() {
|
||||||
return this.settings.defaultNotebook;
|
return this.get("defaultNotebook");
|
||||||
}
|
}
|
||||||
|
|
||||||
async setTitleFormat(format: string) {
|
setTitleFormat(format: string) {
|
||||||
this.settings.titleFormat = format;
|
return this.set("titleFormat", format);
|
||||||
await this.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getTitleFormat() {
|
getTitleFormat() {
|
||||||
return this.settings.titleFormat || "Note $date$ $time$";
|
return this.get("titleFormat");
|
||||||
}
|
}
|
||||||
|
|
||||||
getDateFormat() {
|
getDateFormat() {
|
||||||
return this.settings.dateFormat || "DD-MM-YYYY";
|
return this.get("dateFormat");
|
||||||
}
|
}
|
||||||
|
|
||||||
async setDateFormat(format: string) {
|
setDateFormat(format: string) {
|
||||||
this.settings.dateFormat = format;
|
return this.set("dateFormat", format);
|
||||||
await this.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getTimeFormat() {
|
getTimeFormat() {
|
||||||
return this.settings.timeFormat || "12-hour";
|
return this.get("timeFormat");
|
||||||
}
|
}
|
||||||
|
|
||||||
async setTimeFormat(format: TimeFormat) {
|
setTimeFormat(format: TimeFormat) {
|
||||||
this.settings.timeFormat = format || "12-hour";
|
return this.set("timeFormat", format);
|
||||||
await this.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated only kept here for migration purposes.
|
|
||||||
*/
|
|
||||||
getAlias(id: string) {
|
|
||||||
return this.settings.aliases && this.settings.aliases[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
private reset(settings?: Partial<SettingsItem>) {
|
|
||||||
this.settings = {
|
|
||||||
type: "settings",
|
|
||||||
id: getId(),
|
|
||||||
dateModified: 0,
|
|
||||||
dateCreated: 0,
|
|
||||||
...(settings || {})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async save(updateDateModified = true) {
|
|
||||||
this.db.eventManager.publish(
|
|
||||||
EVENTS.databaseUpdated,
|
|
||||||
"settings",
|
|
||||||
this.settings
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updateDateModified) {
|
|
||||||
this.settings.dateModified = Date.now();
|
|
||||||
this.settings.synced = false;
|
|
||||||
}
|
|
||||||
delete this.settings.remote;
|
|
||||||
|
|
||||||
await this.db.storage().write("settings", this.settings);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default Settings;
|
|
||||||
|
|||||||
@@ -25,10 +25,12 @@ import { getId, makeId } from "./utils/id";
|
|||||||
import {
|
import {
|
||||||
Color,
|
Color,
|
||||||
ContentItem,
|
ContentItem,
|
||||||
|
GroupingKey,
|
||||||
HistorySession,
|
HistorySession,
|
||||||
Item,
|
Item,
|
||||||
ItemMap,
|
ItemMap,
|
||||||
ItemType
|
ItemType,
|
||||||
|
ToolbarConfigPlatforms
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { isCipher } from "./database/crypto";
|
import { isCipher } from "./database/crypto";
|
||||||
import { IndexedCollection } from "./database/indexed-collection";
|
import { IndexedCollection } from "./database/indexed-collection";
|
||||||
@@ -188,7 +190,7 @@ const migrations: Migration[] = [
|
|||||||
version: 5.9,
|
version: 5.9,
|
||||||
items: {
|
items: {
|
||||||
tag: async (item, db) => {
|
tag: async (item, db) => {
|
||||||
const alias = db.settings.getAlias(item.id);
|
const alias = db.legacySettings.getAlias(item.id);
|
||||||
item.title = alias || item.title;
|
item.title = alias || item.title;
|
||||||
item.id = getId(item.dateCreated);
|
item.id = getId(item.dateCreated);
|
||||||
|
|
||||||
@@ -207,7 +209,7 @@ const migrations: Migration[] = [
|
|||||||
for (const tag of item.tags || []) {
|
for (const tag of item.tags || []) {
|
||||||
const oldTagId = makeId(tag);
|
const oldTagId = makeId(tag);
|
||||||
const oldTag = db.tags.tag(oldTagId);
|
const oldTag = db.tags.tag(oldTagId);
|
||||||
const alias = db.settings.getAlias(oldTagId);
|
const alias = db.legacySettings.getAlias(oldTagId);
|
||||||
const newTag = db.tags.all.find(
|
const newTag = db.tags.all.find(
|
||||||
(t) => [alias, tag].includes(t.title) && t.id !== oldTagId
|
(t) => [alias, tag].includes(t.title) && t.id !== oldTagId
|
||||||
);
|
);
|
||||||
@@ -228,7 +230,7 @@ const migrations: Migration[] = [
|
|||||||
if (item.color) {
|
if (item.color) {
|
||||||
const oldColorId = makeId(item.color);
|
const oldColorId = makeId(item.color);
|
||||||
const oldColor = db.tags.tag(oldColorId);
|
const oldColor = db.tags.tag(oldColorId);
|
||||||
const alias = db.settings.getAlias(oldColorId);
|
const alias = db.legacySettings.getAlias(oldColorId);
|
||||||
const newColor = db.tags.all.find(
|
const newColor = db.tags.all.find(
|
||||||
(t) => [alias, item.color].includes(t.title) && t.id !== oldColorId
|
(t) => [alias, item.color].includes(t.title) && t.id !== oldColorId
|
||||||
);
|
);
|
||||||
@@ -289,6 +291,36 @@ const migrations: Migration[] = [
|
|||||||
item.item = { type: "notebook", id: item.item.id };
|
item.item = { type: "notebook", id: item.item.id };
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
settings: async (item, db) => {
|
||||||
|
if (item.trashCleanupInterval)
|
||||||
|
await db.settings.setTrashCleanupInterval(item.trashCleanupInterval);
|
||||||
|
if (item.defaultNotebook)
|
||||||
|
await db.settings.setDefaultNotebook(item.defaultNotebook);
|
||||||
|
|
||||||
|
if (item.titleFormat)
|
||||||
|
await db.settings.setTitleFormat(item.titleFormat);
|
||||||
|
if (item.dateFormat) await db.settings.setDateFormat(item.dateFormat);
|
||||||
|
if (item.timeFormat) await db.settings.setTimeFormat(item.timeFormat);
|
||||||
|
|
||||||
|
if (item.groupOptions) {
|
||||||
|
for (const key in item.groupOptions) {
|
||||||
|
const value = item.groupOptions[key as GroupingKey];
|
||||||
|
if (!value) continue;
|
||||||
|
await db.settings.setGroupOptions(key as GroupingKey, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.toolbarConfig) {
|
||||||
|
for (const key in item.toolbarConfig) {
|
||||||
|
const value = item.toolbarConfig[key as ToolbarConfigPlatforms];
|
||||||
|
if (!value) continue;
|
||||||
|
await db.settings.setToolbarConfig(
|
||||||
|
key as ToolbarConfigPlatforms,
|
||||||
|
value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export type GroupingKey =
|
|||||||
| "notes"
|
| "notes"
|
||||||
| "notebooks"
|
| "notebooks"
|
||||||
| "tags"
|
| "tags"
|
||||||
| "topics"
|
//| "topics"
|
||||||
| "trash"
|
| "trash"
|
||||||
| "favorites"
|
| "favorites"
|
||||||
| "reminders";
|
| "reminders";
|
||||||
@@ -49,7 +49,6 @@ export type GroupHeader = {
|
|||||||
export type Collections = {
|
export type Collections = {
|
||||||
notes: "note" | "trash";
|
notes: "note" | "trash";
|
||||||
notebooks: "notebook" | "trash";
|
notebooks: "notebook" | "trash";
|
||||||
topics: "topic";
|
|
||||||
attachments: "attachment";
|
attachments: "attachment";
|
||||||
reminders: "reminder";
|
reminders: "reminder";
|
||||||
relations: "relation";
|
relations: "relation";
|
||||||
@@ -59,17 +58,21 @@ export type Collections = {
|
|||||||
colors: "color";
|
colors: "color";
|
||||||
notehistory: "session";
|
notehistory: "session";
|
||||||
sessioncontent: "sessioncontent";
|
sessioncontent: "sessioncontent";
|
||||||
|
settingsv2: "settingitem";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated only kept here for migration purposes
|
||||||
|
*/
|
||||||
settings: "settings";
|
settings: "settings";
|
||||||
|
/**
|
||||||
|
* @deprecated only kept here for migration purposes
|
||||||
|
*/
|
||||||
|
topics: "topic";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CollectionType = keyof Collections;
|
export type CollectionType = keyof Collections;
|
||||||
|
|
||||||
export type ItemType =
|
export type ItemType = ValueOf<Collections>;
|
||||||
| ValueOf<Collections>
|
|
||||||
// TODO: ideally there should be no extra types here.
|
|
||||||
// everything should have its own collection
|
|
||||||
| "topic"
|
|
||||||
| "settings";
|
|
||||||
|
|
||||||
export type Item = ValueOf<ItemMap>;
|
export type Item = ValueOf<ItemMap>;
|
||||||
export type GroupableItem = ValueOf<
|
export type GroupableItem = ValueOf<
|
||||||
@@ -83,13 +86,13 @@ export type GroupableItem = ValueOf<
|
|||||||
| "session"
|
| "session"
|
||||||
| "sessioncontent"
|
| "sessioncontent"
|
||||||
| "settings"
|
| "settings"
|
||||||
|
| "settingsv2"
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type ItemMap = {
|
export type ItemMap = {
|
||||||
note: Note;
|
note: Note;
|
||||||
notebook: Notebook;
|
notebook: Notebook;
|
||||||
topic: Topic;
|
|
||||||
attachment: Attachment;
|
attachment: Attachment;
|
||||||
tag: Tag;
|
tag: Tag;
|
||||||
color: Color;
|
color: Color;
|
||||||
@@ -102,7 +105,16 @@ export type ItemMap = {
|
|||||||
content: ContentItem;
|
content: ContentItem;
|
||||||
session: HistorySession;
|
session: HistorySession;
|
||||||
sessioncontent: SessionContentItem;
|
sessioncontent: SessionContentItem;
|
||||||
settings: SettingsItem;
|
settingitem: SettingItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated only kept here for migration purposes
|
||||||
|
*/
|
||||||
|
topic: Topic;
|
||||||
|
/**
|
||||||
|
* @deprecated only kept here for migration purposes
|
||||||
|
*/
|
||||||
|
settings: LegacySettingsItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -307,9 +319,12 @@ export interface SessionContentItem extends BaseItem<"sessioncontent"> {
|
|||||||
export type TrashCleanupInterval = 1 | 7 | 30 | 365 | -1;
|
export type TrashCleanupInterval = 1 | 7 | 30 | 365 | -1;
|
||||||
export type ToolbarConfig = { preset: string; config?: any[] };
|
export type ToolbarConfig = { preset: string; config?: any[] };
|
||||||
export type DefaultNotebook = { id: string; topic?: string };
|
export type DefaultNotebook = { id: string; topic?: string };
|
||||||
export interface SettingsItem extends BaseItem<"settings"> {
|
/**
|
||||||
|
* @deprecated only kept here for migration purposes
|
||||||
|
*/
|
||||||
|
export interface LegacySettingsItem extends BaseItem<"settings"> {
|
||||||
groupOptions?: Partial<Record<GroupingKey, GroupOptions>>;
|
groupOptions?: Partial<Record<GroupingKey, GroupOptions>>;
|
||||||
toolbarConfig?: Record<string, ToolbarConfig>;
|
toolbarConfig?: Record<ToolbarConfigPlatforms, ToolbarConfig>;
|
||||||
trashCleanupInterval?: TrashCleanupInterval;
|
trashCleanupInterval?: TrashCleanupInterval;
|
||||||
titleFormat?: string;
|
titleFormat?: string;
|
||||||
timeFormat?: TimeFormat;
|
timeFormat?: TimeFormat;
|
||||||
@@ -329,6 +344,23 @@ export interface SettingsItem extends BaseItem<"settings"> {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ToolbarConfigPlatforms = "desktop" | "mobile";
|
||||||
|
export type SettingItemMap = {
|
||||||
|
trashCleanupInterval: TrashCleanupInterval;
|
||||||
|
titleFormat: string;
|
||||||
|
timeFormat: TimeFormat;
|
||||||
|
dateFormat: string;
|
||||||
|
defaultNotebook: DefaultNotebook | undefined;
|
||||||
|
} & Record<`groupOptions:${GroupingKey}`, GroupOptions> &
|
||||||
|
Record<`toolbarConfig:${ToolbarConfigPlatforms}`, ToolbarConfig | undefined>;
|
||||||
|
|
||||||
|
export interface SettingItem<
|
||||||
|
TKey extends keyof SettingItemMap = keyof SettingItemMap
|
||||||
|
> extends BaseItem<"settingitem"> {
|
||||||
|
key: TKey;
|
||||||
|
value: SettingItemMap[TKey];
|
||||||
|
}
|
||||||
|
|
||||||
export interface DeletedItem {
|
export interface DeletedItem {
|
||||||
id: string;
|
id: string;
|
||||||
deleted: true;
|
deleted: true;
|
||||||
|
|||||||
Reference in New Issue
Block a user