mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-22 06:29:29 +01:00
mobile: fix exporter and attachment download
This commit is contained in:
committed by
Abdullah Atta
parent
4e157455c8
commit
1f33ea9db3
@@ -215,13 +215,13 @@ export default async function downloadAttachment(
|
|||||||
await db
|
await db
|
||||||
.fs()
|
.fs()
|
||||||
.downloadFile(options.groupId || attachment.hash, attachment.hash);
|
.downloadFile(options.groupId || attachment.hash, attachment.hash);
|
||||||
if (!(await exists(attachment.metadata.hash))) {
|
if (!(await exists(attachment.hash))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.base64 || options.text) {
|
if (options.base64 || options.text) {
|
||||||
return await db.attachments.read(
|
return await db.attachments.read(
|
||||||
attachment.metadata.hash,
|
attachment.hash,
|
||||||
options.base64 ? "base64" : "text"
|
options.base64 ? "base64" : "text"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ type DialogInfo = {
|
|||||||
input: boolean;
|
input: boolean;
|
||||||
inputPlaceholder: string;
|
inputPlaceholder: string;
|
||||||
defaultValue: string;
|
defaultValue: string;
|
||||||
context: "global" | "local";
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
context: "global" | "local" | (string & {});
|
||||||
|
secureTextEntry?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function presentDialog(data: Partial<DialogInfo>): void {
|
export function presentDialog(data: Partial<DialogInfo>): void {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import { PressableButton } from "../../ui/pressable";
|
|||||||
import Seperator from "../../ui/seperator";
|
import Seperator from "../../ui/seperator";
|
||||||
import Heading from "../../ui/typography/heading";
|
import Heading from "../../ui/typography/heading";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
|
import { Dialog } from "../../dialog";
|
||||||
|
|
||||||
const ExportNotesSheet = ({
|
const ExportNotesSheet = ({
|
||||||
ids,
|
ids,
|
||||||
@@ -67,10 +68,12 @@ const ExportNotesSheet = ({
|
|||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
>();
|
>();
|
||||||
const [status, setStatus] = useState(null);
|
const [status, setStatus] = useState<string>();
|
||||||
const premium = useUserStore((state) => state.premium);
|
const premium = useUserStore((state) => state.premium);
|
||||||
|
|
||||||
const save = async (type: "pdf" | "txt" | "md" | "html") => {
|
const save = async (
|
||||||
|
type: "pdf" | "txt" | "md" | "html" | "md-frontmatter"
|
||||||
|
) => {
|
||||||
if (exporting) return;
|
if (exporting) return;
|
||||||
if (!PremiumService.get() && type !== "txt") return;
|
if (!PremiumService.get() && type !== "txt") return;
|
||||||
setExporting(true);
|
setExporting(true);
|
||||||
|
|||||||
@@ -329,7 +329,9 @@ export const useAppEvents = () => {
|
|||||||
if (Platform.OS === "android") {
|
if (Platform.OS === "android") {
|
||||||
try {
|
try {
|
||||||
await RNIap.flushFailedPurchasesCachedAsPendingAndroid();
|
await RNIap.flushFailedPurchasesCachedAsPendingAndroid();
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
refValues.current.subsriptionSuccessListener =
|
refValues.current.subsriptionSuccessListener =
|
||||||
RNIap.purchaseUpdatedListener(onSuccessfulSubscription);
|
RNIap.purchaseUpdatedListener(onSuccessfulSubscription);
|
||||||
@@ -648,11 +650,7 @@ export const useAppEvents = () => {
|
|||||||
if (!db.isInitialized) {
|
if (!db.isInitialized) {
|
||||||
RNBootSplash.hide({ fade: true });
|
RNBootSplash.hide({ fade: true });
|
||||||
DatabaseLogger.info("Initializing database");
|
DatabaseLogger.info("Initializing database");
|
||||||
try {
|
|
||||||
await db.init();
|
await db.init();
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (IsDatabaseMigrationRequired()) return;
|
if (IsDatabaseMigrationRequired()) return;
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
/* eslint-disable no-case-declarations */
|
/* eslint-disable no-case-declarations */
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
import Clipboard from "@react-native-clipboard/clipboard";
|
import { ItemReference } from "@notesnook/core/dist/types";
|
||||||
import type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
|
import type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
|
||||||
import { getDefaultPresets } from "@notesnook/editor/dist/toolbar/tool-definitions";
|
import { getDefaultPresets } from "@notesnook/editor/dist/toolbar/tool-definitions";
|
||||||
|
import Clipboard from "@react-native-clipboard/clipboard";
|
||||||
import { useCallback, useEffect, useRef } from "react";
|
import { useCallback, useEffect, useRef } from "react";
|
||||||
import {
|
import {
|
||||||
BackHandler,
|
BackHandler,
|
||||||
@@ -33,6 +34,7 @@ import {
|
|||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { WebViewMessageEvent } from "react-native-webview";
|
import { WebViewMessageEvent } from "react-native-webview";
|
||||||
import { db } from "../../../common/database";
|
import { db } from "../../../common/database";
|
||||||
|
import downloadAttachment from "../../../common/filesystem/download-attachment";
|
||||||
import ManageTagsSheet from "../../../components/sheets/manage-tags";
|
import ManageTagsSheet from "../../../components/sheets/manage-tags";
|
||||||
import { RelationsList } from "../../../components/sheets/relations-list";
|
import { RelationsList } from "../../../components/sheets/relations-list";
|
||||||
import ReminderSheet from "../../../components/sheets/reminder";
|
import ReminderSheet from "../../../components/sheets/reminder";
|
||||||
@@ -44,7 +46,9 @@ import {
|
|||||||
eUnSubscribeEvent
|
eUnSubscribeEvent
|
||||||
} from "../../../services/event-manager";
|
} from "../../../services/event-manager";
|
||||||
import Navigation from "../../../services/navigation";
|
import Navigation from "../../../services/navigation";
|
||||||
|
import SettingsService from "../../../services/settings";
|
||||||
import { useEditorStore } from "../../../stores/use-editor-store";
|
import { useEditorStore } from "../../../stores/use-editor-store";
|
||||||
|
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||||
import { useTagStore } from "../../../stores/use-tag-store";
|
import { useTagStore } from "../../../stores/use-tag-store";
|
||||||
import { useUserStore } from "../../../stores/use-user-store";
|
import { useUserStore } from "../../../stores/use-user-store";
|
||||||
@@ -63,11 +67,6 @@ import { useDragState } from "../../settings/editor/state";
|
|||||||
import { EventTypes } from "./editor-events";
|
import { EventTypes } from "./editor-events";
|
||||||
import { EditorMessage, EditorProps, useEditorType } from "./types";
|
import { EditorMessage, EditorProps, useEditorType } from "./types";
|
||||||
import { EditorEvents, editorState } from "./utils";
|
import { EditorEvents, editorState } from "./utils";
|
||||||
import { useNoteStore } from "../../../stores/use-notes-store";
|
|
||||||
import SettingsService from "../../../services/settings";
|
|
||||||
import downloadAttachment from "../../../common/filesystem/download-attachment";
|
|
||||||
import { ItemReference } from "@notesnook/core/dist/types";
|
|
||||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
|
||||||
|
|
||||||
const publishNote = async (editor: useEditorType) => {
|
const publishNote = async (editor: useEditorType) => {
|
||||||
const user = useUserStore.getState().user;
|
const user = useUserStore.getState().user;
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ export const Notebooks = ({
|
|||||||
data={notebooks}
|
data={notebooks}
|
||||||
dataType="notebook"
|
dataType="notebook"
|
||||||
renderedInRoute="Notebooks"
|
renderedInRoute="Notebooks"
|
||||||
|
loading={loading}
|
||||||
placeholder={{
|
placeholder={{
|
||||||
title: "Your notebooks",
|
title: "Your notebooks",
|
||||||
paragraph: "You have not added any notebooks yet.",
|
paragraph: "You have not added any notebooks yet.",
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import EventManager from "@notesnook/core/dist/utils/event-manager";
|
import EventManager, {
|
||||||
|
EventHandler
|
||||||
|
} from "@notesnook/core/dist/utils/event-manager";
|
||||||
import Clipboard from "@react-native-clipboard/clipboard";
|
import Clipboard from "@react-native-clipboard/clipboard";
|
||||||
import { RefObject } from "react";
|
import { RefObject } from "react";
|
||||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||||
@@ -60,14 +62,14 @@ const eventManager = new EventManager();
|
|||||||
|
|
||||||
export const eSubscribeEvent = <T = unknown>(
|
export const eSubscribeEvent = <T = unknown>(
|
||||||
eventName: string,
|
eventName: string,
|
||||||
action?: (data: T) => void
|
action: EventHandler
|
||||||
) => {
|
) => {
|
||||||
return eventManager.subscribe(eventName, action);
|
return eventManager.subscribe(eventName, action);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const eUnSubscribeEvent = <T = unknown>(
|
export const eUnSubscribeEvent = <T = unknown>(
|
||||||
eventName: string,
|
eventName: string,
|
||||||
action?: (data: T) => void
|
action: EventHandler
|
||||||
) => {
|
) => {
|
||||||
eventManager.unsubscribe(eventName, action);
|
eventManager.unsubscribe(eventName, action);
|
||||||
};
|
};
|
||||||
@@ -116,6 +118,7 @@ export type PresentSheetOptions = {
|
|||||||
learnMorePress: () => void;
|
learnMorePress: () => void;
|
||||||
enableGesturesInScrollView?: boolean;
|
enableGesturesInScrollView?: boolean;
|
||||||
noBottomPadding?: boolean;
|
noBottomPadding?: boolean;
|
||||||
|
keyboardHandlerDisabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function presentSheet(data: Partial<PresentSheetOptions>) {
|
export function presentSheet(data: Partial<PresentSheetOptions>) {
|
||||||
|
|||||||
@@ -27,62 +27,57 @@ import { DatabaseLogger, db } from "../common/database/index";
|
|||||||
import Storage from "../common/database/storage";
|
import Storage from "../common/database/storage";
|
||||||
|
|
||||||
import { sanitizeFilename } from "@notesnook/common";
|
import { sanitizeFilename } from "@notesnook/common";
|
||||||
|
import { Note } from "@notesnook/core";
|
||||||
|
import { NoteContent } from "@notesnook/core/dist/collections/session-content";
|
||||||
import { presentDialog } from "../components/dialog/functions";
|
import { presentDialog } from "../components/dialog/functions";
|
||||||
import { useSettingStore } from "../stores/use-setting-store";
|
import { useSettingStore } from "../stores/use-setting-store";
|
||||||
import BiometicService from "./biometrics";
|
import BiometicService from "./biometrics";
|
||||||
import { ToastEvent } from "./event-manager";
|
import { ToastManager } from "./event-manager";
|
||||||
|
|
||||||
const MIMETypes = {
|
const MIMETypes = {
|
||||||
txt: "text/plain",
|
txt: "text/plain",
|
||||||
pdf: "application/pdf",
|
pdf: "application/pdf",
|
||||||
md: "text/markdown",
|
md: "text/markdown",
|
||||||
|
"md-frontmatter": "text/markdown",
|
||||||
html: "text/html"
|
html: "text/html"
|
||||||
};
|
};
|
||||||
|
|
||||||
const FolderNames = {
|
const FolderNames: { [name: string]: string } = {
|
||||||
txt: "Text",
|
txt: "Text",
|
||||||
pdf: "PDF",
|
pdf: "PDF",
|
||||||
md: "Markdown",
|
md: "Markdown",
|
||||||
html: "Html"
|
html: "Html"
|
||||||
};
|
};
|
||||||
|
|
||||||
async function releasePermissions(path) {
|
async function releasePermissions(path: string) {
|
||||||
if (Platform.OS === "ios") return;
|
if (Platform.OS === "ios") return;
|
||||||
const uris = await ScopedStorage.getPersistedUriPermissions();
|
const uris = await ScopedStorage.getPersistedUriPermissions();
|
||||||
for (let uri of uris) {
|
for (const uri of uris) {
|
||||||
if (path.startsWith(uri)) {
|
if (path.startsWith(uri)) {
|
||||||
await ScopedStorage.releasePersistableUriPermission(uri);
|
await ScopedStorage.releasePersistableUriPermission(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function getPath(type: string) {
|
||||||
*
|
|
||||||
* @param {"Text" | "PDF" | "Markdown" | "Html" } type
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async function getPath(type) {
|
|
||||||
let path =
|
let path =
|
||||||
Platform.OS === "ios" &&
|
Platform.OS === "ios" &&
|
||||||
(await Storage.checkAndCreateDir(`/exported/${type}/`));
|
(await Storage.checkAndCreateDir(`/exported/${type}/`));
|
||||||
|
|
||||||
if (Platform.OS === "android") {
|
if (Platform.OS === "android") {
|
||||||
let file = await ScopedStorage.openDocumentTree(true);
|
const file = await ScopedStorage.openDocumentTree(true);
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
path = file.uri;
|
path = file.uri;
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function save(
|
||||||
*
|
path: string,
|
||||||
* @param {string} path
|
data: string,
|
||||||
* @param {string} data
|
fileName: string,
|
||||||
* @param {string} title
|
extension: "txt" | "pdf" | "md" | "html" | "md-frontmatter"
|
||||||
* @param {"txt" | "pdf" | "md" | "html"} extension
|
) {
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async function save(path, data, fileName, extension) {
|
|
||||||
let uri;
|
let uri;
|
||||||
if (Platform.OS === "android") {
|
if (Platform.OS === "android") {
|
||||||
uri = await ScopedStorage.writeFile(
|
uri = await ScopedStorage.writeFile(
|
||||||
@@ -101,21 +96,25 @@ async function save(path, data, fileName, extension) {
|
|||||||
return uri || path;
|
return uri || path;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function makeHtml(note) {
|
async function makeHtml(note: Note, content?: NoteContent<false>) {
|
||||||
let html = await db.notes.export(note.id, {
|
let html = await db.notes.export(note.id, {
|
||||||
format: "html"
|
format: "html",
|
||||||
|
contentItem: content
|
||||||
});
|
});
|
||||||
|
if (!html) return "";
|
||||||
|
|
||||||
html = decode(html, {
|
html = decode(html, {
|
||||||
level: EntityLevel.HTML
|
level: EntityLevel.HTML
|
||||||
});
|
});
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function exportAs(
|
||||||
*
|
type: string,
|
||||||
* @param {"txt" | "pdf" | "md" | "html" | "md-frontmatter"} type
|
note: Note,
|
||||||
*/
|
bulk?: boolean,
|
||||||
async function exportAs(type, note, bulk, content) {
|
content?: NoteContent<false>
|
||||||
|
) {
|
||||||
let data;
|
let data;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "html":
|
case "html":
|
||||||
@@ -125,22 +124,24 @@ async function exportAs(type, note, bulk, content) {
|
|||||||
break;
|
break;
|
||||||
case "md":
|
case "md":
|
||||||
data = await db.notes.export(note.id, {
|
data = await db.notes.export(note.id, {
|
||||||
format: "md"
|
format: "md",
|
||||||
|
contentItem: content
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "md-frontmatter":
|
case "md-frontmatter":
|
||||||
data = await db.notes
|
data = await db.notes.export(note.id, {
|
||||||
.note(note.id)
|
format: "md-frontmatter",
|
||||||
.export("md-frontmatter", content?.data);
|
contentItem: content
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case "pdf":
|
case "pdf":
|
||||||
{
|
{
|
||||||
let html = await makeHtml(note, content);
|
const html = await makeHtml(note, content);
|
||||||
let fileName = sanitizeFilename(note.title + Date.now(), {
|
const fileName = sanitizeFilename(note.title + Date.now(), {
|
||||||
replacement: "_"
|
replacement: "_"
|
||||||
});
|
});
|
||||||
|
|
||||||
let options = {
|
const options = {
|
||||||
html: html,
|
html: html,
|
||||||
fileName:
|
fileName:
|
||||||
Platform.OS === "ios" ? "/exported/PDF/" + fileName : fileName,
|
Platform.OS === "ios" ? "/exported/PDF/" + fileName : fileName,
|
||||||
@@ -149,11 +150,12 @@ async function exportAs(type, note, bulk, content) {
|
|||||||
bgColor: "#FFFFFF",
|
bgColor: "#FFFFFF",
|
||||||
padding: 30,
|
padding: 30,
|
||||||
base64: bulk || Platform.OS === "android"
|
base64: bulk || Platform.OS === "android"
|
||||||
};
|
} as { [name: string]: any };
|
||||||
|
|
||||||
if (Platform.OS === "ios") {
|
if (Platform.OS === "ios") {
|
||||||
options.directory = "Documents";
|
options.directory = "Documents";
|
||||||
}
|
}
|
||||||
let res = await RNHTMLtoPDF.convert(options);
|
const res = await RNHTMLtoPDF.convert(options);
|
||||||
data = !bulk && Platform.OS === "ios" ? res.filePath : res.base64;
|
data = !bulk && Platform.OS === "ios" ? res.filePath : res.base64;
|
||||||
if (bulk && res.filePath) {
|
if (bulk && res.filePath) {
|
||||||
RNFetchBlob.fs.unlink(res.filePath);
|
RNFetchBlob.fs.unlink(res.filePath);
|
||||||
@@ -162,7 +164,10 @@ async function exportAs(type, note, bulk, content) {
|
|||||||
break;
|
break;
|
||||||
case "txt":
|
case "txt":
|
||||||
{
|
{
|
||||||
data = await db.notes?.note(note.id).export("txt", content);
|
data = await db.notes.export(note.id, {
|
||||||
|
format: "txt",
|
||||||
|
contentItem: content
|
||||||
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -171,10 +176,10 @@ async function exportAs(type, note, bulk, content) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function unlockVault() {
|
async function unlockVault() {
|
||||||
let biometry = await BiometicService.isBiometryAvailable();
|
const biometry = await BiometicService.isBiometryAvailable();
|
||||||
let fingerprint = await BiometicService.hasInternetCredentials("nn_vault");
|
const fingerprint = await BiometicService.hasInternetCredentials();
|
||||||
if (biometry && fingerprint) {
|
if (biometry && fingerprint) {
|
||||||
let credentials = await BiometicService.getCredentials(
|
const credentials = await BiometicService.getCredentials(
|
||||||
"Unlock vault",
|
"Unlock vault",
|
||||||
"Unlock vault to export locked notes"
|
"Unlock vault to export locked notes"
|
||||||
);
|
);
|
||||||
@@ -196,7 +201,7 @@ async function unlockVault() {
|
|||||||
positivePress: async (value) => {
|
positivePress: async (value) => {
|
||||||
const unlocked = await db.vault.unlock(value);
|
const unlocked = await db.vault.unlock(value);
|
||||||
if (!unlocked) {
|
if (!unlocked) {
|
||||||
ToastEvent.show({
|
ToastManager.show({
|
||||||
heading: "Invalid password",
|
heading: "Invalid password",
|
||||||
message: "Please enter a valid password",
|
message: "Please enter a valid password",
|
||||||
type: "error",
|
type: "error",
|
||||||
@@ -217,11 +222,10 @@ async function unlockVault() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function exportNote(
|
||||||
*
|
id: string,
|
||||||
* @param {"txt" | "pdf" | "md" | "html" | "md-frontmatter"} type
|
type: "txt" | "pdf" | "md" | "html" | "md-frontmatter"
|
||||||
*/
|
) {
|
||||||
async function exportNote(id, type) {
|
|
||||||
const note = await db.notes.note(id);
|
const note = await db.notes.note(id);
|
||||||
if (!note) return;
|
if (!note) return;
|
||||||
|
|
||||||
@@ -229,21 +233,21 @@ async function exportNote(id, type) {
|
|||||||
|
|
||||||
if (note.locked) {
|
if (note.locked) {
|
||||||
try {
|
try {
|
||||||
let unlocked = await unlockVault();
|
const unlocked = await unlockVault();
|
||||||
if (!unlocked) return null;
|
if (!unlocked) return null;
|
||||||
const unlockedNote = await db.vault.open(note.id);
|
const unlockedNote = await db.vault.open(note.id);
|
||||||
content = unlockedNote.content;
|
content = unlockedNote?.content;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e as Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = await getPath(FolderNames[type]);
|
let path = await getPath(FolderNames[type]);
|
||||||
if (!path) return;
|
if (!path) return;
|
||||||
|
|
||||||
let result = await exportAs(type, note, false, content);
|
const result = await exportAs(type, note, false, content);
|
||||||
if (!result) return null;
|
if (!result) return null;
|
||||||
let fileName = sanitizeFilename(note.title + Date.now(), {
|
const fileName = sanitizeFilename(note.title + Date.now(), {
|
||||||
replacement: "_"
|
replacement: "_"
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -261,32 +265,36 @@ async function exportNote(id, type) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyFileAsync(source, dest) {
|
function copyFileAsync(source: string, dest: string) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
ScopedStorage.copyFile(source, dest, () => {
|
ScopedStorage.copyFile(source, dest, () => {
|
||||||
resolve();
|
resolve(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUniqueFileName(fileName, results) {
|
function getUniqueFileName(
|
||||||
|
fileName: string,
|
||||||
|
notebookPath: string,
|
||||||
|
results: { [name: string]: boolean }
|
||||||
|
) {
|
||||||
const chunks = fileName.split(".");
|
const chunks = fileName.split(".");
|
||||||
const ext = chunks.pop();
|
const ext = chunks.pop();
|
||||||
const name = chunks.join(".");
|
const name = chunks.join(".");
|
||||||
let resolvedName = fileName;
|
let resolvedName = fileName;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
while (results[resolvedName]) {
|
while (results[`${notebookPath}${resolvedName}`]) {
|
||||||
resolvedName = `${name}${++count}.${ext}`;
|
resolvedName = `${name}${++count}.${ext}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolvedName;
|
return resolvedName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function bulkExport(
|
||||||
*
|
ids: string[],
|
||||||
* @param {"txt" | "pdf" | "md" | "html" | "md-frontmatter"} type
|
type: "txt" | "pdf" | "md" | "html" | "md-frontmatter",
|
||||||
*/
|
callback: (progress?: string) => void
|
||||||
async function bulkExport(ids, type, callback) {
|
) {
|
||||||
let path = await getPath(FolderNames[type]);
|
let path = await getPath(FolderNames[type]);
|
||||||
if (!path) return;
|
if (!path) return;
|
||||||
|
|
||||||
@@ -295,14 +303,14 @@ async function bulkExport(ids, type, callback) {
|
|||||||
|
|
||||||
await RNFetchBlob.fs.mkdir(exportCacheFolder).catch((e) => console.log(e));
|
await RNFetchBlob.fs.mkdir(exportCacheFolder).catch((e) => console.log(e));
|
||||||
|
|
||||||
const mkdir = async (dir) => {
|
const mkdir = async (dir: string) => {
|
||||||
const folder = `${exportCacheFolder}/${dir}`;
|
const folder = `${exportCacheFolder}/${dir}`;
|
||||||
if (!(await RNFetchBlob.fs.exists(folder))) {
|
if (!(await RNFetchBlob.fs.exists(folder))) {
|
||||||
await RNFetchBlob.fs.mkdir(folder);
|
await RNFetchBlob.fs.mkdir(folder);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const writeFile = async (path, result) => {
|
const writeFile = async (path: string, result: string) => {
|
||||||
const cacheFilePath = exportCacheFolder + path;
|
const cacheFilePath = exportCacheFolder + path;
|
||||||
await RNFetchBlob.fs.writeFile(
|
await RNFetchBlob.fs.writeFile(
|
||||||
cacheFilePath,
|
cacheFilePath,
|
||||||
@@ -311,94 +319,69 @@ async function bulkExport(ids, type, callback) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const results = {};
|
const results: { [name: string]: boolean } = {};
|
||||||
for (var i = 0; i < ids.length; i++) {
|
for (let i = 0; i < ids.length; i++) {
|
||||||
try {
|
try {
|
||||||
let note = await db.notes.note(ids[i]);
|
const note = await db.notes.note(ids[i]);
|
||||||
if (!note) continue;
|
if (!note) continue;
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
if (note.locked) {
|
if (note.locked) {
|
||||||
try {
|
try {
|
||||||
let unlocked = !db.vault.unlocked ? await unlockVault() : true;
|
const unlocked = !db.vault.unlocked ? await unlockVault() : true;
|
||||||
if (!unlocked) {
|
if (!unlocked) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const unlockedNote = await db.vault.open(note.id);
|
const unlockedNote = await db.vault.open(note.id);
|
||||||
content = unlockedNote.content;
|
content = unlockedNote?.content;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e as Error);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = await exportAs(type, note, true, content);
|
const result = await exportAs(type, note, true, content);
|
||||||
let fileName = sanitizeFilename(note.title, {
|
const fileName = sanitizeFilename(note.title, {
|
||||||
replacement: "_"
|
replacement: "_"
|
||||||
});
|
});
|
||||||
if (result) {
|
if (result) {
|
||||||
const notebooks = [
|
const notebooks = await db.relations
|
||||||
...(db.relations
|
|
||||||
?.to({ id: note.id, type: "note" }, "notebook")
|
?.to({ id: note.id, type: "note" }, "notebook")
|
||||||
.map((notebook) => ({
|
.resolve();
|
||||||
title: notebook.title
|
|
||||||
})) || []),
|
|
||||||
...(note.notebooks || []).map((ref) => {
|
|
||||||
const notebook = db.notebooks?.notebook(ref.id);
|
|
||||||
const topics = notebook?.topics.all || [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: notebook?.title,
|
|
||||||
topics: ref.topics
|
|
||||||
.map((topicId) => topics.find((topic) => topic.id === topicId))
|
|
||||||
.filter(Boolean)
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const notebook of notebooks) {
|
for (const notebook of notebooks) {
|
||||||
results[notebook.title] = results[notebook.title] || {};
|
const notebookPath = (await db.notebooks.breadcrumbs(notebook.id))
|
||||||
await mkdir(notebook.title);
|
.map((notebook) => {
|
||||||
|
return notebook.title + "/";
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
if (notebook.topics && notebook.topics.length) {
|
await mkdir(notebookPath);
|
||||||
for (const topic of notebook.topics) {
|
|
||||||
results[notebook.title][topic.title] =
|
console.log("Dir created", notebookPath);
|
||||||
results[notebook.title][topic.title] || {};
|
|
||||||
|
|
||||||
await mkdir(`${notebook.title}/${topic.title}`);
|
|
||||||
const exportedNoteName = getUniqueFileName(
|
const exportedNoteName = getUniqueFileName(
|
||||||
fileName + `.${type}`,
|
fileName + `.${type}`,
|
||||||
results[notebook.title][topic.title]
|
notebookPath,
|
||||||
|
results
|
||||||
);
|
);
|
||||||
results[notebook.title][topic.title][exportedNoteName] = true;
|
results[`${notebookPath}${exportedNoteName}`] = true;
|
||||||
|
await writeFile(`/${notebookPath}${exportedNoteName}`, result);
|
||||||
writeFile(
|
|
||||||
`/${notebook.title}/${topic.title}/${exportedNoteName}`,
|
|
||||||
result
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const exportedNoteName = getUniqueFileName(
|
|
||||||
fileName + `.${type}`,
|
|
||||||
results[notebook.title]
|
|
||||||
);
|
|
||||||
results[notebook.title][exportedNoteName] = true;
|
|
||||||
writeFile(`/${notebook.title}/${exportedNoteName}`, result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!notebooks.length) {
|
if (!notebooks.length) {
|
||||||
const exportedNoteName = getUniqueFileName(
|
const exportedNoteName = getUniqueFileName(
|
||||||
fileName + `.${type}`,
|
fileName + `.${type}`,
|
||||||
|
"",
|
||||||
results
|
results
|
||||||
);
|
);
|
||||||
results[exportedNoteName] = true;
|
results[exportedNoteName] = true;
|
||||||
writeFile(`/${exportedNoteName}`, result);
|
await writeFile(`/${exportedNoteName}`, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callback(`${i + 1}/${ids.length}`);
|
callback(`${i + 1}/${ids.length}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e as Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const fileName = `nn-export-${ids.length}-${type}-${Date.now()}.zip`;
|
const fileName = `nn-export-${ids.length}-${type}-${Date.now()}.zip`;
|
||||||
@@ -424,7 +407,7 @@ async function bulkExport(ids, type, callback) {
|
|||||||
}
|
}
|
||||||
RNFetchBlob.fs.unlink(exportCacheFolder);
|
RNFetchBlob.fs.unlink(exportCacheFolder);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e as Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -183,13 +183,16 @@ export default class Vault {
|
|||||||
/**
|
/**
|
||||||
* Temporarily unlock (open) a note
|
* Temporarily unlock (open) a note
|
||||||
*/
|
*/
|
||||||
async open(noteId: string, password: string) {
|
async open(noteId: string, password?: string) {
|
||||||
const note = await this.db.notes.note(noteId);
|
const note = await this.db.notes.note(noteId);
|
||||||
if (!note) return;
|
if (!note) return;
|
||||||
|
|
||||||
const unlockedNote = await this.unlockNote(note, password, false);
|
const unlockedNote = await this.unlockNote(note, password, false);
|
||||||
|
if (password) {
|
||||||
this.password = password;
|
this.password = password;
|
||||||
if (!(await this.exists())) await this.create(password);
|
if (!(await this.exists())) await this.create(password);
|
||||||
|
}
|
||||||
|
|
||||||
return unlockedNote;
|
return unlockedNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +342,7 @@ export default class Vault {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async unlockNote(note: Note, password: string, perm = false) {
|
private async unlockNote(note: Note, password?: string, perm = false) {
|
||||||
if (!note.contentId) return;
|
if (!note.contentId) return;
|
||||||
|
|
||||||
const encryptedContent = await this.db.content.get(note.contentId);
|
const encryptedContent = await this.db.content.get(note.contentId);
|
||||||
|
|||||||
Reference in New Issue
Block a user