mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-31 11:08:47 +02:00
Compare commits
112 Commits
mobile-fix
...
3.3.0-andr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ae93dec4c | ||
|
|
93ecc9b1ac | ||
|
|
b49be89b06 | ||
|
|
23e0748137 | ||
|
|
2f6d222cca | ||
|
|
ba1cfe05c4 | ||
|
|
11702c9209 | ||
|
|
239e301042 | ||
|
|
c097c4d457 | ||
|
|
28f1f276ce | ||
|
|
9470d2b61f | ||
|
|
95795a20c2 | ||
|
|
5e05d96487 | ||
|
|
18c9912068 | ||
|
|
cd85753252 | ||
|
|
bdb135036c | ||
|
|
5d70f58a0d | ||
|
|
b7d2d66e82 | ||
|
|
6b028ef850 | ||
|
|
8f85795e53 | ||
|
|
a0b7dff5fd | ||
|
|
352018c042 | ||
|
|
99e642780e | ||
|
|
fd48684396 | ||
|
|
9b25ab8708 | ||
|
|
c725ba8530 | ||
|
|
98287fbfc4 | ||
|
|
9f24bd2250 | ||
|
|
94f2a216ad | ||
|
|
78e3ee5fd2 | ||
|
|
1ddcf44eb0 | ||
|
|
5c5fb2ba2c | ||
|
|
aa4995d871 | ||
|
|
f818c41a02 | ||
|
|
3f9d954e2f | ||
|
|
f1fcd227e7 | ||
|
|
44d910121c | ||
|
|
1258a95aad | ||
|
|
1379193065 | ||
|
|
02960fb5a9 | ||
|
|
a7fa42e944 | ||
|
|
02ab8e3938 | ||
|
|
5c120676e1 | ||
|
|
3821dc29ef | ||
|
|
3c51f73427 | ||
|
|
8db373ee34 | ||
|
|
adcb73a2fc | ||
|
|
4c97b3481d | ||
|
|
4785544e18 | ||
|
|
7812c15b48 | ||
|
|
b841c6e0eb | ||
|
|
55b6be0f15 | ||
|
|
dfe6a73ac1 | ||
|
|
afcb8835d2 | ||
|
|
9dc9f186e1 | ||
|
|
875125cfe4 | ||
|
|
32d9cdd265 | ||
|
|
09deae9dbf | ||
|
|
337582ac70 | ||
|
|
820c78ce81 | ||
|
|
565e692af8 | ||
|
|
94e4721663 | ||
|
|
8157c0812a | ||
|
|
bc36837cd0 | ||
|
|
33540b000c | ||
|
|
5a63a45d00 | ||
|
|
8fce6b5eb6 | ||
|
|
4e44661e1b | ||
|
|
81fd1c097e | ||
|
|
aaf82f359b | ||
|
|
aa7f14ae79 | ||
|
|
0ad63da4a2 | ||
|
|
2885b9ac3e | ||
|
|
a15ab99056 | ||
|
|
222cdbfa63 | ||
|
|
a1d8b34fb4 | ||
|
|
874352e753 | ||
|
|
49d1ab4be5 | ||
|
|
65a80dbd46 | ||
|
|
e55456f492 | ||
|
|
3f9fce0d34 | ||
|
|
2ae094cf1f | ||
|
|
bc224f1674 | ||
|
|
8bb1582cca | ||
|
|
d02125a93c | ||
|
|
d91ce4e936 | ||
|
|
deb902549b | ||
|
|
e41ed07805 | ||
|
|
f6e8dc0a88 | ||
|
|
044dd5ce59 | ||
|
|
6cca06fcbc | ||
|
|
b24830666c | ||
|
|
470802c3e2 | ||
|
|
7e0e1eac93 | ||
|
|
58cbf653b2 | ||
|
|
4b35f2ecd2 | ||
|
|
48d9a863ec | ||
|
|
1d616cf68f | ||
|
|
cdf6a4bb5b | ||
|
|
1b3735e2b2 | ||
|
|
850fc6b066 | ||
|
|
8a62070c8e | ||
|
|
8aa31b8380 | ||
|
|
f445a963f9 | ||
|
|
d6e3f27612 | ||
|
|
4688d74edb | ||
|
|
1985007039 | ||
|
|
fb1eb234b6 | ||
|
|
7e70cd96b9 | ||
|
|
f9a89783ed | ||
|
|
7f3c74b55b | ||
|
|
765f3a476d |
4
.github/workflows/desktop.tests.yml
vendored
4
.github/workflows/desktop.tests.yml
vendored
@@ -6,14 +6,14 @@ on:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "app/desktop/**"
|
||||
- "apps/desktop/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/desktop.tests.yml"
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "app/desktop/**"
|
||||
- "apps/desktop/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/desktop.tests.yml"
|
||||
|
||||
|
||||
4
.github/workflows/monograph.publish.yml
vendored
4
.github/workflows/monograph.publish.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
- name: Docker Setup Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
context: apps/monograph
|
||||
file: apps/monograph/Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: streetwriters/monograph:${{ steps.package_metadata.outputs.app_version }},streetwriters/monograph:latest
|
||||
cache-from: streetwriters/monograph:latest
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements:
|
||||
|
||||
Before you can do anything, you'll need to [install Node.js](https://nodejs.org/en/download/) v16 or later on your system.
|
||||
|
||||
Once you have completed the setup, the first step is to `clone` the monorepo:
|
||||
1. `clone` the monorepo:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/streetwriters/notesnook.git
|
||||
@@ -33,19 +33,28 @@ git clone https://github.com/streetwriters/notesnook.git
|
||||
cd notesnook
|
||||
```
|
||||
|
||||
Once you are inside the `./notesnook` directory, run the preparation step:
|
||||
2. Install dependencies:
|
||||
|
||||
```bash
|
||||
# this might take a while to complete
|
||||
npm install
|
||||
```
|
||||
|
||||
Now you can finally start the desktop app for development:
|
||||
3. Run the webapp for desktop environment:
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
npm run start:desktop
|
||||
```
|
||||
|
||||
4. In a separate terminal session, run the desktop app from the root of the project:
|
||||
|
||||
```bash
|
||||
npm run start:desktop
|
||||
```
|
||||
|
||||
### Release mode
|
||||
|
||||
To run the app in release mode:
|
||||
|
||||
```bash
|
||||
|
||||
4
apps/desktop/package-lock.json
generated
4
apps/desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.2.3",
|
||||
"version": "3.2.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.2.3",
|
||||
"version": "3.2.4",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.2.3",
|
||||
"version": "3.2.4",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/cjs/index.js",
|
||||
|
||||
@@ -137,6 +137,7 @@ export async function deleteFile(
|
||||
|
||||
export async function clearFileStorage() {
|
||||
try {
|
||||
await createCacheDir();
|
||||
const files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
|
||||
|
||||
@@ -221,6 +222,7 @@ export async function deleteCacheFileByName(name: string) {
|
||||
}
|
||||
|
||||
export async function deleteDCacheFiles() {
|
||||
await createCacheDir();
|
||||
const files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
for (const file of files) {
|
||||
if (file.includes("_dcache") || file.startsWith("NN_")) {
|
||||
|
||||
@@ -33,6 +33,9 @@ export function hideAuth() {
|
||||
eSendEvent(eCloseLoginDialog);
|
||||
if (initialAuthMode.current === AuthMode.welcomeSignup) {
|
||||
Navigation.replace("FluidPanelsView");
|
||||
setTimeout(() => {
|
||||
Navigation.resetRootState();
|
||||
}, 1000);
|
||||
} else {
|
||||
Navigation.goBack();
|
||||
}
|
||||
|
||||
@@ -89,6 +89,9 @@ const Auth = ({ navigation, route }) => {
|
||||
onPress={() => {
|
||||
if (initialAuthMode.current === 2) {
|
||||
Navigation.replace("FluidPanelsView");
|
||||
setTimeout(() => {
|
||||
Navigation.resetRootState();
|
||||
}, 1000);
|
||||
} else {
|
||||
Navigation.goBack();
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import { BackHandler, Platform, ViewProps } from "react-native";
|
||||
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||||
import Animated, {
|
||||
runOnJS,
|
||||
SharedValue,
|
||||
useAnimatedReaction,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
@@ -67,6 +68,7 @@ export interface TabsRef {
|
||||
setScrollEnabled: () => true;
|
||||
isDrawerOpen: () => boolean;
|
||||
node: RefObject<Animated.View>;
|
||||
tabChangedFromSwipeAction: SharedValue<boolean>;
|
||||
}
|
||||
|
||||
export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
@@ -116,6 +118,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
const isLoaded = useRef(false);
|
||||
const prevWidths = useRef(widths);
|
||||
const isIPhone = Platform.OS === "ios";
|
||||
const tabChangedFromSwipeAction = useSharedValue(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (deviceMode === "tablet" || fullscreen) {
|
||||
@@ -191,6 +194,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
: withTiming(editorPosition);
|
||||
currentTab.value = 2;
|
||||
}
|
||||
tabChangedFromSwipeAction.value = false;
|
||||
},
|
||||
goToIndex: (index: number, animated = true) => {
|
||||
if (deviceMode === "tablet") {
|
||||
@@ -210,6 +214,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
: editorPosition;
|
||||
currentTab.value = 2;
|
||||
}
|
||||
tabChangedFromSwipeAction.value = false;
|
||||
},
|
||||
unlock: () => {
|
||||
forcedLock.value = false;
|
||||
@@ -252,7 +257,8 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
},
|
||||
page: () => (currentTab.value === 1 ? "home" : "editor"),
|
||||
setScrollEnabled: () => true,
|
||||
node: node
|
||||
node: node,
|
||||
tabChangedFromSwipeAction: tabChangedFromSwipeAction
|
||||
}),
|
||||
[
|
||||
currentTab,
|
||||
@@ -262,7 +268,8 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
homePosition,
|
||||
editorPosition,
|
||||
forcedLock,
|
||||
isDrawerOpen
|
||||
isDrawerOpen,
|
||||
tabChangedFromSwipeAction
|
||||
]
|
||||
);
|
||||
|
||||
@@ -362,18 +369,20 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
isDrawerOpen.value = true;
|
||||
currentTab.value = 1;
|
||||
runOnJS(onDrawerStateChange)(true);
|
||||
tabChangedFromSwipeAction.value = true;
|
||||
return;
|
||||
} else if (!isSwipeLeft && finalValue > 100) {
|
||||
translateX.value = withSpring(homePosition, animationConfig);
|
||||
isDrawerOpen.value = false;
|
||||
currentTab.value = 1;
|
||||
runOnJS(onDrawerStateChange)(false);
|
||||
|
||||
tabChangedFromSwipeAction.value = true;
|
||||
return;
|
||||
} else if (!isSwipeLeft && finalValue < 100) {
|
||||
translateX.value = withSpring(0, animationConfig);
|
||||
isDrawerOpen.value = true;
|
||||
currentTab.value = 1;
|
||||
tabChangedFromSwipeAction.value = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -384,6 +393,8 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
translateX.value = withSpring(editorPosition, animationConfig);
|
||||
currentTab.value = 2;
|
||||
isDrawerOpen.value = false;
|
||||
|
||||
tabChangedFromSwipeAction.value = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -398,6 +409,8 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
translateX.value = withSpring(editorPosition, animationConfig);
|
||||
currentTab.value = 2;
|
||||
}
|
||||
|
||||
tabChangedFromSwipeAction.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -153,7 +153,6 @@ export const SearchResult = (props: SearchResultProps) => {
|
||||
for (let i = 0; i <= index; i++) {
|
||||
activeIndex += props.item.content[i].length;
|
||||
}
|
||||
console.log(activeIndex);
|
||||
openNote(activeIndex);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -21,13 +21,14 @@ import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||
import { ColorValues } from "../../utils/colors";
|
||||
import { eOnLoadNote } from "../../utils/events";
|
||||
import { fluidTabsRef } from "../../utils/global-refs";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { Pressable } from "../ui/pressable";
|
||||
@@ -38,8 +39,6 @@ import { DateMeta } from "./date-meta";
|
||||
import { Items } from "./items";
|
||||
import Notebooks from "./notebooks";
|
||||
import { TagStrip, Tags } from "./tags";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
|
||||
const Line = ({ top = 6, bottom = 6 }) => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -151,7 +150,8 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{item.type === "notebook" && item.description ? (
|
||||
{(item.type === "notebook" || item.type === "reminder") &&
|
||||
item.description ? (
|
||||
<Paragraph>{item.description}</Paragraph>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -65,7 +65,9 @@ const COLUMN_BAR_ITEMS: ActionId[] = [
|
||||
"edit-notebook",
|
||||
"move-notes",
|
||||
"move-notebook",
|
||||
"edit-reminder",
|
||||
"pin",
|
||||
"disable-reminder",
|
||||
"default-notebook",
|
||||
"default-tag",
|
||||
"default-homepage",
|
||||
|
||||
@@ -29,6 +29,10 @@ import { ColorTags } from "./color-tags";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import ManageTags from "../../screens/manage-tags";
|
||||
import {
|
||||
paddingTop,
|
||||
paddingVertical
|
||||
} from "deprecated-react-native-prop-types/DeprecatedLayoutPropTypes";
|
||||
|
||||
export const Tags = ({ item, close }) => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -86,7 +90,6 @@ export const TagStrip = ({ item, close }) => {
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
alignItems: "center",
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
||||
gap: 5
|
||||
}}
|
||||
>
|
||||
@@ -107,16 +110,13 @@ const TagItem = ({ tag, close }) => {
|
||||
|
||||
const style = {
|
||||
paddingHorizontal: 0,
|
||||
borderRadius: 100,
|
||||
marginTop: 0,
|
||||
backgroundColor: "transparent"
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
|
||||
};
|
||||
return (
|
||||
<Button
|
||||
onPress={onPress}
|
||||
title={"#" + tag.title}
|
||||
type="plain"
|
||||
height={20}
|
||||
fontSize={AppFontSize.xs}
|
||||
style={style}
|
||||
textStyle={{
|
||||
|
||||
@@ -95,8 +95,6 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
[context]
|
||||
);
|
||||
|
||||
console.log(data?.keyboardHandlerDisabled);
|
||||
|
||||
return !visible || !data ? null : (
|
||||
<SheetWrapper
|
||||
fwdRef={actionSheetRef}
|
||||
|
||||
@@ -310,6 +310,7 @@ const ExportNotesSheet = ({
|
||||
ToastManager.error(e as Error);
|
||||
});
|
||||
} else {
|
||||
await sleep(500);
|
||||
FileViewer.open(result?.filePath, {
|
||||
showOpenWithDialog: true,
|
||||
showAppsSuggestions: true
|
||||
@@ -338,6 +339,7 @@ const ExportNotesSheet = ({
|
||||
.getState()
|
||||
.setAppDidEnterBackgroundForAction(true);
|
||||
if (Platform.OS === "ios") {
|
||||
await sleep(500);
|
||||
Share.open({
|
||||
url: result?.fileDir + result.fileName
|
||||
}).catch(() => {
|
||||
|
||||
@@ -38,10 +38,9 @@ import { eCloseSheet, eOnNotebookUpdated } from "../../../utils/events";
|
||||
import { AppFontSize } from "../../../utils/size";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import { Dialog } from "../../dialog";
|
||||
import { Properties } from "../../properties";
|
||||
import SheetProvider from "../../sheet-provider";
|
||||
import { NotebookItem } from "../../side-menu/notebook-item";
|
||||
import { useSideMenuNotebookTreeStore } from "../../side-menu/stores";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { AddNotebookSheet } from "../add-notebook";
|
||||
@@ -60,13 +59,8 @@ export const Notebooks = (props: {
|
||||
close?: (ctx?: string) => void;
|
||||
}) => {
|
||||
const tree = useNotebookTreeStore((state) => state.tree);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const { colors } = useThemeColors();
|
||||
const [notebooks, setNotebooks] = useState<Notebook[]>();
|
||||
const [filteredNotebooks, setFilteredNotebooks] =
|
||||
React.useState<VirtualizedGrouping<Notebook>>();
|
||||
const searchTimer = React.useRef<NodeJS.Timeout>();
|
||||
const lastQuery = React.useRef<string>();
|
||||
const loadRootNotebooks = React.useCallback(async () => {
|
||||
const notebooks = await db.relations
|
||||
.from(
|
||||
@@ -95,7 +89,6 @@ export const Notebooks = (props: {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
loadRootNotebooks();
|
||||
setIsLoading(false);
|
||||
})();
|
||||
}, [loadRootNotebooks]);
|
||||
|
||||
@@ -151,7 +144,7 @@ export const Notebooks = (props: {
|
||||
height: 400
|
||||
}}
|
||||
>
|
||||
<SheetProvider context="local" />
|
||||
<Dialog context="local" />
|
||||
|
||||
<View
|
||||
style={{
|
||||
@@ -175,7 +168,7 @@ export const Notebooks = (props: {
|
||||
}}
|
||||
name="plus"
|
||||
onPress={() => {
|
||||
AddNotebookSheet.present(props.rootNotebook, undefined, "local");
|
||||
AddNotebookSheet.present(undefined, props.rootNotebook, "local");
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
@@ -244,20 +237,18 @@ const NotebookItemWrapper = React.memo(
|
||||
const onItemUpdate = React.useCallback(async () => {
|
||||
const notebook = await db.notebooks.notebook(item.notebook.id);
|
||||
if (notebook) {
|
||||
useSideMenuNotebookTreeStore
|
||||
.getState()
|
||||
.updateItem(item.notebook.id, notebook);
|
||||
useNotebookTreeStore.getState().updateItem(item.notebook.id, notebook);
|
||||
if (expanded) {
|
||||
useSideMenuNotebookTreeStore
|
||||
useNotebookTreeStore
|
||||
.getState()
|
||||
.setTree(
|
||||
await useSideMenuNotebookTreeStore
|
||||
await useNotebookTreeStore
|
||||
.getState()
|
||||
.fetchAndAdd(item.notebook.id, item.depth + 1)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
useSideMenuNotebookTreeStore.getState().removeItem(item.notebook.id);
|
||||
useNotebookTreeStore.getState().removeItem(item.notebook.id);
|
||||
}
|
||||
}, [expanded, item.depth, item.notebook.id]);
|
||||
|
||||
@@ -274,17 +265,15 @@ const NotebookItemWrapper = React.memo(
|
||||
onToggleExpanded={async () => {
|
||||
useNotebookExpandedStore.getState().setExpanded(item.notebook.id);
|
||||
if (!expanded) {
|
||||
useSideMenuNotebookTreeStore
|
||||
useNotebookTreeStore
|
||||
.getState()
|
||||
.setTree(
|
||||
await useSideMenuNotebookTreeStore
|
||||
await useNotebookTreeStore
|
||||
.getState()
|
||||
.fetchAndAdd(item.notebook.id, item.depth + 1)
|
||||
);
|
||||
} else {
|
||||
useSideMenuNotebookTreeStore
|
||||
.getState()
|
||||
.removeChildren(item.notebook.id);
|
||||
useNotebookTreeStore.getState().removeChildren(item.notebook.id);
|
||||
}
|
||||
}}
|
||||
selected={selected}
|
||||
|
||||
@@ -312,7 +312,6 @@ export const UserSheet = () => {
|
||||
icon: "logout",
|
||||
title: strings.logout(),
|
||||
onPress: async () => {
|
||||
console.log("logout");
|
||||
ref.current?.hide();
|
||||
await sleep(300);
|
||||
logoutUser();
|
||||
|
||||
@@ -49,7 +49,8 @@ export const NotebookItem = ({
|
||||
onPress,
|
||||
onLongPress,
|
||||
onAddNotebook,
|
||||
canDisableSelectionMode
|
||||
canDisableSelectionMode,
|
||||
disableExpand
|
||||
}: {
|
||||
index: number;
|
||||
item: TreeItem;
|
||||
@@ -64,6 +65,7 @@ export const NotebookItem = ({
|
||||
onLongPress?: () => void;
|
||||
onAddNotebook?: () => void;
|
||||
canDisableSelectionMode?: boolean;
|
||||
disableExpand?: boolean;
|
||||
}) => {
|
||||
const notebook = item.notebook;
|
||||
const isFocused = focused;
|
||||
@@ -166,7 +168,7 @@ export const NotebookItem = ({
|
||||
}
|
||||
testID={item.hasChildren ? `expand-notebook-${index}` : ""}
|
||||
onPress={() => {
|
||||
if (item.hasChildren) {
|
||||
if (item.hasChildren && !disableExpand) {
|
||||
onToggleExpanded?.();
|
||||
} else {
|
||||
onPress?.();
|
||||
@@ -182,7 +184,7 @@ export const NotebookItem = ({
|
||||
borderRadius: defaultBorderRadius
|
||||
}}
|
||||
name={
|
||||
!item.hasChildren
|
||||
!item.hasChildren || disableExpand
|
||||
? "book-outline"
|
||||
: expanded
|
||||
? "chevron-down"
|
||||
|
||||
@@ -20,8 +20,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { Notebook } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { FlashList } from "@shopify/flash-list";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FlatList, TextInput, View } from "react-native";
|
||||
import { TextInput, View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import NotebookScreen from "../../screens/notebook";
|
||||
import Navigation from "../../services/navigation";
|
||||
@@ -42,6 +43,7 @@ import {
|
||||
useSideMenuNotebookSelectionStore.setState({
|
||||
multiSelect: true
|
||||
});
|
||||
|
||||
export const SideMenuNotebooks = () => {
|
||||
const tree = useSideMenuNotebookTreeStore((state) => state.tree);
|
||||
const [notebooks, loading] = useNotebooks();
|
||||
@@ -64,13 +66,19 @@ export const SideMenuNotebooks = () => {
|
||||
|
||||
const updateNotebooks = React.useCallback(() => {
|
||||
if (lastQuery.current) {
|
||||
// useSideMenuNotebookTreeStore.setState({
|
||||
// isSearching: true
|
||||
// });
|
||||
db.lookup
|
||||
.notebooks(lastQuery.current)
|
||||
.sorted()
|
||||
.sorted(db.settings.getGroupOptions("notebooks"))
|
||||
.then((filtered) => {
|
||||
setFilteredNotebooks(filtered);
|
||||
});
|
||||
} else {
|
||||
// useSideMenuNotebookTreeStore.setState({
|
||||
// isSearching: false
|
||||
// });
|
||||
setFilteredNotebooks(notebooks);
|
||||
}
|
||||
}, [notebooks]);
|
||||
@@ -137,13 +145,13 @@ export const SideMenuNotebooks = () => {
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<FlatList
|
||||
<FlashList
|
||||
data={tree}
|
||||
bounces={false}
|
||||
bouncesZoom={false}
|
||||
overScrollMode="never"
|
||||
keyExtractor={(item) => item.notebook.id}
|
||||
windowSize={3}
|
||||
// keyExtractor={(item,) => item.notebook.id}
|
||||
estimatedItemSize={30}
|
||||
ListHeaderComponent={
|
||||
<View
|
||||
style={{
|
||||
@@ -196,7 +204,9 @@ const NotebookItemWrapper = React.memo(
|
||||
const expanded = useSideMenuNotebookExpandedStore(
|
||||
(state) => state.expanded[item.notebook.id]
|
||||
);
|
||||
|
||||
const disableExpand = useSideMenuNotebookTreeStore(
|
||||
(state) => state.isSearching
|
||||
);
|
||||
const selectionEnabled = useSideMenuNotebookSelectionStore(
|
||||
(state) => state.enabled
|
||||
);
|
||||
@@ -256,6 +266,7 @@ const NotebookItemWrapper = React.memo(
|
||||
.removeChildren(item.notebook.id);
|
||||
}
|
||||
}}
|
||||
disableExpand={disableExpand}
|
||||
selected={selected}
|
||||
selectionEnabled={selectionEnabled}
|
||||
selectionStore={useSideMenuNotebookSelectionStore}
|
||||
|
||||
@@ -220,7 +220,7 @@ export const SideMenuTags = () => {
|
||||
if (lastQuery.current) {
|
||||
db.lookup
|
||||
.tags(lastQuery.current.trim())
|
||||
.sorted()
|
||||
.sorted(db.settings.getGroupOptions("tags"))
|
||||
.then(async (filtered) => {
|
||||
setFilteredTags(filtered);
|
||||
});
|
||||
|
||||
@@ -154,7 +154,7 @@ const Input = ({
|
||||
isError = customValidator && value === customValidator();
|
||||
break;
|
||||
case "url":
|
||||
isError = isURL(value);
|
||||
isError = isURL(value, { allow_underscores: true });
|
||||
break;
|
||||
case "phonenumber": {
|
||||
const result = phone(value, {
|
||||
|
||||
@@ -54,6 +54,7 @@ type ButtonTypes =
|
||||
| "accent"
|
||||
| "shade"
|
||||
| "secondary"
|
||||
| "selectedAccent"
|
||||
| "secondaryAccented"
|
||||
| "inverted"
|
||||
| "white"
|
||||
@@ -126,6 +127,18 @@ const buttonTypes = (
|
||||
isDark
|
||||
)
|
||||
},
|
||||
selectedAccent: {
|
||||
primary: colors.selected.accent,
|
||||
text: colors.selected.accentForeground,
|
||||
selected: colors.selected.accent,
|
||||
borderWidth: 0.8,
|
||||
borderColor: getColorLinearShade(colors.selected.accent, 0.05, isDark),
|
||||
borderSelectedColor: getColorLinearShade(
|
||||
colors.selected.accent,
|
||||
0.05,
|
||||
isDark
|
||||
)
|
||||
},
|
||||
secondaryAccented: {
|
||||
primary: colors.secondary.background,
|
||||
text: colors.primary.accent,
|
||||
|
||||
@@ -64,8 +64,6 @@ const SheetWrapper = ({
|
||||
const isGestureNavigationEnabled =
|
||||
NotesnookModule.isGestureNavigationEnabled();
|
||||
|
||||
console.log(isGestureNavigationEnabled);
|
||||
|
||||
const style = React.useMemo(() => {
|
||||
return {
|
||||
width: largeTablet || smallTablet ? width : "100%",
|
||||
|
||||
@@ -336,7 +336,7 @@ const emailconfirmed: { id: string; steps: TStep[] } = {
|
||||
steps: [
|
||||
{
|
||||
title: strings.emailConfirmed(),
|
||||
text: strings.emailNotConfirmedDesc(),
|
||||
text: strings.emailConfirmedDesc(),
|
||||
walkthroughItem: (colors) => (
|
||||
<SvgView src={WELCOME_SVG(colors.primary.paragraph)} />
|
||||
),
|
||||
|
||||
@@ -19,9 +19,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { FeatureType } from "./components/sheets/new-feature";
|
||||
|
||||
export const features: FeatureType[] = [
|
||||
{
|
||||
title: "Improved Search",
|
||||
body: "Improved search results with better relevance and highlighting."
|
||||
}
|
||||
];
|
||||
export const features: FeatureType[] = [];
|
||||
|
||||
@@ -26,12 +26,14 @@ import { useCallback } from "react";
|
||||
* @returns Is keyboard floating or not
|
||||
*/
|
||||
const useIsFloatingKeyboard = () => {
|
||||
const { width } = useWindowDimensions();
|
||||
const { width, height } = useWindowDimensions();
|
||||
|
||||
const [floating, setFloating] = useState<boolean>(false);
|
||||
const onKeyboardWillChangeFrame = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
setFloating(event.endCoordinates.width < width);
|
||||
setFloating(
|
||||
event.endCoordinates.width === 0 || event.endCoordinates.width < width
|
||||
);
|
||||
},
|
||||
[width]
|
||||
);
|
||||
@@ -41,8 +43,13 @@ const useIsFloatingKeyboard = () => {
|
||||
"keyboardWillChangeFrame",
|
||||
onKeyboardWillChangeFrame
|
||||
);
|
||||
const sub2 = Keyboard.addListener(
|
||||
"keyboardWillShow",
|
||||
onKeyboardWillChangeFrame
|
||||
);
|
||||
return () => {
|
||||
sub1?.remove();
|
||||
sub2?.remove();
|
||||
};
|
||||
}, [onKeyboardWillChangeFrame, width]);
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ import React, {
|
||||
useState
|
||||
} from "react";
|
||||
import { Dimensions, LayoutChangeEvent, Platform, View } from "react-native";
|
||||
import Orientation, {
|
||||
OrientationType,
|
||||
useDeviceOrientationChange
|
||||
} from "react-native-orientation-locker";
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
@@ -67,10 +71,6 @@ import {
|
||||
} from "../utils/events";
|
||||
import { editorRef, fluidTabsRef } from "../utils/global-refs";
|
||||
import { AppNavigationStack } from "./navigation-stack";
|
||||
import Orientation, {
|
||||
OrientationType,
|
||||
useDeviceOrientationChange
|
||||
} from "react-native-orientation-locker";
|
||||
|
||||
const MOBILE_SIDEBAR_SIZE = 0.85;
|
||||
|
||||
@@ -535,6 +535,15 @@ const onChangeTab = async (event: { i: number; from: number }) => {
|
||||
) {
|
||||
eSendEvent(eUnlockNote);
|
||||
}
|
||||
|
||||
if (
|
||||
fluidTabsRef.current?.tabChangedFromSwipeAction.value &&
|
||||
!useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab)
|
||||
) {
|
||||
editorController?.current?.commands?.focus(
|
||||
useTabStore.getState().currentTab
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (event.from === 2) {
|
||||
deactivateKeepAwake();
|
||||
|
||||
@@ -30,6 +30,7 @@ import useNavigationStore, {
|
||||
import { useSelectionStore } from "../stores/use-selection-store";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { rootNavigatorRef } from "../utils/global-refs";
|
||||
import Navigation from "../services/navigation";
|
||||
|
||||
const RootStack = createNativeStackNavigator();
|
||||
const AppStack = createNativeStackNavigator();
|
||||
@@ -255,12 +256,18 @@ export const RootNavigation = () => {
|
||||
(state) => state.settings.introCompleted
|
||||
);
|
||||
const clearSelection = useSelectionStore((state) => state.clearSelection);
|
||||
const onStateChange = React.useCallback(() => {
|
||||
if (useSelectionStore.getState().selectionMode) {
|
||||
clearSelection();
|
||||
}
|
||||
hideAllTooltips();
|
||||
}, [clearSelection]);
|
||||
const onStateChange = React.useCallback(
|
||||
(state: any) => {
|
||||
if (useSelectionStore.getState().selectionMode) {
|
||||
clearSelection();
|
||||
}
|
||||
setTimeout(() => {
|
||||
Navigation.resetRootState(state);
|
||||
}, 1000);
|
||||
hideAllTooltips();
|
||||
},
|
||||
[clearSelection]
|
||||
);
|
||||
|
||||
return (
|
||||
<NavigationContainer onStateChange={onStateChange} ref={rootNavigatorRef}>
|
||||
|
||||
@@ -47,6 +47,7 @@ import SettingsService from "../../services/settings";
|
||||
import { useRelationStore } from "../../stores/use-relation-store";
|
||||
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
|
||||
const ReminderModes =
|
||||
Platform.OS === "ios"
|
||||
@@ -114,6 +115,7 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
!reminder ? referencedItem?.headline : reminder?.description
|
||||
);
|
||||
const titleRef = useRef<TextInput>(null);
|
||||
const descriptionRef = useRef<TextInput>(null);
|
||||
const timer = useRef<NodeJS.Timeout>();
|
||||
|
||||
const showDatePicker = () => {
|
||||
@@ -128,7 +130,7 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
timer.current = setTimeout(() => {
|
||||
hideDatePicker();
|
||||
setDate(date);
|
||||
}, 50);
|
||||
}, 10);
|
||||
};
|
||||
function nth(n: number) {
|
||||
return (
|
||||
@@ -166,8 +168,11 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
throw new Error(strings.selectDayError());
|
||||
|
||||
if (!title.current) throw new Error(strings.setTitleError());
|
||||
if (date.getTime() < Date.now() && reminderMode === "once") {
|
||||
titleRef?.current?.focus();
|
||||
if (
|
||||
date.getTime() < Date.now() &&
|
||||
reminderMode === "once" &&
|
||||
!props.route.params.reminder
|
||||
) {
|
||||
throw new Error(strings.dateError());
|
||||
}
|
||||
|
||||
@@ -242,15 +247,20 @@ 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
|
||||
}}
|
||||
onSubmit={() => {
|
||||
descriptionRef.current?.focus();
|
||||
}}
|
||||
/>
|
||||
|
||||
<Input
|
||||
defaultValue={
|
||||
reminder ? reminder?.description : referencedItem?.headline
|
||||
}
|
||||
fwdRef={descriptionRef}
|
||||
placeholder={strings.addShortNote()}
|
||||
onChangeText={(text) => (details.current = text)}
|
||||
containerStyle={{
|
||||
@@ -290,7 +300,7 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
type={
|
||||
reminderMode ===
|
||||
ReminderModes[mode as keyof typeof ReminderModes]
|
||||
? "selected"
|
||||
? "selectedAccent"
|
||||
: "plain"
|
||||
}
|
||||
onPress={() => {
|
||||
@@ -443,9 +453,10 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
>
|
||||
<DateTimePickerModal
|
||||
isVisible={isDatePickerVisible}
|
||||
mode="date"
|
||||
mode="datetime"
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={hideDatePicker}
|
||||
isDarkModeEnabled={isDark}
|
||||
is24Hour={db.settings.getTimeFormat() === "24-hour"}
|
||||
date={date || new Date(Date.now())}
|
||||
/>
|
||||
@@ -476,7 +487,9 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
width: "100%"
|
||||
}}
|
||||
title={
|
||||
date ? date.toLocaleDateString() : strings.selectDate()
|
||||
date
|
||||
? getFormattedDate(date, "date-time")
|
||||
: strings.selectDate()
|
||||
}
|
||||
type={date ? "secondaryAccented" : "secondary"}
|
||||
icon="calendar"
|
||||
@@ -578,7 +591,7 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
ReminderNotificationModes[
|
||||
mode as keyof typeof ReminderNotificationModes
|
||||
]
|
||||
? "selected"
|
||||
? "selectedAccent"
|
||||
: "plain"
|
||||
}
|
||||
onPress={() => {
|
||||
|
||||
@@ -131,7 +131,6 @@ const showActionsheet = async () => {
|
||||
.getState()
|
||||
.getNoteIdForTab(useTabStore.getState().currentTab!);
|
||||
if (noteId) {
|
||||
console.log("OPEN NOTE");
|
||||
const note = await db.notes?.note(noteId);
|
||||
Properties.present(note, false);
|
||||
} else {
|
||||
|
||||
@@ -875,7 +875,6 @@ export const useEditor = (
|
||||
}
|
||||
|
||||
lastContentChangeTime.current[note.id] = note.dateEdited;
|
||||
console.log(tab.session?.selection);
|
||||
await postMessage(
|
||||
NativeEvents.updatehtml,
|
||||
{
|
||||
|
||||
@@ -300,6 +300,8 @@ const NotebookItemWrapper = React.memo(
|
||||
const expanded = useNotebookExpandedStore(
|
||||
(state) => state.expanded[item.notebook.id]
|
||||
);
|
||||
const disableExpand = useNotebookTreeStore((state) => state.isSearching);
|
||||
|
||||
const selectionEnabled = useNotebookSelectionStore(
|
||||
(state) => state.enabled
|
||||
);
|
||||
@@ -371,6 +373,7 @@ const NotebookItemWrapper = React.memo(
|
||||
selectionEnabled={selectionEnabled}
|
||||
selectionStore={useNotebookSelectionStore}
|
||||
onItemUpdate={onItemUpdate}
|
||||
disableExpand={disableExpand}
|
||||
focused={false}
|
||||
onPress={onPress}
|
||||
onAddNotebook={() => {
|
||||
|
||||
@@ -112,7 +112,7 @@ const ManageTags = (props: NavigationProps<"ManageTags">) => {
|
||||
if (query && query.trim() !== "") {
|
||||
db.lookup
|
||||
.tags(query)
|
||||
.sorted()
|
||||
.sorted(db.settings.getGroupOptions("tags"))
|
||||
.then((items) => {
|
||||
setTags(items);
|
||||
});
|
||||
@@ -249,6 +249,7 @@ const ManageTags = (props: NavigationProps<"ManageTags">) => {
|
||||
}}
|
||||
>
|
||||
<Header title={strings.manageTags()} canGoBack />
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
|
||||
@@ -328,6 +328,8 @@ const NotebookItemWrapper = React.memo(
|
||||
const expanded = useNotebookExpandedStore(
|
||||
(state) => state.expanded[item.notebook.id]
|
||||
);
|
||||
const disableExpand = useNotebookTreeStore((state) => state.isSearching);
|
||||
|
||||
const selectionEnabled = useNotebookSelectionStore(
|
||||
(state) => state.enabled
|
||||
);
|
||||
@@ -378,6 +380,7 @@ const NotebookItemWrapper = React.memo(
|
||||
useNotebookTreeStore.getState().removeChildren(item.notebook.id);
|
||||
}
|
||||
}}
|
||||
disableExpand={disableExpand}
|
||||
selected={selected}
|
||||
selectionEnabled={selectionEnabled}
|
||||
selectionStore={useNotebookSelectionStore}
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { StackActions } from "@react-navigation/native";
|
||||
import { NavigationHelpers, StackActions } from "@react-navigation/native";
|
||||
import { NativeStackScreenProps } from "@react-navigation/native-stack";
|
||||
import { useFavoriteStore } from "../stores/use-favorite-store";
|
||||
import useNavigationStore, {
|
||||
@@ -166,6 +166,32 @@ function closeDrawer() {
|
||||
fluidTabsRef.current?.closeDrawer();
|
||||
}
|
||||
|
||||
function resetRootState(
|
||||
_state?: ReturnType<NavigationHelpers<any, any>["getState"]>
|
||||
) {
|
||||
const state = _state || rootNavigatorRef.getState();
|
||||
const focusedRoute = state.routes[state.index];
|
||||
|
||||
if (state.routes.length < 2) return;
|
||||
|
||||
const routes = state.routes.filter(
|
||||
(route) =>
|
||||
(route.name !== "Auth" && route.name !== "Welcome") ||
|
||||
route.key === focusedRoute.key
|
||||
);
|
||||
|
||||
if (routes.length === state.routes.length) return;
|
||||
if (routes.length === 0) {
|
||||
routes.push(focusedRoute);
|
||||
}
|
||||
const newIndex = routes.findIndex((route) => route.key === focusedRoute.key);
|
||||
rootNavigatorRef.reset({
|
||||
...state,
|
||||
routes: routes,
|
||||
index: newIndex
|
||||
});
|
||||
}
|
||||
|
||||
const Navigation = {
|
||||
navigate,
|
||||
goBack,
|
||||
@@ -177,7 +203,8 @@ const Navigation = {
|
||||
queueRoutesForUpdate,
|
||||
routeNeedsUpdate,
|
||||
routeNames,
|
||||
routeUpdateFunctions
|
||||
routeUpdateFunctions,
|
||||
resetRootState
|
||||
};
|
||||
|
||||
export default Navigation;
|
||||
|
||||
@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getFormattedReminderTime } from "@notesnook/common";
|
||||
import { isReminderActive, Reminder } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import notifee, {
|
||||
@@ -36,6 +37,7 @@ import dayjs, { Dayjs } from "dayjs";
|
||||
import { encodeNonAsciiHTML } from "entities";
|
||||
import { Platform } from "react-native";
|
||||
import { db, setupDatabase } from "../common/database";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
import { presentDialog } from "../components/dialog/functions";
|
||||
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
|
||||
import { editorState } from "../screens/editor/tiptap/utils";
|
||||
@@ -52,8 +54,6 @@ import { DDS } from "./device-detection";
|
||||
import { eSendEvent } from "./event-manager";
|
||||
import Navigation from "./navigation";
|
||||
import SettingsService from "./settings";
|
||||
import { getFormattedReminderTime } from "@notesnook/common";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
|
||||
let pinned: DisplayedNotification[] = [];
|
||||
|
||||
@@ -454,7 +454,7 @@ async function loadNote(id: string, jump: boolean) {
|
||||
const tab = useTabStore.getState().getTabForNote(id);
|
||||
if (useTabStore.getState().currentTab !== tab) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
note: note
|
||||
item: note
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -919,12 +919,14 @@ async function pinQuickNote(launch: boolean) {
|
||||
|
||||
async function setupReminders(checkNeedsScheduling = false) {
|
||||
const reminders = ((await db.reminders?.all.items()) as Reminder[]) || [];
|
||||
let notificationsCancelled = false;
|
||||
if (Platform.OS === "android") {
|
||||
// If the API level has changed, cancel all notifications.
|
||||
// This is to ensure that the app does not crash on Android 14+.
|
||||
const API_LEVEL = MMKV.getInt("android_apiLevel");
|
||||
if (API_LEVEL !== (Platform.Version as number)) {
|
||||
await notifee.cancelAllNotifications();
|
||||
notificationsCancelled = true;
|
||||
MMKV.setInt("android_apiLevel", Platform.Version as number);
|
||||
}
|
||||
}
|
||||
@@ -938,20 +940,22 @@ async function setupReminders(checkNeedsScheduling = false) {
|
||||
// Skip reminders that are not repeating and their trigger date is in past.
|
||||
if (reminder.mode === "once" && dayjs().isAfter(reminder.date)) continue;
|
||||
|
||||
const pending = triggers.filter((t) =>
|
||||
t.notification.id?.startsWith(reminder.id)
|
||||
);
|
||||
if (!notificationsCancelled) {
|
||||
const pending = triggers.filter((t) =>
|
||||
t.notification.id?.startsWith(reminder.id)
|
||||
);
|
||||
|
||||
let needsReschedule = pending.length === 0 ? true : false;
|
||||
if (!needsReschedule) {
|
||||
needsReschedule = pending[0].notification.data?.dateModified
|
||||
? parseInt(pending[0].notification.data?.dateModified as string) <
|
||||
reminder.dateModified
|
||||
: true;
|
||||
let needsReschedule = pending.length === 0 ? true : false;
|
||||
if (!needsReschedule) {
|
||||
needsReschedule = pending[0].notification.data?.dateModified
|
||||
? parseInt(pending[0].notification.data?.dateModified as string) <
|
||||
reminder.dateModified
|
||||
: true;
|
||||
}
|
||||
|
||||
if (!needsReschedule && checkNeedsScheduling) continue;
|
||||
}
|
||||
|
||||
if (!needsReschedule && checkNeedsScheduling) continue;
|
||||
|
||||
await scheduleNotification(reminder);
|
||||
}
|
||||
// Check for any triggers whose notifications
|
||||
|
||||
@@ -49,6 +49,7 @@ export function createNotebookTreeStores(
|
||||
) {
|
||||
const useNotebookTreeStore = create<{
|
||||
tree: TreeItem[];
|
||||
isSearching?: boolean;
|
||||
setTree: (tree: TreeItem[]) => void;
|
||||
removeItem: (id: string) => void;
|
||||
addNotebooks: (
|
||||
@@ -113,16 +114,32 @@ export function createNotebookTreeStores(
|
||||
}
|
||||
}
|
||||
|
||||
const newTreeItems = notebooks.map((notebook) => {
|
||||
return {
|
||||
parentId,
|
||||
notebook,
|
||||
depth: depth,
|
||||
hasChildren: items.some((item) => {
|
||||
return item.fromId === notebook.id;
|
||||
})
|
||||
};
|
||||
});
|
||||
const rootTreeItems = newTree.filter((item) => item.parentId === "root");
|
||||
const newTreeItems = notebooks.reduce((acc, notebook) => {
|
||||
if (!rootTreeItems.find((item) => item.notebook.id === notebook.id)) {
|
||||
acc.push({
|
||||
parentId,
|
||||
notebook,
|
||||
depth: depth,
|
||||
hasChildren: false
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, [] as TreeItem[]);
|
||||
|
||||
if (parentId === "root") {
|
||||
rootTreeItems.splice(0, 0, ...newTreeItems);
|
||||
}
|
||||
|
||||
for (const treeItem of newTreeItems) {
|
||||
treeItem.hasChildren = items.some((item) => {
|
||||
return (
|
||||
rootTreeItems.findIndex(
|
||||
(treeItem) => treeItem.notebook.id === item.toId
|
||||
) === -1 && item.fromId === treeItem.notebook.id
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
newTree.splice(parentIndex + 1, 0, ...newTreeItems);
|
||||
|
||||
@@ -130,7 +147,7 @@ export function createNotebookTreeStores(
|
||||
const expanded =
|
||||
useNotebookExpandedStore.getState().expanded[item.notebook.id] &&
|
||||
item.hasChildren;
|
||||
if (expanded) {
|
||||
if (expanded && !get().isSearching) {
|
||||
newTree = await get().fetchAndAdd(
|
||||
item.notebook.id,
|
||||
depth + 1,
|
||||
|
||||
@@ -89,10 +89,10 @@ function getSize() {
|
||||
xs: normalize(13.5) * scale.fontScale,
|
||||
sm: normalize(14.5) * scale.fontScale,
|
||||
md: normalize(16.5) * scale.fontScale,
|
||||
lg: normalize(22) * scale.fontScale,
|
||||
xl: normalize(24) * scale.fontScale,
|
||||
xxl: normalize(28) * scale.fontScale,
|
||||
xxxl: normalize(32) * scale.fontScale
|
||||
lg: normalize(20) * scale.fontScale,
|
||||
xl: normalize(22) * scale.fontScale,
|
||||
xxl: normalize(25) * scale.fontScale,
|
||||
xxxl: normalize(30) * scale.fontScale
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 3064
|
||||
versionCode 3068
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
- Improved editor UI for better usability
|
||||
- Improved UX for creating and editing reminders
|
||||
- Minor bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -5,5 +5,5 @@ rootProject.name = 'Notesnook'
|
||||
include ':app'
|
||||
includeBuild('../../node_modules/@react-native/gradle-plugin')
|
||||
|
||||
include ":lazysodium"
|
||||
project(':lazysodium').projectDir = new File(rootProject.projectDir, '../../node_modules/@ammarahmed/react-native-sodium/android/lazysodium')
|
||||
include ":lazysodium-android"
|
||||
project(':lazysodium-android').projectDir = new File(rootProject.projectDir, '../../node_modules/@ammarahmed/react-native-sodium/android/lazysodium-android/app')
|
||||
|
||||
@@ -1089,7 +1089,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2143;
|
||||
CURRENT_PROJECT_VERSION = 2147;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1163,7 +1163,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.2.8;
|
||||
MARKETING_VERSION = 3.2.12;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1194,7 +1194,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2143;
|
||||
CURRENT_PROJECT_VERSION = 2147;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1268,7 +1268,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.2.8;
|
||||
MARKETING_VERSION = 3.2.12;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1427,7 +1427,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2143;
|
||||
CURRENT_PROJECT_VERSION = 2147;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1439,7 +1439,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.2.8;
|
||||
MARKETING_VERSION = 3.2.12;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
@@ -1470,7 +1470,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2143;
|
||||
CURRENT_PROJECT_VERSION = 2147;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1483,7 +1483,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.2.8;
|
||||
MARKETING_VERSION = 3.2.12;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1513,7 +1513,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2143;
|
||||
CURRENT_PROJECT_VERSION = 2147;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1594,7 +1594,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
|
||||
MARKETING_VERSION = 3.2.8;
|
||||
MARKETING_VERSION = 3.2.12;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
@@ -1625,7 +1625,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2143;
|
||||
CURRENT_PROJECT_VERSION = 2147;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1707,7 +1707,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
|
||||
MARKETING_VERSION = 3.2.8;
|
||||
MARKETING_VERSION = 3.2.12;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -1374,7 +1374,7 @@ PODS:
|
||||
- SDWebImage (~> 5.11.1)
|
||||
- react-native-share-extension (2.9.0):
|
||||
- React
|
||||
- react-native-sodium (1.6.4):
|
||||
- react-native-sodium (1.6.5):
|
||||
- React
|
||||
- react-native-theme-switch-animation (0.6.0):
|
||||
- DoubleConversion
|
||||
@@ -2435,7 +2435,7 @@ SPEC CHECKSUMS:
|
||||
react-native-safe-area-context: 9d72abf6d8473da73033b597090a80b709c0b2f1
|
||||
react-native-screenguard: 82437eeb0086a90b5e5d7e54130bb04fb406373e
|
||||
react-native-share-extension: bcb7e466390a9e50c742f4b1019d6f181aedd7ad
|
||||
react-native-sodium: 5b55f74705005612e12838ffd583927229a65e6a
|
||||
react-native-sodium: 285eec063e4232cb67347ef6a434b85e588d38cb
|
||||
react-native-theme-switch-animation: d90fe2de0d9e87a63cd6235d98cba6e7054e9a10
|
||||
react-native-webview: 079eca50edf657503318b66687dadfb903731aa8
|
||||
React-nativeconfig: ecf4dc92c40b97e2b3f0c619938f78bfd6507b08
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
"react-native-screenguard": "^1.0.0",
|
||||
"@formatjs/intl-locale": "4.0.0",
|
||||
"@formatjs/intl-pluralrules": "5.2.14",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.4",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.5",
|
||||
"@react-native-community/datetimepicker": "^8.2.0",
|
||||
"react-native-exit-app": "github:ammarahm-ed/react-native-exit-app",
|
||||
"react-native-begin-background-task": "github:blockfirm/react-native-begin-background-task",
|
||||
|
||||
14
apps/mobile/package-lock.json
generated
14
apps/mobile/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.2.2",
|
||||
"version": "3.2.10",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.2.2",
|
||||
"version": "3.2.10",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
@@ -1098,7 +1098,7 @@
|
||||
"refractor": "^4.8.1",
|
||||
"sqlite-better-trigram": "^0.0.3",
|
||||
"sqlite-regex": "^0.2.4-alpha.1",
|
||||
"sqlite3-fts5-html": "^0.0.3",
|
||||
"sqlite3-fts5-html": "^0.0.4",
|
||||
"vitest": "2.1.8",
|
||||
"vitest-fetch-mock": "^0.2.2",
|
||||
"ws": "^8.13.0"
|
||||
@@ -28324,7 +28324,7 @@
|
||||
"@ammarahmed/react-native-eventsource": "1.1.0",
|
||||
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.0",
|
||||
"@ammarahmed/react-native-share-extension": "^2.9.0",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.4",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.5",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.11",
|
||||
"@callstack/repack": "~5.1.2",
|
||||
"@formatjs/intl-locale": "4.0.0",
|
||||
@@ -28478,9 +28478,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ammarahmed/react-native-sodium": {
|
||||
"version": "1.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.6.4.tgz",
|
||||
"integrity": "sha512-+OIBABV/8IPXVQPRxiub0WW86X/bs14GHnQ1VqtxWGv7phwfyOVzi9c1/LDeRqjCDKBAYHOv2a5PLHZb2RzeGw==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.6.5.tgz",
|
||||
"integrity": "sha512-x8YkHWHaHiB4QomjTAXyw/aLjrsFMWumJxSRrEeUOVeArVszbrwZ88B+2zAq+sPZ+HSMi/YrnrvgzbwvOCLnNA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.2.8",
|
||||
"version": "3.2.12",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
|
||||
39
apps/mobile/patches/@react-navigation+core+6.4.8.patch
Normal file
39
apps/mobile/patches/@react-navigation+core+6.4.8.patch
Normal file
@@ -0,0 +1,39 @@
|
||||
diff --git a/node_modules/@react-navigation/core/lib/commonjs/useNavigationCache.js b/node_modules/@react-navigation/core/lib/commonjs/useNavigationCache.js
|
||||
index c7f1df6..0141f28 100644
|
||||
--- a/node_modules/@react-navigation/core/lib/commonjs/useNavigationCache.js
|
||||
+++ b/node_modules/@react-navigation/core/lib/commonjs/useNavigationCache.js
|
||||
@@ -109,7 +109,7 @@ function useNavigationCache(_ref) {
|
||||
})),
|
||||
isFocused: () => {
|
||||
const state = getState();
|
||||
- if (state.routes[state.index].key !== route.key) {
|
||||
+ if (state.routes[state.index]?.key !== route.key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
diff --git a/node_modules/@react-navigation/core/lib/module/useNavigationCache.js b/node_modules/@react-navigation/core/lib/module/useNavigationCache.js
|
||||
index c4d65c6..89abe63 100644
|
||||
--- a/node_modules/@react-navigation/core/lib/module/useNavigationCache.js
|
||||
+++ b/node_modules/@react-navigation/core/lib/module/useNavigationCache.js
|
||||
@@ -100,7 +100,7 @@ export default function useNavigationCache(_ref) {
|
||||
})),
|
||||
isFocused: () => {
|
||||
const state = getState();
|
||||
- if (state.routes[state.index].key !== route.key) {
|
||||
+ if (state.routes[state.index]?.key !== route.key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
diff --git a/node_modules/@react-navigation/core/src/useNavigationCache.tsx b/node_modules/@react-navigation/core/src/useNavigationCache.tsx
|
||||
index 390120a..1b0f2d1 100644
|
||||
--- a/node_modules/@react-navigation/core/src/useNavigationCache.tsx
|
||||
+++ b/node_modules/@react-navigation/core/src/useNavigationCache.tsx
|
||||
@@ -157,7 +157,7 @@ export default function useNavigationCache<
|
||||
isFocused: () => {
|
||||
const state = getState();
|
||||
|
||||
- if (state.routes[state.index].key !== route.key) {
|
||||
+ if (state.routes[state.index]?.key !== route.key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
diff --git a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
|
||||
index 77c81f0..be34558 100644
|
||||
--- a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
|
||||
+++ b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
|
||||
@@ -156,7 +156,8 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
|
||||
if (
|
||||
Platform.OS === 'ios' &&
|
||||
- this._windowWidth !== this._keyboardEvent.endCoordinates.width
|
||||
+ this._windowWidth !== this._keyboardEvent.endCoordinates.width &&
|
||||
+ this._windowWidth > this._keyboardEvent.endCoordinates.width
|
||||
) {
|
||||
// The keyboard is not the standard bottom-of-the-screen keyboard. For example, floating keyboard on iPadOS.
|
||||
this._setBottom(0);
|
||||
diff --git a/node_modules/react-native/scripts/.packager.env b/node_modules/react-native/scripts/.packager.env
|
||||
new file mode 100644
|
||||
index 0000000..361f5fb
|
||||
--- /dev/null
|
||||
+++ b/node_modules/react-native/scripts/.packager.env
|
||||
@@ -0,0 +1 @@
|
||||
+export RCT_METRO_PORT=8081
|
||||
diff --git a/node_modules/react-native/scripts/packager.sh b/node_modules/react-native/scripts/packager.sh
|
||||
index 00d6ebc..6846832 100755
|
||||
--- a/node_modules/react-native/scripts/packager.sh
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
FROM oven/bun:1.2.2-alpine
|
||||
FROM oven/bun:1.2.21-slim
|
||||
|
||||
RUN mkdir -p /home/bun/app && chown -R bun:bun /home/bun/app
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ function getContentSecurityPolicy(nonce?: string) {
|
||||
const connect_src =
|
||||
process.env.NODE_ENV === "development"
|
||||
? "'self' ws://localhost:*"
|
||||
: "'self'";
|
||||
: "'self' https://notesnook.com/api/v1/reports/submit";
|
||||
|
||||
return (
|
||||
`script-src ${script_src} 'strict-dynamic'; ` +
|
||||
|
||||
4
apps/monograph/package-lock.json
generated
4
apps/monograph/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/monograph",
|
||||
"version": "1.2.4",
|
||||
"version": "1.2.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/monograph",
|
||||
"version": "1.2.4",
|
||||
"version": "1.2.5",
|
||||
"dependencies": {
|
||||
"@emotion/cache": "11.11.0",
|
||||
"@emotion/react": "11.11.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/monograph",
|
||||
"version": "1.2.4",
|
||||
"version": "1.2.5",
|
||||
"private": true,
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
|
||||
@@ -430,12 +430,12 @@ test("control + alt + right arrow should go to next note", async ({ page }) => {
|
||||
|
||||
await note1?.openNote();
|
||||
await note2?.openNote(true);
|
||||
await page.keyboard.press("Control+Alt+ArrowRight");
|
||||
await page.keyboard.press("ControlOrMeta+Alt+ArrowRight");
|
||||
|
||||
expect(await notes.editor.getTitle()).toBe("Note 1");
|
||||
expect(await notes.editor.getContent("text")).toBe("Note 1 content");
|
||||
|
||||
await page.keyboard.press("Control+Alt+ArrowRight");
|
||||
await page.keyboard.press("ControlOrMeta+Alt+ArrowRight");
|
||||
|
||||
expect(await notes.editor.getTitle()).toBe("Note 2");
|
||||
expect(await notes.editor.getContent("text")).toBe("Note 2 content");
|
||||
@@ -458,12 +458,12 @@ test("control + alt + left arrow should go to previous note", async ({
|
||||
|
||||
await note1?.openNote();
|
||||
await note2?.openNote(true);
|
||||
await page.keyboard.press("Control+Alt+ArrowLeft");
|
||||
await page.keyboard.press("ControlOrMeta+Alt+ArrowLeft");
|
||||
|
||||
expect(await notes.editor.getTitle()).toBe("Note 1");
|
||||
expect(await notes.editor.getContent("text")).toBe("Note 1 content");
|
||||
|
||||
await page.keyboard.press("Control+Alt+ArrowLeft");
|
||||
await page.keyboard.press("ControlOrMeta+Alt+ArrowLeft");
|
||||
|
||||
expect(await notes.editor.getTitle()).toBe("Note 2");
|
||||
expect(await notes.editor.getContent("text")).toBe("Note 2 content");
|
||||
|
||||
4
apps/web/package-lock.json
generated
4
apps/web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.2.3",
|
||||
"version": "3.2.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.2.3",
|
||||
"version": "3.2.4",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.2.3",
|
||||
"version": "3.2.4",
|
||||
"private": true,
|
||||
"main": "./src/app.js",
|
||||
"homepage": "https://notesnook.com/",
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useState, Suspense, useEffect, useRef } from "react";
|
||||
import { Suspense, useEffect, useRef } from "react";
|
||||
import { Box, Flex } from "@theme-ui/components";
|
||||
import { ScopedThemeProvider } from "./components/theme-provider";
|
||||
import useMobile from "./hooks/use-mobile";
|
||||
@@ -224,7 +224,7 @@ function DesktopAppContents() {
|
||||
initialSize={380}
|
||||
style={{ flex: 1, display: "flex" }}
|
||||
snapSize={120}
|
||||
maxSize={500}
|
||||
maxSize={1000}
|
||||
className="list-pane"
|
||||
>
|
||||
<ScopedThemeProvider
|
||||
|
||||
@@ -56,7 +56,7 @@ async function initializeDatabase(persistence: DatabasePersistence) {
|
||||
);
|
||||
await storage.migrate();
|
||||
|
||||
const multiTab = isFeatureSupported("opfs");
|
||||
const multiTab = !!globalThis.SharedWorker && isFeatureSupported("opfs");
|
||||
database.setup({
|
||||
sqliteOptions: {
|
||||
dialect: (name, init) =>
|
||||
|
||||
@@ -45,7 +45,7 @@ class _DialogManager {
|
||||
onClose: (result: any) => void;
|
||||
}
|
||||
>;
|
||||
return new Promise<Result>((resolve, reject) => {
|
||||
return new Promise<Result | false>((resolve, reject) => {
|
||||
const close = () => {
|
||||
this.openedDialogs.delete(component);
|
||||
root.unmount();
|
||||
@@ -53,7 +53,7 @@ class _DialogManager {
|
||||
};
|
||||
this.openedDialogs.set(component, () => {
|
||||
close();
|
||||
reject(new Error("Dialog force closed."));
|
||||
resolve(false);
|
||||
});
|
||||
root.render(
|
||||
<Dialog
|
||||
@@ -72,11 +72,14 @@ class _DialogManager {
|
||||
}
|
||||
|
||||
closeAll() {
|
||||
this.openedDialogs.clear();
|
||||
const dialogs = document.querySelectorAll(
|
||||
".ReactModalPortal,[data-react-modal-body-trap]"
|
||||
);
|
||||
dialogs.forEach((elem) => elem.remove());
|
||||
|
||||
for (const close of this.openedDialogs.values()) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
register<Props extends BaseDialogProps<any>>(
|
||||
|
||||
@@ -75,7 +75,10 @@ const actions: Partial<
|
||||
isCommandMode: false
|
||||
}).catch(() => {});
|
||||
},
|
||||
openSettings: () => hashNavigate("/settings", { replace: true }),
|
||||
openSettings: (e) => {
|
||||
if (isInEditor(e)) return;
|
||||
hashNavigate("/settings", { replace: true });
|
||||
},
|
||||
openKeyboardShortcuts: () => KeyboardShortcutsDialog.show({})
|
||||
};
|
||||
|
||||
|
||||
@@ -243,7 +243,8 @@ const MemoizedEditorView = React.memo(EditorView, (prev, next) => {
|
||||
prev.session.needsHydration === next.session.needsHydration &&
|
||||
prev.session.activeBlockId === next.session.activeBlockId &&
|
||||
prev.session.activeSearchResultIndex ===
|
||||
next.session.activeSearchResultIndex
|
||||
next.session.activeSearchResultIndex &&
|
||||
prev.session.nonce === next.session.nonce
|
||||
);
|
||||
});
|
||||
function EditorView({
|
||||
|
||||
@@ -247,7 +247,7 @@ function errorToString(error: unknown) {
|
||||
}
|
||||
|
||||
async function resetDatabase() {
|
||||
const multiTab = isFeatureSupported("opfs");
|
||||
const multiTab = !!globalThis.SharedWorker && isFeatureSupported("opfs");
|
||||
await useKeyStore.getState().clear();
|
||||
const dialect = createDialect({
|
||||
name: "notesnook",
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
} 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";
|
||||
import ScrollContainer from "../scroll-container";
|
||||
@@ -727,26 +728,27 @@ function Reminders({ noteId }: { noteId: string }) {
|
||||
);
|
||||
}
|
||||
function Attachments({ noteId }: { noteId: string }) {
|
||||
const nonce = useAttachmentStore((store) => store.nonce);
|
||||
const result = usePromise(
|
||||
() =>
|
||||
db.attachments
|
||||
.ofNote(noteId, "all")
|
||||
.sorted({ sortBy: "dateCreated", sortDirection: "desc" }),
|
||||
[noteId]
|
||||
[noteId, nonce]
|
||||
);
|
||||
|
||||
if (result.status !== "fulfilled" || result.value.length <= 0) return null;
|
||||
|
||||
return (
|
||||
<Section
|
||||
title={strings.dataTypesPluralCamelCase.attachment()}
|
||||
sx={{ borderTop: "1px solid var(--border)" }}
|
||||
sx={{ borderTop: "1px solid var(--border)", mt: 1 }}
|
||||
>
|
||||
<VirtualizedTable
|
||||
estimatedSize={25}
|
||||
getItemKey={(index) => result.value.key(index)}
|
||||
items={result.value.placeholders}
|
||||
style={{
|
||||
marginTop: 5,
|
||||
tableLayout: "fixed",
|
||||
width: "100%"
|
||||
}}
|
||||
|
||||
@@ -156,7 +156,8 @@ function StatusBar() {
|
||||
ml: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
display: "flex"
|
||||
display: "flex",
|
||||
height: "100%"
|
||||
}}
|
||||
>
|
||||
<Update
|
||||
@@ -182,7 +183,8 @@ function StatusBar() {
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
display: "flex"
|
||||
display: "flex",
|
||||
height: "100%"
|
||||
}}
|
||||
data-test-id="vault-unlocked"
|
||||
>
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Virtualizer } from "@tanstack/react-virtual";
|
||||
import { Flex } from "@theme-ui/components";
|
||||
import React, { useRef } from "react";
|
||||
import React, { useLayoutEffect, useRef, useState } from "react";
|
||||
import { TableVirtuoso } from "react-virtuoso";
|
||||
|
||||
export type VirtualizedTableRowProps<T, C> = {
|
||||
@@ -57,6 +57,14 @@ export function VirtualizedTable<T, C>(props: VirtualizedTableProps<T, C>) {
|
||||
style
|
||||
} = props;
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [scrollParent, setScrollParent] = useState(scrollElement);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setScrollParent(
|
||||
scrollElement ||
|
||||
containerRef.current?.closest<HTMLDivElement>(".ms-container")
|
||||
);
|
||||
}, [scrollElement]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -67,11 +75,7 @@ export function VirtualizedTable<T, C>(props: VirtualizedTableProps<T, C>) {
|
||||
<TableVirtuoso
|
||||
data={items}
|
||||
context={context}
|
||||
customScrollParent={
|
||||
scrollElement ||
|
||||
containerRef.current?.closest(".ms-container") ||
|
||||
undefined
|
||||
}
|
||||
customScrollParent={scrollParent || undefined}
|
||||
increaseViewportBy={300}
|
||||
computeItemKey={(index) => getItemKey(index)}
|
||||
defaultItemHeight={estimatedSize}
|
||||
|
||||
@@ -177,7 +177,7 @@ export function VirtualizedTree<T>(props: TreeViewProps<T>) {
|
||||
const ids =
|
||||
indices.length === nodes.length
|
||||
? nodes.map((c) => c.id)
|
||||
: (indices.map((i) => nodes[i].id).filter(Boolean) as string[]);
|
||||
: (indices.map((i) => nodes[i]?.id).filter(Boolean) as string[]);
|
||||
bulkSelect?.(ids);
|
||||
},
|
||||
focusItemAt: (index) => {
|
||||
|
||||
@@ -140,7 +140,7 @@ export function PaddleCheckout(props: PaddleCheckoutProps) {
|
||||
if (ev.origin !== PADDLE_ORIGIN) return;
|
||||
logger.debug("Paddle event received", { data: ev.data });
|
||||
const { event, event_name, callback_data } = ev.data;
|
||||
const { checkout } = callback_data;
|
||||
const { checkout } = callback_data || {};
|
||||
|
||||
if (event === PaddleEvents["Checkout.RemoveSpinner"]) setIsLoading(false);
|
||||
|
||||
@@ -165,7 +165,7 @@ export function PaddleCheckout(props: PaddleCheckoutProps) {
|
||||
|
||||
const pricingInfo = getPricingInfo(plan, {
|
||||
public_checkout_id: checkout.id,
|
||||
ip_geo_country_code: callback_data.user?.country || "US",
|
||||
ip_geo_country_code: callback_data?.user?.country || "US",
|
||||
items: [
|
||||
{
|
||||
prices: checkout.prices.customer.items,
|
||||
|
||||
@@ -219,8 +219,11 @@ export function SubscriptionStatus() {
|
||||
type: "modal",
|
||||
title: strings.redeemingGiftCode(),
|
||||
subtitle: strings.pleaseWait() + "...",
|
||||
action: () => db.subscriptions.redeemCode(giftCode)
|
||||
}).catch((e) => showToast("error", e.message));
|
||||
action: () =>
|
||||
db.subscriptions
|
||||
.redeemCode(giftCode)
|
||||
.catch((e) => showToast("error", e.message))
|
||||
});
|
||||
}
|
||||
}}
|
||||
sx={{ bg: "background" }}
|
||||
|
||||
@@ -135,13 +135,19 @@ class AttachmentStore extends BaseStore<AttachmentStore> {
|
||||
if (await db.attachments.remove(attachment.hash, false)) {
|
||||
await this.get().refresh();
|
||||
await useNoteStore.getState().refresh();
|
||||
useEditorStore.getState().closeNotes(...linkedNotes);
|
||||
for (const noteId of linkedNotes) {
|
||||
await useEditorStore.getState().openSession(noteId, {
|
||||
force: true,
|
||||
silent: useEditorStore.getState().getActiveNote()?.id !== noteId
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this._changeWorkingStatus(attachment.hash);
|
||||
if (e instanceof Error)
|
||||
showToast("error", `${strings.failedToDelete()}: ${e.message}`);
|
||||
} finally {
|
||||
this._changeWorkingStatus(attachment.hash);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -88,6 +88,10 @@ export type BaseEditorSession = {
|
||||
* The index of search result to scroll to after opening the session successfully.
|
||||
*/
|
||||
activeSearchResultIndex?: number;
|
||||
/**
|
||||
* Used for force refreshing a session.
|
||||
*/
|
||||
nonce?: number;
|
||||
};
|
||||
|
||||
export type LockedEditorSession = BaseEditorSession & {
|
||||
@@ -759,7 +763,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
activeBlockId: options.activeBlockId,
|
||||
tabId
|
||||
},
|
||||
options.silent
|
||||
options
|
||||
);
|
||||
} else if (isLocked && note.type !== "trash") {
|
||||
this.addSession(
|
||||
@@ -770,7 +774,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
activeBlockId: options.activeBlockId,
|
||||
tabId
|
||||
},
|
||||
options.silent
|
||||
options
|
||||
);
|
||||
} else {
|
||||
const content = note.contentId
|
||||
@@ -793,7 +797,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
activeSearchResultIndex: options.activeSearchResultIndex,
|
||||
tabId
|
||||
},
|
||||
options.silent
|
||||
options
|
||||
);
|
||||
} else {
|
||||
const attachmentsLength = await db.attachments
|
||||
@@ -817,7 +821,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
activeSearchResultIndex: options.activeSearchResultIndex,
|
||||
tabId
|
||||
},
|
||||
options.silent
|
||||
options
|
||||
);
|
||||
} else {
|
||||
this.addSession(
|
||||
@@ -838,7 +842,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
activeSearchResultIndex: options.activeSearchResultIndex,
|
||||
tabId
|
||||
},
|
||||
options.silent
|
||||
options
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -934,15 +938,20 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
}
|
||||
};
|
||||
|
||||
addSession = (session: EditorSession, silent = false) => {
|
||||
addSession = (
|
||||
session: EditorSession,
|
||||
options?: { force?: boolean; silent?: boolean }
|
||||
) => {
|
||||
this.set((state) => {
|
||||
const index = state.sessions.findIndex((s) => s.id === session.id);
|
||||
if (index > -1) {
|
||||
if (options?.force)
|
||||
session.nonce = (state.sessions[index]?.nonce || 0) + 1;
|
||||
state.sessions[index] = session;
|
||||
} else state.sessions.push(session);
|
||||
});
|
||||
|
||||
this.activateSession(session.id, undefined, silent);
|
||||
this.activateSession(session.id, undefined, options?.silent);
|
||||
};
|
||||
|
||||
saveSession = async (
|
||||
|
||||
@@ -27,7 +27,7 @@ import { isFeatureSupported } from "./feature-check";
|
||||
|
||||
let logger: typeof _logger = new NoopLogger();
|
||||
async function initializeLogger() {
|
||||
const multiTab = isFeatureSupported("opfs");
|
||||
const multiTab = !!globalThis.SharedWorker && isFeatureSupported("opfs");
|
||||
await initialize(
|
||||
{
|
||||
dialect: (name, init) =>
|
||||
|
||||
@@ -30,8 +30,10 @@ function Reminders() {
|
||||
useNavigate("reminders", () => store.refresh());
|
||||
const reminders = useStore((state) => state.reminders);
|
||||
const refresh = useStore((state) => state.refresh);
|
||||
const filteredItems = useSearch("reminders", (query, sortOptions) =>
|
||||
db.lookup.reminders(query).sorted(sortOptions)
|
||||
const filteredItems = useSearch(
|
||||
"reminders",
|
||||
(query, sortOptions) => db.lookup.reminders(query).sorted(sortOptions),
|
||||
[reminders]
|
||||
);
|
||||
|
||||
if (!reminders) return <ListLoader />;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Standard Notes
|
||||
---
|
||||
|
||||
# How do I import notes from Standard Notes?
|
||||
|
||||
The following steps will help you import your notes from Standard notes easily.
|
||||
|
||||
1. Open Standard Notes app on Desktop or visit [https://app.standardnotes.org](https://app.standardnotes.org) and login to your account.
|
||||
2. Select all your notes, right click, and select `Export`.
|
||||
3. Open the Notesnook (app or desktop).
|
||||
4. Go to `Settings > Notesnook Importer` and select `Standard Notes` from list of apps.
|
||||

|
||||
5. Drop the .zip file you exported earlier from Standard Notes in the box or click anywhere to open system file picker to select the backup.
|
||||

|
||||
6. Once importing completes you should see all your notes in Notesnook. If you face any issues during importing, [report it on github](https://github.com/streetwriters/notesnook).
|
||||
|
||||
## Supported formats
|
||||
|
||||
- [x] Text files
|
||||
- [x] Authentication notes
|
||||
- [x] Spreadsheets
|
||||
- [ ] Tags
|
||||
@@ -22,7 +22,7 @@ The following keyboard shortcuts will help you navigate Notesnook faster.
|
||||
| --- | --- | --- | --- |
|
||||
| Next tab | Ctrl Alt → / Ctrl Alt ⇧ → | Ctrl tab | ⌘ tab |
|
||||
| Previous tab | Ctrl Alt ← / Ctrl Alt ⇧ ← | Ctrl ⇧ tab | ⌘ ⇧ tab |
|
||||
| Command palette | Ctrl K | Ctrl K | ⌘ K |
|
||||
| Command palette | Ctrl ⇧ P | Ctrl ⇧ P | ⌘ ⇧ P |
|
||||
| Quick open | Ctrl P | Ctrl P | ⌘ P |
|
||||
| New tab | - | Ctrl T | ⌘ T |
|
||||
| Close active tab | - | Ctrl W | ⌘ W |
|
||||
@@ -60,8 +60,8 @@ The following keyboard shortcuts will help you navigate Notesnook faster.
|
||||
| Add image | Ctrl ⇧ I | Ctrl ⇧ I | ⌘ ⇧ I |
|
||||
| Toggle italic | Ctrl I | Ctrl I | ⌘ I |
|
||||
| Remove formatting in selection | Ctrl \ | Ctrl \ | ⌘ \ |
|
||||
| Insert internal link | Ctrl ⇧ L | Ctrl ⇧ L | ⌘ ⇧ L |
|
||||
| Insert link | Ctrl ⇧ K | Ctrl ⇧ K | ⌘ ⇧ K |
|
||||
| Insert internal link | Ctrl ⇧ K | Ctrl ⇧ K | ⌘ ⇧ K |
|
||||
| Insert link | Ctrl K | Ctrl K | ⌘ K |
|
||||
| Insert math block | Ctrl ⇧ M | Ctrl ⇧ M | ⌘ ⇧ M |
|
||||
| Toggle ordered list | Ctrl ⇧ 7 | Ctrl ⇧ 7 | ⌘ ⇧ 7 |
|
||||
| Toggle outline list | Ctrl ⇧ O | Ctrl ⇧ O | ⌘ ⇧ O |
|
||||
|
||||
4
fastlane/metadata/android/en-US/changelogs/15329.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/15329.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
- Fix crash on android devices upgraded to android 16
|
||||
- Minor bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
4
fastlane/metadata/android/en-US/changelogs/15334.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/15334.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
- Add scroll to top/bottom in editor
|
||||
- Minor bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
3
fastlane/metadata/android/en-US/changelogs/15339.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/15339.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- Bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
5
fastlane/metadata/android/en-US/changelogs/15344.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/15344.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
- Improved editor UI for better usability
|
||||
- Improved UX for creating and editing reminders
|
||||
- Minor bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -96,7 +96,7 @@ export const hotkeys = {
|
||||
type: "hotkeys"
|
||||
},
|
||||
openCommandPalette: {
|
||||
keys: normalizeKeys(["ctrl+k"]),
|
||||
keys: normalizeKeys(["ctrl+shift+p"]),
|
||||
description: "Command palette",
|
||||
category: "Navigation",
|
||||
type: "hotkeys"
|
||||
@@ -294,13 +294,13 @@ export const tiptapKeys = {
|
||||
type: "tiptap"
|
||||
},
|
||||
insertInternalLink: {
|
||||
keys: "Mod-Shift-L",
|
||||
keys: "Mod-Shift-K",
|
||||
description: "Insert internal link",
|
||||
category: "Editor",
|
||||
type: "tiptap"
|
||||
},
|
||||
insertLink: {
|
||||
keys: "Mod-Shift-K",
|
||||
keys: "Mod-k",
|
||||
description: "Insert link",
|
||||
category: "Editor",
|
||||
type: "tiptap"
|
||||
@@ -330,7 +330,7 @@ export const tiptapKeys = {
|
||||
type: "tiptap"
|
||||
},
|
||||
openSearch: {
|
||||
keys: "Mod-F",
|
||||
keys: "Mod-f",
|
||||
description: "Open search",
|
||||
category: "Editor",
|
||||
type: "tiptap"
|
||||
|
||||
@@ -18,10 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { databaseTest, noteTest, TEST_NOTE } from "./utils/index.js";
|
||||
import { test, expect } from "vitest";
|
||||
import { test, expect, describe } from "vitest";
|
||||
|
||||
function tag(title: string) {
|
||||
return { title };
|
||||
function tag(title: string, dateCreated?: number) {
|
||||
return { title, dateCreated };
|
||||
}
|
||||
|
||||
function color(title: string) {
|
||||
@@ -98,3 +98,50 @@ for (const type of ["tag", "color"] as const) {
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
describe("sort tags by", () => {
|
||||
const tags = ["apple", "mango", "melon", "orange", "zucchini"];
|
||||
const sortTestCases = [
|
||||
{
|
||||
sortBy: "title",
|
||||
sortDirection: "asc",
|
||||
expectedOrder: ["apple", "mango", "melon", "orange", "zucchini"]
|
||||
},
|
||||
{
|
||||
sortBy: "title",
|
||||
sortDirection: "desc",
|
||||
expectedOrder: ["zucchini", "orange", "melon", "mango", "apple"]
|
||||
},
|
||||
{
|
||||
sortBy: "dateCreated",
|
||||
sortDirection: "asc",
|
||||
expectedOrder: ["apple", "mango", "melon", "orange", "zucchini"]
|
||||
},
|
||||
{
|
||||
sortBy: "dateCreated",
|
||||
sortDirection: "desc",
|
||||
expectedOrder: ["zucchini", "orange", "melon", "mango", "apple"]
|
||||
}
|
||||
] as const;
|
||||
for (const testCase of sortTestCases) {
|
||||
test(`${testCase.sortBy} ${testCase.sortDirection}`, () =>
|
||||
databaseTest().then(async (db) => {
|
||||
let previousCreatedDate = Date.now();
|
||||
for (const title of tags) {
|
||||
await db.tags.add(tag(title, previousCreatedDate++));
|
||||
}
|
||||
|
||||
const items = await db.tags.all.grouped({
|
||||
sortBy: testCase.sortBy,
|
||||
sortDirection: testCase.sortDirection,
|
||||
groupBy: "default"
|
||||
});
|
||||
|
||||
for (let i = 0; i < testCase.expectedOrder.length; i++) {
|
||||
expect((await items.item(i)).item?.title).toEqual(
|
||||
testCase.expectedOrder[i]
|
||||
);
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -126,6 +126,16 @@ But this post was supposed to be about the "Secrets" of Typescript Generics, rig
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert HTML to markdown with leadingWhitespaces > html-to-md-leadingWhitespaces.md 1`] = `
|
||||
"two spaces
|
||||
|
||||
four spaces
|
||||
two spaces
|
||||
four spaces
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`convert HTML to markdown with outlinelists > html-to-md-outlinelists.md 1`] = `
|
||||
"Testing outline list:
|
||||
|
||||
@@ -389,6 +399,14 @@ right? Well, let's get into that.
|
||||
4. TYPE INFERENCE USING INTERFACE PROPERTIES"
|
||||
`;
|
||||
|
||||
exports[`convert HTML to text with leadingWhitespaces > html-to-txt-leadingWhitespaces.txt 1`] = `
|
||||
" two spaces
|
||||
|
||||
four spaces
|
||||
two spaces
|
||||
four spaces"
|
||||
`;
|
||||
|
||||
exports[`convert HTML to text with outlinelists > html-to-txt-outlinelists.txt 1`] = `
|
||||
"Testing outline list:
|
||||
* My outline list
|
||||
|
||||
@@ -65,7 +65,8 @@ const HTMLS = {
|
||||
tasklists: `<p>Hello</p><ul data-collapsed="false" class="checklist"><li class="checked checklist--item"><p>Task item 1</p></li><li class="checked checklist--item"><p>Task item 2</p></li><li class="checked checklist--item"><p>Task item 3</p></li><li class="checklist--item"><p>Task item 4</p><ul data-collapsed="false" class="checklist"><li class="checklist--item"><p>Sub task item 1</p></li><li class="checklist--item"><p>Sub task item 2</p></li></ul></li><li class="checklist--item"><p>Task Item 5</p></li></ul><p>Nene</p><ul><li><p>dasvsadv</p></li><li><p>adsva\`sd</p></li><li><p>vasd</p></li><li><p>vsadvdsa</p></li></ul>`,
|
||||
outlinelists: `<p >Testing outline list:</p><ul data-collapsed="false" data-type="outlineList"><li data-type="outlineListItem"><p >My outline list</p></li><li data-type="outlineListItem"><p >works</p></li><li data-type="outlineListItem"><p >but sometimes</p><ul data-collapsed="false" data-type="outlineList"><li data-type="outlineListItem"><p >It doesn't</p></li><li data-type="outlineListItem"><p >what do I do?</p></li><li data-type="outlineListItem"><p >I need to do something!</p></li></ul></li><li data-type="outlineListItem"><p >Makes no sense!</p></li><li data-type="outlineListItem"><p >Yes it doesn't!</p></li></ul>`,
|
||||
codeblock2: `<pre>hello<br></pre>`,
|
||||
singleSpacedParagraphs: `<p data-spacing="single">hello world</p><p data-spacing="single">hello world 2</p>`
|
||||
singleSpacedParagraphs: `<p data-spacing="single">hello world</p><p data-spacing="single">hello world 2</p>`,
|
||||
leadingWhitespaces: `<p data-spacing="double"> two spaces</p><p data-spacing="double"> four spaces</p><p data-spacing="single"> two spaces</p><p data-spacing="single"> four spaces</p>`
|
||||
};
|
||||
|
||||
for (const html in HTMLS) {
|
||||
|
||||
@@ -413,6 +413,22 @@ function convertHtmlToTxt(html: string, wrap = true) {
|
||||
builder.openBlock({
|
||||
leadingLineBreaks: dataSpacing == "single" ? 1 : 2
|
||||
});
|
||||
|
||||
// convert leading whitespace to NO-BREAK SPACE
|
||||
if (elem.children && elem.children.length > 0) {
|
||||
const firstChild = elem.children[0];
|
||||
if (firstChild.type === "text" && firstChild.data) {
|
||||
firstChild.data = firstChild.data.replace(
|
||||
/^([ \t]+)/,
|
||||
(match) => {
|
||||
return match
|
||||
.replace(/ /g, "\u00A0")
|
||||
.replace(/\t/g, "\u00A0\u00A0\u00A0\u00A0");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
walk(elem.children, builder);
|
||||
builder.closeBlock({
|
||||
trailingLineBreaks: 1
|
||||
|
||||
@@ -609,10 +609,10 @@ export class FilteredSelector<T extends Item> {
|
||||
sanitizeSortOptions(this.type, options);
|
||||
|
||||
const sortBy: Set<SortOptions["sortBy"]> = new Set();
|
||||
sortBy.add(options.sortBy);
|
||||
if (options.groupBy === "abc") sortBy.add("title");
|
||||
else if (options.sortBy === "title" && options.groupBy !== "none")
|
||||
sortBy.add("dateCreated");
|
||||
sortBy.add(options.sortBy);
|
||||
|
||||
return <T>(
|
||||
qb: SelectQueryBuilder<DatabaseSchema, keyof DatabaseSchema, T>
|
||||
|
||||
@@ -22,5 +22,5 @@ import { TemplateData } from "./index.js";
|
||||
export function buildText(data: TemplateData) {
|
||||
return `${data.title}
|
||||
|
||||
${data.content}`;
|
||||
${data.content}`;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
}
|
||||
|
||||
/* p { */
|
||||
/* font-family: "Inter"; */
|
||||
/* color: var(--nn_primary_paragraph) ## TODO: use fixed color */
|
||||
/* font-family: "Inter"; */
|
||||
/* color: var(--nn_primary_paragraph) ## TODO: use fixed color */
|
||||
/* } */
|
||||
|
||||
::selection {
|
||||
@@ -33,6 +33,10 @@
|
||||
color: var(--nn_primary_placeholder) !important;
|
||||
}
|
||||
|
||||
.ProseMirror > :first-child {
|
||||
margin-top: 0.4em !important;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
toBlobURL,
|
||||
usePermissionHandler
|
||||
} from "@notesnook/editor";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import FingerprintIcon from "mdi-react/FingerprintIcon";
|
||||
import {
|
||||
@@ -47,7 +48,6 @@ import StatusBar from "./statusbar";
|
||||
import Tags from "./tags";
|
||||
import TiptapEditorWrapper from "./tiptap";
|
||||
import Title from "./title";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
globalThis.toBlobURL = toBlobURL as typeof globalThis.toBlobURL;
|
||||
|
||||
@@ -380,11 +380,15 @@ const Tiptap = ({
|
||||
|
||||
const editor = editors[tab.id];
|
||||
|
||||
const firstChild = editor?.state.doc.firstChild;
|
||||
const isParagraph = firstChild?.type.name === "paragraph";
|
||||
const isFirstChildEmpty =
|
||||
!firstChild?.textContent || firstChild?.textContent?.length === 0;
|
||||
if (isParagraph && isFirstChildEmpty) {
|
||||
const firstChildNodeType = editor?.state.doc.firstChild?.type.name;
|
||||
const isSimpleNode =
|
||||
firstChildNodeType !== "image" &&
|
||||
firstChildNodeType !== "embed" &&
|
||||
firstChildNodeType !== "attachment" &&
|
||||
firstChildNodeType !== "mathBlock" &&
|
||||
firstChildNodeType !== "horizontalRule" &&
|
||||
firstChildNodeType !== "table";
|
||||
if (isSimpleNode) {
|
||||
editor?.commands.focus("end");
|
||||
return;
|
||||
}
|
||||
@@ -405,11 +409,15 @@ const Tiptap = ({
|
||||
const editor = editors[tab.id];
|
||||
const docSize = editor?.state.doc.content.size;
|
||||
if (!docSize) return;
|
||||
const lastChild = editor?.state.doc.lastChild;
|
||||
const isParagraph = lastChild?.type.name === "paragraph";
|
||||
const isLastChildEmpty =
|
||||
!lastChild?.textContent || lastChild?.textContent?.length === 0;
|
||||
if (isParagraph && isLastChildEmpty) {
|
||||
const lastChildNodeType = editor?.state.doc.lastChild?.type.name;
|
||||
const isSimpleNode =
|
||||
lastChildNodeType !== "image" &&
|
||||
lastChildNodeType !== "embed" &&
|
||||
lastChildNodeType !== "attachment" &&
|
||||
lastChildNodeType !== "mathBlock" &&
|
||||
lastChildNodeType !== "horizontalRule" &&
|
||||
lastChildNodeType !== "table";
|
||||
if (isSimpleNode) {
|
||||
editor?.commands.focus("end");
|
||||
return;
|
||||
}
|
||||
@@ -544,16 +552,35 @@ const Tiptap = ({
|
||||
<div
|
||||
onScroll={controller.scroll}
|
||||
ref={containerRef}
|
||||
id="editor-container-scroller"
|
||||
style={{
|
||||
overflowY: controller.loading ? "hidden" : "scroll",
|
||||
height: "100%",
|
||||
display: "block",
|
||||
position: "relative"
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
position: "relative",
|
||||
paddingTop: "12px"
|
||||
}}
|
||||
>
|
||||
{settings.noHeader || tab.session?.locked ? null : (
|
||||
<>
|
||||
<Tags settings={settings} loading={controller.loading} />
|
||||
<div
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "0px 16px",
|
||||
paddingBottom: "6px"
|
||||
}}
|
||||
>
|
||||
<StatusBar
|
||||
container={containerRef}
|
||||
loading={controller.loading}
|
||||
/>
|
||||
<Tags settings={settings} loading={controller.loading} />
|
||||
</div>
|
||||
|
||||
<Title
|
||||
titlePlaceholder={controller.titlePlaceholder}
|
||||
readonly={settings.readonly}
|
||||
@@ -564,11 +591,6 @@ const Tiptap = ({
|
||||
timeFormat={settings.timeFormat}
|
||||
loading={controller.loading}
|
||||
/>
|
||||
|
||||
<StatusBar
|
||||
container={containerRef}
|
||||
loading={controller.loading}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -865,9 +887,10 @@ const Tiptap = ({
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
width: "100%",
|
||||
minHeight: 300
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
minHeight: 100
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -907,7 +930,7 @@ const TiptapProvider = (): JSX.Element => {
|
||||
editorContainer.classList.add("selectable", "main-editor", "searching");
|
||||
editorContainer.style.flex = "1";
|
||||
editorContainer.style.cursor = "text";
|
||||
editorContainer.style.padding = "0px 12px";
|
||||
editorContainer.style.padding = "0px 16px";
|
||||
editorContainer.style.color = colors.primary.paragraph;
|
||||
editorContainer.style.fontSize = `${settings.fontSize}px`;
|
||||
editorContainer.style.fontFamily =
|
||||
|
||||
@@ -19,6 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { ControlledMenu, MenuItem as MenuItemInner } from "@szhsin/react-menu";
|
||||
import ArrowBackIcon from "mdi-react/ArrowBackIcon";
|
||||
import ArrowTopIcon from "mdi-react/ArrowTopIcon";
|
||||
import ArrowDownIcon from "mdi-react/ArrowDownIcon";
|
||||
import ArrowForwardIcon from "mdi-react/ArrowForwardIcon";
|
||||
import ArrowULeftTopIcon from "mdi-react/ArrowULeftTopIcon";
|
||||
import ArrowURightTopIcon from "mdi-react/ArrowURightTopIcon";
|
||||
@@ -56,6 +58,43 @@ const MenuItem = (props: any) => (
|
||||
<MenuItemInner {...props} className={menuItemClassName} />
|
||||
);
|
||||
|
||||
const iconButtonStyle: React.CSSProperties = {
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
};
|
||||
|
||||
function IconButton({
|
||||
onPress,
|
||||
children,
|
||||
fwdRef,
|
||||
style = {},
|
||||
preventDefault = false
|
||||
}: {
|
||||
onPress?: () => void;
|
||||
children: React.ReactNode;
|
||||
fwdRef?: any;
|
||||
style?: React.CSSProperties;
|
||||
preventDefault?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
onPress={onPress}
|
||||
fwdRef={fwdRef}
|
||||
preventDefault={preventDefault}
|
||||
style={{ ...iconButtonStyle, ...style }}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
const Button = ({
|
||||
onPress,
|
||||
children,
|
||||
@@ -118,7 +157,8 @@ function Header({
|
||||
backgroundColor: "var(--nn_primary_background)",
|
||||
position: "sticky",
|
||||
width: "100vw",
|
||||
zIndex: 999
|
||||
zIndex: 999,
|
||||
borderBottom: "0.5px solid var(--nn_primary_border)"
|
||||
}}
|
||||
>
|
||||
{noHeader ? null : (
|
||||
@@ -130,51 +170,39 @@ function Header({
|
||||
flexDirection: "row",
|
||||
paddingTop: insets.top,
|
||||
height: 50,
|
||||
alignItems: "center"
|
||||
alignItems: "center",
|
||||
paddingLeft: 12,
|
||||
paddingRight: 16
|
||||
}}
|
||||
id="header"
|
||||
>
|
||||
{settings.deviceMode !== "mobile" && !settings.fullscreen ? (
|
||||
<div />
|
||||
) : (
|
||||
<Button
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
post(EditorEvents.back, undefined, tab.id, tab.session?.noteId);
|
||||
}}
|
||||
preventDefault={false}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
marginLeft: 6,
|
||||
width: 40,
|
||||
height: 40,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
position: "relative"
|
||||
}}
|
||||
style={{ width: 40, height: 40 }}
|
||||
>
|
||||
<ArrowBackIcon
|
||||
size={28 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
size={25 * settings.fontScale}
|
||||
style={{ position: "absolute" }}
|
||||
color="var(--nn_primary_icon)"
|
||||
/>
|
||||
</Button>
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "row"
|
||||
flexDirection: "row",
|
||||
gap: 8
|
||||
}}
|
||||
>
|
||||
{settings.deviceMode !== "mobile" && !settings.fullscreen ? (
|
||||
<Button
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
post(
|
||||
EditorEvents.fullscreen,
|
||||
@@ -183,32 +211,17 @@ function Header({
|
||||
tab.session?.noteId
|
||||
);
|
||||
}}
|
||||
preventDefault={false}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
marginRight: 10,
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<FullscreenIcon
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
style={{ position: "absolute" }}
|
||||
color="var(--nn_primary_icon)"
|
||||
/>
|
||||
</Button>
|
||||
</IconButton>
|
||||
) : null}
|
||||
|
||||
{tab.session?.readonly ? (
|
||||
<Button
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
post(
|
||||
"editor-events:disable-readonly-mode",
|
||||
@@ -216,46 +229,20 @@ function Header({
|
||||
);
|
||||
}}
|
||||
fwdRef={btnRef}
|
||||
preventDefault={false}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_accent)",
|
||||
marginRight: 12,
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
style={{ color: "var(--nn_primary_accent)" }}
|
||||
>
|
||||
<PencilLockIcon
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
style={{ position: "absolute" }}
|
||||
color="var(--nn_primary_accent)"
|
||||
/>
|
||||
</Button>
|
||||
</IconButton>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
editor?.commands.undo();
|
||||
}}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
marginRight: 10,
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<ArrowULeftTopIcon
|
||||
color={
|
||||
@@ -264,28 +251,14 @@ function Header({
|
||||
: "var(--nn_primary_icon)"
|
||||
}
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
style={{ position: "absolute" }}
|
||||
/>
|
||||
</Button>
|
||||
</IconButton>
|
||||
|
||||
<Button
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
editor?.commands.redo();
|
||||
}}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
marginRight: 10,
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<ArrowURightTopIcon
|
||||
color={
|
||||
@@ -294,13 +267,11 @@ function Header({
|
||||
: "var(--nn_primary_icon)"
|
||||
}
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
style={{ position: "absolute" }}
|
||||
/>
|
||||
</Button>
|
||||
</IconButton>
|
||||
|
||||
<Button
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
post(
|
||||
EditorEvents.showTabs,
|
||||
@@ -309,26 +280,13 @@ function Header({
|
||||
tab.session?.noteId
|
||||
);
|
||||
}}
|
||||
preventDefault={false}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
marginRight: 12,
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
border: "2px solid var(--nn_primary_icon)",
|
||||
width: 19 * settings.fontScale,
|
||||
height: 19 * settings.fontScale,
|
||||
minWidth: 19 * settings.fontScale,
|
||||
width: 17 * settings.fontScale,
|
||||
height: 17 * settings.fontScale,
|
||||
minWidth: 17 * settings.fontScale,
|
||||
borderRadius: 5,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
@@ -346,9 +304,9 @@ function Header({
|
||||
{openedTabsCount}
|
||||
</p>
|
||||
</div>
|
||||
</Button>
|
||||
</IconButton>
|
||||
|
||||
<Button
|
||||
<IconButton
|
||||
fwdRef={btnRef}
|
||||
onPress={() => {
|
||||
if (tab.session?.locked) {
|
||||
@@ -362,38 +320,21 @@ function Header({
|
||||
setOpen(!isOpen);
|
||||
}
|
||||
}}
|
||||
preventDefault={false}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
marginRight: 12,
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
{tab.session?.locked ? (
|
||||
<DotsHorizontalIcon
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
style={{ position: "absolute" }}
|
||||
color="var(--nn_primary_icon)"
|
||||
/>
|
||||
) : (
|
||||
<DotsVerticalIcon
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
style={{ position: "absolute" }}
|
||||
color="var(--nn_primary_icon)"
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</IconButton>
|
||||
|
||||
<ControlledMenu
|
||||
align="end"
|
||||
@@ -428,6 +369,30 @@ function Header({
|
||||
tab.session?.noteId
|
||||
);
|
||||
break;
|
||||
case "scroll-top":
|
||||
{
|
||||
const element = document.getElementById(
|
||||
"editor-container-scroller"
|
||||
);
|
||||
element?.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: "smooth"
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "scroll-bottom":
|
||||
{
|
||||
const element = document.getElementById(
|
||||
"editor-container-scroller"
|
||||
);
|
||||
element?.scrollTo({
|
||||
top: element?.scrollHeight,
|
||||
left: 0,
|
||||
behavior: "smooth"
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "properties":
|
||||
post(
|
||||
EditorEvents.properties,
|
||||
@@ -444,7 +409,7 @@ function Header({
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: 10,
|
||||
gap: 8,
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
@@ -452,7 +417,7 @@ function Header({
|
||||
paddingTop: 5
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
post(
|
||||
EditorEvents.goBack,
|
||||
@@ -462,17 +427,6 @@ function Header({
|
||||
);
|
||||
setOpen(false);
|
||||
}}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<ArrowBackIcon
|
||||
color={
|
||||
@@ -481,13 +435,11 @@ function Header({
|
||||
: "var(--nn_primary_icon)"
|
||||
}
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
style={{ position: "absolute" }}
|
||||
/>
|
||||
</Button>
|
||||
</IconButton>
|
||||
|
||||
<Button
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
post(
|
||||
EditorEvents.goForward,
|
||||
@@ -497,17 +449,6 @@ function Header({
|
||||
);
|
||||
setOpen(false);
|
||||
}}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<ArrowForwardIcon
|
||||
color={
|
||||
@@ -516,37 +457,22 @@ function Header({
|
||||
: "var(--nn_primary_icon)"
|
||||
}
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
style={{ position: "absolute" }}
|
||||
/>
|
||||
</Button>
|
||||
</IconButton>
|
||||
|
||||
<Button
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
editor?.commands.startSearch();
|
||||
setOpen(false);
|
||||
}}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<MagnifyIcon
|
||||
size={28 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
size={25 * settings.fontScale}
|
||||
style={{ position: "absolute" }}
|
||||
color="var(--nn_primary_icon)"
|
||||
/>
|
||||
</Button>
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
<MenuItem
|
||||
@@ -558,7 +484,7 @@ function Header({
|
||||
}}
|
||||
>
|
||||
<PlusIcon
|
||||
size={22 * settings.fontScale}
|
||||
size={20 * settings.fontScale}
|
||||
color="var(--nn_primary_icon)"
|
||||
/>
|
||||
<span
|
||||
@@ -579,7 +505,7 @@ function Header({
|
||||
}}
|
||||
>
|
||||
<TableOfContentsIcon
|
||||
size={22 * settings.fontScale}
|
||||
size={20 * settings.fontScale}
|
||||
color="var(--nn_primary_icon)"
|
||||
/>
|
||||
<span
|
||||
@@ -590,6 +516,47 @@ function Header({
|
||||
{strings.toc()}
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
value="scroll-top"
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: 10,
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<ArrowTopIcon
|
||||
size={20 * settings.fontScale}
|
||||
color="var(--nn_primary_icon)"
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
color: "var(--nn_primary_paragraph)"
|
||||
}}
|
||||
>
|
||||
{strings.scrollToTop()}
|
||||
</span>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem
|
||||
value="scroll-bottom"
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: 10,
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<ArrowDownIcon
|
||||
size={20 * settings.fontScale}
|
||||
color="var(--nn_primary_icon)"
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
color: "var(--nn_primary_paragraph)"
|
||||
}}
|
||||
>
|
||||
{strings.scrollToBottom()}
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
value="properties"
|
||||
style={{
|
||||
@@ -599,7 +566,7 @@ function Header({
|
||||
}}
|
||||
>
|
||||
<DotsHorizontalIcon
|
||||
size={22 * settings.fontScale}
|
||||
size={20 * settings.fontScale}
|
||||
color="var(--nn_primary_icon)"
|
||||
/>
|
||||
<span
|
||||
|
||||
@@ -29,19 +29,13 @@ function StatusBar({
|
||||
container: RefObject<HTMLDivElement>;
|
||||
loading?: boolean;
|
||||
}) {
|
||||
const [status, setStatus] = useState({
|
||||
date: "",
|
||||
saved: ""
|
||||
});
|
||||
const tab = useTabContext();
|
||||
const [sticky, setSticky] = useState(false);
|
||||
const stickyRef = useRef(false);
|
||||
const prevScroll = useRef(0);
|
||||
const lastStickyChangeTime = useRef(0);
|
||||
const [showChars, setShowChars] = useState(false);
|
||||
const [words, setWords] = useState(strings.totalWords(0));
|
||||
const currentWords = useRef(words);
|
||||
const [chars, setChars] = useState(0);
|
||||
const statusBar = useRef({
|
||||
set: setStatus,
|
||||
set: () => {},
|
||||
updateWords: () => {
|
||||
const editor = editors[tab.id];
|
||||
if (!editor) return;
|
||||
@@ -57,109 +51,51 @@ function StatusBar({
|
||||
|
||||
useEffect(() => {
|
||||
globalThis.statusBars[tab.id] = statusBar;
|
||||
if (showChars) {
|
||||
editors[tab.id]?.on("selectionUpdate", (event) => {
|
||||
setChars(event.editor.extensionStorage.characterCount.characters());
|
||||
});
|
||||
}
|
||||
if (editors[tab.id]) {
|
||||
setChars(
|
||||
editors[tab.id]?.extensionStorage.characterCount.characters() || 0
|
||||
);
|
||||
}
|
||||
|
||||
return () => {
|
||||
globalThis.statusBars[tab.id] = undefined;
|
||||
};
|
||||
}, [tab.id, statusBar]);
|
||||
|
||||
const scrollState = useRef({
|
||||
isMovingUp: false,
|
||||
startingOffset: 0
|
||||
});
|
||||
const onScroll = React.useCallback((event: Event) => {
|
||||
const currentOffset = (event.target as HTMLElement)?.scrollTop;
|
||||
if (currentOffset < 200) {
|
||||
if (stickyRef.current) {
|
||||
stickyRef.current = false;
|
||||
setSticky(false);
|
||||
lastStickyChangeTime.current = Date.now();
|
||||
prevScroll.current = currentOffset;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (Date.now() - lastStickyChangeTime.current < 300) return;
|
||||
if (currentOffset > prevScroll.current) {
|
||||
if (
|
||||
!scrollState.current.startingOffset ||
|
||||
scrollState.current.isMovingUp
|
||||
) {
|
||||
scrollState.current.startingOffset = currentOffset;
|
||||
}
|
||||
scrollState.current.isMovingUp = false;
|
||||
} else {
|
||||
if (
|
||||
!scrollState.current.startingOffset ||
|
||||
!scrollState.current.isMovingUp
|
||||
) {
|
||||
scrollState.current.startingOffset = currentOffset;
|
||||
}
|
||||
scrollState.current.isMovingUp = true;
|
||||
}
|
||||
|
||||
if (scrollState.current.isMovingUp) {
|
||||
if (currentOffset < scrollState.current.startingOffset - 50) {
|
||||
if (!stickyRef.current) {
|
||||
stickyRef.current = true;
|
||||
setSticky(true);
|
||||
}
|
||||
scrollState.current.startingOffset = 0;
|
||||
}
|
||||
} else {
|
||||
if (currentOffset > scrollState.current.startingOffset + 50) {
|
||||
if (stickyRef.current) {
|
||||
stickyRef.current = false;
|
||||
setSticky(false);
|
||||
}
|
||||
scrollState.current.startingOffset = 0;
|
||||
}
|
||||
}
|
||||
lastStickyChangeTime.current = Date.now();
|
||||
prevScroll.current = currentOffset;
|
||||
}, []);
|
||||
}, [tab.id, statusBar, showChars]);
|
||||
|
||||
useEffect(() => {
|
||||
currentWords.current = words;
|
||||
}, [words]);
|
||||
|
||||
useEffect(() => {
|
||||
const node = container.current;
|
||||
node?.addEventListener("scroll", onScroll);
|
||||
return () => {
|
||||
node?.removeEventListener("scroll", onScroll);
|
||||
};
|
||||
}, [onScroll, container]);
|
||||
|
||||
const paragraphStyle: React.CSSProperties = {
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
fontSize: "12px",
|
||||
fontSize: 12,
|
||||
color: "var(--nn_secondary_paragraph)",
|
||||
marginRight: 8,
|
||||
paddingBottom: 0,
|
||||
userSelect: "none",
|
||||
pointerEvents: "none"
|
||||
userSelect: "none"
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
display: loading ? "none" : "flex",
|
||||
paddingRight: 12,
|
||||
paddingLeft: 12,
|
||||
position: sticky ? "sticky" : "relative",
|
||||
top: -3,
|
||||
backgroundColor: "var(--nn_primary_background)",
|
||||
zIndex: 10,
|
||||
justifyContent: sticky ? "center" : "flex-start",
|
||||
paddingTop: 4,
|
||||
paddingBottom: 2
|
||||
display: "flex",
|
||||
height: "25px",
|
||||
alignItems: "center"
|
||||
}}
|
||||
id="statusbar"
|
||||
>
|
||||
<p style={paragraphStyle}>{words}</p>
|
||||
<p style={paragraphStyle}>{status.date}</p>
|
||||
<p style={paragraphStyle}>{status.saved}</p>
|
||||
<p
|
||||
onMouseDown={(e) => {
|
||||
setShowChars(!showChars);
|
||||
}}
|
||||
style={paragraphStyle}
|
||||
>
|
||||
{showChars ? strings.charactersCount(chars) : words}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,14 +50,15 @@
|
||||
z-index: 999;
|
||||
list-style: none;
|
||||
user-select: none;
|
||||
padding: 0px;
|
||||
font-family: sans-serif;
|
||||
font-size: 0.95em;
|
||||
border: 1px solid var(--nn_primary_border);
|
||||
box-shadow: 1px 1px 20px 1px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 6px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--nn_secondary_background);
|
||||
min-width: 12rem;
|
||||
min-width: 13rem;
|
||||
padding: 0 !important;
|
||||
|
||||
}
|
||||
|
||||
.menuOpening {
|
||||
@@ -73,10 +74,11 @@
|
||||
.menuItem {
|
||||
cursor: pointer;
|
||||
border-radius: 0;
|
||||
padding: 1rem 0.6rem !important;
|
||||
padding: 0.5rem 0.6rem !important;
|
||||
color: var(--nn_primary_paragraph);
|
||||
font-family: "Inter";
|
||||
padding: 12px 6px;
|
||||
font-size: 0.9em;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.menuItemHover {
|
||||
|
||||
@@ -17,14 +17,13 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { strings } from "@notesnook/intl";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useTabContext } from "../hooks/useTabStore";
|
||||
import { Settings } from "../utils";
|
||||
import { EditorEvents } from "../utils/editor-events";
|
||||
import styles from "./styles.module.css";
|
||||
import { useTabContext } from "../hooks/useTabStore";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
function Tags(props: { settings: Settings; loading?: boolean }): JSX.Element {
|
||||
function Tags(props: { settings: Settings; loading?: boolean }) {
|
||||
const [tags, setTags] = useState<
|
||||
{ title: string; alias: string; id: string; type: "tag" }[]
|
||||
>([]);
|
||||
@@ -50,87 +49,85 @@ function Tags(props: { settings: Settings; loading?: boolean }): JSX.Element {
|
||||
};
|
||||
const fontScale = props.settings?.fontScale || 1;
|
||||
|
||||
return (
|
||||
return !tab.session?.noteId ? null : (
|
||||
<div
|
||||
className={styles.container}
|
||||
style={{
|
||||
padding: "0px 12px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
overflowX: "scroll",
|
||||
minHeight: "40px",
|
||||
opacity: props.loading ? 0 : 1
|
||||
minHeight: "25px",
|
||||
opacity: props.loading ? 0 : 1,
|
||||
gap: 6
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className={styles.btn}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
openManageTagsSheet();
|
||||
}}
|
||||
style={{
|
||||
border: `1px solid var(--nn_primary_border)`,
|
||||
backgroundColor: "var(--nn_secondary_background)",
|
||||
marginRight: 5,
|
||||
borderRadius: 100,
|
||||
padding: "0px 10px",
|
||||
fontFamily: "Inter",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
height: "30px",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none"
|
||||
}}
|
||||
>
|
||||
{tags.length === 0 ? (
|
||||
{tags.length === 0 ? (
|
||||
<button
|
||||
className={styles.btn}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
openManageTagsSheet();
|
||||
}}
|
||||
style={{
|
||||
border: `none`,
|
||||
backgroundColor: "transparent",
|
||||
borderRadius: 8,
|
||||
padding: "0px 6px",
|
||||
fontFamily: "Inter",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
height: "24px",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none"
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width={18 * fontScale}
|
||||
height={18 * fontScale}
|
||||
viewBox={`0 0 24 24`}
|
||||
>
|
||||
<path
|
||||
fill="var(--nn_primary_accent)"
|
||||
d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<p
|
||||
style={{
|
||||
marginRight: 4,
|
||||
fontSize: 13,
|
||||
fontSize: 12,
|
||||
marginLeft: 4,
|
||||
color: "var(--nn_primary_icon)",
|
||||
userSelect: "none"
|
||||
}}
|
||||
>
|
||||
{strings.addATag()}
|
||||
{strings.addTag()}
|
||||
</p>
|
||||
) : null}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width={20 * fontScale}
|
||||
height={20 * fontScale}
|
||||
viewBox={`0 0 24 24`}
|
||||
>
|
||||
<path
|
||||
fill="var(--nn_primary_accent)"
|
||||
d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</button>
|
||||
) : null}
|
||||
|
||||
{tags.map((tag) => (
|
||||
{tags.slice(0, 2).map((tag, index) => (
|
||||
<button
|
||||
key={tag.title}
|
||||
className={styles.btn}
|
||||
style={{
|
||||
border: "1px solid var(--nn_primary_border)",
|
||||
backgroundColor: "var(--nn_secondary_background)",
|
||||
marginRight: 5,
|
||||
borderRadius: 100,
|
||||
padding: "0px 10px",
|
||||
height: "30px",
|
||||
border: index !== 0 ? "none" : "1px solid var(--nn_primary_border)",
|
||||
backgroundColor:
|
||||
index !== 0 ? "transparent" : "var(--nn_secondary_background)",
|
||||
borderRadius: 6,
|
||||
padding: "0px 4px",
|
||||
height: "24px",
|
||||
fontFamily: "Inter",
|
||||
fontSize: 13,
|
||||
fontSize: 12,
|
||||
color: "var(--nn_primary_icon)",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none"
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
post(EditorEvents.tag, tag, tab.id, tab.session?.noteId);
|
||||
openManageTagsSheet();
|
||||
}}
|
||||
>
|
||||
#{tag.alias}
|
||||
{index > 0 ? `+${tags.length - 1}` : `#${tag.title}`}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function TiptapEditorWrapper(props: {
|
||||
sx={{
|
||||
display: props.settings.noToolbar ? "none" : "flex",
|
||||
overflowY: "hidden",
|
||||
minHeight: "50px",
|
||||
minHeight: "40px",
|
||||
...(globalThis.__PLATFORM__ === "ios" && {
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
|
||||
@@ -49,9 +49,9 @@ function Title({
|
||||
if (!titleSizeDiv.current || !titleRef.current) return;
|
||||
titleSizeDiv.current.innerText = titleRef.current.value;
|
||||
titleRef.current.style.height = `${titleSizeDiv.current.clientHeight}px`;
|
||||
titleRef.current.style.minHeight = `${titleSizeDiv.current.clientHeight}px`;
|
||||
titleSizeDiv.current.style.width = `${titleRef.current.clientWidth}px`;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (titleRef.current) {
|
||||
titleRef.current.value = title;
|
||||
@@ -88,8 +88,8 @@ function Title({
|
||||
maxWidth: "100%",
|
||||
minHeight: 40,
|
||||
opacity: 0,
|
||||
paddingRight: 10,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 16,
|
||||
paddingLeft: 16,
|
||||
fontWeight: 600,
|
||||
fontFamily: getFontById(fontFamily)?.font || "Inter",
|
||||
boxSizing: "border-box",
|
||||
@@ -119,8 +119,8 @@ function Title({
|
||||
boxSizing: "border-box",
|
||||
border: 0,
|
||||
opacity: 1,
|
||||
paddingRight: 10,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 16,
|
||||
paddingLeft: 16,
|
||||
fontWeight: 600,
|
||||
fontFamily: getFontById(fontFamily)?.font || "Inter",
|
||||
backgroundColor: "transparent",
|
||||
|
||||
@@ -32,6 +32,7 @@ type ResizerProps = {
|
||||
height?: number;
|
||||
handleColor?: SchemeColors;
|
||||
onResize: (width: number, height: number) => void;
|
||||
onResizeStop: (width: number, height: number) => void;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
export function Resizer(props: PropsWithChildren<ResizerProps>) {
|
||||
@@ -39,6 +40,7 @@ export function Resizer(props: PropsWithChildren<ResizerProps>) {
|
||||
enabled,
|
||||
selected,
|
||||
onResize,
|
||||
onResizeStop,
|
||||
width,
|
||||
height,
|
||||
children,
|
||||
@@ -92,13 +94,20 @@ export function Resizer(props: PropsWithChildren<ResizerProps>) {
|
||||
/>
|
||||
)
|
||||
}}
|
||||
onResizeStop={(_e, _direction, ref) => {
|
||||
onResize={(_e, _direction, ref) => {
|
||||
try {
|
||||
onResize(ref.clientWidth, ref.clientHeight);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}}
|
||||
onResizeStop={(_e, _direction, ref) => {
|
||||
try {
|
||||
onResizeStop(ref.clientWidth, ref.clientHeight);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}}
|
||||
onResizeStart={(e) => e.preventDefault()}
|
||||
lockAspectRatio={true}
|
||||
>
|
||||
|
||||
@@ -26,7 +26,8 @@ import {
|
||||
import {
|
||||
MenuPresenter,
|
||||
MenuPresenterProps,
|
||||
PopupPresenter
|
||||
PopupPresenter,
|
||||
PopupPresenterProps
|
||||
} from "@notesnook/ui";
|
||||
import { getPopupContainer } from "../../toolbar/utils/dom.js";
|
||||
|
||||
@@ -51,7 +52,8 @@ export function MobileOnly(props: PropsWithChildren<unknown>) {
|
||||
|
||||
export type PopupType = "sheet" | "menu" | "none" | "popup";
|
||||
export type ResponsivePresenterProps = MenuPresenterProps &
|
||||
ActionSheetPresenterProps & {
|
||||
ActionSheetPresenterProps &
|
||||
PopupPresenterProps & {
|
||||
mobile?: PopupType;
|
||||
desktop?: PopupType;
|
||||
};
|
||||
|
||||
@@ -51,6 +51,10 @@ export function ImageComponent(
|
||||
const [bloburl, setBloburl] = useState<string | undefined>(
|
||||
toBlobURL("", "image", mime, hash)
|
||||
);
|
||||
const [resizing, setResizing] = useState<{
|
||||
width: number;
|
||||
height: number;
|
||||
} | null>(null);
|
||||
const controllerRef = useRef(new AbortController());
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
@@ -123,6 +127,10 @@ export function ImageComponent(
|
||||
width={size.width}
|
||||
height={size.height}
|
||||
onResize={(width, height) => {
|
||||
setResizing({ width, height });
|
||||
}}
|
||||
onResizeStop={(width, height) => {
|
||||
setResizing(null);
|
||||
editor.commands.setImageSize({ width, height });
|
||||
}}
|
||||
>
|
||||
@@ -165,6 +173,26 @@ export function ImageComponent(
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{Boolean(resizing) && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: -30,
|
||||
left: 0,
|
||||
zIndex: 9999,
|
||||
background: "var(--background-secondary)",
|
||||
px: 2,
|
||||
py: 1,
|
||||
borderRadius: "default"
|
||||
}}
|
||||
>
|
||||
<Text variant="subBody" sx={{ fontWeight: "bold" }}>
|
||||
{resizing?.width}
|
||||
{" × "}
|
||||
{resizing?.height}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</DesktopOnly>
|
||||
{progress ? (
|
||||
<Flex
|
||||
|
||||
@@ -232,9 +232,10 @@ export const Link = Mark.create<LinkOptions>({
|
||||
}
|
||||
}),
|
||||
markPasteRule({
|
||||
find: (text) => {
|
||||
find: (text, ev) => {
|
||||
const foundLinks: PasteRuleMatch[] = [];
|
||||
|
||||
const html = ev?.clipboardData?.getData("text/html");
|
||||
if (html && html.includes("<a")) return [];
|
||||
if (text) {
|
||||
const links = find(text).filter((item) => item.isLink);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`paste text > with html link 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><p><a target="_blank" rel="noopener noreferrer nofollow" spellcheck="false" href="nn://note/68798972093c8c6ea22efca2">example.py</a></p></div></div>"`;
|
||||
|
||||
exports[`paste text > with markdown link 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><p><a target="_blank" rel="noopener noreferrer nofollow" spellcheck="false" href="example.com">test</a></p></div></div>"`;
|
||||
|
||||
exports[`paste text > with multiple markdown links 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><p><a target="_blank" rel="noopener noreferrer nofollow" spellcheck="false" href="example.com">test</a> some text <a target="_blank" rel="noopener noreferrer nofollow" spellcheck="false" href="example2.com">test2</a></p></div></div>"`;
|
||||
|
||||
@@ -114,4 +114,33 @@ describe("paste text", () => {
|
||||
|
||||
expect(editorElement.outerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("with html link", async () => {
|
||||
const editorElement = h("div");
|
||||
const { editor } = createEditor({
|
||||
element: editorElement,
|
||||
extensions: {
|
||||
link: Link
|
||||
}
|
||||
});
|
||||
|
||||
const clipboardEvent = new Event("paste", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true
|
||||
});
|
||||
|
||||
(clipboardEvent as unknown as any)["clipboardData"] = {
|
||||
getData: (type: string) =>
|
||||
type === "text/html"
|
||||
? `<meta charset='utf-8'><html><head></head><body><a href="nn://note/68798972093c8c6ea22efca2">example.py</a></body></html>`
|
||||
: ""
|
||||
};
|
||||
|
||||
editor.view.dom.dispatchEvent(clipboardEvent);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
expect(editorElement.outerHTML).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Node, mergeAttributes } from "@tiptap/core";
|
||||
import { Node, mergeAttributes, nodePasteRule } from "@tiptap/core";
|
||||
import { insertMathNode } from "./plugin/index.js";
|
||||
import { NodeSelection } from "prosemirror-state";
|
||||
import { tiptapKeys } from "@notesnook/common";
|
||||
@@ -32,6 +32,8 @@ declare module "@tiptap/core" {
|
||||
|
||||
// simple inputrule for block math
|
||||
const REGEX_BLOCK_MATH_DOLLARS = /\$\$\$\s+$/; //new RegExp("\$\$\s+$", "i");
|
||||
const REGEX_PASTE_BLOCK_MATH_DOLLARS = /\$\$\$([\s\S]*?)\$\$\$/g;
|
||||
|
||||
export const MathBlock = Node.create({
|
||||
name: "mathBlock",
|
||||
group: "block math",
|
||||
@@ -99,5 +101,20 @@ export const MathBlock = Node.create({
|
||||
}
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
addPasteRules() {
|
||||
return [
|
||||
nodePasteRule({
|
||||
find: REGEX_PASTE_BLOCK_MATH_DOLLARS,
|
||||
type: this.type,
|
||||
getAttributes: (match) => {
|
||||
return { content: match[1] };
|
||||
},
|
||||
getContent: (attrs) => {
|
||||
return attrs.content ? [{ type: "text", text: attrs.content }] : [];
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Node, mergeAttributes } from "@tiptap/core";
|
||||
import { Node, mergeAttributes, nodePasteRule } from "@tiptap/core";
|
||||
import { mathPlugin } from "./plugin/index.js";
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
@@ -40,6 +40,7 @@ const REGEX_INLINE_MATH_DOLLARS = /\$\$(.+)\$\$/; //new RegExp("\$(.+)\$", "i");
|
||||
// return REGEX_INLINE_MATH_DOLLARS;
|
||||
// }
|
||||
// })();
|
||||
const REGEX_PASTE_INLINE_MATH_DOLLARS = /\$\$([\s\S]*?)\$\$/g;
|
||||
|
||||
export const MathInline = Node.create({
|
||||
name: "mathInline",
|
||||
@@ -107,5 +108,20 @@ export const MathInline = Node.create({
|
||||
}
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
addPasteRules() {
|
||||
return [
|
||||
nodePasteRule({
|
||||
find: REGEX_PASTE_INLINE_MATH_DOLLARS,
|
||||
type: this.type,
|
||||
getAttributes: (match) => {
|
||||
return { content: match[1] };
|
||||
},
|
||||
getContent: (attrs) => {
|
||||
return attrs.content ? [{ type: "text", text: attrs.content }] : [];
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user