mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 06:59:31 +01:00
feat: implement trash
This commit is contained in:
@@ -71,6 +71,12 @@ test("delete a notebook", () =>
|
|||||||
.notebook(id)
|
.notebook(id)
|
||||||
.topics.topic("General")
|
.topics.topic("General")
|
||||||
.add(noteId);
|
.add(noteId);
|
||||||
|
expect(
|
||||||
|
db.notebooks
|
||||||
|
.notebook(id)
|
||||||
|
.topics.topic("General")
|
||||||
|
.has(noteId)
|
||||||
|
).toBe(true);
|
||||||
let note = db.notes.note(noteId);
|
let note = db.notes.note(noteId);
|
||||||
expect(note.notebook.id).toBe(id);
|
expect(note.notebook.id).toBe(id);
|
||||||
await db.notebooks.delete(id);
|
await db.notebooks.delete(id);
|
||||||
|
|||||||
103
packages/core/__tests__/trash.test.js
Normal file
103
packages/core/__tests__/trash.test.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import {
|
||||||
|
StorageInterface,
|
||||||
|
noteTest,
|
||||||
|
notebookTest,
|
||||||
|
TEST_NOTE,
|
||||||
|
TEST_NOTEBOOK
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
|
beforeEach(() => StorageInterface.clear());
|
||||||
|
|
||||||
|
test("delete a note", () =>
|
||||||
|
noteTest().then(async ({ db, id }) => {
|
||||||
|
let nbId = await db.notebooks.add(TEST_NOTEBOOK);
|
||||||
|
await db.notebooks
|
||||||
|
.notebook(nbId)
|
||||||
|
.topics.topic("General")
|
||||||
|
.add(id);
|
||||||
|
await db.notes.delete(id);
|
||||||
|
expect(db.trash.all.length).toBe(1);
|
||||||
|
expect(await db.trash.deltaStorage.read(id + "_delta")).toBeDefined();
|
||||||
|
await db.trash.delete(db.trash.all[0].id);
|
||||||
|
expect(db.trash.all.length).toBe(0);
|
||||||
|
expect(await db.trash.deltaStorage.read(id + "_delta")).toBeUndefined();
|
||||||
|
}));
|
||||||
|
|
||||||
|
test("restore a deleted note", () =>
|
||||||
|
noteTest().then(async ({ db, id }) => {
|
||||||
|
let nbId = await db.notebooks.add(TEST_NOTEBOOK);
|
||||||
|
await db.notebooks
|
||||||
|
.notebook(nbId)
|
||||||
|
.topics.topic("General")
|
||||||
|
.add(id);
|
||||||
|
await db.notes.delete(id);
|
||||||
|
await db.trash.restore(db.trash.all[0].id);
|
||||||
|
expect(db.trash.all.length).toBe(0);
|
||||||
|
let note = db.notes.note(id);
|
||||||
|
expect(note).toBeDefined();
|
||||||
|
expect(
|
||||||
|
db.notebooks
|
||||||
|
.notebook(nbId)
|
||||||
|
.topics.topic("General")
|
||||||
|
.has(id)
|
||||||
|
).toBe(true);
|
||||||
|
expect(db.notes.note(id).notebook.id).toBe(nbId);
|
||||||
|
expect(db.notes.note(id).notebook.topic).toBe("General");
|
||||||
|
}));
|
||||||
|
|
||||||
|
test("restore a deleted note that's in a deleted notebook", () =>
|
||||||
|
noteTest().then(async ({ db, id }) => {
|
||||||
|
let nbId = await db.notebooks.add(TEST_NOTEBOOK);
|
||||||
|
await db.notebooks
|
||||||
|
.notebook(nbId)
|
||||||
|
.topics.topic("General")
|
||||||
|
.add(id);
|
||||||
|
await db.notes.delete(id);
|
||||||
|
await db.notebooks.delete(nbId);
|
||||||
|
await db.trash.restore(db.trash.all[0].id);
|
||||||
|
let note = db.notes.note(id);
|
||||||
|
expect(note).toBeDefined();
|
||||||
|
expect(db.notes.note(id).notebook).toStrictEqual({});
|
||||||
|
}));
|
||||||
|
|
||||||
|
test("delete a notebook", () =>
|
||||||
|
notebookTest().then(async ({ db, id }) => {
|
||||||
|
let noteId = await db.notes.add(TEST_NOTE);
|
||||||
|
await db.notebooks
|
||||||
|
.notebook(id)
|
||||||
|
.topics.topic("General")
|
||||||
|
.add(noteId);
|
||||||
|
await db.notebooks.delete(id);
|
||||||
|
expect(db.notebooks.notebook(id)).toBeUndefined();
|
||||||
|
expect(db.notes.note(noteId).notebook).toStrictEqual({});
|
||||||
|
}));
|
||||||
|
|
||||||
|
test("restore a deleted notebook", () =>
|
||||||
|
notebookTest().then(async ({ db, id }) => {
|
||||||
|
let noteId = await db.notes.add(TEST_NOTE);
|
||||||
|
await db.notebooks
|
||||||
|
.notebook(id)
|
||||||
|
.topics.topic("General")
|
||||||
|
.add(noteId);
|
||||||
|
await db.notebooks.delete(id);
|
||||||
|
await db.trash.restore(db.trash.all[0].id);
|
||||||
|
let notebook = db.notebooks.notebook(id);
|
||||||
|
expect(notebook).toBeDefined();
|
||||||
|
expect(db.notes.note(noteId).notebook.id).toBe(id);
|
||||||
|
expect(db.notes.note(noteId).notebook.topic).toBe("General");
|
||||||
|
}));
|
||||||
|
|
||||||
|
test("restore a notebook that has deleted notes", () =>
|
||||||
|
notebookTest().then(async ({ db, id }) => {
|
||||||
|
let noteId = await db.notes.add(TEST_NOTE);
|
||||||
|
await db.notebooks
|
||||||
|
.notebook(id)
|
||||||
|
.topics.topic("General")
|
||||||
|
.add(noteId);
|
||||||
|
await db.notebooks.delete(id);
|
||||||
|
await db.notes.delete(noteId);
|
||||||
|
await db.trash.restore(db.trash.all[0].id);
|
||||||
|
let notebook = db.notebooks.notebook(id);
|
||||||
|
expect(notebook).toBeDefined();
|
||||||
|
expect(notebook.topics.topic("General").has(noteId)).toBe(false);
|
||||||
|
}));
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import Notes from "../collections/notes";
|
import Notes from "../collections/notes";
|
||||||
import Notebooks from "../collections/notebooks";
|
import Notebooks from "../collections/notebooks";
|
||||||
|
import Trash from "../collections/trash";
|
||||||
|
|
||||||
class DB {
|
class DB {
|
||||||
constructor(context) {
|
constructor(context) {
|
||||||
@@ -8,8 +9,10 @@ class DB {
|
|||||||
async init() {
|
async init() {
|
||||||
this.notebooks = new Notebooks(this.context);
|
this.notebooks = new Notebooks(this.context);
|
||||||
this.notes = new Notes(this.context);
|
this.notes = new Notes(this.context);
|
||||||
await this.notes.init(this.notebooks);
|
this.trash = new Trash(this.context);
|
||||||
await this.notebooks.init(this.notes);
|
await this.notes.init(this.notebooks, this.trash);
|
||||||
|
await this.notebooks.init(this.notes, this.trash);
|
||||||
|
await this.trash.init(this.notes, this.notebooks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import CachedCollection from "../database/cached-collection";
|
import CachedCollection from "../database/cached-collection";
|
||||||
import fuzzysearch from "fuzzysearch";
|
import fuzzysearch from "fuzzysearch";
|
||||||
import Notebook from "../models/notebook";
|
import Notebook from "../models/notebook";
|
||||||
|
import Notes from "./notes";
|
||||||
|
import Trash from "./trash";
|
||||||
var tfun = require("transfun/transfun.js").tfun;
|
var tfun = require("transfun/transfun.js").tfun;
|
||||||
if (!tfun) {
|
if (!tfun) {
|
||||||
tfun = global.tfun;
|
tfun = global.tfun;
|
||||||
@@ -12,8 +14,14 @@ export default class Notebooks {
|
|||||||
this.notes = undefined;
|
this.notes = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(notes) {
|
/**
|
||||||
|
*
|
||||||
|
* @param {Notes} notes
|
||||||
|
* @param {Trash} trash
|
||||||
|
*/
|
||||||
|
init(notes, trash) {
|
||||||
this.notes = notes;
|
this.notes = notes;
|
||||||
|
this.trash = trash;
|
||||||
return this.collection.init();
|
return this.collection.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,20 +45,19 @@ export default class Notebooks {
|
|||||||
notebook.topics = [...notebook.topics, ...oldNotebook.topics];
|
notebook.topics = [...notebook.topics, ...oldNotebook.topics];
|
||||||
}
|
}
|
||||||
|
|
||||||
let topics = notebook.topics || [];
|
let topics =
|
||||||
|
notebook.topics.filter((v, i) => notebook.topics.indexOf(v) === i) || [];
|
||||||
if (!oldNotebook) {
|
if (!oldNotebook) {
|
||||||
topics[0] = makeTopic("General", id);
|
topics[0] = makeTopic("General", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < topics.length; i++) {
|
for (let i = 0; i < topics.length; i++) {
|
||||||
let topic = topics[i];
|
let topic = topics[i];
|
||||||
let isDuplicate =
|
|
||||||
topics.findIndex(t => t.title === (topic || topic.title)) > -1; //check for duplicate
|
|
||||||
|
|
||||||
let isEmpty =
|
let isEmpty =
|
||||||
!topic || (typeof topic === "string" && topic.trim().length <= 0);
|
!topic || (typeof topic === "string" && topic.trim().length <= 0);
|
||||||
|
|
||||||
if (isEmpty || isDuplicate) {
|
if (isEmpty) {
|
||||||
topics.splice(i, 1);
|
topics.splice(i, 1);
|
||||||
i--;
|
i--;
|
||||||
continue;
|
continue;
|
||||||
@@ -96,6 +103,7 @@ export default class Notebooks {
|
|||||||
notebook.topics.delete(...notebook.topics.all)
|
notebook.topics.delete(...notebook.topics.all)
|
||||||
);
|
);
|
||||||
await this.collection.removeItem(id);
|
await this.collection.removeItem(id);
|
||||||
|
await this.trash.add(notebook.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import Storage from "../database/storage";
|
import Storage from "../database/storage";
|
||||||
import Notebooks from "./notebooks";
|
import Notebooks from "./notebooks";
|
||||||
import Note from "../models/note";
|
import Note from "../models/note";
|
||||||
|
import Trash from "./trash";
|
||||||
var tfun = require("transfun/transfun.js").tfun;
|
var tfun = require("transfun/transfun.js").tfun;
|
||||||
if (!tfun) {
|
if (!tfun) {
|
||||||
tfun = global.tfun;
|
tfun = global.tfun;
|
||||||
@@ -26,10 +27,12 @@ export default class Notes {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Notebooks} notebooks
|
* @param {Notebooks} notebooks
|
||||||
|
* @param {Trash} trash
|
||||||
*/
|
*/
|
||||||
async init(notebooks) {
|
async init(notebooks, trash) {
|
||||||
await this.collection.init();
|
await this.collection.init();
|
||||||
this.notebooks = notebooks;
|
this.notebooks = notebooks;
|
||||||
|
this.trash = trash;
|
||||||
await this.tagsCollection.init();
|
await this.tagsCollection.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +178,7 @@ export default class Notes {
|
|||||||
await this.tagsCollection.remove(tag);
|
await this.tagsCollection.remove(tag);
|
||||||
}
|
}
|
||||||
await this.collection.removeItem(id);
|
await this.collection.removeItem(id);
|
||||||
this.deltaStorage.remove(id + "_delta");
|
await this.trash.add(item.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ export default class Topics {
|
|||||||
this.notes = notes;
|
this.notes = notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exists(topic) {
|
||||||
|
return this.all.findIndex(v => v.title === (topic.title || topic)) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
async add(topic) {
|
async add(topic) {
|
||||||
await this.notebooks.add({
|
await this.notebooks.add({
|
||||||
id: this.notebookId,
|
id: this.notebookId,
|
||||||
@@ -36,23 +40,20 @@ export default class Topics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async delete(...topics) {
|
async delete(...topics) {
|
||||||
let notebook = this.notebooks.notebook(this.notebookId);
|
let allTopics = JSON.parse(JSON.stringify(this.all)); //FIXME: make a deep copy
|
||||||
if (!notebook) return;
|
for (let i = 0; i < allTopics.length; i++) {
|
||||||
notebook = notebook.data;
|
let topic = allTopics[i];
|
||||||
for (let topic of topics) {
|
|
||||||
if (!topic) continue;
|
if (!topic) continue;
|
||||||
let index = notebook.topics.findIndex(
|
let index = topics.findIndex(t => (t.title || t) === topic.title);
|
||||||
t => t.title === topic.title || topic
|
|
||||||
);
|
|
||||||
if (index <= -1) continue;
|
|
||||||
topic = notebook.topics[index];
|
|
||||||
let t = this.topic(topic);
|
let t = this.topic(topic);
|
||||||
await t.transaction(() => t.delete(...topic.notes), false);
|
await t.transaction(() => t.delete(...topic.notes), false);
|
||||||
notebook.topics.splice(index, 1);
|
if (index > -1) {
|
||||||
|
allTopics.splice(i, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await this.notebooks.add({
|
await this.notebooks.add({
|
||||||
id: notebook.id,
|
id: this.notebookId,
|
||||||
topics: notebook.topics
|
topics: allTopics
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
93
packages/core/collections/trash.js
Normal file
93
packages/core/collections/trash.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import CachedCollection from "../database/cached-collection";
|
||||||
|
import Notes from "./notes";
|
||||||
|
import Notebooks from "./notebooks";
|
||||||
|
import Storage from "../database/storage";
|
||||||
|
export default class Trash {
|
||||||
|
constructor(context) {
|
||||||
|
this.collection = new CachedCollection(context, "trash");
|
||||||
|
this.deltaStorage = new Storage(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Notes} notes
|
||||||
|
* @param {Notebooks} notebooks
|
||||||
|
*/
|
||||||
|
async init(notes, notebooks) {
|
||||||
|
this.notes = notes;
|
||||||
|
this.notebooks = notebooks;
|
||||||
|
await this.collection.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
get all() {
|
||||||
|
return this.collection.getAllItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
async add(item) {
|
||||||
|
if (this.collection.exists(item.id + "_deleted"))
|
||||||
|
throw new Error("This item has already been deleted.");
|
||||||
|
item.dateDeleted = Date.now();
|
||||||
|
item.id = item.id + "_deleted";
|
||||||
|
await this.collection.addItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(...ids) {
|
||||||
|
for (let id of ids) {
|
||||||
|
if (!this.collection.exists(id)) return;
|
||||||
|
if (id.indexOf("note") > -1)
|
||||||
|
this.deltaStorage.remove(id.replace("_deleted", "") + "_delta");
|
||||||
|
await this.collection.removeItem(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async restore(...ids) {
|
||||||
|
for (let id of ids) {
|
||||||
|
let item = this.collection.getItem(id);
|
||||||
|
if (!item) continue;
|
||||||
|
delete item.dateDeleted;
|
||||||
|
item.id = item.id.replace("_deleted", "");
|
||||||
|
if (item.type === "note") {
|
||||||
|
let { notebook } = item;
|
||||||
|
item.notebook = {};
|
||||||
|
await this.notes.add(item);
|
||||||
|
|
||||||
|
if (notebook && notebook.id && notebook.topic) {
|
||||||
|
const { id, topic } = notebook;
|
||||||
|
|
||||||
|
// if the notebook has been deleted
|
||||||
|
if (!this.notebooks.collection.exists(id)) {
|
||||||
|
notebook = {};
|
||||||
|
} else {
|
||||||
|
// if the topic has been deleted
|
||||||
|
if (!this.notebooks.notebook(id).topics.exists(topic)) {
|
||||||
|
notebook = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore the note to the topic it was in before deletion
|
||||||
|
if (notebook.id && notebook.topic) {
|
||||||
|
await this.notebooks
|
||||||
|
.notebook(id)
|
||||||
|
.topics.topic(topic)
|
||||||
|
.add(item.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const { topics } = item;
|
||||||
|
item.topics = [];
|
||||||
|
await this.notebooks.add(item);
|
||||||
|
let notebook = this.notebooks.notebook(item.id);
|
||||||
|
for (let topic of topics) {
|
||||||
|
let t = await notebook.topics.add(topic.title);
|
||||||
|
await t.add(...topic.notes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this.collection.removeItem(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async clear() {
|
||||||
|
let indices = await this.collection.indexer.getIndices();
|
||||||
|
return this.delete(...indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,6 +50,10 @@ export default class CachedCollection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exists(id) {
|
||||||
|
return this.map.has(id);
|
||||||
|
}
|
||||||
|
|
||||||
getItem(id) {
|
getItem(id) {
|
||||||
return this.map.get(id);
|
return this.map.get(id);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user