mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-14 18:57:50 +01:00
web: fix many type errors
This commit is contained in:
@@ -15,6 +15,8 @@ apps/mobile/e2e/
|
||||
|
||||
# web
|
||||
apps/web/public/an.js
|
||||
apps/web/src/common/sqlite/wa-sqlite-async.js
|
||||
apps/web/src/common/sqlite/wa-sqlite.js
|
||||
|
||||
# editor
|
||||
packages/editor/styles/
|
||||
|
||||
@@ -81,7 +81,7 @@ export class AppModel {
|
||||
|
||||
async goToTags() {
|
||||
await this.navigateTo("Tags");
|
||||
return new ItemsViewModel(this.page, "tags");
|
||||
return new ItemsViewModel(this.page);
|
||||
}
|
||||
|
||||
async goToColor(color: string) {
|
||||
|
||||
@@ -18,19 +18,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { lazify } from "../utils/lazify";
|
||||
import { Attachment } from "@notesnook/core";
|
||||
import { db } from "./db";
|
||||
|
||||
async function download(hash: string, groupId?: string) {
|
||||
const attachment = db.attachments.attachment(hash);
|
||||
const attachment = await db.attachments.attachment(hash);
|
||||
if (!attachment) return;
|
||||
const downloadResult = await db
|
||||
.fs()
|
||||
.downloadFile(
|
||||
groupId || attachment.metadata.hash,
|
||||
attachment.metadata.hash,
|
||||
attachment.chunkSize,
|
||||
attachment.metadata
|
||||
groupId || attachment.hash,
|
||||
attachment.hash,
|
||||
attachment.chunkSize
|
||||
);
|
||||
if (!downloadResult) throw new Error("Failed to download file.");
|
||||
|
||||
@@ -46,7 +44,7 @@ export async function saveAttachment(hash: string) {
|
||||
|
||||
const { attachment, key } = response;
|
||||
await lazify(import("../interfaces/fs"), ({ saveFile }) =>
|
||||
saveFile(attachment.metadata.hash, {
|
||||
saveFile(attachment.hash, {
|
||||
key,
|
||||
iv: attachment.iv,
|
||||
name: attachment.filename,
|
||||
@@ -77,7 +75,7 @@ export async function downloadAttachment<
|
||||
return (await db.attachments.read(hash, type)) as TOutputType;
|
||||
|
||||
const blob = await lazify(import("../interfaces/fs"), ({ decryptFile }) =>
|
||||
decryptFile(attachment.metadata.hash, {
|
||||
decryptFile(attachment.hash, {
|
||||
key,
|
||||
iv: attachment.iv,
|
||||
name: attachment.filename,
|
||||
|
||||
@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Dialogs } from "../dialogs";
|
||||
import { store as notebookStore } from "../stores/notebook-store";
|
||||
import { store as tagStore } from "../stores/tag-store";
|
||||
import { store as appStore } from "../stores/app-store";
|
||||
import { store as editorStore } from "../stores/editor-store";
|
||||
@@ -26,7 +25,6 @@ import { store as noteStore } from "../stores/note-store";
|
||||
import { db } from "./db";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { Text } from "@theme-ui/components";
|
||||
import { Topic as TopicIcon } from "../components/icons";
|
||||
import Config from "../utils/config";
|
||||
import { AppVersion, getChangelog } from "../utils/version";
|
||||
import { Period } from "../dialogs/buy-dialog/types";
|
||||
@@ -36,7 +34,7 @@ import { ConfirmDialogProps } from "../dialogs/confirm";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { downloadUpdate } from "../utils/updater";
|
||||
import { ThemeMetadata } from "@notesnook/themes-server";
|
||||
import { Reminder } from "@notesnook/core";
|
||||
import { Color, Reminder, Tag } from "@notesnook/core";
|
||||
import { AuthenticatorType } from "@notesnook/core/dist/api/user-manager";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
@@ -405,21 +403,25 @@ export function showPasswordDialog(
|
||||
password?: string;
|
||||
oldPassword?: string;
|
||||
newPassword?: string;
|
||||
deleteAllLockedNotes?: boolean;
|
||||
}) => boolean | Promise<boolean>
|
||||
) {
|
||||
const { title, subtitle, positiveButtonText, checks } = getDialogData(type);
|
||||
return showDialog("PasswordDialog", (Dialog, perform) => (
|
||||
<Dialog
|
||||
type={type}
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
checks={checks}
|
||||
positiveButtonText={positiveButtonText}
|
||||
validate={validate}
|
||||
onClose={() => perform(false)}
|
||||
onDone={() => perform(true)}
|
||||
/>
|
||||
));
|
||||
return showDialog<"PasswordDialog", boolean>(
|
||||
"PasswordDialog",
|
||||
(Dialog, perform) => (
|
||||
<Dialog
|
||||
type={type}
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
checks={checks}
|
||||
positiveButtonText={positiveButtonText}
|
||||
validate={validate}
|
||||
onClose={() => perform(false)}
|
||||
onDone={() => perform(true)}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function showBackupPasswordDialog(
|
||||
@@ -462,9 +464,7 @@ export function showCreateTagDialog() {
|
||||
));
|
||||
}
|
||||
|
||||
export function showEditTagDialog(tagId: string) {
|
||||
const tag = db.tags.tag(tagId);
|
||||
if (!tag) return;
|
||||
export function showEditTagDialog(tag: Tag) {
|
||||
return showDialog("ItemDialog", (Dialog, perform) => (
|
||||
<Dialog
|
||||
title={"Edit tag"}
|
||||
@@ -474,7 +474,7 @@ export function showEditTagDialog(tagId: string) {
|
||||
onClose={() => perform(false)}
|
||||
onAction={async (title: string) => {
|
||||
if (!title) return;
|
||||
await db.tags.add({ id: tagId, title });
|
||||
await db.tags.add({ id: tag.id, title });
|
||||
showToast("success", "Tag edited!");
|
||||
tagStore.refresh();
|
||||
editorStore.refreshTags();
|
||||
@@ -486,9 +486,7 @@ export function showEditTagDialog(tagId: string) {
|
||||
));
|
||||
}
|
||||
|
||||
export function showRenameColorDialog(colorId: string) {
|
||||
const color = db.colors.color(colorId);
|
||||
if (!color) return;
|
||||
export function showRenameColorDialog(color: Color) {
|
||||
return showDialog("ItemDialog", (Dialog, perform) => (
|
||||
<Dialog
|
||||
title={"Rename color"}
|
||||
@@ -498,7 +496,7 @@ export function showRenameColorDialog(colorId: string) {
|
||||
onClose={() => perform(false)}
|
||||
onAction={async (title: string) => {
|
||||
if (!title) return;
|
||||
await db.tags.add({ id: colorId, title });
|
||||
await db.tags.add({ id: color.id, title });
|
||||
showToast("success", "Color renamed!");
|
||||
appStore.refreshNavItems();
|
||||
perform(true);
|
||||
|
||||
@@ -41,6 +41,8 @@ import { createWritableStream } from "./desktop-bridge";
|
||||
import { createZipStream } from "../utils/streams/zip-stream";
|
||||
import { FeatureKeys } from "../dialogs/feature-dialog";
|
||||
import { ZipEntry, createUnzipIterator } from "../utils/streams/unzip-stream";
|
||||
import { User } from "@notesnook/core/dist/api/user-manager";
|
||||
import { LegacyBackupFile } from "@notesnook/core";
|
||||
|
||||
export const CREATE_BUTTON_MAP = {
|
||||
notes: {
|
||||
@@ -52,10 +54,6 @@ export const CREATE_BUTTON_MAP = {
|
||||
title: "Create a notebook",
|
||||
onClick: () => hashNavigate("/notebooks/create", { replace: true })
|
||||
},
|
||||
topics: {
|
||||
title: "Create a topic",
|
||||
onClick: () => hashNavigate(`/topics/create`, { replace: true })
|
||||
},
|
||||
tags: {
|
||||
title: "Create a tag",
|
||||
onClick: () => hashNavigate(`/tags/create`, { replace: true })
|
||||
@@ -227,7 +225,7 @@ export async function restoreBackupFile(backupFile: File) {
|
||||
}
|
||||
|
||||
async function restoreWithProgress(
|
||||
backup: Record<string, unknown>,
|
||||
backup: LegacyBackupFile,
|
||||
password?: string,
|
||||
key?: string
|
||||
) {
|
||||
@@ -263,8 +261,8 @@ async function restoreWithProgress(
|
||||
|
||||
export async function verifyAccount() {
|
||||
if (!(await db.user?.getUser())) return true;
|
||||
return showPasswordDialog("verify_account", ({ password }) => {
|
||||
return db.user?.verifyPassword(password) || false;
|
||||
return showPasswordDialog("verify_account", async ({ password }) => {
|
||||
return !!password && (await db.user?.verifyPassword(password));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -297,7 +295,7 @@ export async function showUpgradeReminderDialogs() {
|
||||
}
|
||||
|
||||
async function restore(
|
||||
backup: Record<string, unknown>,
|
||||
backup: LegacyBackupFile,
|
||||
password?: string,
|
||||
key?: string
|
||||
) {
|
||||
|
||||
@@ -27,7 +27,6 @@ import { showToast } from "../utils/toast";
|
||||
import Vault from "./vault";
|
||||
import { TaskManager } from "./task-manager";
|
||||
import { pluralize } from "@notesnook/common";
|
||||
import { Reminder } from "@notesnook/core";
|
||||
|
||||
async function moveNotesToTrash(ids: string[], confirm = true) {
|
||||
if (confirm && !(await showMultiDeleteConfirmation(ids.length))) return;
|
||||
@@ -103,10 +102,10 @@ async function deleteAttachments(ids: string[]) {
|
||||
showToast("success", `${pluralize(ids.length, "attachment")} deleted`);
|
||||
}
|
||||
|
||||
async function moveRemindersToTrash(reminders: Reminder[]) {
|
||||
const isMultiselect = reminders.length > 1;
|
||||
async function moveRemindersToTrash(ids: string[]) {
|
||||
const isMultiselect = ids.length > 1;
|
||||
if (isMultiselect) {
|
||||
if (!(await showMultiDeleteConfirmation(reminders.length))) return;
|
||||
if (!(await showMultiDeleteConfirmation(ids.length))) return;
|
||||
}
|
||||
|
||||
await TaskManager.startTask({
|
||||
@@ -114,13 +113,13 @@ async function moveRemindersToTrash(reminders: Reminder[]) {
|
||||
id: "deleteReminders",
|
||||
action: async (report) => {
|
||||
report({
|
||||
text: `Deleting ${pluralize(reminders.length, "reminder")}...`
|
||||
text: `Deleting ${pluralize(ids.length, "reminder")}...`
|
||||
});
|
||||
await reminderStore.delete(...reminders.map((i) => i.id));
|
||||
await reminderStore.delete(...ids);
|
||||
}
|
||||
});
|
||||
|
||||
showToast("success", `${pluralize(reminders.length, "reminder")} deleted.`);
|
||||
showToast("success", `${pluralize(ids.length, "reminder")} deleted.`);
|
||||
}
|
||||
|
||||
export const Multiselect = {
|
||||
|
||||
@@ -288,10 +288,11 @@ export function Factory(Module) {
|
||||
return sqlite3.column_blob(stmt, iCol);
|
||||
case SQLite.SQLITE_FLOAT:
|
||||
return sqlite3.column_double(stmt, iCol);
|
||||
case SQLite.SQLITE_INTEGER:
|
||||
case SQLite.SQLITE_INTEGER: {
|
||||
const lo32 = sqlite3.column_int(stmt, iCol);
|
||||
const hi32 = Module.getTempRet0();
|
||||
return cvt32x2AsSafe(lo32, hi32);
|
||||
}
|
||||
case SQLite.SQLITE_NULL:
|
||||
return null;
|
||||
case SQLite.SQLITE_TEXT:
|
||||
@@ -813,10 +814,11 @@ export function Factory(Module) {
|
||||
return sqlite3.value_blob(pValue);
|
||||
case SQLite.SQLITE_FLOAT:
|
||||
return sqlite3.value_double(pValue);
|
||||
case SQLite.SQLITE_INTEGER:
|
||||
case SQLite.SQLITE_INTEGER: {
|
||||
const lo32 = sqlite3.value_int(pValue);
|
||||
const hi32 = Module.getTempRet0();
|
||||
return cvt32x2AsSafe(lo32, hi32);
|
||||
}
|
||||
case SQLite.SQLITE_NULL:
|
||||
return null;
|
||||
case SQLite.SQLITE_TEXT:
|
||||
|
||||
@@ -26,6 +26,7 @@ class Vault {
|
||||
static async createVault() {
|
||||
if (await db.vault.exists()) return false;
|
||||
return await showPasswordDialog("create_vault", async ({ password }) => {
|
||||
if (!password) return false;
|
||||
await db.vault.create(password);
|
||||
showToast("success", "Vault created.");
|
||||
return true;
|
||||
@@ -35,6 +36,7 @@ class Vault {
|
||||
static async clearVault() {
|
||||
if (!(await db.vault.exists())) return false;
|
||||
return await showPasswordDialog("clear_vault", async ({ password }) => {
|
||||
if (!password) return false;
|
||||
try {
|
||||
await db.vault.clear(password);
|
||||
return true;
|
||||
@@ -49,6 +51,7 @@ class Vault {
|
||||
return await showPasswordDialog(
|
||||
"delete_vault",
|
||||
async ({ password, deleteAllLockedNotes }) => {
|
||||
if (!password) return false;
|
||||
if (!(await db.user.verifyPassword(password))) return false;
|
||||
await db.vault.delete(!!deleteAllLockedNotes);
|
||||
return true;
|
||||
@@ -62,6 +65,7 @@ class Vault {
|
||||
*/
|
||||
static unlockVault() {
|
||||
return showPasswordDialog("ask_vault_password", ({ password }) => {
|
||||
if (!password) return false;
|
||||
return db.vault
|
||||
.unlock(password)
|
||||
.then(() => true)
|
||||
@@ -73,6 +77,7 @@ class Vault {
|
||||
return showPasswordDialog(
|
||||
"change_password",
|
||||
async ({ oldPassword, newPassword }) => {
|
||||
if (!oldPassword || !newPassword) return false;
|
||||
await db.vault.changePassword(oldPassword, newPassword);
|
||||
showToast("success", "Vault password changed.");
|
||||
return true;
|
||||
@@ -80,29 +85,28 @@ class Vault {
|
||||
);
|
||||
}
|
||||
|
||||
static unlockNote(id, type = "unlock_note") {
|
||||
return new Promise((resolve) => {
|
||||
return showPasswordDialog(type, ({ password }) => {
|
||||
return db.vault
|
||||
.remove(id, password)
|
||||
.then(() => true)
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
return false;
|
||||
});
|
||||
}).then(resolve);
|
||||
static unlockNote(id: string, type = "unlock_note") {
|
||||
return showPasswordDialog(type, ({ password }) => {
|
||||
if (!password) return false;
|
||||
return db.vault
|
||||
.remove(id, password)
|
||||
.then(() => true)
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static lockNote(id) {
|
||||
static lockNote(id: string): Promise<boolean> {
|
||||
return db.vault
|
||||
.add(id)
|
||||
.then(() => true)
|
||||
.catch(({ message }) => {
|
||||
switch (message) {
|
||||
case db.vault.ERRORS.noVault:
|
||||
case VAULT_ERRORS.noVault:
|
||||
return Vault.createVault().then(() => Vault.lockNote(id));
|
||||
case db.vault.ERRORS.vaultLocked:
|
||||
case VAULT_ERRORS.vaultLocked:
|
||||
return Vault.unlockVault().then(() => Vault.lockNote(id));
|
||||
default:
|
||||
showToast("error", message);
|
||||
@@ -112,8 +116,9 @@ class Vault {
|
||||
});
|
||||
}
|
||||
|
||||
static askPassword(action) {
|
||||
static askPassword(action: (password: string) => Promise<boolean>) {
|
||||
return showPasswordDialog("ask_vault_password", ({ password }) => {
|
||||
if (!password) return false;
|
||||
return action(password);
|
||||
});
|
||||
}
|
||||
@@ -50,7 +50,7 @@ type DialogProps = SxProp & {
|
||||
description?: string;
|
||||
positiveButton?: DialogButtonProps | null;
|
||||
negativeButton?: DialogButtonProps | null;
|
||||
footer?: React.Component;
|
||||
footer?: React.ReactNode;
|
||||
noScroll?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -157,13 +157,15 @@ async function addAttachment(
|
||||
file: File,
|
||||
options: AddAttachmentOptions = {}
|
||||
): Promise<string> {
|
||||
const { default: FS } = await import("../../interfaces/fs");
|
||||
const { getUploadedFileSize, hashStream, writeEncryptedFile } = await import(
|
||||
"../../interfaces/fs"
|
||||
);
|
||||
const { expectedFileHash, showProgress = true } = options;
|
||||
let forceWrite = options.forceWrite;
|
||||
|
||||
const action = async () => {
|
||||
const reader = file.stream().getReader();
|
||||
const { hash, type: hashType } = await FS.hashStream(reader);
|
||||
const { hash, type: hashType } = await hashStream(reader);
|
||||
reader.releaseLock();
|
||||
|
||||
if (expectedFileHash && hash !== expectedFileHash)
|
||||
@@ -171,15 +173,15 @@ async function addAttachment(
|
||||
`Please select the same file for reuploading. Expected hash ${expectedFileHash} but got ${hash}.`
|
||||
);
|
||||
|
||||
const exists = db.attachments?.attachment(hash);
|
||||
const exists = await db.attachments.attachment(hash);
|
||||
if (!forceWrite && exists) {
|
||||
forceWrite = (await FS.getUploadedFileSize(hash)) <= 0;
|
||||
forceWrite = (await getUploadedFileSize(hash)) <= 0;
|
||||
}
|
||||
|
||||
if (forceWrite || !exists) {
|
||||
const key: SerializedKey = await getEncryptionKey();
|
||||
|
||||
const output = await FS.writeEncryptedFile(file, key, hash);
|
||||
const output = await writeEncryptedFile(file, key, hash);
|
||||
if (!output) throw new Error("Could not encrypt file.");
|
||||
|
||||
if (forceWrite && exists) await db.attachments.reset(hash);
|
||||
|
||||
@@ -49,7 +49,7 @@ function TitleBox(props: TitleBoxProps) {
|
||||
);
|
||||
|
||||
const updateFontSize = useCallback(
|
||||
(length) => {
|
||||
(length: number) => {
|
||||
if (!inputRef.current) return;
|
||||
const fontSize = textLengthToFontSize(
|
||||
length,
|
||||
|
||||
@@ -143,7 +143,11 @@ function Field(props: FieldProps) {
|
||||
}}
|
||||
disabled={action.disabled}
|
||||
>
|
||||
{action.component ? action.component : <action.icon size={20} />}
|
||||
{action.component ? (
|
||||
action.component
|
||||
) : action.icon ? (
|
||||
<action.icon size={20} />
|
||||
) : null}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
@@ -27,7 +27,7 @@ type FilteredListProps<T> = {
|
||||
placeholders: { filter: string; empty: string };
|
||||
filter: (query: string) => Promise<T[]>;
|
||||
onCreateNewItem: (title: string) => Promise<void>;
|
||||
} & VirtualizedListProps<T>;
|
||||
} & VirtualizedListProps<T, unknown>;
|
||||
|
||||
export function FilteredList<T>(props: FilteredListProps<T>) {
|
||||
const { items, filter, onCreateNewItem, placeholders, ...listProps } = props;
|
||||
@@ -38,7 +38,7 @@ export function FilteredList<T>(props: FilteredListProps<T>) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const _filter = useCallback(
|
||||
async (query) => {
|
||||
async (query = "") => {
|
||||
setFilteredItems(query ? await filter(query) : []);
|
||||
setQuery(query);
|
||||
},
|
||||
@@ -46,7 +46,7 @@ export function FilteredList<T>(props: FilteredListProps<T>) {
|
||||
);
|
||||
|
||||
const _createNewItem = useCallback(
|
||||
async (title) => {
|
||||
async (title: string) => {
|
||||
await onCreateNewItem(title);
|
||||
setQuery(undefined);
|
||||
if (inputRef.current) inputRef.current.value = "";
|
||||
|
||||
@@ -121,7 +121,7 @@ function NavigationMenu(props: NavigationMenuProps) {
|
||||
);
|
||||
|
||||
const _navigate = useCallback(
|
||||
(path) => {
|
||||
(path: string) => {
|
||||
toggleNavigationContainer(true);
|
||||
const nestedRoute = findNestedRoute(path);
|
||||
navigate(!nestedRoute || nestedRoute === location ? path : nestedRoute);
|
||||
@@ -218,7 +218,7 @@ function NavigationMenu(props: NavigationMenuProps) {
|
||||
key: "rename",
|
||||
title: "Rename color",
|
||||
onClick: async () => {
|
||||
await showRenameColorDialog(color.id);
|
||||
await showRenameColorDialog(color);
|
||||
}
|
||||
}
|
||||
]}
|
||||
|
||||
@@ -94,7 +94,7 @@ import {
|
||||
NotebooksWithDateEdited,
|
||||
TagsWithDateEdited
|
||||
} from "../list-container/types";
|
||||
import { SchemeColors, StaticColors } from "@notesnook/theme";
|
||||
import { SchemeColors } from "@notesnook/theme";
|
||||
import Vault from "../../common/vault";
|
||||
|
||||
type NoteProps = {
|
||||
|
||||
@@ -46,6 +46,7 @@ import { ResolvedItem } from "../list-container/resolved-item";
|
||||
import { SessionItem } from "../session-item";
|
||||
import { COLORS } from "../../common/constants";
|
||||
import { DefaultColors } from "@notesnook/core";
|
||||
import { VirtualizedTable } from "../virtualized-table";
|
||||
|
||||
const tools = [
|
||||
{ key: "pin", property: "pinned", icon: Pin, label: "Pin" },
|
||||
@@ -272,10 +273,14 @@ function Notebooks({ noteId }: { noteId: string }) {
|
||||
<VirtualizedList
|
||||
mode="fixed"
|
||||
estimatedSize={50}
|
||||
getItemKey={(index) => result.value.getKey(index)}
|
||||
items={result.value.ungrouped}
|
||||
renderItem={({ item: id }) => (
|
||||
<ListItemWrapper id={id} items={result.value} simplified />
|
||||
getItemKey={(index) => result.value.key(index)}
|
||||
items={result.value.ids}
|
||||
renderItem={({ item: index }) => (
|
||||
<ResolvedItem index={index} items={result.value} type="notebook">
|
||||
{({ item, data }) => (
|
||||
<ListItemWrapper item={item} data={data} simplified />
|
||||
)}
|
||||
</ResolvedItem>
|
||||
)}
|
||||
/>
|
||||
</Section>
|
||||
@@ -295,10 +300,14 @@ function Reminders({ noteId }: { noteId: string }) {
|
||||
<VirtualizedList
|
||||
mode="fixed"
|
||||
estimatedSize={54}
|
||||
getItemKey={(index) => result.value.getKey(index)}
|
||||
items={result.value.ungrouped}
|
||||
renderItem={({ item: id }) => (
|
||||
<ListItemWrapper id={id} items={result.value} simplified />
|
||||
getItemKey={(index) => result.value.key(index)}
|
||||
items={result.value.ids}
|
||||
renderItem={({ item: index }) => (
|
||||
<ResolvedItem index={index} items={result.value} type="reminder">
|
||||
{({ item, data }) => (
|
||||
<ListItemWrapper item={item} data={data} simplified />
|
||||
)}
|
||||
</ResolvedItem>
|
||||
)}
|
||||
/>
|
||||
</Section>
|
||||
@@ -315,14 +324,17 @@ function Attachments({ noteId }: { noteId: string }) {
|
||||
|
||||
return (
|
||||
<Section title="Attachments">
|
||||
{result.value.ids.map((id, index) => (
|
||||
<ListItemWrapper
|
||||
key={result.value.getKey(index)}
|
||||
id={id as string}
|
||||
items={result.value}
|
||||
compact
|
||||
/>
|
||||
))}
|
||||
<VirtualizedTable
|
||||
estimatedSize={30}
|
||||
getItemKey={(index) => result.value.key(index)}
|
||||
items={result.value.ids}
|
||||
header={<></>}
|
||||
renderRow={({ item: index }) => (
|
||||
<ResolvedItem index={index} type="attachment" items={result.value}>
|
||||
{({ item }) => <ListItemWrapper item={item} compact />}
|
||||
</ResolvedItem>
|
||||
)}
|
||||
/>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -353,10 +365,10 @@ function SessionHistory({
|
||||
<VirtualizedList
|
||||
mode="fixed"
|
||||
estimatedSize={28}
|
||||
getItemKey={(index) => result.value.getKey(index)}
|
||||
items={result.value.ungrouped}
|
||||
renderItem={({ item: id }) => (
|
||||
<ResolvedItem type="session" id={id} items={result.value}>
|
||||
getItemKey={(index) => result.value.key(index)}
|
||||
items={result.value.ids}
|
||||
renderItem={({ item: index }) => (
|
||||
<ResolvedItem type="session" index={index} items={result.value}>
|
||||
{({ item }) => (
|
||||
<SessionItem
|
||||
noteId={noteId}
|
||||
|
||||
@@ -30,6 +30,7 @@ import { showToast } from "../../utils/toast";
|
||||
import { pluralize } from "@notesnook/common";
|
||||
import { MenuItem } from "@notesnook/ui";
|
||||
import { Tag } from "@notesnook/core/dist/types";
|
||||
import { showEditTagDialog } from "../../common/dialog-controller";
|
||||
|
||||
type TagProps = { item: Tag; totalNotes: number };
|
||||
function Tag(props: TagProps) {
|
||||
@@ -70,7 +71,7 @@ const menuItems: (tag: Tag, ids?: string[]) => MenuItem[] = (tag, ids = []) => {
|
||||
title: "Rename tag",
|
||||
icon: Edit.path,
|
||||
onClick: () => {
|
||||
hashNavigate(`/tags/${tag.id}/edit`);
|
||||
showEditTagDialog(tag);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -66,6 +66,7 @@ export function ThemePreview(props: ThemePreviewProps) {
|
||||
theme.previewColors.background
|
||||
].map((color) => (
|
||||
<Circle
|
||||
key={color}
|
||||
color={color}
|
||||
size={18}
|
||||
sx={{
|
||||
|
||||
@@ -52,7 +52,7 @@ function Unlock(props: UnlockProps) {
|
||||
try {
|
||||
if (!password) return;
|
||||
const note = await db.vault.open(noteId, password);
|
||||
console.log(note);
|
||||
if (!note) return;
|
||||
openLockedSession(note);
|
||||
} catch (e) {
|
||||
if (
|
||||
|
||||
@@ -31,7 +31,7 @@ import { store as notestore } from "../stores/note-store";
|
||||
import { store as editorStore } from "../stores/editor-store";
|
||||
import { Perform } from "../common/dialog-controller";
|
||||
import { FilteredList } from "../components/filtered-list";
|
||||
import { ItemReference, Tag, isGroupHeader } from "@notesnook/core/dist/types";
|
||||
import { ItemReference, Tag } from "@notesnook/core/dist/types";
|
||||
import { ResolvedItem } from "../components/list-container/resolved-item";
|
||||
import { create } from "zustand";
|
||||
|
||||
@@ -66,23 +66,17 @@ function AddTagsDialog(props: AddTagsDialogProps) {
|
||||
await useStore.getState().refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
const selected: SelectedReference[] = [];
|
||||
for (const tag of tags.ids) {
|
||||
if (isGroupHeader(tag)) continue;
|
||||
if (selected.findIndex((a) => a.id === tag) > -1) continue;
|
||||
|
||||
if (await tagHasNotes(tag, noteIds)) {
|
||||
selected.push({
|
||||
id: tag,
|
||||
op: "add",
|
||||
new: false
|
||||
});
|
||||
}
|
||||
}
|
||||
const selectedTags = await db.relations
|
||||
.to({ type: "note", ids: noteIds }, "tag")
|
||||
.get();
|
||||
selectedTags.forEach((r) => {
|
||||
if (selected.findIndex((a) => a.id === r.fromId) > -1) return;
|
||||
selected.push({ id: r.fromId, op: "add", new: false });
|
||||
});
|
||||
useSelectionStore.getState().setSelected(selected);
|
||||
})();
|
||||
}, [tags]);
|
||||
}, [tags, noteIds]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -115,10 +109,10 @@ function AddTagsDialog(props: AddTagsDialogProps) {
|
||||
>
|
||||
{tags && (
|
||||
<FilteredList
|
||||
getItemKey={(index, items) => items[index]}
|
||||
getItemKey={(index) => tags.key(index)}
|
||||
mode="fixed"
|
||||
estimatedSize={30}
|
||||
items={tags.ungrouped}
|
||||
items={tags.ids}
|
||||
sx={{ mt: 2 }}
|
||||
itemGap={5}
|
||||
placeholders={{
|
||||
@@ -132,10 +126,10 @@ function AddTagsDialog(props: AddTagsDialogProps) {
|
||||
const { selected, setSelected } = useSelectionStore.getState();
|
||||
setSelected([...selected, { id: tagId, new: true, op: "add" }]);
|
||||
}}
|
||||
renderItem={({ item: tagId }) => {
|
||||
renderItem={({ item: index }) => {
|
||||
return (
|
||||
<ResolvedItem key={tagId} type="tag" items={tags} id={tagId}>
|
||||
{({ item }) => <TagItem tag={item} key={tagId} />}
|
||||
<ResolvedItem key={index} type="tag" items={tags} index={index}>
|
||||
{({ item }) => <TagItem tag={item} />}
|
||||
</ResolvedItem>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -162,7 +162,7 @@ function AttachmentsDialog({ onClose }: AttachmentsDialogProps) {
|
||||
<Sidebar
|
||||
onDownloadAll={() => download(allAttachments?.ungrouped || [])}
|
||||
filter={async (query) => {
|
||||
setAttachments(await db.lookup.attachments(query));
|
||||
setAttachments(await db.lookup.attachments(query).sorted());
|
||||
}}
|
||||
counts={counts}
|
||||
onRouteChange={async (route) => {
|
||||
|
||||
@@ -528,7 +528,7 @@ function SetupSMS(props: SetupAuthenticatorProps) {
|
||||
}
|
||||
}}
|
||||
action={{
|
||||
disabled: error || isSending || !enabled,
|
||||
disabled: !!error || isSending || !enabled,
|
||||
component: (
|
||||
<Text variant={"body"}>
|
||||
{isSending ? (
|
||||
|
||||
@@ -38,7 +38,7 @@ import { Perform, showAddNotebookDialog } from "../common/dialog-controller";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { isMac } from "../utils/platform";
|
||||
import { create } from "zustand";
|
||||
import { Notebook, isGroupHeader } from "@notesnook/core/dist/types";
|
||||
import { Notebook } from "@notesnook/core/dist/types";
|
||||
import {
|
||||
UncontrolledTreeEnvironment,
|
||||
Tree,
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
} from "react-complex-tree";
|
||||
import { FlexScrollContainer } from "../components/scroll-container";
|
||||
import { pluralize } from "@notesnook/common";
|
||||
import usePromise from "../hooks/use-promise";
|
||||
|
||||
type MoveDialogProps = { onClose: Perform; noteIds: string[] };
|
||||
type NotebookReference = {
|
||||
@@ -73,18 +74,14 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
const setIsMultiselect = useSelectionStore((store) => store.setIsMultiselect);
|
||||
const isMultiselect = useSelectionStore((store) => store.isMultiselect);
|
||||
const refreshNotebooks = useStore((store) => store.refresh);
|
||||
const notebooks = useStore((store) => store.notebooks);
|
||||
// const notebooks = useStore((store) => store.notebooks);
|
||||
const reloadItem = useRef<(changedItemIds: TreeItemIndex[]) => void>();
|
||||
const treeRef = useRef<TreeEnvironmentRef>(null);
|
||||
const rootNotebooks = usePromise(() =>
|
||||
db.notebooks.roots.ids(db.settings.getGroupOptions("notebooks"))
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!notebooks) {
|
||||
(async function () {
|
||||
await refreshNotebooks();
|
||||
})();
|
||||
return;
|
||||
}
|
||||
|
||||
// for (const notebook of notebooks.ids) {
|
||||
// if (isGroupHeader(notebook)) continue;
|
||||
// // for (const topic of notebook.topics) {
|
||||
@@ -141,7 +138,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
// new: false
|
||||
// });
|
||||
// }
|
||||
}, [noteIds, notebooks, refreshNotebooks, setSelected, setIsMultiselect]);
|
||||
}, [noteIds, refreshNotebooks, setSelected, setIsMultiselect]);
|
||||
|
||||
const _onClose = useCallback(
|
||||
(result: boolean) => {
|
||||
@@ -214,7 +211,8 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
Reset selection
|
||||
</Button>
|
||||
)}
|
||||
{notebooks && notebooks.ids.length > 0 ? (
|
||||
{rootNotebooks.status === "fulfilled" &&
|
||||
rootNotebooks.value.length > 0 ? (
|
||||
<FlexScrollContainer>
|
||||
<UncontrolledTreeEnvironment
|
||||
ref={treeRef}
|
||||
@@ -235,9 +233,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
isFolder: true,
|
||||
canMove: false,
|
||||
canRename: false,
|
||||
children: notebooks.ids.filter(
|
||||
(t) => !isGroupHeader(t)
|
||||
) as string[]
|
||||
children: rootNotebooks.value
|
||||
};
|
||||
}
|
||||
|
||||
@@ -265,7 +261,6 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
"notebook"
|
||||
)
|
||||
.get();
|
||||
console.log(itemIds, notebooks?.ids);
|
||||
return itemIds.filter(Boolean).map((id) => {
|
||||
if (id === "root") {
|
||||
return {
|
||||
@@ -274,9 +269,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
isFolder: true,
|
||||
canMove: false,
|
||||
canRename: false,
|
||||
children: notebooks.ids.filter(
|
||||
(t) => !isGroupHeader(t)
|
||||
) as string[]
|
||||
children: rootNotebooks.value
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import IconTag from "../components/icon-tag";
|
||||
import { Clock, Refresh } from "../components/icons";
|
||||
import Note from "../components/note";
|
||||
import { getFormattedReminderTime } from "@notesnook/common";
|
||||
import usePromise from "../hooks/use-promise";
|
||||
|
||||
export type ReminderPreviewDialogProps = {
|
||||
onClose: Perform;
|
||||
@@ -57,9 +58,11 @@ export default function ReminderPreviewDialog(
|
||||
props: ReminderPreviewDialogProps
|
||||
) {
|
||||
const { reminder } = props;
|
||||
const referencedNotes = db.relations
|
||||
.to({ id: reminder.id, type: "reminder" }, "note")
|
||||
.resolved();
|
||||
const referencedNotes = usePromise(
|
||||
() =>
|
||||
db.relations.to({ id: reminder.id, type: "reminder" }, "note").resolve(),
|
||||
[reminder.id]
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -117,20 +120,16 @@ export default function ReminderPreviewDialog(
|
||||
</Button>
|
||||
))}
|
||||
</Flex>
|
||||
{referencedNotes && referencedNotes.length > 0 && (
|
||||
<>
|
||||
<Text variant="body">References:</Text>
|
||||
{referencedNotes.map((item, index) => (
|
||||
<Note
|
||||
key={item.id}
|
||||
item={item}
|
||||
date={item.dateCreated}
|
||||
tags={[]}
|
||||
compact
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{referencedNotes &&
|
||||
referencedNotes.status === "fulfilled" &&
|
||||
referencedNotes.value.length > 0 && (
|
||||
<>
|
||||
<Text variant="body">References:</Text>
|
||||
{referencedNotes.value.map((item, index) => (
|
||||
<Note key={item.id} item={item} date={item.dateCreated} compact />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -49,13 +49,12 @@ export const AuthenticationSettings: SettingsGroup[] = [
|
||||
await createBackup();
|
||||
const result = await showPasswordDialog(
|
||||
"change_account_password",
|
||||
async (data) => {
|
||||
async ({ newPassword, oldPassword }) => {
|
||||
if (!newPassword || !oldPassword) return false;
|
||||
await db.user.clearSessions();
|
||||
return (
|
||||
db.user.changePassword(
|
||||
data.oldPassword,
|
||||
data.newPassword
|
||||
) || false
|
||||
(await db.user.changePassword(oldPassword, newPassword)) ||
|
||||
false
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@ export function Importer() {
|
||||
const notesCounter = useRef<HTMLSpanElement>(null);
|
||||
const importProgress = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onDrop = useCallback((acceptedFiles) => {
|
||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||
setFiles((files) => {
|
||||
const newFiles = [...acceptedFiles, ...files];
|
||||
return newFiles;
|
||||
@@ -43,7 +43,9 @@ export function Importer() {
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
onDrop,
|
||||
accept: [".zip"]
|
||||
accept: {
|
||||
"application/zip": [".zip"]
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -36,7 +36,7 @@ export function SpellCheckerLanguages() {
|
||||
}, [spellChecker.enabledLanguages, spellChecker.languages]);
|
||||
|
||||
const filter = useCallback(
|
||||
async (query) => {
|
||||
async (query: string) => {
|
||||
if (!spellChecker.languages) return;
|
||||
setLanguages(
|
||||
spellChecker.languages.filter(
|
||||
|
||||
@@ -103,6 +103,7 @@ export const ProfileSettings: SettingsGroup[] = [
|
||||
title: "Delete account",
|
||||
action: async () =>
|
||||
showPasswordDialog("delete_account", async ({ password }) => {
|
||||
if (!password) return false;
|
||||
await db.user.deleteUser(password);
|
||||
return true;
|
||||
})
|
||||
|
||||
@@ -30,7 +30,6 @@ export function useIsUserPremium() {
|
||||
}
|
||||
|
||||
export function isUserPremium(user?: User) {
|
||||
return true;
|
||||
if (IS_TESTING) return true;
|
||||
if (!user) user = userstore.get().user;
|
||||
if (!user) return false;
|
||||
|
||||
@@ -89,7 +89,7 @@ export default function useSlider(
|
||||
}, [ref, slides, onSliding, onChange]);
|
||||
|
||||
const slideToIndex = useCallback(
|
||||
(index) => {
|
||||
(index: number) => {
|
||||
if (!slides || !ref.current || index >= slides.length) return;
|
||||
console.log(slides[index].offset, slides[index].width);
|
||||
const slider = ref.current;
|
||||
|
||||
@@ -33,7 +33,7 @@ import { ProgressStream } from "../utils/streams/progress-stream";
|
||||
import { consumeReadableStream } from "../utils/stream";
|
||||
import { Base64DecoderStream } from "../utils/streams/base64-decoder-stream";
|
||||
import { toBlob } from "@notesnook-importer/core/dist/src/utils/stream";
|
||||
import { Cipher, DataFormat, SerializedKey } from "@notesnook/crypto";
|
||||
import { DataFormat, SerializedKey } from "@notesnook/crypto";
|
||||
import { IDataType } from "hash-wasm/dist/lib/util";
|
||||
import { IndexedDBKVStore } from "./key-value";
|
||||
import FileHandle from "@notesnook/streamable-fs/dist/src/filehandle";
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
} from "./file-store";
|
||||
import { isFeatureSupported } from "../utils/feature-check";
|
||||
import {
|
||||
FileEncryptionMetadataWithHash,
|
||||
FileEncryptionMetadataWithOutputType,
|
||||
IFileStorage,
|
||||
Output,
|
||||
@@ -117,7 +118,7 @@ export async function writeEncryptedFile(
|
||||
return {
|
||||
chunkSize: CHUNK_SIZE,
|
||||
iv: iv,
|
||||
length: file.size,
|
||||
size: file.size,
|
||||
salt: key.salt!,
|
||||
alg: "xcha-stream"
|
||||
};
|
||||
@@ -134,15 +135,15 @@ async function writeEncryptedBase64(
|
||||
data: string,
|
||||
key: SerializedKey,
|
||||
mimeType: string
|
||||
) {
|
||||
): Promise<FileEncryptionMetadataWithHash> {
|
||||
const bytes = new Uint8Array(Buffer.from(data, "base64"));
|
||||
|
||||
const { hash, type: hashType } = await hashBuffer(bytes);
|
||||
|
||||
const attachment = db.attachments.attachment(hash);
|
||||
const attachment = await db.attachments.attachment(hash);
|
||||
|
||||
const file = new File([bytes.buffer], hash, {
|
||||
type: attachment?.metadata.type || mimeType || "application/octet-stream"
|
||||
type: attachment?.mimeType || mimeType || "application/octet-stream"
|
||||
});
|
||||
|
||||
const result = await writeEncryptedFile(file, key, hash);
|
||||
@@ -180,7 +181,7 @@ export async function hashStream(
|
||||
return { type: "xxh64", hash: hasher.digest("hex") };
|
||||
}
|
||||
|
||||
async function readEncrypted<TOutputFormat extends OutputFormat>(
|
||||
async function readEncrypted<TOutputFormat extends DataFormat>(
|
||||
filename: string,
|
||||
key: SerializedKey,
|
||||
cipherData: FileEncryptionMetadataWithOutputType<TOutputFormat>
|
||||
@@ -468,7 +469,7 @@ async function downloadFile(
|
||||
return true;
|
||||
else if (handle) await handle.delete();
|
||||
|
||||
const attachment = db.attachments?.attachment(filename);
|
||||
const attachment = await db.attachments.attachment(filename);
|
||||
|
||||
reportProgress(
|
||||
{ total: 100, loaded: 0 },
|
||||
@@ -510,7 +511,7 @@ async function downloadFile(
|
||||
|
||||
const totalChunks = Math.ceil(contentLength / chunkSize);
|
||||
const decryptedLength = contentLength - totalChunks * ABYTES;
|
||||
if (attachment && attachment.length !== decryptedLength) {
|
||||
if (attachment && attachment.size !== decryptedLength) {
|
||||
const error = `File length mismatch. Please upload this file again from the attachment manager. (File hash: ${filename})`;
|
||||
await db.attachments.markAsFailed(filename, error);
|
||||
throw new Error(error);
|
||||
@@ -519,7 +520,7 @@ async function downloadFile(
|
||||
const fileHandle = await streamablefs.createFile(
|
||||
filename,
|
||||
decryptedLength,
|
||||
attachment?.metadata.type || "application/octet-stream"
|
||||
attachment?.mimeType || "application/octet-stream"
|
||||
);
|
||||
|
||||
await response.body
|
||||
@@ -554,7 +555,7 @@ async function downloadFile(
|
||||
async function exists(filename: string) {
|
||||
const handle = await streamablefs.readFile(filename);
|
||||
return (
|
||||
handle &&
|
||||
!!handle &&
|
||||
handle.file.size === (await handle.size()) - handle.file.chunks * ABYTES
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
showBuyDialog,
|
||||
showCreateTagDialog,
|
||||
showEditReminderDialog,
|
||||
showEditTagDialog,
|
||||
showEmailVerificationDialog,
|
||||
showFeatureDialog,
|
||||
showOnboardingDialog,
|
||||
@@ -64,9 +63,6 @@ const hashroutes = defineRoutes({
|
||||
"/tags/create": () => {
|
||||
showCreateTagDialog().then(afterAction);
|
||||
},
|
||||
"/tags/:tagId/edit": ({ tagId }) => {
|
||||
showEditTagDialog(tagId)?.then(afterAction);
|
||||
},
|
||||
"/notes/create": () => {
|
||||
closeOpenedDialog();
|
||||
hashNavigate("/notes/create", { addNonce: true, replace: true });
|
||||
|
||||
@@ -293,8 +293,7 @@ class AppStore extends BaseStore<AppStore> {
|
||||
try {
|
||||
const result = await db.sync({
|
||||
type: full ? "full" : "send",
|
||||
force,
|
||||
serverLastSynced: lastSynced
|
||||
force
|
||||
});
|
||||
|
||||
if (!result) return this.updateSyncStatus("failed");
|
||||
|
||||
@@ -124,10 +124,7 @@ async function processNote(entry: ZipEntry, attachments: Record<string, any>) {
|
||||
for (const nb of notebooks) {
|
||||
const notebook = await importNotebook(nb).catch(() => ({ id: undefined }));
|
||||
if (!notebook.id) continue;
|
||||
await db.notes.addToNotebook(
|
||||
{ id: notebook.id, topic: notebook.topic },
|
||||
noteId
|
||||
);
|
||||
await db.notes.addToNotebook(notebook.id, noteId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ export class AttachmentStream extends ReadableStream<ZipFile> {
|
||||
const file = await lazify(
|
||||
import("../../interfaces/fs"),
|
||||
({ decryptFile }) =>
|
||||
decryptFile(attachment.metadata.hash, {
|
||||
decryptFile(attachment.hash, {
|
||||
key,
|
||||
iv: attachment.iv,
|
||||
name: attachment.filename,
|
||||
|
||||
@@ -43,27 +43,32 @@ export class WebExtensionServer implements Server {
|
||||
}
|
||||
|
||||
async getNotes(): Promise<ItemReference[] | undefined> {
|
||||
return db.notes.all
|
||||
.filter((n) => !n.locked)
|
||||
.map((note) => ({ id: note.id, title: note.title }));
|
||||
const notes = await db.notes.all
|
||||
.where((eb) => eb("notes.locked", "==", false))
|
||||
.fields(["notes.id", "notes.title"])
|
||||
.items(undefined, db.settings.getGroupOptions("notes"));
|
||||
return notes;
|
||||
}
|
||||
|
||||
async getNotebooks(): Promise<NotebookReference[] | undefined> {
|
||||
return db.notebooks.all.map((nb) => ({
|
||||
id: nb.id,
|
||||
title: nb.title,
|
||||
topics: nb.topics.map((topic: ItemReference) => ({
|
||||
id: topic.id,
|
||||
title: topic.title
|
||||
}))
|
||||
}));
|
||||
async getNotebooks(
|
||||
parentId?: string
|
||||
): Promise<NotebookReference[] | undefined> {
|
||||
if (!parentId)
|
||||
return await db.notebooks.roots
|
||||
.fields(["notebooks.id", "notebooks.title"])
|
||||
.items();
|
||||
|
||||
return await db.relations
|
||||
.from({ type: "notebook", id: parentId as string }, "notebook")
|
||||
.selector.fields(["notebooks.id", "notebooks.title"])
|
||||
.items();
|
||||
}
|
||||
|
||||
async getTags(): Promise<ItemReference[] | undefined> {
|
||||
return db.tags.all.map((tag) => ({
|
||||
id: tag.id,
|
||||
title: tag.title
|
||||
}));
|
||||
const tags = await db.tags.all
|
||||
.fields(["notes.id", "notes.title"])
|
||||
.items(undefined, db.settings.getGroupOptions("tags"));
|
||||
return tags;
|
||||
}
|
||||
|
||||
async saveClip(clip: Clip) {
|
||||
@@ -94,9 +99,10 @@ export class WebExtensionServer implements Server {
|
||||
}).outerHTML;
|
||||
}
|
||||
|
||||
const note = clip.note?.id ? db.notes.note(clip.note?.id) : null;
|
||||
const note = clip.note?.id ? await db.notes.note(clip.note?.id) : null;
|
||||
|
||||
let content = (await note?.content()) || "";
|
||||
let content =
|
||||
(!!note?.contentId && (await db.content.get(note.contentId))?.data) || "";
|
||||
if (isCipher(content)) return;
|
||||
|
||||
content += clipContent;
|
||||
@@ -113,12 +119,9 @@ export class WebExtensionServer implements Server {
|
||||
});
|
||||
|
||||
if (id && clip.tags) {
|
||||
for (const title of clip.tags) {
|
||||
const tagId = db.tags.tag(title)?.id || (await db.tags.add({ title }));
|
||||
await db.relations.add(
|
||||
{ id: tagId, type: "tag" },
|
||||
{ id, type: "note" }
|
||||
);
|
||||
for (const id of clip.tags) {
|
||||
if (!(await db.tags.exists(id))) continue;
|
||||
await db.relations.add({ id: id, type: "tag" }, { id, type: "note" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,13 +129,7 @@ export class WebExtensionServer implements Server {
|
||||
for (const ref of clip.refs) {
|
||||
switch (ref.type) {
|
||||
case "notebook":
|
||||
await db.notes.addToNotebook({ id: ref.id }, id);
|
||||
break;
|
||||
case "topic":
|
||||
await db.notes.addToNotebook(
|
||||
{ id: ref.parentId, topic: ref.id },
|
||||
id
|
||||
);
|
||||
await db.notes.addToNotebook(ref.id, id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,10 @@ import {
|
||||
import { pluralize } from "@notesnook/common";
|
||||
import { Allotment, AllotmentHandle } from "allotment";
|
||||
import { Plus } from "../components/icons";
|
||||
import { useStore as useNotesStore } from "../stores/note-store";
|
||||
import {
|
||||
notesFromContext,
|
||||
useStore as useNotesStore
|
||||
} from "../stores/note-store";
|
||||
import { useStore as useNotebookStore } from "../stores/notebook-store";
|
||||
import Placeholder from "../components/placeholders";
|
||||
import { db } from "../common/db";
|
||||
@@ -74,8 +77,9 @@ function Notebook(props: NotebookProps) {
|
||||
const filteredItems = useSearch(
|
||||
"notes",
|
||||
(query) => {
|
||||
if (!context || !notes || context.type !== "notebook") return;
|
||||
return db.lookup.notes(query, notes.ungrouped).sorted();
|
||||
if (!context || context.type !== "notebook") return;
|
||||
const notes = notesFromContext(context);
|
||||
return db.lookup.notes(query, notes).sorted();
|
||||
},
|
||||
[context, notes]
|
||||
);
|
||||
@@ -95,7 +99,7 @@ function Notebook(props: NotebookProps) {
|
||||
setContext({ type: "notebook", id: notebookId || rootId });
|
||||
}, [rootId, notebookId]);
|
||||
|
||||
const toggleCollapse = useCallback((isCollapsed) => {
|
||||
const toggleCollapse = useCallback((isCollapsed: boolean) => {
|
||||
if (!paneRef.current || !sizes.current) return;
|
||||
|
||||
if (!isCollapsed) {
|
||||
|
||||
@@ -33,9 +33,7 @@ export type ItemReference = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
export type NotebookReference = ItemReference & {
|
||||
topics: ItemReference[];
|
||||
};
|
||||
export type NotebookReference = ItemReference;
|
||||
|
||||
export type ClientMetadata = {
|
||||
id: string;
|
||||
@@ -76,7 +74,7 @@ export type Clip = {
|
||||
export interface Server {
|
||||
login(): Promise<User | null>;
|
||||
getNotes(): Promise<ItemReference[] | undefined>;
|
||||
getNotebooks(): Promise<NotebookReference[] | undefined>;
|
||||
getNotebooks(parentId?: string): Promise<NotebookReference[] | undefined>;
|
||||
getTags(): Promise<ItemReference[] | undefined>;
|
||||
saveClip(clip: Clip): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ function Notebook(props: NotebookProps) {
|
||||
}}
|
||||
/>
|
||||
|
||||
<FilteredList
|
||||
{/* <FilteredList
|
||||
getAll={() => notebook.topics}
|
||||
itemName="topic"
|
||||
placeholder={"Search for a topic"}
|
||||
@@ -188,7 +188,7 @@ function Notebook(props: NotebookProps) {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
/> */}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ export default class Vault {
|
||||
|
||||
async exists(vaultKey?: Cipher<"base64">) {
|
||||
if (!vaultKey) vaultKey = await this.getKey();
|
||||
return vaultKey && isCipher(vaultKey);
|
||||
return !!vaultKey && isCipher(vaultKey);
|
||||
}
|
||||
|
||||
// Private & internal methods
|
||||
|
||||
@@ -62,8 +62,10 @@ type EncryptedBackupFile = BaseBackupFile & {
|
||||
encrypted: true;
|
||||
};
|
||||
|
||||
type BackupFile = UnencryptedBackupFile | EncryptedBackupFile;
|
||||
type LegacyBackupFile = LegacyUnencryptedBackupFile | LegacyEncryptedBackupFile;
|
||||
export type BackupFile = UnencryptedBackupFile | EncryptedBackupFile;
|
||||
export type LegacyBackupFile =
|
||||
| LegacyUnencryptedBackupFile
|
||||
| LegacyEncryptedBackupFile;
|
||||
|
||||
type BackupState = {
|
||||
buffer: string[];
|
||||
|
||||
@@ -20,3 +20,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
export * from "./types";
|
||||
export { VirtualizedGrouping } from "./utils/virtualized-grouping";
|
||||
export { DefaultColors } from "./collections/colors";
|
||||
export { type BackupFile, type LegacyBackupFile } from "./database/backup";
|
||||
|
||||
Reference in New Issue
Block a user