mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
mobile: migrate to sqlite
This commit is contained in:
committed by
Abdullah Atta
parent
2168609577
commit
5bfad0149b
@@ -50,6 +50,7 @@ const App = () => {
|
|||||||
if (appLockMode && appLockMode !== "none") {
|
if (appLockMode && appLockMode !== "none") {
|
||||||
useUserStore.getState().lockApp(true);
|
useUserStore.getState().lockApp(true);
|
||||||
}
|
}
|
||||||
|
//@ts-ignore
|
||||||
globalThis["IS_MAIN_APP_RUNNING"] = true;
|
globalThis["IS_MAIN_APP_RUNNING"] = true;
|
||||||
init();
|
init();
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ import { Platform } from "react-native";
|
|||||||
import * as Gzip from "react-native-gzip";
|
import * as Gzip from "react-native-gzip";
|
||||||
import EventSource from "../../utils/sse/even-source-ios";
|
import EventSource from "../../utils/sse/even-source-ios";
|
||||||
import AndroidEventSource from "../../utils/sse/event-source";
|
import AndroidEventSource from "../../utils/sse/event-source";
|
||||||
|
import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from "kysely";
|
||||||
import filesystem from "../filesystem";
|
import filesystem from "../filesystem";
|
||||||
|
|
||||||
import Storage from "./storage";
|
import Storage from "./storage";
|
||||||
|
import { RNSqliteDriver } from "./sqlite.kysely";
|
||||||
|
|
||||||
database.host(
|
database.host(
|
||||||
__DEV__
|
__DEV__
|
||||||
@@ -57,6 +58,21 @@ database.setup({
|
|||||||
compressor: {
|
compressor: {
|
||||||
compress: Gzip.deflate,
|
compress: Gzip.deflate,
|
||||||
decompress: Gzip.inflate
|
decompress: Gzip.inflate
|
||||||
|
},
|
||||||
|
batchSize: 500,
|
||||||
|
sqliteOptions: {
|
||||||
|
dialect: {
|
||||||
|
createDriver: () =>
|
||||||
|
new RNSqliteDriver({ async: true, dbName: "test.db" }),
|
||||||
|
createAdapter: () => new SqliteAdapter(),
|
||||||
|
createIntrospector: (db) => new SqliteIntrospector(db),
|
||||||
|
createQueryCompiler: () => new SqliteQueryCompiler()
|
||||||
|
}
|
||||||
|
// journalMode: "MEMORY",
|
||||||
|
// synchronous: "normal",
|
||||||
|
// pageSize: 8192,
|
||||||
|
// cacheSize: -16000,
|
||||||
|
// lockingMode: "exclusive"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
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 { initalize } from "@notesnook/core/dist/logger";
|
|
||||||
import { MMKVLoader } from "react-native-mmkv-storage";
|
import { MMKVLoader } from "react-native-mmkv-storage";
|
||||||
|
import { initialize } from "@notesnook/core/dist/logger";
|
||||||
import { KV } from "./storage";
|
import { KV } from "./storage";
|
||||||
|
|
||||||
const LoggerStorage = new MMKVLoader()
|
const LoggerStorage = new MMKVLoader()
|
||||||
.withInstanceID("notesnook_logs")
|
.withInstanceID("notesnook_logs")
|
||||||
.initialize();
|
.initialize();
|
||||||
|
|
||||||
initalize(new KV(LoggerStorage));
|
initialize(new KV(LoggerStorage));
|
||||||
|
|
||||||
export { LoggerStorage };
|
export { LoggerStorage };
|
||||||
|
|||||||
123
apps/mobile/app/common/database/sqlite.kysely.ts
Normal file
123
apps/mobile/app/common/database/sqlite.kysely.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the Notesnook project (https://notesnook.com/)
|
||||||
|
|
||||||
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { DatabaseConnection, Driver, QueryResult } from "kysely";
|
||||||
|
import { CompiledQuery } from "kysely";
|
||||||
|
import { QuickSQLiteConnection, open } from "react-native-quick-sqlite";
|
||||||
|
|
||||||
|
type Config = { dbName: string; async: boolean; location: string };
|
||||||
|
|
||||||
|
export class RNSqliteDriver implements Driver {
|
||||||
|
private connection?: DatabaseConnection;
|
||||||
|
private connectionMutex = new ConnectionMutex();
|
||||||
|
private db: QuickSQLiteConnection;
|
||||||
|
constructor(private readonly config: Config) {
|
||||||
|
this.db = open({
|
||||||
|
name: config.dbName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(): Promise<void> {
|
||||||
|
this.connection = new RNSqliteConnection(this.db);
|
||||||
|
}
|
||||||
|
|
||||||
|
async acquireConnection(): Promise<DatabaseConnection> {
|
||||||
|
// SQLite only has one single connection. We use a mutex here to wait
|
||||||
|
// until the single connection has been released.
|
||||||
|
await this.connectionMutex.lock();
|
||||||
|
return this.connection!;
|
||||||
|
}
|
||||||
|
|
||||||
|
async beginTransaction(connection: DatabaseConnection): Promise<void> {
|
||||||
|
await connection.executeQuery(CompiledQuery.raw("begin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async commitTransaction(connection: DatabaseConnection): Promise<void> {
|
||||||
|
await connection.executeQuery(CompiledQuery.raw("commit"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async rollbackTransaction(connection: DatabaseConnection): Promise<void> {
|
||||||
|
await connection.executeQuery(CompiledQuery.raw("rollback"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async releaseConnection(): Promise<void> {
|
||||||
|
this.connectionMutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroy(): Promise<void> {
|
||||||
|
this.db.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionMutex {
|
||||||
|
private promise?: Promise<void>;
|
||||||
|
private resolve?: () => void;
|
||||||
|
|
||||||
|
async lock(): Promise<void> {
|
||||||
|
while (this.promise) {
|
||||||
|
await this.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.promise = new Promise((resolve) => {
|
||||||
|
this.resolve = resolve;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock(): void {
|
||||||
|
const resolve = this.resolve;
|
||||||
|
|
||||||
|
this.promise = undefined;
|
||||||
|
this.resolve = undefined;
|
||||||
|
|
||||||
|
resolve?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RNSqliteConnection implements DatabaseConnection {
|
||||||
|
constructor(private readonly db: QuickSQLiteConnection) {}
|
||||||
|
|
||||||
|
streamQuery<R>(): AsyncIterableIterator<QueryResult<R>> {
|
||||||
|
throw new Error("wasqlite driver doesn't support streaming");
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeQuery<R>(
|
||||||
|
compiledQuery: CompiledQuery<unknown>
|
||||||
|
): Promise<QueryResult<R>> {
|
||||||
|
const { parameters, sql, query } = compiledQuery;
|
||||||
|
const mode =
|
||||||
|
query.kind === "SelectQueryNode"
|
||||||
|
? "query"
|
||||||
|
: query.kind === "RawNode"
|
||||||
|
? "raw"
|
||||||
|
: "exec";
|
||||||
|
const result = await this.db.executeAsync(sql, parameters as any[]);
|
||||||
|
|
||||||
|
console.log("SQLITE result:", result?.rows?._array);
|
||||||
|
if (mode === "query" || !result.insertId)
|
||||||
|
return {
|
||||||
|
rows: result.rows?._array || []
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
insertId: BigInt(result.insertId),
|
||||||
|
numAffectedRows: BigInt(result.rowsAffected),
|
||||||
|
rows: mode === "raw" ? result.rows?._array || [] : []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -189,7 +189,7 @@ export default async function downloadAttachment(
|
|||||||
) {
|
) {
|
||||||
await createCacheDir();
|
await createCacheDir();
|
||||||
|
|
||||||
let attachment = db.attachments.attachment(hash);
|
let attachment = await db.attachments.attachment(hash);
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
console.log("attachment not found");
|
console.log("attachment not found");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -17,9 +17,13 @@ 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 { formatBytes } from "@notesnook/common";
|
||||||
|
import { Attachment, Note, VirtualizedGrouping } from "@notesnook/core";
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import Clipboard from "@react-native-clipboard/clipboard";
|
import Clipboard from "@react-native-clipboard/clipboard";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { RefObject, useEffect, useState } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
|
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||||
import { ScrollView } from "react-native-gesture-handler";
|
import { ScrollView } from "react-native-gesture-handler";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import filesystem from "../../common/filesystem";
|
import filesystem from "../../common/filesystem";
|
||||||
@@ -27,14 +31,17 @@ import downloadAttachment from "../../common/filesystem/download-attachment";
|
|||||||
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
|
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
|
||||||
import picker from "../../screens/editor/tiptap/picker";
|
import picker from "../../screens/editor/tiptap/picker";
|
||||||
import {
|
import {
|
||||||
|
ToastManager,
|
||||||
eSendEvent,
|
eSendEvent,
|
||||||
presentSheet,
|
presentSheet
|
||||||
ToastManager
|
|
||||||
} from "../../services/event-manager";
|
} from "../../services/event-manager";
|
||||||
import PremiumService from "../../services/premium";
|
import PremiumService from "../../services/premium";
|
||||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import {
|
||||||
import { eCloseAttachmentDialog, eCloseSheet } from "../../utils/events";
|
eCloseAttachmentDialog,
|
||||||
|
eCloseSheet,
|
||||||
|
eDBItemUpdate
|
||||||
|
} from "../../utils/events";
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
import { sleep } from "../../utils/time";
|
import { sleep } from "../../utils/time";
|
||||||
import { Dialog } from "../dialog";
|
import { Dialog } from "../dialog";
|
||||||
@@ -46,28 +53,37 @@ import { Notice } from "../ui/notice";
|
|||||||
import { PressableButton } from "../ui/pressable";
|
import { PressableButton } from "../ui/pressable";
|
||||||
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 { formatBytes } from "@notesnook/common";
|
|
||||||
|
|
||||||
const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
const Actions = ({
|
||||||
|
attachment,
|
||||||
|
close,
|
||||||
|
setAttachments,
|
||||||
|
fwdRef
|
||||||
|
}: {
|
||||||
|
attachment: Attachment;
|
||||||
|
setAttachments: (attachments?: VirtualizedGrouping<Attachment>) => void;
|
||||||
|
close: () => void;
|
||||||
|
fwdRef: RefObject<ActionSheetRef>;
|
||||||
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const contextId = attachment.metadata.hash;
|
const contextId = attachment.hash;
|
||||||
const [filename, setFilename] = useState(attachment.metadata.filename);
|
const [filename, setFilename] = useState(attachment.filename);
|
||||||
const [currentProgress] = useAttachmentProgress(attachment);
|
const [currentProgress] = useAttachmentProgress(attachment);
|
||||||
const [failed, setFailed] = useState(attachment.failed);
|
const [failed, setFailed] = useState<string | undefined>(attachment.failed);
|
||||||
const [notes, setNotes] = useState([]);
|
const [notes, setNotes] = useState<Note[]>([]);
|
||||||
const [loading, setLoading] = useState({
|
const [loading, setLoading] = useState<{
|
||||||
name: null
|
name?: string;
|
||||||
});
|
}>({});
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
{
|
{
|
||||||
name: "Download",
|
name: "Download",
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
if (currentProgress) {
|
if (currentProgress) {
|
||||||
await db.fs().cancel(attachment.metadata.hash);
|
await db.fs().cancel(attachment.hash);
|
||||||
useAttachmentStore.getState().remove(attachment.metadata.hash);
|
useAttachmentStore.getState().remove(attachment.hash);
|
||||||
}
|
}
|
||||||
downloadAttachment(attachment.metadata.hash, false);
|
downloadAttachment(attachment.hash, false);
|
||||||
eSendEvent(eCloseSheet, contextId);
|
eSendEvent(eCloseSheet, contextId);
|
||||||
},
|
},
|
||||||
icon: "download"
|
icon: "download"
|
||||||
@@ -85,9 +101,9 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
|||||||
}
|
}
|
||||||
await picker.pick({
|
await picker.pick({
|
||||||
reupload: true,
|
reupload: true,
|
||||||
hash: attachment.metadata.hash,
|
hash: attachment.hash,
|
||||||
context: contextId,
|
context: contextId,
|
||||||
type: attachment.metadata.type
|
type: attachment.type
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: "upload"
|
icon: "upload"
|
||||||
@@ -98,7 +114,7 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
|||||||
setLoading({
|
setLoading({
|
||||||
name: "Run file check"
|
name: "Run file check"
|
||||||
});
|
});
|
||||||
let res = await filesystem.checkAttachment(attachment.metadata.hash);
|
let res = await filesystem.checkAttachment(attachment.hash);
|
||||||
if (res.failed) {
|
if (res.failed) {
|
||||||
db.attachments.markAsFailed(attachment.id, res.failed);
|
db.attachments.markAsFailed(attachment.id, res.failed);
|
||||||
setFailed(res.failed);
|
setFailed(res.failed);
|
||||||
@@ -108,8 +124,9 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
|||||||
context: "local"
|
context: "local"
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setFailed(null);
|
setFailed(undefined);
|
||||||
db.attachments.markAsFailed(attachment.id, null);
|
db.attachments.markAsFailed(attachment.id);
|
||||||
|
eSendEvent(eDBItemUpdate, attachment.id);
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: "File check passed",
|
heading: "File check passed",
|
||||||
type: "success",
|
type: "success",
|
||||||
@@ -119,7 +136,7 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
|||||||
|
|
||||||
setAttachments();
|
setAttachments();
|
||||||
setLoading({
|
setLoading({
|
||||||
name: null
|
name: undefined
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: "file-check"
|
icon: "file-check"
|
||||||
@@ -128,19 +145,20 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
|||||||
name: "Rename",
|
name: "Rename",
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
presentDialog({
|
presentDialog({
|
||||||
context: contextId,
|
context: contextId as any,
|
||||||
input: true,
|
input: true,
|
||||||
title: "Rename file",
|
title: "Rename file",
|
||||||
paragraph: "Enter a new name for the file",
|
paragraph: "Enter a new name for the file",
|
||||||
defaultValue: attachment.metadata.filename,
|
defaultValue: attachment.filename,
|
||||||
positivePress: async (value) => {
|
positivePress: async (value) => {
|
||||||
if (value && value.length > 0) {
|
if (value && value.length > 0) {
|
||||||
await db.attachments.add({
|
await db.attachments.add({
|
||||||
hash: attachment.metadata.hash,
|
hash: attachment.hash,
|
||||||
filename: value
|
filename: value
|
||||||
});
|
});
|
||||||
setFilename(value);
|
setFilename(value);
|
||||||
setAttachments();
|
setAttachments();
|
||||||
|
eSendEvent(eDBItemUpdate, attachment.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
positiveText: "Rename"
|
positiveText: "Rename"
|
||||||
@@ -151,34 +169,23 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
|||||||
{
|
{
|
||||||
name: "Delete",
|
name: "Delete",
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
await db.attachments.remove(attachment.metadata.hash, false);
|
await db.attachments.remove(attachment.hash, false);
|
||||||
setAttachments();
|
setAttachments();
|
||||||
|
eSendEvent(eDBItemUpdate, attachment.id);
|
||||||
close();
|
close();
|
||||||
},
|
},
|
||||||
icon: "delete-outline"
|
icon: "delete-outline"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const getNotes = useCallback(() => {
|
|
||||||
let allNotes = db.notes.all;
|
|
||||||
let attachmentNotes = attachment.noteIds?.map((id) => {
|
|
||||||
let index = allNotes?.findIndex((note) => id === note.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
return allNotes[index];
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
type: "notfound",
|
|
||||||
title: `Note with id ${id} does not exist.`,
|
|
||||||
id: id
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return attachmentNotes;
|
|
||||||
}, [attachment.noteIds]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNotes(getNotes());
|
db.relations
|
||||||
}, [attachment, getNotes]);
|
.to(attachment, "note")
|
||||||
|
.selector.items()
|
||||||
|
.then((items) => {
|
||||||
|
setNotes(items);
|
||||||
|
});
|
||||||
|
}, [attachment]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
@@ -221,7 +228,7 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
|||||||
}}
|
}}
|
||||||
color={colors.secondary.paragraph}
|
color={colors.secondary.paragraph}
|
||||||
>
|
>
|
||||||
{attachment.metadata.type}
|
{attachment.type}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
@@ -230,10 +237,10 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
|||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
color={colors.secondary.paragraph}
|
color={colors.secondary.paragraph}
|
||||||
>
|
>
|
||||||
{formatBytes(attachment.length)}
|
{formatBytes(attachment.size)}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
{attachment.noteIds ? (
|
{notes.length ? (
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
marginRight: 10
|
marginRight: 10
|
||||||
@@ -241,13 +248,13 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
|||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
color={colors.secondary.paragraph}
|
color={colors.secondary.paragraph}
|
||||||
>
|
>
|
||||||
{attachment.noteIds.length} note
|
{notes.length} note
|
||||||
{attachment.noteIds.length > 1 ? "s" : ""}
|
{notes.length > 1 ? "s" : ""}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
) : null}
|
) : null}
|
||||||
<Paragraph
|
<Paragraph
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
Clipboard.setString(attachment.metadata.hash);
|
Clipboard.setString(attachment.hash);
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
type: "success",
|
type: "success",
|
||||||
heading: "Attachment hash copied",
|
heading: "Attachment hash copied",
|
||||||
@@ -257,7 +264,7 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
|||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
color={colors.secondary.paragraph}
|
color={colors.secondary.paragraph}
|
||||||
>
|
>
|
||||||
{attachment.metadata.hash}
|
{attachment.hash}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -286,21 +293,11 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
|||||||
{notes.map((item) => (
|
{notes.map((item) => (
|
||||||
<PressableButton
|
<PressableButton
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
if (item.type === "notfound") {
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Note not found",
|
|
||||||
message:
|
|
||||||
"A note with the given id was not found. Maybe you have deleted the note or moved it to trash already.",
|
|
||||||
type: "error",
|
|
||||||
context: "local"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
eSendEvent(eCloseSheet, contextId);
|
eSendEvent(eCloseSheet, contextId);
|
||||||
await sleep(150);
|
await sleep(150);
|
||||||
eSendEvent(eCloseAttachmentDialog);
|
eSendEvent(eCloseAttachmentDialog);
|
||||||
await sleep(300);
|
await sleep(300);
|
||||||
openNote(item, item.type === "trash");
|
openNote(item, (item as any).type === "trash");
|
||||||
}}
|
}}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
@@ -321,17 +318,16 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
|||||||
<Button
|
<Button
|
||||||
key={item.name}
|
key={item.name}
|
||||||
buttonType={{
|
buttonType={{
|
||||||
text: item.on
|
text:
|
||||||
? colors.primary.accent
|
item.name === "Delete" || item.name === "PermDelete"
|
||||||
: item.name === "Delete" || item.name === "PermDelete"
|
? colors.error.paragraph
|
||||||
? colors.error.paragraph
|
: colors.primary.paragraph
|
||||||
: colors.primary.paragraph
|
|
||||||
}}
|
}}
|
||||||
onPress={item.onPress}
|
onPress={item.onPress}
|
||||||
title={item.name}
|
title={item.name}
|
||||||
icon={item.icon}
|
icon={item.icon}
|
||||||
loading={loading?.name === item.name}
|
loading={loading?.name === item.name}
|
||||||
type={item.on ? "shade" : "gray"}
|
type="gray"
|
||||||
fontSize={SIZE.sm}
|
fontSize={SIZE.sm}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
@@ -359,7 +355,11 @@ const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Actions.present = (attachment, set, context) => {
|
Actions.present = (
|
||||||
|
attachment: Attachment,
|
||||||
|
set: (attachments?: VirtualizedGrouping<Attachment>) => void,
|
||||||
|
context?: string
|
||||||
|
) => {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
context: context,
|
context: context,
|
||||||
component: (ref, close) => (
|
component: (ref, close) => (
|
||||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { formatBytes } from "@notesnook/common";
|
import { formatBytes } from "@notesnook/common";
|
||||||
import React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { TouchableOpacity, View } from "react-native";
|
import { TouchableOpacity, View } from "react-native";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
@@ -29,32 +29,47 @@ import { IconButton } from "../ui/icon-button";
|
|||||||
import { ProgressCircleComponent } from "../ui/svg/lazy";
|
import { ProgressCircleComponent } from "../ui/svg/lazy";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import Actions from "./actions";
|
import Actions from "./actions";
|
||||||
|
import { Attachment, VirtualizedGrouping } from "@notesnook/core";
|
||||||
|
import { useDBItem } from "../../hooks/use-db-item";
|
||||||
|
|
||||||
function getFileExtension(filename) {
|
function getFileExtension(filename: string) {
|
||||||
var ext = /^.+\.([^.]+)$/.exec(filename);
|
var ext = /^.+\.([^.]+)$/.exec(filename);
|
||||||
return ext == null ? "" : ext[1];
|
return ext == null ? "" : ext[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AttachmentItem = ({
|
export const AttachmentItem = ({
|
||||||
attachment,
|
id,
|
||||||
|
attachments,
|
||||||
encryption,
|
encryption,
|
||||||
setAttachments,
|
setAttachments,
|
||||||
pressable = true,
|
pressable = true,
|
||||||
hideWhenNotDownloading,
|
hideWhenNotDownloading,
|
||||||
context
|
context
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
attachments?: VirtualizedGrouping<Attachment>;
|
||||||
|
encryption?: boolean;
|
||||||
|
setAttachments: (attachments: any) => void;
|
||||||
|
pressable?: boolean;
|
||||||
|
hideWhenNotDownloading?: boolean;
|
||||||
|
context?: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
const [attachment] = useDBItem(id, "attachment", attachments?.item);
|
||||||
|
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
|
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
|
||||||
attachment,
|
attachment,
|
||||||
encryption
|
encryption
|
||||||
);
|
);
|
||||||
|
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
if (!pressable) return;
|
if (!pressable || !attachment) return;
|
||||||
Actions.present(attachment, setAttachments, context);
|
Actions.present(attachment, setAttachments, context);
|
||||||
};
|
};
|
||||||
|
|
||||||
return hideWhenNotDownloading &&
|
return (hideWhenNotDownloading &&
|
||||||
(!currentProgress || !currentProgress.value) ? null : (
|
(!currentProgress || !(currentProgress as any).value)) ||
|
||||||
|
!attachment ? null : (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
activeOpacity={0.9}
|
activeOpacity={0.9}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
@@ -67,7 +82,6 @@ export const AttachmentItem = ({
|
|||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
backgroundColor: colors.secondary.background
|
backgroundColor: colors.secondary.background
|
||||||
}}
|
}}
|
||||||
type="grayBg"
|
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -93,7 +107,7 @@ export const AttachmentItem = ({
|
|||||||
position: "absolute"
|
position: "absolute"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{getFileExtension(attachment.metadata.filename).toUpperCase()}
|
{getFileExtension(attachment.filename).toUpperCase()}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -113,14 +127,14 @@ export const AttachmentItem = ({
|
|||||||
lineBreakMode="middle"
|
lineBreakMode="middle"
|
||||||
color={colors.primary.paragraph}
|
color={colors.primary.paragraph}
|
||||||
>
|
>
|
||||||
{attachment.metadata.filename}
|
{attachment.filename}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
{!hideWhenNotDownloading ? (
|
{!hideWhenNotDownloading ? (
|
||||||
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
|
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
|
||||||
{formatBytes(attachment.length)}{" "}
|
{formatBytes(attachment.size)}{" "}
|
||||||
{currentProgress?.type
|
{(currentProgress as any)?.type
|
||||||
? "(" + currentProgress.type + "ing - tap to cancel)"
|
? "(" + (currentProgress as any).type + "ing - tap to cancel)"
|
||||||
: ""}
|
: ""}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -132,8 +146,8 @@ export const AttachmentItem = ({
|
|||||||
activeOpacity={0.9}
|
activeOpacity={0.9}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (encryption || !pressable) return;
|
if (encryption || !pressable) return;
|
||||||
db.fs.cancel(attachment.metadata.hash);
|
db.fs().cancel(attachment.metadata.hash);
|
||||||
setCurrentProgress(null);
|
setCurrentProgress(undefined);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
@@ -21,7 +21,10 @@ import React, { useRef, useState } from "react";
|
|||||||
import { Platform, View } from "react-native";
|
import { Platform, View } from "react-native";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import { downloadAttachments } from "../../common/filesystem/download-attachment";
|
import { downloadAttachments } from "../../common/filesystem/download-attachment";
|
||||||
import { presentSheet } from "../../services/event-manager";
|
import {
|
||||||
|
PresentSheetOptions,
|
||||||
|
presentSheet
|
||||||
|
} from "../../services/event-manager";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import Heading from "../ui/typography/heading";
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
@@ -29,8 +32,19 @@ import { ProgressBarComponent } from "../ui/svg/lazy";
|
|||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import { FlatList } from "react-native-actions-sheet";
|
import { FlatList } from "react-native-actions-sheet";
|
||||||
import { AttachmentItem } from "./attachment-item";
|
import { AttachmentItem } from "./attachment-item";
|
||||||
|
import { Attachment, VirtualizedGrouping } from "@notesnook/core";
|
||||||
|
|
||||||
const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
const DownloadAttachments = ({
|
||||||
|
close,
|
||||||
|
attachments,
|
||||||
|
isNote,
|
||||||
|
update
|
||||||
|
}: {
|
||||||
|
attachments: VirtualizedGrouping<Attachment>;
|
||||||
|
close?: ((ctx?: string | undefined) => void) | undefined;
|
||||||
|
isNote?: boolean;
|
||||||
|
update?: (props: PresentSheetOptions) => void;
|
||||||
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const [downloading, setDownloading] = useState(false);
|
const [downloading, setDownloading] = useState(false);
|
||||||
const [progress, setProgress] = useState({
|
const [progress, setProgress] = useState({
|
||||||
@@ -39,38 +53,40 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
|||||||
});
|
});
|
||||||
const [result, setResult] = useState(new Map());
|
const [result, setResult] = useState(new Map());
|
||||||
const canceled = useRef(false);
|
const canceled = useRef(false);
|
||||||
const groupId = useRef();
|
const groupId = useRef<string>();
|
||||||
|
|
||||||
const onDownload = async () => {
|
const onDownload = async () => {
|
||||||
update({
|
update?.({
|
||||||
disableClosing: true
|
disableClosing: true
|
||||||
});
|
} as PresentSheetOptions);
|
||||||
setDownloading(true);
|
setDownloading(true);
|
||||||
canceled.current = false;
|
canceled.current = false;
|
||||||
groupId.current = Date.now().toString();
|
groupId.current = Date.now().toString();
|
||||||
const result = await downloadAttachments(
|
const result = await downloadAttachments(
|
||||||
attachments,
|
attachments,
|
||||||
(progress, statusText) => setProgress({ value: progress, statusText }),
|
(progress: number, statusText: string) =>
|
||||||
|
setProgress({ value: progress, statusText }),
|
||||||
canceled,
|
canceled,
|
||||||
groupId.current
|
groupId.current
|
||||||
);
|
);
|
||||||
if (canceled.current) return;
|
if (canceled.current) return;
|
||||||
setResult(result || new Map());
|
setResult(result || new Map());
|
||||||
setDownloading(false);
|
setDownloading(false);
|
||||||
update({
|
update?.({
|
||||||
disableClosing: false
|
disableClosing: false
|
||||||
});
|
} as PresentSheetOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancel = async () => {
|
const cancel = async () => {
|
||||||
update({
|
update?.({
|
||||||
disableClosing: false
|
disableClosing: false
|
||||||
});
|
} as PresentSheetOptions);
|
||||||
canceled.current = true;
|
canceled.current = true;
|
||||||
|
if (!groupId.current) return;
|
||||||
console.log(groupId.current, "canceling groupId downloads");
|
console.log(groupId.current, "canceling groupId downloads");
|
||||||
await db.fs().cancel(groupId.current);
|
await db.fs().cancel(groupId.current, "download");
|
||||||
setDownloading(false);
|
setDownloading(false);
|
||||||
groupId.current = null;
|
groupId.current = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const successResults = () => {
|
const successResults = () => {
|
||||||
@@ -91,11 +107,11 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
|||||||
|
|
||||||
function getResultText() {
|
function getResultText() {
|
||||||
const downloadedAttachmentsCount =
|
const downloadedAttachmentsCount =
|
||||||
attachments.length - failedResults().length;
|
attachments?.ids?.length - failedResults().length;
|
||||||
if (downloadedAttachmentsCount === 0)
|
if (downloadedAttachmentsCount === 0)
|
||||||
return "Failed to download all attachments";
|
return "Failed to download all attachments";
|
||||||
return `Successfully downloaded ${downloadedAttachmentsCount}/${
|
return `Successfully downloaded ${downloadedAttachmentsCount}/${
|
||||||
attachments.length
|
attachments?.ids.length
|
||||||
} attachments as a zip file at ${
|
} attachments as a zip file at ${
|
||||||
Platform.OS === "android" ? "the selected folder" : "Notesnook/downloads"
|
Platform.OS === "android" ? "the selected folder" : "Notesnook/downloads"
|
||||||
}`;
|
}`;
|
||||||
@@ -157,7 +173,9 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
|||||||
width={null}
|
width={null}
|
||||||
animated={true}
|
animated={true}
|
||||||
useNativeDriver
|
useNativeDriver
|
||||||
progress={progress.value ? progress.value / attachments.length : 0}
|
progress={
|
||||||
|
progress.value ? progress.value / attachments.ids?.length : 0
|
||||||
|
}
|
||||||
unfilledColor={colors.secondary.background}
|
unfilledColor={colors.secondary.background}
|
||||||
color={colors.primary.accent}
|
color={colors.primary.accent}
|
||||||
borderWidth={0}
|
borderWidth={0}
|
||||||
@@ -174,7 +192,7 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
|||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
marginVertical: 12
|
marginVertical: 12
|
||||||
}}
|
}}
|
||||||
data={downloading ? attachments : []}
|
data={downloading ? attachments.ids : undefined}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -189,14 +207,15 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
|||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
keyExtractor={(item) => item.id}
|
keyExtractor={(item) => item as string}
|
||||||
renderItem={({ item }) => {
|
renderItem={({ item }) => {
|
||||||
return (
|
return (
|
||||||
<AttachmentItem
|
<AttachmentItem
|
||||||
attachment={item}
|
id={item as string}
|
||||||
setAttachments={() => {}}
|
setAttachments={() => {}}
|
||||||
pressable={false}
|
pressable={false}
|
||||||
hideWhenNotDownloading={true}
|
hideWhenNotDownloading={true}
|
||||||
|
attachments={attachments}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
@@ -209,7 +228,9 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
|||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
marginTop: 20
|
marginTop: 20
|
||||||
}}
|
}}
|
||||||
onPress={close}
|
onPress={() => {
|
||||||
|
close?.();
|
||||||
|
}}
|
||||||
type="accent"
|
type="accent"
|
||||||
title="Done"
|
title="Done"
|
||||||
/>
|
/>
|
||||||
@@ -227,7 +248,9 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
|||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
marginRight: 5
|
marginRight: 5
|
||||||
}}
|
}}
|
||||||
onPress={close}
|
onPress={() => {
|
||||||
|
close?.();
|
||||||
|
}}
|
||||||
type="grayBg"
|
type="grayBg"
|
||||||
title="No"
|
title="No"
|
||||||
/>
|
/>
|
||||||
@@ -258,7 +281,11 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
DownloadAttachments.present = (context, attachments, isNote) => {
|
DownloadAttachments.present = (
|
||||||
|
context: string,
|
||||||
|
attachments: VirtualizedGrouping<Attachment>,
|
||||||
|
isNote?: boolean
|
||||||
|
) => {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
context: context,
|
context: context,
|
||||||
component: (ref, close, update) => (
|
component: (ref, close, update) => (
|
||||||
@@ -17,15 +17,25 @@ 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 React, { useRef, useState } from "react";
|
import {
|
||||||
|
Attachment,
|
||||||
|
Note,
|
||||||
|
SortOptions,
|
||||||
|
VirtualizedGrouping
|
||||||
|
} from "@notesnook/core";
|
||||||
|
import { FilteredSelector } from "@notesnook/core/dist/database/sql-collection";
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { ActivityIndicator, ScrollView, View } from "react-native";
|
import { ActivityIndicator, ScrollView, View } from "react-native";
|
||||||
|
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import filesystem from "../../common/filesystem";
|
import filesystem from "../../common/filesystem";
|
||||||
import { presentSheet } from "../../services/event-manager";
|
import { presentSheet } from "../../services/event-manager";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
import SheetProvider from "../sheet-provider";
|
import SheetProvider from "../sheet-provider";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
import { IconButton } from "../ui/icon-button";
|
import { IconButton } from "../ui/icon-button";
|
||||||
import Input from "../ui/input";
|
import Input from "../ui/input";
|
||||||
import Seperator from "../ui/seperator";
|
import Seperator from "../ui/seperator";
|
||||||
@@ -33,82 +43,83 @@ import Heading from "../ui/typography/heading";
|
|||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { AttachmentItem } from "./attachment-item";
|
import { AttachmentItem } from "./attachment-item";
|
||||||
import DownloadAttachments from "./download-attachments";
|
import DownloadAttachments from "./download-attachments";
|
||||||
import { Button } from "../ui/button";
|
|
||||||
import {
|
|
||||||
isAudio,
|
|
||||||
isDocument,
|
|
||||||
isImage,
|
|
||||||
isVideo
|
|
||||||
} from "@notesnook/core/dist/utils/filename";
|
|
||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
|
||||||
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
|
||||||
|
|
||||||
export const AttachmentDialog = ({ note }) => {
|
const DEFAULT_SORTING: SortOptions = {
|
||||||
|
sortBy: "dateEdited",
|
||||||
|
sortDirection: "desc"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AttachmentDialog = ({ note }: { note?: Note }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const { height } = useSettingStore((state) => state.dimensions);
|
const { height } = useSettingStore((state) => state.dimensions);
|
||||||
const [attachments, setAttachments] = useState(
|
const [attachments, setAttachments] =
|
||||||
note
|
useState<VirtualizedGrouping<Attachment>>();
|
||||||
? db.attachments.ofNote(note?.id, "all")
|
const attachmentSearchValue = useRef<string>();
|
||||||
: [...(db.attachments.all || [])]
|
const searchTimer = useRef<NodeJS.Timeout>();
|
||||||
);
|
|
||||||
|
|
||||||
const attachmentSearchValue = useRef();
|
|
||||||
const searchTimer = useRef();
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [currentFilter, setCurrentFilter] = useState("all");
|
const [currentFilter, setCurrentFilter] = useState("all");
|
||||||
|
|
||||||
const onChangeText = (text) => {
|
const refresh = React.useCallback(() => {
|
||||||
const attachments = note?.id
|
if (note) {
|
||||||
? db.attachments.ofNote(note?.id, "all")
|
db.attachments.ofNote(note.id, "all").sorted(DEFAULT_SORTING);
|
||||||
: [...(db.attachments.all || [])];
|
} else {
|
||||||
|
db.attachments.all
|
||||||
|
.sorted(DEFAULT_SORTING)
|
||||||
|
.then((attachments) => setAttachments(attachments));
|
||||||
|
}
|
||||||
|
}, [note]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refresh();
|
||||||
|
}, [note, refresh]);
|
||||||
|
|
||||||
|
const onChangeText = (text: string) => {
|
||||||
attachmentSearchValue.current = text;
|
attachmentSearchValue.current = text;
|
||||||
if (
|
if (
|
||||||
!attachmentSearchValue.current ||
|
!attachmentSearchValue.current ||
|
||||||
attachmentSearchValue.current === ""
|
attachmentSearchValue.current === ""
|
||||||
) {
|
) {
|
||||||
setAttachments(filterAttachments(currentFilter));
|
setAttachments(filterAttachments(currentFilter));
|
||||||
|
refresh();
|
||||||
}
|
}
|
||||||
clearTimeout(searchTimer.current);
|
clearTimeout(searchTimer.current);
|
||||||
searchTimer.current = setTimeout(() => {
|
searchTimer.current = setTimeout(async () => {
|
||||||
let results = db.lookup.attachments(
|
let results = await db.lookup.attachments(
|
||||||
attachments,
|
attachmentSearchValue.current as string
|
||||||
attachmentSearchValue.current
|
|
||||||
);
|
);
|
||||||
if (results.length === 0) return;
|
if (results.length === 0) return;
|
||||||
|
|
||||||
setAttachments(filterAttachments(currentFilter, results));
|
setAttachments(filterAttachments(currentFilter));
|
||||||
|
setAttachments(results);
|
||||||
}, 300);
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderItem = ({ item }) => (
|
const renderItem = ({ item }: { item: string }) => (
|
||||||
<AttachmentItem
|
<AttachmentItem
|
||||||
setAttachments={() => {
|
setAttachments={() => {
|
||||||
setAttachments(filterAttachments(currentFilter));
|
setAttachments(filterAttachments(currentFilter));
|
||||||
}}
|
}}
|
||||||
attachment={item}
|
attachments={attachments}
|
||||||
|
id={item}
|
||||||
context="attachments-list"
|
context="attachments-list"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const onCheck = async () => {
|
const onCheck = async () => {
|
||||||
|
if (!attachments) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const checkedAttachments = [];
|
for (let id of attachments.ids) {
|
||||||
for (let attachment of attachments) {
|
const attachment = await attachments.item(id as string);
|
||||||
let result = await filesystem.checkAttachment(attachment.metadata.hash);
|
if (!attachment) continue;
|
||||||
|
|
||||||
|
let result = await filesystem.checkAttachment(attachment.hash);
|
||||||
if (result.failed) {
|
if (result.failed) {
|
||||||
await db.attachments.markAsFailed(
|
await db.attachments.markAsFailed(attachment.hash, result.failed);
|
||||||
attachment.metadata.hash,
|
|
||||||
result.failed
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
await db.attachments.markAsFailed(attachment.id, null);
|
await db.attachments.markAsFailed(id as string, undefined);
|
||||||
}
|
}
|
||||||
checkedAttachments.push(
|
|
||||||
db.attachments.attachment(attachment.metadata.hash)
|
|
||||||
);
|
|
||||||
setAttachments([...checkedAttachments]);
|
|
||||||
}
|
}
|
||||||
|
refresh();
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -135,33 +146,33 @@ export const AttachmentDialog = ({ note }) => {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const filterAttachments = (type, _attachments) => {
|
const filterAttachments = async (type: string) => {
|
||||||
const attachments = _attachments
|
let items: FilteredSelector<Attachment> = db.attachments.all;
|
||||||
? _attachments
|
|
||||||
: note
|
|
||||||
? db.attachments.ofNote(note?.id, "all")
|
|
||||||
: [...(db.attachments.all || [])];
|
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "all":
|
case "all":
|
||||||
return attachments;
|
items = db.attachments.all;
|
||||||
|
break;
|
||||||
case "images":
|
case "images":
|
||||||
return attachments.filter((attachment) =>
|
items = note
|
||||||
isImage(attachment.metadata.type)
|
? db.attachments.ofNote(note.id, "images")
|
||||||
);
|
: db.attachments.images;
|
||||||
|
break;
|
||||||
case "video":
|
case "video":
|
||||||
return attachments.filter((attachment) =>
|
items = items = note
|
||||||
isVideo(attachment.metadata.type)
|
? db.attachments.ofNote(note.id, "all")
|
||||||
);
|
: db.attachments.videos;
|
||||||
|
break;
|
||||||
case "audio":
|
case "audio":
|
||||||
return attachments.filter((attachment) =>
|
items = db.attachments.all;
|
||||||
isAudio(attachment.metadata.type)
|
break;
|
||||||
);
|
|
||||||
case "documents":
|
case "documents":
|
||||||
return attachments.filter((attachment) =>
|
items = note
|
||||||
isDocument(attachment.metadata.type)
|
? db.attachments.ofNote(note.id, "all")
|
||||||
);
|
: db.attachments.documents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return await items.sorted(DEFAULT_SORTING);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -219,6 +230,7 @@ export const AttachmentDialog = ({ note }) => {
|
|||||||
}}
|
}}
|
||||||
color={colors.primary.paragraph}
|
color={colors.primary.paragraph}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
if (!attachments) return;
|
||||||
DownloadAttachments.present(
|
DownloadAttachments.present(
|
||||||
"attachments-list",
|
"attachments-list",
|
||||||
attachments,
|
attachments,
|
||||||
@@ -235,7 +247,7 @@ export const AttachmentDialog = ({ note }) => {
|
|||||||
placeholder="Filter attachments by filename, type or hash"
|
placeholder="Filter attachments by filename, type or hash"
|
||||||
onChangeText={onChangeText}
|
onChangeText={onChangeText}
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
onChangeText(attachmentSearchValue.current);
|
onChangeText(attachmentSearchValue.current as string);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -257,10 +269,7 @@ export const AttachmentDialog = ({ note }) => {
|
|||||||
<Button
|
<Button
|
||||||
type={currentFilter === item.filterBy ? "grayAccent" : "gray"}
|
type={currentFilter === item.filterBy ? "grayAccent" : "gray"}
|
||||||
key={item.title}
|
key={item.title}
|
||||||
title={
|
title={item.title}
|
||||||
item.title +
|
|
||||||
` (${filterAttachments(item.filterBy)?.length || 0})`
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
@@ -270,9 +279,9 @@ export const AttachmentDialog = ({ note }) => {
|
|||||||
? "transparent"
|
? "transparent"
|
||||||
: colors.primary.accent
|
: colors.primary.accent
|
||||||
}}
|
}}
|
||||||
onPress={() => {
|
onPress={async () => {
|
||||||
setCurrentFilter(item.filterBy);
|
setCurrentFilter(item.filterBy);
|
||||||
setAttachments(filterAttachments(item.filterBy));
|
setAttachments(await filterAttachments(item.filterBy));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -303,7 +312,7 @@ export const AttachmentDialog = ({ note }) => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
estimatedItemSize={50}
|
estimatedItemSize={50}
|
||||||
data={attachments}
|
data={attachments?.ids as string[]}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -326,7 +335,7 @@ export const AttachmentDialog = ({ note }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
AttachmentDialog.present = (note) => {
|
AttachmentDialog.present = (note?: Note) => {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: () => <AttachmentDialog note={note} />
|
component: () => <AttachmentDialog note={note} />
|
||||||
});
|
});
|
||||||
@@ -119,7 +119,7 @@ const FloatingButton = ({
|
|||||||
<PressableButton
|
<PressableButton
|
||||||
testID={notesnook.buttons.add}
|
testID={notesnook.buttons.add}
|
||||||
type="accent"
|
type="accent"
|
||||||
accentColor={colors.static[color as keyof typeof colors.static]}
|
accentColor={color}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
...getElevationStyle(5),
|
...getElevationStyle(5),
|
||||||
borderRadius: 100
|
borderRadius: 100
|
||||||
|
|||||||
@@ -17,15 +17,17 @@ 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 { useThemeColors } from "@notesnook/theme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useNoteStore } from "../../stores/use-notes-store";
|
import { useNoteStore } from "../../stores/use-notes-store";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { AnnouncementDialog } from "../announcements";
|
import { AnnouncementDialog } from "../announcements";
|
||||||
import AuthModal from "../auth/auth-modal";
|
import AuthModal from "../auth/auth-modal";
|
||||||
import { SessionExpired } from "../auth/session-expired";
|
import { SessionExpired } from "../auth/session-expired";
|
||||||
import { Dialog } from "../dialog";
|
import { Dialog } from "../dialog";
|
||||||
import { AddTopicDialog } from "../dialogs/add-topic";
|
import { AddTopicDialog } from "../dialogs/add-topic";
|
||||||
|
import JumpToSectionDialog from "../dialogs/jump-to-section";
|
||||||
import { LoadingDialog } from "../dialogs/loading";
|
import { LoadingDialog } from "../dialogs/loading";
|
||||||
|
import PDFPreview from "../dialogs/pdf-preview";
|
||||||
import ResultDialog from "../dialogs/result";
|
import ResultDialog from "../dialogs/result";
|
||||||
import { VaultDialog } from "../dialogs/vault";
|
import { VaultDialog } from "../dialogs/vault";
|
||||||
import ImagePreview from "../image-preview";
|
import ImagePreview from "../image-preview";
|
||||||
@@ -36,7 +38,6 @@ import SheetProvider from "../sheet-provider";
|
|||||||
import RateAppSheet from "../sheets/rate-app";
|
import RateAppSheet from "../sheets/rate-app";
|
||||||
import RecoveryKeySheet from "../sheets/recovery-key";
|
import RecoveryKeySheet from "../sheets/recovery-key";
|
||||||
import RestoreDataSheet from "../sheets/restore-data";
|
import RestoreDataSheet from "../sheets/restore-data";
|
||||||
import PDFPreview from "../dialogs/pdf-preview";
|
|
||||||
|
|
||||||
const DialogProvider = () => {
|
const DialogProvider = () => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
@@ -46,7 +47,6 @@ const DialogProvider = () => {
|
|||||||
<>
|
<>
|
||||||
<LoadingDialog />
|
<LoadingDialog />
|
||||||
<Dialog context="global" />
|
<Dialog context="global" />
|
||||||
<AddTopicDialog colors={colors} />
|
|
||||||
<PremiumDialog colors={colors} />
|
<PremiumDialog colors={colors} />
|
||||||
<AuthModal colors={colors} />
|
<AuthModal colors={colors} />
|
||||||
<MergeConflicts />
|
<MergeConflicts />
|
||||||
@@ -62,6 +62,7 @@ const DialogProvider = () => {
|
|||||||
<AnnouncementDialog />
|
<AnnouncementDialog />
|
||||||
<SessionExpired />
|
<SessionExpired />
|
||||||
<PDFPreview />
|
<PDFPreview />
|
||||||
|
<JumpToSectionDialog />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,11 +21,9 @@ import { eSendEvent } from "../../services/event-manager";
|
|||||||
import {
|
import {
|
||||||
eCloseActionSheet,
|
eCloseActionSheet,
|
||||||
eCloseAddNotebookDialog,
|
eCloseAddNotebookDialog,
|
||||||
eCloseAddTopicDialog,
|
|
||||||
eCloseMoveNoteDialog,
|
eCloseMoveNoteDialog,
|
||||||
eOpenActionSheet,
|
eOpenActionSheet,
|
||||||
eOpenAddNotebookDialog,
|
eOpenAddNotebookDialog,
|
||||||
eOpenAddTopicDialog,
|
|
||||||
eOpenMoveNoteDialog
|
eOpenMoveNoteDialog
|
||||||
} from "../../utils/events";
|
} from "../../utils/events";
|
||||||
|
|
||||||
@@ -52,9 +50,3 @@ export const AddNotebookEvent = (notebook) => {
|
|||||||
export const HideAddNotebookEvent = (notebook) => {
|
export const HideAddNotebookEvent = (notebook) => {
|
||||||
eSendEvent(eCloseAddNotebookDialog, notebook);
|
eSendEvent(eCloseAddNotebookDialog, notebook);
|
||||||
};
|
};
|
||||||
export const AddTopicEvent = (topic) => {
|
|
||||||
eSendEvent(eOpenAddTopicDialog, topic);
|
|
||||||
};
|
|
||||||
export const HideAddTopicEvent = (notebook) => {
|
|
||||||
eSendEvent(eCloseAddTopicDialog, notebook);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,194 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of the Notesnook project (https://notesnook.com/)
|
|
||||||
|
|
||||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { createRef } from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
|
|
||||||
import { useMenuStore } from "../../../stores/use-menu-store";
|
|
||||||
import {
|
|
||||||
eSendEvent,
|
|
||||||
eSubscribeEvent,
|
|
||||||
eUnSubscribeEvent,
|
|
||||||
ToastManager
|
|
||||||
} from "../../../services/event-manager";
|
|
||||||
import Navigation from "../../../services/navigation";
|
|
||||||
import { db } from "../../../common/database";
|
|
||||||
import {
|
|
||||||
eCloseAddTopicDialog,
|
|
||||||
eOnTopicSheetUpdate,
|
|
||||||
eOpenAddTopicDialog
|
|
||||||
} from "../../../utils/events";
|
|
||||||
import { sleep } from "../../../utils/time";
|
|
||||||
import BaseDialog from "../../dialog/base-dialog";
|
|
||||||
import DialogButtons from "../../dialog/dialog-buttons";
|
|
||||||
import DialogContainer from "../../dialog/dialog-container";
|
|
||||||
import DialogHeader from "../../dialog/dialog-header";
|
|
||||||
import Input from "../../ui/input";
|
|
||||||
import Seperator from "../../ui/seperator";
|
|
||||||
import { Toast } from "../../toast";
|
|
||||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
|
||||||
|
|
||||||
export class AddTopicDialog extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
visible: false,
|
|
||||||
titleFocused: false,
|
|
||||||
loading: false
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ref = createRef();
|
|
||||||
this.title;
|
|
||||||
this.titleRef = createRef();
|
|
||||||
this.notebook = null;
|
|
||||||
this.toEdit = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
addNewTopic = async () => {
|
|
||||||
try {
|
|
||||||
if (!this.title || this.title?.trim() === "") {
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Topic title is required",
|
|
||||||
type: "error",
|
|
||||||
context: "local"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.toEdit) {
|
|
||||||
await db.notebooks.topics(this.notebook.id).add({
|
|
||||||
title: this.title
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
let topic = this.toEdit;
|
|
||||||
topic.title = this.title;
|
|
||||||
await db.notebooks.topics(topic.notebookId).add({
|
|
||||||
id: topic.id,
|
|
||||||
title: topic.title
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.close();
|
|
||||||
setTimeout(() => {
|
|
||||||
Navigation.queueRoutesForUpdate();
|
|
||||||
useMenuStore.getState().setMenuPins();
|
|
||||||
});
|
|
||||||
eSendEvent(eOnTopicSheetUpdate);
|
|
||||||
useRelationStore.getState().update();
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
eSubscribeEvent(eOpenAddTopicDialog, this.open);
|
|
||||||
eSubscribeEvent(eCloseAddTopicDialog, this.close);
|
|
||||||
}
|
|
||||||
componentWillUnmount() {
|
|
||||||
eUnSubscribeEvent(eOpenAddTopicDialog, this.open);
|
|
||||||
eUnSubscribeEvent(eCloseAddTopicDialog, this.close);
|
|
||||||
}
|
|
||||||
|
|
||||||
open = async ({ notebookId, toEdit }) => {
|
|
||||||
let id = notebookId;
|
|
||||||
if (id) {
|
|
||||||
this.notebook = await db.notebooks.notebook(id).data;
|
|
||||||
}
|
|
||||||
this.toEdit = toEdit;
|
|
||||||
|
|
||||||
if (this.toEdit) {
|
|
||||||
this.title = this.toEdit.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
visible: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
close = () => {
|
|
||||||
this.title = null;
|
|
||||||
this.notebook = null;
|
|
||||||
this.toEdit = null;
|
|
||||||
this.setState({
|
|
||||||
visible: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { visible } = this.state;
|
|
||||||
if (!visible) return null;
|
|
||||||
return (
|
|
||||||
<BaseDialog
|
|
||||||
onShow={async () => {
|
|
||||||
if (this.toEdit) {
|
|
||||||
this.titleRef.current?.setNativeProps({
|
|
||||||
text: this.toEdit.title
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ref.current?.animateNextTransition();
|
|
||||||
await sleep(300);
|
|
||||||
this.titleRef.current?.focus();
|
|
||||||
}}
|
|
||||||
bounce={false}
|
|
||||||
statusBarTranslucent={false}
|
|
||||||
visible={true}
|
|
||||||
onRequestClose={this.close}
|
|
||||||
>
|
|
||||||
<DialogContainer>
|
|
||||||
<DialogHeader
|
|
||||||
icon="book-outline"
|
|
||||||
title={this.toEdit ? "Edit topic" : "New topic"}
|
|
||||||
paragraph={
|
|
||||||
this.toEdit
|
|
||||||
? "Edit title of the topic"
|
|
||||||
: "Add a new topic in " + this.notebook.title
|
|
||||||
}
|
|
||||||
padding={12}
|
|
||||||
/>
|
|
||||||
<Seperator half />
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
zIndex: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
fwdRef={this.titleRef}
|
|
||||||
testID="input-title"
|
|
||||||
onChangeText={(value) => {
|
|
||||||
this.title = value;
|
|
||||||
}}
|
|
||||||
blurOnSubmit={false}
|
|
||||||
placeholder="Enter title"
|
|
||||||
onSubmit={() => this.addNewTopic()}
|
|
||||||
returnKeyLabel="Done"
|
|
||||||
returnKeyType="done"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<DialogButtons
|
|
||||||
positiveTitle={this.toEdit ? "Save" : "Add"}
|
|
||||||
onPressNegative={() => this.close()}
|
|
||||||
onPressPositive={() => this.addNewTopic()}
|
|
||||||
/>
|
|
||||||
</DialogContainer>
|
|
||||||
<Toast context="local" />
|
|
||||||
</BaseDialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,15 +17,22 @@ 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 React, { useEffect, useState } from "react";
|
import { GroupHeader, Item, VirtualizedGrouping } from "@notesnook/core";
|
||||||
import { ScrollView, View } from "react-native";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
|
import React, {
|
||||||
|
RefObject,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState
|
||||||
|
} from "react";
|
||||||
|
import { FlatList, ScrollView, View } from "react-native";
|
||||||
import { DDS } from "../../../services/device-detection";
|
import { DDS } from "../../../services/device-detection";
|
||||||
import {
|
import {
|
||||||
eSubscribeEvent,
|
eSubscribeEvent,
|
||||||
eUnSubscribeEvent
|
eUnSubscribeEvent
|
||||||
} from "../../../services/event-manager";
|
} from "../../../services/event-manager";
|
||||||
import { useMessageStore } from "../../../stores/use-message-store";
|
import { useMessageStore } from "../../../stores/use-message-store";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { getElevationStyle } from "../../../utils/elevation";
|
import { getElevationStyle } from "../../../utils/elevation";
|
||||||
import {
|
import {
|
||||||
eCloseJumpToDialog,
|
eCloseJumpToDialog,
|
||||||
@@ -36,27 +43,47 @@ import { SIZE } from "../../../utils/size";
|
|||||||
import BaseDialog from "../../dialog/base-dialog";
|
import BaseDialog from "../../dialog/base-dialog";
|
||||||
import { PressableButton } from "../../ui/pressable";
|
import { PressableButton } from "../../ui/pressable";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
import { useCallback } from "react";
|
|
||||||
|
|
||||||
const offsets = [];
|
const JumpToSectionDialog = () => {
|
||||||
let timeout = null;
|
const scrollRef = useRef<RefObject<FlatList>>();
|
||||||
const JumpToSectionDialog = ({ scrollRef, data, type }) => {
|
const [data, setData] = useState<VirtualizedGrouping<Item>>();
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const notes = data;
|
const notes = data;
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
const offsets = useRef<number[]>([]);
|
||||||
|
const timeout = useRef<NodeJS.Timeout>();
|
||||||
|
|
||||||
const onPress = (item) => {
|
const onPress = (item: GroupHeader) => {
|
||||||
let ind = notes.findIndex(
|
const index = notes?.ids?.findIndex((i) => {
|
||||||
(i) => i.title === item.title && i.type === "header"
|
if (typeof i === "object") {
|
||||||
);
|
return i.title === item.title && i.type === "header";
|
||||||
scrollRef.current?.scrollToIndex({
|
} else {
|
||||||
index: ind,
|
false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
scrollRef.current?.current?.scrollToIndex({
|
||||||
|
index: index as number,
|
||||||
animated: true
|
animated: true
|
||||||
});
|
});
|
||||||
close();
|
close();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const open = useCallback(
|
||||||
|
({
|
||||||
|
data,
|
||||||
|
ref
|
||||||
|
}: {
|
||||||
|
data: VirtualizedGrouping<Item>;
|
||||||
|
ref: RefObject<FlatList>;
|
||||||
|
}) => {
|
||||||
|
setData(data);
|
||||||
|
scrollRef.current = ref;
|
||||||
|
setVisible(true);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSubscribeEvent(eOpenJumpToDialog, open);
|
eSubscribeEvent(eOpenJumpToDialog, open);
|
||||||
eSubscribeEvent(eCloseJumpToDialog, close);
|
eSubscribeEvent(eCloseJumpToDialog, close);
|
||||||
@@ -69,50 +96,52 @@ const JumpToSectionDialog = ({ scrollRef, data, type }) => {
|
|||||||
};
|
};
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
const onScroll = (data) => {
|
const onScroll = (data: { x: number; y: number }) => {
|
||||||
let y = data.y;
|
const y = data.y;
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout.current);
|
||||||
timeout = null;
|
timeout.current = undefined;
|
||||||
}
|
}
|
||||||
timeout = setTimeout(() => {
|
timeout.current = setTimeout(() => {
|
||||||
let index = offsets.findIndex((o, i) => o <= y && offsets[i + 1] > y);
|
setCurrentIndex(
|
||||||
setCurrentIndex(index || 0);
|
offsets.current?.findIndex(
|
||||||
|
(o, i) => o <= y && offsets.current[i + 1] > y
|
||||||
|
) || 0
|
||||||
|
);
|
||||||
}, 200);
|
}, 200);
|
||||||
};
|
};
|
||||||
|
|
||||||
const open = useCallback(
|
|
||||||
(_type) => {
|
|
||||||
if (_type !== type) return;
|
|
||||||
setVisible(true);
|
|
||||||
},
|
|
||||||
[type]
|
|
||||||
);
|
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadOffsets = useCallback(() => {
|
||||||
|
notes?.ids
|
||||||
|
.filter((i) => typeof i === "object" && i.type === "header")
|
||||||
|
.map((item, index) => {
|
||||||
|
if (typeof item === "string") return;
|
||||||
|
|
||||||
|
let offset = 35 * index;
|
||||||
|
let ind = notes.ids.findIndex(
|
||||||
|
(i) =>
|
||||||
|
typeof i === "object" &&
|
||||||
|
i.title === item.title &&
|
||||||
|
i.type === "header"
|
||||||
|
);
|
||||||
|
const messageState = useMessageStore.getState().message;
|
||||||
|
const msgOffset = messageState?.visible ? 60 : 10;
|
||||||
|
|
||||||
|
ind = ind + 1;
|
||||||
|
ind = ind - (index + 1);
|
||||||
|
offset = offset + ind * 100 + msgOffset;
|
||||||
|
offsets.current.push(offset);
|
||||||
|
});
|
||||||
|
}, [notes]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadOffsets();
|
loadOffsets();
|
||||||
}, [loadOffsets, notes]);
|
}, [loadOffsets, notes]);
|
||||||
|
|
||||||
const loadOffsets = useCallback(() => {
|
|
||||||
notes
|
|
||||||
.filter((i) => i.type === "header")
|
|
||||||
.map((item, index) => {
|
|
||||||
let offset = 35 * index;
|
|
||||||
let ind = notes.findIndex(
|
|
||||||
(i) => i.title === item.title && i.type === "header"
|
|
||||||
);
|
|
||||||
let messageState = useMessageStore.getState().message;
|
|
||||||
let msgOffset = messageState?.visible ? 60 : 10;
|
|
||||||
ind = ind + 1;
|
|
||||||
ind = ind - (index + 1);
|
|
||||||
offset = offset + ind * 100 + msgOffset;
|
|
||||||
offsets.push(offset);
|
|
||||||
});
|
|
||||||
}, [notes]);
|
|
||||||
return !visible ? null : (
|
return !visible ? null : (
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
onShow={() => {
|
onShow={() => {
|
||||||
@@ -149,13 +178,13 @@ const JumpToSectionDialog = ({ scrollRef, data, type }) => {
|
|||||||
paddingBottom: 20
|
paddingBottom: 20
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{notes
|
{notes?.ids
|
||||||
.filter((i) => i.type === "header")
|
.filter((i) => typeof i === "object" && i.type === "header")
|
||||||
.map((item, index) => {
|
.map((item, index) => {
|
||||||
return item.title ? (
|
return typeof item === "object" && item.title ? (
|
||||||
<PressableButton
|
<PressableButton
|
||||||
key={item.title}
|
key={item.title}
|
||||||
onPress={() => onPress(item, index)}
|
onPress={() => onPress(item)}
|
||||||
type={currentIndex === index ? "selected" : "transparent"}
|
type={currentIndex === index ? "selected" : "transparent"}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
minWidth: "20%",
|
minWidth: "20%",
|
||||||
@@ -42,7 +42,6 @@ const ImagePreview = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSubscribeEvent("ImagePreview", open);
|
eSubscribeEvent("ImagePreview", open);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
eUnSubscribeEvent("ImagePreview", open);
|
eUnSubscribeEvent("ImagePreview", open);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,57 +17,39 @@ 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 { useThemeColors } from "@notesnook/theme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { useMessageStore } from "../../../stores/use-message-store";
|
import { useMessageStore } from "../../../stores/use-message-store";
|
||||||
import { ColorValues } from "../../../utils/colors";
|
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||||
import { Announcement } from "../../announcements/announcement";
|
import { Announcement } from "../../announcements/announcement";
|
||||||
import { Card } from "../../list/card";
|
import { Card } from "../../list/card";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
export type ListHeaderProps = {
|
||||||
import { SIZE } from "../../../utils/size";
|
noAnnouncement?: boolean;
|
||||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
color?: string;
|
||||||
|
messageCard?: boolean;
|
||||||
|
screen?: string;
|
||||||
|
shouldShow?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export const Header = React.memo(
|
export const Header = React.memo(
|
||||||
({
|
({
|
||||||
type,
|
|
||||||
messageCard = true,
|
messageCard = true,
|
||||||
color,
|
color,
|
||||||
shouldShow = false,
|
shouldShow = false,
|
||||||
noAnnouncement,
|
noAnnouncement,
|
||||||
warning
|
screen
|
||||||
}) => {
|
}: ListHeaderProps) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const announcements = useMessageStore((state) => state.announcements);
|
const announcements = useMessageStore((state) => state.announcements);
|
||||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||||
|
|
||||||
return selectionMode ? null : (
|
return selectionMode ? null : (
|
||||||
<>
|
<>
|
||||||
{warning ? (
|
{announcements.length !== 0 && !noAnnouncement ? (
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
padding: 12,
|
|
||||||
backgroundColor: colors.error.background,
|
|
||||||
width: "95%",
|
|
||||||
alignSelf: "center",
|
|
||||||
borderRadius: 5,
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name="sync-alert"
|
|
||||||
size={SIZE.md}
|
|
||||||
color={colors.error.icon}
|
|
||||||
f
|
|
||||||
/>
|
|
||||||
<Paragraph style={{ marginLeft: 5 }} color={colors.error.icon}>
|
|
||||||
{warning.title}
|
|
||||||
</Paragraph>
|
|
||||||
</View>
|
|
||||||
) : announcements.length !== 0 && !noAnnouncement ? (
|
|
||||||
<Announcement color={color || colors.primary.accent} />
|
<Announcement color={color || colors.primary.accent} />
|
||||||
) : type === "search" ? null : !shouldShow ? (
|
) : (screen as any) === "Search" ? null : !shouldShow ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
marginBottom: 5,
|
marginBottom: 5,
|
||||||
@@ -78,11 +60,7 @@ export const Header = React.memo(
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{messageCard ? (
|
{messageCard ? (
|
||||||
<Card
|
<Card color={color || colors.primary.accent} />
|
||||||
color={
|
|
||||||
ColorValues[color?.toLowerCase()] || colors.primary.accent
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -17,27 +17,33 @@ 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 { useRef, useState } from "react";
|
import { Notebook } from "@notesnook/core";
|
||||||
import React from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import { useMenuStore } from "../../../stores/use-menu-store";
|
import React, { useState } from "react";
|
||||||
import { ToastManager } from "../../../services/event-manager";
|
import { View } from "react-native";
|
||||||
import { getTotalNotes } from "@notesnook/common";
|
|
||||||
import { db } from "../../../common/database";
|
import { db } from "../../../common/database";
|
||||||
|
import { ToastManager } from "../../../services/event-manager";
|
||||||
|
import { useMenuStore } from "../../../stores/use-menu-store";
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
import { IconButton } from "../../ui/icon-button";
|
import { IconButton } from "../../ui/icon-button";
|
||||||
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 { getFormattedDate } from "@notesnook/common";
|
||||||
|
|
||||||
export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
export const NotebookHeader = ({
|
||||||
|
notebook,
|
||||||
|
onEditNotebook,
|
||||||
|
totalNotes = 0
|
||||||
|
}: {
|
||||||
|
notebook: Notebook;
|
||||||
|
onEditNotebook: () => void;
|
||||||
|
totalNotes: number;
|
||||||
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const [isPinnedToMenu, setIsPinnedToMenu] = useState(
|
const [isPinnedToMenu, setIsPinnedToMenu] = useState(
|
||||||
db.shortcuts.exists(notebook.id)
|
db.shortcuts.exists(notebook.id)
|
||||||
);
|
);
|
||||||
const setMenuPins = useMenuStore((state) => state.setMenuPins);
|
const setMenuPins = useMenuStore((state) => state.setMenuPins);
|
||||||
const totalNotes = getTotalNotes(notebook);
|
|
||||||
const shortcutRef = useRef();
|
|
||||||
|
|
||||||
const onPinNotebook = async () => {
|
const onPinNotebook = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -76,7 +82,7 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
|
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
|
||||||
{new Date(notebook.dateEdited).toLocaleString()}
|
{getFormattedDate(notebook.dateModified, "date-time")}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -103,7 +109,6 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
|||||||
name={isPinnedToMenu ? "link-variant-off" : "link-variant"}
|
name={isPinnedToMenu ? "link-variant-off" : "link-variant"}
|
||||||
onPress={onPinNotebook}
|
onPress={onPinNotebook}
|
||||||
tooltipText={"Create shortcut in side menu"}
|
tooltipText={"Create shortcut in side menu"}
|
||||||
fwdRef={shortcutRef}
|
|
||||||
customStyle={{
|
customStyle={{
|
||||||
marginRight: 15,
|
marginRight: 15,
|
||||||
width: 40,
|
width: 40,
|
||||||
@@ -138,15 +143,15 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
|||||||
style={{
|
style={{
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
fontStyle: "italic",
|
fontStyle: "italic",
|
||||||
fontFamily: null
|
fontFamily: undefined
|
||||||
}}
|
}}
|
||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
color={colors.secondary.paragraph}
|
color={colors.secondary.paragraph}
|
||||||
>
|
>
|
||||||
{notebook.topics.length === 1
|
{/* {notebook.topics.length === 1
|
||||||
? "1 topic"
|
? "1 topic"
|
||||||
: `${notebook.topics.length} topics`}
|
: `${notebook.topics.length} topics`}
|
||||||
,{" "}
|
,{" "} */}
|
||||||
{notebook && totalNotes > 1
|
{notebook && totalNotes > 1
|
||||||
? totalNotes + " notes"
|
? totalNotes + " notes"
|
||||||
: totalNotes === 1
|
: totalNotes === 1
|
||||||
@@ -17,39 +17,52 @@ 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 { GroupHeader, GroupOptions, ItemType } from "@notesnook/core";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useRef } from "react";
|
import React from "react";
|
||||||
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
|
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
|
||||||
import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
|
||||||
|
import { presentSheet } from "../../../services/event-manager";
|
||||||
import SettingsService from "../../../services/settings";
|
import SettingsService from "../../../services/settings";
|
||||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
import { RouteName } from "../../../stores/use-navigation-store";
|
||||||
import { ColorValues } from "../../../utils/colors";
|
import { ColorValues } from "../../../utils/colors";
|
||||||
import { GROUP } from "../../../utils/constants";
|
import { GROUP } from "../../../utils/constants";
|
||||||
import { eOpenJumpToDialog } from "../../../utils/events";
|
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
import Sort from "../../sheets/sort";
|
import Sort from "../../sheets/sort";
|
||||||
import { Button } from "../../ui/button";
|
import { Button } from "../../ui/button";
|
||||||
import { IconButton } from "../../ui/icon-button";
|
import { IconButton } from "../../ui/icon-button";
|
||||||
import Heading from "../../ui/typography/heading";
|
import Heading from "../../ui/typography/heading";
|
||||||
|
|
||||||
export const SectionHeader = React.memo(
|
type SectionHeaderProps = {
|
||||||
function SectionHeader({ item, index, type, color, screen, groupOptions }) {
|
item: GroupHeader;
|
||||||
|
index: number;
|
||||||
|
dataType: ItemType;
|
||||||
|
color?: string;
|
||||||
|
screen?: RouteName;
|
||||||
|
groupOptions: GroupOptions;
|
||||||
|
onOpenJumpToDialog: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SectionHeader = React.memo<
|
||||||
|
React.FunctionComponent<SectionHeaderProps>
|
||||||
|
>(
|
||||||
|
function SectionHeader({
|
||||||
|
item,
|
||||||
|
index,
|
||||||
|
dataType,
|
||||||
|
color,
|
||||||
|
screen,
|
||||||
|
groupOptions,
|
||||||
|
onOpenJumpToDialog
|
||||||
|
}: SectionHeaderProps) {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const { fontScale } = useWindowDimensions();
|
const { fontScale } = useWindowDimensions();
|
||||||
let groupBy = Object.keys(GROUP).find(
|
let groupBy = Object.keys(GROUP).find(
|
||||||
(key) => GROUP[key] === groupOptions.groupBy
|
(key) => GROUP[key as keyof typeof GROUP] === groupOptions.groupBy
|
||||||
);
|
);
|
||||||
const jumpToRef = useRef();
|
const isCompactModeEnabled = useIsCompactModeEnabled(
|
||||||
const sortRef = useRef();
|
dataType as "note" | "notebook"
|
||||||
const compactModeRef = useRef();
|
|
||||||
|
|
||||||
const notebooksListMode = useSettingStore(
|
|
||||||
(state) => state.settings.notebooksListMode
|
|
||||||
);
|
);
|
||||||
const notesListMode = useSettingStore(
|
|
||||||
(state) => state.settings.notesListMode
|
|
||||||
);
|
|
||||||
const listMode = type === "notebooks" ? notebooksListMode : notesListMode;
|
|
||||||
|
|
||||||
groupBy = !groupBy
|
groupBy = !groupBy
|
||||||
? "Default"
|
? "Default"
|
||||||
@@ -72,9 +85,8 @@ export const SectionHeader = React.memo(
|
|||||||
>
|
>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
eSendEvent(eOpenJumpToDialog, type);
|
onOpenJumpToDialog();
|
||||||
}}
|
}}
|
||||||
ref={jumpToRef}
|
|
||||||
activeOpacity={0.9}
|
activeOpacity={0.9}
|
||||||
hitSlop={{ top: 10, left: 10, right: 30, bottom: 15 }}
|
hitSlop={{ top: 10, left: 10, right: 30, bottom: 15 }}
|
||||||
style={{
|
style={{
|
||||||
@@ -83,7 +95,10 @@ export const SectionHeader = React.memo(
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading
|
<Heading
|
||||||
color={ColorValues[color?.toLowerCase()] || colors.primary.accent}
|
color={
|
||||||
|
ColorValues[color?.toLowerCase() as keyof typeof ColorValues] ||
|
||||||
|
colors.primary.accent
|
||||||
|
}
|
||||||
size={SIZE.sm}
|
size={SIZE.sm}
|
||||||
style={{
|
style={{
|
||||||
minWidth: 60,
|
minWidth: 60,
|
||||||
@@ -106,11 +121,10 @@ export const SectionHeader = React.memo(
|
|||||||
<Button
|
<Button
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: <Sort screen={screen} type={type} />
|
component: <Sort screen={screen} type={dataType} />
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
tooltipText="Change sorting of items in list"
|
tooltipText="Change sorting of items in list"
|
||||||
fwdRef={sortRef}
|
|
||||||
title={groupBy}
|
title={groupBy}
|
||||||
icon={
|
icon={
|
||||||
groupOptions.sortDirection === "asc"
|
groupOptions.sortDirection === "asc"
|
||||||
@@ -123,7 +137,9 @@ export const SectionHeader = React.memo(
|
|||||||
paddingHorizontal: 0,
|
paddingHorizontal: 0,
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
marginRight:
|
marginRight:
|
||||||
type === "notes" || type === "home" || type === "notebooks"
|
dataType === "note" ||
|
||||||
|
screen === "Notes" ||
|
||||||
|
dataType === "notebook"
|
||||||
? 10
|
? 10
|
||||||
: 0
|
: 0
|
||||||
}}
|
}}
|
||||||
@@ -137,24 +153,26 @@ export const SectionHeader = React.memo(
|
|||||||
height: 25
|
height: 25
|
||||||
}}
|
}}
|
||||||
hidden={
|
hidden={
|
||||||
type !== "notes" && type !== "notebooks" && type !== "home"
|
dataType !== "note" &&
|
||||||
|
dataType !== "notebook" &&
|
||||||
|
screen !== "Notes"
|
||||||
}
|
}
|
||||||
testID="icon-compact-mode"
|
testID="icon-compact-mode"
|
||||||
tooltipText={
|
tooltipText={
|
||||||
listMode == "compact"
|
isCompactModeEnabled
|
||||||
? "Switch to normal mode"
|
? "Switch to normal mode"
|
||||||
: "Switch to compact mode"
|
: "Switch to compact mode"
|
||||||
}
|
}
|
||||||
fwdRef={compactModeRef}
|
|
||||||
color={colors.secondary.icon}
|
color={colors.secondary.icon}
|
||||||
name={listMode == "compact" ? "view-list" : "view-list-outline"}
|
name={isCompactModeEnabled ? "view-list" : "view-list-outline"}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
let settings = {};
|
SettingsService.set({
|
||||||
settings[
|
[dataType !== "notebook"
|
||||||
type !== "notebooks" ? "notesListMode" : "notebooksListMode"
|
? "notesListMode"
|
||||||
] = listMode === "normal" ? "compact" : "normal";
|
: "notebooksListMode"]: isCompactModeEnabled
|
||||||
|
? "compact"
|
||||||
SettingsService.set(settings);
|
: "normal"
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
size={SIZE.lg - 2}
|
size={SIZE.lg - 2}
|
||||||
/>
|
/>
|
||||||
@@ -166,7 +184,6 @@ export const SectionHeader = React.memo(
|
|||||||
},
|
},
|
||||||
(prev, next) => {
|
(prev, next) => {
|
||||||
if (prev.item.title !== next.item.title) return false;
|
if (prev.item.title !== next.item.title) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -17,21 +17,30 @@ 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 { getUpcomingReminder } from "@notesnook/core/dist/collections/reminders";
|
import {
|
||||||
import { decode, EntityLevel } from "entities";
|
BaseTrashItem,
|
||||||
|
Color,
|
||||||
|
Note,
|
||||||
|
Reminder,
|
||||||
|
TrashItem
|
||||||
|
} from "@notesnook/core";
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
|
import { EntityLevel, decode } from "entities";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { notesnook } from "../../../../e2e/test.ids";
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
import { db } from "../../../common/database";
|
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
|
||||||
import NotebookScreen from "../../../screens/notebook";
|
import NotebookScreen from "../../../screens/notebook";
|
||||||
import { TaggedNotes } from "../../../screens/notes/tagged";
|
import { TaggedNotes } from "../../../screens/notes/tagged";
|
||||||
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
import { useEditorStore } from "../../../stores/use-editor-store";
|
||||||
import useNavigationStore from "../../../stores/use-navigation-store";
|
import useNavigationStore from "../../../stores/use-navigation-store";
|
||||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { ColorValues } from "../../../utils/colors";
|
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
|
import {
|
||||||
|
NotebooksWithDateEdited,
|
||||||
|
TagsWithDateEdited
|
||||||
|
} from "../../list/list-item.wrapper";
|
||||||
import { Properties } from "../../properties";
|
import { Properties } from "../../properties";
|
||||||
import { Button } from "../../ui/button";
|
import { Button } from "../../ui/button";
|
||||||
import { IconButton } from "../../ui/icon-button";
|
import { IconButton } from "../../ui/icon-button";
|
||||||
@@ -39,73 +48,39 @@ import { ReminderTime } from "../../ui/reminder-time";
|
|||||||
import { TimeSince } from "../../ui/time-since";
|
import { TimeSince } from "../../ui/time-since";
|
||||||
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 { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
|
|
||||||
import { useEditorStore } from "../../../stores/use-editor-store";
|
|
||||||
|
|
||||||
function navigateToTag(item) {
|
type NoteItemProps = {
|
||||||
const tag = db.tags.tag(item.id);
|
item: Note | BaseTrashItem<Note>;
|
||||||
if (!tag) return;
|
index: number;
|
||||||
TaggedNotes.navigate(tag, true);
|
tags?: TagsWithDateEdited;
|
||||||
}
|
notebooks?: NotebooksWithDateEdited;
|
||||||
|
color?: Color;
|
||||||
const showActionSheet = (item) => {
|
reminder?: Reminder;
|
||||||
Properties.present(item);
|
attachmentsCount: number;
|
||||||
|
date: number;
|
||||||
|
isTrash?: boolean;
|
||||||
|
noOpen?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getNotebook(item) {
|
|
||||||
const isTrash = item.type === "trash";
|
|
||||||
const currentId = useNavigationStore.getState().currentScreen.id;
|
|
||||||
if (isTrash) return [];
|
|
||||||
const items = [];
|
|
||||||
const notebooks = db.relations.to(item, "notebook") || [];
|
|
||||||
|
|
||||||
for (let notebook of notebooks) {
|
|
||||||
if (items.length > 1) break;
|
|
||||||
if (notebook.id === currentId) continue;
|
|
||||||
items.push(notebook);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.notebooks) {
|
|
||||||
for (let nb of item.notebooks) {
|
|
||||||
if (items.length > 1) break;
|
|
||||||
const notebook = db.notebooks?.notebook(nb.id)?.data;
|
|
||||||
if (!notebook) continue;
|
|
||||||
for (let topicId of nb.topics) {
|
|
||||||
if (items.length > 1) break;
|
|
||||||
if (topicId === currentId) continue;
|
|
||||||
const topic = notebook.topics.find((t) => t.id === topicId);
|
|
||||||
if (!topic) continue;
|
|
||||||
items.push(topic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTags(item) {
|
|
||||||
const noteTags = db.relations.to(item, "tag").resolved();
|
|
||||||
return noteTags;
|
|
||||||
}
|
|
||||||
|
|
||||||
const NoteItem = ({
|
const NoteItem = ({
|
||||||
item,
|
item,
|
||||||
isTrash,
|
isTrash,
|
||||||
dateBy = "dateCreated",
|
date,
|
||||||
|
color,
|
||||||
|
notebooks,
|
||||||
|
reminder,
|
||||||
|
tags,
|
||||||
|
attachmentsCount,
|
||||||
noOpen = false
|
noOpen = false
|
||||||
}) => {
|
}: NoteItemProps) => {
|
||||||
const isEditingNote = useEditorStore(
|
const isEditingNote = useEditorStore(
|
||||||
(state) => state.currentEditingNote === item.id
|
(state) => state.currentEditingNote === item.id
|
||||||
);
|
);
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const compactMode = useIsCompactModeEnabled(item);
|
const compactMode = useIsCompactModeEnabled(
|
||||||
const attachmentCount = db.attachments?.ofNote(item.id, "all")?.length || 0;
|
(item as TrashItem).itemType || item.type
|
||||||
|
);
|
||||||
const _update = useRelationStore((state) => state.updater);
|
const _update = useRelationStore((state) => state.updater);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
const notebooks = React.useMemo(() => getNotebook(item), [item, _update]);
|
|
||||||
const reminders = db.relations.from(item, "reminder");
|
|
||||||
const reminder = getUpcomingReminder(reminders);
|
|
||||||
const noteColor = ColorValues[item.color?.toLowerCase()];
|
|
||||||
const tags = getTags(item);
|
|
||||||
const primaryColors = isEditingNote ? colors.selected : colors.primary;
|
const primaryColors = isEditingNote ? colors.selected : colors.primary;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -127,44 +102,45 @@ const NoteItem = ({
|
|||||||
flexWrap: "wrap"
|
flexWrap: "wrap"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{notebooks?.map((item) => (
|
{notebooks?.items
|
||||||
<Button
|
?.filter(
|
||||||
title={
|
(item) =>
|
||||||
item.title.length > 25
|
item.id !== useNavigationStore.getState().currentScreen?.id
|
||||||
? item.title.slice(0, 25) + "..."
|
)
|
||||||
: item.title
|
.map((item) => (
|
||||||
}
|
<Button
|
||||||
tooltipText={item.title}
|
title={
|
||||||
key={item.id}
|
item.title.length > 25
|
||||||
height={25}
|
? item.title.slice(0, 25) + "..."
|
||||||
icon={item.type === "topic" ? "bookmark" : "book-outline"}
|
: item.title
|
||||||
type="grayBg"
|
|
||||||
fontSize={SIZE.xs}
|
|
||||||
iconSize={SIZE.sm}
|
|
||||||
textStyle={{
|
|
||||||
marginRight: 0
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
borderRadius: 5,
|
|
||||||
marginRight: 5,
|
|
||||||
borderWidth: 0.5,
|
|
||||||
borderColor: primaryColors.border,
|
|
||||||
paddingHorizontal: 6,
|
|
||||||
marginBottom: 5
|
|
||||||
}}
|
|
||||||
onPress={() => {
|
|
||||||
if (item.type === "topic") {
|
|
||||||
TopicNotes.navigate(item, true);
|
|
||||||
} else {
|
|
||||||
NotebookScreen.navigate(item);
|
|
||||||
}
|
}
|
||||||
}}
|
tooltipText={item.title}
|
||||||
/>
|
key={item.id}
|
||||||
))}
|
height={25}
|
||||||
|
icon={item.type === "notebook" ? "bookmark" : "book-outline"}
|
||||||
|
type="grayBg"
|
||||||
|
fontSize={SIZE.xs}
|
||||||
|
iconSize={SIZE.sm}
|
||||||
|
textStyle={{
|
||||||
|
marginRight: 0
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
borderRadius: 5,
|
||||||
|
marginRight: 5,
|
||||||
|
borderWidth: 0.5,
|
||||||
|
borderColor: primaryColors.border,
|
||||||
|
paddingHorizontal: 6,
|
||||||
|
marginBottom: 5
|
||||||
|
}}
|
||||||
|
onPress={() => {
|
||||||
|
NotebookScreen.navigate(item);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
<ReminderTime
|
<ReminderTime
|
||||||
reminder={reminder}
|
reminder={reminder}
|
||||||
color={noteColor}
|
color={color?.colorCode}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
Properties.present(reminder);
|
Properties.present(reminder);
|
||||||
}}
|
}}
|
||||||
@@ -178,9 +154,7 @@ const NoteItem = ({
|
|||||||
{compactMode ? (
|
{compactMode ? (
|
||||||
<Paragraph
|
<Paragraph
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
color={
|
color={color?.colorCode || primaryColors.heading}
|
||||||
ColorValues[item.color?.toLowerCase()] || primaryColors.heading
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
flexWrap: "wrap"
|
flexWrap: "wrap"
|
||||||
}}
|
}}
|
||||||
@@ -191,9 +165,7 @@ const NoteItem = ({
|
|||||||
) : (
|
) : (
|
||||||
<Heading
|
<Heading
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
color={
|
color={color?.colorCode || primaryColors.heading}
|
||||||
ColorValues[item.color?.toLowerCase()] || primaryColors.heading
|
|
||||||
}
|
|
||||||
style={{
|
style={{
|
||||||
flexWrap: "wrap"
|
flexWrap: "wrap"
|
||||||
}}
|
}}
|
||||||
@@ -246,13 +218,11 @@ const NoteItem = ({
|
|||||||
color: colors.secondary.paragraph,
|
color: colors.secondary.paragraph,
|
||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
time={item[dateBy]}
|
time={date}
|
||||||
updateFrequency={
|
updateFrequency={Date.now() - date < 60000 ? 2000 : 60000}
|
||||||
Date.now() - item[dateBy] < 60000 ? 2000 : 60000
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{attachmentCount > 0 ? (
|
{attachmentsCount > 0 ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -269,7 +239,7 @@ const NoteItem = ({
|
|||||||
color={colors.secondary.paragraph}
|
color={colors.secondary.paragraph}
|
||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
>
|
>
|
||||||
{attachmentCount}
|
{attachmentsCount}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -282,10 +252,7 @@ const NoteItem = ({
|
|||||||
style={{
|
style={{
|
||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
color={
|
color={color?.colorCode || primaryColors.accent}
|
||||||
ColorValues[item.color?.toLowerCase()] ||
|
|
||||||
primaryColors.accent
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
@@ -314,7 +281,7 @@ const NoteItem = ({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{!isTrash && !compactMode && tags
|
{!isTrash && !compactMode && tags
|
||||||
? tags.map((item) =>
|
? tags.items?.map((item) =>
|
||||||
item.id ? (
|
item.id ? (
|
||||||
<Button
|
<Button
|
||||||
title={"#" + item.title}
|
title={"#" + item.title}
|
||||||
@@ -331,9 +298,9 @@ const NoteItem = ({
|
|||||||
paddingHorizontal: 6,
|
paddingHorizontal: 6,
|
||||||
marginRight: 4,
|
marginRight: 4,
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
maxWidth: tags.length > 1 ? 130 : null
|
maxWidth: tags.items?.length > 1 ? 130 : null
|
||||||
}}
|
}}
|
||||||
onPress={() => navigateToTag(item)}
|
onPress={() => TaggedNotes.navigate(item, true)}
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
)
|
)
|
||||||
@@ -361,7 +328,8 @@ const NoteItem = ({
|
|||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.itemType[0].toUpperCase() + item.itemType.slice(1)}
|
{(item as TrashItem).itemType[0].toUpperCase() +
|
||||||
|
(item as TrashItem).itemType.slice(1)}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -417,8 +385,8 @@ const NoteItem = ({
|
|||||||
color: colors.secondary.paragraph,
|
color: colors.secondary.paragraph,
|
||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
time={item[dateBy]}
|
time={date}
|
||||||
updateFrequency={Date.now() - item[dateBy] < 60000 ? 2000 : 60000}
|
updateFrequency={Date.now() - date < 60000 ? 2000 : 60000}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -428,7 +396,7 @@ const NoteItem = ({
|
|||||||
color={primaryColors.paragraph}
|
color={primaryColors.paragraph}
|
||||||
name="dots-horizontal"
|
name="dots-horizontal"
|
||||||
size={SIZE.xl}
|
size={SIZE.xl}
|
||||||
onPress={() => !noOpen && showActionSheet(item, isTrash)}
|
onPress={() => !noOpen && Properties.present(item)}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
height: 35,
|
height: 35,
|
||||||
@@ -17,6 +17,7 @@ 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 { BaseTrashItem, Color, Note, Reminder } from "@notesnook/core";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import NoteItem from ".";
|
import NoteItem from ".";
|
||||||
import { notesnook } from "../../../../e2e/test.ids";
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
@@ -32,50 +33,43 @@ import { useEditorStore } from "../../../stores/use-editor-store";
|
|||||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||||
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
|
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
|
||||||
import { tabBarRef } from "../../../utils/global-refs";
|
import { tabBarRef } from "../../../utils/global-refs";
|
||||||
import { presentDialog } from "../../dialog/functions";
|
import type {
|
||||||
|
NotebooksWithDateEdited,
|
||||||
|
TagsWithDateEdited
|
||||||
|
} from "../../list/list-item.wrapper";
|
||||||
import NotePreview from "../../note-history/preview";
|
import NotePreview from "../../note-history/preview";
|
||||||
import SelectionWrapper from "../selection-wrapper";
|
import SelectionWrapper from "../selection-wrapper";
|
||||||
|
|
||||||
const present = () =>
|
export const openNote = async (
|
||||||
presentDialog({
|
item: Note,
|
||||||
title: "Note not synced",
|
isTrash?: boolean,
|
||||||
negativeText: "Ok",
|
isSheet?: boolean
|
||||||
paragraph: "Please sync again to open this note for editing"
|
) => {
|
||||||
});
|
let note: Note = item;
|
||||||
|
|
||||||
export const openNote = async (item, isTrash, setSelectedItem, isSheet) => {
|
|
||||||
let _note = item;
|
|
||||||
if (isSheet) hideSheet();
|
if (isSheet) hideSheet();
|
||||||
|
|
||||||
if (!isTrash) {
|
if (!isTrash) {
|
||||||
_note = db.notes.note(item.id).data;
|
note = (await db.notes.note(item.id)) as Note;
|
||||||
if (!db.notes.note(item.id)?.synced()) {
|
|
||||||
present();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!db.trash.synced(item.id)) {
|
|
||||||
present();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const { selectedItemsList, selectionMode, clearSelection } =
|
|
||||||
|
const { selectedItemsList, selectionMode, clearSelection, setSelectedItem } =
|
||||||
useSelectionStore.getState();
|
useSelectionStore.getState();
|
||||||
|
|
||||||
if (selectedItemsList.length > 0 && selectionMode) {
|
if (selectedItemsList.length > 0 && selectionMode) {
|
||||||
setSelectedItem && setSelectedItem(_note);
|
setSelectedItem(note);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_note.conflicted) {
|
if (note.conflicted) {
|
||||||
eSendEvent(eShowMergeDialog, _note);
|
eSendEvent(eShowMergeDialog, note);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_note.locked) {
|
if (note.locked) {
|
||||||
openVault({
|
openVault({
|
||||||
item: _note,
|
item: note,
|
||||||
novault: true,
|
novault: true,
|
||||||
locked: true,
|
locked: true,
|
||||||
goToEditor: true,
|
goToEditor: true,
|
||||||
@@ -85,16 +79,18 @@ export const openNote = async (item, isTrash, setSelectedItem, isSheet) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isTrash) {
|
if (isTrash) {
|
||||||
const content = await db.content.get(item.contentId);
|
if (!note.contentId) return;
|
||||||
|
|
||||||
|
const content = await db.content.get(note.contentId as string);
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: (
|
component: (
|
||||||
<NotePreview note={item} content={{ type: "tiptap", data: content }} />
|
<NotePreview note={item} content={{ type: "tiptap", data: content }} />
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
useEditorStore.getState().setReadonly(_note?.readonly);
|
useEditorStore.getState().setReadonly(note?.readonly);
|
||||||
eSendEvent(eOnLoadNote, {
|
eSendEvent(eOnLoadNote, {
|
||||||
item: _note
|
item: note
|
||||||
});
|
});
|
||||||
if (!DDS.isTab) {
|
if (!DDS.isTab) {
|
||||||
tabBarRef.current?.goToPage(1);
|
tabBarRef.current?.goToPage(1);
|
||||||
@@ -102,33 +98,62 @@ export const openNote = async (item, isTrash, setSelectedItem, isSheet) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoteWrapper = React.memo(
|
type NoteWrapperProps = {
|
||||||
function NoteWrapper({ item, index, dateBy, isSheet }) {
|
item: Note | BaseTrashItem<Note>;
|
||||||
|
index: number;
|
||||||
|
tags?: TagsWithDateEdited;
|
||||||
|
notebooks?: NotebooksWithDateEdited;
|
||||||
|
color?: Color;
|
||||||
|
reminder?: Reminder;
|
||||||
|
attachmentsCount: number;
|
||||||
|
date: number;
|
||||||
|
isRenderedInActionSheet: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NoteWrapper = React.memo<
|
||||||
|
React.FunctionComponent<NoteWrapperProps>
|
||||||
|
>(
|
||||||
|
function NoteWrapper({
|
||||||
|
item,
|
||||||
|
index,
|
||||||
|
isRenderedInActionSheet,
|
||||||
|
...restProps
|
||||||
|
}: NoteWrapperProps) {
|
||||||
const isTrash = item.type === "trash";
|
const isTrash = item.type === "trash";
|
||||||
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectionWrapper
|
<SelectionWrapper
|
||||||
index={index}
|
|
||||||
height={100}
|
|
||||||
testID={notesnook.ids.note.get(index)}
|
testID={notesnook.ids.note.get(index)}
|
||||||
onPress={() => openNote(item, isTrash, setSelectedItem, isSheet)}
|
onPress={() => openNote(item as Note, isTrash, isRenderedInActionSheet)}
|
||||||
isSheet={isSheet}
|
isSheet={isRenderedInActionSheet}
|
||||||
item={item}
|
item={item}
|
||||||
|
color={restProps.color?.colorCode}
|
||||||
>
|
>
|
||||||
<NoteItem item={item} dateBy={dateBy} isTrash={isTrash} />
|
<NoteItem {...restProps} item={item} index={index} isTrash={isTrash} />
|
||||||
</SelectionWrapper>
|
</SelectionWrapper>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(prev, next) => {
|
(prev, next) => {
|
||||||
if (prev.dateBy !== next.dateBy) {
|
if (prev.date !== next.date) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (prev.item?.dateEdited !== next.item?.dateEdited) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prev.item !== next.item) {
|
if (
|
||||||
|
prev.tags?.dateEdited !== next.tags?.dateEdited ||
|
||||||
|
prev.tags?.items?.length !== next.tags?.items?.length
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
prev.notebooks?.dateEdited !== next.notebooks?.dateEdited ||
|
||||||
|
prev.notebooks?.items?.length !== next.notebooks?.items?.length
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (prev.color !== next.color) return false;
|
||||||
|
if (prev.reminder?.id !== next.reminder?.id) return false;
|
||||||
|
if (prev.attachmentsCount !== next.attachmentsCount) return false;
|
||||||
|
if (prev.item?.dateModified !== next.item?.dateModified) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,41 +17,38 @@ 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 { getFormattedDate } from "@notesnook/common";
|
||||||
|
import { BaseTrashItem, Notebook, TrashItem } from "@notesnook/core";
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { notesnook } from "../../../../e2e/test.ids";
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
|
||||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
import { Properties } from "../../properties";
|
import { Properties } from "../../properties";
|
||||||
import { Button } from "../../ui/button";
|
|
||||||
import { IconButton } from "../../ui/icon-button";
|
import { IconButton } from "../../ui/icon-button";
|
||||||
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 { getFormattedDate } from "@notesnook/common";
|
|
||||||
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
|
|
||||||
|
|
||||||
const showActionSheet = (item) => {
|
type NotebookItemProps = {
|
||||||
Properties.present(item);
|
item: Notebook | BaseTrashItem<Notebook>;
|
||||||
};
|
totalNotes: number;
|
||||||
|
date: number;
|
||||||
const navigateToTopic = (topic) => {
|
index: number;
|
||||||
if (useSelectionStore.getState().selectedItemsList.length > 0) return;
|
isTrash: boolean;
|
||||||
TopicNotes.navigate(topic, true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NotebookItem = ({
|
export const NotebookItem = ({
|
||||||
item,
|
item,
|
||||||
isTopic = false,
|
|
||||||
isTrash,
|
isTrash,
|
||||||
dateBy,
|
date,
|
||||||
totalNotes
|
totalNotes
|
||||||
}) => {
|
}: NotebookItemProps) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const compactMode = useIsCompactModeEnabled(item);
|
const compactMode = useIsCompactModeEnabled(
|
||||||
const topics = item.topics?.slice(0, 3) || [];
|
(item as TrashItem).itemType || item.type
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -83,7 +80,7 @@ export const NotebookItem = ({
|
|||||||
</Heading>
|
</Heading>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isTopic || !item.description || compactMode ? null : (
|
{!item.description || compactMode ? null : (
|
||||||
<Paragraph
|
<Paragraph
|
||||||
size={SIZE.sm}
|
size={SIZE.sm}
|
||||||
numberOfLines={2}
|
numberOfLines={2}
|
||||||
@@ -95,42 +92,6 @@ export const NotebookItem = ({
|
|||||||
</Paragraph>
|
</Paragraph>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isTopic || compactMode ? null : (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
flexWrap: "wrap"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{topics.map((topic) => (
|
|
||||||
<Button
|
|
||||||
title={topic.title}
|
|
||||||
key={topic.id}
|
|
||||||
height={null}
|
|
||||||
textStyle={{
|
|
||||||
marginRight: 0
|
|
||||||
}}
|
|
||||||
type="grayBg"
|
|
||||||
fontSize={SIZE.xs}
|
|
||||||
icon="bookmark-outline"
|
|
||||||
iconSize={SIZE.sm}
|
|
||||||
style={{
|
|
||||||
borderRadius: 5,
|
|
||||||
maxWidth: 120,
|
|
||||||
borderWidth: 0.5,
|
|
||||||
paddingVertical: 2.5,
|
|
||||||
borderColor: colors.primary.border,
|
|
||||||
paddingHorizontal: 6,
|
|
||||||
marginVertical: 5,
|
|
||||||
marginRight: 5
|
|
||||||
}}
|
|
||||||
onPress={() => navigateToTopic(topic)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!compactMode ? (
|
{!compactMode ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -152,7 +113,9 @@ export const NotebookItem = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{"Deleted on " +
|
{"Deleted on " +
|
||||||
new Date(item.dateDeleted).toISOString().slice(0, 10)}
|
new Date((item as TrashItem).dateDeleted)
|
||||||
|
.toISOString()
|
||||||
|
.slice(0, 10)}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
color={colors.primary.accent}
|
color={colors.primary.accent}
|
||||||
@@ -162,7 +125,8 @@ export const NotebookItem = ({
|
|||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.itemType[0].toUpperCase() + item.itemType.slice(1)}
|
{(item as TrashItem).itemType[0].toUpperCase() +
|
||||||
|
(item as TrashItem).itemType.slice(1)}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -173,7 +137,7 @@ export const NotebookItem = ({
|
|||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{getFormattedDate(item[dateBy], "date")}
|
{getFormattedDate(date, "date")}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
)}
|
)}
|
||||||
<Paragraph
|
<Paragraph
|
||||||
@@ -233,7 +197,7 @@ export const NotebookItem = ({
|
|||||||
name="dots-horizontal"
|
name="dots-horizontal"
|
||||||
testID={notesnook.ids.notebook.menu}
|
testID={notesnook.ids.notebook.menu}
|
||||||
size={SIZE.xl}
|
size={SIZE.xl}
|
||||||
onPress={() => showActionSheet(item)}
|
onPress={() => Properties.present(item)}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
height: 35,
|
height: 35,
|
||||||
@@ -17,18 +17,18 @@ 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 { BaseTrashItem, Notebook } from "@notesnook/core";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { NotebookItem } from ".";
|
import { NotebookItem } from ".";
|
||||||
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
import { db } from "../../../common/database";
|
||||||
import { ToastManager } from "../../../services/event-manager";
|
import { ToastManager } from "../../../services/event-manager";
|
||||||
import Navigation from "../../../services/navigation";
|
import Navigation from "../../../services/navigation";
|
||||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||||
import { useTrashStore } from "../../../stores/use-trash-store";
|
import { useTrashStore } from "../../../stores/use-trash-store";
|
||||||
import { db } from "../../../common/database";
|
|
||||||
import { presentDialog } from "../../dialog/functions";
|
import { presentDialog } from "../../dialog/functions";
|
||||||
import SelectionWrapper from "../selection-wrapper";
|
import SelectionWrapper from "../selection-wrapper";
|
||||||
|
|
||||||
const navigateToNotebook = (item, canGoBack) => {
|
const navigateToNotebook = (item: Notebook, canGoBack?: boolean) => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
Navigation.navigate(
|
Navigation.navigate(
|
||||||
@@ -46,7 +46,7 @@ const navigateToNotebook = (item, canGoBack) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const openNotebookTopic = (item) => {
|
export const openNotebookTopic = (item: Notebook | BaseTrashItem<Notebook>) => {
|
||||||
const isTrash = item.type === "trash";
|
const isTrash = item.type === "trash";
|
||||||
const { selectedItemsList, setSelectedItem, selectionMode, clearSelection } =
|
const { selectedItemsList, setSelectedItem, selectionMode, clearSelection } =
|
||||||
useSelectionStore.getState();
|
useSelectionStore.getState();
|
||||||
@@ -85,29 +85,30 @@ export const openNotebookTopic = (item) => {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (item.type === "topic") {
|
navigateToNotebook(item, true);
|
||||||
TopicNotes.navigate(item, true);
|
};
|
||||||
} else {
|
|
||||||
navigateToNotebook(item, true);
|
type NotebookWrapperProps = {
|
||||||
}
|
item: Notebook | BaseTrashItem<Notebook>;
|
||||||
|
totalNotes: number;
|
||||||
|
date: number;
|
||||||
|
index: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NotebookWrapper = React.memo(
|
export const NotebookWrapper = React.memo(
|
||||||
function NotebookWrapper({ item, index, dateBy, totalNotes }) {
|
function NotebookWrapper({
|
||||||
|
item,
|
||||||
|
index,
|
||||||
|
date,
|
||||||
|
totalNotes
|
||||||
|
}: NotebookWrapperProps) {
|
||||||
const isTrash = item.type === "trash";
|
const isTrash = item.type === "trash";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectionWrapper
|
<SelectionWrapper onPress={() => openNotebookTopic(item)} item={item}>
|
||||||
pinned={item.pinned}
|
|
||||||
index={index}
|
|
||||||
onPress={() => openNotebookTopic(item)}
|
|
||||||
height={item.type === "topic" ? 80 : 110}
|
|
||||||
item={item}
|
|
||||||
>
|
|
||||||
<NotebookItem
|
<NotebookItem
|
||||||
isTopic={item.type === "topic"}
|
|
||||||
item={item}
|
item={item}
|
||||||
dateBy={dateBy}
|
date={date}
|
||||||
index={index}
|
index={index}
|
||||||
isTrash={isTrash}
|
isTrash={isTrash}
|
||||||
totalNotes={totalNotes}
|
totalNotes={totalNotes}
|
||||||
@@ -117,17 +118,8 @@ export const NotebookWrapper = React.memo(
|
|||||||
},
|
},
|
||||||
(prev, next) => {
|
(prev, next) => {
|
||||||
if (prev.totalNotes !== next.totalNotes) return false;
|
if (prev.totalNotes !== next.totalNotes) return false;
|
||||||
if (prev.item.title !== next.item.title) return false;
|
if (prev.date !== next.date) return false;
|
||||||
if (prev.dateBy !== next.dateBy) {
|
if (prev.item?.dateModified !== next.item?.dateModified) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prev.item?.dateEdited !== next.item?.dateEdited) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (JSON.stringify(prev.item) !== JSON.stringify(next.item)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { notesnook } from "../../../../e2e/test.ids";
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
import type { Reminder } from "../../../services/notifications";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
import { Properties } from "../../properties";
|
import { Properties } from "../../properties";
|
||||||
@@ -30,6 +29,7 @@ import SelectionWrapper from "../selection-wrapper";
|
|||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import ReminderSheet from "../../sheets/reminder";
|
import ReminderSheet from "../../sheets/reminder";
|
||||||
import { ReminderTime } from "../../ui/reminder-time";
|
import { ReminderTime } from "../../ui/reminder-time";
|
||||||
|
import { Reminder } from "@notesnook/core";
|
||||||
|
|
||||||
const ReminderItem = React.memo(
|
const ReminderItem = React.memo(
|
||||||
({
|
({
|
||||||
@@ -131,7 +131,11 @@ const ReminderItem = React.memo(
|
|||||||
height: 30
|
height: 30
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="reload" size={SIZE.md} color={colors.primary.accent} />
|
<Icon
|
||||||
|
name="reload"
|
||||||
|
size={SIZE.md}
|
||||||
|
color={colors.primary.accent}
|
||||||
|
/>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
color={colors.secondary.paragraph}
|
color={colors.secondary.paragraph}
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ import React from "react";
|
|||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import useIsSelected from "../../../hooks/use-selected";
|
import useIsSelected from "../../../hooks/use-selected";
|
||||||
import { useEditorStore } from "../../../stores/use-editor-store";
|
import { useEditorStore } from "../../../stores/use-editor-store";
|
||||||
|
import { Item } from "@notesnook/core";
|
||||||
|
|
||||||
export const Filler = ({ item }) => {
|
export const Filler = ({ item, color }: { item: Item; color?: string }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const isEditingNote = useEditorStore(
|
const isEditingNote = useEditorStore(
|
||||||
(state) => state.currentEditingNote === item.id
|
(state) => state.currentEditingNote === item.id
|
||||||
@@ -18,24 +18,35 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useRef } from "react";
|
import React, { PropsWithChildren, useRef } from "react";
|
||||||
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
|
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
|
||||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||||
import { PressableButton } from "../../ui/pressable";
|
import { PressableButton } from "../../ui/pressable";
|
||||||
import { Filler } from "./back-fill";
|
import { Filler } from "./back-fill";
|
||||||
import { SelectionIcon } from "./selection";
|
import { SelectionIcon } from "./selection";
|
||||||
|
import { Item, TrashItem } from "@notesnook/core";
|
||||||
|
|
||||||
|
type SelectionWrapperProps = PropsWithChildren<{
|
||||||
|
item: Item;
|
||||||
|
onPress: () => void;
|
||||||
|
testID?: string;
|
||||||
|
isSheet?: boolean;
|
||||||
|
color?: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
const SelectionWrapper = ({
|
const SelectionWrapper = ({
|
||||||
children,
|
|
||||||
item,
|
item,
|
||||||
background,
|
|
||||||
onPress,
|
onPress,
|
||||||
testID,
|
testID,
|
||||||
isSheet
|
isSheet,
|
||||||
}) => {
|
children,
|
||||||
|
color
|
||||||
|
}: SelectionWrapperProps) => {
|
||||||
const itemId = useRef(item.id);
|
const itemId = useRef(item.id);
|
||||||
const { colors, isDark } = useThemeColors();
|
const { colors, isDark } = useThemeColors();
|
||||||
const compactMode = useIsCompactModeEnabled(item);
|
const compactMode = useIsCompactModeEnabled(
|
||||||
|
(item as TrashItem).itemType || item.type
|
||||||
|
);
|
||||||
|
|
||||||
if (item.id !== itemId.current) {
|
if (item.id !== itemId.current) {
|
||||||
itemId.current = item.id;
|
itemId.current = item.id;
|
||||||
@@ -43,9 +54,6 @@ const SelectionWrapper = ({
|
|||||||
|
|
||||||
const onLongPress = () => {
|
const onLongPress = () => {
|
||||||
if (!useSelectionStore.getState().selectionMode) {
|
if (!useSelectionStore.getState().selectionMode) {
|
||||||
useSelectionStore.setState({
|
|
||||||
selectedItemsList: []
|
|
||||||
});
|
|
||||||
useSelectionStore.getState().setSelectionMode(true);
|
useSelectionStore.getState().setSelectionMode(true);
|
||||||
}
|
}
|
||||||
useSelectionStore.getState().setSelectedItem(item);
|
useSelectionStore.getState().setSelectedItem(item);
|
||||||
@@ -72,14 +80,8 @@ const SelectionWrapper = ({
|
|||||||
marginBottom: isSheet ? 12 : undefined
|
marginBottom: isSheet ? 12 : undefined
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.type === "note" ? (
|
{item.type === "note" ? <Filler item={item} color={color} /> : null}
|
||||||
<Filler background={background} item={item} />
|
<SelectionIcon item={item} />
|
||||||
) : null}
|
|
||||||
<SelectionIcon
|
|
||||||
compactMode={compactMode}
|
|
||||||
item={item}
|
|
||||||
onLongPress={onLongPress}
|
|
||||||
/>
|
|
||||||
{children}
|
{children}
|
||||||
</PressableButton>
|
</PressableButton>
|
||||||
);
|
);
|
||||||
@@ -25,13 +25,16 @@ import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enab
|
|||||||
import useIsSelected from "../../../hooks/use-selected";
|
import useIsSelected from "../../../hooks/use-selected";
|
||||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
|
import { Item, TrashItem } from "@notesnook/core";
|
||||||
|
|
||||||
export const SelectionIcon = ({ item }) => {
|
export const SelectionIcon = ({ item }: { item: Item }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||||
const [selected] = useIsSelected(item);
|
const [selected] = useIsSelected(item);
|
||||||
|
|
||||||
const compactMode = useIsCompactModeEnabled(item);
|
const compactMode = useIsCompactModeEnabled(
|
||||||
|
(item as TrashItem).itemType || item.type
|
||||||
|
);
|
||||||
|
|
||||||
return selectionMode ? (
|
return selectionMode ? (
|
||||||
<View
|
<View
|
||||||
@@ -17,34 +17,41 @@ 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 { Tag } from "@notesnook/core";
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { notesnook } from "../../../../e2e/test.ids";
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
import { TaggedNotes } from "../../../screens/notes/tagged";
|
import { TaggedNotes } from "../../../screens/notes/tagged";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
import { Properties } from "../../properties";
|
import { Properties } from "../../properties";
|
||||||
import { IconButton } from "../../ui/icon-button";
|
import { IconButton } from "../../ui/icon-button";
|
||||||
import { PressableButton } from "../../ui/pressable";
|
import { PressableButton } from "../../ui/pressable";
|
||||||
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 { db } from "../../../common/database";
|
|
||||||
|
|
||||||
const TagItem = React.memo(
|
const TagItem = React.memo(
|
||||||
({ item, index }) => {
|
({
|
||||||
|
item,
|
||||||
|
index,
|
||||||
|
totalNotes
|
||||||
|
}: {
|
||||||
|
item: Tag;
|
||||||
|
index: number;
|
||||||
|
totalNotes: number;
|
||||||
|
}) => {
|
||||||
const { colors, isDark } = useThemeColors();
|
const { colors, isDark } = useThemeColors();
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
TaggedNotes.navigate(item, true);
|
TaggedNotes.navigate(item, true);
|
||||||
};
|
};
|
||||||
const relations = db.relations.from(item, "note");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PressableButton
|
<PressableButton
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
selectedColor={colors.secondary.background}
|
customSelectedColor={colors.secondary.background}
|
||||||
testID={notesnook.ids.tag.get(index)}
|
testID={notesnook.ids.tag.get(index)}
|
||||||
alpha={!isDark ? -0.02 : 0.02}
|
customAlpha={!isDark ? -0.02 : 0.02}
|
||||||
opacity={1}
|
customOpacity={1}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -77,10 +84,10 @@ const TagItem = React.memo(
|
|||||||
marginTop: 5
|
marginTop: 5
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{relations.length && relations.length > 1
|
{totalNotes > 1
|
||||||
? relations.length + " notes"
|
? totalNotes + " notes"
|
||||||
: relations.length === 1
|
: totalNotes === 1
|
||||||
? relations.length + " note"
|
? totalNotes + " note"
|
||||||
: null}
|
: null}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
@@ -105,10 +112,7 @@ const TagItem = React.memo(
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
(prev, next) => {
|
(prev, next) => {
|
||||||
if (prev.item?.dateEdited !== next.item?.dateEdited) {
|
if (prev.item?.dateModified !== next.item?.dateModified) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (JSON.stringify(prev.item) !== JSON.stringify(next.item)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,14 +27,15 @@ import { SIZE } from "../../utils/size";
|
|||||||
import { PressableButton } from "../ui/pressable";
|
import { PressableButton } from "../ui/pressable";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
|
||||||
export const Card = ({ color, warning }) => {
|
export const Card = ({ color }: { color?: string }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
color = color ? color : colors.primary.accent;
|
color = color ? color : colors.primary.accent;
|
||||||
const messageBoardState = useMessageStore((state) => state.message);
|
const messageBoardState = useMessageStore((state) => state.message);
|
||||||
const announcement = useMessageStore((state) => state.announcement);
|
const announcements = useMessageStore((state) => state.announcements);
|
||||||
const fontScale = Dimensions.get("window").fontScale;
|
const fontScale = Dimensions.get("window").fontScale;
|
||||||
|
|
||||||
return !messageBoardState.visible || announcement || warning ? null : (
|
return !messageBoardState.visible ||
|
||||||
|
(announcements && announcements.length) ? null : (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: "95%"
|
width: "95%"
|
||||||
@@ -17,14 +17,13 @@ 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 { useThemeColors } from "@notesnook/theme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ActivityIndicator, useWindowDimensions, View } from "react-native";
|
import { ActivityIndicator, useWindowDimensions, View } from "react-native";
|
||||||
import { notesnook } from "../../../e2e/test.ids";
|
import { notesnook } from "../../../e2e/test.ids";
|
||||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||||
import { useTip } from "../../services/tip-manager";
|
import { TTip, useTip } from "../../services/tip-manager";
|
||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { ColorValues } from "../../utils/colors";
|
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
import { Tip } from "../tip";
|
import { Tip } from "../tip";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
@@ -32,14 +31,33 @@ 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";
|
||||||
|
|
||||||
|
export type PlaceholderData = {
|
||||||
|
title: string;
|
||||||
|
paragraph: string;
|
||||||
|
button?: string;
|
||||||
|
action?: () => void;
|
||||||
|
loading?: string;
|
||||||
|
type?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type EmptyListProps = {
|
||||||
|
loading?: boolean;
|
||||||
|
placeholder?: PlaceholderData;
|
||||||
|
title?: string;
|
||||||
|
color?: string;
|
||||||
|
dataType: string;
|
||||||
|
screen?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export const Empty = React.memo(
|
export const Empty = React.memo(
|
||||||
function Empty({
|
function Empty({
|
||||||
loading = true,
|
loading = true,
|
||||||
placeholderData,
|
placeholder,
|
||||||
headerProps,
|
title,
|
||||||
type,
|
color,
|
||||||
|
dataType,
|
||||||
screen
|
screen
|
||||||
}) {
|
}: EmptyListProps) {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const insets = useGlobalSafeAreaInsets();
|
const insets = useGlobalSafeAreaInsets();
|
||||||
const { height } = useWindowDimensions();
|
const { height } = useWindowDimensions();
|
||||||
@@ -50,8 +68,8 @@ export const Empty = React.memo(
|
|||||||
const tip = useTip(
|
const tip = useTip(
|
||||||
screen === "Notes" && introCompleted
|
screen === "Notes" && introCompleted
|
||||||
? "first-note"
|
? "first-note"
|
||||||
: placeholderData.type || type,
|
: placeholder?.type || ((dataType + "s") as any),
|
||||||
screen === "Notes" ? "notes" : null
|
screen === "Notes" ? "notes" : "list"
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -68,28 +86,23 @@ export const Empty = React.memo(
|
|||||||
{!loading ? (
|
{!loading ? (
|
||||||
<>
|
<>
|
||||||
<Tip
|
<Tip
|
||||||
color={
|
color={color ? color : "accent"}
|
||||||
ColorValues[headerProps.color?.toLowerCase()]
|
tip={tip || ({ text: placeholder?.paragraph } as TTip)}
|
||||||
? headerProps.color
|
|
||||||
: "accent"
|
|
||||||
}
|
|
||||||
tip={tip || { text: placeholderData.paragraph }}
|
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
paddingHorizontal: 0
|
paddingHorizontal: 0
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{placeholderData.button && (
|
{placeholder?.button && (
|
||||||
<Button
|
<Button
|
||||||
testID={notesnook.buttons.add}
|
testID={notesnook.buttons.add}
|
||||||
type="grayAccent"
|
type="grayAccent"
|
||||||
title={placeholderData.button}
|
title={placeholder?.button}
|
||||||
iconPosition="right"
|
iconPosition="right"
|
||||||
icon="arrow-right"
|
icon="arrow-right"
|
||||||
onPress={placeholderData.action}
|
onPress={placeholder?.action}
|
||||||
buttonType={{
|
buttonType={{
|
||||||
text:
|
text: color || colors.primary.accent
|
||||||
colors.static[headerProps.color] || colors.primary.accent
|
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
alignSelf: "flex-start",
|
alignSelf: "flex-start",
|
||||||
@@ -108,17 +121,14 @@ export const Empty = React.memo(
|
|||||||
width: "100%"
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading>{placeholderData.heading}</Heading>
|
<Heading>{placeholder?.title}</Heading>
|
||||||
<Paragraph size={SIZE.sm} textBreakStrategy="balanced">
|
<Paragraph size={SIZE.sm} textBreakStrategy="balanced">
|
||||||
{placeholderData.loading}
|
{placeholder?.loading}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Seperator />
|
<Seperator />
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
size={SIZE.lg}
|
size={SIZE.lg}
|
||||||
color={
|
color={color || colors.primary.accent}
|
||||||
ColorValues[headerProps.color?.toLowerCase()] ||
|
|
||||||
colors.primary.accent
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
@@ -17,177 +17,169 @@ 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 { getTotalNotes } from "@notesnook/common";
|
import {
|
||||||
|
GroupHeader,
|
||||||
|
GroupingKey,
|
||||||
|
Item,
|
||||||
|
VirtualizedGrouping,
|
||||||
|
isGroupHeader
|
||||||
|
} from "@notesnook/core";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import { FlashList } from "@shopify/flash-list";
|
import { FlashList } from "@shopify/flash-list";
|
||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { RefreshControl, View } from "react-native";
|
import {
|
||||||
|
NativeScrollEvent,
|
||||||
|
NativeSyntheticEvent,
|
||||||
|
RefreshControl
|
||||||
|
} from "react-native";
|
||||||
import Animated, { FadeInDown } from "react-native-reanimated";
|
import Animated, { FadeInDown } from "react-native-reanimated";
|
||||||
import { notesnook } from "../../../e2e/test.ids";
|
import { notesnook } from "../../../e2e/test.ids";
|
||||||
import { useGroupOptions } from "../../hooks/use-group-options";
|
import { useGroupOptions } from "../../hooks/use-group-options";
|
||||||
import { eSendEvent } from "../../services/event-manager";
|
import { eSendEvent } from "../../services/event-manager";
|
||||||
import Sync from "../../services/sync";
|
import Sync from "../../services/sync";
|
||||||
|
import { RouteName } from "../../stores/use-navigation-store";
|
||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { eScrollEvent } from "../../utils/events";
|
import { eOpenJumpToDialog, eScrollEvent } from "../../utils/events";
|
||||||
import { tabBarRef } from "../../utils/global-refs";
|
import { tabBarRef } from "../../utils/global-refs";
|
||||||
import JumpToSectionDialog from "../dialogs/jump-to-section";
|
|
||||||
import { Footer } from "../list-items/footer";
|
import { Footer } from "../list-items/footer";
|
||||||
import { Header } from "../list-items/headers/header";
|
import { Header } from "../list-items/headers/header";
|
||||||
import { SectionHeader } from "../list-items/headers/section-header";
|
import { SectionHeader } from "../list-items/headers/section-header";
|
||||||
import { NoteWrapper } from "../list-items/note/wrapper";
|
import { Empty, PlaceholderData } from "./empty";
|
||||||
import { NotebookWrapper } from "../list-items/notebook/wrapper";
|
import { ListItemWrapper } from "./list-item.wrapper";
|
||||||
import ReminderItem from "../list-items/reminder";
|
|
||||||
import TagItem from "../list-items/tag";
|
|
||||||
import { Empty } from "./empty";
|
|
||||||
|
|
||||||
const renderItems = {
|
type ListProps = {
|
||||||
note: NoteWrapper,
|
data: VirtualizedGrouping<Item> | undefined;
|
||||||
notebook: NotebookWrapper,
|
dataType: Item["type"];
|
||||||
topic: NotebookWrapper,
|
onRefresh?: () => void;
|
||||||
tag: TagItem,
|
loading?: boolean;
|
||||||
section: SectionHeader,
|
headerTitle?: string;
|
||||||
header: SectionHeader,
|
customAccentColor?: string;
|
||||||
reminder: ReminderItem
|
renderedInRoute?: RouteName;
|
||||||
|
CustomLisHeader?: React.JSX.Element;
|
||||||
|
isRenderedInActionSheet?: boolean;
|
||||||
|
CustomListComponent?: React.JSX.ElementType;
|
||||||
|
placeholder?: PlaceholderData;
|
||||||
};
|
};
|
||||||
|
|
||||||
const RenderItem = ({ item, index, type, ...restArgs }) => {
|
export default function List(props: ListProps) {
|
||||||
if (!item) return <View />;
|
|
||||||
const Item = renderItems[item.itemType || item.type] || View;
|
|
||||||
const totalNotes = getTotalNotes(item);
|
|
||||||
return (
|
|
||||||
<Item
|
|
||||||
item={item}
|
|
||||||
index={index}
|
|
||||||
type={type}
|
|
||||||
totalNotes={totalNotes}
|
|
||||||
{...restArgs}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {any} param0
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const List = ({
|
|
||||||
listData,
|
|
||||||
type,
|
|
||||||
refreshCallback,
|
|
||||||
placeholderData,
|
|
||||||
loading,
|
|
||||||
headerProps = {
|
|
||||||
heading: "Home",
|
|
||||||
color: null
|
|
||||||
},
|
|
||||||
screen,
|
|
||||||
ListHeader,
|
|
||||||
warning,
|
|
||||||
isSheet = false,
|
|
||||||
onMomentumScrollEnd,
|
|
||||||
handlers,
|
|
||||||
ScrollComponent
|
|
||||||
}) => {
|
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const scrollRef = useRef();
|
const scrollRef = useRef();
|
||||||
const [notesListMode, notebooksListMode] = useSettingStore((state) => [
|
const [notesListMode, notebooksListMode] = useSettingStore((state) => [
|
||||||
state.settings.notesListMode,
|
state.settings.notesListMode,
|
||||||
state.settings.notebooksListMode
|
state.settings.notebooksListMode
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const isCompactModeEnabled =
|
const isCompactModeEnabled =
|
||||||
(type === "notes" && notesListMode === "compact") ||
|
(props.dataType === "note" && notesListMode === "compact") ||
|
||||||
type === "notebooks" ||
|
props.dataType === "notebook" ||
|
||||||
notebooksListMode === "compact";
|
notebooksListMode === "compact";
|
||||||
|
|
||||||
const groupType =
|
const groupType =
|
||||||
screen === "Home" ? "home" : screen === "Favorites" ? "favorites" : type;
|
props.renderedInRoute === "Notes"
|
||||||
|
? "home"
|
||||||
|
: props.renderedInRoute === "Favorites"
|
||||||
|
? "favorites"
|
||||||
|
: `${props.dataType}s`;
|
||||||
|
|
||||||
const groupOptions = useGroupOptions(groupType);
|
const groupOptions = useGroupOptions(groupType);
|
||||||
|
|
||||||
const dateBy =
|
|
||||||
groupOptions.sortBy !== "title" ? groupOptions.sortBy : "dateEdited";
|
|
||||||
|
|
||||||
const renderItem = React.useCallback(
|
|
||||||
({ item, index }) => (
|
|
||||||
<RenderItem
|
|
||||||
item={item}
|
|
||||||
index={index}
|
|
||||||
color={headerProps?.color}
|
|
||||||
title={headerProps?.heading}
|
|
||||||
dateBy={dateBy}
|
|
||||||
type={groupType}
|
|
||||||
screen={screen}
|
|
||||||
isSheet={isSheet}
|
|
||||||
groupOptions={groupOptions}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[
|
|
||||||
headerProps?.color,
|
|
||||||
headerProps?.heading,
|
|
||||||
screen,
|
|
||||||
isSheet,
|
|
||||||
dateBy,
|
|
||||||
groupType,
|
|
||||||
groupOptions
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
const _onRefresh = async () => {
|
const _onRefresh = async () => {
|
||||||
Sync.run("global", false, true, () => {
|
Sync.run("global", false, true, () => {
|
||||||
if (refreshCallback) {
|
props.onRefresh?.();
|
||||||
refreshCallback();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const _onScroll = React.useCallback(
|
const renderItem = React.useCallback(
|
||||||
(event) => {
|
({ item, index }: { item: string | GroupHeader; index: number }) => {
|
||||||
|
if (isGroupHeader(item)) {
|
||||||
|
return (
|
||||||
|
<SectionHeader
|
||||||
|
screen={props.renderedInRoute}
|
||||||
|
item={item}
|
||||||
|
index={index}
|
||||||
|
dataType={props.dataType}
|
||||||
|
color={props.customAccentColor}
|
||||||
|
groupOptions={groupOptions}
|
||||||
|
onOpenJumpToDialog={() => {
|
||||||
|
eSendEvent(eOpenJumpToDialog, {
|
||||||
|
ref: scrollRef,
|
||||||
|
data: props.data
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<ListItemWrapper
|
||||||
|
id={item}
|
||||||
|
index={index}
|
||||||
|
isSheet={props.isRenderedInActionSheet || false}
|
||||||
|
items={props.data}
|
||||||
|
group={groupType as GroupingKey}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
groupOptions,
|
||||||
|
groupType,
|
||||||
|
props.customAccentColor,
|
||||||
|
props.data,
|
||||||
|
props.dataType,
|
||||||
|
props.isRenderedInActionSheet,
|
||||||
|
props.renderedInRoute
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onListScroll = React.useCallback(
|
||||||
|
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||||
if (!event) return;
|
if (!event) return;
|
||||||
let y = event.nativeEvent.contentOffset.y;
|
|
||||||
eSendEvent(eScrollEvent, {
|
eSendEvent(eScrollEvent, {
|
||||||
y,
|
y: event.nativeEvent.contentOffset.y,
|
||||||
screen
|
screen: props.renderedInRoute
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[screen]
|
[props.renderedInRoute]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSendEvent(eScrollEvent, {
|
eSendEvent(eScrollEvent, {
|
||||||
y: 0,
|
y: 0,
|
||||||
screen
|
screen: props.renderedInRoute
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
let styles = {
|
const styles = {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
minHeight: 1,
|
minHeight: 1,
|
||||||
minWidth: 1
|
minWidth: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
const ListView = ScrollComponent ? ScrollComponent : FlashList;
|
const ListView = props.CustomListComponent
|
||||||
|
? props.CustomListComponent
|
||||||
|
: FlashList;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={{
|
style={{
|
||||||
flex: 1
|
flex: 1
|
||||||
}}
|
}}
|
||||||
entering={type === "search" ? undefined : FadeInDown}
|
entering={props.renderedInRoute === "Search" ? undefined : FadeInDown}
|
||||||
>
|
>
|
||||||
<ListView
|
<ListView
|
||||||
{...handlers}
|
|
||||||
style={styles}
|
style={styles}
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
testID={notesnook.list.id}
|
testID={notesnook.list.id}
|
||||||
data={listData}
|
data={props.data?.ids || []}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
onScroll={_onScroll}
|
onScroll={onListScroll}
|
||||||
nestedScrollEnabled={true}
|
nestedScrollEnabled={true}
|
||||||
onMomentumScrollEnd={() => {
|
onMomentumScrollEnd={() => {
|
||||||
tabBarRef.current?.unlock();
|
tabBarRef.current?.unlock();
|
||||||
onMomentumScrollEnd?.();
|
|
||||||
}}
|
}}
|
||||||
getItemType={(item) => item.itemType || item.type}
|
getItemType={(item: any) => (isGroupHeader(item) ? "header" : "item")}
|
||||||
estimatedItemSize={isCompactModeEnabled ? 60 : 100}
|
estimatedItemSize={isCompactModeEnabled ? 60 : 100}
|
||||||
directionalLockEnabled={true}
|
directionalLockEnabled={true}
|
||||||
keyboardShouldPersistTaps="always"
|
keyboardShouldPersistTaps="always"
|
||||||
@@ -202,44 +194,31 @@ const List = ({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
placeholderData ? (
|
props.placeholder ? (
|
||||||
<Empty
|
<Empty
|
||||||
loading={loading}
|
loading={props.loading}
|
||||||
placeholderData={placeholderData}
|
title={props.headerTitle}
|
||||||
headerProps={headerProps}
|
dataType={props.dataType}
|
||||||
type={type}
|
color={props.customAccentColor}
|
||||||
screen={screen}
|
placeholder={props.placeholder}
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
ListFooterComponent={<Footer />}
|
ListFooterComponent={<Footer />}
|
||||||
ListHeaderComponent={
|
ListHeaderComponent={
|
||||||
<>
|
<>
|
||||||
{ListHeader ? (
|
{props.CustomLisHeader ? (
|
||||||
ListHeader
|
props.CustomLisHeader
|
||||||
) : !headerProps ? null : (
|
) : !props.headerTitle ? null : (
|
||||||
<Header
|
<Header
|
||||||
title={headerProps.heading}
|
color={props.customAccentColor}
|
||||||
color={headerProps.color}
|
screen={props.renderedInRoute}
|
||||||
type={type}
|
|
||||||
screen={screen}
|
|
||||||
warning={warning}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
{listData ? (
|
|
||||||
<JumpToSectionDialog
|
|
||||||
screen={screen}
|
|
||||||
data={listData}
|
|
||||||
type={screen === "Home" ? "home" : type}
|
|
||||||
scrollRef={scrollRef}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default List;
|
|
||||||
269
apps/mobile/app/components/list/list-item.wrapper.tsx
Normal file
269
apps/mobile/app/components/list/list-item.wrapper.tsx
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the Notesnook project (https://notesnook.com/)
|
||||||
|
|
||||||
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
import { getSortValue } from "@notesnook/core/dist/utils/grouping";
|
||||||
|
import {
|
||||||
|
GroupingKey,
|
||||||
|
Item,
|
||||||
|
VirtualizedGrouping,
|
||||||
|
Color,
|
||||||
|
Reminder,
|
||||||
|
Notebook,
|
||||||
|
Tag,
|
||||||
|
TrashItem,
|
||||||
|
ItemType,
|
||||||
|
Note
|
||||||
|
} from "@notesnook/core";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { View } from "react-native";
|
||||||
|
import { NoteWrapper } from "../list-items/note/wrapper";
|
||||||
|
import { db } from "../../common/database";
|
||||||
|
import { NotebookWrapper } from "../list-items/notebook/wrapper";
|
||||||
|
import ReminderItem from "../list-items/reminder";
|
||||||
|
import TagItem from "../list-items/tag";
|
||||||
|
|
||||||
|
export type WithDateEdited<T> = { items: T[]; dateEdited: number };
|
||||||
|
export type NotebooksWithDateEdited = WithDateEdited<Notebook>;
|
||||||
|
export type TagsWithDateEdited = WithDateEdited<Tag>;
|
||||||
|
|
||||||
|
type ListItemWrapperProps<TItem = Item> = {
|
||||||
|
group?: GroupingKey;
|
||||||
|
items: VirtualizedGrouping<TItem> | undefined;
|
||||||
|
id: string;
|
||||||
|
isSheet: boolean;
|
||||||
|
index: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||||
|
const { id, items, group, isSheet, index } = props;
|
||||||
|
const [item, setItem] = useState<Item>();
|
||||||
|
const tags = useRef<TagsWithDateEdited>();
|
||||||
|
const notebooks = useRef<NotebooksWithDateEdited>();
|
||||||
|
const reminder = useRef<Reminder>();
|
||||||
|
const color = useRef<Color>();
|
||||||
|
const totalNotes = useRef<number>(0);
|
||||||
|
const attachmentsCount = useRef(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async function () {
|
||||||
|
const { item, data } = (await items?.item(id, resolveItems)) || {};
|
||||||
|
if (!item) return;
|
||||||
|
if (item.type === "note" && isNoteResolvedData(data)) {
|
||||||
|
tags.current = data.tags;
|
||||||
|
notebooks.current = data.notebooks;
|
||||||
|
reminder.current = data.reminder;
|
||||||
|
color.current = data.color;
|
||||||
|
attachmentsCount.current = data.attachmentsCount;
|
||||||
|
} else if (item.type === "notebook" && typeof data === "number") {
|
||||||
|
totalNotes.current = data;
|
||||||
|
} else if (item.type === "tag" && typeof data === "number") {
|
||||||
|
totalNotes.current = data;
|
||||||
|
}
|
||||||
|
setItem(item);
|
||||||
|
})();
|
||||||
|
}, [id, items]);
|
||||||
|
|
||||||
|
if (!item) return <View style={{ height: 100, width: "100%" }} />;
|
||||||
|
|
||||||
|
const type = ((item as TrashItem).itemType || item.type) as ItemType;
|
||||||
|
switch (type) {
|
||||||
|
case "note": {
|
||||||
|
return (
|
||||||
|
<NoteWrapper
|
||||||
|
item={item as Note}
|
||||||
|
tags={tags.current}
|
||||||
|
color={color.current}
|
||||||
|
notebooks={notebooks.current}
|
||||||
|
reminder={reminder.current}
|
||||||
|
attachmentsCount={attachmentsCount.current}
|
||||||
|
date={getDate(item, group)}
|
||||||
|
isRenderedInActionSheet={isSheet}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "notebook":
|
||||||
|
return (
|
||||||
|
<NotebookWrapper
|
||||||
|
item={item as Notebook}
|
||||||
|
totalNotes={totalNotes.current}
|
||||||
|
date={getDate(item, group)}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "reminder":
|
||||||
|
return (
|
||||||
|
<ReminderItem item={item as Reminder} index={index} isSheet={isSheet} />
|
||||||
|
);
|
||||||
|
case "tag":
|
||||||
|
return (
|
||||||
|
<TagItem
|
||||||
|
item={item as Tag}
|
||||||
|
index={index}
|
||||||
|
totalNotes={totalNotes.current}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function withDateEdited<
|
||||||
|
T extends { dateEdited: number } | { dateModified: number }
|
||||||
|
>(items: T[]): WithDateEdited<T> {
|
||||||
|
let latestDateEdited = 0;
|
||||||
|
items.forEach((item) => {
|
||||||
|
const date = "dateEdited" in item ? item.dateEdited : item.dateModified;
|
||||||
|
if (latestDateEdited < date) latestDateEdited = date;
|
||||||
|
});
|
||||||
|
return { dateEdited: latestDateEdited, items };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDate(item: Item, groupType?: GroupingKey): number {
|
||||||
|
return (
|
||||||
|
getSortValue(
|
||||||
|
groupType
|
||||||
|
? db.settings.getGroupOptions(groupType)
|
||||||
|
: {
|
||||||
|
groupBy: "default",
|
||||||
|
sortBy: "dateEdited",
|
||||||
|
sortDirection: "desc"
|
||||||
|
},
|
||||||
|
item
|
||||||
|
) || 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveItems(ids: string[], items: Record<string, Item>) {
|
||||||
|
const { type } = items[ids[0]];
|
||||||
|
if (type === "note") return resolveNotes(ids);
|
||||||
|
else if (type === "notebook") {
|
||||||
|
const data: Record<string, number> = {};
|
||||||
|
for (const id of ids) data[id] = await db.notebooks.totalNotes(id);
|
||||||
|
return data;
|
||||||
|
} else if (type === "tag") {
|
||||||
|
const data: Record<string, number> = {};
|
||||||
|
for (const id of ids)
|
||||||
|
data[id] = await db.relations.from({ id, type: "tag" }, "note").count();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
type NoteResolvedData = {
|
||||||
|
notebooks?: NotebooksWithDateEdited;
|
||||||
|
reminder?: Reminder;
|
||||||
|
color?: Color;
|
||||||
|
tags?: TagsWithDateEdited;
|
||||||
|
attachmentsCount: number;
|
||||||
|
};
|
||||||
|
async function resolveNotes(ids: string[]) {
|
||||||
|
console.time("relations");
|
||||||
|
const relations = [
|
||||||
|
...(await db.relations
|
||||||
|
.to({ type: "note", ids }, ["notebook", "tag", "color"])
|
||||||
|
.get()),
|
||||||
|
...(await db.relations.from({ type: "note", ids }, "reminder").get())
|
||||||
|
];
|
||||||
|
console.timeEnd("relations");
|
||||||
|
|
||||||
|
const relationIds: {
|
||||||
|
notebooks: Set<string>;
|
||||||
|
colors: Set<string>;
|
||||||
|
tags: Set<string>;
|
||||||
|
reminders: Set<string>;
|
||||||
|
} = {
|
||||||
|
colors: new Set(),
|
||||||
|
notebooks: new Set(),
|
||||||
|
tags: new Set(),
|
||||||
|
reminders: new Set()
|
||||||
|
};
|
||||||
|
|
||||||
|
const grouped: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
notebooks: string[];
|
||||||
|
color?: string;
|
||||||
|
tags: string[];
|
||||||
|
reminder?: string;
|
||||||
|
}
|
||||||
|
> = {};
|
||||||
|
for (const relation of relations) {
|
||||||
|
const noteId =
|
||||||
|
relation.toType === "relation" ? relation.fromId : relation.toId;
|
||||||
|
const data = grouped[noteId] || {
|
||||||
|
notebooks: [],
|
||||||
|
tags: []
|
||||||
|
};
|
||||||
|
|
||||||
|
if (relation.toType === "relation" && !data.reminder) {
|
||||||
|
data.reminder = relation.fromId;
|
||||||
|
relationIds.reminders.add(relation.fromId);
|
||||||
|
} else if (relation.fromType === "notebook" && data.notebooks.length < 2) {
|
||||||
|
data.notebooks.push(relation.fromId);
|
||||||
|
relationIds.notebooks.add(relation.fromId);
|
||||||
|
} else if (relation.fromType === "tag" && data.tags.length < 3) {
|
||||||
|
data.tags.push(relation.fromId);
|
||||||
|
relationIds.tags.add(relation.fromId);
|
||||||
|
} else if (relation.fromType === "color" && !data.color) {
|
||||||
|
data.color = relation.fromId;
|
||||||
|
relationIds.colors.add(relation.fromId);
|
||||||
|
}
|
||||||
|
grouped[relation.toId] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.time("resolve");
|
||||||
|
const resolved = {
|
||||||
|
notebooks: await db.notebooks.all.records(
|
||||||
|
Array.from(relationIds.notebooks)
|
||||||
|
),
|
||||||
|
tags: await db.tags.all.records(Array.from(relationIds.tags)),
|
||||||
|
colors: await db.colors.all.records(Array.from(relationIds.colors)),
|
||||||
|
reminders: await db.reminders.all.records(Array.from(relationIds.reminders))
|
||||||
|
};
|
||||||
|
console.timeEnd("resolve");
|
||||||
|
|
||||||
|
const data: Record<string, NoteResolvedData> = {};
|
||||||
|
for (const noteId in grouped) {
|
||||||
|
const group = grouped[noteId];
|
||||||
|
data[noteId] = {
|
||||||
|
color: group.color ? resolved.colors[group.color] : undefined,
|
||||||
|
reminder: group.reminder ? resolved.reminders[group.reminder] : undefined,
|
||||||
|
tags: withDateEdited(group.tags.map((id) => resolved.tags[id])),
|
||||||
|
notebooks: withDateEdited(
|
||||||
|
group.notebooks.map((id) => resolved.notebooks[id])
|
||||||
|
),
|
||||||
|
attachmentsCount:
|
||||||
|
(await db.attachments?.ofNote(noteId, "all"))?.length || 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNoteResolvedData(data: unknown): data is NoteResolvedData {
|
||||||
|
return (
|
||||||
|
typeof data === "object" &&
|
||||||
|
!!data &&
|
||||||
|
"notebooks" in data &&
|
||||||
|
"reminder" in data &&
|
||||||
|
"color" in data &&
|
||||||
|
"tags" in data
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -35,6 +35,11 @@ import { presentDialog } from "../dialog/functions";
|
|||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {any} param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export default function NotePreview({ session, content, note }) {
|
export default function NotePreview({ session, content, note }) {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const editorId = ":noteHistory";
|
const editorId = ":noteHistory";
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
|||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {any} param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export const ProTag = ({ width, size, background }) => {
|
export const ProTag = ({ width, size, background }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { DefaultColors } from "@notesnook/core/dist/collections/colors";
|
import { DefaultColors } from "@notesnook/core/dist/collections/colors";
|
||||||
import { Note } from "@notesnook/core/dist/types";
|
import { Color, ItemReference, Note } from "@notesnook/core/dist/types";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { notesnook } from "../../../e2e/test.ids";
|
import { notesnook } from "../../../e2e/test.ids";
|
||||||
@@ -40,16 +40,15 @@ export const ColorTags = ({ item }: { item: Note }) => {
|
|||||||
const isTablet = useSettingStore((state) => state.deviceMode) !== "mobile";
|
const isTablet = useSettingStore((state) => state.deviceMode) !== "mobile";
|
||||||
const updater = useRelationStore((state) => state.updater);
|
const updater = useRelationStore((state) => state.updater);
|
||||||
|
|
||||||
const getColorInfo = (colorCode: string) => {
|
const getColorInfo = async (colorCode: string) => {
|
||||||
const dbColor = db.colors.all.find((v) => v.colorCode === colorCode);
|
const dbColor = await db.colors.all.find((v) =>
|
||||||
|
v.and([v(`colorCode`, "==", colorCode)])
|
||||||
|
);
|
||||||
let isLinked = false;
|
let isLinked = false;
|
||||||
|
|
||||||
if (dbColor) {
|
if (dbColor) {
|
||||||
const note = db.relations
|
const hasRelation = await db.relations.from(dbColor, "note").has(item.id);
|
||||||
.from(dbColor, "note")
|
if (hasRelation) {
|
||||||
.find((relation) => relation.to.id === item.id);
|
|
||||||
|
|
||||||
if (note) {
|
|
||||||
isLinked = true;
|
isLinked = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,36 +59,16 @@ export const ColorTags = ({ item }: { item: Note }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeColor = async (color: string) => {
|
const ColorItem = ({ name }: { name: keyof typeof DefaultColors }) => {
|
||||||
const colorInfo = getColorInfo(DefaultColors[color]);
|
|
||||||
|
|
||||||
if (colorInfo.item) {
|
|
||||||
if (colorInfo.linked) {
|
|
||||||
await db.relations.unlink(colorInfo.item, item);
|
|
||||||
} else {
|
|
||||||
await db.relations.add(colorInfo.item, item);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const colorId = await db.colors.add({
|
|
||||||
title: color,
|
|
||||||
colorCode: DefaultColors[color]
|
|
||||||
});
|
|
||||||
|
|
||||||
const dbColor = db.colors.color(colorId);
|
|
||||||
if (dbColor) {
|
|
||||||
await db.relations.add(dbColor, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useRelationStore.getState().update();
|
|
||||||
setColorNotes();
|
|
||||||
Navigation.queueRoutesForUpdate();
|
|
||||||
eSendEvent(refreshNotesPage);
|
|
||||||
};
|
|
||||||
|
|
||||||
const _renderColor = (name: keyof typeof DefaultColors) => {
|
|
||||||
const color = DefaultColors[name];
|
const color = DefaultColors[name];
|
||||||
const colorInfo = getColorInfo(color);
|
const [colorInfo, setColorInfo] = useState<{
|
||||||
|
linked: boolean;
|
||||||
|
item: Color | undefined;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getColorInfo(color).then((info) => setColorInfo(info));
|
||||||
|
}, [color]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PressableButton
|
<PressableButton
|
||||||
@@ -108,13 +87,40 @@ export const ColorTags = ({ item }: { item: Note }) => {
|
|||||||
marginRight: isTablet ? 10 : undefined
|
marginRight: isTablet ? 10 : undefined
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{colorInfo.linked ? (
|
{colorInfo?.linked ? (
|
||||||
<Icon testID="icon-check" name="check" color="white" size={SIZE.lg} />
|
<Icon testID="icon-check" name="check" color="white" size={SIZE.lg} />
|
||||||
) : null}
|
) : null}
|
||||||
</PressableButton>
|
</PressableButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const changeColor = async (color: string) => {
|
||||||
|
const colorInfo = await getColorInfo(DefaultColors[color]);
|
||||||
|
|
||||||
|
if (colorInfo.item) {
|
||||||
|
if (colorInfo.linked) {
|
||||||
|
await db.relations.unlink(colorInfo.item, item);
|
||||||
|
} else {
|
||||||
|
await db.relations.add(colorInfo.item, item);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const colorId = await db.colors.add({
|
||||||
|
title: color,
|
||||||
|
colorCode: DefaultColors[color]
|
||||||
|
});
|
||||||
|
|
||||||
|
const dbColor = await db.colors.color(colorId);
|
||||||
|
if (dbColor) {
|
||||||
|
await db.relations.add(dbColor as unknown as ItemReference, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useRelationStore.getState().update();
|
||||||
|
setColorNotes();
|
||||||
|
Navigation.queueRoutesForUpdate();
|
||||||
|
eSendEvent(refreshNotesPage);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -127,7 +133,9 @@ export const ColorTags = ({ item }: { item: Note }) => {
|
|||||||
justifyContent: isTablet ? "center" : "space-between"
|
justifyContent: isTablet ? "center" : "space-between"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Object.keys(DefaultColors).map(_renderColor)}
|
{Object.keys(DefaultColors).map((name: keyof typeof DefaultColors) => {
|
||||||
|
return <ColorItem key={name} name={name} />;
|
||||||
|
})}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,23 +53,24 @@ export const DateMeta = ({ item }) => {
|
|||||||
return keys.filter((key) => key.startsWith("date") && key !== "date");
|
return keys.filter((key) => key.startsWith("date") && key !== "date");
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderItem = (key) => (
|
const renderItem = (key) =>
|
||||||
<View
|
!item[key] ? null : (
|
||||||
key={key}
|
<View
|
||||||
style={{
|
key={key}
|
||||||
flexDirection: "row",
|
style={{
|
||||||
justifyContent: "space-between",
|
flexDirection: "row",
|
||||||
paddingVertical: 3
|
justifyContent: "space-between",
|
||||||
}}
|
paddingVertical: 3
|
||||||
>
|
}}
|
||||||
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
>
|
||||||
{getNameFromKey(key)}
|
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||||
</Paragraph>
|
{getNameFromKey(key)}
|
||||||
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
</Paragraph>
|
||||||
{getFormattedDate(item[key], "date-time")}
|
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||||
</Paragraph>
|
{getFormattedDate(item[key], "date-time")}
|
||||||
</View>
|
</Paragraph>
|
||||||
);
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import { Items } from "./items";
|
|||||||
import Notebooks from "./notebooks";
|
import Notebooks from "./notebooks";
|
||||||
import { Synced } from "./synced";
|
import { Synced } from "./synced";
|
||||||
import { TagStrip, Tags } from "./tags";
|
import { TagStrip, Tags } from "./tags";
|
||||||
|
|
||||||
const Line = ({ top = 6, bottom = 6 }) => {
|
const Line = ({ top = 6, bottom = 6 }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
return (
|
return (
|
||||||
@@ -151,7 +152,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Properties.present = (item, buttons = [], isSheet) => {
|
Properties.present = async (item, buttons = [], isSheet) => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
let type = item?.type;
|
let type = item?.type;
|
||||||
let props = [];
|
let props = [];
|
||||||
@@ -163,7 +164,7 @@ Properties.present = (item, buttons = [], isSheet) => {
|
|||||||
break;
|
break;
|
||||||
case "note":
|
case "note":
|
||||||
android = Platform.OS === "android" ? ["pin-to-notifications"] : [];
|
android = Platform.OS === "android" ? ["pin-to-notifications"] : [];
|
||||||
props[0] = db.notes.note(item.id).data;
|
props[0] = await db.notes.note(item.id);
|
||||||
props.push([
|
props.push([
|
||||||
"notebooks",
|
"notebooks",
|
||||||
"add-reminder",
|
"add-reminder",
|
||||||
@@ -188,33 +189,34 @@ Properties.present = (item, buttons = [], isSheet) => {
|
|||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case "notebook":
|
case "notebook":
|
||||||
props[0] = db.notebooks.notebook(item.id).data;
|
props[0] = await db.notebooks.notebook(item.id);
|
||||||
props.push([
|
props.push([
|
||||||
"edit-notebook",
|
"edit-notebook",
|
||||||
"pin",
|
"pin",
|
||||||
"add-shortcut",
|
"add-shortcut",
|
||||||
"trash",
|
"trash",
|
||||||
"default-notebook"
|
"default-notebook",
|
||||||
]);
|
"add-notebook"
|
||||||
break;
|
|
||||||
case "topic":
|
|
||||||
props[0] = db.notebooks
|
|
||||||
.notebook(item.notebookId)
|
|
||||||
.topics.topic(item.id)._topic;
|
|
||||||
props.push([
|
|
||||||
"move-notes",
|
|
||||||
"edit-topic",
|
|
||||||
"add-shortcut",
|
|
||||||
"trash",
|
|
||||||
"default-topic"
|
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
|
// case "topic":
|
||||||
|
// props[0] = db.notebooks
|
||||||
|
// .notebook(item.notebookId)
|
||||||
|
// .topics.topic(item.id)._topic;
|
||||||
|
// props.push([
|
||||||
|
// "move-notes",
|
||||||
|
// "edit-topic",
|
||||||
|
// "add-shortcut",
|
||||||
|
// "trash",
|
||||||
|
// "default-topic"
|
||||||
|
// ]);
|
||||||
|
// break;
|
||||||
case "tag":
|
case "tag":
|
||||||
props[0] = db.tags.tag(item.id);
|
props[0] = await db.tags.tag(item.id);
|
||||||
props.push(["add-shortcut", "trash", "rename-tag"]);
|
props.push(["add-shortcut", "trash", "rename-tag"]);
|
||||||
break;
|
break;
|
||||||
case "reminder": {
|
case "reminder": {
|
||||||
props[0] = db.reminders.reminder(item.id);
|
props[0] = await db.reminders.reminder(item.id);
|
||||||
props.push(["edit-reminder", "trash", "disable-reminder"]);
|
props.push(["edit-reminder", "trash", "disable-reminder"]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { SIZE } from "../../utils/size";
|
|||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { PressableButton } from "../ui/pressable";
|
import { PressableButton } from "../ui/pressable";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
|
||||||
export const Items = ({ item, buttons, close }) => {
|
export const Items = ({ item, buttons, close }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const dimensions = useSettingStore((state) => state.dimensions);
|
const dimensions = useSettingStore((state) => state.dimensions);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ 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 React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { ScrollView, View } from "react-native";
|
import { ScrollView, View } from "react-native";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
@@ -39,38 +39,20 @@ import { eClearEditor } from "../../utils/events";
|
|||||||
export default function Notebooks({ note, close, full }) {
|
export default function Notebooks({ note, close, full }) {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const notebooks = useNotebookStore((state) => state.notebooks);
|
const notebooks = useNotebookStore((state) => state.notebooks);
|
||||||
function getNotebooks(item) {
|
async function getNotebooks(item) {
|
||||||
let filteredNotebooks = [];
|
let filteredNotebooks = [];
|
||||||
const relations = db.relations.to(note, "notebook");
|
const relations = await db.relations.to(note, "notebook").resolve();
|
||||||
filteredNotebooks.push(
|
|
||||||
...relations.map((notebook) => ({
|
|
||||||
...notebook,
|
|
||||||
topics: []
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
if (!item.notebooks || item.notebooks.length < 1) return filteredNotebooks;
|
|
||||||
|
|
||||||
for (let notebookReference of item.notebooks) {
|
filteredNotebooks.push(relations);
|
||||||
let notebook = {
|
|
||||||
...(notebooks.find((item) => item.id === notebookReference.id) || {})
|
if (!item.notebooks || item.notebooks.length < 1) return filteredNotebooks;
|
||||||
};
|
|
||||||
if (notebook.id) {
|
|
||||||
notebook.topics = notebook.topics.filter((topic) => {
|
|
||||||
return notebookReference.topics.findIndex((t) => t === topic.id) > -1;
|
|
||||||
});
|
|
||||||
const index = filteredNotebooks.findIndex(
|
|
||||||
(item) => item.id === notebook.id
|
|
||||||
);
|
|
||||||
if (index > -1) {
|
|
||||||
filteredNotebooks[index].topics = notebook.topics;
|
|
||||||
} else {
|
|
||||||
filteredNotebooks.push(notebook);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filteredNotebooks;
|
return filteredNotebooks;
|
||||||
}
|
}
|
||||||
const noteNotebooks = getNotebooks(note);
|
const [noteNotebooks, setNoteNotebooks] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getNotebooks().then((notebooks) => setNoteNotebooks(notebooks));
|
||||||
|
});
|
||||||
|
|
||||||
const navigateNotebook = (id) => {
|
const navigateNotebook = (id) => {
|
||||||
let item = db.notebooks.notebook(id)?.data;
|
let item = db.notebooks.notebook(id)?.data;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { sleep } from "../../utils/time";
|
|||||||
import ManageTagsSheet from "../sheets/manage-tags";
|
import ManageTagsSheet from "../sheets/manage-tags";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { ColorTags } from "./color-tags";
|
import { ColorTags } from "./color-tags";
|
||||||
|
|
||||||
export const Tags = ({ item, close }) => {
|
export const Tags = ({ item, close }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of the Notesnook project (https://notesnook.com/)
|
|
||||||
|
|
||||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { View } from "react-native";
|
|
||||||
import { TopicNotes } from "../../screens/notes/topic-notes";
|
|
||||||
import { SIZE } from "../../utils/size";
|
|
||||||
import { Button } from "../ui/button";
|
|
||||||
export const Topics = ({ item, close }) => {
|
|
||||||
const open = (topic) => {
|
|
||||||
close();
|
|
||||||
TopicNotes.navigate(topic, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderItem = (topic) => (
|
|
||||||
<Button
|
|
||||||
key={topic.id}
|
|
||||||
title={topic.title}
|
|
||||||
type="grayBg"
|
|
||||||
// buttonType={{
|
|
||||||
// text: colors.primary.accent
|
|
||||||
// }}
|
|
||||||
height={30}
|
|
||||||
onPress={() => open(topic)}
|
|
||||||
icon="bookmark-outline"
|
|
||||||
fontSize={SIZE.xs}
|
|
||||||
style={{
|
|
||||||
marginRight: 5,
|
|
||||||
paddingHorizontal: 8,
|
|
||||||
borderRadius: 100,
|
|
||||||
marginVertical: 5
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return item &&
|
|
||||||
item.type === "notebook" &&
|
|
||||||
item.topics &&
|
|
||||||
item.topics.length > 0 ? (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
marginTop: 5,
|
|
||||||
width: "100%",
|
|
||||||
flexWrap: "wrap"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.topics
|
|
||||||
.sort((a, b) => a.dateEdited - b.dateEdited)
|
|
||||||
.slice(0, 6)
|
|
||||||
.map(renderItem)}
|
|
||||||
</View>
|
|
||||||
) : null;
|
|
||||||
};
|
|
||||||
@@ -1,560 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of the Notesnook project (https://notesnook.com/)
|
|
||||||
|
|
||||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { createRef } from "react";
|
|
||||||
import {
|
|
||||||
Keyboard,
|
|
||||||
StyleSheet,
|
|
||||||
TextInput,
|
|
||||||
TouchableOpacity,
|
|
||||||
View
|
|
||||||
} from "react-native";
|
|
||||||
import { FlatList } from "react-native-actions-sheet";
|
|
||||||
import { notesnook } from "../../../../e2e/test.ids";
|
|
||||||
import { db } from "../../../common/database";
|
|
||||||
import { DDS } from "../../../services/device-detection";
|
|
||||||
import { ToastManager, presentSheet } from "../../../services/event-manager";
|
|
||||||
import Navigation from "../../../services/navigation";
|
|
||||||
import { useMenuStore } from "../../../stores/use-menu-store";
|
|
||||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
|
||||||
import { SIZE, ph, pv } from "../../../utils/size";
|
|
||||||
import { sleep } from "../../../utils/time";
|
|
||||||
import { Button } from "../../ui/button";
|
|
||||||
import { IconButton } from "../../ui/icon-button";
|
|
||||||
import Input from "../../ui/input";
|
|
||||||
import Seperator from "../../ui/seperator";
|
|
||||||
import Heading from "../../ui/typography/heading";
|
|
||||||
import { MoveNotes } from "../move-notes/movenote";
|
|
||||||
|
|
||||||
let refs = [];
|
|
||||||
export class AddNotebookSheet extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
refs = [];
|
|
||||||
this.state = {
|
|
||||||
notebook: props.notebook,
|
|
||||||
topics:
|
|
||||||
props.notebook?.topics?.map((item) => {
|
|
||||||
return item.title;
|
|
||||||
}) || [],
|
|
||||||
description: null,
|
|
||||||
titleFocused: false,
|
|
||||||
descFocused: false,
|
|
||||||
count: 0,
|
|
||||||
topicInputFocused: false,
|
|
||||||
editTopic: false,
|
|
||||||
loading: false
|
|
||||||
};
|
|
||||||
|
|
||||||
this.title = props.notebook?.title;
|
|
||||||
this.description = props.notebook?.description;
|
|
||||||
this.listRef;
|
|
||||||
this.prevItem = null;
|
|
||||||
this.prevIndex = null;
|
|
||||||
this.currentSelectedInput = null;
|
|
||||||
this.id = props.notebook?.id;
|
|
||||||
this.backPressCount = 0;
|
|
||||||
this.currentInputValue = null;
|
|
||||||
this.titleRef;
|
|
||||||
this.descriptionRef;
|
|
||||||
this.topicsToDelete = [];
|
|
||||||
this.hiddenInput = createRef();
|
|
||||||
this.topicInputRef = createRef();
|
|
||||||
this.addingTopic = false;
|
|
||||||
this.actionSheetRef = props.actionSheetRef;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
refs = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
sleep(300).then(() => {
|
|
||||||
!this.state.notebook && this.titleRef?.focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
close = () => {
|
|
||||||
refs = [];
|
|
||||||
this.props.close(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
onDelete = (index) => {
|
|
||||||
let { topics } = this.state;
|
|
||||||
let prevTopics = topics;
|
|
||||||
refs = [];
|
|
||||||
prevTopics.splice(index, 1);
|
|
||||||
let edit = this.state.notebook;
|
|
||||||
if (edit && edit.id) {
|
|
||||||
let topicToDelete = edit.topics[index];
|
|
||||||
|
|
||||||
if (topicToDelete) {
|
|
||||||
this.topicsToDelete.push(topicToDelete.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let nextTopics = [...prevTopics];
|
|
||||||
if (this.prevIndex === index) {
|
|
||||||
this.prevIndex = null;
|
|
||||||
this.prevItem = null;
|
|
||||||
this.currentInputValue = null;
|
|
||||||
this.topicInputRef.current?.setNativeProps({
|
|
||||||
text: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
topics: nextTopics
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
addNewNotebook = async () => {
|
|
||||||
this.setState({
|
|
||||||
loading: true
|
|
||||||
});
|
|
||||||
let { topics, notebook } = this.state;
|
|
||||||
|
|
||||||
if (!this.title || this.title?.trim().length === 0) {
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Notebook title is required",
|
|
||||||
type: "error",
|
|
||||||
context: "local"
|
|
||||||
});
|
|
||||||
this.setState({
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let toEdit = null;
|
|
||||||
if (notebook) {
|
|
||||||
toEdit = db.notebooks.notebook(notebook.id).data;
|
|
||||||
}
|
|
||||||
|
|
||||||
let prevTopics = [...topics];
|
|
||||||
|
|
||||||
if (this.currentInputValue && this.currentInputValue.trim().length !== 0) {
|
|
||||||
if (this.prevItem != null) {
|
|
||||||
prevTopics[this.prevIndex] = this.currentInputValue;
|
|
||||||
} else {
|
|
||||||
prevTopics.push(this.currentInputValue);
|
|
||||||
this.currentInputValue = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let newNotebookId = null;
|
|
||||||
if (notebook) {
|
|
||||||
if (this.topicsToDelete?.length > 0) {
|
|
||||||
await db.notebooks
|
|
||||||
.notebook(toEdit.id)
|
|
||||||
.topics.delete(...this.topicsToDelete);
|
|
||||||
toEdit = db.notebooks.notebook(toEdit.id).data;
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.notebooks.add({
|
|
||||||
title: this.title,
|
|
||||||
description: this.description,
|
|
||||||
id: notebook.id
|
|
||||||
});
|
|
||||||
|
|
||||||
let nextTopics = toEdit.topics.map((topic, index) => {
|
|
||||||
let copy = { ...topic };
|
|
||||||
copy.title = prevTopics[index];
|
|
||||||
return copy;
|
|
||||||
});
|
|
||||||
|
|
||||||
prevTopics.forEach((title, index) => {
|
|
||||||
if (!nextTopics[index]) {
|
|
||||||
nextTopics.push(title);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.notebooks.topics(toEdit.id).add(
|
|
||||||
...nextTopics.map((topic) => ({
|
|
||||||
title: topic
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
this.close();
|
|
||||||
} else {
|
|
||||||
newNotebookId = await db.notebooks.add({
|
|
||||||
title: this.title,
|
|
||||||
description: this.description,
|
|
||||||
topics: prevTopics.map((topic) => ({
|
|
||||||
title: topic
|
|
||||||
})),
|
|
||||||
id: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
useMenuStore.getState().setMenuPins();
|
|
||||||
Navigation.queueRoutesForUpdate();
|
|
||||||
useRelationStore.getState().update();
|
|
||||||
MoveNotes.present(db.notebooks.notebook(newNotebookId).data);
|
|
||||||
};
|
|
||||||
|
|
||||||
onSubmit = (forward = true) => {
|
|
||||||
this.hiddenInput.current?.focus();
|
|
||||||
let willFocus = true;
|
|
||||||
let { topics } = this.state;
|
|
||||||
if (!this.currentInputValue || this.currentInputValue?.trim().length === 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let prevTopics = [...topics];
|
|
||||||
if (this.prevItem === null) {
|
|
||||||
prevTopics.push(this.currentInputValue);
|
|
||||||
this.setState({
|
|
||||||
topics: prevTopics
|
|
||||||
});
|
|
||||||
setTimeout(() => {
|
|
||||||
this.listRef.current?.scrollToEnd?.({ animated: true });
|
|
||||||
}, 30);
|
|
||||||
this.currentInputValue = null;
|
|
||||||
} else {
|
|
||||||
prevTopics[this.prevIndex] = this.currentInputValue;
|
|
||||||
this.setState({
|
|
||||||
topics: prevTopics
|
|
||||||
});
|
|
||||||
this.currentInputValue = null;
|
|
||||||
|
|
||||||
if (this.state.editTopic) {
|
|
||||||
this.topicInputRef.current?.blur();
|
|
||||||
Keyboard.dismiss();
|
|
||||||
this.setState({
|
|
||||||
editTopic: false
|
|
||||||
});
|
|
||||||
willFocus = false;
|
|
||||||
}
|
|
||||||
this.prevItem = null;
|
|
||||||
this.prevIndex = null;
|
|
||||||
this.currentInputValue = null;
|
|
||||||
|
|
||||||
if (forward) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.listRef.current?.scrollToEnd?.({ animated: true });
|
|
||||||
}, 30);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.topicInputRef.current?.setNativeProps({
|
|
||||||
text: ""
|
|
||||||
});
|
|
||||||
willFocus && this.topicInputRef.current?.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
renderTopicItem = ({ item, index }) => (
|
|
||||||
<TopicItem
|
|
||||||
item={item}
|
|
||||||
onPress={(item, index) => {
|
|
||||||
this.prevIndex = index;
|
|
||||||
this.prevItem = item;
|
|
||||||
this.topicInputRef.current?.setNativeProps({
|
|
||||||
text: item
|
|
||||||
});
|
|
||||||
this.topicInputRef.current?.focus();
|
|
||||||
this.currentInputValue = item;
|
|
||||||
this.setState({
|
|
||||||
editTopic: true
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onDelete={this.onDelete}
|
|
||||||
index={index}
|
|
||||||
colors={this.props.colors}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { colors } = this.props;
|
|
||||||
const { topics, topicInputFocused, notebook } = this.state;
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
maxHeight: DDS.isTab ? "90%" : "97%",
|
|
||||||
borderRadius: DDS.isTab ? 5 : 0,
|
|
||||||
paddingHorizontal: 12
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TextInput
|
|
||||||
ref={this.hiddenInput}
|
|
||||||
style={{
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
opacity: 0,
|
|
||||||
position: "absolute"
|
|
||||||
}}
|
|
||||||
blurOnSubmit={false}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
width: "100%",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Heading size={SIZE.lg}>
|
|
||||||
{notebook && notebook.dateCreated
|
|
||||||
? "Edit Notebook"
|
|
||||||
: "New Notebook"}
|
|
||||||
</Heading>
|
|
||||||
<Button
|
|
||||||
title="Save"
|
|
||||||
type="accent"
|
|
||||||
height={40}
|
|
||||||
style={{
|
|
||||||
borderRadius: 100,
|
|
||||||
paddingHorizontal: 24
|
|
||||||
}}
|
|
||||||
onPress={this.addNewNotebook}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Seperator />
|
|
||||||
|
|
||||||
<Input
|
|
||||||
fwdRef={(ref) => (this.titleRef = ref)}
|
|
||||||
testID={notesnook.ids.dialogs.notebook.inputs.title}
|
|
||||||
onChangeText={(value) => {
|
|
||||||
this.title = value;
|
|
||||||
}}
|
|
||||||
placeholder="Enter a title"
|
|
||||||
onSubmit={() => {
|
|
||||||
this.descriptionRef.focus();
|
|
||||||
}}
|
|
||||||
returnKeyLabel="Next"
|
|
||||||
returnKeyType="next"
|
|
||||||
defaultValue={notebook ? notebook.title : null}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
fwdRef={(ref) => (this.descriptionRef = ref)}
|
|
||||||
testID={notesnook.ids.dialogs.notebook.inputs.description}
|
|
||||||
onChangeText={(value) => {
|
|
||||||
this.description = value;
|
|
||||||
}}
|
|
||||||
placeholder="Describe your notebook."
|
|
||||||
onSubmit={() => {
|
|
||||||
this.topicInputRef.current?.focus();
|
|
||||||
}}
|
|
||||||
returnKeyLabel="Next"
|
|
||||||
returnKeyType="next"
|
|
||||||
defaultValue={notebook ? notebook.description : null}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
fwdRef={this.topicInputRef}
|
|
||||||
testID={notesnook.ids.dialogs.notebook.inputs.topic}
|
|
||||||
onChangeText={(value) => {
|
|
||||||
this.currentInputValue = value;
|
|
||||||
if (this.prevItem !== null) {
|
|
||||||
refs[this.prevIndex].setNativeProps({
|
|
||||||
text: value,
|
|
||||||
style: {
|
|
||||||
borderBottomColor: colors.primary.accent
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
returnKeyLabel="Done"
|
|
||||||
returnKeyType="done"
|
|
||||||
onSubmit={() => {
|
|
||||||
this.onSubmit();
|
|
||||||
}}
|
|
||||||
blurOnSubmit={false}
|
|
||||||
button={{
|
|
||||||
testID: "topic-add-button",
|
|
||||||
icon: this.state.editTopic ? "check" : "plus",
|
|
||||||
onPress: this.onSubmit,
|
|
||||||
color: topicInputFocused
|
|
||||||
? colors.primary.accent
|
|
||||||
: colors.secondary.icon
|
|
||||||
}}
|
|
||||||
placeholder="Add a topic"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FlatList
|
|
||||||
data={topics}
|
|
||||||
ref={(ref) => (this.listRef = ref)}
|
|
||||||
nestedScrollEnabled
|
|
||||||
keyExtractor={(item, index) => item + index.toString()}
|
|
||||||
keyboardShouldPersistTaps="always"
|
|
||||||
keyboardDismissMode="interactive"
|
|
||||||
ListFooterComponent={
|
|
||||||
topics.length === 0 ? null : <View style={{ height: 50 }} />
|
|
||||||
}
|
|
||||||
renderItem={this.renderTopicItem}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AddNotebookSheet.present = (notebook) => {
|
|
||||||
presentSheet({
|
|
||||||
component: (ref, close, _update, colors) => (
|
|
||||||
<AddNotebookSheet
|
|
||||||
actionSheetRef={ref}
|
|
||||||
notebook={notebook}
|
|
||||||
close={close}
|
|
||||||
colors={colors}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const TopicItem = ({ item, index, colors, onPress, onDelete }) => {
|
|
||||||
const topicRef = (ref) => (refs[index] = ref);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
backgroundColor: colors.secondary.background,
|
|
||||||
borderRadius: 5,
|
|
||||||
marginVertical: 5
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={{
|
|
||||||
width: "80%",
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
zIndex: 10,
|
|
||||||
position: "absolute",
|
|
||||||
height: 30
|
|
||||||
}}
|
|
||||||
onPress={() => {
|
|
||||||
onPress(item, index);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
ref={topicRef}
|
|
||||||
editable={false}
|
|
||||||
style={[
|
|
||||||
styles.topicInput,
|
|
||||||
{
|
|
||||||
color: colors.primary.paragraph
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
defaultValue={item}
|
|
||||||
placeholderTextColor={colors.primary.placeholder}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
width: 80,
|
|
||||||
position: "absolute",
|
|
||||||
right: 0,
|
|
||||||
alignItems: "center",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "flex-end"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
onPress={() => {
|
|
||||||
onPress(item, index);
|
|
||||||
}}
|
|
||||||
name="pencil"
|
|
||||||
size={SIZE.lg - 5}
|
|
||||||
color={colors.secondary.icon}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
onPress={() => {
|
|
||||||
onDelete(index);
|
|
||||||
}}
|
|
||||||
name="minus"
|
|
||||||
size={SIZE.lg}
|
|
||||||
color={colors.secondary.icon}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
wrapper: {
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
backgroundColor: "rgba(0,0,0,0.3)",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center"
|
|
||||||
},
|
|
||||||
overlay: {
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
position: "absolute"
|
|
||||||
},
|
|
||||||
headingContainer: {
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center"
|
|
||||||
},
|
|
||||||
headingText: {
|
|
||||||
marginLeft: 5,
|
|
||||||
fontSize: SIZE.xl
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
paddingRight: 12,
|
|
||||||
paddingHorizontal: 0,
|
|
||||||
borderRadius: 0,
|
|
||||||
minHeight: 45,
|
|
||||||
fontSize: SIZE.md,
|
|
||||||
padding: pv - 2,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
marginTop: 10,
|
|
||||||
marginBottom: 5
|
|
||||||
},
|
|
||||||
addBtn: {
|
|
||||||
width: "12%",
|
|
||||||
minHeight: 45,
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
position: "absolute",
|
|
||||||
right: 0
|
|
||||||
},
|
|
||||||
buttonContainer: {
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
flexDirection: "row",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: 20
|
|
||||||
},
|
|
||||||
|
|
||||||
topicContainer: {
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
marginTop: 10
|
|
||||||
},
|
|
||||||
topicInput: {
|
|
||||||
padding: pv - 5,
|
|
||||||
fontSize: SIZE.sm,
|
|
||||||
//fontFamily: "sans-serif",
|
|
||||||
paddingHorizontal: ph,
|
|
||||||
paddingRight: 40,
|
|
||||||
paddingVertical: 10,
|
|
||||||
width: "100%",
|
|
||||||
maxWidth: "100%"
|
|
||||||
},
|
|
||||||
topicBtn: {
|
|
||||||
borderRadius: 5,
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
position: "absolute",
|
|
||||||
right: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
173
apps/mobile/app/components/sheets/add-notebook/index.tsx
Normal file
173
apps/mobile/app/components/sheets/add-notebook/index.tsx
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the Notesnook project (https://notesnook.com/)
|
||||||
|
|
||||||
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Notebook } from "@notesnook/core";
|
||||||
|
import React, { useRef, useState } from "react";
|
||||||
|
import { TextInput, View } from "react-native";
|
||||||
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
|
import { db } from "../../../common/database";
|
||||||
|
import { DDS } from "../../../services/device-detection";
|
||||||
|
import {
|
||||||
|
ToastManager,
|
||||||
|
eSendEvent,
|
||||||
|
presentSheet
|
||||||
|
} from "../../../services/event-manager";
|
||||||
|
import Navigation from "../../../services/navigation";
|
||||||
|
import { useMenuStore } from "../../../stores/use-menu-store";
|
||||||
|
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||||
|
import { SIZE } from "../../../utils/size";
|
||||||
|
import { Button } from "../../ui/button";
|
||||||
|
import Input from "../../ui/input";
|
||||||
|
import Seperator from "../../ui/seperator";
|
||||||
|
import Heading from "../../ui/typography/heading";
|
||||||
|
import { MoveNotes } from "../move-notes/movenote";
|
||||||
|
import { eOnNotebookUpdated } from "../../../utils/events";
|
||||||
|
|
||||||
|
export const AddNotebookSheet = ({
|
||||||
|
notebook,
|
||||||
|
parentNotebook,
|
||||||
|
close
|
||||||
|
}: {
|
||||||
|
notebook?: Notebook;
|
||||||
|
parentNotebook?: Notebook;
|
||||||
|
close: ((context?: string | undefined) => void) | undefined;
|
||||||
|
}) => {
|
||||||
|
const title = useRef(notebook?.title);
|
||||||
|
const description = useRef(notebook?.description);
|
||||||
|
const titleInput = useRef<TextInput>(null);
|
||||||
|
const descriptionInput = useRef<TextInput>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const onSaveChanges = async () => {
|
||||||
|
if (loading) return;
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
if (!title.current || title?.current.trim().length === 0) {
|
||||||
|
ToastManager.show({
|
||||||
|
heading: "Notebook title is required",
|
||||||
|
type: "error",
|
||||||
|
context: "local"
|
||||||
|
});
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = await db.notebooks.add({
|
||||||
|
title: title.current,
|
||||||
|
description: description.current,
|
||||||
|
id: notebook?.id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (parentNotebook) {
|
||||||
|
await db.relations.add(parentNotebook, {
|
||||||
|
type: "notebook",
|
||||||
|
id: id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
useMenuStore.getState().setMenuPins();
|
||||||
|
Navigation.queueRoutesForUpdate();
|
||||||
|
useRelationStore.getState().update();
|
||||||
|
eSendEvent(eOnNotebookUpdated, parentNotebook?.id);
|
||||||
|
if (notebook) {
|
||||||
|
eSendEvent(eOnNotebookUpdated, notebook.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!notebook) {
|
||||||
|
setTimeout(async () => {
|
||||||
|
MoveNotes.present(await db.notebooks.notebook(id));
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
close?.();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
maxHeight: DDS.isTab ? "90%" : "97%",
|
||||||
|
borderRadius: DDS.isTab ? 5 : 0,
|
||||||
|
paddingHorizontal: 12
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
width: "100%",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Heading size={SIZE.lg}>
|
||||||
|
{notebook ? "Edit Notebook" : "New Notebook"}
|
||||||
|
</Heading>
|
||||||
|
<Button
|
||||||
|
title={notebook ? "Save" : "Add"}
|
||||||
|
type="accent"
|
||||||
|
height={40}
|
||||||
|
style={{
|
||||||
|
paddingHorizontal: 24
|
||||||
|
}}
|
||||||
|
onPress={onSaveChanges}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Seperator />
|
||||||
|
|
||||||
|
<Input
|
||||||
|
fwdRef={titleInput}
|
||||||
|
testID={notesnook.ids.dialogs.notebook.inputs.title}
|
||||||
|
onChangeText={(value) => {
|
||||||
|
title.current = value;
|
||||||
|
}}
|
||||||
|
placeholder="Enter a title"
|
||||||
|
onSubmit={() => {
|
||||||
|
descriptionInput.current?.focus();
|
||||||
|
}}
|
||||||
|
returnKeyLabel="Next"
|
||||||
|
returnKeyType="next"
|
||||||
|
defaultValue={notebook ? notebook.title : ""}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
fwdRef={descriptionInput}
|
||||||
|
testID={notesnook.ids.dialogs.notebook.inputs.description}
|
||||||
|
onChangeText={(value) => {
|
||||||
|
description.current = value;
|
||||||
|
}}
|
||||||
|
placeholder="Describe your notebook."
|
||||||
|
returnKeyLabel="Next"
|
||||||
|
returnKeyType="next"
|
||||||
|
defaultValue={notebook ? notebook.description : ""}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
AddNotebookSheet.present = (notebook?: Notebook, parentNotebook?: Notebook) => {
|
||||||
|
presentSheet({
|
||||||
|
component: (ref, close) => (
|
||||||
|
<AddNotebookSheet
|
||||||
|
notebook={notebook}
|
||||||
|
parentNotebook={parentNotebook}
|
||||||
|
close={close}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -20,10 +20,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import { createContext, useContext } from "react";
|
import { createContext, useContext } from "react";
|
||||||
|
|
||||||
export const SelectionContext = createContext({
|
export const SelectionContext = createContext({
|
||||||
toggleSelection: (item) => null,
|
toggleSelection: (item) => {},
|
||||||
deselect: (item) => null,
|
deselect: (item) => {},
|
||||||
select: (item) => null,
|
select: (item) => {},
|
||||||
deselectAll: () => null
|
deselectAll: () => {}
|
||||||
});
|
});
|
||||||
export const SelectionProvider = SelectionContext.Provider;
|
export const SelectionProvider = SelectionContext.Provider;
|
||||||
export const useSelectionContext = () => useContext(SelectionContext);
|
export const useSelectionContext = () => useContext(SelectionContext);
|
||||||
|
|||||||
@@ -17,38 +17,47 @@ 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 React, { useCallback, useEffect, useMemo } from "react";
|
import { Note, Notebook } from "@notesnook/core";
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
|
import React, { RefObject, useCallback, useEffect, useMemo } from "react";
|
||||||
import { Keyboard, TouchableOpacity, View } from "react-native";
|
import { Keyboard, TouchableOpacity, View } from "react-native";
|
||||||
|
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { db } from "../../../common/database";
|
import { db } from "../../../common/database";
|
||||||
import {
|
import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
||||||
eSendEvent,
|
|
||||||
presentSheet,
|
|
||||||
ToastManager
|
|
||||||
} from "../../../services/event-manager";
|
|
||||||
import Navigation from "../../../services/navigation";
|
import Navigation from "../../../services/navigation";
|
||||||
import SearchService from "../../../services/search";
|
import SearchService from "../../../services/search";
|
||||||
import { useNotebookStore } from "../../../stores/use-notebook-store";
|
import { useNotebookStore } from "../../../stores/use-notebook-store";
|
||||||
|
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||||
import { eOnTopicSheetUpdate } from "../../../utils/events";
|
import { eOnNotebookUpdated } from "../../../utils/events";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
|
||||||
import { Dialog } from "../../dialog";
|
import { Dialog } from "../../dialog";
|
||||||
import DialogHeader from "../../dialog/dialog-header";
|
import DialogHeader from "../../dialog/dialog-header";
|
||||||
import { presentDialog } from "../../dialog/functions";
|
|
||||||
import { Button } from "../../ui/button";
|
import { Button } from "../../ui/button";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
import { SelectionProvider } from "./context";
|
import { SelectionProvider } from "./context";
|
||||||
import { FilteredList } from "./filtered-list";
|
import { FilteredList } from "./filtered-list";
|
||||||
import { ListItem } from "./list-item";
|
import { ListItem } from "./list-item";
|
||||||
import { useItemSelectionStore } from "./store";
|
import { useItemSelectionStore } from "./store";
|
||||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
|
||||||
|
|
||||||
const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
/**
|
||||||
|
* Render all notebooks
|
||||||
|
* Render sub notebooks
|
||||||
|
* fix selection, remove topics stuff.
|
||||||
|
* show already selected notebooks regardless of their level
|
||||||
|
* show intermediate selection for nested notebooks at all levels.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const MoveNoteSheet = ({
|
||||||
|
note,
|
||||||
|
actionSheetRef
|
||||||
|
}: {
|
||||||
|
note: Note;
|
||||||
|
actionSheetRef: RefObject<ActionSheetRef>;
|
||||||
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const notebooks = useNotebookStore((state) =>
|
const notebooks = useNotebookStore((state) => state.notebooks);
|
||||||
state.notebooks.filter((n) => n?.type === "notebook")
|
|
||||||
);
|
|
||||||
const dimensions = useSettingStore((state) => state.dimensions);
|
const dimensions = useSettingStore((state) => state.dimensions);
|
||||||
const selectedItemsList = useSelectionStore(
|
const selectedItemsList = useSelectionStore(
|
||||||
(state) => state.selectedItemsList
|
(state) => state.selectedItemsList
|
||||||
@@ -57,90 +66,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
|||||||
|
|
||||||
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
|
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
|
||||||
|
|
||||||
const onAddNotebook = async (title) => {
|
|
||||||
if (!title || title.trim().length === 0) {
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Notebook title is required",
|
|
||||||
type: "error",
|
|
||||||
context: "local"
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.notebooks.add({
|
|
||||||
title: title,
|
|
||||||
description: null,
|
|
||||||
topics: [],
|
|
||||||
id: null
|
|
||||||
});
|
|
||||||
setNotebooks();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const openAddTopicDialog = (item) => {
|
|
||||||
presentDialog({
|
|
||||||
context: "move_note",
|
|
||||||
input: true,
|
|
||||||
inputPlaceholder: "Enter title",
|
|
||||||
title: "New topic",
|
|
||||||
paragraph: "Add a new topic in " + item.title,
|
|
||||||
positiveText: "Add",
|
|
||||||
positivePress: (value) => {
|
|
||||||
return onAddTopic(value, item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onAddTopic = useCallback(
|
|
||||||
async (value, item) => {
|
|
||||||
if (!value || value.trim().length === 0) {
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Topic title is required",
|
|
||||||
type: "error",
|
|
||||||
context: "local"
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.notebooks.topics(item.id).add({
|
|
||||||
title: value
|
|
||||||
});
|
|
||||||
setNotebooks();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
[setNotebooks]
|
|
||||||
);
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
const getSelectedNotesCountInItem = React.useCallback(
|
|
||||||
(item) => {
|
|
||||||
switch (item.type) {
|
|
||||||
case "notebook": {
|
|
||||||
const notes = db.relations.from(item, "note");
|
|
||||||
if (notes.length === 0) return 0;
|
|
||||||
let count = 0;
|
|
||||||
selectedItemsList.forEach((item) =>
|
|
||||||
notes.findIndex((note) => note.id === item.id) > -1
|
|
||||||
? count++
|
|
||||||
: undefined
|
|
||||||
);
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
case "topic": {
|
|
||||||
const noteIds = db.notes?.topicReferences.get(item.id);
|
|
||||||
let count = 0;
|
|
||||||
selectedItemsList.forEach((item) =>
|
|
||||||
noteIds.indexOf(item.id) > -1 ? count++ : undefined
|
|
||||||
);
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[selectedItemsList]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
resetItemState();
|
|
||||||
return () => {
|
return () => {
|
||||||
useItemSelectionStore.getState().setMultiSelect(false);
|
useItemSelectionStore.getState().setMultiSelect(false);
|
||||||
useItemSelectionStore.getState().setItemState({});
|
useItemSelectionStore.getState().setItemState({});
|
||||||
@@ -148,68 +74,10 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const resetItemState = useCallback(
|
const updateItemState = useCallback(function (
|
||||||
(state) => {
|
item: Notebook,
|
||||||
const itemState = {};
|
state: "selected" | "intermediate" | "deselected"
|
||||||
const notebooks = db.notebooks.all;
|
) {
|
||||||
let count = 0;
|
|
||||||
for (let notebook of notebooks) {
|
|
||||||
itemState[notebook.id] = state
|
|
||||||
? state
|
|
||||||
: areAllSelectedItemsInNotebook(notebook, selectedItemsList)
|
|
||||||
? "selected"
|
|
||||||
: getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
|
|
||||||
? "intermediate"
|
|
||||||
: "deselected";
|
|
||||||
if (itemState[notebook.id] === "selected") {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
for (let topic of notebook.topics) {
|
|
||||||
itemState[topic.id] = state
|
|
||||||
? state
|
|
||||||
: areAllSelectedItemsInTopic(topic, selectedItemsList) &&
|
|
||||||
getSelectedNotesCountInItem(topic, selectedItemsList)
|
|
||||||
? "selected"
|
|
||||||
: getSelectedNotesCountInItem(topic, selectedItemsList) > 0
|
|
||||||
? "intermediate"
|
|
||||||
: "deselected";
|
|
||||||
if (itemState[topic.id] === "selected") {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (count > 1) {
|
|
||||||
useItemSelectionStore.getState().setMultiSelect(true);
|
|
||||||
} else {
|
|
||||||
useItemSelectionStore.getState().setMultiSelect(false);
|
|
||||||
}
|
|
||||||
useItemSelectionStore.getState().setItemState(itemState);
|
|
||||||
},
|
|
||||||
[getSelectedNotesCountInItem, selectedItemsList]
|
|
||||||
);
|
|
||||||
|
|
||||||
const getItemsForItem = (item) => {
|
|
||||||
switch (item.type) {
|
|
||||||
case "notebook":
|
|
||||||
return item.topics?.filter((t) => t.type === "topic");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function areAllSelectedItemsInNotebook(notebook, items) {
|
|
||||||
const notes = db.relations.from(notebook, "note");
|
|
||||||
if (notes.length === 0) return false;
|
|
||||||
return items.every((item) => {
|
|
||||||
return notes.find((note) => note.id === item.id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function areAllSelectedItemsInTopic(topic, items) {
|
|
||||||
return items.every((item) => {
|
|
||||||
return db.notes.topicReferences.get(topic.id).indexOf(item.id) > -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateItemState = useCallback(function (item, state) {
|
|
||||||
const itemState = { ...useItemSelectionStore.getState().itemState };
|
const itemState = { ...useItemSelectionStore.getState().itemState };
|
||||||
const mergeState = {
|
const mergeState = {
|
||||||
[item.id]: state
|
[item.id]: state
|
||||||
@@ -218,11 +86,12 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
|||||||
...itemState,
|
...itemState,
|
||||||
...mergeState
|
...mergeState
|
||||||
});
|
});
|
||||||
}, []);
|
},
|
||||||
|
[]);
|
||||||
|
|
||||||
const contextValue = useMemo(
|
const contextValue = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
toggleSelection: (item) => {
|
toggleSelection: (item: Notebook) => {
|
||||||
const itemState = useItemSelectionStore.getState().itemState;
|
const itemState = useItemSelectionStore.getState().itemState;
|
||||||
if (itemState[item.id] === "selected") {
|
if (itemState[item.id] === "selected") {
|
||||||
updateItemState(item, "deselected");
|
updateItemState(item, "deselected");
|
||||||
@@ -230,68 +99,43 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
|||||||
updateItemState(item, "selected");
|
updateItemState(item, "selected");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deselect: (item) => {
|
deselect: (item: Notebook) => {
|
||||||
updateItemState(item, "deselected");
|
updateItemState(item, "deselected");
|
||||||
},
|
},
|
||||||
select: (item) => {
|
select: (item: Notebook) => {
|
||||||
updateItemState(item, "selected");
|
updateItemState(item, "selected");
|
||||||
},
|
},
|
||||||
deselectAll: (state) => {
|
deselectAll: () => {
|
||||||
resetItemState(state);
|
useItemSelectionStore.setState({
|
||||||
|
itemState: {}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[resetItemState, updateItemState]
|
[updateItemState]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getItemFromId = (id) => {
|
|
||||||
for (const nb of notebooks) {
|
|
||||||
if (nb.id === id) return nb;
|
|
||||||
for (const tp of nb.topics) {
|
|
||||||
if (tp.id === id) return tp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSave = async () => {
|
const onSave = async () => {
|
||||||
const noteIds = note ? [note.id] : selectedItemsList.map((n) => n.id);
|
const noteIds = note
|
||||||
|
? [note.id]
|
||||||
|
: selectedItemsList.map((n) => (n as Note).id);
|
||||||
const itemState = useItemSelectionStore.getState().itemState;
|
const itemState = useItemSelectionStore.getState().itemState;
|
||||||
for (const id in itemState) {
|
for (const id in itemState) {
|
||||||
const item = getItemFromId(id);
|
const item = await db.notebooks.notebook(id);
|
||||||
|
if (!item) continue;
|
||||||
if (itemState[id] === "selected") {
|
if (itemState[id] === "selected") {
|
||||||
if (item.type === "notebook") {
|
for (let noteId of noteIds) {
|
||||||
for (let noteId of noteIds) {
|
await db.relations.add(item, { id: noteId, type: "note" });
|
||||||
await db.relations.add(item, { id: noteId, type: "note" });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await db.notes.addToNotebook(
|
|
||||||
{
|
|
||||||
topic: item.id,
|
|
||||||
id: item.notebookId,
|
|
||||||
rebuildCache: true
|
|
||||||
},
|
|
||||||
...noteIds
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if (itemState[id] === "deselected") {
|
} else if (itemState[id] === "deselected") {
|
||||||
if (item.type === "notebook") {
|
for (let noteId of noteIds) {
|
||||||
for (let noteId of noteIds) {
|
await db.relations.unlink(item, { id: noteId, type: "note" });
|
||||||
await db.relations.unlink(item, { id: noteId, type: "note" });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await db.notes.removeFromNotebook(
|
|
||||||
{
|
|
||||||
id: item.notebookId,
|
|
||||||
topic: item.id,
|
|
||||||
rebuildCache: true
|
|
||||||
},
|
|
||||||
...noteIds
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
setNotebooks();
|
setNotebooks();
|
||||||
eSendEvent(eOnTopicSheetUpdate);
|
eSendEvent(eOnNotebookUpdated);
|
||||||
SearchService.updateAndSearch();
|
SearchService.updateAndSearch();
|
||||||
useRelationStore.getState().update();
|
useRelationStore.getState().update();
|
||||||
actionSheetRef.current?.hide();
|
actionSheetRef.current?.hide();
|
||||||
@@ -360,7 +204,9 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
|||||||
}}
|
}}
|
||||||
type="grayAccent"
|
type="grayAccent"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
resetItemState();
|
useItemSelectionStore.setState({
|
||||||
|
itemState: {}
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -370,12 +216,12 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
|||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
maxHeight: dimensions.height * 0.85,
|
maxHeight: dimensions.height * 0.85,
|
||||||
height: 50 * (notebooks.length + 2)
|
height: 50 * ((notebooks?.ids.length || 0) + 2)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FilteredList
|
<FilteredList
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
notebooks.length > 0 ? null : (
|
notebooks?.ids.length ? null : (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@@ -396,7 +242,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
estimatedItemSize={50}
|
estimatedItemSize={50}
|
||||||
data={notebooks}
|
data={notebooks?.ids.length}
|
||||||
hasHeaderSearch={true}
|
hasHeaderSearch={true}
|
||||||
renderItem={({ item, index }) => (
|
renderItem={({ item, index }) => (
|
||||||
<ListItem
|
<ListItem
|
||||||
@@ -24,7 +24,11 @@ import Share from "react-native-share";
|
|||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { notesnook } from "../../../../e2e/test.ids";
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
import { db } from "../../../common/database";
|
import { db } from "../../../common/database";
|
||||||
import { presentSheet, ToastManager } from "../../../services/event-manager";
|
import {
|
||||||
|
presentSheet,
|
||||||
|
PresentSheetOptions,
|
||||||
|
ToastManager
|
||||||
|
} from "../../../services/event-manager";
|
||||||
import Exporter from "../../../services/exporter";
|
import Exporter from "../../../services/exporter";
|
||||||
import PremiumService from "../../../services/premium";
|
import PremiumService from "../../../services/premium";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
@@ -44,20 +48,35 @@ import { eSendEvent } from "../../../services/event-manager";
|
|||||||
import { eCloseSheet } from "../../../utils/events";
|
import { eCloseSheet } from "../../../utils/events";
|
||||||
import { requestInAppReview } from "../../../services/app-review";
|
import { requestInAppReview } from "../../../services/app-review";
|
||||||
import { Dialog } from "../../dialog";
|
import { Dialog } from "../../dialog";
|
||||||
|
import { Note } from "@notesnook/core";
|
||||||
|
|
||||||
const ExportNotesSheet = ({ notes, update }) => {
|
const ExportNotesSheet = ({
|
||||||
|
notes,
|
||||||
|
update
|
||||||
|
}: {
|
||||||
|
notes: Note[];
|
||||||
|
update: ((props: PresentSheetOptions) => void) | undefined;
|
||||||
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const [exporting, setExporting] = useState(false);
|
const [exporting, setExporting] = useState(false);
|
||||||
const [complete, setComplete] = useState(false);
|
const [complete, setComplete] = useState(false);
|
||||||
const [result, setResult] = useState({});
|
const [result, setResult] = useState<
|
||||||
|
| {
|
||||||
|
fileName: string;
|
||||||
|
filePath: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
>();
|
||||||
const [status, setStatus] = useState(null);
|
const [status, setStatus] = useState(null);
|
||||||
const premium = useUserStore((state) => state.premium);
|
const premium = useUserStore((state) => state.premium);
|
||||||
|
|
||||||
const save = async (type) => {
|
const save = async (type: "pdf" | "txt" | "md" | "html") => {
|
||||||
if (exporting) return;
|
if (exporting) return;
|
||||||
if (!PremiumService.get() && type !== "txt") return;
|
if (!PremiumService.get() && type !== "txt") return;
|
||||||
setExporting(true);
|
setExporting(true);
|
||||||
update({ disableClosing: true });
|
update?.({ disableClosing: true } as PresentSheetOptions);
|
||||||
setComplete(false);
|
setComplete(false);
|
||||||
let result;
|
let result;
|
||||||
if (notes.length > 1) {
|
if (notes.length > 1) {
|
||||||
@@ -67,11 +86,11 @@ const ExportNotesSheet = ({ notes, update }) => {
|
|||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
}
|
}
|
||||||
if (!result) {
|
if (!result) {
|
||||||
update({ disableClosing: false });
|
update?.({ disableClosing: false } as PresentSheetOptions);
|
||||||
return setExporting(false);
|
return setExporting(false);
|
||||||
}
|
}
|
||||||
setResult(result);
|
setResult(result as any);
|
||||||
update({ disableClosing: false });
|
update?.({ disableClosing: false } as PresentSheetOptions);
|
||||||
setComplete(true);
|
setComplete(true);
|
||||||
setExporting(false);
|
setExporting(false);
|
||||||
requestInAppReview();
|
requestInAppReview();
|
||||||
@@ -267,7 +286,7 @@ const ExportNotesSheet = ({ notes, update }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Your {notes.length > 1 ? "notes are" : "note is"} exported
|
Your {notes.length > 1 ? "notes are" : "note is"} exported
|
||||||
successfully as {result.fileName}
|
successfully as {result?.fileName}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Button
|
<Button
|
||||||
title="Open"
|
title="Open"
|
||||||
@@ -279,9 +298,10 @@ const ExportNotesSheet = ({ notes, update }) => {
|
|||||||
borderRadius: 100
|
borderRadius: 100
|
||||||
}}
|
}}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
|
if (!result?.filePath) return;
|
||||||
eSendEvent(eCloseSheet);
|
eSendEvent(eCloseSheet);
|
||||||
await sleep(500);
|
await sleep(500);
|
||||||
FileViewer.open(result.filePath, {
|
FileViewer.open(result?.filePath, {
|
||||||
showOpenWithDialog: true,
|
showOpenWithDialog: true,
|
||||||
showAppsSuggestions: true
|
showAppsSuggestions: true
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
@@ -305,6 +325,7 @@ const ExportNotesSheet = ({ notes, update }) => {
|
|||||||
borderRadius: 100
|
borderRadius: 100
|
||||||
}}
|
}}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
|
if (!result?.filePath) return;
|
||||||
if (Platform.OS === "ios") {
|
if (Platform.OS === "ios") {
|
||||||
Share.open({
|
Share.open({
|
||||||
url: result.filePath
|
url: result.filePath
|
||||||
@@ -314,7 +335,7 @@ const ExportNotesSheet = ({ notes, update }) => {
|
|||||||
showOpenWithDialog: true,
|
showOpenWithDialog: true,
|
||||||
showAppsSuggestions: true,
|
showAppsSuggestions: true,
|
||||||
shareFile: true
|
shareFile: true
|
||||||
}).catch(console.log);
|
} as any).catch(console.log);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -329,7 +350,7 @@ const ExportNotesSheet = ({ notes, update }) => {
|
|||||||
}}
|
}}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
setComplete(false);
|
setComplete(false);
|
||||||
setResult(null);
|
setResult(undefined);
|
||||||
setExporting(false);
|
setExporting(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -342,13 +363,11 @@ const ExportNotesSheet = ({ notes, update }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ExportNotesSheet.present = (notes, allNotes) => {
|
ExportNotesSheet.present = async (notes?: Note[], allNotes?: boolean) => {
|
||||||
|
const exportNotes = allNotes ? await db.notes.all?.items() : notes || [];
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: (ref, close, update) => (
|
component: (ref, close, update) => (
|
||||||
<ExportNotesSheet
|
<ExportNotesSheet notes={exportNotes} update={update} />
|
||||||
notes={allNotes ? db.notes.all : notes}
|
|
||||||
update={update}
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
keyboardHandlerDisabled: true
|
keyboardHandlerDisabled: true
|
||||||
});
|
});
|
||||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Tags } from "@notesnook/core/dist/collections/tags";
|
import { Tags } from "@notesnook/core/dist/collections/tags";
|
||||||
import { GroupedItems, Note, Tag } from "@notesnook/core/dist/types";
|
import { Note, Tag, isGroupHeader } from "@notesnook/core/dist/types";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, {
|
import React, {
|
||||||
RefObject,
|
RefObject,
|
||||||
@@ -42,9 +42,16 @@ import Input from "../../ui/input";
|
|||||||
import { PressableButton } from "../../ui/pressable";
|
import { PressableButton } from "../../ui/pressable";
|
||||||
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 { VirtualizedGrouping } from "@notesnook/core";
|
||||||
|
|
||||||
function ungroup(items: GroupedItems<Tag>) {
|
function tagHasSomeNotes(tagId: string, noteIds: string[]) {
|
||||||
return items.filter((item) => item.type !== "header") as Tag[];
|
return db.relations.from({ type: "tag", id: tagId }, "note").has(...noteIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tagHasAllNotes(tagId: string, noteIds: string[]) {
|
||||||
|
return db.relations
|
||||||
|
.from({ type: "tag", id: tagId }, "note")
|
||||||
|
.hasAll(...noteIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ManageTagsSheet = (props: {
|
const ManageTagsSheet = (props: {
|
||||||
@@ -53,49 +60,18 @@ const ManageTagsSheet = (props: {
|
|||||||
}) => {
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const notes = useMemo(() => props.notes || [], [props.notes]);
|
const notes = useMemo(() => props.notes || [], [props.notes]);
|
||||||
const allTags = useTagStore((state) => ungroup(state.tags));
|
const tags = useTagStore((state) => state.tags);
|
||||||
const [tags, setTags] = useState<Tag[]>([]);
|
|
||||||
const [query, setQuery] = useState<string>();
|
const [query, setQuery] = useState<string>();
|
||||||
const inputRef = useRef<TextInput>(null);
|
const inputRef = useRef<TextInput>(null);
|
||||||
const [focus, setFocus] = useState(false);
|
const [focus, setFocus] = useState(false);
|
||||||
|
const [queryExists, setQueryExists] = useState(false);
|
||||||
|
|
||||||
const sortTags = useCallback(() => {
|
const checkQueryExists = (query: string) => {
|
||||||
let _tags = db.tags.all;
|
db.tags.all
|
||||||
|
.find((v) => v.and([v(`title`, "==", query)]))
|
||||||
_tags = _tags.sort((a, b) => a.title.localeCompare(b.title));
|
.then((exists) => setQueryExists(!!exists));
|
||||||
if (query) {
|
};
|
||||||
_tags = db.lookup.tags(_tags, query) as Tag[];
|
|
||||||
}
|
|
||||||
let tagsMerged = notes
|
|
||||||
.map((note) => db.relations.to(note, "tag").resolved())
|
|
||||||
.flat();
|
|
||||||
// Get unique tags and remove duplicates
|
|
||||||
tagsMerged = [
|
|
||||||
...new Map(tagsMerged.map((item) => [item.id, item])).values()
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!tagsMerged || !tagsMerged.length) {
|
|
||||||
setTags(_tags);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let noteTags = [];
|
|
||||||
for (const tag of tagsMerged) {
|
|
||||||
const index = _tags.findIndex((t) => t.id === tag.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
noteTags.push(_tags[index]);
|
|
||||||
_tags.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
noteTags = noteTags.sort((a, b) => a.title.localeCompare(b.title));
|
|
||||||
const combinedTags = [...noteTags, ..._tags];
|
|
||||||
|
|
||||||
setTags(combinedTags);
|
|
||||||
}, [notes, query]);
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// sortTags();
|
|
||||||
// }, [allTags.length]);
|
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
if (!query || query === "" || query.trimStart().length == 0) {
|
if (!query || query === "" || query.trimStart().length == 0) {
|
||||||
@@ -109,29 +85,35 @@ const ManageTagsSheet = (props: {
|
|||||||
|
|
||||||
const tag = query;
|
const tag = query;
|
||||||
setQuery(undefined);
|
setQuery(undefined);
|
||||||
|
|
||||||
inputRef.current?.setNativeProps({
|
inputRef.current?.setNativeProps({
|
||||||
text: ""
|
text: ""
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const exists = db.tags.all.filter((t: Tag) => t.title === tag);
|
const exists = await db.tags.all.find((v) =>
|
||||||
const id = exists.length
|
v.and([v(`title`, "==", tag)])
|
||||||
? exists[0]?.id
|
);
|
||||||
|
|
||||||
|
const id = exists
|
||||||
|
? exists?.id
|
||||||
: await db.tags.add({
|
: await db.tags.add({
|
||||||
title: tag
|
title: tag
|
||||||
});
|
});
|
||||||
|
|
||||||
const createdTag = db.tags.tag(id);
|
if (id) {
|
||||||
if (createdTag) {
|
|
||||||
for (const note of notes) {
|
for (const note of notes) {
|
||||||
await db.relations.add(createdTag, note);
|
await db.relations.add(
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
type: "tag"
|
||||||
|
},
|
||||||
|
note
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useRelationStore.getState().update();
|
useRelationStore.getState().update();
|
||||||
useTagStore.getState().setTags();
|
useTagStore.getState().setTags();
|
||||||
setTimeout(() => {
|
|
||||||
sortTags();
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: "Cannot add tag",
|
heading: "Cannot add tag",
|
||||||
@@ -165,9 +147,7 @@ const ManageTagsSheet = (props: {
|
|||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
onChangeText={(v) => {
|
onChangeText={(v) => {
|
||||||
setQuery(Tags.sanitize(v));
|
setQuery(Tags.sanitize(v));
|
||||||
setTimeout(() => {
|
checkQueryExists(Tags.sanitize(v));
|
||||||
sortTags();
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
onFocusInput={() => {
|
onFocusInput={() => {
|
||||||
setFocus(true);
|
setFocus(true);
|
||||||
@@ -187,7 +167,7 @@ const ManageTagsSheet = (props: {
|
|||||||
keyboardDismissMode="none"
|
keyboardDismissMode="none"
|
||||||
keyboardShouldPersistTaps="always"
|
keyboardShouldPersistTaps="always"
|
||||||
>
|
>
|
||||||
{query && query !== tags[0]?.title ? (
|
{query && !queryExists ? (
|
||||||
<PressableButton
|
<PressableButton
|
||||||
key={"query_item"}
|
key={"query_item"}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
@@ -205,7 +185,7 @@ const ManageTagsSheet = (props: {
|
|||||||
<Icon name="plus" color={colors.selected.icon} size={SIZE.lg} />
|
<Icon name="plus" color={colors.selected.icon} size={SIZE.lg} />
|
||||||
</PressableButton>
|
</PressableButton>
|
||||||
) : null}
|
) : null}
|
||||||
{!allTags || allTags.length === 0 ? (
|
{!tags || tags.ids.length === 0 ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@@ -226,9 +206,16 @@ const ManageTagsSheet = (props: {
|
|||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{tags.map((item) => (
|
{tags?.ids
|
||||||
<TagItem key={item.id} tag={item} notes={notes} />
|
.filter((id) => !isGroupHeader(id))
|
||||||
))}
|
.map((item) => (
|
||||||
|
<TagItem
|
||||||
|
key={item as string}
|
||||||
|
tags={tags}
|
||||||
|
id={item as string}
|
||||||
|
notes={notes}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -244,24 +231,56 @@ ManageTagsSheet.present = (notes?: Note[]) => {
|
|||||||
|
|
||||||
export default ManageTagsSheet;
|
export default ManageTagsSheet;
|
||||||
|
|
||||||
const TagItem = ({ tag, notes }: { tag: Tag; notes: Note[] }) => {
|
const TagItem = ({
|
||||||
|
id,
|
||||||
|
notes,
|
||||||
|
tags
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
notes: Note[];
|
||||||
|
tags: VirtualizedGrouping<Tag>;
|
||||||
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
|
const [tag, setTag] = useState<Tag>();
|
||||||
|
const [selection, setSelection] = useState({
|
||||||
|
all: false,
|
||||||
|
some: false
|
||||||
|
});
|
||||||
const update = useRelationStore((state) => state.updater);
|
const update = useRelationStore((state) => state.updater);
|
||||||
|
|
||||||
const someNotesTagged = notes.some((note) => {
|
const refresh = useCallback(() => {
|
||||||
const relations = db.relations.from(tag, "note");
|
tags.item(id).then(async (tag) => {
|
||||||
return relations.findIndex((relation) => relation.to.id === note.id) > -1;
|
if (tag?.id) {
|
||||||
});
|
setSelection({
|
||||||
|
all: await tagHasAllNotes(
|
||||||
|
tag.id,
|
||||||
|
notes.map((note) => note.id)
|
||||||
|
),
|
||||||
|
some: await tagHasSomeNotes(
|
||||||
|
tag.id,
|
||||||
|
notes.map((note) => note.id)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setTag(tag);
|
||||||
|
});
|
||||||
|
}, [id, tags, notes]);
|
||||||
|
|
||||||
const allNotesTagged = notes.every((note) => {
|
if (tag?.id !== id) {
|
||||||
const relations = db.relations.from(tag, "note");
|
refresh();
|
||||||
return relations.findIndex((relation) => relation.to.id === note.id) > -1;
|
}
|
||||||
});
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tag?.id === id) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}, [id, refresh, tag?.id, update]);
|
||||||
|
|
||||||
const onPress = async () => {
|
const onPress = async () => {
|
||||||
for (const note of notes) {
|
for (const note of notes) {
|
||||||
try {
|
try {
|
||||||
if (someNotesTagged) {
|
if (!tag?.id) return;
|
||||||
|
if (selection.all) {
|
||||||
await db.relations.unlink(tag, note);
|
await db.relations.unlink(tag, note);
|
||||||
} else {
|
} else {
|
||||||
await db.relations.add(tag, note);
|
await db.relations.add(tag, note);
|
||||||
@@ -275,6 +294,7 @@ const TagItem = ({ tag, notes }: { tag: Tag; notes: Note[] }) => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
}, 1);
|
}, 1);
|
||||||
|
refresh();
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<PressableButton
|
<PressableButton
|
||||||
@@ -287,34 +307,48 @@ const TagItem = ({ tag, notes }: { tag: Tag; notes: Note[] }) => {
|
|||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
type="gray"
|
type="gray"
|
||||||
>
|
>
|
||||||
<IconButton
|
{!tag ? null : (
|
||||||
size={22}
|
<IconButton
|
||||||
customStyle={{
|
size={22}
|
||||||
marginRight: 5,
|
customStyle={{
|
||||||
width: 23,
|
marginRight: 5,
|
||||||
height: 23
|
width: 23,
|
||||||
}}
|
height: 23
|
||||||
color={
|
}}
|
||||||
someNotesTagged || allNotesTagged
|
onPress={onPress}
|
||||||
? colors.selected.icon
|
color={
|
||||||
: colors.primary.icon
|
selection.some || selection.all
|
||||||
}
|
? colors.selected.icon
|
||||||
testID={
|
: colors.primary.icon
|
||||||
allNotesTagged
|
}
|
||||||
? "check-circle-outline"
|
testID={
|
||||||
: someNotesTagged
|
selection.all
|
||||||
? "minus-circle-outline"
|
? "check-circle-outline"
|
||||||
: "checkbox-blank-circle-outline"
|
: selection.some
|
||||||
}
|
? "minus-circle-outline"
|
||||||
name={
|
: "checkbox-blank-circle-outline"
|
||||||
allNotesTagged
|
}
|
||||||
? "check-circle-outline"
|
name={
|
||||||
: someNotesTagged
|
selection.all
|
||||||
? "minus-circle-outline"
|
? "check-circle-outline"
|
||||||
: "checkbox-blank-circle-outline"
|
: selection.some
|
||||||
}
|
? "minus-circle-outline"
|
||||||
/>
|
: "checkbox-blank-circle-outline"
|
||||||
<Paragraph size={SIZE.sm}>{"#" + tag.title}</Paragraph>
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{tag ? (
|
||||||
|
<Paragraph size={SIZE.sm}>{"#" + tag?.title}</Paragraph>
|
||||||
|
) : (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
width: 200,
|
||||||
|
height: 30,
|
||||||
|
backgroundColor: colors.secondary.background,
|
||||||
|
borderRadius: 5
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</PressableButton>
|
</PressableButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,54 +17,52 @@ 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 { Note, Notebook, Topic } from "@notesnook/core/dist/types";
|
import { VirtualizedGrouping } from "@notesnook/core";
|
||||||
|
import { Note, Notebook } from "@notesnook/core/dist/types";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { RefObject, useState } from "react";
|
import React, { RefObject, useEffect, useState } from "react";
|
||||||
import { Platform, useWindowDimensions, View } from "react-native";
|
import { Platform, View, useWindowDimensions } from "react-native";
|
||||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||||
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
||||||
import { db } from "../../../common/database";
|
import { db } from "../../../common/database";
|
||||||
import {
|
import { presentSheet } from "../../../services/event-manager";
|
||||||
eSendEvent,
|
|
||||||
presentSheet,
|
|
||||||
ToastManager
|
|
||||||
} from "../../../services/event-manager";
|
|
||||||
import Navigation from "../../../services/navigation";
|
import Navigation from "../../../services/navigation";
|
||||||
import SearchService from "../../../services/search";
|
import SearchService from "../../../services/search";
|
||||||
import { eCloseSheet } from "../../../utils/events";
|
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
import { Dialog } from "../../dialog";
|
import { Dialog } from "../../dialog";
|
||||||
import DialogHeader from "../../dialog/dialog-header";
|
import DialogHeader from "../../dialog/dialog-header";
|
||||||
import { presentDialog } from "../../dialog/functions";
|
|
||||||
import { Button } from "../../ui/button";
|
import { Button } from "../../ui/button";
|
||||||
import { IconButton } from "../../ui/icon-button";
|
import { IconButton } from "../../ui/icon-button";
|
||||||
import { PressableButton } from "../../ui/pressable";
|
import { PressableButton } from "../../ui/pressable";
|
||||||
import Seperator from "../../ui/seperator";
|
import Seperator from "../../ui/seperator";
|
||||||
import Heading from "../../ui/typography/heading";
|
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
|
|
||||||
export const MoveNotes = ({
|
export const MoveNotes = ({
|
||||||
notebook,
|
notebook,
|
||||||
selectedTopic,
|
|
||||||
fwdRef
|
fwdRef
|
||||||
}: {
|
}: {
|
||||||
notebook: Notebook;
|
notebook: Notebook;
|
||||||
selectedTopic?: Topic;
|
|
||||||
fwdRef: RefObject<ActionSheetRef>;
|
fwdRef: RefObject<ActionSheetRef>;
|
||||||
}) => {
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const [currentNotebook, setCurrentNotebook] = useState(notebook);
|
const [currentNotebook, setCurrentNotebook] = useState(notebook);
|
||||||
const { height } = useWindowDimensions();
|
const { height } = useWindowDimensions();
|
||||||
let notes = db.notes?.all;
|
|
||||||
|
|
||||||
const [selectedNoteIds, setSelectedNoteIds] = useState<string[]>([]);
|
const [selectedNoteIds, setSelectedNoteIds] = useState<string[]>([]);
|
||||||
const [topic, setTopic] = useState(selectedTopic);
|
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
||||||
|
const [existingNoteIds, setExistingNoteIds] = useState<string[]>([]);
|
||||||
notes = notes.filter((note) => {
|
useEffect(() => {
|
||||||
if (!topic) return [];
|
db.notes?.all.sorted(db.settings.getGroupOptions("notes")).then((notes) => {
|
||||||
const noteIds = db.notes?.topicReferences.get(topic.id);
|
setNotes(notes);
|
||||||
return noteIds.indexOf(note.id) === -1;
|
});
|
||||||
});
|
db.relations
|
||||||
|
.from(currentNotebook, "note")
|
||||||
|
.get()
|
||||||
|
.then((existingNotes) => {
|
||||||
|
setExistingNoteIds(
|
||||||
|
existingNotes.map((existingNote) => existingNote.toId)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [currentNotebook]);
|
||||||
|
|
||||||
const select = React.useCallback(
|
const select = React.useCallback(
|
||||||
(id: string) => {
|
(id: string) => {
|
||||||
@@ -86,128 +84,20 @@ export const MoveNotes = ({
|
|||||||
[selectedNoteIds]
|
[selectedNoteIds]
|
||||||
);
|
);
|
||||||
|
|
||||||
const openAddTopicDialog = () => {
|
|
||||||
presentDialog({
|
|
||||||
context: "local",
|
|
||||||
input: true,
|
|
||||||
inputPlaceholder: "Enter title",
|
|
||||||
title: "New topic",
|
|
||||||
paragraph: "Add a new topic in " + currentNotebook.title,
|
|
||||||
positiveText: "Add",
|
|
||||||
positivePress: (value) => {
|
|
||||||
return addNewTopic(value as string);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const addNewTopic = async (value: string) => {
|
|
||||||
if (!value || value.trim().length === 0) {
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Topic title is required",
|
|
||||||
type: "error",
|
|
||||||
context: "local"
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
await db.notebooks?.topics(currentNotebook.id).add({
|
|
||||||
title: value
|
|
||||||
});
|
|
||||||
|
|
||||||
const notebook = db.notebooks?.notebook(currentNotebook.id);
|
|
||||||
if (notebook) {
|
|
||||||
setCurrentNotebook(notebook.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
Navigation.queueRoutesForUpdate();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderItem = React.useCallback(
|
const renderItem = React.useCallback(
|
||||||
({ item }: { item: Topic | Note }) => {
|
({ item }: { item: string }) => {
|
||||||
return (
|
return (
|
||||||
<PressableButton
|
<SelectableNoteItem
|
||||||
testID="listitem.select"
|
id={item}
|
||||||
onPress={() => {
|
items={notes}
|
||||||
if (item.type == "topic") {
|
select={select}
|
||||||
setTopic(topic || item);
|
selected={selectedNoteIds?.indexOf(item) > -1}
|
||||||
} else {
|
/>
|
||||||
select(item.id);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
type={"transparent"}
|
|
||||||
customStyle={{
|
|
||||||
paddingVertical: 12,
|
|
||||||
justifyContent: "space-between",
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
flexDirection: "row"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexShrink: 1
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Paragraph
|
|
||||||
numberOfLines={1}
|
|
||||||
color={
|
|
||||||
item?.id === topic?.id
|
|
||||||
? colors.primary.accent
|
|
||||||
: colors.primary.paragraph
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{item.title}
|
|
||||||
</Paragraph>
|
|
||||||
{item.type == "note" && item.headline ? (
|
|
||||||
<Paragraph
|
|
||||||
numberOfLines={1}
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
size={SIZE.xs}
|
|
||||||
>
|
|
||||||
{item.headline}
|
|
||||||
</Paragraph>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{item.type === "topic" ? (
|
|
||||||
<Paragraph
|
|
||||||
style={{
|
|
||||||
fontSize: SIZE.xs
|
|
||||||
}}
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
>
|
|
||||||
{item.notes?.length} Notes
|
|
||||||
</Paragraph>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{selectedNoteIds.indexOf(item.id) > -1 ? (
|
|
||||||
<IconButton
|
|
||||||
customStyle={{
|
|
||||||
width: undefined,
|
|
||||||
height: undefined,
|
|
||||||
backgroundColor: "transparent"
|
|
||||||
}}
|
|
||||||
name="check"
|
|
||||||
type="selected"
|
|
||||||
color={colors.selected.icon}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</PressableButton>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[
|
[notes, select, selectedNoteIds]
|
||||||
colors.primary.accent,
|
|
||||||
colors.secondary.paragraph,
|
|
||||||
colors.primary.paragraph,
|
|
||||||
colors.selected.icon,
|
|
||||||
select,
|
|
||||||
selectedNoteIds,
|
|
||||||
topic
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -217,66 +107,12 @@ export const MoveNotes = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Dialog context="local" />
|
<Dialog context="local" />
|
||||||
{topic ? (
|
|
||||||
<PressableButton
|
|
||||||
onPress={() => {
|
|
||||||
setTopic(undefined);
|
|
||||||
}}
|
|
||||||
customStyle={{
|
|
||||||
paddingVertical: 12,
|
|
||||||
justifyContent: "space-between",
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
marginBottom: 10,
|
|
||||||
alignItems: "flex-start"
|
|
||||||
}}
|
|
||||||
type="grayBg"
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
width: "100%"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Heading size={SIZE.md}>
|
|
||||||
Adding notes to {currentNotebook.title}
|
|
||||||
</Heading>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View
|
<DialogHeader
|
||||||
style={{
|
title={`Add notes to ${currentNotebook.title}`}
|
||||||
flexDirection: "row",
|
paragraph={"Select the topic in which you would like to move notes."}
|
||||||
justifyContent: "space-between",
|
/>
|
||||||
alignItems: "center",
|
<Seperator />
|
||||||
width: "100%",
|
|
||||||
marginTop: 5
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Paragraph color={colors.selected.paragraph}>
|
|
||||||
in {topic.title}
|
|
||||||
</Paragraph>
|
|
||||||
|
|
||||||
<Paragraph
|
|
||||||
style={{
|
|
||||||
fontSize: SIZE.xs
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Tap to change
|
|
||||||
</Paragraph>
|
|
||||||
</View>
|
|
||||||
</PressableButton>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<DialogHeader
|
|
||||||
title={`Add notes to ${currentNotebook.title}`}
|
|
||||||
paragraph={
|
|
||||||
"Select the topic in which you would like to move notes."
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Seperator />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<FlashList
|
<FlashList
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
@@ -288,41 +124,25 @@ export const MoveNotes = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paragraph color={colors.secondary.paragraph}>
|
<Paragraph color={colors.secondary.paragraph}>
|
||||||
{topic ? "No notes to show" : "No topics in this notebook"}
|
No notes to show
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
{!topic && (
|
|
||||||
<Button
|
|
||||||
style={{
|
|
||||||
marginTop: 10,
|
|
||||||
height: 40
|
|
||||||
}}
|
|
||||||
onPress={() => {
|
|
||||||
openAddTopicDialog();
|
|
||||||
}}
|
|
||||||
title="Add first topic"
|
|
||||||
type="grayAccent"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
data={topic ? notes : currentNotebook.topics}
|
data={(notes?.ids as string[])?.filter(
|
||||||
|
(id) => existingNoteIds?.indexOf(id) === -1
|
||||||
|
)}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
/>
|
/>
|
||||||
{selectedNoteIds.length > 0 ? (
|
{selectedNoteIds.length > 0 ? (
|
||||||
<Button
|
<Button
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
if (!topic) return;
|
|
||||||
await db.notes?.addToNotebook(
|
await db.notes?.addToNotebook(
|
||||||
{
|
currentNotebook.id,
|
||||||
topic: topic.id,
|
|
||||||
id: topic.notebookId
|
|
||||||
},
|
|
||||||
...selectedNoteIds
|
...selectedNoteIds
|
||||||
);
|
);
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
SearchService.updateAndSearch();
|
SearchService.updateAndSearch();
|
||||||
eSendEvent(eCloseSheet);
|
fwdRef?.current?.hide();
|
||||||
}}
|
}}
|
||||||
title="Move selected notes"
|
title="Move selected notes"
|
||||||
type="accent"
|
type="accent"
|
||||||
@@ -333,10 +153,81 @@ export const MoveNotes = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
MoveNotes.present = (notebook: Notebook, topic: Topic) => {
|
const SelectableNoteItem = ({
|
||||||
|
id,
|
||||||
|
items,
|
||||||
|
select,
|
||||||
|
selected
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
items?: VirtualizedGrouping<Note>;
|
||||||
|
select: (id: string) => void;
|
||||||
|
selected?: boolean;
|
||||||
|
}) => {
|
||||||
|
const { colors } = useThemeColors();
|
||||||
|
const [item, setItem] = useState<Note>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
items?.item(id).then((item) => setItem(item));
|
||||||
|
}, [id, items]);
|
||||||
|
|
||||||
|
return !item ? null : (
|
||||||
|
<PressableButton
|
||||||
|
testID="listitem.select"
|
||||||
|
onPress={() => {
|
||||||
|
if (!item) return;
|
||||||
|
select(item?.id);
|
||||||
|
}}
|
||||||
|
type={"transparent"}
|
||||||
|
customStyle={{
|
||||||
|
paddingVertical: 12,
|
||||||
|
flexDirection: "row",
|
||||||
|
width: "100%",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
height: 50
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
customStyle={{
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
marginRight: 5
|
||||||
|
}}
|
||||||
|
onPress={() => {
|
||||||
|
if (!item) return;
|
||||||
|
select(item?.id);
|
||||||
|
}}
|
||||||
|
name={
|
||||||
|
selected ? "check-circle-outline" : "checkbox-blank-circle-outline"
|
||||||
|
}
|
||||||
|
type="selected"
|
||||||
|
color={selected ? colors.selected.icon : colors.primary.icon}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexShrink: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Paragraph numberOfLines={1}>{item?.title}</Paragraph>
|
||||||
|
{item.type == "note" && item.headline ? (
|
||||||
|
<Paragraph
|
||||||
|
numberOfLines={1}
|
||||||
|
color={colors?.secondary.paragraph}
|
||||||
|
size={SIZE.xs}
|
||||||
|
>
|
||||||
|
{item.headline}
|
||||||
|
</Paragraph>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
</PressableButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MoveNotes.present = (notebook?: Notebook) => {
|
||||||
|
if (!notebook) return;
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: (ref: RefObject<ActionSheetRef>) => (
|
component: (ref: RefObject<ActionSheetRef>) => (
|
||||||
<MoveNotes fwdRef={ref} notebook={notebook} selectedTopic={topic} />
|
<MoveNotes fwdRef={ref} notebook={notebook} />
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,174 +16,127 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
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 { GroupHeader, Notebook, VirtualizedGrouping } from "@notesnook/core";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import qclone from "qclone";
|
|
||||||
import React, {
|
import React, {
|
||||||
createContext,
|
createContext,
|
||||||
RefObject,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
useState
|
useState
|
||||||
} from "react";
|
} from "react";
|
||||||
import { RefreshControl, useWindowDimensions, View } from "react-native";
|
import { RefreshControl, View, useWindowDimensions } from "react-native";
|
||||||
import ActionSheet, {
|
import ActionSheet, {
|
||||||
ActionSheetRef,
|
ActionSheetRef,
|
||||||
FlatList
|
FlashList
|
||||||
} from "react-native-actions-sheet";
|
} from "react-native-actions-sheet";
|
||||||
import { db } from "../../../common/database";
|
|
||||||
import { IconButton } from "../../../components/ui/icon-button";
|
|
||||||
import { PressableButton } from "../../../components/ui/pressable";
|
|
||||||
import Paragraph from "../../../components/ui/typography/paragraph";
|
|
||||||
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
|
||||||
import {
|
|
||||||
eSendEvent,
|
|
||||||
eSubscribeEvent,
|
|
||||||
eUnSubscribeEvent,
|
|
||||||
presentSheet
|
|
||||||
} from "../../../services/event-manager";
|
|
||||||
import useNavigationStore, {
|
|
||||||
NotebookScreenParams
|
|
||||||
} from "../../../stores/use-navigation-store";
|
|
||||||
import {
|
|
||||||
eOnNewTopicAdded,
|
|
||||||
eOnTopicSheetUpdate,
|
|
||||||
eOpenAddTopicDialog
|
|
||||||
} from "../../../utils/events";
|
|
||||||
import { normalize, SIZE } from "../../../utils/size";
|
|
||||||
|
|
||||||
import { getTotalNotes } from "@notesnook/common";
|
|
||||||
import { groupArray } from "@notesnook/core/dist/utils/grouping";
|
|
||||||
import Config from "react-native-config";
|
import Config from "react-native-config";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
|
import create from "zustand";
|
||||||
import { notesnook } from "../../../../e2e/test.ids";
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
import { MMKV } from "../../../common/database/mmkv";
|
import { MMKV } from "../../../common/database/mmkv";
|
||||||
|
import { useNotebook } from "../../../hooks/use-notebook";
|
||||||
|
import NotebookScreen from "../../../screens/notebook";
|
||||||
import { openEditor } from "../../../screens/notes/common";
|
import { openEditor } from "../../../screens/notes/common";
|
||||||
|
import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
||||||
|
import useNavigationStore from "../../../stores/use-navigation-store";
|
||||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||||
|
import { eOnNotebookUpdated } from "../../../utils/events";
|
||||||
import { deleteItems } from "../../../utils/functions";
|
import { deleteItems } from "../../../utils/functions";
|
||||||
|
import { findRootNotebookId } from "../../../utils/notebooks";
|
||||||
|
import { SIZE, normalize } from "../../../utils/size";
|
||||||
import { Properties } from "../../properties";
|
import { Properties } from "../../properties";
|
||||||
|
import { IconButton } from "../../ui/icon-button";
|
||||||
|
import { PressableButton } from "../../ui/pressable";
|
||||||
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
|
import { AddNotebookSheet } from "../add-notebook";
|
||||||
import Sort from "../sort";
|
import Sort from "../sort";
|
||||||
import { GroupedItems, GroupHeader, Topic } from "@notesnook/core/dist/types";
|
|
||||||
|
|
||||||
type ConfigItem = { id: string; type: string };
|
type ConfigItem = { id: string; type: string };
|
||||||
class TopicSheetConfig {
|
class NotebookSheetConfig {
|
||||||
static storageKey: "$$sp";
|
static storageKey: "$$sp";
|
||||||
|
|
||||||
static makeId(item: ConfigItem) {
|
static makeId(item: ConfigItem) {
|
||||||
return `${TopicSheetConfig.storageKey}:${item.type}:${item.id}`;
|
return `${NotebookSheetConfig.storageKey}:${item.type}:${item.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get(item: ConfigItem) {
|
static get(item: ConfigItem) {
|
||||||
return MMKV.getInt(TopicSheetConfig.makeId(item)) || 0;
|
return MMKV.getInt(NotebookSheetConfig.makeId(item)) || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static set(item: ConfigItem, index = 0) {
|
static set(item: ConfigItem, index = 0) {
|
||||||
MMKV.setInt(TopicSheetConfig.makeId(item), index);
|
MMKV.setInt(NotebookSheetConfig.makeId(item), index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TopicsSheet = () => {
|
const useNotebookExpandedStore = create<{
|
||||||
|
expanded: {
|
||||||
|
[id: string]: boolean;
|
||||||
|
};
|
||||||
|
setExpanded: (id: string) => void;
|
||||||
|
}>((set, get) => ({
|
||||||
|
expanded: {},
|
||||||
|
setExpanded(id: string) {
|
||||||
|
set({
|
||||||
|
expanded: {
|
||||||
|
...get().expanded,
|
||||||
|
[id]: !get().expanded[id]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const NotebookSheet = () => {
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
const currentScreen = useNavigationStore((state) => state.currentScreen);
|
const currentScreen = useNavigationStore((state) => state.currentScreen);
|
||||||
const canShow =
|
const canShow = currentScreen.name === "Notebook";
|
||||||
currentScreen.name === "Notebook" || currentScreen.name === "TopicNotes";
|
const [selection, setSelection] = useState<Notebook[]>([]);
|
||||||
const [notebook, setNotebook] = useState(
|
|
||||||
canShow
|
|
||||||
? db.notebooks?.notebook(
|
|
||||||
currentScreen?.notebookId || currentScreen?.id || ""
|
|
||||||
)?.data
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
const [selection, setSelection] = useState<Topic[]>([]);
|
|
||||||
const [enabled, setEnabled] = useState(false);
|
const [enabled, setEnabled] = useState(false);
|
||||||
const { colors } = useThemeColors("sheet");
|
const { colors } = useThemeColors("sheet");
|
||||||
const ref = useRef<ActionSheetRef>(null);
|
const ref = useRef<ActionSheetRef>(null);
|
||||||
const isTopic = currentScreen.name === "TopicNotes";
|
|
||||||
const [topics, setTopics] = useState<GroupedItems<Topic>>(
|
|
||||||
notebook
|
|
||||||
? qclone(
|
|
||||||
groupArray(notebook.topics, db.settings.getGroupOptions("topics"))
|
|
||||||
)
|
|
||||||
: []
|
|
||||||
);
|
|
||||||
const currentItem = useRef<string>();
|
const currentItem = useRef<string>();
|
||||||
const { fontScale } = useWindowDimensions();
|
const { fontScale } = useWindowDimensions();
|
||||||
const [groupOptions, setGroupOptions] = useState(
|
const [root, setRoot] = useState<string>();
|
||||||
db.settings.getGroupOptions("topics")
|
const {
|
||||||
);
|
onUpdate: onRequestUpdate,
|
||||||
|
notebook,
|
||||||
const onRequestUpdate = React.useCallback(
|
nestedNotebooks: notebooks,
|
||||||
(data?: NotebookScreenParams) => {
|
nestedNotebookNotesCount: totalNotes,
|
||||||
if (!canShow) return;
|
groupOptions
|
||||||
if (!data) data = { item: notebook } as NotebookScreenParams;
|
} = useNotebook(currentScreen.name === "Notebook" ? root : undefined);
|
||||||
const _notebook = db.notebooks?.notebook(data.item?.id)?.data;
|
|
||||||
|
|
||||||
if (_notebook) {
|
|
||||||
setNotebook(_notebook);
|
|
||||||
setTopics(
|
|
||||||
qclone(
|
|
||||||
groupArray(_notebook.topics, db.settings.getGroupOptions("topics"))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[canShow, notebook]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onUpdate = useCallback(() => {
|
|
||||||
setGroupOptions({ ...(db.settings.getGroupOptions("topics") as any) });
|
|
||||||
onRequestUpdate();
|
|
||||||
}, [onRequestUpdate]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
eSubscribeEvent("groupOptionsUpdate", onUpdate);
|
|
||||||
return () => {
|
|
||||||
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
|
|
||||||
};
|
|
||||||
}, [onUpdate]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const onTopicUpdate = () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
onRequestUpdate();
|
|
||||||
}, 1);
|
|
||||||
};
|
|
||||||
eSubscribeEvent(eOnTopicSheetUpdate, onTopicUpdate);
|
|
||||||
eSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
|
|
||||||
return () => {
|
|
||||||
eUnSubscribeEvent(eOnTopicSheetUpdate, onRequestUpdate);
|
|
||||||
eUnSubscribeEvent(eOnNewTopicAdded, onTopicUpdate);
|
|
||||||
};
|
|
||||||
}, [onRequestUpdate]);
|
|
||||||
|
|
||||||
const PLACEHOLDER_DATA = {
|
const PLACEHOLDER_DATA = {
|
||||||
heading: "Topics",
|
heading: "Notebooks",
|
||||||
paragraph: "You have not added any topics yet.",
|
paragraph: "You have not added any notebooks yet.",
|
||||||
button: "Add first topic",
|
button: "Add a notebook",
|
||||||
action: () => {
|
action: () => {
|
||||||
if (!notebook) return;
|
if (!notebook) return;
|
||||||
eSendEvent(eOpenAddTopicDialog, { notebookId: notebook.id });
|
AddNotebookSheet.present(undefined, notebook);
|
||||||
},
|
},
|
||||||
loading: "Loading notebook topics"
|
loading: "Loading notebook topics"
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderTopic = ({
|
const renderNotebook = ({
|
||||||
item,
|
item,
|
||||||
index
|
index
|
||||||
}: {
|
}: {
|
||||||
item: Topic | GroupHeader;
|
item: string | GroupHeader;
|
||||||
index: number;
|
index: number;
|
||||||
}) =>
|
}) =>
|
||||||
(item as GroupHeader).type === "header" ? null : (
|
(item as GroupHeader).type === "header" ? null : (
|
||||||
<TopicItem sheetRef={ref} item={item as Topic} index={index} />
|
<NotebookItem
|
||||||
|
items={notebooks}
|
||||||
|
id={item as string}
|
||||||
|
index={index}
|
||||||
|
totalNotes={totalNotes}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectionContext = {
|
const selectionContext = {
|
||||||
selection: selection,
|
selection: selection,
|
||||||
enabled,
|
enabled,
|
||||||
setEnabled,
|
setEnabled,
|
||||||
toggleSelection: (item: Topic) => {
|
toggleSelection: (item: Notebook) => {
|
||||||
setSelection((state) => {
|
setSelection((state) => {
|
||||||
const selection = [...state];
|
const selection = [...state];
|
||||||
const index = selection.findIndex(
|
const index = selection.findIndex(
|
||||||
@@ -204,45 +157,33 @@ export const TopicsSheet = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (canShow) {
|
if (canShow) {
|
||||||
setTimeout(() => {
|
setTimeout(async () => {
|
||||||
const id = isTopic ? currentScreen?.notebookId : currentScreen?.id;
|
const id = currentScreen?.id;
|
||||||
if (currentItem.current !== id) {
|
const nextRoot = await findRootNotebookId(id);
|
||||||
|
setRoot(nextRoot);
|
||||||
|
if (nextRoot !== currentItem.current) {
|
||||||
setSelection([]);
|
setSelection([]);
|
||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
}
|
}
|
||||||
currentItem.current = id;
|
currentItem.current = nextRoot;
|
||||||
const notebook = db.notebooks?.notebook(id as string)?.data;
|
const snapPoint = NotebookSheetConfig.get({
|
||||||
const snapPoint = isTopic
|
type: "notebook",
|
||||||
? 0
|
id: nextRoot as string
|
||||||
: TopicSheetConfig.get({
|
});
|
||||||
type: isTopic ? "topic" : "notebook",
|
|
||||||
id: currentScreen.id as string
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ref.current?.isOpen()) {
|
if (ref.current?.isOpen()) {
|
||||||
ref.current?.snapToIndex(snapPoint);
|
ref.current?.snapToIndex(snapPoint);
|
||||||
} else {
|
} else {
|
||||||
ref.current?.show(snapPoint);
|
ref.current?.show(snapPoint);
|
||||||
}
|
}
|
||||||
if (notebook) {
|
onRequestUpdate();
|
||||||
onRequestUpdate({
|
}, 0);
|
||||||
item: notebook
|
|
||||||
} as any);
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
} else {
|
} else {
|
||||||
setSelection([]);
|
setSelection([]);
|
||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
ref.current?.hide();
|
ref.current?.hide();
|
||||||
}
|
}
|
||||||
}, [
|
}, [canShow, currentScreen?.id, currentScreen.name, onRequestUpdate]);
|
||||||
canShow,
|
|
||||||
currentScreen?.id,
|
|
||||||
currentScreen.name,
|
|
||||||
currentScreen?.notebookId,
|
|
||||||
onRequestUpdate,
|
|
||||||
isTopic
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionSheet
|
<ActionSheet
|
||||||
@@ -262,9 +203,9 @@ export const TopicsSheet = () => {
|
|||||||
}}
|
}}
|
||||||
onSnapIndexChange={(index) => {
|
onSnapIndexChange={(index) => {
|
||||||
setCollapsed(index === 0);
|
setCollapsed(index === 0);
|
||||||
TopicSheetConfig.set(
|
NotebookSheetConfig.set(
|
||||||
{
|
{
|
||||||
type: isTopic ? "topic" : "notebook",
|
type: "notebook",
|
||||||
id: currentScreen.id as string
|
id: currentScreen.id as string
|
||||||
},
|
},
|
||||||
index
|
index
|
||||||
@@ -326,7 +267,7 @@ export const TopicsSheet = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paragraph size={SIZE.xs} color={colors.primary.icon}>
|
<Paragraph size={SIZE.xs} color={colors.primary.icon}>
|
||||||
TOPICS
|
NOTEBOOKS
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -367,7 +308,7 @@ export const TopicsSheet = () => {
|
|||||||
}
|
}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: <Sort screen="TopicSheet" type="topics" />
|
component: <Sort screen="TopicSheet" type="notebook" />
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
testID="group-topic-button"
|
testID="group-topic-button"
|
||||||
@@ -413,23 +354,24 @@ export const TopicsSheet = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<SelectionContext.Provider value={selectionContext}>
|
<SelectionContext.Provider value={selectionContext}>
|
||||||
<FlatList
|
<FlashList
|
||||||
data={topics}
|
data={notebooks?.ids}
|
||||||
style={{
|
style={{
|
||||||
width: "100%"
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
|
estimatedItemSize={50}
|
||||||
refreshControl={
|
refreshControl={
|
||||||
<RefreshControl
|
<RefreshControl
|
||||||
refreshing={false}
|
refreshing={false}
|
||||||
onRefresh={() => {
|
onRefresh={() => {
|
||||||
onRequestUpdate();
|
eSendEvent(eOnNotebookUpdated);
|
||||||
}}
|
}}
|
||||||
colors={[colors.primary.accent]}
|
colors={[colors.primary.accent]}
|
||||||
progressBackgroundColor={colors.primary.background}
|
progressBackgroundColor={colors.primary.background}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
keyExtractor={(item) => (item as Topic).id}
|
keyExtractor={(item) => item as string}
|
||||||
renderItem={renderTopic}
|
renderItem={renderNotebook}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -439,7 +381,7 @@ export const TopicsSheet = () => {
|
|||||||
height: 200
|
height: 200
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paragraph color={colors.primary.icon}>No topics</Paragraph>
|
<Paragraph color={colors.primary.icon}>No notebooks</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
ListFooterComponent={<View style={{ height: 50 }} />}
|
ListFooterComponent={<View style={{ height: 50 }} />}
|
||||||
@@ -451,108 +393,190 @@ export const TopicsSheet = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SelectionContext = createContext<{
|
const SelectionContext = createContext<{
|
||||||
selection: Topic[];
|
selection: Notebook[];
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
setEnabled: (value: boolean) => void;
|
setEnabled: (value: boolean) => void;
|
||||||
toggleSelection: (item: Topic) => void;
|
toggleSelection: (item: Notebook) => void;
|
||||||
}>({
|
}>({
|
||||||
selection: [],
|
selection: [],
|
||||||
enabled: false,
|
enabled: false,
|
||||||
setEnabled: (_value: boolean) => {},
|
setEnabled: (_value: boolean) => {},
|
||||||
toggleSelection: (_item: Topic) => {}
|
toggleSelection: (_item: Notebook) => {}
|
||||||
});
|
});
|
||||||
const useSelection = () => useContext(SelectionContext);
|
const useSelection = () => useContext(SelectionContext);
|
||||||
|
|
||||||
const TopicItem = ({
|
type NotebookParentProp = {
|
||||||
item,
|
parent?: NotebookParentProp;
|
||||||
|
item?: Notebook;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NotebookItem = ({
|
||||||
|
id,
|
||||||
|
totalNotes,
|
||||||
|
currentLevel = 0,
|
||||||
index,
|
index,
|
||||||
sheetRef
|
parent,
|
||||||
|
items
|
||||||
}: {
|
}: {
|
||||||
item: Topic;
|
id: string;
|
||||||
|
totalNotes: (id: string) => number;
|
||||||
|
currentLevel?: number;
|
||||||
index: number;
|
index: number;
|
||||||
sheetRef: RefObject<ActionSheetRef>;
|
parent?: NotebookParentProp;
|
||||||
|
items?: VirtualizedGrouping<Notebook>;
|
||||||
}) => {
|
}) => {
|
||||||
|
const {
|
||||||
|
nestedNotebookNotesCount,
|
||||||
|
nestedNotebooks,
|
||||||
|
notebook: item
|
||||||
|
} = useNotebook(id, items);
|
||||||
const screen = useNavigationStore((state) => state.currentScreen);
|
const screen = useNavigationStore((state) => state.currentScreen);
|
||||||
const { colors } = useThemeColors("sheet");
|
const { colors } = useThemeColors("sheet");
|
||||||
const selection = useSelection();
|
const selection = useSelection();
|
||||||
const isSelected =
|
const isSelected =
|
||||||
selection.selection.findIndex((selected) => selected.id === item.id) > -1;
|
selection.selection.findIndex((selected) => selected.id === item?.id) > -1;
|
||||||
const isFocused = screen.id === item.id;
|
const isFocused = screen.id === id;
|
||||||
const notesCount = getTotalNotes(item);
|
|
||||||
const { fontScale } = useWindowDimensions();
|
const { fontScale } = useWindowDimensions();
|
||||||
|
const expanded = useNotebookExpandedStore((state) => state.expanded[id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PressableButton
|
<View
|
||||||
type={isSelected || isFocused ? "selected" : "transparent"}
|
style={{
|
||||||
onLongPress={() => {
|
paddingLeft: currentLevel > 0 && currentLevel < 6 ? 15 : undefined,
|
||||||
if (selection.enabled) return;
|
width: "100%"
|
||||||
selection.setEnabled(true);
|
|
||||||
selection.toggleSelection(item);
|
|
||||||
}}
|
|
||||||
testID={`topic-sheet-item-${index}`}
|
|
||||||
onPress={() => {
|
|
||||||
if (selection.enabled) {
|
|
||||||
selection.toggleSelection(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TopicNotes.navigate(item, true);
|
|
||||||
}}
|
|
||||||
customStyle={{
|
|
||||||
justifyContent: "space-between",
|
|
||||||
width: "100%",
|
|
||||||
alignItems: "center",
|
|
||||||
flexDirection: "row",
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
borderRadius: 0
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<PressableButton
|
||||||
style={{
|
type={isSelected || isFocused ? "selected" : "transparent"}
|
||||||
|
onLongPress={() => {
|
||||||
|
if (selection.enabled || !item) return;
|
||||||
|
selection.setEnabled(true);
|
||||||
|
selection.toggleSelection(item);
|
||||||
|
}}
|
||||||
|
testID={`topic-sheet-item-${currentLevel}-${index}`}
|
||||||
|
onPress={() => {
|
||||||
|
if (!item) return;
|
||||||
|
if (selection.enabled) {
|
||||||
|
selection.toggleSelection(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NotebookScreen.navigate(item, true);
|
||||||
|
}}
|
||||||
|
customStyle={{
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
alignItems: "center",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center"
|
paddingLeft: 0,
|
||||||
|
paddingRight: 12,
|
||||||
|
borderRadius: 0
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{selection.enabled ? (
|
<View
|
||||||
<IconButton
|
style={{
|
||||||
size={SIZE.lg}
|
flexDirection: "row",
|
||||||
color={isSelected ? colors.selected.icon : colors.primary.icon}
|
alignItems: "center"
|
||||||
top={0}
|
}}
|
||||||
left={0}
|
>
|
||||||
bottom={0}
|
{selection.enabled ? (
|
||||||
right={0}
|
<IconButton
|
||||||
name={
|
size={SIZE.lg}
|
||||||
isSelected
|
color={isSelected ? colors.selected.icon : colors.primary.icon}
|
||||||
? "check-circle-outline"
|
top={0}
|
||||||
: "checkbox-blank-circle-outline"
|
left={0}
|
||||||
}
|
bottom={0}
|
||||||
/>
|
right={0}
|
||||||
) : null}
|
customStyle={{
|
||||||
<Paragraph size={SIZE.sm}>
|
width: 40,
|
||||||
{item.title}{" "}
|
height: 40
|
||||||
{notesCount ? (
|
}}
|
||||||
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
name={
|
||||||
{notesCount}
|
isSelected
|
||||||
</Paragraph>
|
? "check-circle-outline"
|
||||||
|
: "checkbox-blank-circle-outline"
|
||||||
|
}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</Paragraph>
|
|
||||||
</View>
|
{nestedNotebooks?.ids.length ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
name="dots-horizontal"
|
size={SIZE.lg}
|
||||||
customStyle={{
|
color={isSelected ? colors.selected.icon : colors.primary.icon}
|
||||||
width: 40 * fontScale,
|
onPress={() => {
|
||||||
height: 40 * fontScale
|
useNotebookExpandedStore.getState().setExpanded(id);
|
||||||
}}
|
}}
|
||||||
testID={notesnook.ids.notebook.menu}
|
top={0}
|
||||||
onPress={() => {
|
left={0}
|
||||||
Properties.present(item);
|
bottom={0}
|
||||||
}}
|
right={0}
|
||||||
left={0}
|
customStyle={{
|
||||||
right={0}
|
width: 40,
|
||||||
bottom={0}
|
height: 40
|
||||||
top={0}
|
}}
|
||||||
color={colors.primary.icon}
|
name={expanded ? "chevron-down" : "chevron-right"}
|
||||||
size={SIZE.xl}
|
/>
|
||||||
/>
|
) : (
|
||||||
</PressableButton>
|
<>
|
||||||
|
{selection?.enabled ? null : (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
width: 40,
|
||||||
|
height: 40
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Paragraph
|
||||||
|
color={
|
||||||
|
isFocused ? colors.selected.paragraph : colors.secondary.paragraph
|
||||||
|
}
|
||||||
|
size={SIZE.sm}
|
||||||
|
>
|
||||||
|
{item?.title}{" "}
|
||||||
|
{totalNotes(id) ? (
|
||||||
|
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||||
|
{totalNotes(id)}
|
||||||
|
</Paragraph>
|
||||||
|
) : null}
|
||||||
|
</Paragraph>
|
||||||
|
</View>
|
||||||
|
<IconButton
|
||||||
|
name="dots-horizontal"
|
||||||
|
customStyle={{
|
||||||
|
width: 40 * fontScale,
|
||||||
|
height: 40 * fontScale
|
||||||
|
}}
|
||||||
|
testID={notesnook.ids.notebook.menu}
|
||||||
|
onPress={() => {
|
||||||
|
Properties.present(item);
|
||||||
|
}}
|
||||||
|
left={0}
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
top={0}
|
||||||
|
color={colors.primary.icon}
|
||||||
|
size={SIZE.xl}
|
||||||
|
/>
|
||||||
|
</PressableButton>
|
||||||
|
|
||||||
|
{!expanded
|
||||||
|
? null
|
||||||
|
: nestedNotebooks?.ids.map((id, index) => (
|
||||||
|
<NotebookItem
|
||||||
|
key={id as string}
|
||||||
|
id={id as string}
|
||||||
|
index={index}
|
||||||
|
totalNotes={nestedNotebookNotesCount}
|
||||||
|
currentLevel={currentLevel + 1}
|
||||||
|
items={nestedNotebooks}
|
||||||
|
parent={{
|
||||||
|
parent: parent,
|
||||||
|
item: item
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -36,22 +36,27 @@ 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 { requestInAppReview } from "../../../services/app-review";
|
import { requestInAppReview } from "../../../services/app-review";
|
||||||
|
import { Note } from "@notesnook/core";
|
||||||
|
|
||||||
const PublishNoteSheet = ({ note: item }) => {
|
const PublishNoteSheet = ({
|
||||||
|
note: item
|
||||||
|
}: {
|
||||||
|
note: Note;
|
||||||
|
close?: (ctx?: string) => void;
|
||||||
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const actionSheetRef = useRef();
|
|
||||||
|
|
||||||
const attachmentDownloads = useAttachmentStore((state) => state.downloading);
|
const attachmentDownloads = useAttachmentStore((state) => state.downloading);
|
||||||
const downloading = attachmentDownloads[`monograph-${item.id}`];
|
const downloading = attachmentDownloads?.[`monograph-${item.id}`];
|
||||||
const [selfDestruct, setSelfDestruct] = useState(false);
|
const [selfDestruct, setSelfDestruct] = useState(false);
|
||||||
const [isLocked, setIsLocked] = useState(false);
|
const [isLocked, setIsLocked] = useState(false);
|
||||||
const [note, setNote] = useState(item);
|
const [note, setNote] = useState<Note | undefined>(item);
|
||||||
const [publishing, setPublishing] = useState(false);
|
const [publishing, setPublishing] = useState(false);
|
||||||
const publishUrl =
|
const publishUrl =
|
||||||
note && `https://monogr.ph/${db.monographs.monograph(note?.id)}`;
|
note && `https://monogr.ph/${db.monographs.monograph(note?.id)}`;
|
||||||
const isPublished = note && db.monographs.isPublished(note?.id);
|
const isPublished = note && db.monographs.isPublished(note?.id);
|
||||||
const pwdInput = useRef();
|
const pwdInput = useRef(null);
|
||||||
const passwordValue = useRef();
|
const passwordValue = useRef<string>();
|
||||||
|
|
||||||
const publishNote = async () => {
|
const publishNote = async () => {
|
||||||
if (publishing) return;
|
if (publishing) return;
|
||||||
@@ -59,12 +64,12 @@ const PublishNoteSheet = ({ note: item }) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (note?.id) {
|
if (note?.id) {
|
||||||
if (isLocked && !passwordValue) return;
|
if (isLocked && !passwordValue.current) return;
|
||||||
await db.monographs.publish(note.id, {
|
await db.monographs.publish(note.id, {
|
||||||
selfDestruct: selfDestruct,
|
selfDestruct: selfDestruct,
|
||||||
password: isLocked && passwordValue.current
|
password: isLocked ? passwordValue.current : undefined
|
||||||
});
|
});
|
||||||
setNote(db.notes.note(note.id)?.data);
|
setNote(await db.notes.note(note.id));
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
setPublishLoading(false);
|
setPublishLoading(false);
|
||||||
}
|
}
|
||||||
@@ -72,7 +77,7 @@ const PublishNoteSheet = ({ note: item }) => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: "Could not publish note",
|
heading: "Could not publish note",
|
||||||
message: e.message,
|
message: (e as Error).message,
|
||||||
type: "error",
|
type: "error",
|
||||||
context: "local"
|
context: "local"
|
||||||
});
|
});
|
||||||
@@ -81,7 +86,7 @@ const PublishNoteSheet = ({ note: item }) => {
|
|||||||
setPublishLoading(false);
|
setPublishLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setPublishLoading = (value) => {
|
const setPublishLoading = (value: boolean) => {
|
||||||
setPublishing(value);
|
setPublishing(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,19 +96,18 @@ const PublishNoteSheet = ({ note: item }) => {
|
|||||||
try {
|
try {
|
||||||
if (note?.id) {
|
if (note?.id) {
|
||||||
await db.monographs.unpublish(note.id);
|
await db.monographs.unpublish(note.id);
|
||||||
setNote(db.notes.note(note.id)?.data);
|
setNote(await db.notes.note(note.id));
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
setPublishLoading(false);
|
setPublishLoading(false);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: "Could not unpublish note",
|
heading: "Could not unpublish note",
|
||||||
message: e.message,
|
message: (e as Error).message,
|
||||||
type: "error",
|
type: "error",
|
||||||
context: "local"
|
context: "local"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
actionSheetRef.current?.hide();
|
|
||||||
setPublishLoading(false);
|
setPublishLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -171,10 +175,7 @@ const PublishNoteSheet = ({ note: item }) => {
|
|||||||
<Paragraph
|
<Paragraph
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
try {
|
try {
|
||||||
await openLinkInBrowser(
|
await openLinkInBrowser(publishUrl);
|
||||||
publishUrl,
|
|
||||||
colors.primary.accent
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
@@ -192,7 +193,7 @@ const PublishNoteSheet = ({ note: item }) => {
|
|||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
Clipboard.setString(publishUrl);
|
Clipboard.setString(publishUrl as string);
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: "Note publish url copied",
|
heading: "Note publish url copied",
|
||||||
type: "success",
|
type: "success",
|
||||||
@@ -356,10 +357,7 @@ const PublishNoteSheet = ({ note: item }) => {
|
|||||||
}}
|
}}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
try {
|
try {
|
||||||
await openLinkInBrowser(
|
await openLinkInBrowser("https://docs.notesnook.com/monographs/");
|
||||||
"https://docs.notesnook.com/monographs/",
|
|
||||||
colors.primary.accent
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
@@ -371,15 +369,10 @@ const PublishNoteSheet = ({ note: item }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
PublishNoteSheet.present = (note) => {
|
PublishNoteSheet.present = (note: Note) => {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: (ref, close, update) => (
|
component: (ref, close, update) => (
|
||||||
<PublishNoteSheet
|
<PublishNoteSheet close={close} note={note} />
|
||||||
actionSheetRef={ref}
|
|
||||||
close={close}
|
|
||||||
update={update}
|
|
||||||
note={note}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -17,7 +17,7 @@ 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 { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { RefObject } from "react";
|
import React, { RefObject, useEffect, useState } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||||
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
||||||
@@ -35,7 +35,8 @@ import SheetProvider from "../../sheet-provider";
|
|||||||
import { Button } from "../../ui/button";
|
import { Button } from "../../ui/button";
|
||||||
import { PressableButtonProps } from "../../ui/pressable";
|
import { PressableButtonProps } from "../../ui/pressable";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
import { ItemReference, ItemType } from "@notesnook/core/dist/types";
|
import { Item, ItemReference, ItemType } from "@notesnook/core/dist/types";
|
||||||
|
import { VirtualizedGrouping } from "@notesnook/core";
|
||||||
|
|
||||||
type RelationsListProps = {
|
type RelationsListProps = {
|
||||||
actionSheetRef: RefObject<ActionSheetRef>;
|
actionSheetRef: RefObject<ActionSheetRef>;
|
||||||
@@ -73,13 +74,24 @@ export const RelationsList = ({
|
|||||||
const updater = useRelationStore((state) => state.updater);
|
const updater = useRelationStore((state) => state.updater);
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
|
|
||||||
const items =
|
const [items, setItems] = useState<VirtualizedGrouping<Item>>();
|
||||||
|
|
||||||
|
const hasNoRelations = !items || items?.ids?.length === 0;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
db.relations?.[relationType]?.(
|
db.relations?.[relationType]?.(
|
||||||
{ id: item?.id, type: item?.type } as ItemReference,
|
{ id: item?.id, type: item?.type } as ItemReference,
|
||||||
referenceType as ItemType
|
referenceType as any
|
||||||
) || [];
|
)
|
||||||
|
.selector.sorted({
|
||||||
const hasNoRelations = !items || items.length === 0;
|
sortBy: "dateEdited",
|
||||||
|
sortDirection: "desc",
|
||||||
|
groupBy: "default"
|
||||||
|
})
|
||||||
|
.then((grouped) => {
|
||||||
|
setItems(grouped);
|
||||||
|
});
|
||||||
|
}, [relationType, referenceType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
@@ -119,15 +131,11 @@ export const RelationsList = ({
|
|||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<List
|
<List
|
||||||
listData={items}
|
data={items}
|
||||||
ScrollComponent={FlashList}
|
CustomListComponent={FlashList}
|
||||||
loading={false}
|
loading={false}
|
||||||
type={referenceType}
|
dataType={referenceType as any}
|
||||||
headerProps={null}
|
isRenderedInActionSheet={true}
|
||||||
isSheet={true}
|
|
||||||
onMomentumScrollEnd={() => {
|
|
||||||
actionSheetRef?.current?.handleChildScrollEnd();
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -17,23 +17,29 @@ 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 dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import React, { RefObject } from "react";
|
import React, { RefObject, useEffect, useState } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
|
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
|
||||||
|
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { db } from "../../../common/database";
|
import { db } from "../../../common/database";
|
||||||
import {
|
import {
|
||||||
presentSheet,
|
presentSheet,
|
||||||
PresentSheetOptions
|
PresentSheetOptions
|
||||||
} from "../../../services/event-manager";
|
} from "../../../services/event-manager";
|
||||||
import Notifications, { Reminder } from "../../../services/notifications";
|
import Notifications from "../../../services/notifications";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
import { ItemReference } from "../../../utils/types";
|
|
||||||
import List from "../../list";
|
import List from "../../list";
|
||||||
import { Button } from "../../ui/button";
|
import { Button } from "../../ui/button";
|
||||||
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 {
|
||||||
|
Reminder,
|
||||||
|
ItemReference,
|
||||||
|
VirtualizedGrouping,
|
||||||
|
Note
|
||||||
|
} from "@notesnook/core";
|
||||||
|
|
||||||
type ReminderSheetProps = {
|
type ReminderSheetProps = {
|
||||||
actionSheetRef: RefObject<ActionSheetRef>;
|
actionSheetRef: RefObject<ActionSheetRef>;
|
||||||
@@ -48,7 +54,16 @@ export default function ReminderNotify({
|
|||||||
reminder
|
reminder
|
||||||
}: ReminderSheetProps) {
|
}: ReminderSheetProps) {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const references = db.relations?.to(reminder as ItemReference, "note") || [];
|
const [references, setReferences] = useState<VirtualizedGrouping<Note>>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
db.relations
|
||||||
|
?.to(reminder as ItemReference, "note")
|
||||||
|
.selector.grouped(db.settings.getGroupOptions("notes"))
|
||||||
|
.then((items) => {
|
||||||
|
setReferences(items);
|
||||||
|
});
|
||||||
|
}, [reminder]);
|
||||||
|
|
||||||
const QuickActions = [
|
const QuickActions = [
|
||||||
{
|
{
|
||||||
@@ -76,7 +91,7 @@ export default function ReminderNotify({
|
|||||||
snoozeUntil: snoozeTime
|
snoozeUntil: snoozeTime
|
||||||
});
|
});
|
||||||
await Notifications.scheduleNotification(
|
await Notifications.scheduleNotification(
|
||||||
db.reminders?.reminder(reminder?.id)
|
await db.reminders?.reminder(reminder?.id as string)
|
||||||
);
|
);
|
||||||
close?.();
|
close?.();
|
||||||
};
|
};
|
||||||
@@ -135,12 +150,14 @@ export default function ReminderNotify({
|
|||||||
})}
|
})}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
{references.length > 0 ? (
|
{references?.ids && references?.ids?.length > 0 ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height:
|
height:
|
||||||
160 * references?.length < 500 ? 160 * references?.length : 500,
|
160 * references?.ids?.length < 500
|
||||||
|
? 160 * references?.ids?.length
|
||||||
|
: 500,
|
||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
borderTopColor: colors.secondary.background,
|
borderTopColor: colors.secondary.background,
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
@@ -157,14 +174,11 @@ export default function ReminderNotify({
|
|||||||
REFERENCED IN
|
REFERENCED IN
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<List
|
<List
|
||||||
listData={references}
|
data={references}
|
||||||
|
CustomListComponent={FlashList}
|
||||||
loading={false}
|
loading={false}
|
||||||
type="notes"
|
dataType="note"
|
||||||
headerProps={null}
|
isRenderedInActionSheet={true}
|
||||||
isSheet={true}
|
|
||||||
onMomentumScrollEnd={() =>
|
|
||||||
actionSheetRef.current?.handleChildScrollEnd()
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import DatePicker from "react-native-date-picker";
|
|||||||
import { db } from "../../../common/database";
|
import { db } from "../../../common/database";
|
||||||
import { DDS } from "../../../services/device-detection";
|
import { DDS } from "../../../services/device-detection";
|
||||||
import Navigation from "../../../services/navigation";
|
import Navigation from "../../../services/navigation";
|
||||||
import Notifications, { Reminder } from "../../../services/notifications";
|
import Notifications from "../../../services/notifications";
|
||||||
import PremiumService from "../../../services/premium";
|
import PremiumService from "../../../services/premium";
|
||||||
import SettingsService from "../../../services/settings";
|
import SettingsService from "../../../services/settings";
|
||||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||||
@@ -43,7 +43,7 @@ import { Dialog } from "../../dialog";
|
|||||||
import { ReminderTime } from "../../ui/reminder-time";
|
import { ReminderTime } from "../../ui/reminder-time";
|
||||||
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 { ItemReference } from "@notesnook/core/dist/types";
|
import { ItemReference, Note, Reminder } from "@notesnook/core";
|
||||||
|
|
||||||
type ReminderSheetProps = {
|
type ReminderSheetProps = {
|
||||||
actionSheetRef: RefObject<ActionSheetRef>;
|
actionSheetRef: RefObject<ActionSheetRef>;
|
||||||
@@ -113,7 +113,7 @@ export default function ReminderSheet({
|
|||||||
>(reminder?.priority || SettingsService.get().reminderNotificationMode);
|
>(reminder?.priority || SettingsService.get().reminderNotificationMode);
|
||||||
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
|
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
|
||||||
const [repeatFrequency, setRepeatFrequency] = useState(1);
|
const [repeatFrequency, setRepeatFrequency] = useState(1);
|
||||||
const referencedItem = reference ? db.notes?.note(reference.id)?.data : null;
|
const referencedItem = reference ? (reference as Note) : null;
|
||||||
|
|
||||||
const title = useRef<string | undefined>(
|
const title = useRef<string | undefined>(
|
||||||
reminder?.title || referencedItem?.title
|
reminder?.title || referencedItem?.title
|
||||||
@@ -195,7 +195,7 @@ export default function ReminderSheet({
|
|||||||
disabled: false
|
disabled: false
|
||||||
});
|
});
|
||||||
if (!reminderId) return;
|
if (!reminderId) return;
|
||||||
const _reminder = db.reminders?.reminder(reminderId);
|
const _reminder = await db.reminders?.reminder(reminderId);
|
||||||
|
|
||||||
if (!_reminder) {
|
if (!_reminder) {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
|||||||
await db.backup.import(backup, password, key);
|
await db.backup.import(backup, password, key);
|
||||||
await db.initCollections();
|
await db.initCollections();
|
||||||
initialize();
|
initialize();
|
||||||
ToastEvent.show({
|
ToastManager.show({
|
||||||
heading: "Backup restored successfully.",
|
heading: "Backup restored successfully.",
|
||||||
type: "success",
|
type: "success",
|
||||||
context: "global"
|
context: "global"
|
||||||
@@ -253,7 +253,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const backupError = (e) => {
|
const backupError = (e) => {
|
||||||
ToastEvent.show({
|
ToastManager.show({
|
||||||
heading: "Restore failed",
|
heading: "Restore failed",
|
||||||
message:
|
message:
|
||||||
e.message ||
|
e.message ||
|
||||||
@@ -317,7 +317,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
|||||||
initialize();
|
initialize();
|
||||||
setRestoring(false);
|
setRestoring(false);
|
||||||
close();
|
close();
|
||||||
ToastEvent.show({
|
ToastManager.show({
|
||||||
heading: "Backup restored successfully.",
|
heading: "Backup restored successfully.",
|
||||||
type: "success",
|
type: "success",
|
||||||
context: "global"
|
context: "global"
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const Sort = ({ type, screen }) => {
|
|||||||
const isTopicSheet = screen === "TopicSheet";
|
const isTopicSheet = screen === "TopicSheet";
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const [groupOptions, setGroupOptions] = useState(
|
const [groupOptions, setGroupOptions] = useState(
|
||||||
db.settings.getGroupOptions(type)
|
db.settings.getGroupOptions(screen === "Notes" ? "home" : type + "s")
|
||||||
);
|
);
|
||||||
const updateGroupOptions = async (_groupOptions) => {
|
const updateGroupOptions = async (_groupOptions) => {
|
||||||
await db.settings.setGroupOptions(type, _groupOptions);
|
await db.settings.setGroupOptions(type, _groupOptions);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { presentDialog } from "../dialog/functions";
|
|||||||
import { PressableButton } from "../ui/pressable";
|
import { PressableButton } from "../ui/pressable";
|
||||||
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 { Color } from "@notesnook/core";
|
||||||
|
|
||||||
export const ColorSection = React.memo(
|
export const ColorSection = React.memo(
|
||||||
function ColorSection() {
|
function ColorSection() {
|
||||||
@@ -44,29 +45,33 @@ export const ColorSection = React.memo(
|
|||||||
}
|
}
|
||||||
}, [loading, setColorNotes]);
|
}, [loading, setColorNotes]);
|
||||||
|
|
||||||
return colorNotes.map((item, index) => {
|
return colorNotes.map((item) => {
|
||||||
return <ColorItem key={item.id} item={item} index={index} />;
|
return <ColorItem key={item.id} item={item} />;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
() => true
|
() => true
|
||||||
);
|
);
|
||||||
|
|
||||||
const ColorItem = React.memo(
|
const ColorItem = React.memo(
|
||||||
function ColorItem({ item }) {
|
function ColorItem({ item }: { item: Color }) {
|
||||||
const { colors, isDark } = useThemeColors();
|
const { colors, isDark } = useThemeColors();
|
||||||
const setColorNotes = useMenuStore((state) => state.setColorNotes);
|
const setColorNotes = useMenuStore((state) => state.setColorNotes);
|
||||||
const [headerTextState, setHeaderTextState] = useState(null);
|
const [headerTextState, setHeaderTextState] = useState<{
|
||||||
|
id: string | undefined;
|
||||||
|
}>({
|
||||||
|
id: undefined
|
||||||
|
});
|
||||||
const isFocused = headerTextState?.id === item.id;
|
const isFocused = headerTextState?.id === item.id;
|
||||||
|
|
||||||
const onHeaderStateChange = useCallback(
|
const onHeaderStateChange = useCallback(
|
||||||
(state) => {
|
(state: any) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
let id = state.currentScreen?.id;
|
let id = state.currentScreen?.id;
|
||||||
if (id === item.id) {
|
if (id === item.id) {
|
||||||
setHeaderTextState({ id: state.currentScreen.id });
|
setHeaderTextState({ id: state.currentScreen.id });
|
||||||
} else {
|
} else {
|
||||||
if (headerTextState !== null) {
|
if (headerTextState !== null) {
|
||||||
setHeaderTextState(null);
|
setHeaderTextState({ id: undefined });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
@@ -75,13 +80,13 @@ const ColorItem = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let unsub = useNavigationStore.subscribe(onHeaderStateChange);
|
const remove = useNavigationStore.subscribe(onHeaderStateChange);
|
||||||
return () => {
|
return () => {
|
||||||
unsub();
|
remove();
|
||||||
};
|
};
|
||||||
}, [headerTextState, onHeaderStateChange]);
|
}, [headerTextState, onHeaderStateChange]);
|
||||||
|
|
||||||
const onPress = (item) => {
|
const onPress = (item: Color) => {
|
||||||
ColoredNotes.navigate(item, false);
|
ColoredNotes.navigate(item, false);
|
||||||
|
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
@@ -39,6 +39,7 @@ import SheetWrapper from "../ui/sheet";
|
|||||||
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 { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
import { Notebook, Tag } from "@notesnook/core";
|
||||||
|
|
||||||
export const TagsSection = React.memo(
|
export const TagsSection = React.memo(
|
||||||
function TagsSection() {
|
function TagsSection() {
|
||||||
@@ -52,20 +53,18 @@ export const TagsSection = React.memo(
|
|||||||
}
|
}
|
||||||
}, [loading, setMenuPins]);
|
}, [loading, setMenuPins]);
|
||||||
|
|
||||||
const onPress = (item) => {
|
const onPress = (item: Notebook | Tag) => {
|
||||||
if (item.type === "notebook") {
|
if (item.type === "notebook") {
|
||||||
NotebookScreen.navigate(item);
|
NotebookScreen.navigate(item);
|
||||||
} else if (item.type === "tag") {
|
} else if (item.type === "tag") {
|
||||||
TaggedNotes.navigate(item);
|
TaggedNotes.navigate(item);
|
||||||
} else {
|
|
||||||
TopicNotes.navigate(item);
|
|
||||||
}
|
}
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
Navigation.closeDrawer();
|
Navigation.closeDrawer();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const renderItem = ({ item, index }) => {
|
const renderItem = ({ item }: { item: Notebook | Tag; index: number }) => {
|
||||||
return <PinItem item={item} index={index} onPress={onPress} />;
|
return <PinItem item={item} onPress={onPress} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -99,12 +98,22 @@ export const TagsSection = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const PinItem = React.memo(
|
export const PinItem = React.memo(
|
||||||
function PinItem({ item, onPress, placeholder }) {
|
function PinItem({
|
||||||
|
item,
|
||||||
|
onPress,
|
||||||
|
isPlaceholder
|
||||||
|
}: {
|
||||||
|
item: Notebook | Tag;
|
||||||
|
onPress: (item: Notebook | Tag) => void;
|
||||||
|
isPlaceholder?: boolean;
|
||||||
|
}) {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const setMenuPins = useMenuStore((state) => state.setMenuPins);
|
const setMenuPins = useMenuStore((state) => state.setMenuPins);
|
||||||
|
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [headerTextState, setHeaderTextState] = useState(null);
|
const [headerTextState, setHeaderTextState] = useState<{
|
||||||
|
id?: string;
|
||||||
|
}>({});
|
||||||
const primaryColors =
|
const primaryColors =
|
||||||
headerTextState?.id === item.id ? colors.selected : colors.primary;
|
headerTextState?.id === item.id ? colors.selected : colors.primary;
|
||||||
|
|
||||||
@@ -116,16 +125,16 @@ export const PinItem = React.memo(
|
|||||||
const fwdRef = useRef();
|
const fwdRef = useRef();
|
||||||
|
|
||||||
const onHeaderStateChange = useCallback(
|
const onHeaderStateChange = useCallback(
|
||||||
(state) => {
|
(state: any) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
let id = state.currentScreen?.id;
|
const id = state.currentScreen?.id;
|
||||||
if (id === item.id) {
|
if (id === item.id) {
|
||||||
setHeaderTextState({
|
setHeaderTextState({
|
||||||
id: state.currentScreen.id
|
id
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (headerTextState !== null) {
|
if (headerTextState !== null) {
|
||||||
setHeaderTextState(null);
|
setHeaderTextState({});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
@@ -134,9 +143,9 @@ export const PinItem = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let unsub = useNavigationStore.subscribe(onHeaderStateChange);
|
const remove = useNavigationStore.subscribe(onHeaderStateChange);
|
||||||
return () => {
|
return () => {
|
||||||
unsub();
|
remove();
|
||||||
};
|
};
|
||||||
}, [headerTextState, onHeaderStateChange]);
|
}, [headerTextState, onHeaderStateChange]);
|
||||||
|
|
||||||
@@ -155,7 +164,6 @@ export const PinItem = React.memo(
|
|||||||
}}
|
}}
|
||||||
gestureEnabled={false}
|
gestureEnabled={false}
|
||||||
fwdRef={fwdRef}
|
fwdRef={fwdRef}
|
||||||
visible={true}
|
|
||||||
>
|
>
|
||||||
<Seperator />
|
<Seperator />
|
||||||
<Button
|
<Button
|
||||||
@@ -177,7 +185,7 @@ export const PinItem = React.memo(
|
|||||||
<PressableButton
|
<PressableButton
|
||||||
type={isFocused ? "selected" : "gray"}
|
type={isFocused ? "selected" : "gray"}
|
||||||
onLongPress={() => {
|
onLongPress={() => {
|
||||||
if (placeholder) return;
|
if (isPlaceholder) return;
|
||||||
Properties.present(item);
|
Properties.present(item);
|
||||||
}}
|
}}
|
||||||
onPress={() => onPress(item)}
|
onPress={() => onPress(item)}
|
||||||
@@ -28,6 +28,7 @@ import { SIZE } from "../../utils/size";
|
|||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import Seperator from "../ui/seperator";
|
import Seperator from "../ui/seperator";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
|
||||||
export const Tip = ({
|
export const Tip = ({
|
||||||
tip,
|
tip,
|
||||||
style,
|
style,
|
||||||
@@ -39,7 +40,7 @@ export const Tip = ({
|
|||||||
tip: TTip;
|
tip: TTip;
|
||||||
style?: ViewStyle;
|
style?: ViewStyle;
|
||||||
textStyle?: TextStyle;
|
textStyle?: TextStyle;
|
||||||
neverShowAgain: boolean;
|
neverShowAgain?: boolean;
|
||||||
noImage?: boolean;
|
noImage?: boolean;
|
||||||
color?: string;
|
color?: string;
|
||||||
}) => {
|
}) => {
|
||||||
@@ -77,9 +78,10 @@ export const Tip = ({
|
|||||||
alignSelf: "flex-start",
|
alignSelf: "flex-start",
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor:
|
borderColor: color ? color : colors.primary.accent
|
||||||
colors.static[color as never] ||
|
}}
|
||||||
(colors.primary[color as never] as string)
|
buttonType={{
|
||||||
|
text: color
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ import { useAppState } from "../../../hooks/use-app-state";
|
|||||||
import SettingsService from "../../../services/settings";
|
import SettingsService from "../../../services/settings";
|
||||||
import { useUserStore } from "../../../stores/use-user-store";
|
import { useUserStore } from "../../../stores/use-user-store";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {any} param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
const SheetWrapper = ({
|
const SheetWrapper = ({
|
||||||
children,
|
children,
|
||||||
fwdRef,
|
fwdRef,
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import {
|
|||||||
Notebook,
|
Notebook,
|
||||||
Reminder,
|
Reminder,
|
||||||
Tag,
|
Tag,
|
||||||
Topic,
|
|
||||||
TrashItem
|
TrashItem
|
||||||
} from "@notesnook/core/dist/types";
|
} from "@notesnook/core/dist/types";
|
||||||
import { DisplayedNotification } from "@notifee/react-native";
|
import { DisplayedNotification } from "@notifee/react-native";
|
||||||
@@ -39,7 +38,6 @@ import NoteHistory from "../components/note-history";
|
|||||||
import { AddNotebookSheet } from "../components/sheets/add-notebook";
|
import { AddNotebookSheet } from "../components/sheets/add-notebook";
|
||||||
import MoveNoteSheet from "../components/sheets/add-to";
|
import MoveNoteSheet from "../components/sheets/add-to";
|
||||||
import ExportNotesSheet from "../components/sheets/export-notes";
|
import ExportNotesSheet from "../components/sheets/export-notes";
|
||||||
import { MoveNotes } from "../components/sheets/move-notes/movenote";
|
|
||||||
import PublishNoteSheet from "../components/sheets/publish-note";
|
import PublishNoteSheet from "../components/sheets/publish-note";
|
||||||
import { RelationsList } from "../components/sheets/relations-list/index";
|
import { RelationsList } from "../components/sheets/relations-list/index";
|
||||||
import ReminderSheet from "../components/sheets/reminder";
|
import ReminderSheet from "../components/sheets/reminder";
|
||||||
@@ -60,11 +58,7 @@ import { useSelectionStore } from "../stores/use-selection-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";
|
||||||
import Errors from "../utils/errors";
|
import Errors from "../utils/errors";
|
||||||
import {
|
import { eOpenLoginDialog } from "../utils/events";
|
||||||
eOnTopicSheetUpdate,
|
|
||||||
eOpenAddTopicDialog,
|
|
||||||
eOpenLoginDialog
|
|
||||||
} from "../utils/events";
|
|
||||||
import { deleteItems } from "../utils/functions";
|
import { deleteItems } from "../utils/functions";
|
||||||
import { convertNoteToText } from "../utils/note-to-text";
|
import { convertNoteToText } from "../utils/note-to-text";
|
||||||
import { sleep } from "../utils/time";
|
import { sleep } from "../utils/time";
|
||||||
@@ -73,7 +67,7 @@ export const useActions = ({
|
|||||||
close,
|
close,
|
||||||
item
|
item
|
||||||
}: {
|
}: {
|
||||||
item: Note | Notebook | Topic | Reminder | Tag | Color | TrashItem;
|
item: Note | Notebook | Reminder | Tag | Color | TrashItem;
|
||||||
close: () => void;
|
close: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const clearSelection = useSelectionStore((state) => state.clearSelection);
|
const clearSelection = useSelectionStore((state) => state.clearSelection);
|
||||||
@@ -89,6 +83,7 @@ export const useActions = ({
|
|||||||
const [defaultNotebook, setDefaultNotebook] = useState(
|
const [defaultNotebook, setDefaultNotebook] = useState(
|
||||||
db.settings.getDefaultNotebook()
|
db.settings.getDefaultNotebook()
|
||||||
);
|
);
|
||||||
|
const [noteInCurrentNotebook, setNoteInCurrentNotebook] = useState(false);
|
||||||
|
|
||||||
const isPublished =
|
const isPublished =
|
||||||
item.type === "note" && db.monographs.isPublished(item.id);
|
item.type === "note" && db.monographs.isPublished(item.id);
|
||||||
@@ -156,63 +151,21 @@ export const useActions = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const checkItemSynced = () => {
|
const checkItemSynced = () => {
|
||||||
if (!user) return true;
|
|
||||||
if (item.type !== "note" || (item as unknown as TrashItem)) return true;
|
|
||||||
|
|
||||||
let isTrash = (item as unknown as TrashItem).type === "trash";
|
|
||||||
|
|
||||||
if (!isTrash && !db.notes.note(item.id)?.synced()) {
|
|
||||||
ToastManager.show({
|
|
||||||
context: "local",
|
|
||||||
heading: "Note not synced",
|
|
||||||
message: "Please run sync before making changes",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTrash && !db.trash.synced(item.id)) {
|
|
||||||
ToastManager.show({
|
|
||||||
context: "local",
|
|
||||||
heading: "Note not synced",
|
|
||||||
message: "Please run sync before making changes",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function createMenuShortcut() {
|
async function createMenuShortcut() {
|
||||||
if (
|
if (item.type !== "notebook" && item.type !== "tag") return;
|
||||||
item.type !== "notebook" &&
|
|
||||||
item.type !== "topic" &&
|
|
||||||
item.type !== "tag"
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
close();
|
close();
|
||||||
try {
|
try {
|
||||||
if (isPinnedToMenu) {
|
if (isPinnedToMenu) {
|
||||||
await db.shortcuts.remove(item.id);
|
await db.shortcuts.remove(item.id);
|
||||||
} else {
|
} else {
|
||||||
if (item.type === "topic") {
|
await db.shortcuts.add({
|
||||||
await db.shortcuts.add({
|
itemId: item.id,
|
||||||
item: {
|
itemType: item.type
|
||||||
type: "topic",
|
});
|
||||||
id: item.id,
|
|
||||||
notebookId: item.notebookId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await db.shortcuts.add({
|
|
||||||
item: {
|
|
||||||
type: item.type,
|
|
||||||
id: item.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setIsPinnedToMenu(db.shortcuts.exists(item.id));
|
setIsPinnedToMenu(db.shortcuts.exists(item.id));
|
||||||
setMenuPins();
|
setMenuPins();
|
||||||
@@ -300,23 +253,6 @@ export const useActions = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function removeNoteFromTopic() {
|
|
||||||
if (item.type !== "note") return;
|
|
||||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
|
||||||
if (currentScreen.name !== "TopicNotes" || !currentScreen.notebookId)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await db.notes.removeFromNotebook(
|
|
||||||
{
|
|
||||||
id: currentScreen.notebookId,
|
|
||||||
topic: currentScreen.id
|
|
||||||
},
|
|
||||||
item.id
|
|
||||||
);
|
|
||||||
Navigation.queueRoutesForUpdate();
|
|
||||||
eSendEvent(eOnTopicSheetUpdate);
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteTrashItem() {
|
async function deleteTrashItem() {
|
||||||
if (item.type !== "trash") return;
|
if (item.type !== "trash") return;
|
||||||
@@ -440,11 +376,7 @@ export const useActions = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (item.type === "tag" || item.type === "notebook") {
|
||||||
item.type === "tag" ||
|
|
||||||
item.type === "topic" ||
|
|
||||||
item.type === "notebook"
|
|
||||||
) {
|
|
||||||
actions.push({
|
actions.push({
|
||||||
id: "add-shortcut",
|
id: "add-shortcut",
|
||||||
title: isPinnedToMenu ? "Remove Shortcut" : "Add Shortcut",
|
title: isPinnedToMenu ? "Remove Shortcut" : "Add Shortcut",
|
||||||
@@ -460,27 +392,12 @@ export const useActions = ({
|
|||||||
if (item.type === "notebook") {
|
if (item.type === "notebook") {
|
||||||
actions.push(
|
actions.push(
|
||||||
{
|
{
|
||||||
id: "default-notebook",
|
id: "add-notebook",
|
||||||
title:
|
title: "Add notebook",
|
||||||
defaultNotebook?.id === item.id
|
icon: "plus",
|
||||||
? "Remove as default"
|
|
||||||
: "Set as default",
|
|
||||||
hidden: item.type !== "notebook",
|
|
||||||
icon: "notebook",
|
|
||||||
func: async () => {
|
func: async () => {
|
||||||
if (defaultNotebook?.id === item.id) {
|
AddNotebookSheet.present(undefined, item);
|
||||||
await db.settings.setDefaultNotebook(undefined);
|
}
|
||||||
setDefaultNotebook(undefined);
|
|
||||||
} else {
|
|
||||||
const notebook = {
|
|
||||||
id: item.id
|
|
||||||
};
|
|
||||||
await db.settings.setDefaultNotebook(notebook);
|
|
||||||
setDefaultNotebook(notebook);
|
|
||||||
}
|
|
||||||
close();
|
|
||||||
},
|
|
||||||
on: defaultNotebook?.topic ? false : defaultNotebook?.id === item.id
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "edit-notebook",
|
id: "edit-notebook",
|
||||||
@@ -489,61 +406,27 @@ export const useActions = ({
|
|||||||
func: async () => {
|
func: async () => {
|
||||||
AddNotebookSheet.present(item);
|
AddNotebookSheet.present(item);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.type === "topic") {
|
|
||||||
actions.push(
|
|
||||||
{
|
|
||||||
id: "edit-topic",
|
|
||||||
title: "Edit topic",
|
|
||||||
icon: "square-edit-outline",
|
|
||||||
func: async () => {
|
|
||||||
close();
|
|
||||||
await sleep(300);
|
|
||||||
eSendEvent(eOpenAddTopicDialog, {
|
|
||||||
notebookId: item.notebookId,
|
|
||||||
toEdit: item
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "move-notes",
|
id: "default-notebook",
|
||||||
title: "Add notes",
|
|
||||||
icon: "plus",
|
|
||||||
func: async () => {
|
|
||||||
const notebook = db.notebooks.notebook(
|
|
||||||
(item as Topic).notebookId
|
|
||||||
)?.data;
|
|
||||||
if (notebook) {
|
|
||||||
MoveNotes.present(notebook, item as Topic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "default-topic",
|
|
||||||
title:
|
title:
|
||||||
defaultNotebook?.id === item.id
|
defaultNotebook === item.id ? "Remove as default" : "Set as default",
|
||||||
? "Remove as default"
|
hidden: item.type !== "notebook",
|
||||||
: "Set as default",
|
icon: "notebook",
|
||||||
hidden: item.type !== "topic",
|
|
||||||
icon: "bookmark",
|
|
||||||
func: async () => {
|
func: async () => {
|
||||||
if (defaultNotebook?.topic === item.id) {
|
if (defaultNotebook === item.id) {
|
||||||
await db.settings.setDefaultNotebook(undefined);
|
await db.settings.setDefaultNotebook(undefined);
|
||||||
setDefaultNotebook(undefined);
|
setDefaultNotebook(undefined);
|
||||||
} else {
|
} else {
|
||||||
const notebook = {
|
const notebook = {
|
||||||
id: item.notebookId,
|
id: item.id
|
||||||
topic: item.id
|
|
||||||
};
|
};
|
||||||
await db.settings.setDefaultNotebook(notebook);
|
await db.settings.setDefaultNotebook(notebook.id);
|
||||||
setDefaultNotebook(notebook);
|
setDefaultNotebook(notebook.id);
|
||||||
}
|
}
|
||||||
close();
|
close();
|
||||||
},
|
},
|
||||||
on: defaultNotebook?.topic === item.id
|
on: defaultNotebook === item.id
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -579,17 +462,17 @@ export const useActions = ({
|
|||||||
|
|
||||||
async function toggleLocalOnly() {
|
async function toggleLocalOnly() {
|
||||||
if (!checkItemSynced() || !user) return;
|
if (!checkItemSynced() || !user) return;
|
||||||
db.notes.note(item.id)?.localOnly();
|
await db.notes.localOnly(!(item as Note).localOnly, item?.id);
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleReadyOnlyMode = async () => {
|
const toggleReadyOnlyMode = async () => {
|
||||||
await db.notes.note(item.id)?.readonly();
|
const currentReadOnly = (item as Note).localOnly;
|
||||||
const current = db.notes.note(item.id)?.data.readonly;
|
await db.notes.readonly(!currentReadOnly, item?.id);
|
||||||
|
|
||||||
if (useEditorStore.getState().currentEditingNote === item.id) {
|
if (useEditorStore.getState().currentEditingNote === item.id) {
|
||||||
useEditorStore.getState().setReadonly(!!current);
|
useEditorStore.getState().setReadonly(!currentReadOnly);
|
||||||
}
|
}
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
close();
|
close();
|
||||||
@@ -613,23 +496,6 @@ export const useActions = ({
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
const isNoteInTopic = () => {
|
|
||||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
|
||||||
if (item.type !== "note" || currentScreen.name !== "TopicNotes") return;
|
|
||||||
return (
|
|
||||||
db.notes?.topicReferences?.get(currentScreen.id)?.indexOf(item.id) > -1
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isNoteInNotebook = () => {
|
|
||||||
const currentScreen = useNavigationStore.getState().currentScreen;
|
|
||||||
if (item.type !== "note" || currentScreen.name !== "Notebook") return;
|
|
||||||
|
|
||||||
return !!db.relations
|
|
||||||
.to(item, "notebook")
|
|
||||||
.find((notebook) => notebook.id === currentScreen.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
function addTo() {
|
function addTo() {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
setSelectedItem(item);
|
setSelectedItem(item);
|
||||||
@@ -637,9 +503,9 @@ export const useActions = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function addToFavorites() {
|
async function addToFavorites() {
|
||||||
if (!item.id) return;
|
if (!item.id || item.type !== "note") return;
|
||||||
close();
|
close();
|
||||||
await db.notes.note(item.id)?.favorite();
|
await db.notes.favorite(item.favorite, item.id);
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -717,7 +583,7 @@ export const useActions = ({
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PublishNoteSheet.present(item);
|
PublishNoteSheet.present(item as Note);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function shareNote() {
|
async function shareNote() {
|
||||||
@@ -778,7 +644,7 @@ export const useActions = ({
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await db.vault.add(item.id);
|
await db.vault.add(item.id);
|
||||||
const note = db.notes.note(item.id)?.data;
|
const note = await db.notes.note(item.id);
|
||||||
if (note?.locked) {
|
if (note?.locked) {
|
||||||
close();
|
close();
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
@@ -862,7 +728,7 @@ export const useActions = ({
|
|||||||
{
|
{
|
||||||
id: "remove-from-notebook",
|
id: "remove-from-notebook",
|
||||||
title: "Remove from notebook",
|
title: "Remove from notebook",
|
||||||
hidden: !isNoteInNotebook(),
|
hidden: noteInCurrentNotebook,
|
||||||
icon: "minus-circle-outline",
|
icon: "minus-circle-outline",
|
||||||
func: removeNoteFromNotebook
|
func: removeNoteFromNotebook
|
||||||
},
|
},
|
||||||
@@ -879,13 +745,6 @@ export const useActions = ({
|
|||||||
func: openHistory
|
func: openHistory
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
id: "remove-from-topic",
|
|
||||||
title: "Remove from topic",
|
|
||||||
hidden: !isNoteInTopic(),
|
|
||||||
icon: "minus-circle-outline",
|
|
||||||
func: removeNoteFromTopic
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "reminders",
|
id: "reminders",
|
||||||
title: "Reminders",
|
title: "Reminders",
|
||||||
@@ -997,5 +856,16 @@ export const useActions = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const currentScreen = useNavigationStore.getState().currentScreen;
|
||||||
|
if (item.type !== "note" || currentScreen.name !== "Notebook") return;
|
||||||
|
!!db.relations
|
||||||
|
.to(item, "notebook")
|
||||||
|
.selector.find((v) => v("id", "==", currentScreen.id))
|
||||||
|
.then((notebook) => {
|
||||||
|
setNoteInCurrentNotebook(!!notebook);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,11 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AttachmentsProgressEvent,
|
|
||||||
EV,
|
EV,
|
||||||
EVENTS,
|
EVENTS,
|
||||||
SYNC_CHECK_IDS,
|
SYNC_CHECK_IDS,
|
||||||
SyncProgressEvent,
|
|
||||||
SyncStatusEvent
|
SyncStatusEvent
|
||||||
} from "@notesnook/core/dist/common";
|
} from "@notesnook/core/dist/common";
|
||||||
import notifee from "@notifee/react-native";
|
import notifee from "@notifee/react-native";
|
||||||
@@ -129,19 +127,19 @@ const onFileEncryptionProgress = ({
|
|||||||
.setEncryptionProgress(Math.round(progress / total));
|
.setEncryptionProgress(Math.round(progress / total));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDownloadingAttachmentProgress = (data) => {
|
const onDownloadingAttachmentProgress = (data: any) => {
|
||||||
useAttachmentStore.getState().setDownloading(data);
|
useAttachmentStore.getState().setDownloading(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUploadingAttachmentProgress = (data) => {
|
const onUploadingAttachmentProgress = (data: any) => {
|
||||||
useAttachmentStore.getState().setUploading(data);
|
useAttachmentStore.getState().setUploading(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDownloadedAttachmentProgress = (data) => {
|
const onDownloadedAttachmentProgress = (data: any) => {
|
||||||
useAttachmentStore.getState().setDownloading(data);
|
useAttachmentStore.getState().setDownloading(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUploadedAttachmentProgress = (data) => {
|
const onUploadedAttachmentProgress = (data: any) => {
|
||||||
useAttachmentStore.getState().setUploading(data);
|
useAttachmentStore.getState().setUploading(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -234,7 +232,7 @@ async function checkForShareExtensionLaunchedInBackground() {
|
|||||||
|
|
||||||
if (notesAddedFromIntent || shareExtensionOpened) {
|
if (notesAddedFromIntent || shareExtensionOpened) {
|
||||||
const id = useEditorStore.getState().currentEditingNote;
|
const id = useEditorStore.getState().currentEditingNote;
|
||||||
const note = id && db.notes.note(id)?.data;
|
const note = id && (await db.notes.note(id));
|
||||||
eSendEvent("webview_reset");
|
eSendEvent("webview_reset");
|
||||||
if (note) setTimeout(() => eSendEvent("loadingNote", note), 1);
|
if (note) setTimeout(() => eSendEvent("loadingNote", note), 1);
|
||||||
MMKV.removeItem("shareExtensionOpened");
|
MMKV.removeItem("shareExtensionOpened");
|
||||||
@@ -247,7 +245,7 @@ async function checkForShareExtensionLaunchedInBackground() {
|
|||||||
async function saveEditorState() {
|
async function saveEditorState() {
|
||||||
if (editorState().currentlyEditing) {
|
if (editorState().currentlyEditing) {
|
||||||
const id = useEditorStore.getState().currentEditingNote;
|
const id = useEditorStore.getState().currentEditingNote;
|
||||||
const note = id ? db.notes.note(id)?.data : undefined;
|
const note = id ? await db.notes.note(id) : undefined;
|
||||||
|
|
||||||
if (note?.locked) return;
|
if (note?.locked) return;
|
||||||
const state = JSON.stringify({
|
const state = JSON.stringify({
|
||||||
@@ -514,7 +512,7 @@ export const useAppEvents = () => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const id = useEditorStore.getState().currentEditingNote;
|
const id = useEditorStore.getState().currentEditingNote;
|
||||||
const note = id ? db.notes.note(id)?.data : undefined;
|
const note = id ? await db.notes.note(id) : undefined;
|
||||||
if (
|
if (
|
||||||
note?.locked &&
|
note?.locked &&
|
||||||
SettingsService.get().appLockMode === "background"
|
SettingsService.get().appLockMode === "background"
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ type AttachmentProgress = {
|
|||||||
export const useAttachmentProgress = (
|
export const useAttachmentProgress = (
|
||||||
attachment: any,
|
attachment: any,
|
||||||
encryption?: boolean
|
encryption?: boolean
|
||||||
) => {
|
): [
|
||||||
|
AttachmentProgress | undefined,
|
||||||
|
(progress?: AttachmentProgress) => void
|
||||||
|
] => {
|
||||||
const progress = useAttachmentStore((state) => state.progress);
|
const progress = useAttachmentStore((state) => state.progress);
|
||||||
const [currentProgress, setCurrentProgress] = useState<
|
const [currentProgress, setCurrentProgress] = useState<
|
||||||
AttachmentProgress | undefined
|
AttachmentProgress | undefined
|
||||||
|
|||||||
131
apps/mobile/app/hooks/use-db-item.ts
Normal file
131
apps/mobile/app/hooks/use-db-item.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the Notesnook project (https://notesnook.com/)
|
||||||
|
|
||||||
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
Attachment,
|
||||||
|
Color,
|
||||||
|
Note,
|
||||||
|
Notebook,
|
||||||
|
Reminder,
|
||||||
|
Shortcut,
|
||||||
|
Tag,
|
||||||
|
VirtualizedGrouping
|
||||||
|
} from "@notesnook/core";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { db } from "../common/database";
|
||||||
|
import {
|
||||||
|
eSendEvent,
|
||||||
|
eSubscribeEvent,
|
||||||
|
eUnSubscribeEvent
|
||||||
|
} from "../services/event-manager";
|
||||||
|
import { eDBItemUpdate } from "../utils/events";
|
||||||
|
|
||||||
|
type ItemTypeKey = {
|
||||||
|
note: Note;
|
||||||
|
notebook: Notebook;
|
||||||
|
tag: Tag;
|
||||||
|
color: Color;
|
||||||
|
reminder: Reminder;
|
||||||
|
attachment: Attachment;
|
||||||
|
shortcut: Shortcut;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDBItem = <T extends keyof ItemTypeKey>(
|
||||||
|
id?: string,
|
||||||
|
type?: T,
|
||||||
|
items?: VirtualizedGrouping<ItemTypeKey[T]>
|
||||||
|
): [ItemTypeKey[T] | undefined, () => void] => {
|
||||||
|
const [item, setItem] = useState<ItemTypeKey[T]>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onUpdateItem = (itemId?: string) => {
|
||||||
|
if (typeof itemId === "string" && itemId !== id) return;
|
||||||
|
if (!id) {
|
||||||
|
setItem(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items) {
|
||||||
|
items.item(id).then((item) => {
|
||||||
|
setItem(item);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!(db as any)[type + "s"][type]) {
|
||||||
|
console.warn(
|
||||||
|
"no method found for",
|
||||||
|
`db.${type}s.${type}(id: string)`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
(db as any)[type + "s"]
|
||||||
|
?.[type]?.(id)
|
||||||
|
.then((item: ItemTypeKey[T]) => setItem(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onUpdateItem();
|
||||||
|
eSubscribeEvent(eDBItemUpdate, onUpdateItem);
|
||||||
|
return () => {
|
||||||
|
eUnSubscribeEvent(eDBItemUpdate, onUpdateItem);
|
||||||
|
};
|
||||||
|
}, [id, type]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
item as ItemTypeKey[T],
|
||||||
|
() => {
|
||||||
|
if (id) {
|
||||||
|
eSendEvent(eDBItemUpdate, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTotalNotes = (
|
||||||
|
ids: string[],
|
||||||
|
type: "notebook" | "tag" | "color"
|
||||||
|
) => {
|
||||||
|
const [totalNotesById, setTotalNotesById] = useState<{
|
||||||
|
[id: string]: number;
|
||||||
|
}>({});
|
||||||
|
|
||||||
|
const getTotalNotes = React.useCallback(() => {
|
||||||
|
if (!ids || !ids.length || !type) return;
|
||||||
|
db.relations
|
||||||
|
.from({ type: "notebook", ids: ids as string[] }, ["notebook", "note"])
|
||||||
|
.get()
|
||||||
|
.then((relations) => {
|
||||||
|
const totalNotesById: any = {};
|
||||||
|
for (const id of ids) {
|
||||||
|
totalNotesById[id] = relations.filter(
|
||||||
|
(relation) => relation.fromId === id && relation.toType === "note"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setTotalNotesById(totalNotesById);
|
||||||
|
});
|
||||||
|
}, [ids, type]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getTotalNotes();
|
||||||
|
}, [ids, type, getTotalNotes]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalNotes: (id: string) => {
|
||||||
|
return totalNotesById[id] || 0;
|
||||||
|
},
|
||||||
|
getTotalNotes: getTotalNotes
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -16,19 +16,18 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
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 { ItemType } from "@notesnook/core";
|
||||||
import { useSettingStore } from "../stores/use-setting-store";
|
import { useSettingStore } from "../stores/use-setting-store";
|
||||||
|
|
||||||
export function useIsCompactModeEnabled(item: any) {
|
export function useIsCompactModeEnabled(dataType: ItemType) {
|
||||||
const [notebooksListMode, notesListMode] = useSettingStore((state) => [
|
const [notebooksListMode, notesListMode] = useSettingStore((state) => [
|
||||||
state.settings.notebooksListMode,
|
state.settings.notebooksListMode,
|
||||||
state.settings.notesListMode
|
state.settings.notesListMode
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const type = item.itemType || item.type;
|
if (dataType !== "note" && dataType !== "notebook") return false;
|
||||||
|
|
||||||
if (type !== "note" && type !== "notebook") return false;
|
const listMode = dataType === "notebook" ? notebooksListMode : notesListMode;
|
||||||
|
|
||||||
const listMode = type === "notebook" ? notebooksListMode : notesListMode;
|
|
||||||
|
|
||||||
return listMode === "compact";
|
return listMode === "compact";
|
||||||
}
|
}
|
||||||
|
|||||||
87
apps/mobile/app/hooks/use-notebook.ts
Normal file
87
apps/mobile/app/hooks/use-notebook.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the Notesnook project (https://notesnook.com/)
|
||||||
|
|
||||||
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { db } from "../common/database";
|
||||||
|
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
|
||||||
|
import { eOnNotebookUpdated } from "../utils/events";
|
||||||
|
import { useDBItem, useTotalNotes } from "./use-db-item";
|
||||||
|
|
||||||
|
export const useNotebook = (
|
||||||
|
id?: string,
|
||||||
|
items?: VirtualizedGrouping<Notebook>
|
||||||
|
) => {
|
||||||
|
const [item, refresh] = useDBItem(id, "notebook", items);
|
||||||
|
const [groupOptions, setGroupOptions] = useState(
|
||||||
|
db.settings.getGroupOptions("notebooks")
|
||||||
|
);
|
||||||
|
const [notebooks, setNotebooks] = useState<VirtualizedGrouping<Notebook>>();
|
||||||
|
const { totalNotes: nestedNotebookNotesCount } = useTotalNotes(
|
||||||
|
notebooks?.ids as string[],
|
||||||
|
"notebook"
|
||||||
|
);
|
||||||
|
|
||||||
|
const onRequestUpdate = React.useCallback(() => {
|
||||||
|
if (!item || !id) {
|
||||||
|
console.log("unset notebook");
|
||||||
|
setNotebooks(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
db.relations
|
||||||
|
.from(item, "notebook")
|
||||||
|
.selector.sorted(db.settings.getGroupOptions("notebooks"))
|
||||||
|
.then((notebooks) => {
|
||||||
|
setNotebooks(notebooks);
|
||||||
|
});
|
||||||
|
}, [item, id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onRequestUpdate();
|
||||||
|
}, [item, onRequestUpdate]);
|
||||||
|
|
||||||
|
const onUpdate = useCallback(() => {
|
||||||
|
setGroupOptions({ ...(db.settings.getGroupOptions("notebooks") as any) });
|
||||||
|
onRequestUpdate();
|
||||||
|
}, [onRequestUpdate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onNotebookUpdate = (id?: string) => {
|
||||||
|
if (typeof id === "string" && id !== id) return;
|
||||||
|
setImmediate(() => {
|
||||||
|
onRequestUpdate();
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
eSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||||
|
eSubscribeEvent(eOnNotebookUpdated, onNotebookUpdate);
|
||||||
|
return () => {
|
||||||
|
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||||
|
eUnSubscribeEvent(eOnNotebookUpdated, onNotebookUpdate);
|
||||||
|
};
|
||||||
|
}, [onUpdate, onRequestUpdate, id]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
notebook: item,
|
||||||
|
nestedNotebookNotesCount,
|
||||||
|
nestedNotebooks: notebooks,
|
||||||
|
onUpdate: onRequestUpdate,
|
||||||
|
groupOptions
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -25,7 +25,7 @@ import { SafeAreaView } from "react-native";
|
|||||||
import Container from "../components/container";
|
import Container from "../components/container";
|
||||||
import DelayLayout from "../components/delay-layout";
|
import DelayLayout from "../components/delay-layout";
|
||||||
import Intro from "../components/intro";
|
import Intro from "../components/intro";
|
||||||
import { TopicsSheet } from "../components/sheets/topic-sheet";
|
import { NotebookSheet } from "../components/sheets/notebook-sheet";
|
||||||
import useGlobalSafeAreaInsets from "../hooks/use-global-safe-area-insets";
|
import useGlobalSafeAreaInsets from "../hooks/use-global-safe-area-insets";
|
||||||
import { hideAllTooltips } from "../hooks/use-tooltip";
|
import { hideAllTooltips } from "../hooks/use-tooltip";
|
||||||
import Favorites from "../screens/favorites";
|
import Favorites from "../screens/favorites";
|
||||||
@@ -197,7 +197,7 @@ const _NavigationStack = () => {
|
|||||||
<NavigationContainer onStateChange={onStateChange} ref={rootNavigatorRef}>
|
<NavigationContainer onStateChange={onStateChange} ref={rootNavigatorRef}>
|
||||||
<Tabs />
|
<Tabs />
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
{loading ? null : <TopicsSheet />}
|
{loading ? null : <NotebookSheet />}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ export const useEditor = (
|
|||||||
const lock = useRef(false);
|
const lock = useRef(false);
|
||||||
const lockedSessionId = useRef<string>();
|
const lockedSessionId = useRef<string>();
|
||||||
const loadingState = useRef<string>();
|
const loadingState = useRef<string>();
|
||||||
|
|
||||||
const postMessage = useCallback(
|
const postMessage = useCallback(
|
||||||
async <T>(type: string, data: T, waitFor = 300) =>
|
async <T>(type: string, data: T, waitFor = 300) =>
|
||||||
await post(editorRef, sessionIdRef.current, type, data, waitFor),
|
await post(editorRef, sessionIdRef.current, type, data, waitFor),
|
||||||
@@ -190,13 +189,13 @@ export const useEditor = (
|
|||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
try {
|
try {
|
||||||
if (id && !db.notes?.note(id)) {
|
if (id && !(await db.notes?.note(id))) {
|
||||||
isDefaultEditor &&
|
isDefaultEditor &&
|
||||||
useEditorStore.getState().setCurrentlyEditingNote(null);
|
useEditorStore.getState().setCurrentlyEditingNote(null);
|
||||||
await reset();
|
await reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let note = id ? db.notes?.note(id)?.data : undefined;
|
let note = id ? await db.notes?.note(id) : undefined;
|
||||||
const locked = note?.locked;
|
const locked = note?.locked;
|
||||||
if (note?.conflicted) return;
|
if (note?.conflicted) return;
|
||||||
|
|
||||||
@@ -233,13 +232,12 @@ export const useEditor = (
|
|||||||
if (!locked) {
|
if (!locked) {
|
||||||
id = await db.notes?.add(noteData);
|
id = await db.notes?.add(noteData);
|
||||||
if (!note && id) {
|
if (!note && id) {
|
||||||
currentNote.current = db.notes?.note(id)?.data;
|
currentNote.current = await db.notes?.note(id);
|
||||||
const defaultNotebook = db.settings.getDefaultNotebook();
|
const defaultNotebook = db.settings.getDefaultNotebook();
|
||||||
if (!state.current.onNoteCreated && defaultNotebook) {
|
if (!state.current.onNoteCreated && defaultNotebook) {
|
||||||
onNoteCreated(id, {
|
onNoteCreated(id, {
|
||||||
type: defaultNotebook.topic ? "topic" : "notebook",
|
type: "notebook",
|
||||||
id: defaultNotebook.id,
|
id: defaultNotebook
|
||||||
notebook: defaultNotebook.topic
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
state.current?.onNoteCreated && state.current.onNoteCreated(id);
|
state.current?.onNoteCreated && state.current.onNoteCreated(id);
|
||||||
@@ -274,7 +272,7 @@ export const useEditor = (
|
|||||||
await db.vault?.save(noteData as any);
|
await db.vault?.save(noteData as any);
|
||||||
}
|
}
|
||||||
if (id && sessionIdRef.current === currentSessionId) {
|
if (id && sessionIdRef.current === currentSessionId) {
|
||||||
note = db.notes?.note(id)?.data as Note;
|
note = (await db.notes?.note(id)) as Note;
|
||||||
await commands.setStatus(
|
await commands.setStatus(
|
||||||
getFormattedDate(note.dateEdited, "date-time"),
|
getFormattedDate(note.dateEdited, "date-time"),
|
||||||
"Saved"
|
"Saved"
|
||||||
@@ -316,7 +314,7 @@ export const useEditor = (
|
|||||||
noteId: currentNote.current?.id as string
|
noteId: currentNote.current?.id as string
|
||||||
};
|
};
|
||||||
} else if (note.contentId) {
|
} else if (note.contentId) {
|
||||||
const rawContent = await db.content?.raw(note.contentId);
|
const rawContent = await db.content?.get(note.contentId);
|
||||||
if (
|
if (
|
||||||
rawContent &&
|
rawContent &&
|
||||||
!isDeleted(rawContent) &&
|
!isDeleted(rawContent) &&
|
||||||
@@ -396,7 +394,10 @@ export const useEditor = (
|
|||||||
sessionHistoryId.current = Date.now();
|
sessionHistoryId.current = Date.now();
|
||||||
await commands.setSessionId(nextSessionId);
|
await commands.setSessionId(nextSessionId);
|
||||||
currentNote.current = item;
|
currentNote.current = item;
|
||||||
await commands.setStatus(getFormattedDate(item.dateEdited), "Saved");
|
await commands.setStatus(
|
||||||
|
getFormattedDate(item.dateEdited, "date-time"),
|
||||||
|
"Saved"
|
||||||
|
);
|
||||||
await postMessage(EditorEvents.title, item.title);
|
await postMessage(EditorEvents.title, item.title);
|
||||||
loadingState.current = currentContent.current?.data;
|
loadingState.current = currentContent.current?.data;
|
||||||
|
|
||||||
@@ -443,7 +444,7 @@ export const useEditor = (
|
|||||||
const isContentEncrypted =
|
const isContentEncrypted =
|
||||||
typeof (data as ContentItem)?.data === "object";
|
typeof (data as ContentItem)?.data === "object";
|
||||||
|
|
||||||
const note = db.notes?.note(currentNote.current?.id)?.data;
|
const note = await db.notes?.note(currentNote.current?.id);
|
||||||
|
|
||||||
if (lastContentChangeTime.current >= (data as Note).dateEdited) return;
|
if (lastContentChangeTime.current >= (data as Note).dateEdited) return;
|
||||||
|
|
||||||
|
|||||||
@@ -37,13 +37,6 @@ const prepareSearch = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const PLACEHOLDER_DATA = {
|
|
||||||
heading: "Your favorites",
|
|
||||||
paragraph: "You have not added any notes to favorites yet.",
|
|
||||||
button: null,
|
|
||||||
loading: "Loading your favorites"
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Favorites = ({
|
export const Favorites = ({
|
||||||
navigation,
|
navigation,
|
||||||
route
|
route
|
||||||
@@ -70,17 +63,19 @@ export const Favorites = ({
|
|||||||
return (
|
return (
|
||||||
<DelayLayout wait={loading}>
|
<DelayLayout wait={loading}>
|
||||||
<List
|
<List
|
||||||
listData={favorites}
|
data={favorites}
|
||||||
type="notes"
|
dataType="note"
|
||||||
refreshCallback={() => {
|
onRefresh={() => {
|
||||||
setFavorites();
|
setFavorites();
|
||||||
}}
|
}}
|
||||||
screen="Favorites"
|
renderedInRoute="Favorites"
|
||||||
loading={loading || !isFocused}
|
loading={loading || !isFocused}
|
||||||
placeholderData={PLACEHOLDER_DATA}
|
placeholder={{
|
||||||
headerProps={{
|
title: "Your favorites",
|
||||||
heading: "Favorites"
|
paragraph: "You have not added any notes to favorites yet.",
|
||||||
|
loading: "Loading your favorites"
|
||||||
}}
|
}}
|
||||||
|
headerTitle="Favorites"
|
||||||
/>
|
/>
|
||||||
</DelayLayout>
|
</DelayLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -39,14 +39,6 @@ const prepareSearch = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const PLACEHOLDER_DATA = {
|
|
||||||
heading: "Notes",
|
|
||||||
paragraph: "You have not added any notes yet.",
|
|
||||||
button: "Add your first note",
|
|
||||||
action: openEditor,
|
|
||||||
loading: "Loading your notes"
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||||
const notes = useNoteStore((state) => state.notes);
|
const notes = useNoteStore((state) => state.notes);
|
||||||
const loading = useNoteStore((state) => state.loading);
|
const loading = useNoteStore((state) => state.loading);
|
||||||
@@ -66,19 +58,23 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
|||||||
onBlur: () => false,
|
onBlur: () => false,
|
||||||
delay: SettingsService.get().homepage === route.name ? 1 : -1
|
delay: SettingsService.get().homepage === route.name ? 1 : -1
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DelayLayout wait={loading} delay={500}>
|
<DelayLayout wait={loading} delay={500}>
|
||||||
<List
|
<List
|
||||||
listData={notes}
|
data={notes}
|
||||||
type="notes"
|
dataType="note"
|
||||||
screen="Home"
|
renderedInRoute="Notes"
|
||||||
loading={loading || !isFocused}
|
loading={loading || !isFocused}
|
||||||
headerProps={{
|
headerTitle="Notes"
|
||||||
heading: "Notes"
|
placeholder={{
|
||||||
|
title: "Notes",
|
||||||
|
paragraph: "You have not added any notes yet.",
|
||||||
|
button: "Add your first note",
|
||||||
|
action: openEditor,
|
||||||
|
loading: "Loading your notes"
|
||||||
}}
|
}}
|
||||||
placeholderData={PLACEHOLDER_DATA}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FloatingButton title="Create a new note" onPress={openEditor} />
|
<FloatingButton title="Create a new note" onPress={openEditor} />
|
||||||
</DelayLayout>
|
</DelayLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
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 { Note, Notebook, Topic } from "@notesnook/core/dist/types";
|
import { VirtualizedGrouping } from "@notesnook/core";
|
||||||
import { groupArray } from "@notesnook/core/dist/utils/grouping";
|
import { Note, Notebook } from "@notesnook/core/dist/types";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import DelayLayout from "../../components/delay-layout";
|
import DelayLayout from "../../components/delay-layout";
|
||||||
@@ -38,13 +38,9 @@ import { eOnNewTopicAdded } from "../../utils/events";
|
|||||||
import { openEditor, setOnFirstSave } from "../notes/common";
|
import { openEditor, setOnFirstSave } from "../notes/common";
|
||||||
|
|
||||||
const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||||
const [notes, setNotes] = useState(
|
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
||||||
groupArray(
|
|
||||||
db.relations?.from(route.params.item, "note").resolved(),
|
|
||||||
db.settings.getGroupOptions("notes")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const params = useRef<NotebookScreenParams>(route?.params);
|
const params = useRef<NotebookScreenParams>(route?.params);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useNavigationFocus(navigation, {
|
useNavigationFocus(navigation, {
|
||||||
onFocus: () => {
|
onFocus: () => {
|
||||||
@@ -77,21 +73,23 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
|||||||
}, [route.name]);
|
}, [route.name]);
|
||||||
|
|
||||||
const onRequestUpdate = React.useCallback(
|
const onRequestUpdate = React.useCallback(
|
||||||
(data?: NotebookScreenParams) => {
|
async (data?: NotebookScreenParams) => {
|
||||||
if (data) params.current = data;
|
if (data) params.current = data;
|
||||||
params.current.title = params.current.item.title;
|
params.current.title = params.current.item.title;
|
||||||
try {
|
try {
|
||||||
const notebook = db.notebooks?.notebook(
|
const notebook = await db.notebooks?.notebook(
|
||||||
params?.current?.item?.id
|
params?.current?.item?.id
|
||||||
)?.data;
|
);
|
||||||
if (notebook) {
|
if (notebook) {
|
||||||
params.current.item = notebook;
|
params.current.item = notebook;
|
||||||
const notes = db.relations?.from(notebook, "note").resolved();
|
|
||||||
setNotes(
|
setNotes(
|
||||||
groupArray(notes || [], db.settings.getGroupOptions("notes"))
|
await db.relations
|
||||||
|
.from(notebook, "note")
|
||||||
|
.selector.grouped(db.settings.getGroupOptions("notes"))
|
||||||
);
|
);
|
||||||
syncWithNavigation();
|
syncWithNavigation();
|
||||||
}
|
}
|
||||||
|
setLoading(false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
@@ -100,6 +98,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
onRequestUpdate();
|
||||||
eSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
|
eSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
|
||||||
return () => {
|
return () => {
|
||||||
eUnSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
|
eUnSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
|
||||||
@@ -113,37 +112,35 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const prepareSearch = () => {
|
const prepareSearch = () => {
|
||||||
SearchService.update({
|
// SearchService.update({
|
||||||
placeholder: `Search in "${params.current.title}"`,
|
// placeholder: `Search in "${params.current.title}"`,
|
||||||
type: "notes",
|
// type: "notes",
|
||||||
title: params.current.title,
|
// title: params.current.title,
|
||||||
get: () => {
|
// get: () => {
|
||||||
const notebook = db.notebooks?.notebook(
|
// const notebook = db.notebooks?.notebook(
|
||||||
params?.current?.item?.id
|
// params?.current?.item?.id
|
||||||
)?.data;
|
// )?.data;
|
||||||
if (!notebook) return [];
|
// if (!notebook) return [];
|
||||||
|
// const notes = db.relations?.from(notebook, "note") || [];
|
||||||
const notes = db.relations?.from(notebook, "note") || [];
|
// const topicNotes = db.notebooks
|
||||||
const topicNotes = db.notebooks
|
// .notebook(notebook.id)
|
||||||
.notebook(notebook.id)
|
// ?.topics.all.map((topic: Topic) => {
|
||||||
?.topics.all.map((topic: Topic) => {
|
// return db.notes?.topicReferences
|
||||||
return db.notes?.topicReferences
|
// .get(topic.id)
|
||||||
.get(topic.id)
|
// .map((id: string) => db.notes?.note(id)?.data);
|
||||||
.map((id: string) => db.notes?.note(id)?.data);
|
// })
|
||||||
})
|
// .flat()
|
||||||
.flat()
|
// .filter(
|
||||||
.filter(
|
// (topicNote) =>
|
||||||
(topicNote) =>
|
// notes.findIndex((note) => note?.id !== topicNote?.id) === -1
|
||||||
notes.findIndex((note) => note?.id !== topicNote?.id) === -1
|
// ) as Note[];
|
||||||
) as Note[];
|
// return [...notes, ...topicNotes];
|
||||||
|
// }
|
||||||
return [...notes, ...topicNotes];
|
// });
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const PLACEHOLDER_DATA = {
|
const PLACEHOLDER_DATA = {
|
||||||
heading: params.current.item?.title,
|
title: params.current.item?.title,
|
||||||
paragraph: "You have not added any notes yet.",
|
paragraph: "You have not added any notes yet.",
|
||||||
button: "Add your first note",
|
button: "Add your first note",
|
||||||
action: openEditor,
|
action: openEditor,
|
||||||
@@ -154,32 +151,33 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
|||||||
<>
|
<>
|
||||||
<DelayLayout>
|
<DelayLayout>
|
||||||
<List
|
<List
|
||||||
listData={notes}
|
data={notes}
|
||||||
type="notes"
|
dataType="note"
|
||||||
refreshCallback={() => {
|
onRefresh={() => {
|
||||||
onRequestUpdate();
|
onRequestUpdate();
|
||||||
}}
|
}}
|
||||||
screen="Notebook"
|
renderedInRoute="Notebook"
|
||||||
headerProps={{
|
headerTitle={params.current.title}
|
||||||
heading: params.current.title
|
loading={loading}
|
||||||
}}
|
CustomLisHeader={
|
||||||
loading={false}
|
|
||||||
ListHeader={
|
|
||||||
<NotebookHeader
|
<NotebookHeader
|
||||||
onEditNotebook={() => {
|
onEditNotebook={() => {
|
||||||
AddNotebookSheet.present(params.current.item);
|
AddNotebookSheet.present(params.current.item);
|
||||||
}}
|
}}
|
||||||
notebook={params.current.item}
|
notebook={params.current.item}
|
||||||
|
totalNotes={
|
||||||
|
notes?.ids.filter((id) => typeof id === "string")?.length || 0
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
placeholderData={PLACEHOLDER_DATA}
|
placeholder={PLACEHOLDER_DATA}
|
||||||
/>
|
/>
|
||||||
</DelayLayout>
|
</DelayLayout>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
NotebookScreen.navigate = (item: Notebook, canGoBack: boolean) => {
|
NotebookScreen.navigate = (item: Notebook, canGoBack?: boolean) => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
Navigation.navigate<"Notebook">(
|
Navigation.navigate<"Notebook">(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ 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 React from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Config } from "react-native-config";
|
import { Config } from "react-native-config";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import { FloatingButton } from "../../components/container/floating-button";
|
import { FloatingButton } from "../../components/container/floating-button";
|
||||||
@@ -45,14 +45,6 @@ const prepareSearch = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const PLACEHOLDER_DATA = {
|
|
||||||
heading: "Your notebooks",
|
|
||||||
paragraph: "You have not added any notebooks yet.",
|
|
||||||
button: "Add your first notebook",
|
|
||||||
action: onPressFloatingButton,
|
|
||||||
loading: "Loading your notebooks"
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Notebooks = ({
|
export const Notebooks = ({
|
||||||
navigation,
|
navigation,
|
||||||
route
|
route
|
||||||
@@ -69,12 +61,6 @@ export const Notebooks = ({
|
|||||||
});
|
});
|
||||||
SearchService.prepareSearch = prepareSearch;
|
SearchService.prepareSearch = prepareSearch;
|
||||||
useNavigationStore.getState().setButtonAction(onPressFloatingButton);
|
useNavigationStore.getState().setButtonAction(onPressFloatingButton);
|
||||||
//@ts-ignore need to update typings in core to fix this
|
|
||||||
if (db.notebooks.all.length === 0 && !Config.isTesting) {
|
|
||||||
Walkthrough.present("notebooks");
|
|
||||||
} else {
|
|
||||||
Walkthrough.update("notebooks");
|
|
||||||
}
|
|
||||||
|
|
||||||
return !prev?.current;
|
return !prev?.current;
|
||||||
},
|
},
|
||||||
@@ -82,20 +68,34 @@ export const Notebooks = ({
|
|||||||
delay: SettingsService.get().homepage === route.name ? 1 : -1
|
delay: SettingsService.get().homepage === route.name ? 1 : -1
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (notebooks?.ids) {
|
||||||
|
if (notebooks?.ids?.length === 0 && !Config.isTesting) {
|
||||||
|
Walkthrough.present("notebooks");
|
||||||
|
} else {
|
||||||
|
Walkthrough.update("notebooks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [notebooks]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DelayLayout delay={1}>
|
<DelayLayout delay={1}>
|
||||||
<List
|
<List
|
||||||
listData={notebooks}
|
data={notebooks}
|
||||||
type="notebooks"
|
dataType="notebook"
|
||||||
screen="Notebooks"
|
renderedInRoute="Notebooks"
|
||||||
loading={!isFocused}
|
loading={!isFocused}
|
||||||
placeholderData={PLACEHOLDER_DATA}
|
placeholder={{
|
||||||
headerProps={{
|
title: "Your notebooks",
|
||||||
heading: "Notebooks"
|
paragraph: "You have not added any notebooks yet.",
|
||||||
|
button: "Add your first notebook",
|
||||||
|
action: onPressFloatingButton,
|
||||||
|
loading: "Loading your notebooks"
|
||||||
}}
|
}}
|
||||||
|
headerTitle="Notebooks"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!notebooks || notebooks.length === 0 || !isFocused ? null : (
|
{!notebooks || notebooks.ids.length === 0 || !isFocused ? null : (
|
||||||
<FloatingButton
|
<FloatingButton
|
||||||
title="Create a new notebook"
|
title="Create a new notebook"
|
||||||
onPress={onPressFloatingButton}
|
onPress={onPressFloatingButton}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const ColoredNotes = ({
|
|||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
route={route}
|
route={route}
|
||||||
get={ColoredNotes.get}
|
get={ColoredNotes.get}
|
||||||
placeholderData={PLACEHOLDER_DATA}
|
placeholder={PLACEHOLDER_DATA}
|
||||||
onPressFloatingButton={openEditor}
|
onPressFloatingButton={openEditor}
|
||||||
canGoBack={route.params?.canGoBack}
|
canGoBack={route.params?.canGoBack}
|
||||||
focusControl={true}
|
focusControl={true}
|
||||||
@@ -42,11 +42,14 @@ export const ColoredNotes = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ColoredNotes.get = (params: NotesScreenParams, grouped = true) => {
|
ColoredNotes.get = async (params: NotesScreenParams, grouped = true) => {
|
||||||
const notes = db.relations.from(params.item, "note").resolved();
|
if (!grouped) {
|
||||||
return grouped
|
return await db.relations.from(params.item, "note").resolve();
|
||||||
? groupArray(notes, db.settings.getGroupOptions("notes"))
|
}
|
||||||
: notes;
|
|
||||||
|
return await db.relations
|
||||||
|
.from(params.item, "note")
|
||||||
|
.selector.grouped(db.settings.getGroupOptions("notes"));
|
||||||
};
|
};
|
||||||
|
|
||||||
ColoredNotes.navigate = (item: Color, canGoBack: boolean) => {
|
ColoredNotes.navigate = (item: Color, canGoBack: boolean) => {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import Navigation from "../../services/navigation";
|
|||||||
import { useMenuStore } from "../../stores/use-menu-store";
|
import { useMenuStore } from "../../stores/use-menu-store";
|
||||||
import { useRelationStore } from "../../stores/use-relation-store";
|
import { useRelationStore } from "../../stores/use-relation-store";
|
||||||
import { useTagStore } from "../../stores/use-tag-store";
|
import { useTagStore } from "../../stores/use-tag-store";
|
||||||
import { eOnLoadNote, eOnTopicSheetUpdate } from "../../utils/events";
|
import { eOnLoadNote, eOnNotebookUpdated } from "../../utils/events";
|
||||||
import { openLinkInBrowser } from "../../utils/functions";
|
import { openLinkInBrowser } from "../../utils/functions";
|
||||||
import { tabBarRef } from "../../utils/global-refs";
|
import { tabBarRef } from "../../utils/global-refs";
|
||||||
import { editorController, editorState } from "../editor/tiptap/utils";
|
import { editorController, editorState } from "../editor/tiptap/utils";
|
||||||
@@ -87,24 +87,12 @@ export async function onNoteCreated(noteId: string, data: FirstSaveData) {
|
|||||||
);
|
);
|
||||||
editorState().onNoteCreated = null;
|
editorState().onNoteCreated = null;
|
||||||
useRelationStore.getState().update();
|
useRelationStore.getState().update();
|
||||||
break;
|
eSendEvent(eOnNotebookUpdated, data.id);
|
||||||
}
|
|
||||||
case "topic": {
|
|
||||||
if (!data.notebook) break;
|
|
||||||
await db.notes?.addToNotebook(
|
|
||||||
{
|
|
||||||
topic: data.id,
|
|
||||||
id: data.notebook
|
|
||||||
},
|
|
||||||
noteId
|
|
||||||
);
|
|
||||||
editorState().onNoteCreated = null;
|
|
||||||
eSendEvent(eOnTopicSheetUpdate);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "tag": {
|
case "tag": {
|
||||||
const note = db.notes.note(noteId)?.data;
|
const note = await db.notes.note(noteId);
|
||||||
const tag = db.tags.tag(data.id);
|
const tag = await db.tags.tag(data.id);
|
||||||
|
|
||||||
if (tag && note) {
|
if (tag && note) {
|
||||||
await db.relations.add(tag, note);
|
await db.relations.add(tag, note);
|
||||||
@@ -116,8 +104,8 @@ export async function onNoteCreated(noteId: string, data: FirstSaveData) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "color": {
|
case "color": {
|
||||||
const note = db.notes.note(noteId)?.data;
|
const note = await db.notes.note(noteId);
|
||||||
const color = db.colors.color(data.id);
|
const color = await db.colors.color(data.id);
|
||||||
if (note && color) {
|
if (note && color) {
|
||||||
await db.relations.add(color, note);
|
await db.relations.add(color, note);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,13 @@ 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 { Color, GroupedItems, Item, Topic } from "@notesnook/core/dist/types";
|
import {
|
||||||
|
Color,
|
||||||
|
GroupedItems,
|
||||||
|
Item,
|
||||||
|
Note,
|
||||||
|
Topic
|
||||||
|
} from "@notesnook/core/dist/types";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
@@ -48,12 +54,14 @@ import {
|
|||||||
setOnFirstSave,
|
setOnFirstSave,
|
||||||
toCamelCase
|
toCamelCase
|
||||||
} from "./common";
|
} from "./common";
|
||||||
|
import { PlaceholderData } from "../../components/list/empty";
|
||||||
|
import { VirtualizedGrouping } from "@notesnook/core";
|
||||||
export const WARNING_DATA = {
|
export const WARNING_DATA = {
|
||||||
title: "Some notes in this topic are not synced"
|
title: "Some notes in this topic are not synced"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PLACEHOLDER_DATA = {
|
export const PLACEHOLDER_DATA = {
|
||||||
heading: "Your notes",
|
title: "Your notes",
|
||||||
paragraph: "You have not added any notes yet.",
|
paragraph: "You have not added any notes yet.",
|
||||||
button: "Add your first Note",
|
button: "Add your first Note",
|
||||||
action: openEditor,
|
action: openEditor,
|
||||||
@@ -71,8 +79,11 @@ export const MONOGRAPH_PLACEHOLDER_DATA = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface RouteProps<T extends RouteName> extends NavigationProps<T> {
|
export interface RouteProps<T extends RouteName> extends NavigationProps<T> {
|
||||||
get: (params: NotesScreenParams, grouped?: boolean) => GroupedItems<Item>;
|
get: (
|
||||||
placeholderData: unknown;
|
params: NotesScreenParams,
|
||||||
|
grouped?: boolean
|
||||||
|
) => Promise<VirtualizedGrouping<Note> | Note[]>;
|
||||||
|
placeholder: PlaceholderData;
|
||||||
onPressFloatingButton: () => void;
|
onPressFloatingButton: () => void;
|
||||||
focusControl?: boolean;
|
focusControl?: boolean;
|
||||||
canGoBack?: boolean;
|
canGoBack?: boolean;
|
||||||
@@ -91,7 +102,7 @@ const NotesPage = ({
|
|||||||
route,
|
route,
|
||||||
navigation,
|
navigation,
|
||||||
get,
|
get,
|
||||||
placeholderData,
|
placeholder,
|
||||||
onPressFloatingButton,
|
onPressFloatingButton,
|
||||||
focusControl = true,
|
focusControl = true,
|
||||||
rightButtons
|
rightButtons
|
||||||
@@ -99,17 +110,19 @@ const NotesPage = ({
|
|||||||
"NotesPage" | "TaggedNotes" | "Monographs" | "ColoredNotes" | "TopicNotes"
|
"NotesPage" | "TaggedNotes" | "Monographs" | "ColoredNotes" | "TopicNotes"
|
||||||
>) => {
|
>) => {
|
||||||
const params = useRef<NotesScreenParams>(route?.params);
|
const params = useRef<NotesScreenParams>(route?.params);
|
||||||
const [notes, setNotes] = useState(get(route.params, true));
|
|
||||||
|
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
||||||
|
|
||||||
const loading = useNoteStore((state) => state.loading);
|
const loading = useNoteStore((state) => state.loading);
|
||||||
const [loadingNotes, setLoadingNotes] = useState(false);
|
const [loadingNotes, setLoadingNotes] = useState(true);
|
||||||
const isMonograph = route.name === "Monographs";
|
const isMonograph = route.name === "Monographs";
|
||||||
|
|
||||||
const notebook =
|
// const notebook =
|
||||||
route.name === "TopicNotes" &&
|
// route.name === "TopicNotes" &&
|
||||||
params.current.item.type === "topic" &&
|
// params.current.item.type === "topic" &&
|
||||||
params.current.item.notebookId
|
// params.current.item.notebookId
|
||||||
? db.notebooks?.notebook((params.current.item as Topic).notebookId)?.data
|
// ? db.notebooks?.notebook((params.current.item as Topic).notebookId)?.data
|
||||||
: null;
|
// : null;
|
||||||
|
|
||||||
const isFocused = useNavigationFocus(navigation, {
|
const isFocused = useNavigationFocus(navigation, {
|
||||||
onFocus: (prev) => {
|
onFocus: (prev) => {
|
||||||
@@ -176,7 +189,7 @@ const NotesPage = ({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const onRequestUpdate = React.useCallback(
|
const onRequestUpdate = React.useCallback(
|
||||||
(data?: NotesScreenParams) => {
|
async (data?: NotesScreenParams) => {
|
||||||
const isNew = data && data?.item?.id !== params.current?.item?.id;
|
const isNew = data && data?.item?.id !== params.current?.item?.id;
|
||||||
if (data) params.current = data;
|
if (data) params.current = data;
|
||||||
params.current.title =
|
params.current.title =
|
||||||
@@ -185,15 +198,19 @@ const NotesPage = ({
|
|||||||
const { item } = params.current;
|
const { item } = params.current;
|
||||||
try {
|
try {
|
||||||
if (isNew) setLoadingNotes(true);
|
if (isNew) setLoadingNotes(true);
|
||||||
const notes = get(params.current, true);
|
const notes = (await get(
|
||||||
|
params.current,
|
||||||
|
true
|
||||||
|
)) as VirtualizedGrouping<Note>;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
((item.type === "tag" || item.type === "color") &&
|
((item.type === "tag" || item.type === "color") &&
|
||||||
(!notes || notes.length === 0)) ||
|
(!notes || notes.ids.length === 0)) ||
|
||||||
(item.type === "topic" && !notes)
|
(item.type === "topic" && !notes)
|
||||||
) {
|
) {
|
||||||
return Navigation.goBack();
|
return Navigation.goBack();
|
||||||
}
|
}
|
||||||
if (notes.length === 0) setLoadingNotes(false);
|
if (notes.ids.length === 0) setLoadingNotes(false);
|
||||||
setNotes(notes);
|
setNotes(notes);
|
||||||
syncWithNavigation();
|
syncWithNavigation();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -204,10 +221,18 @@ const NotesPage = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loadingNotes) {
|
if (loadingNotes && !loading) {
|
||||||
setTimeout(() => setLoadingNotes(false), 50);
|
get(params.current, true)
|
||||||
|
.then((items) => {
|
||||||
|
setNotes(items as VirtualizedGrouping<Note>);
|
||||||
|
setLoadingNotes(false);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log("Error loading notes", params.current?.title, e, e.stack);
|
||||||
|
setLoadingNotes(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [loadingNotes, notes]);
|
}, [loadingNotes, loading, get]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSubscribeEvent(route.name, onRequestUpdate);
|
eSubscribeEvent(route.name, onRequestUpdate);
|
||||||
@@ -221,20 +246,18 @@ const NotesPage = ({
|
|||||||
<DelayLayout
|
<DelayLayout
|
||||||
color={
|
color={
|
||||||
route.name === "ColoredNotes"
|
route.name === "ColoredNotes"
|
||||||
? (params.current?.item as Color).title.toLowerCase()
|
? (params.current?.item as Color)?.colorCode
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
wait={loading || loadingNotes}
|
wait={loading || loadingNotes}
|
||||||
>
|
>
|
||||||
{route.name === "TopicNotes" ? (
|
{/* {route.name === "TopicNotes" ? (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center"
|
alignItems: "center"
|
||||||
// borderBottomWidth: 1,
|
|
||||||
// borderBottomColor: colors.secondary.background
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
@@ -266,27 +289,33 @@ const NotesPage = ({
|
|||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null} */}
|
||||||
<List
|
<List
|
||||||
listData={notes}
|
data={notes}
|
||||||
type="notes"
|
dataType="note"
|
||||||
refreshCallback={onRequestUpdate}
|
onRefresh={onRequestUpdate}
|
||||||
loading={loading || !isFocused}
|
loading={loading || !isFocused}
|
||||||
screen="Notes"
|
renderedInRoute="Notes"
|
||||||
headerProps={{
|
headerTitle={params.current.title}
|
||||||
heading: params.current.title,
|
customAccentColor={
|
||||||
color:
|
route.name === "ColoredNotes"
|
||||||
route.name === "ColoredNotes"
|
? (params.current?.item as Color)?.colorCode
|
||||||
? (params.current?.item as Color).title.toLowerCase()
|
: undefined
|
||||||
: null
|
}
|
||||||
}}
|
placeholder={placeholder}
|
||||||
placeholderData={placeholderData}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isMonograph &&
|
{!isMonograph &&
|
||||||
route.name !== "TopicNotes" &&
|
((notes?.ids && (notes?.ids?.length || 0) > 0) || isFocused) ? (
|
||||||
(notes?.length > 0 || isFocused) ? (
|
<FloatingButton
|
||||||
<FloatingButton title="Create a note" onPress={onPressFloatingButton} />
|
color={
|
||||||
|
route.name === "ColoredNotes"
|
||||||
|
? (params.current?.item as Color)?.colorCode
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
title="Create a note"
|
||||||
|
onPress={onPressFloatingButton}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</DelayLayout>
|
</DelayLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ 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 { groupArray } from "@notesnook/core/dist/utils/grouping";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import NotesPage, { PLACEHOLDER_DATA } from ".";
|
import NotesPage, { PLACEHOLDER_DATA } from ".";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
@@ -34,7 +33,7 @@ export const Monographs = ({
|
|||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
route={route}
|
route={route}
|
||||||
get={Monographs.get}
|
get={Monographs.get}
|
||||||
placeholderData={PLACEHOLDER_DATA}
|
placeholder={PLACEHOLDER_DATA}
|
||||||
onPressFloatingButton={openMonographsWebpage}
|
onPressFloatingButton={openMonographsWebpage}
|
||||||
canGoBack={route.params?.canGoBack}
|
canGoBack={route.params?.canGoBack}
|
||||||
focusControl={true}
|
focusControl={true}
|
||||||
@@ -42,11 +41,12 @@ export const Monographs = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Monographs.get = (params?: NotesScreenParams, grouped = true) => {
|
Monographs.get = async (params?: NotesScreenParams, grouped = true) => {
|
||||||
const notes = db.monographs?.all || [];
|
if (!grouped) {
|
||||||
return grouped
|
return await db.monographs.all.items();
|
||||||
? groupArray(notes, db.settings.getGroupOptions("notes"))
|
}
|
||||||
: notes;
|
|
||||||
|
return await db.monographs.all.grouped(db.settings.getGroupOptions("notes"));
|
||||||
};
|
};
|
||||||
|
|
||||||
Monographs.navigate = (item?: MonographType, canGoBack?: boolean) => {
|
Monographs.navigate = (item?: MonographType, canGoBack?: boolean) => {
|
||||||
|
|||||||
@@ -17,14 +17,13 @@ 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 { groupArray } from "@notesnook/core/dist/utils/grouping";
|
import { Tag } from "@notesnook/core/dist/types";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import NotesPage, { PLACEHOLDER_DATA } from ".";
|
import NotesPage, { PLACEHOLDER_DATA } from ".";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
||||||
import { openEditor } from "./common";
|
import { openEditor } from "./common";
|
||||||
import { Tag } from "@notesnook/core/dist/types";
|
|
||||||
export const TaggedNotes = ({
|
export const TaggedNotes = ({
|
||||||
navigation,
|
navigation,
|
||||||
route
|
route
|
||||||
@@ -34,7 +33,7 @@ export const TaggedNotes = ({
|
|||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
route={route}
|
route={route}
|
||||||
get={TaggedNotes.get}
|
get={TaggedNotes.get}
|
||||||
placeholderData={PLACEHOLDER_DATA}
|
placeholder={PLACEHOLDER_DATA}
|
||||||
onPressFloatingButton={openEditor}
|
onPressFloatingButton={openEditor}
|
||||||
canGoBack={route.params?.canGoBack}
|
canGoBack={route.params?.canGoBack}
|
||||||
focusControl={true}
|
focusControl={true}
|
||||||
@@ -42,11 +41,14 @@ export const TaggedNotes = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
TaggedNotes.get = (params: NotesScreenParams, grouped = true) => {
|
TaggedNotes.get = async (params: NotesScreenParams, grouped = true) => {
|
||||||
const notes = db.relations.from(params.item, "note").resolved();
|
if (!grouped) {
|
||||||
return grouped
|
return await db.relations.from(params.item, "note").resolve();
|
||||||
? groupArray(notes, db.settings.getGroupOptions("notes"))
|
}
|
||||||
: notes;
|
|
||||||
|
return await db.relations
|
||||||
|
.from(params.item, "note")
|
||||||
|
.selector.grouped(db.settings.getGroupOptions("notes"));
|
||||||
};
|
};
|
||||||
|
|
||||||
TaggedNotes.navigate = (item: Tag, canGoBack?: boolean) => {
|
TaggedNotes.navigate = (item: Tag, canGoBack?: boolean) => {
|
||||||
|
|||||||
@@ -23,10 +23,9 @@ import React from "react";
|
|||||||
import NotesPage, { PLACEHOLDER_DATA } from ".";
|
import NotesPage, { PLACEHOLDER_DATA } from ".";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import { MoveNotes } from "../../components/sheets/move-notes/movenote";
|
import { MoveNotes } from "../../components/sheets/move-notes/movenote";
|
||||||
import { eSendEvent } from "../../services/event-manager";
|
|
||||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
||||||
import { eOpenAddTopicDialog } from "../../utils/events";
|
|
||||||
import { openEditor } from "./common";
|
import { openEditor } from "./common";
|
||||||
|
|
||||||
const headerRightButtons = (params: NotesScreenParams) => [
|
const headerRightButtons = (params: NotesScreenParams) => [
|
||||||
@@ -35,10 +34,10 @@ const headerRightButtons = (params: NotesScreenParams) => [
|
|||||||
onPress: () => {
|
onPress: () => {
|
||||||
const { item } = params;
|
const { item } = params;
|
||||||
if (item.type !== "topic") return;
|
if (item.type !== "topic") return;
|
||||||
eSendEvent(eOpenAddTopicDialog, {
|
// eSendEvent(eOpenAddTopicDialog, {
|
||||||
notebookId: item.notebookId,
|
// notebookId: item.notebookId,
|
||||||
toEdit: item
|
// toEdit: item
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const prepareSearch = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PLACEHOLDER_DATA = {
|
const PLACEHOLDER_DATA = {
|
||||||
heading: "Your reminders",
|
title: "Your reminders",
|
||||||
paragraph: "You have not set any reminders yet.",
|
paragraph: "You have not set any reminders yet.",
|
||||||
button: "Set a new reminder",
|
button: "Set a new reminder",
|
||||||
action: () => {
|
action: () => {
|
||||||
@@ -76,14 +76,12 @@ export const Reminders = ({
|
|||||||
return (
|
return (
|
||||||
<DelayLayout>
|
<DelayLayout>
|
||||||
<List
|
<List
|
||||||
listData={reminders}
|
data={reminders}
|
||||||
type="reminders"
|
dataType="reminder"
|
||||||
headerProps={{
|
headerTitle="Reminders"
|
||||||
heading: "Reminders"
|
renderedInRoute="Reminders"
|
||||||
}}
|
|
||||||
loading={!isFocused}
|
loading={!isFocused}
|
||||||
screen="Reminders"
|
placeholder={PLACEHOLDER_DATA}
|
||||||
placeholderData={PLACEHOLDER_DATA}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FloatingButton
|
<FloatingButton
|
||||||
|
|||||||
@@ -36,13 +36,6 @@ const prepareSearch = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const PLACEHOLDER_DATA = {
|
|
||||||
heading: "Your tags",
|
|
||||||
paragraph: "You have not created any tags for your notes yet.",
|
|
||||||
button: null,
|
|
||||||
loading: "Loading your tags."
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Tags = ({ navigation, route }: NavigationProps<"Tags">) => {
|
export const Tags = ({ navigation, route }: NavigationProps<"Tags">) => {
|
||||||
const tags = useTagStore((state) => state.tags);
|
const tags = useTagStore((state) => state.tags);
|
||||||
const isFocused = useNavigationFocus(navigation, {
|
const isFocused = useNavigationFocus(navigation, {
|
||||||
@@ -65,14 +58,16 @@ export const Tags = ({ navigation, route }: NavigationProps<"Tags">) => {
|
|||||||
return (
|
return (
|
||||||
<DelayLayout>
|
<DelayLayout>
|
||||||
<List
|
<List
|
||||||
listData={tags}
|
data={tags}
|
||||||
type="tags"
|
dataType="tag"
|
||||||
headerProps={{
|
headerTitle="Tags"
|
||||||
heading: "Tags"
|
|
||||||
}}
|
|
||||||
loading={!isFocused}
|
loading={!isFocused}
|
||||||
screen="Tags"
|
renderedInRoute="Tags"
|
||||||
placeholderData={PLACEHOLDER_DATA}
|
placeholder={{
|
||||||
|
title: "Your tags",
|
||||||
|
paragraph: "You have not created any tags for your notes yet.",
|
||||||
|
loading: "Loading your tags."
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</DelayLayout>
|
</DelayLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const onPressFloatingButton = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
const PLACEHOLDER_DATA = (trashCleanupInterval = 7) => ({
|
const PLACEHOLDER_DATA = (trashCleanupInterval = 7) => ({
|
||||||
heading: "Trash",
|
title: "Trash",
|
||||||
paragraph:
|
paragraph:
|
||||||
trashCleanupInterval === -1
|
trashCleanupInterval === -1
|
||||||
? "Set automatic trash cleanup interval from Settings > Behaviour > Clean trash interval."
|
? "Set automatic trash cleanup interval from Settings > Behaviour > Clean trash interval."
|
||||||
@@ -70,7 +70,6 @@ const PLACEHOLDER_DATA = (trashCleanupInterval = 7) => ({
|
|||||||
? "daily."
|
? "daily."
|
||||||
: `after ${trashCleanupInterval} days.`
|
: `after ${trashCleanupInterval} days.`
|
||||||
}`,
|
}`,
|
||||||
button: null,
|
|
||||||
loading: "Loading trash items"
|
loading: "Loading trash items"
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -85,7 +84,10 @@ export const Trash = ({ navigation, route }: NavigationProps<"Trash">) => {
|
|||||||
useNavigationStore.getState().update({
|
useNavigationStore.getState().update({
|
||||||
name: route.name
|
name: route.name
|
||||||
});
|
});
|
||||||
if (useTrashStore.getState().trash.length === 0) {
|
if (
|
||||||
|
!useTrashStore.getState().trash ||
|
||||||
|
useTrashStore.getState().trash?.ids?.length === 0
|
||||||
|
) {
|
||||||
useTrashStore.getState().setTrash();
|
useTrashStore.getState().setTrash();
|
||||||
}
|
}
|
||||||
SearchService.prepareSearch = prepareSearch;
|
SearchService.prepareSearch = prepareSearch;
|
||||||
@@ -97,24 +99,15 @@ export const Trash = ({ navigation, route }: NavigationProps<"Trash">) => {
|
|||||||
return (
|
return (
|
||||||
<DelayLayout>
|
<DelayLayout>
|
||||||
<List
|
<List
|
||||||
listData={trash}
|
data={trash}
|
||||||
type="trash"
|
dataType="trash"
|
||||||
screen="Trash"
|
renderedInRoute="Trash"
|
||||||
loading={!isFocused}
|
loading={!isFocused}
|
||||||
placeholderData={PLACEHOLDER_DATA(
|
placeholder={PLACEHOLDER_DATA(db.settings.getTrashCleanupInterval())}
|
||||||
db.settings.getTrashCleanupInterval()
|
headerTitle="Trash"
|
||||||
)}
|
|
||||||
headerProps={{
|
|
||||||
heading: "Trash",
|
|
||||||
color: null
|
|
||||||
}}
|
|
||||||
// TODO: remove these once we have full typings everywhere
|
|
||||||
ListHeader={undefined}
|
|
||||||
refreshCallback={undefined}
|
|
||||||
warning={undefined}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{trash && trash.length !== 0 ? (
|
{trash && trash?.ids?.length !== 0 ? (
|
||||||
<FloatingButton
|
<FloatingButton
|
||||||
title="Clear all trash"
|
title="Clear all trash"
|
||||||
onPress={onPressFloatingButton}
|
onPress={onPressFloatingButton}
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ async function run(progress, context) {
|
|||||||
progress && eSendEvent(eCloseSheet);
|
progress && eSendEvent(eCloseSheet);
|
||||||
}
|
}
|
||||||
|
|
||||||
ToastEvent.show({
|
ToastManager.show({
|
||||||
heading: "Backup successful",
|
heading: "Backup successful",
|
||||||
message: "Your backup is stored in Notesnook folder on your phone.",
|
message: "Your backup is stored in Notesnook folder on your phone.",
|
||||||
type: "success",
|
type: "success",
|
||||||
@@ -236,7 +236,7 @@ async function run(progress, context) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
await sleep(300);
|
await sleep(300);
|
||||||
progress && eSendEvent(eCloseSheet);
|
progress && eSendEvent(eCloseSheet);
|
||||||
ToastEvent.error(e, "Backup failed!");
|
ToastManager.error(e, "Backup failed!");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,23 +50,6 @@ import { encodeNonAsciiHTML } from "entities";
|
|||||||
import { convertNoteToText } from "../utils/note-to-text";
|
import { convertNoteToText } from "../utils/note-to-text";
|
||||||
import { Reminder } from "@notesnook/core/dist/types";
|
import { Reminder } from "@notesnook/core/dist/types";
|
||||||
|
|
||||||
// export type Reminder = {
|
|
||||||
// id: string;
|
|
||||||
// type: string;
|
|
||||||
// title: string;
|
|
||||||
// description?: string;
|
|
||||||
// priority: "silent" | "vibrate" | "urgent";
|
|
||||||
// date: number;
|
|
||||||
// mode: "repeat" | "once" | "permanent";
|
|
||||||
// recurringMode?: "week" | "month" | "day";
|
|
||||||
// selectedDays?: number[];
|
|
||||||
// dateCreated: number;
|
|
||||||
// dateModified: number;
|
|
||||||
// localOnly?: boolean;
|
|
||||||
// snoozeUntil?: number;
|
|
||||||
// disabled?: boolean;
|
|
||||||
// };
|
|
||||||
|
|
||||||
let pinned: DisplayedNotification[] = [];
|
let pinned: DisplayedNotification[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,7 +107,9 @@ const onEvent = async ({ type, detail }: Event) => {
|
|||||||
const { notification, pressAction, input } = detail;
|
const { notification, pressAction, input } = detail;
|
||||||
if (type === EventType.DELIVERED && Platform.OS === "android") {
|
if (type === EventType.DELIVERED && Platform.OS === "android") {
|
||||||
if (notification?.id) {
|
if (notification?.id) {
|
||||||
const reminder = db.reminders?.reminder(notification?.id?.split("_")[0]);
|
const reminder = await db.reminders?.reminder(
|
||||||
|
notification?.id?.split("_")[0]
|
||||||
|
);
|
||||||
|
|
||||||
if (reminder && reminder.recurringMode === "month") {
|
if (reminder && reminder.recurringMode === "month") {
|
||||||
await initDatabase();
|
await initDatabase();
|
||||||
@@ -172,7 +157,7 @@ const onEvent = async ({ type, detail }: Event) => {
|
|||||||
case "REMINDER_SNOOZE": {
|
case "REMINDER_SNOOZE": {
|
||||||
await initDatabase();
|
await initDatabase();
|
||||||
if (!notification?.id) break;
|
if (!notification?.id) break;
|
||||||
const reminder = db.reminders?.reminder(
|
const reminder = await db.reminders?.reminder(
|
||||||
notification?.id?.split("_")[0]
|
notification?.id?.split("_")[0]
|
||||||
);
|
);
|
||||||
if (!reminder) break;
|
if (!reminder) break;
|
||||||
@@ -185,7 +170,7 @@ const onEvent = async ({ type, detail }: Event) => {
|
|||||||
snoozeUntil: Date.now() + reminderTime * 60000
|
snoozeUntil: Date.now() + reminderTime * 60000
|
||||||
});
|
});
|
||||||
await Notifications.scheduleNotification(
|
await Notifications.scheduleNotification(
|
||||||
db.reminders?.reminder(reminder?.id)
|
await db.reminders?.reminder(reminder?.id)
|
||||||
);
|
);
|
||||||
useRelationStore.getState().update();
|
useRelationStore.getState().update();
|
||||||
useReminderStore.getState().setReminders();
|
useReminderStore.getState().setReminders();
|
||||||
@@ -194,7 +179,7 @@ const onEvent = async ({ type, detail }: Event) => {
|
|||||||
case "REMINDER_DISABLE": {
|
case "REMINDER_DISABLE": {
|
||||||
await initDatabase();
|
await initDatabase();
|
||||||
if (!notification?.id) break;
|
if (!notification?.id) break;
|
||||||
const reminder = db.reminders?.reminder(
|
const reminder = await db.reminders?.reminder(
|
||||||
notification?.id?.split("_")[0]
|
notification?.id?.split("_")[0]
|
||||||
);
|
);
|
||||||
await db.reminders?.add({
|
await db.reminders?.add({
|
||||||
@@ -203,7 +188,7 @@ const onEvent = async ({ type, detail }: Event) => {
|
|||||||
});
|
});
|
||||||
if (!reminder?.id) break;
|
if (!reminder?.id) break;
|
||||||
await Notifications.scheduleNotification(
|
await Notifications.scheduleNotification(
|
||||||
db.reminders?.reminder(reminder?.id)
|
await db.reminders?.reminder(reminder?.id)
|
||||||
);
|
);
|
||||||
useRelationStore.getState().update();
|
useRelationStore.getState().update();
|
||||||
useReminderStore.getState().setReminders();
|
useReminderStore.getState().setReminders();
|
||||||
@@ -253,20 +238,7 @@ const onEvent = async ({ type, detail }: Event) => {
|
|||||||
const defaultNotebook = db.settings?.getDefaultNotebook();
|
const defaultNotebook = db.settings?.getDefaultNotebook();
|
||||||
|
|
||||||
if (defaultNotebook) {
|
if (defaultNotebook) {
|
||||||
if (!defaultNotebook.topic) {
|
await db.notes?.addToNotebook(defaultNotebook, id);
|
||||||
await db.relations?.add(
|
|
||||||
{ type: "notebook", id: defaultNotebook.id },
|
|
||||||
{ type: "note", id: id }
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await db.notes?.addToNotebook(
|
|
||||||
{
|
|
||||||
topic: defaultNotebook.topic,
|
|
||||||
id: defaultNotebook?.id
|
|
||||||
},
|
|
||||||
id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const status = await NetInfo.fetch();
|
const status = await NetInfo.fetch();
|
||||||
@@ -441,9 +413,9 @@ async function scheduleNotification(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadNote(id: string, jump: boolean) {
|
async function loadNote(id: string, jump: boolean) {
|
||||||
if (!id || id === "notesnook_note_input") return;
|
if (!id || id === "notesnook_note_input") return;
|
||||||
const note = db.notes?.note(id)?.data;
|
const note = await db.notes?.note(id);
|
||||||
if (!note) return;
|
if (!note) return;
|
||||||
if (!DDS.isTab && jump) {
|
if (!DDS.isTab && jump) {
|
||||||
tabBarRef.current?.goToPage(1);
|
tabBarRef.current?.goToPage(1);
|
||||||
@@ -871,7 +843,7 @@ async function pinQuickNote(launch: boolean) {
|
|||||||
* reschedules them if anything has changed.
|
* reschedules them if anything has changed.
|
||||||
*/
|
*/
|
||||||
async function setupReminders(checkNeedsScheduling = false) {
|
async function setupReminders(checkNeedsScheduling = false) {
|
||||||
const reminders = (db.reminders?.all as Reminder[]) || [];
|
const reminders = ((await db.reminders?.all.items()) as Reminder[]) || [];
|
||||||
const triggers = await notifee.getTriggerNotifications();
|
const triggers = await notifee.getTriggerNotifications();
|
||||||
|
|
||||||
for (const reminder of reminders) {
|
for (const reminder of reminders) {
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export class TipManager {
|
|||||||
export const useTip = (
|
export const useTip = (
|
||||||
context: Context,
|
context: Context,
|
||||||
fallback: Context,
|
fallback: Context,
|
||||||
options: {
|
options?: {
|
||||||
rotate: boolean;
|
rotate: boolean;
|
||||||
delay: number;
|
delay: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,38 +17,26 @@ 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 { GroupedItems, Note } from "@notesnook/core/dist/types";
|
|
||||||
import { groupArray } from "@notesnook/core/dist/utils/grouping";
|
|
||||||
import create, { State } from "zustand";
|
import create, { State } from "zustand";
|
||||||
import { db } from "../common/database";
|
import { db } from "../common/database";
|
||||||
|
import { Note, VirtualizedGrouping } from "@notesnook/core";
|
||||||
|
|
||||||
export interface FavoriteStore extends State {
|
export interface FavoriteStore extends State {
|
||||||
favorites: GroupedItems<Note>;
|
favorites: VirtualizedGrouping<Note> | undefined;
|
||||||
setFavorites: (items?: Note[]) => void;
|
setFavorites: (items?: Note[]) => void;
|
||||||
clearFavorites: () => void;
|
clearFavorites: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useFavoriteStore = create<FavoriteStore>((set, get) => ({
|
export const useFavoriteStore = create<FavoriteStore>((set) => ({
|
||||||
favorites: [],
|
favorites: undefined,
|
||||||
setFavorites: (items) => {
|
setFavorites: () => {
|
||||||
if (!items) {
|
db.notes.favorites
|
||||||
set({
|
.grouped(db.settings.getGroupOptions("favorites"))
|
||||||
favorites: groupArray(
|
.then((notes) => {
|
||||||
db.notes.favorites || [],
|
set({
|
||||||
db.settings.getGroupOptions("favorites")
|
favorites: notes
|
||||||
)
|
});
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
const prev = get().favorites;
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
const item = items[i];
|
|
||||||
const index = prev.findIndex((v) => v.id === item.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
prev[index] = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set({ favorites: prev });
|
|
||||||
},
|
},
|
||||||
clearFavorites: () => set({ favorites: [] })
|
clearFavorites: () => set({ favorites: undefined })
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ 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 { Color } from "@notesnook/core/dist/types";
|
import { Color, Notebook, Tag } from "@notesnook/core/dist/types";
|
||||||
import create, { State } from "zustand";
|
import create, { State } from "zustand";
|
||||||
import { db } from "../common/database";
|
import { db } from "../common/database";
|
||||||
|
|
||||||
export interface MenuStore extends State {
|
export interface MenuStore extends State {
|
||||||
menuPins: [];
|
menuPins: (Notebook | Tag)[];
|
||||||
colorNotes: Color[];
|
colorNotes: Color[];
|
||||||
setMenuPins: () => void;
|
setMenuPins: () => void;
|
||||||
setColorNotes: () => void;
|
setColorNotes: () => void;
|
||||||
@@ -33,18 +33,16 @@ export const useMenuStore = create<MenuStore>((set) => ({
|
|||||||
menuPins: [],
|
menuPins: [],
|
||||||
colorNotes: [],
|
colorNotes: [],
|
||||||
setMenuPins: () => {
|
setMenuPins: () => {
|
||||||
try {
|
db.shortcuts.resolved().then((shortcuts) => {
|
||||||
set({ menuPins: [...(db.shortcuts?.resolved as [])] });
|
set({ menuPins: [...(shortcuts as [])] });
|
||||||
} catch (e) {
|
});
|
||||||
setTimeout(() => {
|
},
|
||||||
try {
|
setColorNotes: () => {
|
||||||
set({ menuPins: [...(db.shortcuts?.resolved as [])] });
|
db.colors?.all.items().then((colors) => {
|
||||||
} catch (e) {
|
set({
|
||||||
console.error(e);
|
colorNotes: colors
|
||||||
}
|
});
|
||||||
}, 1000);
|
});
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setColorNotes: () => set({ colorNotes: db.colors?.all || [] }),
|
|
||||||
clearAll: () => set({ menuPins: [], colorNotes: [] })
|
clearAll: () => set({ menuPins: [], colorNotes: [] })
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export type Message = {
|
|||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
data: object;
|
data: object;
|
||||||
icon: string;
|
icon: string;
|
||||||
|
type?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Action = {
|
export type Action = {
|
||||||
@@ -93,7 +94,8 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
|
|||||||
actionText: null,
|
actionText: null,
|
||||||
onPress: () => null,
|
onPress: () => null,
|
||||||
data: {},
|
data: {},
|
||||||
icon: "account-outline"
|
icon: "account-outline",
|
||||||
|
type: ""
|
||||||
},
|
},
|
||||||
setMessage: (message) => {
|
setMessage: (message) => {
|
||||||
set({ message: { ...message } });
|
set({ message: { ...message } });
|
||||||
|
|||||||
@@ -17,38 +17,26 @@ 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 { groupArray } from "@notesnook/core/dist/utils/grouping";
|
|
||||||
import create, { State } from "zustand";
|
import create, { State } from "zustand";
|
||||||
import { db } from "../common/database";
|
import { db } from "../common/database";
|
||||||
import { GroupedItems, Notebook } from "@notesnook/core/dist/types";
|
import { VirtualizedGrouping, Notebook } from "@notesnook/core";
|
||||||
|
|
||||||
export interface NotebookStore extends State {
|
export interface NotebookStore extends State {
|
||||||
notebooks: GroupedItems<Notebook>;
|
notebooks: VirtualizedGrouping<Notebook> | undefined;
|
||||||
setNotebooks: (items?: Notebook[]) => void;
|
setNotebooks: (items?: Notebook[]) => void;
|
||||||
clearNotebooks: () => void;
|
clearNotebooks: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNotebookStore = create<NotebookStore>((set, get) => ({
|
export const useNotebookStore = create<NotebookStore>((set) => ({
|
||||||
notebooks: [],
|
notebooks: undefined,
|
||||||
setNotebooks: (items) => {
|
setNotebooks: () => {
|
||||||
if (!items) {
|
db.notebooks.roots
|
||||||
set({
|
.grouped(db.settings.getGroupOptions("notebooks"))
|
||||||
notebooks: groupArray(
|
.then((notebooks) => {
|
||||||
db.notebooks.all || [],
|
set({
|
||||||
db.settings.getGroupOptions("notebooks")
|
notebooks: notebooks
|
||||||
)
|
});
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
const prev = get().notebooks;
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
const item = items[i];
|
|
||||||
const index = prev.findIndex((v) => v.id === item.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
prev[index] = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set({ notebooks: prev });
|
|
||||||
},
|
},
|
||||||
clearNotebooks: () => set({ notebooks: [] })
|
clearNotebooks: () => set({ notebooks: undefined })
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -17,43 +17,28 @@ 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 { groupArray } from "@notesnook/core/dist/utils/grouping";
|
import { Note, VirtualizedGrouping } from "@notesnook/core";
|
||||||
import create, { State } from "zustand";
|
import create, { State } from "zustand";
|
||||||
import { db } from "../common/database";
|
import { db } from "../common/database";
|
||||||
import { GroupedItems, Note } from "@notesnook/core/dist/types";
|
|
||||||
|
|
||||||
export interface NoteStore extends State {
|
export interface NoteStore extends State {
|
||||||
notes: GroupedItems<Note>;
|
notes: VirtualizedGrouping<Note> | undefined;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
setLoading: (loading: boolean) => void;
|
setLoading: (loading: boolean) => void;
|
||||||
setNotes: (items?: Note[]) => void;
|
setNotes: () => void;
|
||||||
clearNotes: () => void;
|
clearNotes: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNoteStore = create<NoteStore>((set, get) => ({
|
export const useNoteStore = create<NoteStore>((set) => ({
|
||||||
notes: [],
|
notes: undefined,
|
||||||
loading: true,
|
loading: true,
|
||||||
setLoading: (loading) => set({ loading: loading }),
|
setLoading: (loading) => set({ loading: loading }),
|
||||||
|
setNotes: () => {
|
||||||
setNotes: (items) => {
|
db.notes.all.grouped(db.settings.getGroupOptions("home")).then((notes) => {
|
||||||
if (!items) {
|
|
||||||
set({
|
set({
|
||||||
notes: groupArray(
|
notes: notes
|
||||||
db.notes.all || [],
|
|
||||||
db.settings.getGroupOptions("home")
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
const prev = get().notes;
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
const item = items[i];
|
|
||||||
const index = prev.findIndex((v) => v.id === item.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
prev[index] = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set({ notes: prev });
|
|
||||||
},
|
},
|
||||||
clearNotes: () => set({ notes: [] })
|
clearNotes: () => set({ notes: undefined })
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -17,26 +17,26 @@ 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 { groupReminders } from "@notesnook/core/dist/utils/grouping";
|
|
||||||
import create, { State } from "zustand";
|
import create, { State } from "zustand";
|
||||||
import { db } from "../common/database";
|
import { db } from "../common/database";
|
||||||
import { GroupedItems, Reminder } from "@notesnook/core/dist/types";
|
import { Reminder, VirtualizedGrouping } from "@notesnook/core";
|
||||||
|
|
||||||
export interface ReminderStore extends State {
|
export interface ReminderStore extends State {
|
||||||
reminders: GroupedItems<Reminder>;
|
reminders: VirtualizedGrouping<Reminder> | undefined;
|
||||||
setReminders: (items?: Reminder[]) => void;
|
setReminders: (items?: Reminder[]) => void;
|
||||||
cleareReminders: () => void;
|
cleareReminders: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useReminderStore = create<ReminderStore>((set) => ({
|
export const useReminderStore = create<ReminderStore>((set) => ({
|
||||||
reminders: [],
|
reminders: undefined,
|
||||||
setReminders: () => {
|
setReminders: () => {
|
||||||
set({
|
db.reminders.all
|
||||||
reminders: groupReminders(
|
.grouped(db.settings.getGroupOptions("reminders"))
|
||||||
(db.reminders?.all as Reminder[]) || [],
|
.then((reminders) => {
|
||||||
db.settings?.getGroupOptions("reminders")
|
set({
|
||||||
)
|
reminders: reminders
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
cleareReminders: () => set({ reminders: [] })
|
cleareReminders: () => set({ reminders: undefined })
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -17,36 +17,24 @@ 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 "@notesnook/core/dist/types";
|
|
||||||
import { groupArray } from "@notesnook/core/dist/utils/grouping";
|
|
||||||
import create, { State } from "zustand";
|
import create, { State } from "zustand";
|
||||||
import { db } from "../common/database";
|
import { db } from "../common/database";
|
||||||
import { GroupedItems, Tag } from "@notesnook/core/dist/types";
|
import { Tag, VirtualizedGrouping } from "@notesnook/core";
|
||||||
|
|
||||||
export interface TagStore extends State {
|
export interface TagStore extends State {
|
||||||
tags: GroupedItems<Tag>;
|
tags: VirtualizedGrouping<Tag> | undefined;
|
||||||
setTags: (items?: Tag[]) => void;
|
setTags: (items?: Tag[]) => void;
|
||||||
clearTags: () => void;
|
clearTags: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTagStore = create<TagStore>((set, get) => ({
|
export const useTagStore = create<TagStore>((set) => ({
|
||||||
tags: [],
|
tags: undefined,
|
||||||
setTags: (items) => {
|
setTags: () => {
|
||||||
if (!items) {
|
db.tags.all.grouped(db.settings.getGroupOptions("tags")).then((tags) => {
|
||||||
set({
|
set({
|
||||||
tags: groupArray(db.tags.all || [], db.settings.getGroupOptions("tags"))
|
tags: tags
|
||||||
});
|
});
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
const prev = get().tags;
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
const item = items[i];
|
|
||||||
const index = prev.findIndex((v) => v.id === item.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
prev[index] = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set({ tags: prev });
|
|
||||||
},
|
},
|
||||||
clearTags: () => set({ tags: [] })
|
clearTags: () => set({ tags: undefined })
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -17,38 +17,25 @@ 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 { groupArray } from "@notesnook/core/dist/utils/grouping";
|
|
||||||
import create, { State } from "zustand";
|
import create, { State } from "zustand";
|
||||||
import { db } from "../common/database";
|
import { db } from "../common/database";
|
||||||
import { GroupedItems, TrashItem } from "@notesnook/core/dist/types";
|
import { GroupedItems, TrashItem } from "@notesnook/core/dist/types";
|
||||||
|
import { VirtualizedGrouping } from "@notesnook/core";
|
||||||
|
|
||||||
export interface TrashStore extends State {
|
export interface TrashStore extends State {
|
||||||
trash: GroupedItems<TrashItem>;
|
trash: VirtualizedGrouping<TrashItem> | undefined;
|
||||||
setTrash: (items?: GroupedItems<TrashItem>) => void;
|
setTrash: (items?: GroupedItems<TrashItem>) => void;
|
||||||
clearTrash: () => void;
|
clearTrash: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTrashStore = create<TrashStore>((set, get) => ({
|
export const useTrashStore = create<TrashStore>((set, get) => ({
|
||||||
trash: [],
|
trash: undefined,
|
||||||
setTrash: (items) => {
|
setTrash: () => {
|
||||||
if (!items) {
|
db.trash.grouped(db.settings.getGroupOptions("trash")).then((trash) => {
|
||||||
set({
|
set({
|
||||||
trash: groupArray(
|
trash: trash
|
||||||
(db.trash.all as TrashItem[]) || [],
|
|
||||||
db.settings.getGroupOptions("trash")
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
const prev = get().trash;
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
const item = items[i];
|
|
||||||
const index = prev.findIndex((v) => v.id === item.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
prev[index] = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set({ trash: prev });
|
|
||||||
},
|
},
|
||||||
clearTrash: () => set({ trash: [] })
|
clearTrash: () => set({ trash: undefined })
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -35,10 +35,6 @@ export const eOpenAddNotebookDialog = "507";
|
|||||||
|
|
||||||
export const eCloseAddNotebookDialog = "508";
|
export const eCloseAddNotebookDialog = "508";
|
||||||
|
|
||||||
export const eOpenAddTopicDialog = "509";
|
|
||||||
|
|
||||||
export const eCloseAddTopicDialog = "510";
|
|
||||||
|
|
||||||
export const eOpenLoginDialog = "511";
|
export const eOpenLoginDialog = "511";
|
||||||
|
|
||||||
export const eCloseLoginDialog = "512";
|
export const eCloseLoginDialog = "512";
|
||||||
@@ -159,8 +155,9 @@ export const eCloseAnnouncementDialog = "604";
|
|||||||
export const eOpenLoading = "605";
|
export const eOpenLoading = "605";
|
||||||
export const eCloseLoading = "606";
|
export const eCloseLoading = "606";
|
||||||
|
|
||||||
export const eOnTopicSheetUpdate = "607";
|
export const eOnNotebookUpdated = "607";
|
||||||
|
|
||||||
export const eUserLoggedIn = "608";
|
export const eUserLoggedIn = "608";
|
||||||
|
|
||||||
export const eLoginSessionExpired = "609";
|
export const eLoginSessionExpired = "609";
|
||||||
|
export const eDBItemUpdate = "610";
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ import SearchService from "../services/search";
|
|||||||
import { useMenuStore } from "../stores/use-menu-store";
|
import { useMenuStore } from "../stores/use-menu-store";
|
||||||
import { useRelationStore } from "../stores/use-relation-store";
|
import { useRelationStore } from "../stores/use-relation-store";
|
||||||
import { useSelectionStore } from "../stores/use-selection-store";
|
import { useSelectionStore } from "../stores/use-selection-store";
|
||||||
import { eClearEditor, eOnTopicSheetUpdate } from "./events";
|
import { eClearEditor, eOnNotebookUpdated } from "./events";
|
||||||
|
import { getParentNotebookId } from "./notebooks";
|
||||||
|
|
||||||
function confirmDeleteAllNotes(items, type, context) {
|
function confirmDeleteAllNotes(items, type, context) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@@ -57,6 +58,23 @@ function confirmDeleteAllNotes(items, type, context) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteNotebook(id, deleteNotes) {
|
||||||
|
const notebook = await db.notebooks.notebook(id);
|
||||||
|
const parentId = getParentNotebookId(id);
|
||||||
|
if (deleteNotes) {
|
||||||
|
const noteRelations = await db.relations.from(notebook, "note").get();
|
||||||
|
await db.notes.delete(...noteRelations.map((relation) => relation.toId));
|
||||||
|
}
|
||||||
|
const subnotebooks = await db.relations.from(notebook, "notebook").get();
|
||||||
|
for (const subnotebook of subnotebooks) {
|
||||||
|
await deleteNotebook(subnotebook.toId, deleteNotes);
|
||||||
|
}
|
||||||
|
await db.notebooks.remove(id);
|
||||||
|
if (parentId) {
|
||||||
|
eSendEvent(eOnNotebookUpdated, parentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const deleteItems = async (item, context) => {
|
export const deleteItems = async (item, context) => {
|
||||||
if (item && db.monographs.isPublished(item.id)) {
|
if (item && db.monographs.isPublished(item.id)) {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
@@ -68,14 +86,13 @@ export const deleteItems = async (item, context) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedItemsList = item
|
const itemsToDelete = item
|
||||||
? [item]
|
? [item]
|
||||||
: useSelectionStore.getState().selectedItemsList;
|
: useSelectionStore.getState().selectedItemsList;
|
||||||
|
|
||||||
let notes = selectedItemsList.filter((i) => i.type === "note");
|
let notes = itemsToDelete.filter((i) => i.type === "note");
|
||||||
let notebooks = selectedItemsList.filter((i) => i.type === "notebook");
|
let notebooks = itemsToDelete.filter((i) => i.type === "notebook");
|
||||||
let topics = selectedItemsList.filter((i) => i.type === "topic");
|
let reminders = itemsToDelete.filter((i) => i.type === "reminder");
|
||||||
let reminders = selectedItemsList.filter((i) => i.type === "reminder");
|
|
||||||
|
|
||||||
if (reminders.length > 0) {
|
if (reminders.length > 0) {
|
||||||
for (let reminder of reminders) {
|
for (let reminder of reminders) {
|
||||||
@@ -100,59 +117,20 @@ export const deleteItems = async (item, context) => {
|
|||||||
eSendEvent(eClearEditor);
|
eSendEvent(eClearEditor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topics?.length > 0) {
|
|
||||||
const result = await confirmDeleteAllNotes(topics, "topic", context);
|
|
||||||
if (!result.delete) return;
|
|
||||||
for (const topic of topics) {
|
|
||||||
if (result.deleteNotes) {
|
|
||||||
const notes = db.notebooks
|
|
||||||
.notebook(topic.notebookId)
|
|
||||||
.topics.topic(topic.id).all;
|
|
||||||
await db.notes.delete(...notes.map((note) => note.id));
|
|
||||||
}
|
|
||||||
await db.notebooks.notebook(topic.notebookId).topics.delete(topic.id);
|
|
||||||
}
|
|
||||||
useMenuStore.getState().setMenuPins();
|
|
||||||
ToastEvent.show({
|
|
||||||
heading: `${topics.length > 1 ? "Topics" : "Topic"} deleted`,
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notebooks?.length > 0) {
|
if (notebooks?.length > 0) {
|
||||||
const result = await confirmDeleteAllNotes(notebooks, "notebook", context);
|
const result = await confirmDeleteAllNotes(notebooks, "notebook", context);
|
||||||
if (!result.delete) return;
|
if (!result.delete) return;
|
||||||
let ids = notebooks.map((i) => i.id);
|
for (const notebook of notebooks) {
|
||||||
if (result.deleteNotes) {
|
await deleteNotebook(notebook.id, result.deleteNotes);
|
||||||
for (let id of ids) {
|
|
||||||
const notebook = db.notebooks.notebook(id);
|
|
||||||
const topics = notebook.topics.all;
|
|
||||||
for (let topic of topics) {
|
|
||||||
const notes = db.notebooks
|
|
||||||
.notebook(topic.notebookId)
|
|
||||||
.topics.topic(topic.id).all;
|
|
||||||
await db.notes.delete(...notes.map((note) => note.id));
|
|
||||||
}
|
|
||||||
const notes = db.relations.from(notebook.data, "note");
|
|
||||||
await db.notes.delete(...notes.map((note) => note.id));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
await db.notebooks.delete(...ids);
|
|
||||||
useMenuStore.getState().setMenuPins();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Navigation.queueRoutesForUpdate();
|
let message = `${itemsToDelete.length} ${
|
||||||
|
itemsToDelete.length === 1 ? "item" : "items"
|
||||||
let message = `${selectedItemsList.length} ${
|
|
||||||
selectedItemsList.length === 1 ? "item" : "items"
|
|
||||||
} moved to trash.`;
|
} moved to trash.`;
|
||||||
|
|
||||||
let deletedItems = [...selectedItemsList];
|
let deletedItems = [...itemsToDelete];
|
||||||
if (
|
if (reminders.length === 0 && (notes.length > 0 || notebooks.length > 0)) {
|
||||||
topics.length === 0 &&
|
|
||||||
reminders.length === 0 &&
|
|
||||||
(notes.length > 0 || notebooks.length > 0)
|
|
||||||
) {
|
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: message,
|
heading: message,
|
||||||
type: "success",
|
type: "success",
|
||||||
@@ -173,6 +151,7 @@ export const deleteItems = async (item, context) => {
|
|||||||
actionText: "Undo"
|
actionText: "Undo"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
if (!item) {
|
if (!item) {
|
||||||
useSelectionStore.getState().clearSelection();
|
useSelectionStore.getState().clearSelection();
|
||||||
@@ -180,7 +159,6 @@ export const deleteItems = async (item, context) => {
|
|||||||
useMenuStore.getState().setMenuPins();
|
useMenuStore.getState().setMenuPins();
|
||||||
useMenuStore.getState().setColorNotes();
|
useMenuStore.getState().setColorNotes();
|
||||||
SearchService.updateAndSearch();
|
SearchService.updateAndSearch();
|
||||||
eSendEvent(eOnTopicSheetUpdate);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const openLinkInBrowser = async (link) => {
|
export const openLinkInBrowser = async (link) => {
|
||||||
|
|||||||
32
apps/mobile/app/utils/notebooks.ts
Normal file
32
apps/mobile/app/utils/notebooks.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { db } from "../common/database";
|
||||||
|
|
||||||
|
export async function findRootNotebookId(id: string) {
|
||||||
|
const relation = await db.relations
|
||||||
|
.to(
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
type: "notebook"
|
||||||
|
},
|
||||||
|
"notebook"
|
||||||
|
)
|
||||||
|
.get();
|
||||||
|
if (!relation || !relation.length) {
|
||||||
|
return id;
|
||||||
|
} else {
|
||||||
|
return findRootNotebookId(relation[0].fromId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getParentNotebookId(id: string) {
|
||||||
|
const relation = await db.relations
|
||||||
|
.to(
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
type: "notebook"
|
||||||
|
},
|
||||||
|
"notebook"
|
||||||
|
)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
return relation?.[0]?.fromId;
|
||||||
|
}
|
||||||
@@ -47,4 +47,5 @@ hermesEnabled=true
|
|||||||
# v8.android.tools.dir=/home/ammarahm-ed/Repos/notesnook-mobile/node_modules/v8-android-jit-nointl/dist/tools/android
|
# v8.android.tools.dir=/home/ammarahm-ed/Repos/notesnook-mobile/node_modules/v8-android-jit-nointl/dist/tools/android
|
||||||
|
|
||||||
# fdroid
|
# fdroid
|
||||||
fdroidBuild=false
|
fdroidBuild=false
|
||||||
|
quickSqliteFlags=-DSQLITE_ENABLE_FTS5
|
||||||
@@ -6,7 +6,15 @@ const configs = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
'@babel/plugin-transform-named-capturing-groups-regex',
|
'@babel/plugin-transform-named-capturing-groups-regex',
|
||||||
'react-native-reanimated/plugin',
|
'react-native-reanimated/plugin',
|
||||||
"@babel/plugin-transform-export-namespace-from"
|
"@babel/plugin-transform-export-namespace-from",
|
||||||
|
],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
test: '../node_modules/kysely',
|
||||||
|
plugins: [
|
||||||
|
["@babel/plugin-transform-private-methods", { "loose": true }]
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
@@ -14,8 +22,16 @@ const configs = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
'@babel/plugin-transform-named-capturing-groups-regex',
|
'@babel/plugin-transform-named-capturing-groups-regex',
|
||||||
'react-native-reanimated/plugin',
|
'react-native-reanimated/plugin',
|
||||||
["@babel/plugin-transform-private-methods", { "loose": true }]
|
],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
test: '../node_modules/kysely',
|
||||||
|
plugins: [
|
||||||
|
["@babel/plugin-transform-private-methods", { "loose": true }]
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
},
|
},
|
||||||
production: {
|
production: {
|
||||||
presets: ['module:metro-react-native-babel-preset'],
|
presets: ['module:metro-react-native-babel-preset'],
|
||||||
@@ -23,7 +39,15 @@ const configs = {
|
|||||||
'transform-remove-console',
|
'transform-remove-console',
|
||||||
'@babel/plugin-transform-named-capturing-groups-regex',
|
'@babel/plugin-transform-named-capturing-groups-regex',
|
||||||
'react-native-reanimated/plugin',
|
'react-native-reanimated/plugin',
|
||||||
"@babel/plugin-transform-export-namespace-from"
|
"@babel/plugin-transform-export-namespace-from",
|
||||||
|
],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
test: '../node_modules/kysely',
|
||||||
|
plugins: [
|
||||||
|
["@babel/plugin-transform-private-methods", { "loose": true }]
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
import "./polyfills/console-time.js"
|
||||||
global.Buffer = require('buffer').Buffer;
|
global.Buffer = require('buffer').Buffer;
|
||||||
import '../app/common/logger/index';
|
import '../app/common/logger/index';
|
||||||
import { DOMParser } from './worker.js';
|
import { DOMParser } from './worker.js';
|
||||||
global.DOMParser = DOMParser;
|
global.DOMParser = DOMParser;
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,13 @@ post_install do |installer|
|
|||||||
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
if target.name == "react-native-quick-sqlite" then
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'SQLITE_ENABLE_FTS5=1'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle"
|
if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle"
|
||||||
target.build_configurations.each do |config|
|
target.build_configurations.each do |config|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ mergedConfig.resolver = {
|
|||||||
"react-dom": path.join(__dirname, "../node_modules/react-dom"),
|
"react-dom": path.join(__dirname, "../node_modules/react-dom"),
|
||||||
"@notesnook": path.join(__dirname, "../../../packages"),
|
"@notesnook": path.join(__dirname, "../../../packages"),
|
||||||
"@notifee/react-native": path.join(__dirname, "../node_modules/@ammarahmed/notifee-react-native"),
|
"@notifee/react-native": path.join(__dirname, "../node_modules/@ammarahmed/notifee-react-native"),
|
||||||
|
|
||||||
},
|
},
|
||||||
resolveRequest: (context, moduleName, platform) => {
|
resolveRequest: (context, moduleName, platform) => {
|
||||||
if (moduleName ==='react') {
|
if (moduleName ==='react') {
|
||||||
@@ -38,6 +39,14 @@ mergedConfig.resolver = {
|
|||||||
type: 'sourceFile',
|
type: 'sourceFile',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (moduleName ==='kysely') {
|
||||||
|
// Resolve react package from mobile app's node_modules folder always.
|
||||||
|
return {
|
||||||
|
filePath: path.resolve(path.join(__dirname, '../node_modules', "kysely","dist", "cjs", "index.js")),
|
||||||
|
type: 'sourceFile',
|
||||||
|
};
|
||||||
|
}
|
||||||
return context.resolveRequest(context, moduleName, platform);
|
return context.resolveRequest(context, moduleName, platform);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -62,7 +62,8 @@
|
|||||||
"react-native-tooltips": "^1.0.3",
|
"react-native-tooltips": "^1.0.3",
|
||||||
"react-native-vector-icons": "9.2.0",
|
"react-native-vector-icons": "9.2.0",
|
||||||
"react-native-webview": "^11.14.1",
|
"react-native-webview": "^11.14.1",
|
||||||
"react-native-zip-archive": "6.0.9"
|
"react-native-zip-archive": "6.0.9",
|
||||||
|
"react-native-quick-sqlite": "^8.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.0",
|
"@babel/core": "^7.20.0",
|
||||||
|
|||||||
50
apps/mobile/native/polyfills/console-time.js
Normal file
50
apps/mobile/native/polyfills/console-time.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
const PerformanceNow =
|
||||||
|
(global.performance && global.performance.now) ||
|
||||||
|
global.performanceNow ||
|
||||||
|
global.nativePerformanceNow || (() => { try {
|
||||||
|
var now = require('fbjs/lib/performanceNow')
|
||||||
|
} finally { return now }})();
|
||||||
|
|
||||||
|
const DEFAULT_LABEL = 'default';
|
||||||
|
const DEFAULT_PREC = 3;
|
||||||
|
|
||||||
|
let counts = {};
|
||||||
|
let startTimes = {};
|
||||||
|
|
||||||
|
const fixed = n => Math.trunc(n) === n ? n + '' : n.toFixed(DEFAULT_PREC);
|
||||||
|
|
||||||
|
console.time = console.time || ((label = DEFAULT_LABEL) => { startTimes[label] = PerformanceNow() });
|
||||||
|
console.timeLog = console.timeLog || ((label = DEFAULT_LABEL, desc) => timeRecord(label, desc));
|
||||||
|
console.timeEnd = console.timeEnd || ((label = DEFAULT_LABEL) => timeRecord(label, undefined, true));
|
||||||
|
|
||||||
|
console.count = console.count || ((label = DEFAULT_LABEL) => {
|
||||||
|
if (!counts[label]) {
|
||||||
|
counts[label] = 0;
|
||||||
|
}
|
||||||
|
counts[label]++;
|
||||||
|
console.log(`${label}: ${counts[label]}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.countReset = console.countReset || ((label = DEFAULT_LABEL) => {
|
||||||
|
if (counts[label]) {
|
||||||
|
counts[label] = 0;
|
||||||
|
} else {
|
||||||
|
console.warn(`Count for '${label}' does not exist`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function timeRecord(label, desc, final) {
|
||||||
|
const endTime = PerformanceNow();
|
||||||
|
const startTime = startTimes[label];
|
||||||
|
if (startTime) {
|
||||||
|
const delta = endTime - startTime;
|
||||||
|
if (desc) {
|
||||||
|
console.log(`${label}: ${fixed(delta)}ms ${desc}`);
|
||||||
|
} else {
|
||||||
|
console.log(`${label}: ${fixed(delta)}ms`);
|
||||||
|
}
|
||||||
|
if (final) delete startTimes[label];
|
||||||
|
} else {
|
||||||
|
console.warn(`Timer '${label}' does not exist`);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user