mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 11:39:21 +02:00
Compare commits
36 Commits
fix/dialog
...
mobile-rel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0af831d50b | ||
|
|
3efaeaeeb9 | ||
|
|
eda284ca25 | ||
|
|
1ed331b4bb | ||
|
|
9621b43bf2 | ||
|
|
503c1a5c8d | ||
|
|
a2fa1d4d78 | ||
|
|
6c6ad9a766 | ||
|
|
fc8807f74d | ||
|
|
edec48f9d4 | ||
|
|
ce98551057 | ||
|
|
74a2cc5b19 | ||
|
|
9628a940e9 | ||
|
|
d48643cbb9 | ||
|
|
558beaf216 | ||
|
|
6cc5d2452b | ||
|
|
e934ef7ffd | ||
|
|
e2d8032ef4 | ||
|
|
81e6101fb5 | ||
|
|
070354920b | ||
|
|
404168fb86 | ||
|
|
d5fa9fd02b | ||
|
|
6cc66d91be | ||
|
|
20a3b3b95e | ||
|
|
9145ea4234 | ||
|
|
0fa79433bd | ||
|
|
5185273b38 | ||
|
|
eadd23de4d | ||
|
|
71d9738980 | ||
|
|
80328af888 | ||
|
|
5e83da5575 | ||
|
|
521afe90ca | ||
|
|
ee3b7b5d7d | ||
|
|
95a9775c26 | ||
|
|
93d1319b25 | ||
|
|
f1fc166713 |
@@ -62,7 +62,17 @@ function registerProtocol() {
|
||||
headers: { "Content-Type": extensionToMimeType[fileExtension] }
|
||||
});
|
||||
} else {
|
||||
return net.fetch(request, { bypassCustomProtocolHandlers: true });
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
if (!requestHeaders.has("referer"))
|
||||
requestHeaders.set("referer", PROTOCOL_URL);
|
||||
return await net.fetch(
|
||||
new Request(request, {
|
||||
headers: requestHeaders
|
||||
}),
|
||||
{
|
||||
bypassCustomProtocolHandlers: true
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
console.info(`${SCHEME} protocol inteception "successful"`);
|
||||
|
||||
@@ -125,7 +125,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 3096
|
||||
versionCode 3097
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -4,4 +4,10 @@
|
||||
<domain includeSubdomains="true">10.0.2.2</domain>
|
||||
<domain includeSubdomains="true">localhost</domain>
|
||||
</domain-config>
|
||||
<base-config>
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
<certificates src="user" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
</network-security-config>
|
||||
@@ -1,8 +1,3 @@
|
||||
- Add support audio playback in editor
|
||||
- Sort tags assigned to a note on top when viewing and managing note tags
|
||||
- Fix account password changing and account recovery
|
||||
- Fix title is editable in readonly mode
|
||||
- Allow attaching image as file
|
||||
- Minor bug fixes and performance improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
|
||||
@@ -33,6 +33,8 @@ import {
|
||||
} from "./utils";
|
||||
import Upload from "@ammarahmed/react-native-upload";
|
||||
import { CloudUploader } from "react-native-nitro-cloud-uploader";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { sleep } from "../../utils/time";
|
||||
|
||||
// Upload constants
|
||||
const CHUNK_SIZE = 10 * 1024 * 1024; // 10 MB
|
||||
@@ -212,6 +214,9 @@ export async function uploadFile(
|
||||
);
|
||||
|
||||
if (Platform.OS === "android") {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: true
|
||||
});
|
||||
const status = await PermissionsAndroid.request(
|
||||
"android.permission.POST_NOTIFICATIONS"
|
||||
);
|
||||
@@ -221,6 +226,10 @@ export async function uploadFile(
|
||||
type: "info"
|
||||
});
|
||||
}
|
||||
await sleep(500);
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: false
|
||||
});
|
||||
}
|
||||
|
||||
let uploaded = false;
|
||||
|
||||
@@ -59,6 +59,7 @@ import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import Navigation from "../../services/navigation";
|
||||
|
||||
const Actions = ({
|
||||
attachment,
|
||||
@@ -304,9 +305,9 @@ const Actions = ({
|
||||
<Pressable
|
||||
onPress={async () => {
|
||||
eSendEvent(eCloseSheet, contextId);
|
||||
await sleep(150);
|
||||
close?.();
|
||||
eSendEvent(eCloseAttachmentDialog);
|
||||
await sleep(300);
|
||||
Navigation.navigate("FluidPanelsView");
|
||||
openNote(item, (item as any).type === "trash");
|
||||
}}
|
||||
style={{
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
ColorValue,
|
||||
KeyboardAvoidingView,
|
||||
Modal,
|
||||
Platform,
|
||||
SafeAreaView,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
@@ -32,7 +33,6 @@ import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { BouncingView } from "../ui/transitions/bouncing-view";
|
||||
import { DIALOG_CONTEXT_STATE } from "./functions";
|
||||
|
||||
export interface BaseDialogProps extends PropsWithChildren {
|
||||
animation?: "fade" | "none" | "slide" | undefined;
|
||||
@@ -51,7 +51,6 @@ export interface BaseDialogProps extends PropsWithChildren {
|
||||
useSafeArea?: boolean;
|
||||
avoidKeyboardResize?: boolean;
|
||||
enableSheetKeyboardHandler?: boolean;
|
||||
context?: string;
|
||||
}
|
||||
|
||||
const BaseDialog = ({
|
||||
@@ -71,14 +70,12 @@ const BaseDialog = ({
|
||||
useSafeArea = true,
|
||||
avoidKeyboardResize = false,
|
||||
enableSheetKeyboardHandler,
|
||||
background,
|
||||
context = "global"
|
||||
background
|
||||
}: BaseDialogProps) => {
|
||||
const floating = useIsFloatingKeyboard();
|
||||
const lockEvents = useRef(false);
|
||||
const [internalVisible, setIntervalVisible] = useState(true);
|
||||
const locked = useUserStore((state) => state.appLocked);
|
||||
const reslover = useRef<(value?: any) => void>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -102,15 +99,6 @@ const BaseDialog = ({
|
||||
}
|
||||
}, [locked]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setTimeout(() => {
|
||||
reslover.current?.();
|
||||
delete DIALOG_CONTEXT_STATE[context];
|
||||
}, 1500);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const Wrapper = useSafeArea ? SafeAreaView : View;
|
||||
|
||||
return (
|
||||
@@ -134,13 +122,9 @@ const BaseDialog = ({
|
||||
useSettingStore.getState().setSheetKeyboardHandler(false);
|
||||
}
|
||||
}
|
||||
|
||||
DIALOG_CONTEXT_STATE[context] = new Promise((resolve) => {
|
||||
reslover.current = resolve;
|
||||
});
|
||||
}}
|
||||
animationType={animation}
|
||||
onRequestClose={(e) => {
|
||||
onRequestClose={() => {
|
||||
if (lockEvents.current) return;
|
||||
if (!closeOnTouch) return null;
|
||||
useSettingStore.getState().setSheetKeyboardHandler(true);
|
||||
|
||||
@@ -70,13 +70,3 @@ export function presentDialog(data: Partial<DialogInfo>): void {
|
||||
export function hideDialog(): void {
|
||||
eSendEvent(eCloseSimpleDialog);
|
||||
}
|
||||
|
||||
export const DIALOG_CONTEXT_STATE: Record<string, Promise<void>> = {};
|
||||
|
||||
export async function awaitDialogIfOpen(context = "global") {
|
||||
if (context in DIALOG_CONTEXT_STATE) {
|
||||
await DIALOG_CONTEXT_STATE[context];
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -134,7 +134,6 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
|
||||
? false
|
||||
: dialogInfo.statusBarTranslucent
|
||||
}
|
||||
context={context}
|
||||
bounce={!dialogInfo.input}
|
||||
closeOnTouch={!dialogInfo.disableBackdropClosing}
|
||||
background={dialogInfo.background}
|
||||
|
||||
@@ -56,7 +56,12 @@ import Seperator from "../../ui/seperator";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import { Note, NoteContent, VAULT_ERRORS } from "@notesnook/core";
|
||||
import {
|
||||
isEncryptedContent,
|
||||
Note,
|
||||
NoteContent,
|
||||
VAULT_ERRORS
|
||||
} from "@notesnook/core";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
|
||||
export const VaultDialog: React.FC = () => {
|
||||
@@ -109,9 +114,17 @@ export const VaultDialog: React.FC = () => {
|
||||
const biometry = await BiometricService.isBiometryAvailable();
|
||||
const available = !!biometry;
|
||||
const fingerprint = await BiometricService.hasInternetCredentials();
|
||||
const noteLocked = data.item
|
||||
? await db.vaults.itemExists(data.item)
|
||||
: false;
|
||||
|
||||
if (data.item) {
|
||||
const locked = await db.vaults.itemExists(data.item);
|
||||
noteLockedRef.current = locked;
|
||||
if (!locked) {
|
||||
const content = await db.content.findByNoteId(data.item!.id);
|
||||
if (content && isEncryptedContent(content)) {
|
||||
noteLockedRef.current = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set refs
|
||||
noteRef.current = data.item;
|
||||
@@ -122,7 +135,6 @@ export const VaultDialog: React.FC = () => {
|
||||
positiveButtonTypeRef.current = data.positiveButtonType || "transparent";
|
||||
customActionTitleRef.current = data.customActionTitle || null;
|
||||
customActionParagraphRef.current = data.customActionParagraph || null;
|
||||
noteLockedRef.current = noteLocked;
|
||||
onUnlockRef.current = data.onUnlock;
|
||||
requestTypeRef.current = data.requestType;
|
||||
|
||||
@@ -417,9 +429,10 @@ export const VaultDialog: React.FC = () => {
|
||||
onUnlockRef.current
|
||||
) {
|
||||
const password = passwordRef.current;
|
||||
const unlock = onUnlockRef.current;
|
||||
close();
|
||||
await sleep(300);
|
||||
onUnlockRef.current(note, password);
|
||||
await sleep(500);
|
||||
unlock(note, password);
|
||||
}
|
||||
} catch (e) {
|
||||
takeErrorAction();
|
||||
|
||||
@@ -32,9 +32,11 @@ import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
|
||||
import { fluidTabsRef } from "../../../utils/global-refs";
|
||||
|
||||
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
|
||||
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
|
||||
import { editorController } from "../../../screens/editor/tiptap/utils";
|
||||
import { RouteParams } from "../../../stores/use-navigation-store";
|
||||
import NotePreview from "../../note-history/preview";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
import { RouteParams } from "../../../stores/use-navigation-store";
|
||||
|
||||
export const openNote = async (
|
||||
item: Note,
|
||||
@@ -63,11 +65,19 @@ export const openNote = async (
|
||||
component: <NotePreview note={item} content={content} />
|
||||
});
|
||||
} else {
|
||||
if (!useTabStore.getState().hasTabForNote(note.id!)) {
|
||||
editorController.current.commands.setLoading(
|
||||
true,
|
||||
useTabStore.getState().currentTab
|
||||
);
|
||||
}
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
});
|
||||
if (!DDS.isTab) {
|
||||
fluidTabsRef.current?.goToPage("editor");
|
||||
setTimeout(() => {
|
||||
fluidTabsRef.current?.goToPage("editor");
|
||||
}, 32);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -60,6 +60,7 @@ import { IconButton } from "../ui/icon-button";
|
||||
import Seperator from "../ui/seperator";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { presentDialog } from "../dialog/functions";
|
||||
import { Cipher } from "@notesnook/crypto";
|
||||
|
||||
const MergeConflicts = () => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -84,12 +85,21 @@ const MergeConflicts = () => {
|
||||
dateEdited: contentToSave.dateEdited
|
||||
});
|
||||
|
||||
const noteLocked = await db.vaults.itemExists(note);
|
||||
const noteContent = await db.content.findByNoteId(note.id);
|
||||
|
||||
if (noteLocked) {
|
||||
await db.vault.save({
|
||||
...contentToSave,
|
||||
sessionId: `${Date.now()}`
|
||||
if (noteContent?.locked) {
|
||||
const selectedContent = contentToSave.conflicted
|
||||
? noteContent
|
||||
: noteContent.conflicted;
|
||||
|
||||
await db.content.add({
|
||||
id: note.contentId,
|
||||
dateResolved: noteContent?.conflicted?.dateModified || Date.now(),
|
||||
sessionId: `${Date.now()}`,
|
||||
data: selectedContent?.data as Cipher<"base64">,
|
||||
type: selectedContent?.type,
|
||||
locked: true,
|
||||
conflicted: undefined
|
||||
});
|
||||
} else {
|
||||
await db.content.add({
|
||||
@@ -138,7 +148,6 @@ const MergeConflicts = () => {
|
||||
onUnlock: async (item, password) => {
|
||||
if (!item || !password) return;
|
||||
const currentContent = await db.content.get(item.contentId!);
|
||||
|
||||
try {
|
||||
noteContent = {
|
||||
...(await db.content.get(item.contentId!)),
|
||||
|
||||
@@ -326,8 +326,9 @@ export const Items = ({
|
||||
|
||||
const getTopBarItemChunksOfFour = () => {
|
||||
const chunks = [];
|
||||
for (let i = 0; i < topBarItems.length; i += 5) {
|
||||
chunks.push(topBarItems.slice(i, i + 5));
|
||||
const itemCount = shouldShrink ? 4 : 5;
|
||||
for (let i = 0; i < topBarItems.length; i += itemCount) {
|
||||
chunks.push(topBarItems.slice(i, i + itemCount));
|
||||
}
|
||||
return chunks;
|
||||
};
|
||||
@@ -375,10 +376,7 @@ export const Items = ({
|
||||
flexDirection: "row",
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
gap: 5,
|
||||
/**
|
||||
* ((columnItemWidth + gap) * noOfItems) - padding
|
||||
*/
|
||||
width: (columnItemWidth + 5) * 5 - 12
|
||||
width: width
|
||||
}}
|
||||
>
|
||||
{item.map(renderTopBarItem)}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { ColorValue, TextProps } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import EvilIcon from "react-native-vector-icons/EvilIcons";
|
||||
import { AppFontSize } from "../../../utils/size";
|
||||
|
||||
export interface IconProps extends TextProps {
|
||||
@@ -43,11 +44,22 @@ export interface IconProps extends TextProps {
|
||||
*
|
||||
*/
|
||||
color?: ColorValue | number | undefined;
|
||||
|
||||
iconFamily?: "evilicons" | "material";
|
||||
}
|
||||
|
||||
export default function AppIcon(props: IconProps) {
|
||||
export default function AppIcon({
|
||||
iconFamily = "material",
|
||||
...props
|
||||
}: IconProps) {
|
||||
const { colors } = useThemeColors();
|
||||
return (
|
||||
return iconFamily === "evilicons" ? (
|
||||
<EvilIcon
|
||||
size={AppFontSize.md}
|
||||
color={colors.primary.icon}
|
||||
{...(props as any)}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
size={AppFontSize.md}
|
||||
color={colors.primary.icon}
|
||||
|
||||
@@ -19,13 +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: "Play audio files",
|
||||
body: "You can now play audio attachments in the editor"
|
||||
},
|
||||
{
|
||||
title: "Password change & account recovery re-enabled",
|
||||
body: "We have re-enabled password change and account recovery after making some improvements"
|
||||
}
|
||||
];
|
||||
export const features: FeatureType[] = [];
|
||||
|
||||
@@ -21,6 +21,7 @@ import { isFeatureAvailable, useAreFeaturesAvailable } from "@notesnook/common";
|
||||
import {
|
||||
Color,
|
||||
createInternalLink,
|
||||
isEncryptedContent,
|
||||
Item,
|
||||
ItemReference,
|
||||
Note,
|
||||
@@ -221,7 +222,15 @@ export const useActions = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (item.type === "note") {
|
||||
db.vaults.itemExists(item).then((locked) => setLocked(locked));
|
||||
db.vaults.itemExists(item).then(async (locked) => {
|
||||
setLocked(locked);
|
||||
if (!locked) {
|
||||
const content = await db.content.findByNoteId(item.id);
|
||||
if (content && isEncryptedContent(content)) {
|
||||
setLocked(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [item]);
|
||||
|
||||
|
||||
@@ -487,6 +487,17 @@ export const useEditor = (
|
||||
};
|
||||
} else if (note.contentId) {
|
||||
const rawContent = await db.content?.get(note.contentId);
|
||||
|
||||
if (rawContent && isEncryptedContent(rawContent)) {
|
||||
useTabStore.getState().updateTab(useTabStore.getState().currentTab, {
|
||||
session: {
|
||||
noteLocked: true,
|
||||
locked: true
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (rawContent && !isDeleted(rawContent) && !rawContent.locked) {
|
||||
currentContents.current[note.id] = {
|
||||
data: rawContent.data,
|
||||
|
||||
@@ -38,10 +38,7 @@ import { DatabaseLogger, db } from "../../../common/database";
|
||||
import filesystem from "../../../common/filesystem";
|
||||
import { deleteCacheFileByName } from "../../../common/filesystem/io";
|
||||
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
|
||||
import {
|
||||
awaitDialogIfOpen,
|
||||
presentDialog
|
||||
} from "../../../components/dialog/functions";
|
||||
import { presentDialog } from "../../../components/dialog/functions";
|
||||
import {
|
||||
endProgress,
|
||||
startProgress,
|
||||
@@ -109,8 +106,6 @@ const restoreBackup = async (options: {
|
||||
|
||||
const isLegacyBackup = options.uri.endsWith(".nnbackup");
|
||||
|
||||
await awaitDialogIfOpen();
|
||||
|
||||
startProgress({
|
||||
title: strings.restoring(),
|
||||
paragraph: strings.preparingBackupRestore(),
|
||||
@@ -575,13 +570,17 @@ const BackupItem = ({
|
||||
paragraph: strings.restoreBackupConfirm(),
|
||||
positiveText: strings.restore(),
|
||||
negativeText: strings.cancel(),
|
||||
context: "global",
|
||||
positivePress: async () => {
|
||||
restoreBackup({
|
||||
uri:
|
||||
Platform.OS === "android"
|
||||
? (item as ScopedStorage.FileType).uri
|
||||
: (item as ReactNativeBlobUtilStat).path
|
||||
});
|
||||
setTimeout(() => {
|
||||
restoreBackup({
|
||||
uri:
|
||||
Platform.OS === "android"
|
||||
? (item as ScopedStorage.FileType).uri
|
||||
: (item as ReactNativeBlobUtilStat).path
|
||||
});
|
||||
}, 500);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
} from "@react-navigation/native";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { ActivityIndicator, TextInput, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { FeatureResult, useIsFeatureAvailable } from "@notesnook/common";
|
||||
//@ts-ignore
|
||||
import ToggleSwitch from "toggle-switch-react-native";
|
||||
@@ -218,14 +217,15 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
}}
|
||||
>
|
||||
{!!item.icon && (
|
||||
<Icon
|
||||
<AppIcon
|
||||
color={
|
||||
item.type === "danger"
|
||||
? colors.error.icon
|
||||
: colors.secondary.icon
|
||||
}
|
||||
iconFamily={item.iconFamily}
|
||||
name={item.icon}
|
||||
size={30}
|
||||
size={item.iconSize || 30}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -92,6 +92,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
{
|
||||
id: "delete-data",
|
||||
name: strings.deleteData(),
|
||||
icon: "delete",
|
||||
description: strings.deleteAccountDesc(),
|
||||
modifer: () => {
|
||||
presentDialog({
|
||||
@@ -1243,7 +1244,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "backup-now",
|
||||
id: "backup-now-with-attachments",
|
||||
name: strings.backupNowWithAttachments(),
|
||||
description: strings.backupNowWithAttachmentsDesc(),
|
||||
hidden: () => !useUserStore.getState().user,
|
||||
@@ -1545,6 +1546,9 @@ export const settingsGroups: SettingSection[] = [
|
||||
id: "join-telegram",
|
||||
name: strings.joinTelegram(),
|
||||
description: strings.joinTelegramDesc(),
|
||||
icon: "sc-telegram",
|
||||
iconFamily: "evilicons",
|
||||
iconSize: 35,
|
||||
modifer: () => {
|
||||
Linking.openURL("https://t.me/notesnook").catch(() => {
|
||||
/* empty */
|
||||
|
||||
@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { TextInput } from "react-native";
|
||||
import { Settings } from "../../stores/use-setting-store";
|
||||
import { FeatureId } from "@notesnook/common";
|
||||
import { IconProps } from "../../components/ui/AppIcon";
|
||||
|
||||
export type SettingSection = {
|
||||
id: string;
|
||||
@@ -34,6 +35,8 @@ export type SettingSection = {
|
||||
name?: string | ((current?: unknown) => string);
|
||||
description?: string | ((current: unknown) => string);
|
||||
icon?: string;
|
||||
iconFamily?: IconProps["iconFamily"];
|
||||
iconSize?: number;
|
||||
property?: keyof Settings;
|
||||
sections?: SettingSection[];
|
||||
component?: string;
|
||||
|
||||
@@ -319,9 +319,8 @@ async function run(
|
||||
};
|
||||
}
|
||||
|
||||
await sleep(300);
|
||||
|
||||
if (canShowCompletionStatus) {
|
||||
await sleep(500);
|
||||
presentBackupCompleteSheet(path);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ import { initDatabase, useShareStore } from "./store";
|
||||
import { isTablet } from "react-native-device-info";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
import { DefaultAppStyles } from "../utils/styles";
|
||||
import { encodeHTML5 } from "entities";
|
||||
|
||||
const getLinkPreview = (url: string) => {
|
||||
return getPreviewData(url, 5000);
|
||||
@@ -81,7 +82,7 @@ async function sanitizeHtml(site: string, title?: string) {
|
||||
return (
|
||||
html +
|
||||
"<hr/>" +
|
||||
`<p>Clipped from <a href="${site}">${title || site}</a></p>
|
||||
`<p>Clipped from <a href="${site}">${encodeHTML5(title || site)}</a></p>
|
||||
<p>Date clipped ${getFormattedDate(Date.now())}</p>`
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -96,7 +97,7 @@ function makeHtmlFromUrl(url: string) {
|
||||
function makeHtmlFromPlainText(text: string) {
|
||||
if (!text) return "";
|
||||
|
||||
return `<p>${text
|
||||
return `<p>${encodeHTML5(text)
|
||||
.replace(/[\n]+/g, "\n")
|
||||
.replace(/(?:\r\n|\r|\n)/g, "</p><p>")}</p>`;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export interface UserStore {
|
||||
appLocked: boolean;
|
||||
lockApp: (verified: boolean) => void;
|
||||
disableAppLockRequests: boolean;
|
||||
setDisableAppLockRequests: (shouldBlockVerifyUser: boolean) => void;
|
||||
setDisableAppLockRequests: (disableAppLockRequests: boolean) => void;
|
||||
profile?: Partial<Profile>;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,9 +46,10 @@ describe("Tags", () => {
|
||||
.waitAndTapByText('Add "#testtag"')
|
||||
.isVisibleByText("#testtag")
|
||||
.pressBack(2)
|
||||
.wait(1000)
|
||||
.openSideMenu()
|
||||
.waitAndTapById("tab-tags")
|
||||
.waitAndTapByText("#testtag")
|
||||
.waitAndTapByText("testtag")
|
||||
.processResult(async (note) => {
|
||||
await TestBuilder.create().isVisibleByText(note.body).run();
|
||||
})
|
||||
|
||||
BIN
apps/mobile/fonts/EvilIcons.ttf
Normal file
BIN
apps/mobile/fonts/EvilIcons.ttf
Normal file
Binary file not shown.
@@ -34,6 +34,8 @@
|
||||
65AA857E25E6DDEC00772A01 /* NotesWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AA857D25E6DDEC00772A01 /* NotesWidget.swift */; };
|
||||
65AA858025E6DDEE00772A01 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 65AA857F25E6DDEE00772A01 /* Assets.xcassets */; };
|
||||
65AA858425E6DDEE00772A01 /* NotesWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 65AA857725E6DDEC00772A01 /* NotesWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
65AD1F4A2F6289110033176F /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 65AD1F492F6289110033176F /* EvilIcons.ttf */; };
|
||||
65AD1F4B2F6289110033176F /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 65AD1F492F6289110033176F /* EvilIcons.ttf */; };
|
||||
65B5014425A672B200E2D264 /* ShareViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 65B5014325A672B200E2D264 /* ShareViewController.m */; };
|
||||
65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 65B5014525A672B200E2D264 /* MainInterface.storyboard */; };
|
||||
65B5014B25A672B200E2D264 /* Make Note.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 65B5014025A672B200E2D264 /* Make Note.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
@@ -129,6 +131,7 @@
|
||||
65AA857D25E6DDEC00772A01 /* NotesWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesWidget.swift; sourceTree = "<group>"; };
|
||||
65AA857F25E6DDEE00772A01 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
65AA858125E6DDEE00772A01 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
65AD1F492F6289110033176F /* EvilIcons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = EvilIcons.ttf; path = ../fonts/EvilIcons.ttf; sourceTree = SOURCE_ROOT; };
|
||||
65B5014025A672B200E2D264 /* Make Note.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Make Note.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
65B5014325A672B200E2D264 /* ShareViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareViewController.m; sourceTree = "<group>"; };
|
||||
65B5014625A672B200E2D264 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
@@ -291,6 +294,7 @@
|
||||
83CBB9F61A601CBA00E9B192 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
65AD1F492F6289110033176F /* EvilIcons.ttf */,
|
||||
653F4E042EA3887C009A42A2 /* Inter-Bold.ttf */,
|
||||
653F4E052EA3887C009A42A2 /* Inter-Italic.ttf */,
|
||||
653F4E062EA3887C009A42A2 /* Inter-Regular.ttf */,
|
||||
@@ -536,6 +540,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
659201D82EA78265006B16DB /* Inter-Bold.ttf in Resources */,
|
||||
65AD1F4B2F6289110033176F /* EvilIcons.ttf in Resources */,
|
||||
659201D92EA78265006B16DB /* Inter-Italic.ttf in Resources */,
|
||||
659201DA2EA78265006B16DB /* Inter-Regular.ttf in Resources */,
|
||||
659201DB2EA78265006B16DB /* Inter-SemiBold.ttf in Resources */,
|
||||
@@ -581,6 +586,7 @@
|
||||
659201DE2EA7826D006B16DB /* Inter-Regular.ttf in Resources */,
|
||||
659201DF2EA7826D006B16DB /* Inter-SemiBold.ttf in Resources */,
|
||||
65C400DF2A80B6B600AA3DF5 /* MaterialCommunityIcons.ttf in Resources */,
|
||||
65AD1F4A2F6289110033176F /* EvilIcons.ttf in Resources */,
|
||||
65C149872A61151B005C40F1 /* extension.bundle in Resources */,
|
||||
65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */,
|
||||
);
|
||||
@@ -1029,7 +1035,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2176;
|
||||
CURRENT_PROJECT_VERSION = 2177;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1104,7 +1110,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.3.16;
|
||||
MARKETING_VERSION = 3.3.17;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1135,7 +1141,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2176;
|
||||
CURRENT_PROJECT_VERSION = 2177;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1210,7 +1216,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.3.16;
|
||||
MARKETING_VERSION = 3.3.17;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1367,7 +1373,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2176;
|
||||
CURRENT_PROJECT_VERSION = 2177;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1379,7 +1385,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.3.16;
|
||||
MARKETING_VERSION = 3.3.17;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
@@ -1410,7 +1416,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2176;
|
||||
CURRENT_PROJECT_VERSION = 2177;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1423,7 +1429,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.3.16;
|
||||
MARKETING_VERSION = 3.3.17;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1453,7 +1459,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2176;
|
||||
CURRENT_PROJECT_VERSION = 2177;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1534,7 +1540,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
|
||||
MARKETING_VERSION = 3.3.16;
|
||||
MARKETING_VERSION = 3.3.17;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
@@ -1565,7 +1571,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2176;
|
||||
CURRENT_PROJECT_VERSION = 2177;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1647,7 +1653,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
|
||||
MARKETING_VERSION = 3.3.16;
|
||||
MARKETING_VERSION = 3.3.17;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1724,10 +1730,7 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
|
||||
@@ -1792,10 +1795,7 @@
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
USE_HERMES = true;
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
<string>Inter-Regular.ttf</string>
|
||||
<string>Inter-Bold.ttf</string>
|
||||
<string>MaterialCommunityIcons.ttf</string>
|
||||
<string>EvilIcons.ttf</string>
|
||||
</array>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.3.16",
|
||||
"version": "3.3.17",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"scripts": {
|
||||
"postinstall": "patch-package && node ./scripts/optimize-fonts.mjs",
|
||||
"run-android": "react-native run-android --active-arch-only",
|
||||
"run-android": "react-native run-android --active-arch-only --mode=release",
|
||||
"build": "echo 'No build script defined'",
|
||||
"run-ios": "react-native run-ios",
|
||||
"start": "react-native start",
|
||||
@@ -208,4 +208,4 @@
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
70
apps/web/package-lock.json
generated
70
apps/web/package-lock.json
generated
@@ -54,6 +54,7 @@
|
||||
"dayjs": "1.11.13",
|
||||
"diffblazer": "^1.0.1",
|
||||
"electron-trpc": "0.7.1",
|
||||
"entities": "^7.0.1",
|
||||
"event-source-polyfill": "1.0.31",
|
||||
"fflate": "^0.8.0",
|
||||
"file-saver": "^2.0.5",
|
||||
@@ -507,7 +508,7 @@
|
||||
},
|
||||
"../desktop": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.3.9-beta.2",
|
||||
"version": "3.3.10",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
@@ -2924,6 +2925,18 @@
|
||||
"zod": "^3.22.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@notesnook-importer/core/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@notesnook-importer/core/node_modules/fflate": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
|
||||
@@ -4163,6 +4176,19 @@
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/hast-util-to-babel-ast/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/plugin-jsx": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz",
|
||||
@@ -6687,6 +6713,18 @@
|
||||
"htmlparser2": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/diffblazer/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/diffblazer/node_modules/htmlparser2": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
|
||||
@@ -6720,6 +6758,18 @@
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
@@ -6920,9 +6970,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
||||
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
@@ -8265,6 +8315,18 @@
|
||||
"entities": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"dayjs": "1.11.13",
|
||||
"diffblazer": "^1.0.1",
|
||||
"electron-trpc": "0.7.1",
|
||||
"entities": "^7.0.1",
|
||||
"event-source-polyfill": "1.0.31",
|
||||
"fflate": "^0.8.0",
|
||||
"file-saver": "^2.0.5",
|
||||
|
||||
@@ -26,6 +26,7 @@ import { checkForUpdate } from "../../utils/updater";
|
||||
import { showToast } from "../../utils/toast";
|
||||
import { db } from "../db";
|
||||
import { logManager } from "@notesnook/core";
|
||||
import { store as settingStore } from "../../stores/setting-store";
|
||||
|
||||
export const desktop: ReturnType<typeof createTRPCProxyClient<AppRouter>> =
|
||||
createTRPCProxyClient<AppRouter>({
|
||||
@@ -78,7 +79,7 @@ function attachListeners() {
|
||||
// });
|
||||
|
||||
TaskScheduler.register("updateCheck", "0 0 */12 * * * *", () => {
|
||||
checkForUpdate();
|
||||
checkForUpdate(settingStore.get().autoUpdates);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import { UnlockView } from "../unlock";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { diff } from "diffblazer";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { escapeUTF8 } from "entities";
|
||||
|
||||
type DiffViewerProps = { session: ConflictedEditorSession | DiffEditorSession };
|
||||
function DiffViewer(props: DiffViewerProps) {
|
||||
@@ -105,8 +106,11 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
session.type === "diff" && session.oldTitle
|
||||
? diff(session.oldTitle || "", session.note.title)
|
||||
: session.note.title
|
||||
? diff(
|
||||
escapeUTF8(session.oldTitle || ""),
|
||||
escapeUTF8(session.note.title)
|
||||
)
|
||||
: escapeUTF8(session.note.title)
|
||||
}}
|
||||
></Text>
|
||||
<Flex mt={1} sx={{ alignSelf: "center", justifySelf: "center" }}>
|
||||
|
||||
@@ -711,12 +711,12 @@ function EditorChrome(props: PropsWithChildren<EditorProps>) {
|
||||
}
|
||||
});
|
||||
|
||||
const editorTitle = document.querySelector("#editor-title");
|
||||
const editorTitle = document.querySelector(`#editor-title-${id}`);
|
||||
if (editorTitle instanceof HTMLTextAreaElement) {
|
||||
resizeTextarea(editorTitle);
|
||||
}
|
||||
}
|
||||
const observer = new ResizeObserver(debounce(onResize, 500));
|
||||
const observer = new ResizeObserver(onResize);
|
||||
observer.observe(editorScrollRef.current);
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
|
||||
@@ -100,7 +100,7 @@ function TitleBox(props: TitleBoxProps) {
|
||||
<Textarea
|
||||
ref={inputRef}
|
||||
variant="clean"
|
||||
id="editor-title"
|
||||
id={`editor-title-${id}`}
|
||||
data-test-id="editor-title"
|
||||
className="editorTitle"
|
||||
placeholder={strings.noteTitle()}
|
||||
@@ -157,6 +157,8 @@ export default React.memo(TitleBox, (prevProps, nextProps) => {
|
||||
});
|
||||
|
||||
export function resizeTextarea(input: HTMLTextAreaElement) {
|
||||
if (input.scrollHeight === 0) return;
|
||||
|
||||
input.style.height = "auto";
|
||||
requestAnimationFrame(() => {
|
||||
input.style.height = input.scrollHeight + "px";
|
||||
|
||||
@@ -57,8 +57,7 @@ export function ImportResult(props: ImportResultProps) {
|
||||
<CheckCircleOutline color="accent" />
|
||||
<Text variant="body" my={2} sx={{ textAlign: "center" }}>
|
||||
{strings.importCompleted()}. {props.result.totalNotes} notes
|
||||
successfully imported.
|
||||
{strings.errorsOccured(result.errors.length)}
|
||||
successfully imported. {strings.errorsOccured(result.errors.length)}
|
||||
</Text>
|
||||
<Button
|
||||
variant="secondary"
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
removeRecentCommand,
|
||||
addRecentCommand
|
||||
} from "./commands";
|
||||
import { escapeUTF8 } from "entities";
|
||||
|
||||
type CommandPaletteDialogProps = BaseDialogProps<boolean> & {
|
||||
isCommandMode: boolean;
|
||||
@@ -400,21 +401,25 @@ function useDatabaseFuzzySearch() {
|
||||
|
||||
const updateCollections = useCallback(async (force = false) => {
|
||||
if (force || !notes.current)
|
||||
notes.current = await db.notes.all
|
||||
.fields(["notes.id", "notes.title"])
|
||||
.items();
|
||||
notes.current = (
|
||||
await db.notes.all.fields(["notes.id", "notes.title"]).items()
|
||||
).map((n) => ({ ...n, title: escapeUTF8(n.title) }));
|
||||
if (force || !notebooks.current)
|
||||
notebooks.current = await db.notebooks.all
|
||||
.fields(["notebooks.id", "notebooks.title"])
|
||||
.items();
|
||||
notebooks.current = (
|
||||
await db.notebooks.all
|
||||
.fields(["notebooks.id", "notebooks.title"])
|
||||
.items()
|
||||
).map((n) => ({ ...n, title: escapeUTF8(n.title) }));
|
||||
if (force || !tags.current)
|
||||
tags.current = await db.tags.all
|
||||
.fields(["tags.id", "tags.title"])
|
||||
.items();
|
||||
tags.current = (
|
||||
await db.tags.all.fields(["tags.id", "tags.title"]).items()
|
||||
).map((t) => ({ ...t, title: escapeUTF8(t.title) }));
|
||||
if (force || !reminders.current)
|
||||
reminders.current = await db.reminders.all
|
||||
.fields(["reminders.id", "reminders.title"])
|
||||
.items();
|
||||
reminders.current = (
|
||||
await db.reminders.all
|
||||
.fields(["reminders.id", "reminders.title"])
|
||||
.items()
|
||||
).map((r) => ({ ...r, title: escapeUTF8(r.title) }));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -446,12 +451,13 @@ function useDatabaseFuzzySearch() {
|
||||
tag: tags.current,
|
||||
reminder: reminders.current
|
||||
};
|
||||
const escapedQuery = escapeUTF8(query);
|
||||
for (const _type in collections) {
|
||||
const type = _type as keyof typeof collections;
|
||||
const items = collections[type];
|
||||
if (!items) continue;
|
||||
for (const item of fuzzy(
|
||||
query,
|
||||
escapedQuery,
|
||||
items,
|
||||
(item) => item.id,
|
||||
{
|
||||
@@ -496,7 +502,7 @@ async function getSessionsAsCommands() {
|
||||
|
||||
commands.push({
|
||||
id: session.note.id,
|
||||
title: session.note.title,
|
||||
title: escapeUTF8(session.note.title),
|
||||
group: strings.dataTypesPluralCamelCase.note(),
|
||||
type: "note" as const,
|
||||
action: commandActions.note,
|
||||
|
||||
@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { escapeUTF8 } from "entities";
|
||||
import { db } from "../../common/db";
|
||||
import {
|
||||
ArrowLeft,
|
||||
@@ -322,7 +323,7 @@ export function removeRecentCommand(id: string) {
|
||||
async function getActiveNoteCommands(): Promise<Command[]> {
|
||||
const note = useEditorStore.getState().getActiveNote();
|
||||
if (!note) return [];
|
||||
const group = strings.actionsForNote(note.title);
|
||||
const group = strings.actionsForNote(escapeUTF8(note.title));
|
||||
|
||||
const commands: Command[] = [];
|
||||
|
||||
@@ -352,7 +353,7 @@ async function getActiveNotebookCommands() {
|
||||
if (context?.type !== "notebook") return [];
|
||||
const notebook = await db.notebooks.notebook(context.id);
|
||||
if (!notebook) return [];
|
||||
const group = strings.actionsForNotebook(notebook.title);
|
||||
const group = strings.actionsForNotebook(escapeUTF8(notebook.title));
|
||||
|
||||
const commands: Command[] = [];
|
||||
|
||||
@@ -372,7 +373,7 @@ async function getActiveTagCommands() {
|
||||
if (context?.type !== "tag") return [];
|
||||
const tag = await db.tags.tag(context.id);
|
||||
if (!tag) return [];
|
||||
const group = strings.actionsForTag(tag.title);
|
||||
const group = strings.actionsForTag(escapeUTF8(tag.title));
|
||||
const commands: Command[] = [];
|
||||
|
||||
const menuItems = await tagMenuItems(tag, [tag.id]);
|
||||
@@ -537,26 +538,26 @@ async function getCommandTitle({ id, type }: RecentCommand) {
|
||||
0
|
||||
);
|
||||
if (!note) return;
|
||||
return note.title;
|
||||
return escapeUTF8(note.title);
|
||||
}
|
||||
case "notebook": {
|
||||
const notebook = (
|
||||
await db.notebooks.all.fields(["notebooks.title"]).items([id])
|
||||
).at(0);
|
||||
if (!notebook) return;
|
||||
return notebook.title;
|
||||
return escapeUTF8(notebook.title);
|
||||
}
|
||||
case "tag": {
|
||||
const tag = (await db.tags.all.fields(["tags.title"]).items([id])).at(0);
|
||||
if (!tag) return;
|
||||
return tag.title;
|
||||
return escapeUTF8(tag.title);
|
||||
}
|
||||
case "reminder": {
|
||||
const reminder = (
|
||||
await db.reminders.all.fields(["reminders.title"]).items([id])
|
||||
).at(0);
|
||||
if (!reminder) return;
|
||||
return reminder.title;
|
||||
return escapeUTF8(reminder.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ const features: Record<FeatureKeys, Feature> = {
|
||||
"You can now play audio files directly in the editor without needing to download them first."
|
||||
},
|
||||
{
|
||||
title: "Orphaned attachmetns are now automatically deleted",
|
||||
title: "Orphaned attachments are now automatically deleted",
|
||||
subtitle:
|
||||
"Attachments that are not linked to any note will now be automatically deleted to save space."
|
||||
},
|
||||
@@ -177,7 +177,11 @@ export const FeatureDialog = DialogManager.register(function FeatureDialog(
|
||||
>
|
||||
<Flex sx={{ alignItems: "center", justifyContent: "start" }}>
|
||||
{feature.icon && <feature.icon size={14} color="accent" />}
|
||||
<Text variant="subtitle" ml={1} sx={{ fontWeight: "normal" }}>
|
||||
<Text
|
||||
variant="subtitle"
|
||||
ml={feature.icon ? 1 : 0}
|
||||
sx={{ fontWeight: "normal" }}
|
||||
>
|
||||
{feature.title}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
@@ -18,11 +18,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { checkForUpdate } from "../utils/updater";
|
||||
import { AppEventManager, AppEvents } from "../common/app-events";
|
||||
import BaseStore from "../stores";
|
||||
import createStore from "../common/store";
|
||||
import { useStore as useSettingStore } from "../stores/setting-store";
|
||||
|
||||
type CompletedUpdateStatus = { type: "completed"; version: string };
|
||||
type DownloadingUpdateStatus = { type: "downloading"; progress: number };
|
||||
@@ -103,8 +103,13 @@ export function useAutoUpdater() {
|
||||
updateDownloadProgress
|
||||
);
|
||||
|
||||
checkingForUpdate();
|
||||
checkForUpdate().catch(console.error);
|
||||
const settingStoreUnsub = useSettingStore.subscribe(
|
||||
(s) => s.autoUpdates,
|
||||
(autoUpdates) => {
|
||||
checkingForUpdate();
|
||||
checkForUpdate(autoUpdates).catch(console.error);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
checkingForUpdateEvent.unsubscribe();
|
||||
@@ -112,6 +117,7 @@ export function useAutoUpdater() {
|
||||
updateNotAvailableEvent.unsubscribe();
|
||||
updateCompletedEvent.unsubscribe();
|
||||
updateProgressEvent.unsubscribe();
|
||||
settingStoreUnsub();
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class SettingStore extends BaseStore<SettingStore> {
|
||||
ImageCompressionOptions.ASK_EVERY_TIME
|
||||
);
|
||||
desktopIntegrationSettings?: DesktopIntegration;
|
||||
autoUpdates = true;
|
||||
autoUpdates = false;
|
||||
isFlatpak = false;
|
||||
isSnap = false;
|
||||
proxyRules?: string;
|
||||
|
||||
@@ -21,9 +21,12 @@ import { AppEventManager, AppEvents } from "../common/app-events";
|
||||
import { desktop } from "../common/desktop-bridge";
|
||||
import { appVersion, getServiceWorkerVersion } from "./version";
|
||||
|
||||
export async function checkForUpdate() {
|
||||
if (IS_DESKTOP_APP) await desktop?.updater.check.query().catch(console.error);
|
||||
else {
|
||||
export async function checkForUpdate(checkOnDesktop = true) {
|
||||
if (IS_DESKTOP_APP && checkOnDesktop) {
|
||||
await desktop?.updater.check.query().catch(console.error);
|
||||
}
|
||||
|
||||
if (!IS_DESKTOP_APP) {
|
||||
AppEventManager.publish(AppEvents.checkingForUpdate);
|
||||
|
||||
const registrations =
|
||||
|
||||
@@ -41,6 +41,6 @@ Notesnook Importer is one of the most robust Evernote importers, supporting almo
|
||||
- [x] Images
|
||||
- [x] Rich text (bold, italic, lists etc.)
|
||||
- [ ] Reminders
|
||||
- [x] Internal note links
|
||||
- [x] Internal note links (limitation: links only resolve correctly if the link text exactly matches the Evernote note title)
|
||||
- [x] Notebooks
|
||||
- [x] Tags
|
||||
|
||||
1065
extensions/web-clipper/package-lock.json
generated
1065
extensions/web-clipper/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -98,9 +98,7 @@
|
||||
"fs-extra": "^10.0.0",
|
||||
"html-loader": "^3.1.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"node-sass": "^9.0.0",
|
||||
"prettier": "^2.5.1",
|
||||
"sass-loader": "^13.2.0",
|
||||
"source-map-loader": "^3.0.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"terser-webpack-plugin": "^5.3.1",
|
||||
|
||||
@@ -92,12 +92,6 @@ var options = {
|
||||
},
|
||||
{
|
||||
loader: "css-loader"
|
||||
},
|
||||
{
|
||||
loader: "sass-loader",
|
||||
options: {
|
||||
sourceMap: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
3
fastlane/metadata/android/en-US/changelogs/15489.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/15489.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- Minor bug fixes and performance improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -153,4 +153,43 @@ describe("notesWithHighlighting", () => {
|
||||
const item = await filtered.item(0);
|
||||
expect(item.item).toBeDefined();
|
||||
}));
|
||||
|
||||
test("search notes with brackets in query should load the item", () =>
|
||||
noteTest({
|
||||
title: "[with brackets]"
|
||||
}).then(async ({ db }) => {
|
||||
await db.notes.add(TEST_NOTE);
|
||||
const filtered = await db.lookup.notesWithHighlighting(
|
||||
"[with brackets]",
|
||||
db.notes.all
|
||||
);
|
||||
const item = await filtered.item(0);
|
||||
expect(item.item).toBeDefined();
|
||||
}));
|
||||
|
||||
test("search should be diacritic agnostic", () =>
|
||||
databaseTest().then(async (db) => {
|
||||
await db.notes.add({ title: "outdoor café" });
|
||||
await db.notes.add({
|
||||
title: "today",
|
||||
content: { type: "tiptap", data: "<p>I went to café</p>" }
|
||||
});
|
||||
await db.notes.add({ title: "indoor cafe" });
|
||||
await db.notes.add({
|
||||
title: "yesterday",
|
||||
content: { type: "tiptap", data: "<p>I went to a cafe</p>" }
|
||||
});
|
||||
|
||||
const searchWithoutDiacritics = await db.lookup.notesWithHighlighting(
|
||||
"cafe",
|
||||
db.notes.all
|
||||
);
|
||||
expect(searchWithoutDiacritics.length).toBe(4);
|
||||
|
||||
const searchWithDiacritics = await db.lookup.notesWithHighlighting(
|
||||
"café",
|
||||
db.notes.all
|
||||
);
|
||||
expect(searchWithDiacritics.length).toBe(4);
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -338,12 +338,9 @@ export default class Lookup {
|
||||
title.title || "",
|
||||
titleTokens.allTokens
|
||||
);
|
||||
const hasMatches = textContainsTokens(highlighted, titleTokens);
|
||||
const result = results.find((c) => c.id === title.id);
|
||||
if (!result) continue;
|
||||
result.title = hasMatches
|
||||
? splitHighlightedMatch(highlighted).flatMap((m) => m)
|
||||
: [];
|
||||
result.title = splitHighlightedMatch(highlighted).flatMap((m) => m);
|
||||
}
|
||||
|
||||
const htmls =
|
||||
@@ -364,7 +361,7 @@ export default class Lookup {
|
||||
html.data,
|
||||
contentTokens.allTokens
|
||||
);
|
||||
if (!textContainsTokens(highlighted, contentTokens)) continue;
|
||||
|
||||
result.content = extractMatchingBlocks(
|
||||
highlighted,
|
||||
MATCH_TAG_NAME
|
||||
@@ -774,15 +771,13 @@ function highlightQueries(
|
||||
|
||||
try {
|
||||
const regex = new RegExp(patterns.join("|"), "gi");
|
||||
|
||||
let hasMatches = false;
|
||||
let matchIdCounter = 0;
|
||||
|
||||
const result = text.replace(regex, (match) => {
|
||||
hasMatches = true;
|
||||
return createSearchResultTag(match, `match-${++matchIdCounter}`);
|
||||
});
|
||||
|
||||
const normalizedText = removeDiacritics(text);
|
||||
const { result, hasMatches } = highlightRegexMatches(
|
||||
text,
|
||||
normalizedText,
|
||||
regex,
|
||||
0
|
||||
);
|
||||
return { text: result, hasMatches };
|
||||
} catch (error) {
|
||||
return { text, hasMatches: false };
|
||||
@@ -898,7 +893,7 @@ function centerMatch(
|
||||
} = options;
|
||||
|
||||
// Handle edge cases
|
||||
if (!match) return {};
|
||||
if (!prefix && !suffix) return {};
|
||||
if (matchLength >= maxLength) return {};
|
||||
|
||||
// Calculate available space for context
|
||||
@@ -953,7 +948,7 @@ function stringToMatch(str: string): Match[] {
|
||||
function highlightHtmlContent(html: string, queries: string[]): string {
|
||||
if (!html || !queries.length) return html;
|
||||
|
||||
// Filter and escape regex special chars
|
||||
// Filter and escape regex special chars (tokens are already diacritics-normalized)
|
||||
const patterns = queries
|
||||
.filter((q) => q && q.length > 0)
|
||||
.map((q) => q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
||||
@@ -979,25 +974,25 @@ function highlightHtmlContent(html: string, queries: string[]): string {
|
||||
const parser = new Parser(
|
||||
{
|
||||
ontext(text) {
|
||||
// Check for matches in text
|
||||
const hasMatch = searchRegex.test(text);
|
||||
// Reset regex state after test
|
||||
searchRegex.lastIndex = 0;
|
||||
|
||||
const processed = text.replace(searchRegex, (match) =>
|
||||
createSearchResultTag(match, `match-${++matchIdCounter}`)
|
||||
const normalizedText = removeDiacritics(text);
|
||||
const highlighted = highlightRegexMatches(
|
||||
text,
|
||||
normalizedText,
|
||||
searchRegex,
|
||||
matchIdCounter
|
||||
);
|
||||
matchIdCounter = highlighted.nextId;
|
||||
|
||||
if (hasMatch) {
|
||||
if (highlighted.hasMatches) {
|
||||
// Mark all ancestor elements as containing a match
|
||||
elementStack.forEach((el) => (el.hasMatch = true));
|
||||
}
|
||||
|
||||
// Add text to current element's buffer or main result
|
||||
if (elementStack.length > 0) {
|
||||
elementStack[elementStack.length - 1].buffer += processed;
|
||||
elementStack[elementStack.length - 1].buffer += highlighted.result;
|
||||
} else {
|
||||
result += processed;
|
||||
result += highlighted.result;
|
||||
}
|
||||
},
|
||||
onopentag(name, attributes) {
|
||||
@@ -1150,30 +1145,6 @@ function getMatchScore(
|
||||
return score;
|
||||
}
|
||||
|
||||
function textContainsTokens(text: string, tokens: QueryTokens) {
|
||||
const lowerCasedText = text.toLowerCase();
|
||||
|
||||
const createTagPattern = (token: string) => {
|
||||
const escapedToken = token.replace(/[()]/g, "\\$&");
|
||||
return `<${MATCH_TAG_NAME}\\s+id="(.+?)">${escapedToken}<\\/${MATCH_TAG_NAME}>`;
|
||||
};
|
||||
|
||||
if (
|
||||
!tokens.notTokens.every(
|
||||
(t) => !new RegExp(createTagPattern(t), "i").test(lowerCasedText)
|
||||
)
|
||||
)
|
||||
return false;
|
||||
return (
|
||||
tokens.andTokens.every((t) =>
|
||||
new RegExp(createTagPattern(t), "i").test(lowerCasedText)
|
||||
) ||
|
||||
tokens.orTokens.some((t) =>
|
||||
new RegExp(createTagPattern(t), "i").test(lowerCasedText)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function filterSmallTokens(tokens: QueryTokens | undefined) {
|
||||
if (!tokens) return;
|
||||
return [...tokens.andTokens, ...tokens.orTokens].filter(
|
||||
@@ -1191,13 +1162,13 @@ function transformTokens(tokens: QueryTokens | undefined) {
|
||||
};
|
||||
|
||||
const andTokens = tokens.andTokens.map((t) =>
|
||||
t.replace(/"(.+)"/g, "$1").toLowerCase()
|
||||
removeDiacritics(t.replace(/"(.+)"/g, "$1").toLowerCase())
|
||||
);
|
||||
const orTokens = tokens.orTokens.map((t) =>
|
||||
t.replace(/"(.+)"/g, "$1").toLowerCase()
|
||||
removeDiacritics(t.replace(/"(.+)"/g, "$1").toLowerCase())
|
||||
);
|
||||
const notTokens = tokens.notTokens.map((t) =>
|
||||
t.replace(/"(.+)"/g, "$1").toLowerCase()
|
||||
removeDiacritics(t.replace(/"(.+)"/g, "$1").toLowerCase())
|
||||
);
|
||||
return {
|
||||
andTokens,
|
||||
@@ -1210,3 +1181,33 @@ function transformTokens(tokens: QueryTokens | undefined) {
|
||||
function createSearchResultTag(content: string, id: string) {
|
||||
return `<${MATCH_TAG_NAME} id="${id}">${content}</${MATCH_TAG_NAME}>`;
|
||||
}
|
||||
|
||||
function highlightRegexMatches(
|
||||
text: string,
|
||||
normalizedText: string,
|
||||
regex: RegExp,
|
||||
startId: number
|
||||
): { result: string; hasMatches: boolean; nextId: number } {
|
||||
let matchIdCounter = startId;
|
||||
let hasMatches = false;
|
||||
let result = "";
|
||||
let lastIndex = 0;
|
||||
let m: RegExpExecArray | null;
|
||||
|
||||
while ((m = regex.exec(normalizedText)) !== null) {
|
||||
hasMatches = true;
|
||||
result += text.slice(lastIndex, m.index);
|
||||
result += createSearchResultTag(
|
||||
text.slice(m.index, m.index + m[0].length),
|
||||
`match-${++matchIdCounter}`
|
||||
);
|
||||
lastIndex = m.index + m[0].length;
|
||||
}
|
||||
|
||||
result += text.slice(lastIndex);
|
||||
return { result, hasMatches, nextId: matchIdCounter };
|
||||
}
|
||||
|
||||
function removeDiacritics(s: string) {
|
||||
return s.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
||||
}
|
||||
|
||||
@@ -93,4 +93,25 @@ describe("lookup.fuzzy", () => {
|
||||
).toStrictEqual([{ id: "1", title: "hello-suffix" }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("separator normalization", () => {
|
||||
const items = [
|
||||
{ id: "1", title: "file search.jpg" },
|
||||
{ id: "2", title: "file-search.jpg" },
|
||||
{ id: "3", title: "file_search.jpg" }
|
||||
];
|
||||
|
||||
test("query with space matches all separator variants", () => {
|
||||
const result = fuzzy("file search", items, (i) => i.id, { title: 1 });
|
||||
expect(result).toStrictEqual(items);
|
||||
});
|
||||
|
||||
test("query with special character between words matches all separator variants", () => {
|
||||
let result = fuzzy("file_search", items, (i) => i.id, { title: 1 });
|
||||
expect(result).toStrictEqual(items);
|
||||
|
||||
result = fuzzy("file-search", items, (i) => i.id, { title: 1 });
|
||||
expect(result).toStrictEqual(items);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { match, surround } from "fuzzyjs";
|
||||
import { clone } from "./clone.js";
|
||||
|
||||
const SEPARATORS = /(?<=\w)[^a-zA-Z0-9](?=\w)/g;
|
||||
|
||||
function normalizeSeparators(str: string): string {
|
||||
return str.replace(SEPARATORS, " ");
|
||||
}
|
||||
|
||||
function hasSeparator(str: string): boolean {
|
||||
const result = SEPARATORS.test(str);
|
||||
SEPARATORS.lastIndex = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function fuzzy<T>(
|
||||
query: string,
|
||||
items: T[],
|
||||
@@ -39,13 +51,20 @@ export function fuzzy<T>(
|
||||
}
|
||||
> = new Map();
|
||||
|
||||
const shouldNormalize = hasSeparator(query);
|
||||
const finalQuery = shouldNormalize ? normalizeSeparators(query) : query;
|
||||
|
||||
for (const item of items) {
|
||||
if (options.limit && results.size >= options.limit) break;
|
||||
|
||||
const identifier = getIdentifier(item);
|
||||
|
||||
for (const field in fields) {
|
||||
const result = match(query, `${item[field]}`);
|
||||
const value = `${item[field]}`;
|
||||
const result = match(
|
||||
finalQuery,
|
||||
shouldNormalize ? normalizeSeparators(value) : value
|
||||
);
|
||||
if (!result.match) continue;
|
||||
|
||||
const oldMatch = results.get(identifier);
|
||||
|
||||
@@ -285,7 +285,7 @@ const Tiptap = ({
|
||||
restoreNoteSelection(scrollTop, selection);
|
||||
}
|
||||
}, 300);
|
||||
}, 1);
|
||||
}, 150);
|
||||
},
|
||||
[restoreNoteSelection]
|
||||
);
|
||||
|
||||
9
packages/editor/package-lock.json
generated
9
packages/editor/package-lock.json
generated
@@ -15,7 +15,7 @@
|
||||
"@notesnook/intl": "file:../intl",
|
||||
"@notesnook/theme": "file:../theme",
|
||||
"@notesnook/ui": "file:../ui",
|
||||
"@social-embed/lib": "^0.1.0-next.7",
|
||||
"@social-embed/lib": "^0.1.0-next.11",
|
||||
"@tiptap/core": "2.6.6",
|
||||
"@tiptap/extension-blockquote": "^2.6.6",
|
||||
"@tiptap/extension-bullet-list": "^2.6.6",
|
||||
@@ -1326,9 +1326,10 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@social-embed/lib": {
|
||||
"version": "0.1.0-next.7",
|
||||
"resolved": "https://registry.npmjs.org/@social-embed/lib/-/lib-0.1.0-next.7.tgz",
|
||||
"integrity": "sha512-aasU+EoZpWaoGkFgKrYgUWGIo6h98YjN2L5kwSJrbCLCGE8lucnwzxFTDRAlW0yHs4JUz3C4Dct0U7jS8cQy4A==",
|
||||
"version": "0.1.0-next.11",
|
||||
"resolved": "https://registry.npmjs.org/@social-embed/lib/-/lib-0.1.0-next.11.tgz",
|
||||
"integrity": "sha512-tT56VJkIbMVkkSm6+6drbhcCvTdLZTgWmj0SJ7k3UoDxxyrlkrwux8C+afjx5PKmTC2oqHznMUjgYFMjAfs3tw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"@notesnook/intl": "file:../intl",
|
||||
"@notesnook/theme": "file:../theme",
|
||||
"@notesnook/ui": "file:../ui",
|
||||
"@social-embed/lib": "^0.1.0-next.7",
|
||||
"@social-embed/lib": "^0.1.0-next.11",
|
||||
"@tiptap/core": "2.6.6",
|
||||
"@tiptap/extension-blockquote": "^2.6.6",
|
||||
"@tiptap/extension-bullet-list": "^2.6.6",
|
||||
|
||||
@@ -28,6 +28,7 @@ import { Icon } from "@notesnook/ui";
|
||||
import { Resizer } from "../../components/resizer/index.js";
|
||||
import { useThemeEngineStore } from "@notesnook/theme";
|
||||
import { useToolbarStore } from "../../toolbar/stores/toolbar-store.js";
|
||||
import { getSandboxFeatures } from "../../utils/sandbox.js";
|
||||
|
||||
export function EmbedComponent(
|
||||
props: ReactNodeViewProps<EmbedAttributes & EmbedAlignmentOptions>
|
||||
@@ -135,6 +136,9 @@ export function EmbedComponent(
|
||||
})}
|
||||
width={"100%"}
|
||||
height={"100%"}
|
||||
allow={getIframeAllowPermissions(src)}
|
||||
allowFullScreen
|
||||
referrerPolicy={isYouTubeEmbed(src) ? "origin" : undefined}
|
||||
sandbox={getSandboxFeatures(src)}
|
||||
onLoad={() => setIsLoading(false)}
|
||||
/>
|
||||
@@ -157,27 +161,6 @@ export function EmbedComponent(
|
||||
);
|
||||
}
|
||||
|
||||
function getSandboxFeatures(src: string) {
|
||||
const features = [];
|
||||
try {
|
||||
const url = new URL(src);
|
||||
if (url.protocol === "http:" || url.protocol === "https:")
|
||||
features.push(
|
||||
"allow-scripts",
|
||||
"allow-same-origin",
|
||||
"allow-popups",
|
||||
"allow-popups-to-escape-sandbox",
|
||||
"allow-forms",
|
||||
"allow-modals",
|
||||
"allow-downloads",
|
||||
"allow-presentation"
|
||||
);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return features.join(" ");
|
||||
}
|
||||
|
||||
function isYouTubeEmbed(urlString: string) {
|
||||
try {
|
||||
const url = new URL(urlString);
|
||||
@@ -208,6 +191,19 @@ function isTwitterX(src: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function getIframeAllowPermissions(src: string) {
|
||||
if (!isYouTubeEmbed(src)) return undefined;
|
||||
return [
|
||||
"accelerometer",
|
||||
"autoplay",
|
||||
"clipboard-write",
|
||||
"encrypted-media",
|
||||
"gyroscope",
|
||||
"picture-in-picture",
|
||||
"web-share"
|
||||
].join("; ");
|
||||
}
|
||||
|
||||
function tweetToEmbed(src: string, isDarkTheme: boolean) {
|
||||
src = src.replaceAll("x.com", "twitter.com");
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Box, Flex, Text } from "@theme-ui/components";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ReactNodeViewProps } from "../react/index.js";
|
||||
import { Icons } from "../../toolbar/index.js";
|
||||
import { Icon } from "@notesnook/ui";
|
||||
@@ -36,45 +36,34 @@ const FAILED_CONTENT = `<html><head>
|
||||
|
||||
export function WebClipComponent(props: ReactNodeViewProps<WebClipAttributes>) {
|
||||
const { editor, selected, node, updateAttributes } = props;
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const embedRef = useRef<HTMLIFrameElement>(null);
|
||||
const resizeObserverRef = useRef<ResizeObserver>();
|
||||
const { src, title, fullscreen, progress } = node.attrs;
|
||||
const { src, title, progress } = node.attrs;
|
||||
const [source, setSource] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
if (!isLoading) return;
|
||||
|
||||
const html = await editor.storage
|
||||
.getAttachmentData?.(node.attrs)
|
||||
.catch(() => null);
|
||||
|
||||
const iframe = embedRef.current;
|
||||
if (!iframe || !iframe.contentDocument) return;
|
||||
iframe.contentDocument.open();
|
||||
iframe.contentDocument.write(
|
||||
typeof html !== "string" || !html ? FAILED_CONTENT : html
|
||||
const doc = new DOMParser().parseFromString(
|
||||
html || FAILED_CONTENT,
|
||||
"text/html"
|
||||
);
|
||||
iframe.contentDocument.close();
|
||||
iframe.contentDocument.head.innerHTML += `<base target="_blank">`;
|
||||
setIsLoading(false);
|
||||
doc.head.innerHTML += `<base target="_blank">`;
|
||||
const blob = new Blob([doc.documentElement.outerHTML], {
|
||||
type: "text/html"
|
||||
});
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
setSource(blobUrl);
|
||||
return () => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
};
|
||||
})();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
function fullscreenchanged() {
|
||||
if (document.fullscreenElement) {
|
||||
resizeObserverRef.current?.disconnect();
|
||||
resetIframeSize(embedRef.current);
|
||||
} else {
|
||||
if (!document.fullscreenElement) {
|
||||
updateAttributes({ fullscreen: false });
|
||||
resizeIframe(node.attrs, embedRef.current);
|
||||
|
||||
if (embedRef.current?.contentDocument) {
|
||||
resizeObserverRef.current?.observe(
|
||||
embedRef.current?.contentDocument?.body
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,20 +73,6 @@ export function WebClipComponent(props: ReactNodeViewProps<WebClipAttributes>) {
|
||||
};
|
||||
}, [updateAttributes]);
|
||||
|
||||
useEffect(() => {
|
||||
if (embedRef.current?.contentDocument) {
|
||||
resizeObserverRef.current = new ResizeObserver(() => {
|
||||
setTimeout(() => resizeIframe(node.attrs, embedRef.current), 100);
|
||||
});
|
||||
resizeObserverRef.current.observe(
|
||||
embedRef.current?.contentDocument?.body
|
||||
);
|
||||
}
|
||||
return () => {
|
||||
resizeObserverRef.current?.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
@@ -183,20 +158,18 @@ export function WebClipComponent(props: ReactNodeViewProps<WebClipAttributes>) {
|
||||
>
|
||||
<Box sx={{ overflow: "hidden" }}>
|
||||
<iframe
|
||||
ref={embedRef}
|
||||
width="auto"
|
||||
frameBorder={"0"}
|
||||
scrolling={fullscreen ? "yes" : "no"}
|
||||
style={{ transformOrigin: "0 0", overflow: "hidden" }}
|
||||
onLoad={() => {
|
||||
if (fullscreen) return;
|
||||
|
||||
resizeIframe(node.attrs, embedRef.current);
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100vh",
|
||||
background: "var(--background)"
|
||||
}}
|
||||
src={source}
|
||||
sandbox="allow-popups allow-popups-to-escape-sandbox"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{isLoading && (
|
||||
{!source && (
|
||||
<Flex
|
||||
sx={{
|
||||
position: "absolute",
|
||||
@@ -218,40 +191,3 @@ export function WebClipComponent(props: ReactNodeViewProps<WebClipAttributes>) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function resizeIframe(
|
||||
attributes: WebClipAttributes,
|
||||
iframe?: HTMLIFrameElement | null
|
||||
) {
|
||||
if (!iframe || !iframe.contentDocument || !iframe.contentDocument.body)
|
||||
return;
|
||||
|
||||
const height = attributes.height
|
||||
? parseInt(attributes.height)
|
||||
: iframe.contentDocument.body.scrollHeight;
|
||||
|
||||
const width = attributes.width
|
||||
? parseInt(attributes.width)
|
||||
: iframe.contentDocument.body.scrollWidth;
|
||||
|
||||
iframe.style.height = `${height}px`;
|
||||
iframe.style.width = `${width}px`;
|
||||
|
||||
const container = iframe.parentElement;
|
||||
if (!container || container.clientWidth > width) return;
|
||||
const scale = container.clientWidth / width;
|
||||
iframe.style.scale = `${scale}`;
|
||||
container.style.height = `${height * scale}px`;
|
||||
}
|
||||
|
||||
function resetIframeSize(iframe?: HTMLIFrameElement | null) {
|
||||
if (!iframe || !iframe.contentDocument || !iframe.contentDocument.body)
|
||||
return;
|
||||
|
||||
const height = iframe.contentDocument.body.scrollHeight;
|
||||
const width = iframe.contentDocument.body.scrollWidth;
|
||||
|
||||
iframe.style.height = `${height}px`;
|
||||
iframe.style.width = `${width}px`;
|
||||
iframe.style.scale = `1`;
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ export function HoverPopupHandler(props: FloatingMenuProps) {
|
||||
align: "start",
|
||||
location: "top",
|
||||
isTargetAbsolute: true,
|
||||
yOffset: -10,
|
||||
yOffset: 0,
|
||||
xOffset: -30
|
||||
}
|
||||
});
|
||||
|
||||
39
packages/editor/src/utils/sandbox.ts
Normal file
39
packages/editor/src/utils/sandbox.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export function getSandboxFeatures(src: string) {
|
||||
const features = [];
|
||||
try {
|
||||
const url = new URL(src);
|
||||
if (url.protocol === "http:" || url.protocol === "https:")
|
||||
features.push(
|
||||
"allow-scripts",
|
||||
"allow-same-origin",
|
||||
"allow-popups",
|
||||
"allow-popups-to-escape-sandbox",
|
||||
"allow-forms",
|
||||
"allow-modals",
|
||||
"allow-downloads",
|
||||
"allow-presentation"
|
||||
);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return features.join(" ");
|
||||
}
|
||||
Reference in New Issue
Block a user