mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 18:19:38 +02:00
Compare commits
9 Commits
beta
...
fix/export
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f39cbad2f1 | ||
|
|
5f71d1d87b | ||
|
|
9d55b179a9 | ||
|
|
c178b6e5ec | ||
|
|
1a716f2f6f | ||
|
|
64e166c11c | ||
|
|
ac283a9fd3 | ||
|
|
57131e00c3 | ||
|
|
d0563a0d93 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
@@ -5,5 +5,6 @@ module.exports = {
|
||||
printWidth: 80,
|
||||
useTabs: false,
|
||||
tabWidth: 2,
|
||||
bracketSpacing: true
|
||||
bracketSpacing: true,
|
||||
endOfLine: "lf"
|
||||
};
|
||||
|
||||
72
.vscode/notesnook.code-workspace
vendored
72
.vscode/notesnook.code-workspace
vendored
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"name": "✨ notesnook",
|
||||
"path": ".."
|
||||
},
|
||||
{
|
||||
"name": "🚀 @notesnook/mobile",
|
||||
"path": "../apps/mobile"
|
||||
},
|
||||
{
|
||||
"name": "🚀 @notesnook/web",
|
||||
"path": "../apps/web"
|
||||
},
|
||||
{
|
||||
"name": "🚀 @notesnook/desktop",
|
||||
"path": "../apps/web/desktop"
|
||||
},
|
||||
{
|
||||
"name": "🚀 @notesnook/web-clipper",
|
||||
"path": "../extensions/web-clipper"
|
||||
},
|
||||
{
|
||||
"name": "📦 @notesnook/clipper",
|
||||
"path": "../packages/clipper"
|
||||
},
|
||||
{
|
||||
"name": "📦 @notesnook/core",
|
||||
"path": "../packages/core"
|
||||
},
|
||||
{
|
||||
"name": "📦 @notesnook/crypto",
|
||||
"path": "../packages/crypto"
|
||||
},
|
||||
{
|
||||
"name": "📦 @notesnook/crypto-worker",
|
||||
"path": "../packages/crypto-worker"
|
||||
},
|
||||
{
|
||||
"name": "📦 @notesnook/editor",
|
||||
"path": "../packages/editor"
|
||||
},
|
||||
{
|
||||
"name": "📦 @notesnook/editor-mobile",
|
||||
"path": "../packages/editor-mobile"
|
||||
},
|
||||
{
|
||||
"name": "📦 @notesnook/logger",
|
||||
"path": "../packages/logger"
|
||||
},
|
||||
{
|
||||
"name": "📦 @notesnook/streamable-fs",
|
||||
"path": "../packages/streamable-fs"
|
||||
},
|
||||
{
|
||||
"name": "📦 @notesnook/theme",
|
||||
"path": "../packages/theme"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"javascript.format.enable": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSaveMode": "modificationsIfAvailable",
|
||||
"eslint.packageManager": "npm",
|
||||
"eslint.run": "onSave",
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"eslint.execArgv": ["--cache"]
|
||||
}
|
||||
}
|
||||
13
.vscode/settings.json
vendored
Normal file
13
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"files.eol": "\n",
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"javascript.format.enable": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSaveMode": "modificationsIfAvailable",
|
||||
"eslint.packageManager": "npm",
|
||||
"eslint.run": "onSave",
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"eslint.execArgv": ["--cache"]
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import { zip } from "../utils/zip";
|
||||
import { saveAs } from "file-saver";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { sanitizeFilename } from "@notesnook/common";
|
||||
import Vault from "./vault";
|
||||
|
||||
export async function exportToPDF(
|
||||
title: string,
|
||||
@@ -54,30 +55,58 @@ export async function exportNotes(
|
||||
title: "Exporting notes",
|
||||
subtitle: "Please wait while your notes are exported.",
|
||||
action: async (report) => {
|
||||
if (format === "pdf") {
|
||||
const note = db.notes?.note(noteIds[0]);
|
||||
if (!note) return false;
|
||||
const html = await note.export("html");
|
||||
if (!html) return false;
|
||||
return await exportToPDF(note.title, html);
|
||||
}
|
||||
let vaultUnlocked = false;
|
||||
|
||||
if (noteIds.length === 1 && db.notes?.note(noteIds[0])?.data.locked) {
|
||||
vaultUnlocked = await Vault.unlockVault();
|
||||
if (!vaultUnlocked) return false;
|
||||
} else if (noteIds.length > 1 && (await db.vault?.exists()))
|
||||
vaultUnlocked = await Vault.unlockVault();
|
||||
|
||||
if (!vaultUnlocked)
|
||||
showToast(
|
||||
"error",
|
||||
"Failed to unlock vault. Locked notes will be skipped."
|
||||
);
|
||||
|
||||
const files = [];
|
||||
let index = 0;
|
||||
for (const noteId of noteIds) {
|
||||
const note = db.notes?.note(noteId);
|
||||
if (!note) continue;
|
||||
if (!vaultUnlocked && note.data.locked) continue;
|
||||
|
||||
report({
|
||||
current: ++index,
|
||||
total: noteIds.length,
|
||||
text: `Exporting "${note.title}"...`
|
||||
});
|
||||
|
||||
const content = await note.export(format).catch((e: Error) => {
|
||||
showToast("error", e.message);
|
||||
});
|
||||
if (!content) continue;
|
||||
files.push({ filename: note.title, content });
|
||||
const rawContent = await db.content?.raw(note.data.contentId);
|
||||
const content = note.data.locked
|
||||
? await db.vault?.decryptContent(rawContent)
|
||||
: rawContent;
|
||||
|
||||
const exported = await note
|
||||
.export(format === "pdf" ? "html" : format, content)
|
||||
.catch((e: Error) => {
|
||||
console.error(note.data, e);
|
||||
showToast(
|
||||
"error",
|
||||
`Failed to export note "${note.title}": ${e.message}`
|
||||
);
|
||||
});
|
||||
|
||||
if (typeof exported !== "string") {
|
||||
showToast("error", `Failed to export note "${note.title}"`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (format === "pdf") {
|
||||
return await exportToPDF(note.title, exported);
|
||||
}
|
||||
|
||||
files.push({ filename: note.title, content: exported });
|
||||
}
|
||||
|
||||
if (!files.length) return false;
|
||||
@@ -90,6 +119,7 @@ export async function exportNotes(
|
||||
const zipped = await zip(files, format);
|
||||
saveAs(new Blob([zipped.buffer]), "notes.zip");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -55,8 +55,12 @@ class Vault {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
static unlockVault() {
|
||||
return showPasswordDialog("lock_note", ({ password }) => {
|
||||
return showPasswordDialog("ask_vault_password", ({ password }) => {
|
||||
return db.vault
|
||||
.unlock(password)
|
||||
.then(() => true)
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
PDF,
|
||||
Markdown,
|
||||
HTML,
|
||||
Text as Plaintext,
|
||||
Readonly,
|
||||
StarOutline,
|
||||
AddReminder,
|
||||
@@ -331,7 +332,7 @@ const formats = [
|
||||
{
|
||||
type: "txt",
|
||||
title: "Text",
|
||||
icon: Text,
|
||||
icon: Plaintext,
|
||||
subtitle: "Can be opened in any plain-text editor."
|
||||
}
|
||||
];
|
||||
@@ -419,7 +420,6 @@ const menuItems = [
|
||||
title: "Print",
|
||||
disabled: ({ note }) => {
|
||||
if (!db.notes.note(note.id).synced()) return notFullySyncedText;
|
||||
if (note.locked) return "Locked notes cannot be printed.";
|
||||
},
|
||||
icon: Print,
|
||||
onClick: async ({ note }) => {
|
||||
@@ -448,7 +448,6 @@ const menuItems = [
|
||||
icon: Export,
|
||||
disabled: ({ note }) => {
|
||||
if (!db.notes.note(note.id).synced()) return notFullySyncedText;
|
||||
if (note.locked) return "Locked notes cannot be exported currently.";
|
||||
},
|
||||
items: formats.map((format) => ({
|
||||
key: format.type,
|
||||
|
||||
@@ -25,14 +25,16 @@ type File = { filename: string; content: string };
|
||||
async function zip(files: File[], format: string): Promise<Uint8Array> {
|
||||
const obj: Unzipped = Object.create(null);
|
||||
files.forEach((file) => {
|
||||
const name = sanitizeFilename(file.filename);
|
||||
const name = sanitizeFilename(file.filename, { replacement: "-" });
|
||||
let counter = 0;
|
||||
while (obj[makeFilename(name, format, counter)]) ++counter;
|
||||
|
||||
obj[makeFilename(name, format, counter)] = textEncoder.encode(file.content);
|
||||
});
|
||||
const { zipSync } = await import("fflate");
|
||||
return zipSync(obj);
|
||||
const { zip } = await import("fflate");
|
||||
return new Promise((resolve, reject) =>
|
||||
zip(obj, (err, data) => (err ? reject(err) : resolve(data)))
|
||||
);
|
||||
}
|
||||
export { zip };
|
||||
|
||||
|
||||
@@ -251,6 +251,14 @@ export default class Vault {
|
||||
password = this._password;
|
||||
}
|
||||
|
||||
if (encryptedContent.noteId && typeof encryptedContent.data !== "object") {
|
||||
await this._db.notes.add({
|
||||
id: encryptedContent.noteId,
|
||||
locked: false
|
||||
});
|
||||
return encryptedContent;
|
||||
}
|
||||
|
||||
let decryptedContent = await this._storage.decrypt(
|
||||
{ password },
|
||||
encryptedContent.data
|
||||
|
||||
@@ -331,7 +331,9 @@ export default class Attachments extends Collection {
|
||||
const { metadata, chunkSize } = attachment;
|
||||
const filename = metadata.hash;
|
||||
|
||||
sendAttachmentsProgressEvent("download", groupId, total, current);
|
||||
if (notify)
|
||||
sendAttachmentsProgressEvent("download", groupId, total, current);
|
||||
|
||||
const isDownloaded = await this._db.fs.downloadFile(
|
||||
groupId,
|
||||
filename,
|
||||
|
||||
@@ -56,7 +56,9 @@ export class Tiptap {
|
||||
formatters: {
|
||||
taskList: (elem, walk, builder, formatOptions) => {
|
||||
return formatList(elem, walk, builder, formatOptions, (elem) => {
|
||||
return elem.attribs.class.includes("checked") ? " ✅ " : " ☐ ";
|
||||
return elem.attribs.class && elem.attribs.class.includes("checked")
|
||||
? " ✅ "
|
||||
: " ☐ ";
|
||||
});
|
||||
},
|
||||
paragraph: (elem, walk, builder) => {
|
||||
@@ -108,6 +110,7 @@ export class Tiptap {
|
||||
}).parse(this.data);
|
||||
|
||||
const images = {};
|
||||
let hasImages = false;
|
||||
for (let i = 0; i < hashes.length; ++i) {
|
||||
const hash = hashes[i];
|
||||
const src = await getData(hash, {
|
||||
@@ -116,8 +119,10 @@ export class Tiptap {
|
||||
});
|
||||
if (!src) continue;
|
||||
images[hash] = src;
|
||||
hasImages = true;
|
||||
}
|
||||
|
||||
if (!hasImages) return this.data;
|
||||
return new HTMLRewriter({
|
||||
ontag: (name, attr) => {
|
||||
const hash = attr[ATTRIBUTES.hash];
|
||||
|
||||
@@ -30,7 +30,7 @@ export default class Note {
|
||||
/**
|
||||
*
|
||||
* @param {import('../api').default} db
|
||||
* @param {Object} note
|
||||
* @param {any} note
|
||||
*/
|
||||
constructor(note, db) {
|
||||
this._note = note;
|
||||
@@ -97,9 +97,11 @@ export default class Note {
|
||||
createdOn: formatDate(this.data.dateCreated),
|
||||
tags: this.tags.join(", ")
|
||||
};
|
||||
contentItem =
|
||||
contentItem || (await this._db.content.raw(this._note.contentId));
|
||||
if (!contentItem) return false;
|
||||
contentItem = contentItem ||
|
||||
(await this._db.content.raw(this._note.contentId)) || {
|
||||
type: "tiptap",
|
||||
data: "<p></p>"
|
||||
};
|
||||
const { data, type } = await this._db.content.downloadMedia(
|
||||
`export-${this.id}`,
|
||||
contentItem,
|
||||
|
||||
Reference in New Issue
Block a user