Compare commits

...

9 Commits

Author SHA1 Message Date
Abdullah Atta
f39cbad2f1 web: add support for exporting locked notes 2023-06-26 12:33:49 +05:00
Abdullah Atta
5f71d1d87b core: only rewrite html if images are found 2023-06-26 12:32:50 +05:00
Abdullah Atta
9d55b179a9 core: do not send download event if notify is false 2023-06-26 12:32:27 +05:00
Abdullah Atta
c178b6e5ec web: use async zip for faster zipping 2023-06-26 12:31:56 +05:00
Abdullah Atta
1a716f2f6f core: fix crash when exporting note with task list 2023-06-26 12:31:34 +05:00
Abdullah Atta
64e166c11c core: export with empty content if no content found 2023-06-26 12:30:56 +05:00
Abdullah Atta
ac283a9fd3 config: force unix style line endings 2023-06-26 12:30:26 +05:00
Abdullah Atta
57131e00c3 core: autofix issue where locked note is not encrypted 2023-06-26 12:26:21 +05:00
Abdullah Atta
d0563a0d93 config: set default eol sequence to LF 2023-06-26 12:25:33 +05:00
12 changed files with 93 additions and 98 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto eol=lf

View File

@@ -5,5 +5,6 @@ module.exports = {
printWidth: 80,
useTabs: false,
tabWidth: 2,
bracketSpacing: true
bracketSpacing: true,
endOfLine: "lf"
};

View File

@@ -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
View 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"]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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