mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-31 11:08:47 +02:00
Compare commits
40 Commits
release/3.
...
3.3.25-and
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7bd06c9f3 | ||
|
|
766a8f28d4 | ||
|
|
86512b5ce7 | ||
|
|
a820fcde53 | ||
|
|
5ddf1eddf5 | ||
|
|
4237d2eb4a | ||
|
|
f67cd503d6 | ||
|
|
14039d6a1e | ||
|
|
d1fed9072e | ||
|
|
b003a3ffed | ||
|
|
55e10e4755 | ||
|
|
7c1dd0f79a | ||
|
|
229cf195a8 | ||
|
|
38da6ac08b | ||
|
|
597dc587cd | ||
|
|
5c76b92789 | ||
|
|
8418d5fa9c | ||
|
|
cd071f8f58 | ||
|
|
32ddc3fa40 | ||
|
|
68d07a7862 | ||
|
|
2aa5162fc9 | ||
|
|
cf5565d09d | ||
|
|
f129e0c411 | ||
|
|
bb43d80e05 | ||
|
|
4e4d3866df | ||
|
|
b2088ef782 | ||
|
|
07db14f6bf | ||
|
|
82d37701c5 | ||
|
|
b48ab3c85d | ||
|
|
bb7ff4f376 | ||
|
|
43b35917ef | ||
|
|
bcfa34ca05 | ||
|
|
bd3edc535a | ||
|
|
1dff7a121d | ||
|
|
3f3467e3a7 | ||
|
|
39bb9afa0e | ||
|
|
886e757715 | ||
|
|
5c93645969 | ||
|
|
07defdfa78 | ||
|
|
27b00d4ef9 |
4
apps/desktop/package-lock.json
generated
4
apps/desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.3.21",
|
||||
"version": "3.3.22",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.3.21",
|
||||
"version": "3.3.22",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.3.21",
|
||||
"version": "3.3.22",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/cjs/index.js",
|
||||
|
||||
@@ -84,35 +84,55 @@ const LANGUAGES: Record<string, string> = {
|
||||
|
||||
type Language = { code: string; name: string };
|
||||
|
||||
const LANGUAGE_REDIRECT_MAP: Record<string, string> = {
|
||||
es: "es-MX",
|
||||
"es-419": "es-MX",
|
||||
"es-ES": "es-AR"
|
||||
};
|
||||
|
||||
export const spellCheckerRouter = t.router({
|
||||
isEnabled: t.procedure.query(() => config.isSpellCheckerEnabled),
|
||||
languages: t.procedure.query(
|
||||
() =>
|
||||
<Language[]>(
|
||||
globalThis.window?.webContents.session.availableSpellCheckerLanguages.map(
|
||||
(code) => ({
|
||||
code,
|
||||
name: LANGUAGES[code]
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
enabledLanguages: t.procedure.query(
|
||||
() =>
|
||||
<Language[]>(
|
||||
globalThis.window?.webContents.session
|
||||
.getSpellCheckerLanguages()
|
||||
.map((code) => ({
|
||||
code,
|
||||
name: LANGUAGES[code]
|
||||
}))
|
||||
)
|
||||
),
|
||||
setLanguages: t.procedure
|
||||
.input(z.array(z.string()))
|
||||
.mutation(({ input: languages }) =>
|
||||
globalThis.window?.webContents.session.setSpellCheckerLanguages(languages)
|
||||
),
|
||||
languages: t.procedure.query(() => {
|
||||
const available =
|
||||
globalThis.window?.webContents.session.availableSpellCheckerLanguages ||
|
||||
[];
|
||||
|
||||
return <Language[]>available
|
||||
.map((code) => ({
|
||||
code,
|
||||
name: LANGUAGES[code] || code
|
||||
}))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}),
|
||||
|
||||
enabledLanguages: t.procedure.query(() => {
|
||||
const enabled =
|
||||
globalThis.window?.webContents.session.getSpellCheckerLanguages() || [];
|
||||
const available =
|
||||
globalThis.window?.webContents.session.availableSpellCheckerLanguages ||
|
||||
[];
|
||||
|
||||
const resolved = enabled
|
||||
.map((code) => resolveLanguage(code, available))
|
||||
.filter(Boolean) as string[];
|
||||
|
||||
return <Language[]>resolved.map((code) => ({
|
||||
code,
|
||||
name: LANGUAGES[code] || code
|
||||
}));
|
||||
}),
|
||||
|
||||
setLanguages: t.procedure.input(z.array(z.string())).mutation(({ input }) => {
|
||||
const available =
|
||||
globalThis.window?.webContents.session.availableSpellCheckerLanguages ||
|
||||
[];
|
||||
|
||||
const resolved = input
|
||||
.map((code) => resolveLanguage(code, available))
|
||||
.filter(Boolean) as string[];
|
||||
|
||||
globalThis.window?.webContents.session.setSpellCheckerLanguages(resolved);
|
||||
}),
|
||||
toggle: t.procedure
|
||||
.input(z.object({ enabled: z.boolean() }))
|
||||
.mutation(({ input: { enabled } }) => {
|
||||
@@ -128,3 +148,16 @@ export const spellCheckerRouter = t.router({
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
function resolveLanguage(code: string, available: string[]) {
|
||||
if (LANGUAGE_REDIRECT_MAP[code]) {
|
||||
const working = LANGUAGE_REDIRECT_MAP[code];
|
||||
return available.includes(working) ? working : code;
|
||||
}
|
||||
const fallback = code.split("-")[0];
|
||||
return available.includes(code)
|
||||
? code
|
||||
: available.includes(fallback)
|
||||
? fallback
|
||||
: undefined;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import {
|
||||
GroupHeader,
|
||||
GroupingByIdKey,
|
||||
GroupingKey,
|
||||
GroupOptions,
|
||||
ItemType
|
||||
@@ -46,6 +47,8 @@ type SectionHeaderProps = {
|
||||
screen?: RouteName;
|
||||
groupOptions: GroupOptions;
|
||||
group: GroupingKey;
|
||||
groupId?: string;
|
||||
type?: GroupingByIdKey;
|
||||
onOpenJumpToDialog: () => void;
|
||||
itemCount?: number;
|
||||
};
|
||||
@@ -62,7 +65,9 @@ export const SectionHeader = React.memo<
|
||||
groupOptions,
|
||||
group,
|
||||
onOpenJumpToDialog,
|
||||
itemCount
|
||||
itemCount,
|
||||
groupId,
|
||||
type
|
||||
}: SectionHeaderProps) {
|
||||
const { colors } = useThemeColors();
|
||||
const isCompactModeEnabled = useIsCompactModeEnabled(
|
||||
@@ -143,8 +148,10 @@ export const SectionHeader = React.memo<
|
||||
component: (
|
||||
<Sort
|
||||
screen={screen}
|
||||
type={dataType}
|
||||
dataType={dataType}
|
||||
type={type}
|
||||
group={group}
|
||||
groupId={groupId}
|
||||
hideGroupOptions={
|
||||
screen === "Reminders" || screen === "Search"
|
||||
}
|
||||
|
||||
@@ -17,7 +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/>.
|
||||
*/
|
||||
|
||||
import { GroupingKey, Item, VirtualizedGrouping } from "@notesnook/core";
|
||||
import {
|
||||
GroupingByIdKey,
|
||||
GroupingKey,
|
||||
Item,
|
||||
VirtualizedGrouping
|
||||
} from "@notesnook/core";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { LegendList, LegendListRenderItemProps } from "@legendapp/list";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
@@ -55,6 +60,7 @@ type ListProps = {
|
||||
placeholder?: PlaceholderData;
|
||||
groupType: GroupingKey;
|
||||
id?: string;
|
||||
type?: GroupingByIdKey;
|
||||
};
|
||||
|
||||
const onMomentumScrollEnd = () => {
|
||||
@@ -74,7 +80,7 @@ export default function List(props: ListProps) {
|
||||
props.dataType === "notebook" ||
|
||||
notebooksListMode === "compact";
|
||||
|
||||
const groupOptions = useGroupOptions(props.groupType);
|
||||
const groupOptions = useGroupOptions(props.groupType, props.id, props.type);
|
||||
|
||||
const _onRefresh = async () => {
|
||||
Sync.run("global", false, "full", () => {
|
||||
@@ -96,23 +102,27 @@ export default function List(props: ListProps) {
|
||||
index={itemProps.index}
|
||||
isSheet={props.isRenderedInActionSheet || false}
|
||||
items={props.data}
|
||||
groupId={props.id}
|
||||
groupOptions={groupOptions}
|
||||
group={props.groupType as GroupingKey}
|
||||
renderedInRoute={props.renderedInRoute}
|
||||
customAccentColor={props.customAccentColor}
|
||||
dataType={props.dataType}
|
||||
type={props.type}
|
||||
scrollRef={scrollRef}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[
|
||||
groupOptions,
|
||||
props.groupType,
|
||||
props.customAccentColor,
|
||||
props.data,
|
||||
props.dataType,
|
||||
props.isRenderedInActionSheet,
|
||||
props.renderedInRoute
|
||||
props.data,
|
||||
props.id,
|
||||
props.groupType,
|
||||
props.renderedInRoute,
|
||||
props.customAccentColor,
|
||||
props.dataType,
|
||||
groupOptions,
|
||||
props.type
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
Color,
|
||||
GroupHeader,
|
||||
GroupOptions,
|
||||
GroupingByIdKey,
|
||||
GroupingKey,
|
||||
HighlightedResult,
|
||||
Item,
|
||||
@@ -40,7 +41,7 @@ import {
|
||||
} from "@notesnook/core";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import { getGroupOptions } from "../../hooks/use-group-options";
|
||||
import { useIsCompactModeEnabled } from "../../hooks/use-is-compact-mode-enabled";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import { RouteName } from "../../stores/use-navigation-store";
|
||||
@@ -49,8 +50,8 @@ import { SectionHeader } from "../list-items/headers/section-header";
|
||||
import { NoteWrapper } from "../list-items/note/wrapper";
|
||||
import { NotebookWrapper } from "../list-items/notebook/wrapper";
|
||||
import ReminderItem from "../list-items/reminder";
|
||||
import TagItem from "../list-items/tag";
|
||||
import { SearchResult } from "../list-items/search-result";
|
||||
import TagItem from "../list-items/tag";
|
||||
|
||||
type ListItemWrapperProps<TItem = Item> = {
|
||||
group: GroupingKey;
|
||||
@@ -62,6 +63,8 @@ type ListItemWrapperProps<TItem = Item> = {
|
||||
dataType: string;
|
||||
scrollRef: any;
|
||||
groupOptions: GroupOptions;
|
||||
groupId?: string;
|
||||
type?: GroupingByIdKey;
|
||||
};
|
||||
|
||||
export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
@@ -183,6 +186,8 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
index={index}
|
||||
dataType={item.type}
|
||||
group={group}
|
||||
groupId={props.groupId}
|
||||
type={props.type}
|
||||
color={props.customAccentColor}
|
||||
groupOptions={groupOptions}
|
||||
onOpenJumpToDialog={() => {
|
||||
@@ -220,6 +225,8 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
index={index}
|
||||
dataType={item.type}
|
||||
group={group}
|
||||
groupId={props.groupId}
|
||||
type={props.type}
|
||||
color={props.customAccentColor}
|
||||
groupOptions={groupOptions}
|
||||
onOpenJumpToDialog={() => {
|
||||
@@ -250,6 +257,8 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
group={group}
|
||||
dataType={item.type}
|
||||
color={props.customAccentColor}
|
||||
type={props.type}
|
||||
groupId={props.groupId}
|
||||
groupOptions={groupOptions}
|
||||
onOpenJumpToDialog={() => {
|
||||
eSendEvent(eOpenJumpToDialog, {
|
||||
@@ -276,6 +285,8 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
index={index}
|
||||
group={group}
|
||||
dataType={item.type}
|
||||
groupId={props.groupId}
|
||||
type={props.type}
|
||||
color={props.customAccentColor}
|
||||
groupOptions={groupOptions}
|
||||
onOpenJumpToDialog={() => {
|
||||
@@ -303,6 +314,8 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
index={index}
|
||||
group={group}
|
||||
dataType={item.type}
|
||||
groupId={props.groupId}
|
||||
type={props.type}
|
||||
color={props.customAccentColor}
|
||||
groupOptions={groupOptions}
|
||||
itemCount={items?.placeholders.length}
|
||||
@@ -322,11 +335,16 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
}
|
||||
}
|
||||
|
||||
function getDate(item: Notebook | Note, groupType?: GroupingKey): number {
|
||||
function getDate(
|
||||
item: Notebook | Note,
|
||||
groupType?: GroupingKey,
|
||||
id?: string,
|
||||
type?: GroupingByIdKey
|
||||
): number {
|
||||
return (
|
||||
getSortValue(
|
||||
groupType
|
||||
? db.settings.getGroupOptions(groupType)
|
||||
? getGroupOptions(groupType, id, type)
|
||||
: {
|
||||
sortBy: "dateEdited",
|
||||
sortDirection: "desc"
|
||||
|
||||
@@ -45,12 +45,15 @@ import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Input from "../../ui/input";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { useAsync } from "react-async-hook";
|
||||
import { eMenuItemUpdate } from "../../../utils/events";
|
||||
import { useIsFeatureAvailable } from "@notesnook/common";
|
||||
import FormInput, {
|
||||
createFormRef,
|
||||
validators
|
||||
} from "../../ui/input/form-input";
|
||||
|
||||
async function fetchMonographData(noteId: string) {
|
||||
const monographId = db.monographs.monograph(noteId);
|
||||
@@ -76,26 +79,31 @@ const PublishNoteSheet = ({
|
||||
const isFeatureAvailable = useIsFeatureAvailable("monographAnalytics");
|
||||
const [isLocked, setIsLocked] = useState(false);
|
||||
const [publishing, setPublishing] = useState(false);
|
||||
const customTitle = useRef<string>("");
|
||||
const pwdInput = useRef<TextInput>(null);
|
||||
const titleInput = useRef<TextInput>(null);
|
||||
const passwordValue = useRef<string>(undefined);
|
||||
const monographData = useAsync(async () => {
|
||||
return fetchMonographData(note?.id);
|
||||
}, []);
|
||||
const monograph = monographData.result?.monograph;
|
||||
customTitle.current = monograph?.title || note.title || "";
|
||||
const publishUrl = monograph && `${hosts.MONOGRAPH_HOST}/${monograph?.id}`;
|
||||
const isPublished = db.monographs.monograph(note?.id);
|
||||
|
||||
const formRef = useRef(
|
||||
createFormRef({
|
||||
title: monograph?.title || note.title || "",
|
||||
password: ""
|
||||
})
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (monograph) {
|
||||
setSelfDestruct(!!monograph?.selfDestruct);
|
||||
if (monograph.password) {
|
||||
passwordValue.current = await db.monographs.decryptPassword(
|
||||
const password = await db.monographs.decryptPassword(
|
||||
monograph?.password
|
||||
);
|
||||
formRef.current.setValue("password", password);
|
||||
setIsLocked(!!monograph?.password);
|
||||
}
|
||||
}
|
||||
@@ -104,20 +112,23 @@ const PublishNoteSheet = ({
|
||||
|
||||
const publishNote = async () => {
|
||||
if (publishing) return;
|
||||
formRef.current.clearErrors();
|
||||
|
||||
if (!formRef.current.validate()) return;
|
||||
|
||||
const values = formRef.current.getValues();
|
||||
|
||||
setPublishLoading(true);
|
||||
|
||||
try {
|
||||
if (note?.id) {
|
||||
if (isLocked && !passwordValue.current) return;
|
||||
await db.monographs.publish(note.id, customTitle.current, {
|
||||
selfDestruct: selfDestruct,
|
||||
password: isLocked ? passwordValue.current : undefined
|
||||
await db.monographs.publish(note.id, values.title, {
|
||||
selfDestruct,
|
||||
password: isLocked ? values.password : undefined
|
||||
});
|
||||
|
||||
await monographData.execute();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
eSendEvent(eMenuItemUpdate);
|
||||
setPublishLoading(false);
|
||||
}
|
||||
requestInAppReview();
|
||||
} catch (e) {
|
||||
@@ -127,9 +138,9 @@ const PublishNoteSheet = ({
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
} finally {
|
||||
setPublishLoading(false);
|
||||
}
|
||||
|
||||
setPublishLoading(false);
|
||||
};
|
||||
const setPublishLoading = (value: boolean) => {
|
||||
setPublishing(value);
|
||||
@@ -247,11 +258,12 @@ const PublishNoteSheet = ({
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
|
||||
<Input
|
||||
<FormInput
|
||||
name="title"
|
||||
formRef={formRef}
|
||||
fwdRef={titleInput}
|
||||
onChangeText={(value) => (customTitle.current = value)}
|
||||
defaultValue={customTitle.current}
|
||||
placeholder={strings.noteTitle()}
|
||||
validators={[validators.required(strings.titleIsRequired())]}
|
||||
/>
|
||||
|
||||
<TouchableOpacity
|
||||
@@ -298,13 +310,16 @@ const PublishNoteSheet = ({
|
||||
|
||||
{isLocked ? (
|
||||
<>
|
||||
<Input
|
||||
<FormInput
|
||||
name="password"
|
||||
formRef={formRef}
|
||||
fwdRef={pwdInput}
|
||||
onChangeText={(value) => (passwordValue.current = value)}
|
||||
blurOnSubmit
|
||||
secureTextEntry
|
||||
defaultValue={passwordValue.current}
|
||||
placeholder={strings.enterPassword()}
|
||||
validators={[
|
||||
validators.required(strings.passwordRequired())
|
||||
]}
|
||||
containerStyle={{
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
|
||||
@@ -1,173 +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 { Item, ItemReference, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { RefObject, useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../../common/database";
|
||||
import {
|
||||
PresentSheetOptions,
|
||||
presentSheet
|
||||
} from "../../../services/event-manager";
|
||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||
import { AppFontSize } from "../../../utils/size";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import List from "../../list";
|
||||
import SheetProvider from "../../sheet-provider";
|
||||
import { Button, ButtonProps } from "../../ui/button";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
type RelationsListProps = {
|
||||
actionSheetRef: RefObject<ActionSheetRef>;
|
||||
close?: () => void;
|
||||
update?: (options: PresentSheetOptions) => void;
|
||||
item: { id: string; type: string };
|
||||
referenceType: string;
|
||||
relationType: "to" | "from";
|
||||
title: string;
|
||||
button?: ButtonProps;
|
||||
onAdd: () => void;
|
||||
};
|
||||
|
||||
const IconsByType = {
|
||||
reminder: "bell"
|
||||
};
|
||||
|
||||
export const RelationsList = ({
|
||||
actionSheetRef,
|
||||
item,
|
||||
referenceType,
|
||||
relationType,
|
||||
title,
|
||||
button,
|
||||
onAdd
|
||||
}: RelationsListProps) => {
|
||||
const updater = useRelationStore((state) => state.updater);
|
||||
const { colors } = useThemeColors();
|
||||
const [items, setItems] = useState<VirtualizedGrouping<Item>>();
|
||||
const hasNoRelations = !items || items?.placeholders?.length === 0;
|
||||
|
||||
useEffect(() => {
|
||||
db.relations?.[relationType]?.(
|
||||
{ id: item?.id, type: item?.type } as ItemReference,
|
||||
referenceType as any
|
||||
)
|
||||
.selector.sorted({
|
||||
sortBy: "dateEdited",
|
||||
sortDirection: "desc"
|
||||
})
|
||||
.then((grouped) => {
|
||||
setTimeout(() => {
|
||||
setItems(grouped);
|
||||
}, 300);
|
||||
});
|
||||
}, [relationType, referenceType, item?.id, item?.type, updater]);
|
||||
|
||||
return (
|
||||
<View style={{ paddingHorizontal: DefaultAppStyles.GAP, height: "100%" }}>
|
||||
<SheetProvider context="local" />
|
||||
<DialogHeader
|
||||
title={title}
|
||||
button={hasNoRelations ? undefined : button}
|
||||
/>
|
||||
{hasNoRelations ? (
|
||||
<View
|
||||
style={{
|
||||
height: "85%",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name={IconsByType[referenceType as keyof typeof IconsByType]}
|
||||
size={60}
|
||||
color={colors.primary.icon}
|
||||
/>
|
||||
<Paragraph>{strings.noLinksFound()}</Paragraph>
|
||||
<Button
|
||||
onPress={() => {
|
||||
onAdd?.();
|
||||
}}
|
||||
fontSize={AppFontSize.sm}
|
||||
// width="100%"
|
||||
type="inverted"
|
||||
icon="plus"
|
||||
title={strings.addItem(
|
||||
referenceType as "notebook" | "tag" | "reminder" | "note"
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<List
|
||||
data={items}
|
||||
loading={false}
|
||||
groupType={
|
||||
referenceType === "note"
|
||||
? "notes"
|
||||
: referenceType === "tag"
|
||||
? "tags"
|
||||
: referenceType === "notebook"
|
||||
? "notebooks"
|
||||
: referenceType === "reminder"
|
||||
? "reminders"
|
||||
: "notes"
|
||||
}
|
||||
dataType={referenceType as any}
|
||||
isRenderedInActionSheet={true}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
RelationsList.present = ({
|
||||
reference,
|
||||
referenceType,
|
||||
relationType,
|
||||
title,
|
||||
button,
|
||||
onAdd
|
||||
}: {
|
||||
reference: { id: string; type: string };
|
||||
referenceType: string;
|
||||
relationType: "to" | "from";
|
||||
title: string;
|
||||
button?: ButtonProps;
|
||||
onAdd: () => void;
|
||||
}) => {
|
||||
presentSheet({
|
||||
component: (ref, close, update) => (
|
||||
<RelationsList
|
||||
actionSheetRef={ref}
|
||||
close={close}
|
||||
update={update}
|
||||
item={reference}
|
||||
referenceType={referenceType}
|
||||
relationType={relationType}
|
||||
title={title}
|
||||
button={button}
|
||||
onAdd={onAdd}
|
||||
/>
|
||||
)
|
||||
});
|
||||
};
|
||||
@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
GroupingByIdKey,
|
||||
GroupingKey,
|
||||
GroupOptions,
|
||||
ItemType,
|
||||
@@ -27,7 +28,10 @@ import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { db } from "../../../common/database";
|
||||
import {
|
||||
getGroupOptions,
|
||||
setGroupOptionsById
|
||||
} from "../../../hooks/use-group-options";
|
||||
import { eSendEvent } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { RouteName } from "../../../stores/use-navigation-store";
|
||||
@@ -43,20 +47,24 @@ import { Pressable } from "../../ui/pressable";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
const Sort = ({
|
||||
type,
|
||||
dataType,
|
||||
screen,
|
||||
hideGroupOptions,
|
||||
group: groupType
|
||||
group: groupType,
|
||||
groupId,
|
||||
type
|
||||
}: {
|
||||
type: ItemType;
|
||||
dataType: ItemType;
|
||||
type?: GroupingByIdKey;
|
||||
screen?: RouteName;
|
||||
group: GroupingKey;
|
||||
hideGroupOptions?: boolean;
|
||||
groupId?: string;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
const [groupOptions, setGroupOptions] = useState(
|
||||
db.settings.getGroupOptions(groupType)
|
||||
getGroupOptions(groupType, groupId, type)
|
||||
);
|
||||
|
||||
const getSortButtonTitle = () => {
|
||||
@@ -79,16 +87,17 @@ const Sort = ({
|
||||
};
|
||||
|
||||
const updateGroupOptions = async (_groupOptions: GroupOptions) => {
|
||||
await db.settings.setGroupOptions(groupType, _groupOptions);
|
||||
console.log(groupId, type);
|
||||
setGroupOptionsById(groupType, _groupOptions, groupId, type);
|
||||
setGroupOptions(_groupOptions);
|
||||
setTimeout(() => {
|
||||
if (screen) Navigation.queueRoutesForUpdate(screen);
|
||||
if (type === "notebook") {
|
||||
if (dataType === "notebook") {
|
||||
useNotebookStore.getState().refresh();
|
||||
} else if (type === "tag") {
|
||||
} else if (dataType === "tag") {
|
||||
useTagStore.getState().refresh();
|
||||
}
|
||||
eSendEvent(eGroupOptionsUpdated, groupType);
|
||||
eSendEvent(eGroupOptionsUpdated, groupType, groupId, type);
|
||||
eSendEvent(refreshNotesPage);
|
||||
}, 1);
|
||||
};
|
||||
|
||||
@@ -493,7 +493,7 @@ const TabBar = (props: SimpleTabBarProps) => {
|
||||
presentSheet({
|
||||
component: (
|
||||
<Sort
|
||||
type={
|
||||
dataType={
|
||||
props.navigationState.index === 1
|
||||
? "notebook"
|
||||
: "tag"
|
||||
|
||||
@@ -45,11 +45,11 @@ import ExportNotesSheet from "../components/sheets/export-notes";
|
||||
import PaywallSheet from "../components/sheets/paywall";
|
||||
import PublishNoteSheet from "../components/sheets/publish-note";
|
||||
import { ReferencesList } from "../components/sheets/references";
|
||||
import { RelationsList } from "../components/sheets/relations-list/index";
|
||||
import { useSideBarDraggingStore } from "../components/side-menu/dragging-store";
|
||||
import { ButtonProps } from "../components/ui/button";
|
||||
import AddReminder from "../screens/add-reminder";
|
||||
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
|
||||
import RelationsList from "../screens/relations-list";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
@@ -784,6 +784,12 @@ export const useActions = ({
|
||||
const duplicateNote = async () => {
|
||||
await db.notes.duplicate(item.id);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
ToastManager.show({
|
||||
heading: strings.noteDuplicated(),
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
await sleep(500);
|
||||
close();
|
||||
};
|
||||
|
||||
@@ -1047,8 +1053,9 @@ export const useActions = ({
|
||||
title: strings.dataTypesPluralCamelCase.reminder(),
|
||||
icon: "clock-outline",
|
||||
onPress: async () => {
|
||||
close();
|
||||
RelationsList.present({
|
||||
reference: item,
|
||||
item,
|
||||
referenceType: "reminder",
|
||||
relationType: "from",
|
||||
title: strings.dataTypesPluralCamelCase.reminder(),
|
||||
@@ -1065,26 +1072,6 @@ export const useActions = ({
|
||||
}
|
||||
AddReminder.present(undefined, item);
|
||||
close();
|
||||
},
|
||||
button: {
|
||||
type: "plain",
|
||||
onPress: async () => {
|
||||
if (features && !features.activeReminders.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: features.activeReminders.error,
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(features.activeReminders);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
AddReminder.present(undefined, item);
|
||||
close();
|
||||
},
|
||||
icon: "plus",
|
||||
iconSize: 20
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -1128,8 +1115,9 @@ export const useActions = ({
|
||||
id: "add-reminder",
|
||||
title: strings.remindMe(),
|
||||
icon: "clock-plus-outline",
|
||||
onPress: () => {
|
||||
onPress: async () => {
|
||||
close();
|
||||
await sleep(100);
|
||||
AddReminder.present(undefined, item);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -16,28 +16,61 @@ 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 { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { db } from "../common/database";
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
|
||||
import Navigation from "../services/navigation";
|
||||
import { eGroupOptionsUpdated } from "../utils/events";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { GroupingByIdKey, GroupingKey, GroupOptions } from "@notesnook/core";
|
||||
|
||||
export function useGroupOptions(type: any) {
|
||||
export function getGroupOptions(
|
||||
groupingKey: GroupingKey,
|
||||
id?: string,
|
||||
type?: GroupingByIdKey
|
||||
) {
|
||||
return id && type
|
||||
? db.settings?.getGroupOptionsById(id, type)
|
||||
: db.settings?.getGroupOptions(groupingKey);
|
||||
}
|
||||
|
||||
export function setGroupOptionsById(
|
||||
groupingKey: GroupingKey,
|
||||
groupOptions: GroupOptions,
|
||||
id?: string,
|
||||
type?: GroupingByIdKey
|
||||
) {
|
||||
return id && type
|
||||
? db.settings?.setGroupOptionsById(id, type, groupOptions)
|
||||
: db.settings?.setGroupOptions(groupingKey, groupOptions);
|
||||
}
|
||||
|
||||
export function useGroupOptions(
|
||||
groupingKey: GroupingKey,
|
||||
id?: string,
|
||||
type?: GroupingByIdKey
|
||||
) {
|
||||
const appLoading = useSettingStore((state) => state.isAppLoading);
|
||||
const [groupOptions, setGroupOptions] = useState(
|
||||
db.settings?.getGroupOptions(type)
|
||||
getGroupOptions(groupingKey, id, type)
|
||||
);
|
||||
console.log(groupingKey, id, type, groupOptions, "options");
|
||||
const groupOptionsRef = useRef(groupOptions);
|
||||
groupOptionsRef.current = groupOptions;
|
||||
|
||||
useEffect(() => {
|
||||
const onUpdate = (groupType: string) => {
|
||||
if (groupType !== type) return;
|
||||
const options = db.settings?.getGroupOptions(type) as any;
|
||||
const onUpdate = (_groupingKey: string, _id?: string, _type?: string) => {
|
||||
if (_groupingKey !== groupingKey) return;
|
||||
if (_id && _type && _id !== id && _type !== type) return;
|
||||
|
||||
const options = getGroupOptions(groupingKey, id, type);
|
||||
if (!options) return;
|
||||
if (
|
||||
groupOptions?.groupBy !== options.groupBy ||
|
||||
groupOptions?.sortBy !== options.sortBy ||
|
||||
groupOptions?.sortDirection !== groupOptions?.sortDirection
|
||||
groupOptionsRef.current?.groupBy !== options.groupBy ||
|
||||
groupOptionsRef.current?.sortBy !== options.sortBy ||
|
||||
groupOptionsRef.current?.sortDirection !== options?.sortDirection
|
||||
) {
|
||||
console.log("onUpdate", _id, _type);
|
||||
setGroupOptions({ ...options });
|
||||
Navigation.queueRoutesForUpdate();
|
||||
}
|
||||
@@ -46,13 +79,13 @@ export function useGroupOptions(type: any) {
|
||||
eSubscribeEvent(eGroupOptionsUpdated, onUpdate);
|
||||
|
||||
if (!appLoading) {
|
||||
onUpdate(type);
|
||||
onUpdate(groupingKey);
|
||||
}
|
||||
|
||||
return () => {
|
||||
eUnSubscribeEvent(eGroupOptionsUpdated, onUpdate);
|
||||
};
|
||||
}, [type, groupOptions, appLoading]);
|
||||
}, [groupingKey, appLoading, id, type]);
|
||||
|
||||
return groupOptions;
|
||||
}
|
||||
|
||||
@@ -279,6 +279,7 @@ let MoveNotes: any = null;
|
||||
let Settings: any = null;
|
||||
let ManageTags: any = null;
|
||||
let AddReminder: any = null;
|
||||
let RelationsList: any = null;
|
||||
let PayWall: any = null;
|
||||
let Wrapped: any = null;
|
||||
export const RootNavigation = () => {
|
||||
@@ -385,6 +386,15 @@ export const RootNavigation = () => {
|
||||
return AddReminder;
|
||||
}}
|
||||
/>
|
||||
|
||||
<RootStack.Screen
|
||||
name="RelationsList"
|
||||
getComponent={() => {
|
||||
RelationsList =
|
||||
RelationsList || require("../screens/relations-list").default;
|
||||
return RelationsList;
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name="PayWall"
|
||||
getComponent={() => {
|
||||
|
||||
@@ -95,7 +95,17 @@ const ReminderNotificationModes = {
|
||||
|
||||
export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
const { reminder, reference } = props.route.params;
|
||||
useNavigationFocus(props.navigation, { focusOnInit: true });
|
||||
useNavigationFocus(props.navigation, {
|
||||
focusOnInit: true,
|
||||
onFocus: () => {
|
||||
if (!props.route.params?.reminder) {
|
||||
setTimeout(() => {
|
||||
titleRef.current?.focus();
|
||||
}, 200);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const { colors, isDark } = useThemeColors();
|
||||
const weekFormat = useSettingStore((state) => state.weekFormat);
|
||||
const [reminderMode, setReminderMode] = useState<Reminder["mode"]>(
|
||||
@@ -282,7 +292,6 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
defaultValue={reminder?.title || referencedItem?.title}
|
||||
placeholder={strings.remindeMeOf()}
|
||||
onChangeText={(text) => (title.current = text)}
|
||||
autoFocus
|
||||
wrapperStyle={{
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
|
||||
@@ -46,7 +46,6 @@ import EditorTabs from "../../../components/sheets/editor-tabs";
|
||||
import { Issue } from "../../../components/sheets/github/issue";
|
||||
import LinkNote from "../../../components/sheets/link-note";
|
||||
import PaywallSheet from "../../../components/sheets/paywall";
|
||||
import { RelationsList } from "../../../components/sheets/relations-list";
|
||||
import TableOfContents from "../../../components/sheets/toc";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import {
|
||||
@@ -80,6 +79,7 @@ import { fluidTabsRef } from "../../../utils/global-refs";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import AddReminder from "../../add-reminder";
|
||||
import ManageTags from "../../manage-tags";
|
||||
import RelationsList from "../../relations-list";
|
||||
import { useDragState } from "../../settings/editor/state";
|
||||
import { EditorMessage, EditorProps, useEditorType } from "./types";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
@@ -466,7 +466,7 @@ export const useEditorEvents = (
|
||||
const note = await db.notes.note(noteId);
|
||||
if (!note) return;
|
||||
RelationsList.present({
|
||||
reference: note as any,
|
||||
item: note as any,
|
||||
referenceType: "reminder",
|
||||
relationType: "from",
|
||||
title: strings.dataTypesPluralCamelCase.reminder(),
|
||||
|
||||
@@ -41,6 +41,7 @@ import { View } from "react-native";
|
||||
import { Notebooks } from "../../components/sheets/notebooks";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { rootNavigatorRef } from "../../utils/global-refs";
|
||||
import { getGroupOptions } from "../../hooks/use-group-options";
|
||||
|
||||
const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
||||
@@ -120,7 +121,9 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
params.current.id = notebook.id;
|
||||
const notes = await db.relations
|
||||
.from(notebook, "note")
|
||||
.selector.grouped(db.settings.getGroupOptions("notes"));
|
||||
.selector.grouped(
|
||||
getGroupOptions("notes", notebook.id, "notebook")
|
||||
);
|
||||
setNotes(notes);
|
||||
await notes.item(0, resolveItems);
|
||||
syncWithNavigation();
|
||||
@@ -190,6 +193,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
onRequestUpdate();
|
||||
}}
|
||||
id={params.current?.id}
|
||||
type="notebook"
|
||||
renderedInRoute="Notebook"
|
||||
headerTitle={notebook?.title}
|
||||
loading={loading}
|
||||
|
||||
@@ -26,6 +26,7 @@ import useNavigationStore, {
|
||||
NotesScreenParams
|
||||
} from "../../stores/use-navigation-store";
|
||||
import { PLACEHOLDER_DATA, openEditor } from "./common";
|
||||
import { getGroupOptions } from "../../hooks/use-group-options";
|
||||
export const ColoredNotes = ({
|
||||
navigation,
|
||||
route
|
||||
@@ -52,7 +53,7 @@ ColoredNotes.get = async (params: NotesScreenParams, grouped = true) => {
|
||||
|
||||
return await db.relations
|
||||
.from({ id: params.id, type: "color" }, "note")
|
||||
.selector.grouped(db.settings.getGroupOptions("notes"));
|
||||
.selector.grouped(getGroupOptions("notes", params.id, "color"));
|
||||
};
|
||||
|
||||
ColoredNotes.navigate = (item: Color, canGoBack: boolean) => {
|
||||
|
||||
@@ -18,8 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { resolveItems } from "@notesnook/common";
|
||||
import { Tag, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { Color, Note } from "@notesnook/core";
|
||||
import { Color, Note, Tag, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { db } from "../../common/database";
|
||||
import { FloatingButton } from "../../components/container/floating-button";
|
||||
@@ -39,10 +39,9 @@ import useNavigationStore, {
|
||||
NotesScreenParams,
|
||||
RouteName
|
||||
} from "../../stores/use-navigation-store";
|
||||
import { setOnFirstSave } from "./common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { rootNavigatorRef } from "../../utils/global-refs";
|
||||
import { setOnFirstSave } from "./common";
|
||||
|
||||
export interface RouteProps<T extends RouteName> extends NavigationProps<T> {
|
||||
get: (
|
||||
@@ -172,7 +171,7 @@ const NotesPage = ({
|
||||
if (loadingNotes) {
|
||||
onRequestUpdate(params.current);
|
||||
}
|
||||
}, [loadingNotes, get, isAppLoading]);
|
||||
}, [loadingNotes, get, isAppLoading, onRequestUpdate]);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(route.name, onRequestUpdate);
|
||||
@@ -222,6 +221,7 @@ const NotesPage = ({
|
||||
loading={false}
|
||||
renderedInRoute={route.name}
|
||||
id={params.current?.id}
|
||||
type={params.current?.item?.type}
|
||||
headerTitle={title || "Monographs"}
|
||||
customAccentColor={accentColor}
|
||||
placeholder={placeholder}
|
||||
|
||||
@@ -26,6 +26,7 @@ import useNavigationStore, {
|
||||
NotesScreenParams
|
||||
} from "../../stores/use-navigation-store";
|
||||
import { PLACEHOLDER_DATA, openEditor } from "./common";
|
||||
import { getGroupOptions } from "../../hooks/use-group-options";
|
||||
|
||||
export const TaggedNotes = ({
|
||||
navigation,
|
||||
@@ -53,7 +54,7 @@ TaggedNotes.get = async (params: NotesScreenParams, grouped = true) => {
|
||||
|
||||
return await db.relations
|
||||
.from({ id: params.id, type: "tag" }, "note")
|
||||
.selector.grouped(db.settings.getGroupOptions("notes"));
|
||||
.selector.grouped(getGroupOptions("notes", params.id, "tag"));
|
||||
};
|
||||
|
||||
TaggedNotes.navigate = (item: Tag, canGoBack?: boolean) => {
|
||||
|
||||
142
apps/mobile/app/screens/relations-list/index.tsx
Normal file
142
apps/mobile/app/screens/relations-list/index.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
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 { Item, ItemReference, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import { Header } from "../../components/header";
|
||||
import List from "../../components/list";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import { useRelationStore } from "../../stores/use-relation-store";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
|
||||
type RelationsListProps = {
|
||||
item: Item;
|
||||
referenceType: "notebook" | "tag" | "reminder" | "note";
|
||||
relationType: "to" | "from";
|
||||
title: string;
|
||||
onAdd?: () => void;
|
||||
};
|
||||
|
||||
const IconsByType = {
|
||||
reminder: "bell"
|
||||
};
|
||||
|
||||
function RelationsList(props: NavigationProps<"RelationsList">) {
|
||||
const { item, referenceType, relationType, title, onAdd } = props.route
|
||||
.params as RelationsListProps;
|
||||
const updater = useRelationStore((state) => state.updater);
|
||||
const { colors } = useThemeColors();
|
||||
const [items, setItems] = useState<VirtualizedGrouping<Item>>();
|
||||
const hasNoRelations = !items || items?.placeholders?.length === 0;
|
||||
|
||||
useEffect(() => {
|
||||
db.relations?.[relationType]?.(
|
||||
{ id: item?.id, type: item?.type } as ItemReference,
|
||||
referenceType
|
||||
)
|
||||
.selector.sorted({
|
||||
sortBy: "dateEdited",
|
||||
sortDirection: "desc"
|
||||
})
|
||||
.then((grouped) => {
|
||||
setTimeout(() => {
|
||||
setItems(grouped);
|
||||
}, 300);
|
||||
});
|
||||
}, [relationType, referenceType, item?.id, item?.type, updater]);
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: colors.primary.background,
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Header title={title} canGoBack />
|
||||
<View style={{ flex: 1 }}>
|
||||
{hasNoRelations ? (
|
||||
<View
|
||||
style={{
|
||||
height: "85%",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name={IconsByType[referenceType as keyof typeof IconsByType]}
|
||||
size={60}
|
||||
color={colors.primary.icon}
|
||||
/>
|
||||
<Paragraph>{strings.noLinksFound()}</Paragraph>
|
||||
<Button
|
||||
onPress={onAdd}
|
||||
fontSize={AppFontSize.sm}
|
||||
type="inverted"
|
||||
icon="plus"
|
||||
title={strings.addItem(referenceType)}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<List
|
||||
data={items}
|
||||
loading={false}
|
||||
groupType={
|
||||
referenceType === "note"
|
||||
? "notes"
|
||||
: referenceType === "tag"
|
||||
? "tags"
|
||||
: referenceType === "notebook"
|
||||
? "notebooks"
|
||||
: referenceType === "reminder"
|
||||
? "reminders"
|
||||
: "notes"
|
||||
}
|
||||
dataType={referenceType}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
RelationsList.present = ({
|
||||
item,
|
||||
relationType,
|
||||
referenceType,
|
||||
title,
|
||||
onAdd
|
||||
}: RelationsListProps) => {
|
||||
Navigation.navigate("RelationsList", {
|
||||
item,
|
||||
relationType,
|
||||
referenceType,
|
||||
title,
|
||||
onAdd
|
||||
});
|
||||
};
|
||||
|
||||
export default RelationsList;
|
||||
@@ -429,36 +429,61 @@ export const settingsGroups: SettingSection[] = [
|
||||
{
|
||||
id: "subscription-not-active",
|
||||
name: strings.subscriptionNotActivated(),
|
||||
hidden: () => Platform.OS !== "ios",
|
||||
useHook: () => useUserStore((state) => state.user),
|
||||
hidden: (user) =>
|
||||
Platform.OS !== "ios" ||
|
||||
(user as User)?.subscription?.plan !== SubscriptionPlan.FREE,
|
||||
modifer: async () => {
|
||||
if (Platform.OS === "android") return;
|
||||
presentSheet({
|
||||
title: strings.loadingSubscription(),
|
||||
paragraph: strings.loadingSubscriptionDesc()
|
||||
});
|
||||
const subscriptions = await RNIap.getPurchaseHistory();
|
||||
subscriptions.sort(
|
||||
(a, b) => b.transactionDate - a.transactionDate
|
||||
);
|
||||
const currentSubscription = subscriptions[0];
|
||||
presentSheet({
|
||||
title: strings.notesnookPro(),
|
||||
paragraph: strings.subscribedOnVerify(
|
||||
new Date(currentSubscription.transactionDate).toLocaleString()
|
||||
),
|
||||
action: async () => {
|
||||
presentSheet({
|
||||
title: strings.verifySubscription(),
|
||||
paragraph: strings.subscriptionVerifyWait()
|
||||
try {
|
||||
presentSheet({
|
||||
title: strings.loadingSubscription(),
|
||||
paragraph: strings.loadingSubscriptionDesc(),
|
||||
progress: true
|
||||
});
|
||||
const subscriptions = await RNIap.getPurchaseHistory();
|
||||
subscriptions.sort(
|
||||
(a, b) => b.transactionDate - a.transactionDate
|
||||
);
|
||||
const currentSubscription = subscriptions[0];
|
||||
|
||||
if (
|
||||
!currentSubscription ||
|
||||
dayjs(currentSubscription.transactionDate).isBefore(
|
||||
dayjs().subtract(30, "day")
|
||||
)
|
||||
) {
|
||||
ToastManager.show({
|
||||
message: "No active subscription found",
|
||||
type: "info"
|
||||
});
|
||||
await PremiumService.subscriptions.verify(
|
||||
currentSubscription
|
||||
);
|
||||
eSendEvent(eCloseSheet);
|
||||
},
|
||||
icon: "information-outline",
|
||||
actionText: strings.verify()
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
presentSheet({
|
||||
title: strings.notesnookPro(),
|
||||
paragraph: strings.subscribedOnVerify(
|
||||
new Date(
|
||||
currentSubscription.transactionDate
|
||||
).toLocaleString()
|
||||
),
|
||||
action: async () => {
|
||||
presentSheet({
|
||||
title: strings.verifySubscription(),
|
||||
paragraph: strings.subscriptionVerifyWait()
|
||||
});
|
||||
await PremiumService.subscriptions.verify(
|
||||
currentSubscription
|
||||
);
|
||||
eSendEvent(eCloseSheet);
|
||||
},
|
||||
icon: "information-outline",
|
||||
actionText: strings.verify()
|
||||
});
|
||||
} catch (e) {
|
||||
eSendEvent(eCloseSheet);
|
||||
}
|
||||
},
|
||||
description: strings.verifySubDesc()
|
||||
},
|
||||
|
||||
@@ -31,7 +31,11 @@ import type {
|
||||
ThemesRouter
|
||||
} from "@notesnook/themes-server";
|
||||
import { keepLocalCopy, pick } from "@react-native-documents/picker";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import {
|
||||
notifyManager,
|
||||
QueryClient,
|
||||
QueryClientProvider
|
||||
} from "@tanstack/react-query";
|
||||
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
|
||||
import { createTRPCReact } from "@trpc/react-query";
|
||||
import React, { useState } from "react";
|
||||
@@ -69,7 +73,8 @@ export const themeTrpcClient = createTRPCProxyClient<ThemesRouter>({
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
notifyManager.setBatchNotifyFunction((cb) => cb());
|
||||
notifyManager.setNotifyFunction((cb) => cb());
|
||||
function ThemeSelector() {
|
||||
const [darkTheme, lightTheme] = useThemeStore((state) => [
|
||||
state.darkTheme,
|
||||
@@ -80,30 +85,20 @@ function ThemeSelector() {
|
||||
const themeColors = colors;
|
||||
const [searchQuery, setSearchQuery] = useState<string>();
|
||||
const [colorScheme, setColorScheme] = useState<string>();
|
||||
|
||||
const filters = [];
|
||||
if (searchQuery) filters.push({ type: "term" as const, value: searchQuery });
|
||||
if (colorScheme)
|
||||
filters.push({ type: "colorScheme" as const, value: colorScheme });
|
||||
|
||||
const themes = trpc.themes.useInfiniteQuery(
|
||||
{
|
||||
limit: 10,
|
||||
compatibilityVersion: THEME_COMPATIBILITY_VERSION,
|
||||
filters: [
|
||||
...(searchQuery && searchQuery !== ""
|
||||
? [
|
||||
{
|
||||
type: "term" as const,
|
||||
value: searchQuery
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(colorScheme && colorScheme !== ""
|
||||
? [
|
||||
{
|
||||
type: "colorScheme" as const,
|
||||
value: colorScheme
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
filters
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor
|
||||
}
|
||||
);
|
||||
@@ -366,12 +361,14 @@ function ThemeSelector() {
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row"
|
||||
flexDirection: "row",
|
||||
gap: DefaultAppStyles.GAP_SMALL
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
style={{
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
|
||||
paddingHorizontal: DefaultAppStyles.GAP_SMALL
|
||||
}}
|
||||
type={
|
||||
colorScheme === "" || !colorScheme ? "accent" : "secondary"
|
||||
@@ -384,7 +381,8 @@ function ThemeSelector() {
|
||||
/>
|
||||
<Button
|
||||
style={{
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
|
||||
paddingHorizontal: DefaultAppStyles.GAP_SMALL
|
||||
}}
|
||||
type={colorScheme === "dark" ? "accent" : "secondary"}
|
||||
title={strings.dark()}
|
||||
@@ -395,7 +393,8 @@ function ThemeSelector() {
|
||||
/>
|
||||
<Button
|
||||
style={{
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
|
||||
paddingHorizontal: DefaultAppStyles.GAP_SMALL
|
||||
}}
|
||||
fontSize={AppFontSize.xs}
|
||||
type={colorScheme === "light" ? "accent" : "secondary"}
|
||||
@@ -409,7 +408,8 @@ function ThemeSelector() {
|
||||
<Button
|
||||
title={strings.loadFromFile()}
|
||||
style={{
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
|
||||
paddingHorizontal: DefaultAppStyles.GAP_SMALL
|
||||
}}
|
||||
type={"secondaryAccented"}
|
||||
icon="folder"
|
||||
|
||||
@@ -71,6 +71,7 @@ const routeNames = {
|
||||
Archive: "Archive",
|
||||
ManageTags: "ManageTags",
|
||||
AddReminder: "AddReminder",
|
||||
RelationsList: "RelationsList",
|
||||
PayWall: "PayWall",
|
||||
Wrapped: "Wrapped"
|
||||
};
|
||||
|
||||
@@ -108,6 +108,13 @@ export interface RouteParams extends ParamListBase {
|
||||
reminder?: Reminder;
|
||||
reference?: Note;
|
||||
};
|
||||
RelationsList: {
|
||||
item: Item;
|
||||
referenceType: "notebook" | "tag" | "reminder" | "note";
|
||||
relationType: "to" | "from";
|
||||
title: string;
|
||||
onAdd?: () => void;
|
||||
};
|
||||
Intro: GenericRouteParam;
|
||||
PayWall: {
|
||||
canGoBack?: boolean;
|
||||
|
||||
30
apps/mobile/package-lock.json
generated
30
apps/mobile/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.3.23",
|
||||
"version": "3.3.24",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.3.23",
|
||||
"version": "3.3.24",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
@@ -51,10 +51,10 @@
|
||||
"@sayem314/react-native-keep-awake": "^1.3.1",
|
||||
"@streetwriters/kysely": "^0.27.4",
|
||||
"@streetwriters/showdown": "^3.0.1-alpha.2",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@trpc/client": "^10.45.2",
|
||||
"@trpc/react-query": "^10.45.2",
|
||||
"@trpc/server": "^10.45.2",
|
||||
"@tanstack/react-query": "^4.18.0",
|
||||
"@trpc/client": "10.45.2",
|
||||
"@trpc/react-query": "10.45.2",
|
||||
"@trpc/server": "10.45.2",
|
||||
"@types/validator": "^13.12.2",
|
||||
"absolutify": "^0.1.0",
|
||||
"async-mutex": "0.5.0",
|
||||
@@ -5634,9 +5634,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.41.0.tgz",
|
||||
"integrity": "sha512-193R4Jp9hjvlij6LryxrB5Mpbffd2L9PeWh3KlIy/hJV4SkBOfiQZ+jc5qAZLDCrdbkA5FjGj+UoDYw6TcNnyA==",
|
||||
"version": "4.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.18.0.tgz",
|
||||
"integrity": "sha512-PP4mG8MD08sq64RZCqMfXMYfaj7+Oulwg7xZ/fJoEOdTZNcPIgaOkHajZvUBsNLbi/0ViMvJB4cFkL2Jg2WPbw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -5644,12 +5644,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "4.42.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.42.0.tgz",
|
||||
"integrity": "sha512-j0tiofkzE3CSrYKmVRaKuwGgvCE+P2OOEDlhmfjeZf5ufcuFHwYwwgw3j08n4WYPVZ+OpsHblcFYezhKA3jDwg==",
|
||||
"version": "4.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.18.0.tgz",
|
||||
"integrity": "sha512-s1kdbGMdVcfUIllzsHUqVUdktBT5uuIRgnvrqFNLjl9TSOXEoBSDrhjsGjao0INQZv8cMpQlgOh3YH9YtN6cKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "4.41.0",
|
||||
"@tanstack/query-core": "4.18.0",
|
||||
"use-sync-external-store": "^1.2.0"
|
||||
},
|
||||
"funding": {
|
||||
@@ -5657,8 +5657,8 @@
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-native": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
|
||||
@@ -69,10 +69,10 @@
|
||||
"@sayem314/react-native-keep-awake": "^1.3.1",
|
||||
"@streetwriters/kysely": "^0.27.4",
|
||||
"@streetwriters/showdown": "^3.0.1-alpha.2",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@trpc/client": "^10.45.2",
|
||||
"@trpc/react-query": "^10.45.2",
|
||||
"@trpc/server": "^10.45.2",
|
||||
"@tanstack/react-query": "^4.18.0",
|
||||
"@trpc/client": "10.45.2",
|
||||
"@trpc/react-query": "10.45.2",
|
||||
"@trpc/server": "10.45.2",
|
||||
"@types/validator": "^13.12.2",
|
||||
"absolutify": "^0.1.0",
|
||||
"async-mutex": "0.5.0",
|
||||
|
||||
4
apps/web/package-lock.json
generated
4
apps/web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.3.21",
|
||||
"version": "3.3.22",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.3.21",
|
||||
"version": "3.3.22",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.3.21",
|
||||
"version": "3.3.22",
|
||||
"private": true,
|
||||
"main": "./src/app.js",
|
||||
"homepage": "https://notesnook.com/",
|
||||
|
||||
@@ -46,7 +46,21 @@ export function DayPicker(props: DayPickerProps) {
|
||||
maxDate,
|
||||
minDate
|
||||
},
|
||||
years: { numberOfYears: 99 },
|
||||
years: {
|
||||
/**
|
||||
* When minDate and maxDate are provided, we switch to "fluid" mode and calculate the number of years in a way that all valid years are visible.
|
||||
*
|
||||
* When not provided, use default mode and numberOfYears.
|
||||
*/
|
||||
numberOfYears:
|
||||
minDate && maxDate
|
||||
? Math.max(
|
||||
Math.abs(maxDate.getFullYear() - selected.getFullYear()) + 1,
|
||||
Math.abs(selected.getFullYear() - minDate.getFullYear()) + 1
|
||||
) * 2
|
||||
: 99,
|
||||
mode: minDate && maxDate ? "fluid" : "decade"
|
||||
},
|
||||
calendar: {
|
||||
startDay: 0
|
||||
}
|
||||
@@ -86,7 +100,10 @@ export function DayPicker(props: DayPickerProps) {
|
||||
<Button
|
||||
variant="icon"
|
||||
sx={{ p: 0 }}
|
||||
{...subtractOffset({ months: 1 }, { disabled: isPrevMonthBeforeMin })}
|
||||
{...subtractOffset(
|
||||
{ months: 1 },
|
||||
{ disabled: isPrevMonthBeforeMin }
|
||||
)}
|
||||
>
|
||||
<ChevronLeft />
|
||||
</Button>
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
Lock,
|
||||
NewTab,
|
||||
Note,
|
||||
NoteAdd,
|
||||
NoteRemove,
|
||||
Pin,
|
||||
Plus,
|
||||
@@ -98,7 +97,6 @@ type ToolButton = {
|
||||
export function EditorActionBar() {
|
||||
const { isMaximized, isFullscreen, hasNativeWindowControls } =
|
||||
useWindowControls();
|
||||
const isFocusMode = useAppStore((store) => store.isFocusMode);
|
||||
const activeTab = useEditorStore((store) => store.getActiveTab());
|
||||
const activeSession = useEditorStore((store) =>
|
||||
activeTab ? store.getSession(activeTab.sessionId) : undefined
|
||||
@@ -187,8 +185,7 @@ export function EditorActionBar() {
|
||||
activeSession &&
|
||||
activeSession.type !== "new" &&
|
||||
activeSession.type !== "locked" &&
|
||||
activeSession.type !== "conflicted" &&
|
||||
!isFocusMode,
|
||||
activeSession.type !== "conflicted",
|
||||
onClick: () => useEditorStore.getState().toggleProperties(),
|
||||
toggled: arePropertiesVisible
|
||||
},
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
} from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useStore as useSearchStore } from "../../stores/search-store";
|
||||
import type { Context } from "../list-container/types";
|
||||
|
||||
const groupByToTitleMap = {
|
||||
none: "None",
|
||||
@@ -61,6 +62,7 @@ type GroupingMenuOptions = {
|
||||
groupingKey: GroupingKey;
|
||||
refresh: () => void;
|
||||
isSearching?: boolean;
|
||||
context?: Context;
|
||||
};
|
||||
|
||||
const groupByMenu: (options: GroupingMenuOptions) => MenuItem | null = (
|
||||
@@ -192,6 +194,39 @@ export function showSortMenu(groupingKey: GroupingKey, refresh: () => void) {
|
||||
);
|
||||
}
|
||||
|
||||
function getGroupOptions(
|
||||
context: Context | undefined,
|
||||
isSearching: boolean | undefined,
|
||||
groupingKey: GroupingKey
|
||||
): GroupOptions {
|
||||
return isSearching
|
||||
? db.settings.getGroupOptions("search")
|
||||
: context?.type === "notebook" ||
|
||||
context?.type === "tag" ||
|
||||
context?.type === "color"
|
||||
? db.settings.getGroupOptionsById(context.id, context.type)
|
||||
: db.settings.getGroupOptions(groupingKey);
|
||||
}
|
||||
|
||||
async function setGroupOptions(
|
||||
options: GroupingMenuOptions,
|
||||
groupOptions: GroupOptions
|
||||
) {
|
||||
if (
|
||||
options.context?.type === "notebook" ||
|
||||
options.context?.type === "tag" ||
|
||||
options.context?.type === "color"
|
||||
) {
|
||||
await db.settings.setGroupOptionsById(
|
||||
options.context.id,
|
||||
options.context.type,
|
||||
groupOptions
|
||||
);
|
||||
} else {
|
||||
await db.settings.setGroupOptions(options.groupingKey, groupOptions);
|
||||
}
|
||||
}
|
||||
|
||||
async function changeGroupOptions(
|
||||
options: GroupingMenuOptions,
|
||||
item: Omit<MenuButtonItem, "type">
|
||||
@@ -207,7 +242,9 @@ async function changeGroupOptions(
|
||||
? "dateModified"
|
||||
: groupOptions.sortBy;
|
||||
}
|
||||
await db.settings.setGroupOptions(options.groupingKey, groupOptions);
|
||||
|
||||
await setGroupOptions(options, groupOptions);
|
||||
|
||||
if (options.groupingKey === "search")
|
||||
useSearchStore.setState({ sortOptions: groupOptions });
|
||||
options.refresh();
|
||||
@@ -235,6 +272,7 @@ type GroupHeaderProps = {
|
||||
onSelectGroup: () => void;
|
||||
isFocused: boolean;
|
||||
isSearching?: boolean;
|
||||
context?: Context;
|
||||
};
|
||||
function GroupHeader(props: GroupHeaderProps) {
|
||||
const {
|
||||
@@ -246,10 +284,12 @@ function GroupHeader(props: GroupHeaderProps) {
|
||||
refresh,
|
||||
onSelectGroup,
|
||||
isFocused,
|
||||
isSearching
|
||||
isSearching,
|
||||
context
|
||||
} = props;
|
||||
|
||||
const [groupOptions, setGroupOptions] = useState(
|
||||
db.settings.getGroupOptions(isSearching ? "search" : groupingKey)
|
||||
getGroupOptions(context, isSearching, groupingKey)
|
||||
);
|
||||
const groupHeaderRef = useRef<HTMLDivElement>(null);
|
||||
const { openMenu, target } = useMenuTrigger();
|
||||
@@ -359,8 +399,10 @@ function GroupHeader(props: GroupHeaderProps) {
|
||||
groupByToTitleMap[groupOptions.groupBy || "default"]
|
||||
}`}
|
||||
onClick={() => {
|
||||
const groupOptions = db.settings.getGroupOptions(
|
||||
isSearching ? "search" : groupingKey
|
||||
const groupOptions = getGroupOptions(
|
||||
context,
|
||||
isSearching,
|
||||
groupingKey
|
||||
);
|
||||
setGroupOptions(groupOptions);
|
||||
|
||||
@@ -368,7 +410,8 @@ function GroupHeader(props: GroupHeaderProps) {
|
||||
groupingKey: isSearching ? "search" : groupingKey,
|
||||
groupOptions,
|
||||
refresh,
|
||||
isSearching
|
||||
isSearching,
|
||||
context
|
||||
};
|
||||
const groupBy = groupByMenu({
|
||||
...menuOptions,
|
||||
|
||||
@@ -370,6 +370,7 @@ function ItemRenderer({
|
||||
title={resolvedItem.group.title}
|
||||
isFocused={index === focusedGroupIndex}
|
||||
index={index}
|
||||
context={itemContext}
|
||||
onSelectGroup={async () => {
|
||||
if (!items.groups) return;
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ import {
|
||||
DefaultEditorSession
|
||||
} from "../../stores/editor-store";
|
||||
import { db } from "../../common/db";
|
||||
import { useStore as useAppStore } from "../../stores/app-store";
|
||||
import { useStore as useAttachmentStore } from "../../stores/attachment-store";
|
||||
import { store as noteStore } from "../../stores/note-store";
|
||||
import Toggle from "./toggle";
|
||||
@@ -109,7 +108,6 @@ type EditorPropertiesProps = {
|
||||
};
|
||||
function EditorProperties(props: EditorPropertiesProps) {
|
||||
const toggleProperties = useEditorStore((store) => store.toggleProperties);
|
||||
const isFocusMode = useAppStore((store) => store.isFocusMode);
|
||||
const dateFormat = useSettingStore((store) => store.dateFormat);
|
||||
const timeFormat = useSettingStore((store) => store.timeFormat);
|
||||
const metadataItems = [
|
||||
@@ -140,7 +138,8 @@ function EditorProperties(props: EditorPropertiesProps) {
|
||||
"diff"
|
||||
])
|
||||
);
|
||||
if (isFocusMode || !session) return null;
|
||||
if (!session) return null;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
|
||||
@@ -48,6 +48,8 @@ import Skeleton from "react-loading-skeleton";
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
const MAX_DATE = dayjs().add(99, "year").endOf("year").toDate();
|
||||
|
||||
export type AddReminderDialogProps = BaseDialogProps<boolean> & {
|
||||
reminder?: Reminder;
|
||||
note?: Note;
|
||||
@@ -200,6 +202,14 @@ export const AddReminderDialog = DialogManager.register(
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode !== Modes.REPEAT && date.isAfter(MAX_DATE)) {
|
||||
showToast(
|
||||
"error",
|
||||
strings.maximumReminderDate(getFormattedDate(MAX_DATE, "date"))
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = await db.reminders.add({
|
||||
id: reminder?.id,
|
||||
recurringMode,
|
||||
@@ -431,7 +441,7 @@ export const AddReminderDialog = DialogManager.register(
|
||||
}}
|
||||
selected={dayjs(date).toDate()}
|
||||
minDate={new Date()}
|
||||
maxDate={new Date(new Date().getFullYear() + 99, 11, 31)}
|
||||
maxDate={MAX_DATE}
|
||||
onSelect={(day) => {
|
||||
if (!day) return;
|
||||
const date = getFormattedDate(day, "date");
|
||||
|
||||
@@ -65,6 +65,7 @@ export const EditNoteCreationDateDialog = DialogManager.register(
|
||||
onClose(false);
|
||||
}}
|
||||
title={strings.editCreationDate()}
|
||||
description={`${strings.note()}: ${strings.creationDateCannotBeAfterLastEditedDate()}`}
|
||||
negativeButton={{
|
||||
text: strings.cancel(),
|
||||
onClick: () => {
|
||||
@@ -76,14 +77,10 @@ export const EditNoteCreationDateDialog = DialogManager.register(
|
||||
text: strings.save(),
|
||||
onClick: async () => {
|
||||
try {
|
||||
if (date.isAfter(dayjs())) {
|
||||
showToast("error", "Creation date cannot be in the future");
|
||||
return;
|
||||
}
|
||||
if (dateEdited && date.isAfter(dayjs(dateEdited))) {
|
||||
if (dateEdited && date.isAfter(dayjs(dateEdited), "minute")) {
|
||||
showToast(
|
||||
"error",
|
||||
"Creation date cannot be after last edited date"
|
||||
strings.creationDateCannotBeAfterLastEditedDate()
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -140,7 +137,7 @@ export const EditNoteCreationDateDialog = DialogManager.register(
|
||||
width: 300
|
||||
}}
|
||||
selected={dayjs(date).toDate()}
|
||||
maxDate={new Date()}
|
||||
maxDate={new Date(dateEdited)}
|
||||
onSelect={(day) => {
|
||||
if (!day) return;
|
||||
const date = getFormattedDate(day, "date");
|
||||
|
||||
@@ -54,19 +54,26 @@ export const NoteExpiryDateDialog = DialogManager.register(
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
title={"Set Custom Expiry Date"}
|
||||
title={strings.setExpiry()}
|
||||
onClose={() => onClose(false)}
|
||||
width={400}
|
||||
positiveButton={{
|
||||
text: strings.done(),
|
||||
onClick: async () => {
|
||||
if (date.isBefore(dayjs())) {
|
||||
showToast("error", "Expiry date must be in the future");
|
||||
showToast("error", strings.expiryDateMustBeInTheFuture());
|
||||
return;
|
||||
}
|
||||
if (date.isAfter(dayjs().add(1, "year"))) {
|
||||
showToast(
|
||||
"error",
|
||||
strings.expiryDateCannotBeMoreThan1YearInTheFuture()
|
||||
);
|
||||
return;
|
||||
}
|
||||
await db.notes.setExpiryDate(date.valueOf(), noteId);
|
||||
store.refresh();
|
||||
showToast("success", "Expiry date set");
|
||||
showToast("success", strings.expiryDateSet());
|
||||
onClose(true);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -714,7 +714,10 @@ export async function getUploadedFileSize(filename: string) {
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
|
||||
const contentLength = parseInt(attachmentInfo.headers["content-length"]);
|
||||
const contentLength = parseInt(
|
||||
attachmentInfo.headers["x-object-size"] ??
|
||||
attachmentInfo.headers["content-length"]
|
||||
);
|
||||
return isNaN(contentLength) ? 0 : contentLength;
|
||||
} catch (e) {
|
||||
logger.error(e, "Failed to get uploaded file size.", { filename });
|
||||
|
||||
@@ -57,18 +57,23 @@ class NoteStore extends BaseStore<NoteStore> {
|
||||
};
|
||||
|
||||
setContext = async (context?: Context) => {
|
||||
const groupOptions =
|
||||
context?.type === "notebook" ||
|
||||
context?.type === "tag" ||
|
||||
context?.type === "color"
|
||||
? db.settings.getGroupOptionsById(context.id, context.type)
|
||||
: db.settings.getGroupOptions(
|
||||
context?.type === "favorite"
|
||||
? "favorites"
|
||||
: context?.type === "archive"
|
||||
? "archive"
|
||||
: "notes"
|
||||
);
|
||||
|
||||
this.set({
|
||||
context,
|
||||
contextNotes: context
|
||||
? await notesFromContext(context).grouped(
|
||||
db.settings.getGroupOptions(
|
||||
context.type === "favorite"
|
||||
? "favorites"
|
||||
: context.type === "archive"
|
||||
? "archive"
|
||||
: "notes"
|
||||
)
|
||||
)
|
||||
? await notesFromContext(context).grouped(groupOptions)
|
||||
: undefined
|
||||
});
|
||||
};
|
||||
|
||||
@@ -38,6 +38,7 @@ export function Notebooks() {
|
||||
const roots = useStore((store) => store.notebooks);
|
||||
const [filteredNotebooks, setFilteredNotebooks] =
|
||||
useState<VirtualizedGrouping<NotebookType>>();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const treeRef =
|
||||
useRef<
|
||||
VirtualizedTreeHandle<{ notebook: NotebookType; totalNotes: number }>
|
||||
@@ -61,6 +62,13 @@ export function Notebooks() {
|
||||
|
||||
useEffect(() => {
|
||||
treeRef.current?.refresh();
|
||||
|
||||
const query = inputRef.current?.value.trim();
|
||||
if (!query) return;
|
||||
|
||||
(async () => {
|
||||
setFilteredNotebooks(await db.lookup.notebooks(query).sorted());
|
||||
})();
|
||||
}, [roots]);
|
||||
|
||||
return (
|
||||
@@ -165,6 +173,7 @@ export function Notebooks() {
|
||||
)}
|
||||
</Box>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
variant="clean"
|
||||
placeholder="Filter notebooks..."
|
||||
sx={{ borderTop: "1px solid var(--border)", mx: 0 }}
|
||||
|
||||
@@ -23,23 +23,31 @@ import Placeholder from "../components/placeholders";
|
||||
import { db } from "../common/db";
|
||||
import { ListLoader } from "../components/loaders/list-loader";
|
||||
import { Flex, Input } from "@theme-ui/components";
|
||||
import { forwardRef, useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { debounce } from "@notesnook/common";
|
||||
import { Tag, VirtualizedGrouping } from "@notesnook/core";
|
||||
import ScrollContainer from "../components/scroll-container";
|
||||
import { ScrollerProps } from "react-virtuoso";
|
||||
import { SidebarScroller } from "../components/sidebar-scroller";
|
||||
|
||||
function Tags() {
|
||||
const tags = useStore((store) => store.tags);
|
||||
const refresh = useStore((store) => store.refresh);
|
||||
const [filteredTags, setFilteredTags] = useState<VirtualizedGrouping<Tag>>();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const items = filteredTags || tags;
|
||||
|
||||
useEffect(() => {
|
||||
store.refresh();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const query = inputRef.current?.value.trim();
|
||||
if (!query) return;
|
||||
|
||||
(async () => {
|
||||
setFilteredTags(await db.lookup.tags(query).sorted());
|
||||
})();
|
||||
}, [tags]);
|
||||
|
||||
if (!items) return <ListLoader />;
|
||||
return (
|
||||
<Flex
|
||||
@@ -62,6 +70,7 @@ function Tags() {
|
||||
Scroller={SidebarScroller}
|
||||
/>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
variant="clean"
|
||||
placeholder="Filter tags..."
|
||||
sx={{ borderTop: "1px solid var(--border)", mx: 0 }}
|
||||
|
||||
@@ -47,3 +47,29 @@ test("save trash cleanup interval", () =>
|
||||
await db.settings.setTrashCleanupInterval(interval);
|
||||
expect(db.settings.getTrashCleanupInterval()).toBe(interval);
|
||||
}));
|
||||
|
||||
const GROUP_OPTIONS_BY_ID_TESTS = ["notebook", "tag", "color"];
|
||||
|
||||
for (const type of GROUP_OPTIONS_BY_ID_TESTS) {
|
||||
test(`get ${type} id group options`, () =>
|
||||
databaseTest().then(async (db) => {
|
||||
const id = `test-${type}-id`;
|
||||
const groupOptions = {
|
||||
groupBy: "year",
|
||||
sortBy: "title",
|
||||
sortDirection: "asc"
|
||||
};
|
||||
await db.settings.setGroupOptionsById(id, type, groupOptions);
|
||||
expect(db.settings.getGroupOptionsById(id, type)).toMatchObject(
|
||||
groupOptions
|
||||
);
|
||||
}));
|
||||
|
||||
test(`get ${type} id group options fallback to notes group options`, () =>
|
||||
databaseTest().then(async (db) => {
|
||||
const id = `non-existent-${type}-id`;
|
||||
const defaultOptions = db.settings.getGroupOptions("notes");
|
||||
const result = db.settings.getGroupOptionsById(id, type);
|
||||
expect(result).toMatchObject(defaultOptions);
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@ import {
|
||||
TrashCleanupInterval,
|
||||
TimeFormat,
|
||||
DayFormat,
|
||||
WeekFormat
|
||||
WeekFormat,
|
||||
GroupingByIdKey
|
||||
} from "../types.js";
|
||||
import { ICollection } from "./collection.js";
|
||||
import { SQLCachedCollection } from "../database/sql-cached-collection.js";
|
||||
@@ -64,6 +65,9 @@ const defaultSettings: SettingItemMap = {
|
||||
trashCleanupInterval: 7,
|
||||
profile: undefined,
|
||||
|
||||
"groupOptions:notes:notebooks": {},
|
||||
"groupOptions:notes:tags": {},
|
||||
"groupOptions:notes:colors": {},
|
||||
"groupOptions:trash": DEFAULT_GROUP_OPTIONS("trash"),
|
||||
"groupOptions:tags": DEFAULT_GROUP_OPTIONS("tags"),
|
||||
"groupOptions:notes": DEFAULT_GROUP_OPTIONS("notes"),
|
||||
@@ -150,6 +154,34 @@ export class Settings implements ICollection {
|
||||
return this.set(`groupOptions:${key}`, groupOptions);
|
||||
}
|
||||
|
||||
async setGroupOptionsById(
|
||||
id: string,
|
||||
type: GroupingByIdKey,
|
||||
groupOptions: GroupOptions
|
||||
) {
|
||||
const groupOptionsKey =
|
||||
type === "notebook"
|
||||
? "groupOptions:notes:notebooks"
|
||||
: type === "tag"
|
||||
? "groupOptions:notes:tags"
|
||||
: "groupOptions:notes:colors";
|
||||
|
||||
const groupOptionsMap = this.get(groupOptionsKey);
|
||||
groupOptionsMap[id] = groupOptions;
|
||||
return this.set(groupOptionsKey, groupOptionsMap);
|
||||
}
|
||||
|
||||
getGroupOptionsById(id: string, type: GroupingByIdKey) {
|
||||
const groupOptions = this.get(
|
||||
type === "notebook"
|
||||
? "groupOptions:notes:notebooks"
|
||||
: type === "tag"
|
||||
? "groupOptions:notes:tags"
|
||||
: "groupOptions:notes:colors"
|
||||
);
|
||||
return groupOptions[id] || this.get("groupOptions:notes");
|
||||
}
|
||||
|
||||
setToolbarConfig(platform: ToolbarConfigPlatforms, config: ToolbarConfig) {
|
||||
return this.set(`toolbarConfig:${platform}`, config);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ export const GroupingKey = [
|
||||
"search"
|
||||
] as const;
|
||||
export type GroupingKey = (typeof GroupingKey)[number];
|
||||
export type GroupingByIdKey = "notebook" | "tag" | "color";
|
||||
|
||||
export type ValueOf<T> = T[keyof T];
|
||||
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
||||
@@ -486,6 +487,12 @@ export type SettingItemMap = {
|
||||
defaultTag: string | undefined;
|
||||
profile: Profile | undefined;
|
||||
} & Record<`groupOptions:${GroupingKey}`, GroupOptions> &
|
||||
Record<
|
||||
| `groupOptions:notes:notebooks`
|
||||
| `groupOptions:notes:tags`
|
||||
| `groupOptions:notes:colors`,
|
||||
Record<string, GroupOptions>
|
||||
> &
|
||||
Record<`toolbarConfig:${ToolbarConfigPlatforms}`, ToolbarConfig | undefined> &
|
||||
Record<`sideBarOrder:${SideBarSection}`, string[]> &
|
||||
Record<`sideBarHiddenItems:${SideBarHideableSection}`, string[]>;
|
||||
|
||||
@@ -139,6 +139,7 @@ function Header({
|
||||
}): JSX.Element {
|
||||
const tab = useTabContext();
|
||||
const editor = editors[tab.id];
|
||||
const tableOfContents = editorControllers[tab.id]?.getTableOfContents?.();
|
||||
const insets = useSafeArea();
|
||||
const openedTabsCount = useTabStore((state) => state.tabs.length);
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
@@ -148,6 +149,8 @@ function Header({
|
||||
state.canGoForward
|
||||
]);
|
||||
|
||||
console.log(tableOfContents?.length);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -496,26 +499,29 @@ function Header({
|
||||
</span>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem
|
||||
value="toc"
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: 10,
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<TableOfContentsIcon
|
||||
size={20 * settings.fontScale}
|
||||
color="var(--nn_primary_icon)"
|
||||
/>
|
||||
<span
|
||||
{tableOfContents?.length ? (
|
||||
<MenuItem
|
||||
value="toc"
|
||||
style={{
|
||||
color: "var(--nn_primary_paragraph)"
|
||||
display: "flex",
|
||||
gap: 10,
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{strings.toc()}
|
||||
</span>
|
||||
</MenuItem>
|
||||
<TableOfContentsIcon
|
||||
size={20 * settings.fontScale}
|
||||
color="var(--nn_primary_icon)"
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
color: "var(--nn_primary_paragraph)"
|
||||
}}
|
||||
>
|
||||
{strings.toc()}
|
||||
</span>
|
||||
</MenuItem>
|
||||
) : null}
|
||||
|
||||
<MenuItem
|
||||
value="scroll-top"
|
||||
style={{
|
||||
|
||||
@@ -113,13 +113,19 @@ function Tags(props: { settings: Settings; loading?: boolean }) {
|
||||
backgroundColor:
|
||||
index !== 0 ? "transparent" : "var(--nn_secondary_background)",
|
||||
borderRadius: 6,
|
||||
padding: "0px 4px",
|
||||
height: "24px",
|
||||
padding: "2px 4px",
|
||||
height: "25px",
|
||||
fontFamily: "Inter",
|
||||
fontSize: 12,
|
||||
color: "var(--nn_primary_icon)",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none"
|
||||
WebkitUserSelect: "none",
|
||||
textAlign: "left",
|
||||
maxWidth: 150,
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
wordWrap: "break-word",
|
||||
whiteSpace: "nowrap"
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -17,17 +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/>.
|
||||
*/
|
||||
|
||||
import { Flex, Text } from "@theme-ui/components";
|
||||
import { useCallback, useState } from "react";
|
||||
import { Popup } from "../components/popup.js";
|
||||
import { Input, Textarea } from "@theme-ui/components";
|
||||
import { Embed, EmbedSizeOptions } from "../../extensions/embed/index.js";
|
||||
import { convertUrlToEmbedUrl } from "@social-embed/lib";
|
||||
import { InlineInput } from "../../components/inline-input/index.js";
|
||||
import { Tabs, Tab } from "../../components/tabs/index.js";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { convertUrlToEmbedUrl, isValidUrl } from "@social-embed/lib";
|
||||
import { Flex, Input, Text, Textarea } from "@theme-ui/components";
|
||||
import { useCallback, useState } from "react";
|
||||
import { InlineInput } from "../../components/inline-input/index.js";
|
||||
import { Tab, Tabs } from "../../components/tabs/index.js";
|
||||
import { Embed, EmbedSizeOptions } from "../../extensions/embed/index.js";
|
||||
import { Popup } from "../components/popup.js";
|
||||
|
||||
type EmbedSource = "url" | "code";
|
||||
|
||||
export type EmbedPopupProps = {
|
||||
onClose: (embed?: Embed) => void;
|
||||
title: string;
|
||||
@@ -38,33 +38,30 @@ export type EmbedPopupProps = {
|
||||
|
||||
export function EmbedPopup(props: EmbedPopupProps) {
|
||||
const { onClose, onSizeChanged, title, embed } = props;
|
||||
const [width, setWidth] = useState(embed?.width || 300);
|
||||
const [height, setHeight] = useState(embed?.height || 150);
|
||||
const [src, setSrc] = useState(embed?.src || "");
|
||||
const [embedSource, setEmbedSource] = useState<EmbedSource>("url");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [size, setSize] = useState<EmbedSizeOptions>({
|
||||
width: embed?.width || 300,
|
||||
height: embed?.height || 150
|
||||
});
|
||||
|
||||
const onSizeChange = useCallback(
|
||||
(newWidth?: number, newHeight?: number) => {
|
||||
const size: EmbedSizeOptions = newWidth
|
||||
? {
|
||||
width: newWidth,
|
||||
height: newWidth * (height / width)
|
||||
}
|
||||
: newHeight
|
||||
? {
|
||||
width: newHeight * (width / height),
|
||||
height: newHeight
|
||||
}
|
||||
: {
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
setWidth(size.width);
|
||||
setHeight(size.height);
|
||||
if (onSizeChanged) onSizeChanged(size);
|
||||
const hasNewWidth = Number.isFinite(newWidth);
|
||||
const hasNewHeight = Number.isFinite(newHeight);
|
||||
|
||||
if (!hasNewWidth && !hasNewHeight) return;
|
||||
setSize((size) => {
|
||||
const newSize = {
|
||||
width: hasNewWidth ? ((newWidth || 0) as number) : size.width,
|
||||
height: hasNewHeight ? ((newHeight || 0) as number) : size.height
|
||||
};
|
||||
if (onSizeChanged) onSizeChanged(newSize);
|
||||
return newSize;
|
||||
});
|
||||
},
|
||||
[height, width, onSizeChanged]
|
||||
[onSizeChanged]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -77,8 +74,9 @@ export function EmbedPopup(props: EmbedPopupProps) {
|
||||
onClick: () => {
|
||||
setError(null);
|
||||
let _src = src;
|
||||
let _width = width;
|
||||
let _height = height;
|
||||
let _width = size.width;
|
||||
let _height = size.height;
|
||||
|
||||
if (embedSource === "code") {
|
||||
const document = new DOMParser().parseFromString(src, "text/html");
|
||||
if (document.getElementsByTagName("iframe").length <= 0)
|
||||
@@ -100,8 +98,19 @@ export function EmbedPopup(props: EmbedPopupProps) {
|
||||
if (heightValue && !isNaN(parseInt(heightValue)))
|
||||
_height = parseInt(heightValue);
|
||||
}
|
||||
|
||||
if (embedSource === "url" && !isValidUrl(src)) {
|
||||
return setError("Please provide a valid url.");
|
||||
}
|
||||
|
||||
const convertedUrl = convertUrlToEmbedUrl(_src);
|
||||
|
||||
if (convertedUrl) _src = convertedUrl;
|
||||
|
||||
if (!_src && embedSource === "url") {
|
||||
return setError("Please provide a valid embed url.");
|
||||
}
|
||||
|
||||
if (_src.startsWith("javascript:")) {
|
||||
return setError("Embedding javascript code is not supported.");
|
||||
}
|
||||
@@ -147,7 +156,7 @@ export function EmbedPopup(props: EmbedPopupProps) {
|
||||
label="width"
|
||||
type="number"
|
||||
placeholder={strings.width()}
|
||||
value={width}
|
||||
defaultValue={size.width}
|
||||
sx={{
|
||||
mr: 1,
|
||||
fontSize: "body"
|
||||
@@ -158,7 +167,7 @@ export function EmbedPopup(props: EmbedPopupProps) {
|
||||
label="height"
|
||||
type="number"
|
||||
placeholder={strings.height()}
|
||||
value={height}
|
||||
defaultValue={size.height}
|
||||
sx={{ fontSize: "body" }}
|
||||
onChange={(e) =>
|
||||
onSizeChange(undefined, e.target.valueAsNumber)
|
||||
|
||||
@@ -1973,6 +1973,10 @@ msgstr "Created at"
|
||||
msgid "Creating a{0} backup"
|
||||
msgstr "Creating a{0} backup"
|
||||
|
||||
#: src/strings.ts:2695
|
||||
msgid "Creation date cannot be after last edited date"
|
||||
msgstr "Creation date cannot be after last edited date"
|
||||
|
||||
#: src/strings.ts:2049
|
||||
msgid "Credentials"
|
||||
msgstr "Credentials"
|
||||
@@ -2767,6 +2771,18 @@ msgstr "Experience the next level of private note taking\""
|
||||
msgid "Expiry date"
|
||||
msgstr "Expiry date"
|
||||
|
||||
#: src/strings.ts:2692
|
||||
msgid "Expiry date cannot be more than 1 year in the future"
|
||||
msgstr "Expiry date cannot be more than 1 year in the future"
|
||||
|
||||
#: src/strings.ts:2690
|
||||
msgid "Expiry date must be in the future"
|
||||
msgstr "Expiry date must be in the future"
|
||||
|
||||
#: src/strings.ts:2693
|
||||
msgid "Expiry date set"
|
||||
msgstr "Expiry date set"
|
||||
|
||||
#: src/strings.ts:2550
|
||||
msgid "Explore all plans"
|
||||
msgstr "Explore all plans"
|
||||
@@ -3930,6 +3946,10 @@ msgstr "Math & formulas"
|
||||
msgid "Maximize"
|
||||
msgstr "Maximize"
|
||||
|
||||
#: src/strings.ts:2698
|
||||
msgid "Maximum reminder date is {maxDate}"
|
||||
msgstr "Maximum reminder date is {maxDate}"
|
||||
|
||||
#: src/strings.ts:2080
|
||||
msgid "Meet other privacy-minded people & talk to us directly about your concerns, issues and suggestions."
|
||||
msgstr "Meet other privacy-minded people & talk to us directly about your concerns, issues and suggestions."
|
||||
@@ -4293,6 +4313,10 @@ msgstr "Note copied to clipboard"
|
||||
msgid "Note does not exist"
|
||||
msgstr "Note does not exist"
|
||||
|
||||
#: src/strings.ts:2696
|
||||
msgid "Note duplicated"
|
||||
msgstr "Note duplicated"
|
||||
|
||||
#: src/strings.ts:458
|
||||
msgid "Note history"
|
||||
msgstr "Note history"
|
||||
|
||||
@@ -1962,6 +1962,10 @@ msgstr ""
|
||||
msgid "Creating a{0} backup"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2695
|
||||
msgid "Creation date cannot be after last edited date"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2049
|
||||
msgid "Credentials"
|
||||
msgstr ""
|
||||
@@ -2756,6 +2760,18 @@ msgstr ""
|
||||
msgid "Expiry date"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2692
|
||||
msgid "Expiry date cannot be more than 1 year in the future"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2690
|
||||
msgid "Expiry date must be in the future"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2693
|
||||
msgid "Expiry date set"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2550
|
||||
msgid "Explore all plans"
|
||||
msgstr ""
|
||||
@@ -3910,6 +3926,10 @@ msgstr ""
|
||||
msgid "Maximize"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2698
|
||||
msgid "Maximum reminder date is {maxDate}"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2080
|
||||
msgid "Meet other privacy-minded people & talk to us directly about your concerns, issues and suggestions."
|
||||
msgstr ""
|
||||
@@ -4273,6 +4293,10 @@ msgstr ""
|
||||
msgid "Note does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2696
|
||||
msgid "Note duplicated"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:458
|
||||
msgid "Note history"
|
||||
msgstr ""
|
||||
|
||||
@@ -2686,5 +2686,14 @@ Continue without attachments?`,
|
||||
openingLocalFileDesc: (filePath: string) =>
|
||||
t`Are you sure you want to open this file: ${filePath}?`,
|
||||
cantOpenFileLinksInBrowsers: () =>
|
||||
t`File links cannot be opened in browsers. Please use the Notesnook desktop app.`
|
||||
t`File links cannot be opened in browsers. Please use the Notesnook desktop app.`,
|
||||
expiryDateMustBeInTheFuture: () => t`Expiry date must be in the future`,
|
||||
expiryDateCannotBeMoreThan1YearInTheFuture: () =>
|
||||
t`Expiry date cannot be more than 1 year in the future`,
|
||||
expiryDateSet: () => t`Expiry date set`,
|
||||
creationDateCannotBeAfterLastEditedDate: () =>
|
||||
t`Creation date cannot be after last edited date`,
|
||||
noteDuplicated: () => t`Note duplicated`,
|
||||
maximumReminderDate: (maxDate: string) =>
|
||||
t`Maximum reminder date is ${maxDate}`
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user