Compare commits

..

2 Commits

Author SHA1 Message Date
Abdullah Atta
9fb8d29a34 web: fix editor readonly state not updating in realtime 2026-05-25 12:51:21 +05:00
Abdullah Atta
8756f92316 core: do not refetch user before decrypting each chunk in sync 2026-05-25 12:46:02 +05:00
65 changed files with 712 additions and 1334 deletions

View File

@@ -38,11 +38,6 @@ jobs:
with:
xcode-version: "26.1.1"
- name: Setup iOS Platform
run: |
xcodebuild -downloadPlatform iOS -exportPath ~/Downloads
xcodebuild -importPlatform ~/Downloads/iphonesimulator_26.1_23B86.dmg
- name: Install node modules
run: |
npm ci --ignore-scripts --prefer-offline --no-audit

View File

@@ -19,11 +19,6 @@ jobs:
with:
xcode-version: "26.1.1"
- name: Setup iOS Platform
run: |
xcodebuild -downloadPlatform iOS -exportPath ~/Downloads
xcodebuild -importPlatform ~/Downloads/iphonesimulator_26.1_23B86.dmg
- name: Install node modules
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
@@ -116,7 +111,6 @@ jobs:
api-private-key: ${{ secrets.API_KEY }}
- name: Upload Notesnook.ipa to Github
continue-on-error: true
uses: actions/upload-artifact@v4
with:
name: Notesnook.zip

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "3.3.23",
"version": "3.3.20",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.3.23",
"version": "3.3.20",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.3.23",
"version": "3.3.20",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",

View File

