web: fix many type errors

This commit is contained in:
Abdullah Atta
2023-12-21 13:37:55 +05:00
parent 0cd6191190
commit 7b3f671159
42 changed files with 236 additions and 232 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -50,7 +50,7 @@ type DialogProps = SxProp & {
description?: string;
positiveButton?: DialogButtonProps | null;
negativeButton?: DialogButtonProps | null;
footer?: React.Component;
footer?: React.ReactNode;
noScroll?: boolean;
};

View File

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

View File

@@ -49,7 +49,7 @@ function TitleBox(props: TitleBoxProps) {
);
const updateFontSize = useCallback(
(length) => {
(length: number) => {
if (!inputRef.current) return;
const fontSize = textLengthToFontSize(
length,

View File

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

View File

@@ -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 = "";

View File

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

View File

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

View File

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

View File

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

View File

@@ -66,6 +66,7 @@ export function ThemePreview(props: ThemePreviewProps) {
theme.previewColors.background
].map((color) => (
<Circle
key={color}
color={color}
size={18}
sx={{

View File

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

View File

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

View File

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

View File

@@ -528,7 +528,7 @@ function SetupSMS(props: SetupAuthenticatorProps) {
}
}}
action={{
disabled: error || isSending || !enabled,
disabled: !!error || isSending || !enabled,
component: (
<Text variant={"body"}>
{isSending ? (

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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