feat: migrate from delta to tiny

This commit is contained in:
thecodrr
2021-02-02 12:24:54 +05:00
parent 0d35f58c75
commit 4fa53ba9c0
17 changed files with 119 additions and 99 deletions

View File

@@ -100,6 +100,11 @@ describe.each([
).toBeTruthy(); ).toBeTruthy();
expect(db.notebooks.all.every((v) => v.title != null)).toBeTruthy(); expect(db.notebooks.all.every((v) => v.title != null)).toBeTruthy();
const allContent = await db.content.all();
expect(
allContent.every((v) => v.type === "tiny" || v.deleted)
).toBeTruthy();
}); });
}); });

View File

@@ -14,10 +14,12 @@ beforeEach(async () => {
StorageInterface.clear(); StorageInterface.clear();
}); });
const content = { ...TEST_NOTE.content, data: "<p>5</p>" };
//TODO //TODO
test("search notes", () => test("search notes", () =>
noteTest({ noteTest({
content: { type: "delta", data: [{ insert: "5" }] }, content: content,
}).then(async ({ db }) => { }).then(async ({ db }) => {
await db.notes.add(TEST_NOTE); await db.notes.add(TEST_NOTE);
let filtered = await db.lookup.notes(db.notes.all, "5"); let filtered = await db.lookup.notes(db.notes.all, "5");
@@ -26,7 +28,7 @@ test("search notes", () =>
test("search notes with a locked note", () => test("search notes with a locked note", () =>
noteTest({ noteTest({
content: { type: "delta", data: [{ insert: "5" }] }, content: content,
}).then(async ({ db }) => { }).then(async ({ db }) => {
const noteId = await db.notes.add(TEST_NOTE); const noteId = await db.notes.add(TEST_NOTE);
await db.vault.create("password"); await db.vault.create("password");
@@ -37,11 +39,11 @@ test("search notes with a locked note", () =>
test("search notes with an empty note", () => test("search notes with an empty note", () =>
noteTest({ noteTest({
content: { type: "delta", data: [{ insert: "5" }] }, content: content,
}).then(async ({ db }) => { }).then(async ({ db }) => {
await db.notes.add({ await db.notes.add({
title: "hello world", title: "hello world",
content: { type: "delta", data: [{ insert: "\n" }] }, content: { type: "tiny", data: "<p><br></p>" },
}); });
let filtered = await db.lookup.notes(db.notes.all, "hello world"); let filtered = await db.lookup.notes(db.notes.all, "hello world");
expect(filtered.length).toBe(1); expect(filtered.length).toBe(1);

View File

@@ -60,7 +60,7 @@ test("get all notes", () =>
test("note without a title should get title from content", () => test("note without a title should get title from content", () =>
noteTest().then(async ({ db, id }) => { noteTest().then(async ({ db, id }) => {
let note = db.notes.note(id); let note = db.notes.note(id);
expect(note.title).toBe("Hello This is colorful"); expect(note.title).toBe("HelloThis is colorful");
})); }));
test("note title should allow trailing space", () => test("note title should allow trailing space", () =>
@@ -85,8 +85,8 @@ test("update note", () =>
id, id,
title: "I am a new title", title: "I am a new title",
content: { content: {
type: "delta", type: TEST_NOTE.content.type,
data: [], data: "<p><br></p>",
}, },
pinned: true, pinned: true,
favorite: true, favorite: true,
@@ -106,8 +106,8 @@ test("updating empty note should delete it", () =>
id, id,
title: "\n", title: "\n",
content: { content: {
type: "delta", type: TEST_NOTE.content.type,
data: [{ insert: "\n" }], data: "<p><br></p>",
}, },
}); });
expect(id).toBeUndefined(); expect(id).toBeUndefined();
@@ -221,7 +221,7 @@ test("export note to html", () =>
const html = await db.notes.note(id).export("html"); const html = await db.notes.note(id).export("html");
expect( expect(
html.includes( html.includes(
`<p>Hello<br/><span style="color:#f00">This is colorful</span></p>` `<p>Hello<br/><span style='color:#f00'>This is colorful</span></p>`
) )
).toBeTruthy(); ).toBeTruthy();
})); }));
@@ -229,7 +229,7 @@ test("export note to html", () =>
test("export note to md", () => test("export note to md", () =>
noteTest().then(async ({ db, id }) => { noteTest().then(async ({ db, id }) => {
const md = await db.notes.note(id).export("md"); const md = await db.notes.note(id).export("md");
expect(md.includes(`Hello\nThis is colorful\n`)).toBeTruthy(); expect(md.includes(`Hello \nThis is colorful\n`)).toBeTruthy();
})); }));
test("export note to txt", () => test("export note to txt", () =>

View File

@@ -69,7 +69,7 @@ test("get topic", () =>
await topics.add("Home"); await topics.add("Home");
let topic = topics.topic("Home"); let topic = topics.topic("Home");
let noteId = await db.notes.add({ let noteId = await db.notes.add({
content: { type: "delta", data: [{ insert: "Hello" }] }, content: TEST_NOTE.content,
}); });
await topic.add(noteId); await topic.add(noteId);
topic = topics.topic("Home"); topic = topics.topic("Home");

View File

@@ -27,11 +27,8 @@ const notebookTest = (notebook = TEST_NOTEBOOK) =>
var TEST_NOTE = { var TEST_NOTE = {
content: { content: {
type: "delta", type: "tiny",
data: [ data: `<p>Hello<br/><span style='color:#f00'>This is colorful</span></p>`,
{ insert: "Hello\n" },
{ insert: "This is colorful", attributes: { color: "#f00" } },
],
}, },
}; };

View File

@@ -61,7 +61,7 @@ test("unlock a note", () =>
const note = await db.vault.open(id, "password"); const note = await db.vault.open(id, "password");
expect(note.id).toBe(id); expect(note.id).toBe(id);
expect(note.content.data).toBeDefined(); expect(note.content.data).toBeDefined();
expect(note.content.type).toBe("delta"); expect(note.content.type).toBe(TEST_NOTE.content.type);
})); }));
test("unlock a note permanently", () => test("unlock a note permanently", () =>
@@ -74,7 +74,7 @@ test("unlock a note permanently", () =>
expect(note.headline).not.toBe(""); expect(note.headline).not.toBe("");
const content = await db.content.raw(note.data.contentId); const content = await db.content.raw(note.data.contentId);
expect(content.data).toBeDefined(); expect(content.data).toBeDefined();
expect(typeof content.data).toBe("object"); expect(typeof content.data).toBe("string");
})); }));
test("save a locked note", () => test("save a locked note", () =>

View File

@@ -49,6 +49,7 @@ class Migrations {
{ {
index: [this._db.settings.raw], index: [this._db.settings.raw],
dbCollection: this._db.settings, dbCollection: this._db.settings,
type: "settings",
}, },
]; ];
await this._migrator.migrate(collections, (item) => item, this.dbVersion); await this._migrator.migrate(collections, (item) => item, this.dbVersion);

View File

@@ -47,7 +47,7 @@ test("sync without merge conflicts, cause merge conflicts, resolve them and then
// 4. edit the note's content // 4. edit the note's content
await db.notes.add({ await db.notes.add({
id: noteId, id: noteId,
content: { type: "delta", data: [{ insert: "text" }] }, content: { ...TEST_NOTE.content },
}); });
// 5. sync again and expect conflicts // 5. sync again and expect conflicts
@@ -57,10 +57,10 @@ test("sync without merge conflicts, cause merge conflicts, resolve them and then
v: CURRENT_DATABASE_VERSION, v: CURRENT_DATABASE_VERSION,
...(await getEncrypted({ ...(await getEncrypted({
id: contentId, id: contentId,
type: "delta", type: TEST_NOTE.content.type,
dateEdited: Date.now(), dateEdited: Date.now(),
conflicted: false, conflicted: false,
data: [{ insert: "text" }], data: TEST_NOTE.content.data,
})), })),
}; };
@@ -88,8 +88,8 @@ test("sync without merge conflicts, cause merge conflicts, resolve them and then
id: noteId, id: noteId,
conflicted: false, conflicted: false,
content: { content: {
type: "delta", type: TEST_NOTE.content.type,
data: [{ insert: "text" }], data: TEST_NOTE.content.data,
resolved: true, resolved: true,
}, },
}); });

View File

@@ -31,4 +31,4 @@ export const EVENTS = {
noteRemoved: "note:removed", noteRemoved: "note:removed",
}; };
export const CURRENT_DATABASE_VERSION = 4.3; export const CURRENT_DATABASE_VERSION = 5.0;

View File

@@ -1,68 +0,0 @@
import { QuillDeltaToHtmlConverter } from "quill-delta-to-html";
import { deltaToMarkdown } from "quill-delta-to-markdown";
const splitter = /\W+/gm;
class Delta {
constructor(data) {
this.data = data;
}
toHTML() {
const deltaConverter = new QuillDeltaToHtmlConverter(this.data, {
classPrefix: "nn",
inlineStyles: true,
});
return deltaConverter.convert();
}
toTXT() {
return this.data.reduce(function (text, op) {
if (!op.insert) return text;
if (typeof op.insert !== "string") return text + " ";
return text + op.insert;
}, "");
}
toMD() {
return deltaToMarkdown(this.data);
}
toTitle() {
return getSubstringFromDelta(this.data, 30);
}
toHeadline() {
return getSubstringFromDelta(this.data, 80);
}
isEmpty() {
return this.toTXT().trim().length <= 0;
}
/**
* @returns {Boolean}
*/
search(query) {
const tokens = query.toLowerCase().split(splitter);
return this.data.some((item) => {
if (item.insert && item.insert.indexOf) {
return tokens.some(
(token) => item.insert.toLowerCase().indexOf(token) > -1
);
}
return false;
});
}
}
export default Delta;
function getSubstringFromDelta(data, limit) {
let substr = "";
for (var i = 0; i < data.length; ++i) {
const item = data[i];
if (item.insert && typeof item.insert === "string")
substr += item.insert.replace(/\r?\n/g, " ");
if (substr.length > limit) return substr.substring(0, limit);
}
return substr;
}

View File

@@ -1,9 +1,9 @@
import Delta from "./delta"; import Tiny from "./tiny";
export function getContentFromData(type, data) { export function getContentFromData(type, data) {
switch (type) { switch (type) {
case "delta": case "tiny":
return new Delta(data); return new Tiny(data);
default: default:
return null; return null;
} }

View File

@@ -0,0 +1,48 @@
import TurndownService from "turndown";
var turndownService = new TurndownService();
const splitter = /\W+/gm;
class Tiny {
constructor(data) {
this.data = data;
}
toHTML() {
return this.data;
}
toTXT() {
if (window.DOMParser) {
let doc = new DOMParser().parseFromString(this.data, "text/html");
return doc.body.textContent || "";
} else {
return this.data.replace(/<br>/gm, "\n").replace(/<[^>]+>/g, "");
}
}
toMD() {
return turndownService.turndown(this.data);
}
toTitle() {
return this.toTXT().substring(0, 30);
}
toHeadline() {
return this.toTXT().substring(0, 80);
}
isEmpty() {
return this.toTXT().trim().length <= 0;
}
/**
* @returns {Boolean}
*/
search(query) {
const tokens = query.toLowerCase().split(splitter);
const lowercase = this.toTXT().toLowerCase();
return tokens.some((token) => lowercase.indexOf(token) > -1);
}
}
export default Tiny;

View File

@@ -97,6 +97,7 @@ export default class Backup {
case 4: case 4:
case 4.1: case 4.1:
case 4.2: case 4.2:
case 4.3:
case 3: case 3:
case 2: { case 2: {
return backup; return backup;
@@ -144,6 +145,7 @@ export default class Backup {
{ {
index: data["delta"], index: data["delta"],
dbCollection: this._db.content, dbCollection: this._db.content,
type: "delta",
}, },
{ {
index: data["content"], index: data["content"],
@@ -152,6 +154,7 @@ export default class Backup {
{ {
index: ["settings"], index: ["settings"],
dbCollection: this._db.settings, dbCollection: this._db.settings,
type: "settings",
}, },
]; ];

View File

@@ -15,7 +15,7 @@ class Migrator {
if (item.deleted) if (item.deleted)
return await collection.dbCollection._collection.addItem(item); return await collection.dbCollection._collection.addItem(item);
const migrate = migrations[version][item.type || id]; const migrate = migrations[version][item.type || collection.type];
if (migrate) item = migrate(item); if (migrate) item = migrate(item);
if (!!collection.dbCollection.merge) { if (!!collection.dbCollection.merge) {

View File

@@ -1,3 +1,5 @@
import { QuillDeltaToHtmlConverter } from "quill-delta-to-html";
export const migrations = { export const migrations = {
0: { 0: {
note: function (item) { note: function (item) {
@@ -14,7 +16,7 @@ export const migrations = {
item.data = item.data.ops; item.data = item.data.ops;
item.type = "delta"; item.type = "delta";
item.migrated = true; item.migrated = true;
return item; return migrations["2"].delta(item);
}, },
trash: function (item) { trash: function (item) {
item.itemType = item.type; item.itemType = item.type;
@@ -40,6 +42,7 @@ export const migrations = {
return migrations[3].note(item); return migrations[3].note(item);
}, },
delta: (item) => migrations["3"].delta(item),
}, },
3: { 3: {
note: function (item) { note: function (item) {
@@ -49,6 +52,7 @@ export const migrations = {
return migrations[4].note(item); return migrations[4].note(item);
}, },
delta: (item) => migrations["4"].delta(item),
}, },
4: { 4: {
note: function (item) { note: function (item) {
@@ -57,11 +61,13 @@ export const migrations = {
} }
return migrations["4.1"].note(item); return migrations["4.1"].note(item);
}, },
delta: (item) => migrations["4.1"].delta(item),
}, },
4.1: { 4.1: {
note: function (item) { note: function (item) {
return migrations["4.2"].note(item); return migrations["4.2"].note(item);
}, },
delta: (item) => migrations["4.2"].delta(item),
}, },
4.2: { 4.2: {
note: function (item) { note: function (item) {
@@ -73,8 +79,21 @@ export const migrations = {
item.migrated = true; item.migrated = true;
return item; return item;
}, },
delta: (item) => migrations["4.3"].delta(item),
}, },
4.3: { 4.3: {
delta: function (item) {
const deltaConverter = new QuillDeltaToHtmlConverter(item.data, {
classPrefix: "nn",
inlineStyles: true,
});
item.data = deltaConverter.convert();
item.type = "tiny";
item.migrated = true;
return item;
},
},
5.0: {
note: false, note: false,
notebook: false, notebook: false,
tag: false, tag: false,

View File

@@ -28,6 +28,7 @@
"qclone": "^1.0.4", "qclone": "^1.0.4",
"quill-delta-to-html": "^0.12.0", "quill-delta-to-html": "^0.12.0",
"quill-delta-to-markdown": "https://github.com/streetwriters/quill-delta-to-markdown", "quill-delta-to-markdown": "https://github.com/streetwriters/quill-delta-to-markdown",
"transfun": "^1.0.2" "transfun": "^1.0.2",
"turndown": "^7.0.0"
} }
} }

View File

@@ -2237,6 +2237,11 @@ domexception@^1.0.1:
dependencies: dependencies:
webidl-conversions "^4.0.2" webidl-conversions "^4.0.2"
domino@^2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.6.tgz#fe4ace4310526e5e7b9d12c7de01b7f485a57ffe"
integrity sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==
ecc-jsbn@~0.1.1: ecc-jsbn@~0.1.1:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
@@ -4782,6 +4787,13 @@ tunnel-agent@^0.6.0:
dependencies: dependencies:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
turndown@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/turndown/-/turndown-7.0.0.tgz#19b2a6a2d1d700387a1e07665414e4af4fec5225"
integrity sha512-G1FfxfR0mUNMeGjszLYl3kxtopC4O9DRRiMlMDDVHvU1jaBkGFg4qxIyjIk2aiKLHyDyZvZyu4qBO2guuYBy3Q==
dependencies:
domino "^2.1.6"
tweetnacl@^0.14.3, tweetnacl@~0.14.0: tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5" version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"