core: migrate settings to its own collection

This commit is contained in:
Abdullah Atta
2023-09-16 14:35:09 +05:00
parent 79b75c5ba7
commit 588e37aa20
11 changed files with 350 additions and 293 deletions

View File

@@ -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: {

View File

@@ -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();
} // }
}); // });
}); // });
}); // });

View File

@@ -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 = {

View File

@@ -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();

View File

@@ -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,

View File

@@ -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;

View File

@@ -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 = {

View 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];
}
}

View File

@@ -17,192 +17,156 @@ 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 = { groupBy: "default",
type: "settings", sortBy:
dateModified: 0, key === "trash"
dateCreated: 0, ? "dateDeleted"
id: getId() : key === "tags"
}; ? "dateCreated"
constructor(private readonly db: Database) {} : key === "reminders"
? "dueDate"
: "dateEdited",
sortDirection: key === "reminders" ? "asc" : "desc"
} satisfies GroupOptions);
async init() { const defaultSettings: SettingItemMap = {
const settings = await this.db.storage().read<SettingsItem>("settings"); timeFormat: "12-hour",
this.reset(settings); dateFormat: "DD-MM-YYYY",
await this.save(false); titleFormat: "Note $date$ $time$",
defaultNotebook: undefined,
trashCleanupInterval: 7,
EV.subscribe(EVENTS.userLoggedOut, async () => { "groupOptions:trash": DEFAULT_GROUP_OPTIONS("trash"),
this.reset(); "groupOptions:tags": DEFAULT_GROUP_OPTIONS("tags"),
await this.save(false); "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
);
}
init() {
return this.collection.init();
} }
get raw() { get raw() {
return this.settings; return this.collection.raw();
} }
async merge(remoteItem: SettingsItem, lastSynced: number) { private async set<TKey extends keyof SettingItemMap>(
if (this.settings.dateModified > lastSynced) { key: TKey,
this.settings = { value: SettingItemMap[TKey]
...this.settings, ) {
...(isDeleted(remoteItem) const id = makeId(key);
? {} const oldItem = this.collection.get(id);
: { if (oldItem && oldItem.key !== key) throw new Error("Key conflict.");
...remoteItem,
groupOptions: { await this.collection.add({
...this.settings.groupOptions, id,
...remoteItem.groupOptions key,
}, value,
toolbarConfig: { type: "settingitem",
...this.settings.toolbarConfig, dateCreated: oldItem?.dateCreated || Date.now(),
...remoteItem.toolbarConfig dateModified: oldItem?.dateCreated || Date.now()
}, });
aliases: { return id;
...this.settings.aliases,
...remoteItem.aliases
}
})
};
this.settings.dateModified = Date.now();
} else {
this.reset(remoteItem);
}
await this.save(false);
} }
async setGroupOptions(key: GroupingKey, groupOptions: GroupOptions) { private get<TKey extends keyof SettingItemMap>(
if (!this.settings.groupOptions) this.settings.groupOptions = {}; key: TKey
this.settings.groupOptions[key] = groupOptions; ): SettingItemMap[TKey] {
await this.save(); 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) { getGroupOptions(key: GroupingKey) {
return ( return this.get(`groupOptions:${key}`);
(this.settings.groupOptions && this.settings.groupOptions[key]) || {
groupBy: "default",
sortBy:
key === "trash"
? "dateDeleted"
: key === "tags"
? "dateCreated"
: key === "reminders"
? "dueDate"
: "dateEdited",
sortDirection: key === "reminders" ? "asc" : "desc"
}
);
} }
async setToolbarConfig(key: string, config: ToolbarConfig) { setGroupOptions(key: GroupingKey, groupOptions: GroupOptions) {
if (!this.settings.toolbarConfig) this.settings.toolbarConfig = {}; return this.set(`groupOptions:${key}`, groupOptions);
this.settings.toolbarConfig[key] = config;
await this.save();
} }
getToolbarConfig(key: string) { setToolbarConfig(platform: ToolbarConfigPlatforms, config: ToolbarConfig) {
return this.settings.toolbarConfig && this.settings.toolbarConfig[key]; return this.set(`toolbarConfig:${platform}`, config);
} }
/** getToolbarConfig(platform: ToolbarConfigPlatforms) {
* Setting to -1 means never clear trash. return this.get(`toolbarConfig:${platform}`);
*/ }
async setTrashCleanupInterval(interval: TrashCleanupInterval) {
this.settings.trashCleanupInterval = interval; setTrashCleanupInterval(interval: TrashCleanupInterval) {
await this.save(); 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;

View File

@@ -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;
} }
} }
}, },

View File

@@ -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;