@@ -84,55 +84,35 @@ 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(() => {
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);
}),
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)
),
toggle: t.procedure
.input(z.object({ enabled: z.boolean() }))
.mutation(({ input: { enabled } }) => {
@@ -148,16 +128,3 @@ 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;
}

View File

@@ -140,7 +140,7 @@ android {
if (project.hasProperty("prBuildNumber")) {
versionCode Integer.parseInt(prBuildNumber())
} else {
versionCode 3107
versionCode 3105
}
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')

View File

@@ -115,10 +115,6 @@ export const FileSizeResult = {
Error: -1
};
function getFileSizeFromHeaders(headers: Headers) {
return headers.get("x-object-size") || headers.get("content-length");
}
export async function getUploadedFileSize(hash: string, retry = 0) {
try {
const url = `${hosts.API_HOST}/s3?name=${hash}`;
@@ -128,21 +124,24 @@ export async function getUploadedFileSize(hash: string, retry = 0) {
headers: { Authorization: `Bearer ${token}` }
});
const fileSize = getFileSizeFromHeaders(attachmentInfo.headers);
if (!attachmentInfo.ok || fileSize === null) {
if (
!attachmentInfo.ok ||
attachmentInfo.headers?.get("content-length") === null
) {
if (retry < 3) {
DatabaseLogger.log(`Retrying file size check: ${hash}, ${retry}`);
return getUploadedFileSize(hash, retry + 1);
}
throw new Error(
`File size check failed: ${hash}, ${attachmentInfo.status}, ${fileSize}`
`File size check failed: ${hash}, ${
attachmentInfo.status
}, ${attachmentInfo.headers?.get("content-length")}`
);
}
console.log(attachmentInfo.headers);
const contentLength = parseInt(fileSize as string);
const contentLength = parseInt(
attachmentInfo.headers?.get("content-length") as string
);
return isNaN(contentLength) ? FileSizeResult.Empty : contentLength;
} catch (e) {
DatabaseLogger.error(e);
@@ -162,10 +161,10 @@ export async function checkUpload(
size === 0
? `File size is 0.`
: size === -1
? `File verification check failed.`
: expectedSize !== decryptedLength
? `File size mismatch. Expected ${size} bytes but got ${decryptedLength} bytes.`
: undefined;
? `File verification check failed.`
: expectedSize !== decryptedLength
? `File size mismatch. Expected ${size} bytes but got ${decryptedLength} bytes.`
: undefined;
if (error) throw new Error(error);
}
@@ -192,8 +191,7 @@ export async function checkAndCreateDir(path: string) {
}
export const santizeUri = (uri: string) => {
const decoded = decodeURI(uri);
return Platform.OS === "ios" ? decoded.replace("file:///", "/") : decoded;
return Platform.OS === "ios" ? decodeURI(uri).replace("file:///", "/") : uri;
};
export function isSuccessStatusCode(statusCode: number) {

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import {
GroupHeader,
GroupingByIdKey,
GroupingKey,
GroupOptions,
ItemType
@@ -47,8 +46,6 @@ type SectionHeaderProps = {
screen?: RouteName;
groupOptions: GroupOptions;
group: GroupingKey;
groupId?: string;
type?: GroupingByIdKey;
onOpenJumpToDialog: () => void;
itemCount?: number;
};
@@ -65,9 +62,7 @@ export const SectionHeader = React.memo<
groupOptions,
group,
onOpenJumpToDialog,
itemCount,
groupId,
type
itemCount
}: SectionHeaderProps) {
const { colors } = useThemeColors();
const isCompactModeEnabled = useIsCompactModeEnabled(
@@ -148,10 +143,8 @@ export const SectionHeader = React.memo<
component: (
<Sort
screen={screen}
dataType={dataType}
type={type}
type={dataType}
group={group}
groupId={groupId}
hideGroupOptions={
screen === "Reminders" || screen === "Search"
}

View File

@@ -17,12 +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/>.
*/
import {
GroupingByIdKey,
GroupingKey,
Item,
VirtualizedGrouping
} from "@notesnook/core";
import { GroupingKey, Item, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import { LegendList, LegendListRenderItemProps } from "@legendapp/list";
import React, { useEffect, useRef } from "react";
@@ -60,7 +55,6 @@ type ListProps = {
placeholder?: PlaceholderData;
groupType: GroupingKey;
id?: string;
type?: GroupingByIdKey;
};
const onMomentumScrollEnd = () => {
@@ -80,7 +74,7 @@ export default function List(props: ListProps) {
props.dataType === "notebook" ||
notebooksListMode === "compact";
const groupOptions = useGroupOptions(props.groupType, props.id, props.type);
const groupOptions = useGroupOptions(props.groupType);
const _onRefresh = async () => {
Sync.run("global", false, "full", () => {
@@ -102,27 +96,23 @@ 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}
/>
);
},
[
props.isRenderedInActionSheet,
props.data,
props.id,
props.groupType,
props.renderedInRoute,
props.customAccentColor,
props.dataType,
groupOptions,
props.type
props.groupType,
props.customAccentColor,
props.data,
props.dataType,
props.isRenderedInActionSheet,
props.renderedInRoute
]
);

View File

@@ -26,7 +26,6 @@ import {
Color,
GroupHeader,
GroupOptions,
GroupingByIdKey,
GroupingKey,
HighlightedResult,
Item,
@@ -41,7 +40,7 @@ import {
} from "@notesnook/core";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { getGroupOptions } from "../../hooks/use-group-options";
import { db } from "../../common/database";
import { useIsCompactModeEnabled } from "../../hooks/use-is-compact-mode-enabled";
import { eSendEvent } from "../../services/event-manager";
import { RouteName } from "../../stores/use-navigation-store";
@@ -50,8 +49,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 { SearchResult } from "../list-items/search-result";
import TagItem from "../list-items/tag";
import { SearchResult } from "../list-items/search-result";
type ListItemWrapperProps<TItem = Item> = {
group: GroupingKey;
@@ -63,8 +62,6 @@ type ListItemWrapperProps<TItem = Item> = {
dataType: string;
scrollRef: any;
groupOptions: GroupOptions;
groupId?: string;
type?: GroupingByIdKey;
};
export function ListItemWrapper(props: ListItemWrapperProps) {
@@ -186,8 +183,6 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
index={index}
dataType={item.type}
group={group}
groupId={props.groupId}
type={props.type}
color={props.customAccentColor}
groupOptions={groupOptions}
onOpenJumpToDialog={() => {
@@ -225,8 +220,6 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
index={index}
dataType={item.type}
group={group}
groupId={props.groupId}
type={props.type}
color={props.customAccentColor}
groupOptions={groupOptions}
onOpenJumpToDialog={() => {
@@ -257,8 +250,6 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
group={group}
dataType={item.type}
color={props.customAccentColor}
type={props.type}
groupId={props.groupId}
groupOptions={groupOptions}
onOpenJumpToDialog={() => {
eSendEvent(eOpenJumpToDialog, {
@@ -285,8 +276,6 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
index={index}
group={group}
dataType={item.type}
groupId={props.groupId}
type={props.type}
color={props.customAccentColor}
groupOptions={groupOptions}
onOpenJumpToDialog={() => {
@@ -314,8 +303,6 @@ 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}
@@ -335,16 +322,11 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
}
}
function getDate(
item: Notebook | Note,
groupType?: GroupingKey,
id?: string,
type?: GroupingByIdKey
): number {
function getDate(item: Notebook | Note, groupType?: GroupingKey): number {
return (
getSortValue(
groupType
? getGroupOptions(groupType, id, type)
? db.settings.getGroupOptions(groupType)
: {
sortBy: "dateEdited",
sortDirection: "desc"

View File

@@ -110,7 +110,6 @@ export const DateMeta = ({ item }: { item: Item }) => {
}}
maximumDate={new Date((item as Note).dateEdited)}
isDarkModeEnabled={isDark}
themeVariant={isDark ? "dark" : "light"}
is24Hour={db.settings.getTimeFormat() === "24-hour"}
date={new Date(dateCreated)}
/>

View File

@@ -45,15 +45,12 @@ 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);
@@ -79,31 +76,26 @@ 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) {
const password = await db.monographs.decryptPassword(
passwordValue.current = await db.monographs.decryptPassword(
monograph?.password
);
formRef.current.setValue("password", password);
setIsLocked(!!monograph?.password);
}
}
@@ -112,23 +104,20 @@ 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) {
await db.monographs.publish(note.id, values.title, {
selfDestruct,
password: isLocked ? values.password : undefined
if (isLocked && !passwordValue.current) return;
await db.monographs.publish(note.id, customTitle.current, {
selfDestruct: selfDestruct,
password: isLocked ? passwordValue.current : undefined
});
await monographData.execute();
Navigation.queueRoutesForUpdate();
eSendEvent(eMenuItemUpdate);
setPublishLoading(false);
}
requestInAppReview();
} catch (e) {
@@ -138,9 +127,9 @@ const PublishNoteSheet = ({
type: "error",
context: "local"
});
} finally {
setPublishLoading(false);
}
setPublishLoading(false);
};
const setPublishLoading = (value: boolean) => {
setPublishing(value);
@@ -258,17 +247,11 @@ const PublishNoteSheet = ({
</TouchableOpacity>
) : null}
<FormInput
name="title"
formRef={formRef}
<Input
fwdRef={titleInput}
multiline
scrollEnabled
containerStyle={{
maxHeight: 100
}}
onChangeText={(value) => (customTitle.current = value)}
defaultValue={customTitle.current}
placeholder={strings.noteTitle()}
validators={[validators.required(strings.titleIsRequired())]}
/>
<TouchableOpacity
@@ -315,16 +298,13 @@ const PublishNoteSheet = ({
{isLocked ? (
<>
<FormInput
name="password"
formRef={formRef}
<Input
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
}}

View File

@@ -21,7 +21,7 @@ import { sanitizeFilename } from "@notesnook/common";
import { strings } from "@notesnook/intl";
import Clipboard from "@react-native-clipboard/clipboard";
import React, { createRef } from "react";
import { PermissionsAndroid, Platform, View } from "react-native";
import { Platform, View } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import FileViewer from "react-native-file-viewer";
import * as ScopedStorage from "react-native-scoped-storage";
@@ -45,7 +45,6 @@ import SheetWrapper from "../../ui/sheet";
import { QRCode } from "../../ui/svg/lazy";
import Paragraph from "../../ui/typography/paragraph";
import { DefaultAppStyles } from "../../../utils/styles";
import { CameraRoll } from "@react-native-camera-roll/camera-roll";
class RecoveryKeySheet extends React.Component {
constructor(props) {
@@ -109,14 +108,22 @@ class RecoveryKeySheet extends React.Component {
saveQRCODE = async () => {
this.svg.current?.toDataURL(async (data) => {
try {
let fileName =
"nn_" + this.user.email + "_recovery_key_qrcode" + "_" + Date.now();
let path;
let fileName = "nn_" + this.user.email + "_recovery_key_qrcode";
fileName = sanitizeFilename(fileName, { replacement: "_" });
fileName = fileName + ".png";
const path = RNFetchBlob.fs.dirs.CacheDir + fileName;
await RNFetchBlob.fs.writeFile(path, data, "base64");
await CameraRoll.saveToCameraRoll(`file://` + path);
if (Platform.OS === "android") {
await ScopedStorage.createDocument(
fileName,
"image/png",
data,
"base64"
);
} else {
path = await filesystem.checkAndCreateDir("/");
await RNFetchBlob.fs.writeFile(path + fileName, data, "base64");
}
ToastManager.show({
heading: strings.recoveryKeyQRCodeSaved(),
type: "success",

View 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 { 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}
/>
)
});
};

View File

@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {
GroupingByIdKey,
GroupingKey,
GroupOptions,
ItemType,
@@ -28,10 +27,7 @@ import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useState } from "react";
import { View } from "react-native";
import {
getGroupOptions,
setGroupOptionsById
} from "../../../hooks/use-group-options";
import { db } from "../../../common/database";
import { eSendEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { RouteName } from "../../../stores/use-navigation-store";
@@ -47,24 +43,20 @@ import { Pressable } from "../../ui/pressable";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
const Sort = ({
dataType,
type,
screen,
hideGroupOptions,
group: groupType,
groupId,
type
group: groupType
}: {
dataType: ItemType;
type?: GroupingByIdKey;
type: ItemType;
screen?: RouteName;
group: GroupingKey;
hideGroupOptions?: boolean;
groupId?: string;
}) => {
const { colors } = useThemeColors();
const [groupOptions, setGroupOptions] = useState(
getGroupOptions(groupType, groupId, type)
db.settings.getGroupOptions(groupType)
);
const getSortButtonTitle = () => {
@@ -87,17 +79,16 @@ const Sort = ({
};
const updateGroupOptions = async (_groupOptions: GroupOptions) => {
console.log(groupId, type);
setGroupOptionsById(groupType, _groupOptions, groupId, type);
await db.settings.setGroupOptions(groupType, _groupOptions);
setGroupOptions(_groupOptions);
setTimeout(() => {
if (screen) Navigation.queueRoutesForUpdate(screen);
if (dataType === "notebook") {
if (type === "notebook") {
useNotebookStore.getState().refresh();
} else if (dataType === "tag") {
} else if (type === "tag") {
useTagStore.getState().refresh();
}
eSendEvent(eGroupOptionsUpdated, groupType, groupId, type);
eSendEvent(eGroupOptionsUpdated, groupType);
eSendEvent(refreshNotesPage);
}, 1);
};

View File

@@ -493,7 +493,7 @@ const TabBar = (props: SimpleTabBarProps) => {
presentSheet({
component: (
<Sort
dataType={
type={
props.navigationState.index === 1
? "notebook"
: "tag"

View File

@@ -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,12 +784,6 @@ 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();
};
@@ -1053,9 +1047,8 @@ export const useActions = ({
title: strings.dataTypesPluralCamelCase.reminder(),
icon: "clock-outline",
onPress: async () => {
close();
RelationsList.present({
item,
reference: item,
referenceType: "reminder",
relationType: "from",
title: strings.dataTypesPluralCamelCase.reminder(),
@@ -1072,6 +1065,26 @@ 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
}
});
},
@@ -1115,9 +1128,8 @@ export const useActions = ({
id: "add-reminder",
title: strings.remindMe(),
icon: "clock-plus-outline",
onPress: async () => {
onPress: () => {
close();
await sleep(100);
AddReminder.present(undefined, item);
}
},

View File

@@ -16,61 +16,28 @@ 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, useRef, useState } from "react";
import { useEffect, 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 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
) {
export function useGroupOptions(type: any) {
const appLoading = useSettingStore((state) => state.isAppLoading);
const [groupOptions, setGroupOptions] = useState(
getGroupOptions(groupingKey, id, type)
db.settings?.getGroupOptions(type)
);
console.log(groupingKey, id, type, groupOptions, "options");
const groupOptionsRef = useRef(groupOptions);
groupOptionsRef.current = groupOptions;
useEffect(() => {
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);
const onUpdate = (groupType: string) => {
if (groupType !== type) return;
const options = db.settings?.getGroupOptions(type) as any;
if (!options) return;
if (
groupOptionsRef.current?.groupBy !== options.groupBy ||
groupOptionsRef.current?.sortBy !== options.sortBy ||
groupOptionsRef.current?.sortDirection !== options?.sortDirection
groupOptions?.groupBy !== options.groupBy ||
groupOptions?.sortBy !== options.sortBy ||
groupOptions?.sortDirection !== groupOptions?.sortDirection
) {
console.log("onUpdate", _id, _type);
setGroupOptions({ ...options });
Navigation.queueRoutesForUpdate();
}
@@ -79,13 +46,13 @@ export function useGroupOptions(
eSubscribeEvent(eGroupOptionsUpdated, onUpdate);
if (!appLoading) {
onUpdate(groupingKey);
onUpdate(type);
}
return () => {
eUnSubscribeEvent(eGroupOptionsUpdated, onUpdate);
};
}, [groupingKey, appLoading, id, type]);
}, [type, groupOptions, appLoading]);
return groupOptions;
}

View File

@@ -279,7 +279,6 @@ 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 = () => {
@@ -386,15 +385,6 @@ export const RootNavigation = () => {
return AddReminder;
}}
/>
<RootStack.Screen
name="RelationsList"
getComponent={() => {
RelationsList =
RelationsList || require("../screens/relations-list").default;
return RelationsList;
}}
/>
<RootStack.Screen
name="PayWall"
getComponent={() => {

View File

@@ -95,17 +95,7 @@ const ReminderNotificationModes = {
export default function AddReminder(props: NavigationProps<"AddReminder">) {
const { reminder, reference } = props.route.params;
useNavigationFocus(props.navigation, {
focusOnInit: true,
onFocus: () => {
if (!props.route.params?.reminder) {
setTimeout(() => {
titleRef.current?.focus();
}, 200);
}
return false;
}
});
useNavigationFocus(props.navigation, { focusOnInit: true });
const { colors, isDark } = useThemeColors();
const weekFormat = useSettingStore((state) => state.weekFormat);
const [reminderMode, setReminderMode] = useState<Reminder["mode"]>(
@@ -292,6 +282,7 @@ 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
}}

View File

@@ -46,6 +46,7 @@ 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 {
@@ -79,7 +80,6 @@ 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({
item: note as any,
reference: note as any,
referenceType: "reminder",
relationType: "from",
title: strings.dataTypesPluralCamelCase.reminder(),

View File

@@ -41,7 +41,6 @@ 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>>();
@@ -121,9 +120,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
params.current.id = notebook.id;
const notes = await db.relations
.from(notebook, "note")
.selector.grouped(
getGroupOptions("notes", notebook.id, "notebook")
);
.selector.grouped(db.settings.getGroupOptions("notes"));
setNotes(notes);
await notes.item(0, resolveItems);
syncWithNavigation();
@@ -193,7 +190,6 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
onRequestUpdate();
}}
id={params.current?.id}
type="notebook"
renderedInRoute="Notebook"
headerTitle={notebook?.title}
loading={loading}

View File

@@ -26,7 +26,6 @@ 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
@@ -53,7 +52,7 @@ ColoredNotes.get = async (params: NotesScreenParams, grouped = true) => {
return await db.relations
.from({ id: params.id, type: "color" }, "note")
.selector.grouped(getGroupOptions("notes", params.id, "color"));
.selector.grouped(db.settings.getGroupOptions("notes"));
};
ColoredNotes.navigate = (item: Color, canGoBack: boolean) => {

View File

@@ -18,8 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { resolveItems } from "@notesnook/common";
import { Color, Note, Tag, VirtualizedGrouping } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { Tag, VirtualizedGrouping } from "@notesnook/core";
import { Color, Note } from "@notesnook/core";
import React, { useEffect, useRef, useState } from "react";
import { db } from "../../common/database";
import { FloatingButton } from "../../components/container/floating-button";
@@ -39,9 +39,10 @@ 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: (
@@ -171,7 +172,7 @@ const NotesPage = ({
if (loadingNotes) {
onRequestUpdate(params.current);
}
}, [loadingNotes, get, isAppLoading, onRequestUpdate]);
}, [loadingNotes, get, isAppLoading]);
useEffect(() => {
eSubscribeEvent(route.name, onRequestUpdate);
@@ -221,7 +222,6 @@ const NotesPage = ({
loading={false}
renderedInRoute={route.name}
id={params.current?.id}
type={params.current?.item?.type}
headerTitle={title || "Monographs"}
customAccentColor={accentColor}
placeholder={placeholder}

View File

@@ -26,7 +26,6 @@ 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,
@@ -54,7 +53,7 @@ TaggedNotes.get = async (params: NotesScreenParams, grouped = true) => {
return await db.relations
.from({ id: params.id, type: "tag" }, "note")
.selector.grouped(getGroupOptions("notes", params.id, "tag"));
.selector.grouped(db.settings.getGroupOptions("notes"));
};
TaggedNotes.navigate = (item: Tag, canGoBack?: boolean) => {

View File

@@ -1,142 +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, { 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;

View File

@@ -429,61 +429,36 @@ export const settingsGroups: SettingSection[] = [
{
id: "subscription-not-active",
name: strings.subscriptionNotActivated(),
useHook: () => useUserStore((state) => state.user),
hidden: (user) =>
Platform.OS !== "ios" ||
(user as User)?.subscription?.plan !== SubscriptionPlan.FREE,
hidden: () => Platform.OS !== "ios",
modifer: async () => {
if (Platform.OS === "android") return;
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"
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()
});
await PremiumService.subscriptions.verify(
currentSubscription
);
eSendEvent(eCloseSheet);
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);
}
},
icon: "information-outline",
actionText: strings.verify()
});
},
description: strings.verifySubDesc()
},
@@ -1718,7 +1693,7 @@ export const settingsGroups: SettingSection[] = [
console.error(e);
}
},
description: Platform.OS === "ios" ? "3.3.27" : getVersion()
description: getVersion()
}
]
}

View File

@@ -31,11 +31,7 @@ import type {
ThemesRouter
} from "@notesnook/themes-server";
import { keepLocalCopy, pick } from "@react-native-documents/picker";
import {
notifyManager,
QueryClient,
QueryClientProvider
} from "@tanstack/react-query";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
import { createTRPCReact } from "@trpc/react-query";
import React, { useState } from "react";
@@ -63,8 +59,6 @@ import { getElevationStyle } from "../../utils/elevation";
import { MenuItemsList } from "../../utils/menu-items";
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { openLinkInBrowser } from "../../utils/functions";
import Clipboard from "@react-native-clipboard/clipboard";
const THEME_SERVER_URL = "https://themes-api.notesnook.com";
//@ts-ignore
@@ -75,8 +69,7 @@ export const themeTrpcClient = createTRPCProxyClient<ThemesRouter>({
})
]
});
notifyManager.setBatchNotifyFunction((cb) => cb());
notifyManager.setNotifyFunction((cb) => cb());
function ThemeSelector() {
const [darkTheme, lightTheme] = useThemeStore((state) => [
state.darkTheme,
@@ -86,35 +79,35 @@ function ThemeSelector() {
const { colors } = useThemeColors();
const themeColors = colors;
const [searchQuery, setSearchQuery] = useState<string>();
const [colorScheme, setColorScheme] = useState<"all" | "dark" | "light">(
"all"
);
const filters = [];
if (searchQuery) filters.push({ type: "term" as const, value: searchQuery });
if (colorScheme !== "all")
filters.push({ type: "colorScheme" as const, value: colorScheme });
const [colorScheme, setColorScheme] = useState<string>();
const themes = trpc.themes.useInfiniteQuery(
{
limit: 10,
compatibilityVersion: THEME_COMPATIBILITY_VERSION,
filters
filters: [
...(searchQuery && searchQuery !== ""
? [
{
type: "term" as const,
value: searchQuery
}
]
: []),
...(colorScheme && colorScheme !== ""
? [
{
type: "colorScheme" as const,
value: colorScheme
}
]
: [])
]
},
{
keepPreviousData: true,
getNextPageParam: (lastPage) => lastPage.nextCursor
}
);
if (themes?.isError) {
DatabaseLogger.error(
new Error(themes.error.message),
"themes loading error",
themes.error?.data || undefined
);
}
const select = (item: Partial<ThemeMetadata>, fromFile?: boolean) => {
presentSheet({
context: "theme-details",
@@ -124,209 +117,204 @@ function ThemeSelector() {
});
};
const renderItem = React.useCallback(
({ item, index }: { item: ThemeMetadata; index: number }) => {
const colors =
item.previewColors ||
getPreviewColors(item as unknown as ThemeDefinition);
const renderItem = ({
item,
index
}: {
item: ThemeMetadata;
index: number;
}) => {
const colors =
item.previewColors ||
getPreviewColors(item as unknown as ThemeDefinition);
return (
<>
<TouchableOpacity
activeOpacity={0.9}
return (
<>
<TouchableOpacity
activeOpacity={0.9}
style={{
borderRadius: 10,
padding: DefaultAppStyles.GAP_SMALL,
marginBottom: DefaultAppStyles.GAP_VERTICAL,
flexShrink: 1,
marginHorizontal: 10
}}
onPress={() => select(item)}
>
<View
style={{
backgroundColor: colors?.background,
height: 200,
width: "100%",
borderRadius: 10,
padding: DefaultAppStyles.GAP_SMALL,
marginBottom: DefaultAppStyles.GAP_VERTICAL,
flexShrink: 1,
marginHorizontal: 10
overflow: "hidden",
flexDirection: "row",
justifyContent: "space-between",
...getElevationStyle(3)
}}
onPress={() => select(item)}
>
<View
style={{
backgroundColor: colors?.background,
height: 200,
width: "100%",
borderRadius: 10,
marginBottom: DefaultAppStyles.GAP_VERTICAL,
overflow: "hidden",
flexDirection: "row",
justifyContent: "space-between",
...getElevationStyle(3)
height: "100%",
width: "49.5%",
backgroundColor: colors.navigationMenu.background,
padding: DefaultAppStyles.GAP_SMALL,
paddingVertical: 3,
borderRadius: defaultBorderRadius
}}
>
{MenuItemsList.map((item, index) => (
<View
key={item.id}
style={{
height: 12,
width: "100%",
backgroundColor:
index === 0
? colors.navigationMenu.accent + 40
: colors.navigationMenu.background,
borderRadius: 2,
paddingHorizontal: 3,
flexDirection: "row",
alignItems: "center",
marginBottom: 4
}}
>
<Icon
size={8}
name={item.icon}
color={
index === 0
? colors.navigationMenu.accent
: colors.navigationMenu.icon
}
/>
<View
style={{
height: 3,
width: "40%",
backgroundColor:
index === 0
? colors.navigationMenu.accent
: colors.paragraph,
borderRadius: 2,
marginLeft: 3
}}
></View>
</View>
))}
</View>
<View
style={{
height: "100%",
width: "49.5%",
backgroundColor: colors.list.background,
borderRadius: defaultBorderRadius,
paddingHorizontal: 2,
paddingRight: 6
}}
>
<View
style={{
height: "100%",
width: "49.5%",
backgroundColor: colors.navigationMenu.background,
padding: DefaultAppStyles.GAP_SMALL,
paddingVertical: 3,
borderRadius: defaultBorderRadius
}}
>
{MenuItemsList.map((item, index) => (
<View
key={item.id}
style={{
height: 12,
width: "100%",
backgroundColor:
index === 0
? colors.navigationMenu.accent + 40
: colors.navigationMenu.background,
borderRadius: 2,
paddingHorizontal: 3,
flexDirection: "row",
alignItems: "center",
marginBottom: 4
}}
>
<Icon
size={8}
name={item.icon}
color={
index === 0
? colors.navigationMenu.accent
: colors.navigationMenu.icon
}
/>
<View
style={{
height: 3,
width: "40%",
backgroundColor:
index === 0
? colors.navigationMenu.accent
: colors.paragraph,
borderRadius: 2,
marginLeft: 3
}}
></View>
</View>
))}
</View>
<View
style={{
height: "100%",
width: "49.5%",
backgroundColor: colors.list.background,
borderRadius: defaultBorderRadius,
paddingHorizontal: 2,
paddingRight: 6
height: 12,
width: "100%",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginTop: 3
}}
>
<View
style={{
height: 12,
width: "100%",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginTop: 3
alignItems: "center"
}}
>
<View
<Icon size={8} color={colors.list.heading} name="menu" />
<Heading
style={{
flexDirection: "row",
alignItems: "center"
marginLeft: 3
}}
color={colors.list.heading}
size={7}
>
<Icon size={8} color={colors.list.heading} name="menu" />
<Heading
style={{
marginLeft: 3
}}
color={colors.list.heading}
size={7}
>
{strings.dataTypesPluralCamelCase.note()}
</Heading>
</View>
<Icon name="magnify" color={colors.list.heading} size={7} />
{strings.dataTypesPluralCamelCase.note()}
</Heading>
</View>
</View>
<View
style={{
width: "100%",
alignItems: "flex-end",
justifyContent: "flex-end",
marginTop: DefaultAppStyles.GAP_VERTICAL_SMALL,
position: "absolute",
bottom: 6,
right: 6,
flexDirection: "row",
gap: 10
}}
>
{darkTheme.id === item.id || lightTheme.id === item.id ? (
<IconButton
name="check"
type="plain"
style={{
borderRadius: 100,
paddingHorizontal: 6,
alignSelf: "flex-end",
width: 25,
height: 25
}}
color={colors.accent}
size={16}
/>
) : null}
<Button
title={
item.colorScheme === "dark"
? strings.dark()
: strings.light()
}
type="secondaryAccented"
height={25}
buttonType={{
color: item.colorScheme === "dark" ? "black" : "#f0f0f060",
text: colors.accent
}}
style={{
borderRadius: 100,
paddingHorizontal: DefaultAppStyles.GAP,
alignSelf: "flex-end",
borderColor:
item.colorScheme === "dark"
? getColorLinearShade("#000000", 0.1, true)
: getColorLinearShade("#f0f0f0", 0.1, true)
}}
fontSize={AppFontSize.xxs}
/>
<Icon name="magnify" color={colors.list.heading} size={7} />
</View>
</View>
<Heading size={AppFontSize.sm} color={themeColors.primary.heading}>
{item.name}
</Heading>
<Paragraph
size={AppFontSize.xs}
color={themeColors.secondary?.paragraph}
<View
style={{
width: "100%",
alignItems: "flex-end",
justifyContent: "flex-end",
marginTop: DefaultAppStyles.GAP_VERTICAL_SMALL,
position: "absolute",
bottom: 6,
right: 6,
flexDirection: "row",
gap: 10
}}
>
{strings.by()} {item.authors?.[0].name}
</Paragraph>
</TouchableOpacity>
</>
);
},
[
darkTheme.id,
lightTheme.id,
themeColors.primary.heading,
themeColors.secondary?.paragraph
]
);
{darkTheme.id === item.id || lightTheme.id === item.id ? (
<IconButton
name="check"
type="plain"
style={{
borderRadius: 100,
paddingHorizontal: 6,
alignSelf: "flex-end",
width: 25,
height: 25
}}
color={colors.accent}
size={16}
/>
) : null}
<Button
title={
item.colorScheme === "dark" ? strings.dark() : strings.light()
}
type="secondaryAccented"
height={25}
buttonType={{
color: item.colorScheme === "dark" ? "black" : "#f0f0f060",
text: colors.accent
}}
style={{
borderRadius: 100,
paddingHorizontal: DefaultAppStyles.GAP,
alignSelf: "flex-end",
borderColor:
item.colorScheme === "dark"
? getColorLinearShade("#000000", 0.1, true)
: getColorLinearShade("#f0f0f0", 0.1, true)
}}
fontSize={AppFontSize.xxs}
/>
</View>
</View>
<Heading size={AppFontSize.sm} color={themeColors.primary.heading}>
{item.name}
</Heading>
<Paragraph
size={AppFontSize.xs}
color={themeColors.secondary?.paragraph}
>
{strings.by()} {item.authors?.[0].name}
</Paragraph>
</TouchableOpacity>
</>
);
};
let resetTimer: NodeJS.Timeout;
const onSearch = (text: string) => {
clearTimeout(resetTimer as NodeJS.Timeout);
@@ -346,14 +334,11 @@ function ThemeSelector() {
.flat()
.filter((theme) =>
searchQuery && searchQuery !== ""
? colorScheme === "all" || colorScheme === theme.colorScheme
: darkTheme.id !== theme.id &&
lightTheme.id !== theme.id &&
(colorScheme === "all" || colorScheme === theme.colorScheme)
? true
: darkTheme.id !== theme.id && lightTheme.id !== theme.id
) || []
);
}
return (
<>
<SheetProvider context="theme-details" />
@@ -381,28 +366,25 @@ function ThemeSelector() {
>
<View
style={{
flexDirection: "row",
gap: DefaultAppStyles.GAP_SMALL
flexDirection: "row"
}}
>
<Button
style={{
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
paddingHorizontal: DefaultAppStyles.GAP_SMALL
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
}}
type={
colorScheme === "all" || !colorScheme ? "accent" : "secondary"
colorScheme === "" || !colorScheme ? "accent" : "secondary"
}
title={strings.all()}
fontSize={AppFontSize.xs}
onPress={() => {
setColorScheme("all");
setColorScheme("");
}}
/>
<Button
style={{
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
paddingHorizontal: DefaultAppStyles.GAP_SMALL
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
}}
type={colorScheme === "dark" ? "accent" : "secondary"}
title={strings.dark()}
@@ -413,8 +395,7 @@ function ThemeSelector() {
/>
<Button
style={{
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
paddingHorizontal: DefaultAppStyles.GAP_SMALL
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
}}
fontSize={AppFontSize.xs}
type={colorScheme === "light" ? "accent" : "secondary"}
@@ -428,8 +409,7 @@ function ThemeSelector() {
<Button
title={strings.loadFromFile()}
style={{
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
paddingHorizontal: DefaultAppStyles.GAP_SMALL
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
}}
type={"secondaryAccented"}
icon="folder"
@@ -461,55 +441,14 @@ function ThemeSelector() {
ReactNativeBlobUtil.fs
.unlink(themeJsonCopiedPath)
.catch(() => {});
let json;
try {
json = JSON.parse(themeJson);
} catch (e) {
ToastManager.show({
heading: strings.invalidThemeFileFormat(),
type: "error",
context: "global",
actionText: strings.learnMore(),
func: () => {
openLinkInBrowser(
"https://help.notesnook.com/custom-themes/introduction"
);
}
});
return;
}
const json = JSON.parse(themeJson);
const result = validateTheme(json);
if (result.error) {
if (
typeof result.error === "string" &&
result.error.includes("missing from the theme")
) {
ToastManager.show({
heading: strings.themeMissingRequiredFields(),
type: "error",
context: "global",
actionText: strings.copyLogs(),
func: () => {
Clipboard.setString(result.error || "");
ToastManager.show({
heading: strings.logsCopied(),
type: "success",
context: "global"
});
}
});
} else {
ToastManager.error(new Error(result.error));
}
ToastManager.error(new Error(result.error));
return;
}
select(json, true);
} catch (e) {
if ((e as Error).message.includes("Code=3072")) {
return;
}
ToastManager.error(e as Error);
}
}}
@@ -519,21 +458,15 @@ function ThemeSelector() {
<LegendList
numColumns={2}
data={
themes.isLoading || themes.isError
data={[
...(colorScheme === "dark" || (searchQuery && searchQuery !== "")
? []
: [
...(colorScheme === "dark" ||
(searchQuery && searchQuery !== "")
? []
: [lightTheme as unknown as ThemeMetadata]),
...(colorScheme === "light" ||
(searchQuery && searchQuery !== "")
? []
: [darkTheme as unknown as ThemeMetadata]),
...getThemes()
]
}
: [lightTheme as unknown as ThemeMetadata]),
...(colorScheme === "light" || (searchQuery && searchQuery !== "")
? []
: [darkTheme as unknown as ThemeMetadata]),
...getThemes()
]}
ListEmptyComponent={
<View
style={{
@@ -555,23 +488,20 @@ function ThemeSelector() {
</View>
}
ListFooterComponent={
<View
style={{
height: 100,
width: "100%",
justifyContent: "center",
alignItems: "center"
}}
>
{themes.isError ? (
themes.isError ? (
<View
style={{
height: 100,
width: "100%",
justifyContent: "center",
alignItems: "center"
}}
>
<Paragraph color={colors.error.paragraph}>
{strings.errorLoadingThemes()}. {themes.error.message}.
</Paragraph>
) : (themes.isLoading || themes.isFetching) &&
getThemes().length ? (
<ActivityIndicator color={colors.primary.accent} />
) : null}
</View>
</View>
) : null
}
estimatedItemSize={200}
renderItem={renderItem}

View File

@@ -71,7 +71,6 @@ const routeNames = {
Archive: "Archive",
ManageTags: "ManageTags",
AddReminder: "AddReminder",
RelationsList: "RelationsList",
PayWall: "PayWall",
Wrapped: "Wrapped"
};

View File

@@ -108,13 +108,6 @@ 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;

View File

@@ -630,7 +630,6 @@
"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
@@ -644,7 +643,6 @@
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -783,7 +781,6 @@
"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
@@ -797,7 +794,6 @@
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -843,7 +839,6 @@
"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
@@ -857,7 +852,6 @@
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;

View File

@@ -1914,34 +1914,6 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- react-native-cameraroll (7.10.2):
- boost
- DoubleConversion
- fast_float
- fmt
- glog
- hermes-engine
- RCT-Folly
- RCT-Folly/Fabric
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- react-native-config (1.5.7):
- react-native-config/App (= 1.5.7)
- react-native-config/App (1.5.7):
@@ -3550,7 +3522,6 @@ DEPENDENCIES:
- react-native-background-actions (from `../node_modules/react-native-background-actions`)
- react-native-begin-background-task (from `../node_modules/react-native-begin-background-task`)
- react-native-blob-util (from `../node_modules/react-native-blob-util`)
- "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)"
- react-native-config (from `../node_modules/react-native-config`)
- react-native-date-picker (from `../node_modules/react-native-date-picker`)
- "react-native-document-picker (from `../node_modules/@react-native-documents/picker`)"
@@ -3746,8 +3717,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-begin-background-task"
react-native-blob-util:
:path: "../node_modules/react-native-blob-util"
react-native-cameraroll:
:path: "../node_modules/@react-native-camera-roll/camera-roll"
react-native-config:
:path: "../node_modules/react-native-config"
react-native-date-picker:
@@ -3972,7 +3941,6 @@ SPEC CHECKSUMS:
react-native-background-actions: 40e09df6ea8c7d2753f6c1d75f6f1eee4a9bc35f
react-native-begin-background-task: 2191f2a84b0328932a3d44db4361e666fee781bb
react-native-blob-util: 7946b7e13acf0da5e849dc2f73fcfebe1d981699
react-native-cameraroll: bb98380ee21115d5fe1ae0f8b80c86e044613746
react-native-config: 963b5efabc864cf69412e54b5de49b6a23e4af03
react-native-date-picker: 4f4f40f6e65798038bb4b1bff47890c2be69c2e6
react-native-document-picker: d624d3d9bd9311da87f6f7b64aa44f69927d8543

View File

@@ -1,6 +1,6 @@
// Production iOS build identifiers
IOS_CURRENT_PROJECT_VERSION = 2186
IOS_MARKETING_VERSION = 3.4.2
IOS_CURRENT_PROJECT_VERSION = 2184
IOS_MARKETING_VERSION = 3.3.24
IOS_MAIN_BUNDLE_ID = org.streetwriters.notesnook
IOS_WIDGET_BUNDLE_ID = org.streetwriters.notesnook.notewidget
IOS_SHARE_BUNDLE_ID = org.streetwriters.notesnook.share

View File

@@ -1,6 +1,6 @@
// Production iOS build identifiers
IOS_CURRENT_PROJECT_VERSION = 2186
IOS_MARKETING_VERSION = 3.4.2
IOS_CURRENT_PROJECT_VERSION = 2184
IOS_MARKETING_VERSION = 3.3.24
IOS_MAIN_BUNDLE_ID = org.streetwriters.notesnook
IOS_WIDGET_BUNDLE_ID = org.streetwriters.notesnook.notewidget
IOS_SHARE_BUNDLE_ID = org.streetwriters.notesnook.share

View File

@@ -1,6 +1,6 @@
// Staging iOS build identifiers
IOS_CURRENT_PROJECT_VERSION = 2186
IOS_MARKETING_VERSION = 3.4.2
IOS_CURRENT_PROJECT_VERSION = 2184
IOS_MARKETING_VERSION = 3.3.24
IOS_MAIN_BUNDLE_ID = org.streetwriters.notesnook
IOS_WIDGET_BUNDLE_ID = org.streetwriters.notesnook.notewidget
IOS_SHARE_BUNDLE_ID = org.streetwriters.notesnook.share

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
"version": "3.3.27",
"version": "3.3.23",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "3.3.27",
"version": "3.3.23",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
@@ -37,7 +37,6 @@
"@notesnook/logger": "file:../../packages/logger",
"@notesnook/theme": "file:../../packages/theme",
"@notesnook/themes-server": "file:../../servers/themes",
"@react-native-camera-roll/camera-roll": "^7.10.2",
"@react-native-clipboard/clipboard": "^1.16.3",
"@react-native-community/checkbox": "^0.5.20",
"@react-native-community/datetimepicker": "^8.4.5",
@@ -52,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.18.0",
"@trpc/client": "10.45.2",
"@trpc/react-query": "10.45.2",
"@trpc/server": "10.45.2",
"@tanstack/react-query": "^4.36.1",
"@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",
@@ -4198,18 +4197,6 @@
"node": ">=14"
}
},
"node_modules/@react-native-camera-roll/camera-roll": {
"version": "7.10.2",
"resolved": "https://registry.npmjs.org/@react-native-camera-roll/camera-roll/-/camera-roll-7.10.2.tgz",
"integrity": "sha512-XgJQJDFUycmqSX+MH7vTcRigQwEIQNLIu1GvOngCZRwlSV2mF61UzeruSmmHwkBcGnHZFXkKg9fil0FQVfyglw==",
"license": "MIT",
"engines": {
"node": ">= 18.17.0"
},
"peerDependencies": {
"react-native": ">=0.59"
}
},
"node_modules/@react-native-clipboard/clipboard": {
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.16.3.tgz",
@@ -5647,9 +5634,9 @@
}
},
"node_modules/@tanstack/query-core": {
"version": "4.18.0",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.18.0.tgz",
"integrity": "sha512-PP4mG8MD08sq64RZCqMfXMYfaj7+Oulwg7xZ/fJoEOdTZNcPIgaOkHajZvUBsNLbi/0ViMvJB4cFkL2Jg2WPbw==",
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.41.0.tgz",
"integrity": "sha512-193R4Jp9hjvlij6LryxrB5Mpbffd2L9PeWh3KlIy/hJV4SkBOfiQZ+jc5qAZLDCrdbkA5FjGj+UoDYw6TcNnyA==",
"license": "MIT",
"funding": {
"type": "github",
@@ -5657,12 +5644,12 @@
}
},
"node_modules/@tanstack/react-query": {
"version": "4.18.0",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.18.0.tgz",
"integrity": "sha512-s1kdbGMdVcfUIllzsHUqVUdktBT5uuIRgnvrqFNLjl9TSOXEoBSDrhjsGjao0INQZv8cMpQlgOh3YH9YtN6cKw==",
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.42.0.tgz",
"integrity": "sha512-j0tiofkzE3CSrYKmVRaKuwGgvCE+P2OOEDlhmfjeZf5ufcuFHwYwwgw3j08n4WYPVZ+OpsHblcFYezhKA3jDwg==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "4.18.0",
"@tanstack/query-core": "4.41.0",
"use-sync-external-store": "^1.2.0"
},
"funding": {
@@ -5670,8 +5657,8 @@
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
"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-native": "*"
},
"peerDependenciesMeta": {

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "3.3.27",
"version": "3.3.24",
"private": true,
"license": "GPL-3.0-or-later",
"scripts": {
@@ -55,7 +55,6 @@
"@notesnook/logger": "file:../../packages/logger",
"@notesnook/theme": "file:../../packages/theme",
"@notesnook/themes-server": "file:../../servers/themes",
"@react-native-camera-roll/camera-roll": "^7.10.2",
"@react-native-clipboard/clipboard": "^1.16.3",
"@react-native-community/checkbox": "^0.5.20",
"@react-native-community/datetimepicker": "^8.4.5",
@@ -70,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.18.0",
"@trpc/client": "10.45.2",
"@trpc/react-query": "10.45.2",
"@trpc/server": "10.45.2",
"@tanstack/react-query": "^4.36.1",
"@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",

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/web",
"version": "3.3.23",
"version": "3.3.20",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/web",
"version": "3.3.23",
"version": "3.3.20",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "3.3.23",
"version": "3.3.20",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",

View File

@@ -46,21 +46,7 @@ export function DayPicker(props: DayPickerProps) {
maxDate,
minDate
},
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"
},
years: { numberOfYears: 99 },
calendar: {
startDay: 0
}
@@ -100,10 +86,7 @@ export function DayPicker(props: DayPickerProps) {
<Button
variant="icon"
sx={{ p: 0 }}
{...subtractOffset(
{ months: 1 },
{ disabled: isPrevMonthBeforeMin }
)}
{...subtractOffset({ months: 1 }, { disabled: isPrevMonthBeforeMin })}
>
<ChevronLeft />
</Button>

View File

@@ -27,6 +27,7 @@ import {
Lock,
NewTab,
Note,
NoteAdd,
NoteRemove,
Pin,
Plus,
@@ -97,6 +98,7 @@ 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
@@ -185,7 +187,8 @@ export function EditorActionBar() {
activeSession &&
activeSession.type !== "new" &&
activeSession.type !== "locked" &&
activeSession.type !== "conflicted",
activeSession.type !== "conflicted" &&
!isFocusMode,
onClick: () => useEditorStore.getState().toggleProperties(),
toggled: arePropertiesVisible
},

View File

@@ -45,7 +45,6 @@ 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",
@@ -62,7 +61,6 @@ type GroupingMenuOptions = {
groupingKey: GroupingKey;
refresh: () => void;
isSearching?: boolean;
context?: Context;
};
const groupByMenu: (options: GroupingMenuOptions) => MenuItem | null = (
@@ -194,39 +192,6 @@ 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">
@@ -242,9 +207,7 @@ async function changeGroupOptions(
? "dateModified"
: groupOptions.sortBy;
}
await setGroupOptions(options, groupOptions);
await db.settings.setGroupOptions(options.groupingKey, groupOptions);
if (options.groupingKey === "search")
useSearchStore.setState({ sortOptions: groupOptions });
options.refresh();
@@ -272,7 +235,6 @@ type GroupHeaderProps = {
onSelectGroup: () => void;
isFocused: boolean;
isSearching?: boolean;
context?: Context;
};
function GroupHeader(props: GroupHeaderProps) {
const {
@@ -284,12 +246,10 @@ function GroupHeader(props: GroupHeaderProps) {
refresh,
onSelectGroup,
isFocused,
isSearching,
context
isSearching
} = props;
const [groupOptions, setGroupOptions] = useState(
getGroupOptions(context, isSearching, groupingKey)
db.settings.getGroupOptions(isSearching ? "search" : groupingKey)
);
const groupHeaderRef = useRef<HTMLDivElement>(null);
const { openMenu, target } = useMenuTrigger();
@@ -399,10 +359,8 @@ function GroupHeader(props: GroupHeaderProps) {
groupByToTitleMap[groupOptions.groupBy || "default"]
}`}
onClick={() => {
const groupOptions = getGroupOptions(
context,
isSearching,
groupingKey
const groupOptions = db.settings.getGroupOptions(
isSearching ? "search" : groupingKey
);
setGroupOptions(groupOptions);
@@ -410,8 +368,7 @@ function GroupHeader(props: GroupHeaderProps) {
groupingKey: isSearching ? "search" : groupingKey,
groupOptions,
refresh,
isSearching,
context
isSearching
};
const groupBy = groupByMenu({
...menuOptions,

View File

@@ -370,7 +370,6 @@ function ItemRenderer({
title={resolvedItem.group.title}
isFocused={index === focusedGroupIndex}
index={index}
context={itemContext}
onSelectGroup={async () => {
if (!items.groups) return;

View File

@@ -41,6 +41,7 @@ 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";
@@ -108,6 +109,7 @@ 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 = [
@@ -138,8 +140,7 @@ function EditorProperties(props: EditorPropertiesProps) {
"diff"
])
);
if (!session) return null;
if (isFocusMode || !session) return null;
return (
<Flex
sx={{

View File

@@ -48,8 +48,6 @@ 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;
@@ -202,14 +200,6 @@ 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,
@@ -441,7 +431,7 @@ export const AddReminderDialog = DialogManager.register(
}}
selected={dayjs(date).toDate()}
minDate={new Date()}
maxDate={MAX_DATE}
maxDate={new Date(new Date().getFullYear() + 99, 11, 31)}
onSelect={(day) => {
if (!day) return;
const date = getFormattedDate(day, "date");

View File

@@ -65,7 +65,6 @@ export const EditNoteCreationDateDialog = DialogManager.register(
onClose(false);
}}
title={strings.editCreationDate()}
description={`${strings.note()}: ${strings.creationDateCannotBeAfterLastEditedDate()}`}
negativeButton={{
text: strings.cancel(),
onClick: () => {
@@ -77,10 +76,14 @@ export const EditNoteCreationDateDialog = DialogManager.register(
text: strings.save(),
onClick: async () => {
try {
if (dateEdited && date.isAfter(dayjs(dateEdited), "minute")) {
if (date.isAfter(dayjs())) {
showToast("error", "Creation date cannot be in the future");
return;
}
if (dateEdited && date.isAfter(dayjs(dateEdited))) {
showToast(
"error",
strings.creationDateCannotBeAfterLastEditedDate()
"Creation date cannot be after last edited date"
);
return;
}
@@ -137,7 +140,7 @@ export const EditNoteCreationDateDialog = DialogManager.register(
width: 300
}}
selected={dayjs(date).toDate()}
maxDate={new Date(dateEdited)}
maxDate={new Date()}
onSelect={(day) => {
if (!day) return;
const date = getFormattedDate(day, "date");

View File

@@ -96,7 +96,19 @@ const features: Record<FeatureKeys, Feature> = {
)
}
]
: [],
: [
{
icon: File,
title: "Improved attachments UX",
subtitle:
"We've improved the UI/UX of attaching multiple files into the editor. The entire process is now handled in a unified dialog."
},
{
icon: InternalLink,
title: "Opening file links on desktop",
subtitle: "The NN Desktop app can now open file links (file:///)."
}
],
cta: {
title: strings.gotIt(),
icon: Checkmark,

View File

@@ -54,26 +54,19 @@ export const NoteExpiryDateDialog = DialogManager.register(
return (
<Dialog
isOpen={true}
title={strings.setExpiry()}
title={"Set Custom Expiry Date"}
onClose={() => onClose(false)}
width={400}
positiveButton={{
text: strings.done(),
onClick: async () => {
if (date.isBefore(dayjs())) {
showToast("error", strings.expiryDateMustBeInTheFuture());
return;
}
if (date.isAfter(dayjs().add(1, "year"))) {
showToast(
"error",
strings.expiryDateCannotBeMoreThan1YearInTheFuture()
);
showToast("error", "Expiry date must be in the future");
return;
}
await db.notes.setExpiryDate(date.valueOf(), noteId);
store.refresh();
showToast("success", strings.expiryDateSet());
showToast("success", "Expiry date set");
onClose(true);
}
}}

View File

@@ -714,10 +714,7 @@ export async function getUploadedFileSize(filename: string) {
headers: { Authorization: `Bearer ${token}` }
});
const contentLength = parseInt(
attachmentInfo.headers["x-object-size"] ??
attachmentInfo.headers["content-length"]
);
const contentLength = parseInt(attachmentInfo.headers["content-length"]);
return isNaN(contentLength) ? 0 : contentLength;
} catch (e) {
logger.error(e, "Failed to get uploaded file size.", { filename });

View File

@@ -57,23 +57,18 @@ 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(groupOptions)
? await notesFromContext(context).grouped(
db.settings.getGroupOptions(
context.type === "favorite"
? "favorites"
: context.type === "archive"
? "archive"
: "notes"
)
)
: undefined
});
};

View File

@@ -38,7 +38,6 @@ 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 }>
@@ -62,13 +61,6 @@ 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 (
@@ -173,7 +165,6 @@ export function Notebooks() {
)}
</Box>
<Input
ref={inputRef}
variant="clean"
placeholder="Filter notebooks..."
sx={{ borderTop: "1px solid var(--border)", mx: 0 }}

View File

@@ -23,31 +23,23 @@ 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 { useEffect, useRef, useState } from "react";
import { forwardRef, useEffect, 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
@@ -70,7 +62,6 @@ function Tags() {
Scroller={SidebarScroller}
/>
<Input
ref={inputRef}
variant="clean"
placeholder="Filter tags..."
sx={{ borderTop: "1px solid var(--border)", mx: 0 }}

View File

@@ -1,3 +0,0 @@
- Bug fixes and improvements
Thank you for using Notesnook!

View File

@@ -1,3 +0,0 @@
- Bug fixes and improvements
Thank you for using Notesnook!

View File

@@ -47,29 +47,3 @@ 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);
}));
}

View File

@@ -32,8 +32,7 @@ import {
TrashCleanupInterval,
TimeFormat,
DayFormat,
WeekFormat,
GroupingByIdKey
WeekFormat
} from "../types.js";
import { ICollection } from "./collection.js";
import { SQLCachedCollection } from "../database/sql-cached-collection.js";
@@ -65,9 +64,6 @@ 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"),
@@ -154,34 +150,6 @@ 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);
}

View File

@@ -57,7 +57,6 @@ 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>;
@@ -487,12 +486,6 @@ 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[]>;

View File

@@ -139,7 +139,6 @@ 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);
@@ -149,8 +148,6 @@ function Header({
state.canGoForward
]);
console.log(tableOfContents?.length);
return (
<div
style={{
@@ -499,29 +496,26 @@ function Header({
</span>
</MenuItem>
{tableOfContents?.length ? (
<MenuItem
value="toc"
<MenuItem
value="toc"
style={{
display: "flex",
gap: 10,
alignItems: "center"
}}
>
<TableOfContentsIcon
size={20 * settings.fontScale}
color="var(--nn_primary_icon)"
/>
<span
style={{
display: "flex",
gap: 10,
alignItems: "center"
color: "var(--nn_primary_paragraph)"
}}
>
<TableOfContentsIcon
size={20 * settings.fontScale}
color="var(--nn_primary_icon)"
/>
<span
style={{
color: "var(--nn_primary_paragraph)"
}}
>
{strings.toc()}
</span>
</MenuItem>
) : null}
{strings.toc()}
</span>
</MenuItem>
<MenuItem
value="scroll-top"
style={{

View File

@@ -113,19 +113,13 @@ function Tags(props: { settings: Settings; loading?: boolean }) {
backgroundColor:
index !== 0 ? "transparent" : "var(--nn_secondary_background)",
borderRadius: 6,
padding: "2px 4px",
height: "25px",
padding: "0px 4px",
height: "24px",
fontFamily: "Inter",
fontSize: 12,
color: "var(--nn_primary_icon)",
userSelect: "none",
WebkitUserSelect: "none",
textAlign: "left",
maxWidth: 150,
textOverflow: "ellipsis",
overflow: "hidden",
wordWrap: "break-word",
whiteSpace: "nowrap"
WebkitUserSelect: "none"
}}
onClick={(e) => {
e.preventDefault();

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Extension } from "@tiptap/core";
import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
import { Decoration, DecorationSet } from "prosemirror-view";
import {
EditorState,
Plugin,
@@ -28,7 +28,6 @@ import {
} from "prosemirror-state";
import { SearchSettings } from "../../toolbar/stores/search-store.js";
import { tiptapKeys } from "@notesnook/common";
import { toggleNodesUnderPos } from "../heading/index.js";
type DispatchFn = (tr: Transaction) => void;
declare module "@tiptap/core" {
@@ -296,11 +295,12 @@ export const SearchReplace = Extension.create<SearchOptions, SearchStorage>({
)
);
const domNode = this.editor.view.domAtPos(from).node;
scrollIntoView(domNode);
this.storage.selectedIndex = nextIndex;
tr.setMeta("isSearching", true);
tr.setMeta("selectedIndex", nextIndex);
if (dispatch) updateView(state, dispatch);
return true;
},
moveToPreviousResult:
@@ -322,8 +322,10 @@ export const SearchReplace = Extension.create<SearchOptions, SearchStorage>({
)
);
const domNode = this.editor.view.domAtPos(from).node;
scrollIntoView(domNode);
this.storage.selectedIndex = prevIndex;
tr.setMeta("isSearching", true);
tr.setMeta("selectedIndex", prevIndex);
if (dispatch) updateView(state, dispatch);
@@ -468,90 +470,14 @@ export const SearchReplace = Extension.create<SearchOptions, SearchStorage>({
decorations(state) {
return key.getState(state).results;
}
},
appendTransaction: (transactions, oldState, newState) => {
const isSearchTransaction = transactions.find((t) =>
t.getMeta("isSearching")
);
const selectedResult =
this.storage.results?.[this.storage.selectedIndex];
if (!isSearchTransaction || !selectedResult) return;
const tr = newState.tr;
scrollIntoView(this.editor.view, selectedResult.from);
if (expandCollapsedParents(tr, selectedResult.from)) {
return tr;
}
}
})
];
}
});
function expandCollapsedParents(tr: Transaction, pos: number) {
try {
let changed = false;
const $pos = tr.doc.resolve(pos);
for (let depth = 1; depth <= $pos.depth; depth++) {
const node = $pos.node(depth);
const nodePos = $pos.before(depth);
if (
(node.type.name === "callout" ||
node.type.name === "outlineListItem") &&
node.attrs.collapsed
) {
tr.setNodeAttribute(nodePos, "collapsed", false);
changed = true;
}
// expand collapsed heading that hid this node via hidden attribute
if (node.attrs.hidden) {
const parentNode = $pos.node(depth - 1);
const parentContentStart = depth === 1 ? 0 : $pos.before(depth - 1) + 1;
let collapsedHeadingPos = -1;
let collapsedHeadingLevel = -1;
parentNode.forEach((child, offset) => {
const childAbsPos = parentContentStart + offset;
if (childAbsPos >= nodePos) return;
if (
child.type.name === "heading" &&
child.attrs.collapsed &&
!child.attrs.hidden
) {
collapsedHeadingPos = childAbsPos;
collapsedHeadingLevel = child.attrs.level;
}
});
if (collapsedHeadingPos !== -1) {
tr.setNodeAttribute(collapsedHeadingPos, "collapsed", false);
toggleNodesUnderPos(
tr,
collapsedHeadingPos,
collapsedHeadingLevel,
false
);
changed = true;
}
}
}
if (changed) tr.setMeta("preventSave", true);
return changed;
} catch (e) {
console.error("Error expanding collapsed parents: ", e);
}
}
function scrollIntoView(view: EditorView, pos: number) {
function scrollIntoView(domNode: Node) {
setTimeout(() => {
const domNode = view.domAtPos(pos).node;
if ("scrollIntoView" in domNode) {
(domNode as Element).scrollIntoView({
behavior: "instant",

View File

@@ -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 { strings } from "@notesnook/intl";
import { convertUrlToEmbedUrl, isValidUrl } from "@social-embed/lib";
import { Flex, Input, Text, Textarea } from "@theme-ui/components";
import { Flex, Text } 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";
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";
type EmbedSource = "url" | "code";
export type EmbedPopupProps = {
onClose: (embed?: Embed) => void;
title: string;
@@ -38,30 +38,33 @@ 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 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;
});
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);
},
[onSizeChanged]
[height, width, onSizeChanged]
);
return (
@@ -74,9 +77,8 @@ export function EmbedPopup(props: EmbedPopupProps) {
onClick: () => {
setError(null);
let _src = src;
let _width = size.width;
let _height = size.height;
let _width = width;
let _height = height;
if (embedSource === "code") {
const document = new DOMParser().parseFromString(src, "text/html");
if (document.getElementsByTagName("iframe").length <= 0)
@@ -98,19 +100,8 @@ 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.");
}
@@ -156,7 +147,7 @@ export function EmbedPopup(props: EmbedPopupProps) {
label="width"
type="number"
placeholder={strings.width()}
defaultValue={size.width}
value={width}
sx={{
mr: 1,
fontSize: "body"
@@ -167,7 +158,7 @@ export function EmbedPopup(props: EmbedPopupProps) {
label="height"
type="number"
placeholder={strings.height()}
defaultValue={size.height}
value={height}
sx={{ fontSize: "body" }}
onChange={(e) =>
onSizeChange(undefined, e.target.valueAsNumber)

View File

@@ -25,7 +25,6 @@ import { ToolButton } from "../components/tool-button.js";
import { Editor } from "../../types.js";
import { useEditorSearchStore } from "../stores/search-store.js";
import { strings } from "@notesnook/intl";
import { inlineDebounce } from "@notesnook/common";
export type SearchReplacePopupProps = { editor: Editor };
export function SearchReplacePopup(props: SearchReplacePopupProps) {
@@ -95,7 +94,7 @@ export function SearchReplacePopup(props: SearchReplacePopupProps) {
sx={{ p: 0, fontFamily: "monospace" }}
value={searchTerm}
onChange={(e) => {
inlineDebounce("search", () => search(e.target.value), 100);
search(e.target.value);
useEditorSearchStore.setState({ searchTerm: e.target.value });
}}
onKeyDown={(e) => {

View File

@@ -1868,10 +1868,6 @@ msgstr "Copy link"
msgid "Copy link text"
msgstr "Copy link text"
#: src/strings.ts:2703
msgid "Copy logs"
msgstr "Copy logs"
#: src/strings.ts:454
msgid "Copy note"
msgstr "Copy note"
@@ -1977,10 +1973,6 @@ 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"
@@ -2775,18 +2767,6 @@ 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"
@@ -3950,10 +3930,6 @@ 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."
@@ -4317,10 +4293,6 @@ 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"
@@ -4678,10 +4650,6 @@ msgstr "Payment method"
msgid "PDF is password protected"
msgstr "PDF is password protected"
#: src/strings.ts:2705
msgid "Permission required to save QR-Code to Gallery"
msgstr "Permission required to save QR-Code to Gallery"
#: src/strings.ts:1729
msgid "phone number"
msgstr "phone number"
@@ -7281,14 +7249,6 @@ msgstr "We are creating a backup of your data. Please wait..."
msgid "We are sorry, it seems that the app crashed due to an error. You can submit a bug report below so we can fix this asap."
msgstr "We are sorry, it seems that the app crashed due to an error. You can submit a bug report below so we can fix this asap."
#: src/strings.ts:2700
msgid "We couldn't load this theme. Please make sure the file is a valid JSON theme file."
msgstr "We couldn't load this theme. Please make sure the file is a valid JSON theme file."
#: src/strings.ts:2702
msgid "We couldn't load this theme. The file appears to be incomplete or missing required theme properties."
msgstr "We couldn't load this theme. The file appears to be incomplete or missing required theme properties."
#: src/strings.ts:1390
msgid "We have sent you an email confirmation link. Please check your email inbox. If you cannot find the email, check your spam folder."
msgstr "We have sent you an email confirmation link. Please check your email inbox. If you cannot find the email, check your spam folder."

View File

@@ -1857,10 +1857,6 @@ msgstr ""
msgid "Copy link text"
msgstr ""
#: src/strings.ts:2703
msgid "Copy logs"
msgstr ""
#: src/strings.ts:454
msgid "Copy note"
msgstr ""
@@ -1966,10 +1962,6 @@ 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 ""
@@ -2764,18 +2756,6 @@ 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 ""
@@ -3930,10 +3910,6 @@ 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 ""
@@ -4297,10 +4273,6 @@ msgstr ""
msgid "Note does not exist"
msgstr ""
#: src/strings.ts:2696
msgid "Note duplicated"
msgstr ""
#: src/strings.ts:458
msgid "Note history"
msgstr ""
@@ -4652,10 +4624,6 @@ msgstr ""
msgid "PDF is password protected"
msgstr ""
#: src/strings.ts:2705
msgid "Permission required to save QR-Code to Gallery"
msgstr ""
#: src/strings.ts:1729
msgid "phone number"
msgstr ""
@@ -7229,14 +7197,6 @@ msgstr ""
#: src/strings.ts:474
msgid "We are sorry, it seems that the app crashed due to an error. You can submit a bug report below so we can fix this asap."
msgstr "<<<<<<< HEAD"
#: src/strings.ts:2700
msgid "We couldn't load this theme. Please make sure the file is a valid JSON theme file."
msgstr ""
#: src/strings.ts:2702
msgid "We couldn't load this theme. The file appears to be incomplete or missing required theme properties."
msgstr ""
#: src/strings.ts:1390

View File

@@ -2686,21 +2686,5 @@ 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.`,
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}`,
invalidThemeFileFormat: () =>
t`We couldn't load this theme. Please make sure the file is a valid JSON theme file.`,
themeMissingRequiredFields: () =>
t`We couldn't load this theme. The file appears to be incomplete or missing required theme properties.`,
copyLogs: () => t`Copy logs`,
permissionRequiredToSaveQRCode: () =>
t`Permission required to save QR-Code to Gallery`
t`File links cannot be opened in browsers. Please use the Notesnook desktop app.`
};