mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 23:19:40 +01:00
feat: migrate from tiny -> tiptap
This commit is contained in:
@@ -104,7 +104,7 @@ describe.each([
|
|||||||
|
|
||||||
const allContent = await db.content.all();
|
const allContent = await db.content.all();
|
||||||
expect(
|
expect(
|
||||||
allContent.every((v) => v.type === "tiny" || 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();
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ test("search notes with an empty note", () =>
|
|||||||
}).then(async ({ db }) => {
|
}).then(async ({ db }) => {
|
||||||
await db.notes.add({
|
await db.notes.add({
|
||||||
title: "world is a heavy tune",
|
title: "world is a heavy tune",
|
||||||
content: { type: "tiny", data: "<p><br></p>" },
|
content: { type: "tiptap", data: "<p><br></p>" },
|
||||||
});
|
});
|
||||||
let filtered = await db.lookup.notes(db.notes.all, "heavy tune");
|
let filtered = await db.lookup.notes(db.notes.all, "heavy tune");
|
||||||
expect(filtered).toHaveLength(1);
|
expect(filtered).toHaveLength(1);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ beforeEach(async () => {
|
|||||||
// let note = await db.notes.note(noteId).data;
|
// let note = await db.notes.note(noteId).data;
|
||||||
// let content = {
|
// let content = {
|
||||||
// data: await db.notes.note(noteId).content(),
|
// data: await db.notes.note(noteId).content(),
|
||||||
// type: "tiny",
|
// type: "tiptap",
|
||||||
// };
|
// };
|
||||||
// let session = await db.noteHistory.add(noteId, note.dateEdited, content);
|
// let session = await db.noteHistory.add(noteId, note.dateEdited, content);
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ test("editing the same note should create multiple history sessions", () =>
|
|||||||
noteTest({ ...TEST_NOTE, sessionId: Date.now() }).then(async ({ db, id }) => {
|
noteTest({ ...TEST_NOTE, sessionId: Date.now() }).then(async ({ db, id }) => {
|
||||||
let editedContent = {
|
let editedContent = {
|
||||||
data: TEST_NOTE.content.data + "<p>Some new content</p>",
|
data: TEST_NOTE.content.data + "<p>Some new content</p>",
|
||||||
type: "tiny",
|
type: "tiptap",
|
||||||
};
|
};
|
||||||
|
|
||||||
await db.notes.add({
|
await db.notes.add({
|
||||||
@@ -52,7 +52,7 @@ test("restoring an old session should replace note's content", () =>
|
|||||||
noteTest({ ...TEST_NOTE, sessionId: Date.now() }).then(async ({ db, id }) => {
|
noteTest({ ...TEST_NOTE, sessionId: Date.now() }).then(async ({ db, id }) => {
|
||||||
let editedContent = {
|
let editedContent = {
|
||||||
data: TEST_NOTE.content.data + "<p>Some new content</p>",
|
data: TEST_NOTE.content.data + "<p>Some new content</p>",
|
||||||
type: "tiny",
|
type: "tiptap",
|
||||||
};
|
};
|
||||||
|
|
||||||
await db.notes.add({
|
await db.notes.add({
|
||||||
@@ -75,7 +75,7 @@ test("date created of session should not change on edit", () =>
|
|||||||
|
|
||||||
let editedContent = {
|
let editedContent = {
|
||||||
data: TEST_NOTE.content.data + "<p>Some new content</p>",
|
data: TEST_NOTE.content.data + "<p>Some new content</p>",
|
||||||
type: "tiny",
|
type: "tiptap",
|
||||||
};
|
};
|
||||||
|
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
@@ -141,7 +141,7 @@ test("auto clear sessions if they exceed the limit", () =>
|
|||||||
noteTest({ ...TEST_NOTE, sessionId: Date.now() }).then(async ({ db, id }) => {
|
noteTest({ ...TEST_NOTE, sessionId: Date.now() }).then(async ({ db, id }) => {
|
||||||
let editedContent = {
|
let editedContent = {
|
||||||
data: TEST_NOTE.content.data + "<p>Some new content</p>",
|
data: TEST_NOTE.content.data + "<p>Some new content</p>",
|
||||||
type: "tiny",
|
type: "tiptap",
|
||||||
};
|
};
|
||||||
|
|
||||||
await db.notes.add({
|
await db.notes.add({
|
||||||
@@ -168,7 +168,7 @@ test("save a locked note should add a locked session to note history", () =>
|
|||||||
await db.vault.add(id);
|
await db.vault.add(id);
|
||||||
|
|
||||||
const note = db.notes.note(id).data;
|
const note = db.notes.note(id).data;
|
||||||
const editedContent = { type: "tiny", data: "<p>hello world</p>" };
|
const editedContent = { type: "tiptap", data: "<p>hello world</p>" };
|
||||||
await db.vault.save({
|
await db.vault.save({
|
||||||
...note,
|
...note,
|
||||||
content: editedContent,
|
content: editedContent,
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ test("note content should not contain image base64 data after save", () =>
|
|||||||
id
|
id
|
||||||
);
|
);
|
||||||
|
|
||||||
await db.notes.add({ id, content: { type: "tiny", data: IMG_CONTENT } });
|
await db.notes.add({ id, content: { type: "tiptap", data: IMG_CONTENT } });
|
||||||
const note = db.notes.note(id);
|
const note = db.notes.note(id);
|
||||||
const content = await note.content();
|
const content = await note.content();
|
||||||
expect(content).not.toContain(`src="data:image/png;`);
|
expect(content).not.toContain(`src="data:image/png;`);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const notebookTest = (notebook = TEST_NOTEBOOK) =>
|
|||||||
|
|
||||||
var TEST_NOTE = {
|
var TEST_NOTE = {
|
||||||
content: {
|
content: {
|
||||||
type: "tiny",
|
type: "tiptap",
|
||||||
data: `<p>Hello<br><span style="color:#f00">This is colorful</span></p>`,
|
data: `<p>Hello<br><span style="color:#f00">This is colorful</span></p>`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ test("save an edited locked note", () =>
|
|||||||
const note = db.notes.note(id).data;
|
const note = db.notes.note(id).data;
|
||||||
await db.vault.save({
|
await db.vault.save({
|
||||||
...note,
|
...note,
|
||||||
content: { type: "tiny", data: "<p>hello world</p>" },
|
content: { type: "tiptap", data: "<p>hello world</p>" },
|
||||||
});
|
});
|
||||||
|
|
||||||
const content = await db.content.raw(note.contentId);
|
const content = await db.content.raw(note.contentId);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CURRENT_DATABASE_VERSION, EV, EVENTS } from "../common";
|
import { CURRENT_DATABASE_VERSION, EVENTS } from "../common";
|
||||||
import Migrator from "../database/migrator";
|
import Migrator from "../database/migrator";
|
||||||
|
|
||||||
class Migrations {
|
class Migrations {
|
||||||
@@ -9,6 +9,7 @@ class Migrations {
|
|||||||
constructor(db) {
|
constructor(db) {
|
||||||
this._db = db;
|
this._db = db;
|
||||||
this._migrator = new Migrator();
|
this._migrator = new Migrator();
|
||||||
|
this._isMigrating = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
@@ -18,7 +19,13 @@ class Migrations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async migrate() {
|
async migrate() {
|
||||||
if (this.dbVersion >= CURRENT_DATABASE_VERSION) return;
|
if (this.dbVersion >= CURRENT_DATABASE_VERSION || this._isMigrating) return;
|
||||||
|
|
||||||
|
this._isMigrating = true;
|
||||||
|
this._db.eventManager.publish(EVENTS.databaseMigrating, {
|
||||||
|
from: this.dbVersion,
|
||||||
|
to: CURRENT_DATABASE_VERSION,
|
||||||
|
});
|
||||||
|
|
||||||
await this._db.notes.init();
|
await this._db.notes.init();
|
||||||
const content = await this._db.content.all();
|
const content = await this._db.content.all();
|
||||||
@@ -58,10 +65,7 @@ class Migrations {
|
|||||||
await this._migrator.migrate(collections, (item) => item, this.dbVersion);
|
await this._migrator.migrate(collections, (item) => item, this.dbVersion);
|
||||||
await this._db.storage.write("v", CURRENT_DATABASE_VERSION);
|
await this._db.storage.write("v", CURRENT_DATABASE_VERSION);
|
||||||
|
|
||||||
EV.publish(EVENTS.databaseMigrated, {
|
this._db.eventManager.publish(EVENTS.databaseMigrated);
|
||||||
prev: this.dbVersion,
|
|
||||||
current: CURRENT_DATABASE_VERSION,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dbVersion = CURRENT_DATABASE_VERSION;
|
this.dbVersion = CURRENT_DATABASE_VERSION;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ test.skip(
|
|||||||
for (let i = 0; i < 10; ++i) {
|
for (let i = 0; i < 10; ++i) {
|
||||||
const id = await deviceA.notes.add({
|
const id = await deviceA.notes.add({
|
||||||
content: {
|
content: {
|
||||||
type: "tiny",
|
type: "tiptap",
|
||||||
data: `<p>deviceA=true</p>`,
|
data: `<p>deviceA=true</p>`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -137,7 +137,7 @@ test.skip(
|
|||||||
const noteId = await deviceA.notes.add({
|
const noteId = await deviceA.notes.add({
|
||||||
id,
|
id,
|
||||||
content: {
|
content: {
|
||||||
type: "tiny",
|
type: "tiptap",
|
||||||
data: `<p>deviceA=true+changed=true</p>`,
|
data: `<p>deviceA=true+changed=true</p>`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -162,7 +162,7 @@ test.skip(
|
|||||||
await deviceB.notes.add({
|
await deviceB.notes.add({
|
||||||
id,
|
id,
|
||||||
content: {
|
content: {
|
||||||
type: "tiny",
|
type: "tiptap",
|
||||||
data: "<p>changes from device B</p>",
|
data: "<p>changes from device B</p>",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -211,7 +211,7 @@ test.skip(
|
|||||||
for (let i = 0; i < 3; ++i) {
|
for (let i = 0; i < 3; ++i) {
|
||||||
const id = await deviceA.notes.add({
|
const id = await deviceA.notes.add({
|
||||||
content: {
|
content: {
|
||||||
type: "tiny",
|
type: "tiptap",
|
||||||
data: `<p>deviceA=true</p>`,
|
data: `<p>deviceA=true</p>`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class Merger {
|
|||||||
if (deserialized.alg && deserialized.cipher) return deserialized;
|
if (deserialized.alg && deserialized.cipher) return deserialized;
|
||||||
|
|
||||||
let type = deserialized.type;
|
let type = deserialized.type;
|
||||||
if (!type && deserialized.data) type = "tiny";
|
if (!type && deserialized.data) type = "tiptap";
|
||||||
|
|
||||||
if (!migrations[version]) {
|
if (!migrations[version]) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -110,7 +110,10 @@ class Merger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const migrate = migrations[version][type];
|
const migrate = migrations[version][type];
|
||||||
if (migrate) return migrate(deserialized);
|
if (migrate) {
|
||||||
|
console.log(migrate, version, type);
|
||||||
|
return migrate(deserialized);
|
||||||
|
}
|
||||||
return deserialized;
|
return deserialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { CHECK_IDS, EV, EVENTS, checkIsUserPremium } from "../common";
|
import { CHECK_IDS, EV, EVENTS, checkIsUserPremium } from "../common";
|
||||||
|
import { tinyToTiptap } from "../migrations";
|
||||||
|
|
||||||
const ERASE_TIME = 1000 * 60 * 30;
|
const ERASE_TIME = 1000 * 60 * 30;
|
||||||
var ERASER_TIMEOUT = null;
|
var ERASER_TIMEOUT = null;
|
||||||
@@ -228,7 +229,18 @@ export default class Vault {
|
|||||||
encryptedContent.data
|
encryptedContent.data
|
||||||
);
|
);
|
||||||
|
|
||||||
return { type: encryptedContent.type, data: JSON.parse(decryptedContent) };
|
const content = {
|
||||||
|
type: encryptedContent.type,
|
||||||
|
data: JSON.parse(decryptedContent),
|
||||||
|
};
|
||||||
|
|
||||||
|
// #MIGRATION: convert tiny to tiptap
|
||||||
|
if (content.type === "tiny") {
|
||||||
|
content.type = "tiptap";
|
||||||
|
content.data = tinyToTiptap(content.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export const EVENTS = {
|
|||||||
syncProgress: "sync:progress",
|
syncProgress: "sync:progress",
|
||||||
syncCompleted: "sync:completed",
|
syncCompleted: "sync:completed",
|
||||||
databaseMigrated: "db:migrated",
|
databaseMigrated: "db:migrated",
|
||||||
|
databaseMigrating: "db:migrating",
|
||||||
databaseUpdated: "db:updated",
|
databaseUpdated: "db:updated",
|
||||||
databaseCollectionInitiated: "db:collectionInitiated",
|
databaseCollectionInitiated: "db:collectionInitiated",
|
||||||
appRefreshRequested: "app:refreshRequested",
|
appRefreshRequested: "app:refreshRequested",
|
||||||
@@ -65,7 +66,7 @@ export const EVENTS = {
|
|||||||
systemTimeInvalid: "system:invalidTime",
|
systemTimeInvalid: "system:invalidTime",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CURRENT_DATABASE_VERSION = 5.4;
|
export const CURRENT_DATABASE_VERSION = 5.5;
|
||||||
|
|
||||||
export function setUserPersonalizationBytes(userSalt) {
|
export function setUserPersonalizationBytes(userSalt) {
|
||||||
USER_PERSONALIZATION_HASH = new Uint8Array(
|
USER_PERSONALIZATION_HASH = new Uint8Array(
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,9 +1,9 @@
|
|||||||
import Tiny from "./tiny";
|
import { Tiptap } from "./tiptap";
|
||||||
|
|
||||||
export function getContentFromData(type, data) {
|
export function getContentFromData(type, data) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "tiny":
|
case "tiptap":
|
||||||
return new Tiny(data);
|
return new Tiptap(data);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ var converter = new showdown.Converter();
|
|||||||
converter.setFlavor("original");
|
converter.setFlavor("original");
|
||||||
|
|
||||||
const splitter = /\W+/gm;
|
const splitter = /\W+/gm;
|
||||||
class Tiny {
|
export class Tiptap {
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.text;
|
this.text;
|
||||||
@@ -144,7 +144,6 @@ class Tiny {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default Tiny;
|
|
||||||
|
|
||||||
function getDatasetAttribute(element, attribute) {
|
function getDatasetAttribute(element, attribute) {
|
||||||
return element.getAttribute(`data-${attribute}`);
|
return element.getAttribute(`data-${attribute}`);
|
||||||
@@ -109,6 +109,7 @@ export default class Backup {
|
|||||||
|
|
||||||
switch (version) {
|
switch (version) {
|
||||||
case CURRENT_DATABASE_VERSION:
|
case CURRENT_DATABASE_VERSION:
|
||||||
|
case 5.4:
|
||||||
case 5.3:
|
case 5.3:
|
||||||
case 5.2:
|
case 5.2:
|
||||||
case 5.1:
|
case 5.1:
|
||||||
|
|||||||
@@ -13,27 +13,36 @@ export const migrations = {
|
|||||||
tiny: (item) => {
|
tiny: (item) => {
|
||||||
item = replaceDateEditedWithDateModified()(item);
|
item = replaceDateEditedWithDateModified()(item);
|
||||||
|
|
||||||
if (!item.data || item.data.iv) return item;
|
if (!item.data || item.data.iv) return migrations["5.3"].tiny(item);
|
||||||
|
|
||||||
item.data = removeToxClassFromChecklist(wrapTablesWithDiv(item.data));
|
item.data = removeToxClassFromChecklist(wrapTablesWithDiv(item.data));
|
||||||
return item;
|
return migrations["5.3"].tiny(item);
|
||||||
},
|
},
|
||||||
settings: replaceDateEditedWithDateModified(true),
|
settings: replaceDateEditedWithDateModified(true),
|
||||||
},
|
},
|
||||||
5.3: {
|
5.3: {
|
||||||
tiny: (item) => {
|
tiny: (item) => {
|
||||||
if (!item.data || item.data.iv) return item;
|
if (!item.data || item.data.iv) return migrations["5.4"].tiny(item);
|
||||||
item.data = decodeWrappedTableHtml(item.data);
|
item.data = decodeWrappedTableHtml(item.data);
|
||||||
return item;
|
return migrations["5.4"].tiny(item);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
5.4: {
|
5.4: {
|
||||||
|
tiny: (item) => {
|
||||||
|
if (!item.data || item.data.iv) return item;
|
||||||
|
item.type = "tiptap";
|
||||||
|
item.data = tinyToTiptap(item.data);
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
5.5: {
|
||||||
note: false,
|
note: false,
|
||||||
notebook: false,
|
notebook: false,
|
||||||
tag: false,
|
tag: false,
|
||||||
attachment: false,
|
attachment: false,
|
||||||
trash: false,
|
trash: false,
|
||||||
tiny: false,
|
tiny: false,
|
||||||
|
tiptap: false,
|
||||||
settings: false,
|
settings: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -83,3 +92,54 @@ function decodeWrappedTableHtml(html) {
|
|||||||
return html;
|
return html;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function tinyToTiptap(html) {
|
||||||
|
const document = parseHTML(html);
|
||||||
|
|
||||||
|
const tables = document.querySelectorAll("table");
|
||||||
|
for (const table of tables) {
|
||||||
|
table.removeAttribute("contenteditable");
|
||||||
|
if (table.parentElement?.nodeName.toLowerCase() === "div") {
|
||||||
|
table.parentElement.replaceWith(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const images = document.querySelectorAll("p > img");
|
||||||
|
for (const image of images) {
|
||||||
|
image.parentElement.replaceWith(image.cloneNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
const breaks = document.querySelectorAll("br");
|
||||||
|
for (const br of breaks) {
|
||||||
|
br.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const paragraphs = document.querySelectorAll("p");
|
||||||
|
for (const p of paragraphs) {
|
||||||
|
if (!p.childNodes.length) p.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const listItems = document.querySelectorAll("li");
|
||||||
|
for (const li of listItems) {
|
||||||
|
if (li.firstChild) {
|
||||||
|
const p = document.createElement("p");
|
||||||
|
p.appendChild(li.firstChild.cloneNode());
|
||||||
|
li.firstChild.replaceWith(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bogus = document.querySelectorAll("[data-mce-bogus]");
|
||||||
|
for (const element of bogus) {
|
||||||
|
element.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributes = document.querySelectorAll(
|
||||||
|
"[data-mce-href], [data-mce-flag]"
|
||||||
|
);
|
||||||
|
for (const element of attributes) {
|
||||||
|
element.removeAttribute("data-mce-href");
|
||||||
|
element.removeAttribute("data-mce-flag");
|
||||||
|
}
|
||||||
|
|
||||||
|
return document.body.innerHTML;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user