Compare commits

...

54 Commits

Author SHA1 Message Date
Abdullah Atta
e6ff0a2430 editor: adjust font size and padding of inline code 2025-11-24 11:51:24 +05:00
01zulfi
eab0bdee8b editor: update text color of inline code
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-24 10:10:01 +05:00
01zulfi
0d94797bc1 editor: remove top&bottom padding from inline code
* it was cutting off a bit of text from top&bottom lines

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-24 09:36:24 +05:00
01zulfi
372c49d81e editor: improve inline code styling
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-21 15:46:24 +05:00
Abdullah Atta
97d21c818e Merge pull request #8858 from 01zulfi/editor/disable-collapsible-headings-in-table
editor: disable collapsible headings inside tables
2025-11-12 11:30:38 +05:00
Abdullah Atta
3beb60d8b6 Merge pull request #8918 from streetwriters/core/fix-keys-reencryption-on-update-password
core: fix user keys re-encryption when updating password
2025-11-12 11:29:03 +05:00
Ammar Ahmed
6343212d7e Merge pull request #8923 from streetwriters/mobile-release-3.3.9
mobile: release v3.3.9
2025-11-12 09:47:26 +05:00
01zulfi
f728ad8826 editor: disable collapsible headings inside tables
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-11 11:39:41 +05:00
Abdullah Atta
b96af6c4cf mobile: release v3.3.9 2025-11-05 23:35:27 +05:00
Abdullah Atta
6069a41fa4 Merge pull request #8909 from streetwriters/mobile-release-3.3.8
Mobile release 3.3.8
2025-11-05 23:28:48 +05:00
01zulfi
7190207e5c core: fix user keys re-encryption when updating password
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-05 14:02:48 +05:00
01zulfi
015453a40b Merge pull request #8914 from streetwriters/release/v3.3.5
web: bump version to 3.3.5
2025-11-05 13:07:01 +05:00
01zulfi
dad92a186c web: bump version to 3.3.5
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-05 12:08:04 +05:00
Abdullah Atta
7e5e19e72c Merge pull request #8913 from streetwriters/editor/fix-checklist-bulletlist
editor: fix checklist parsing as bulletlist
2025-11-05 12:03:55 +05:00
01zulfi
8e1484a029 editor: fix checklist parsing as bulletlist
* reduce list-item's parseHTML priority lower than check-list-item's parseHTML priority

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-05 11:01:52 +05:00
Ammar Ahmed
4a3b3a14ec mobile: release v3.3.8 2025-11-04 10:35:34 +05:00
Ammar Ahmed
758157cf78 mobile: update documentation link 2025-11-04 10:34:43 +05:00
Ammar Ahmed
a1a171d648 mobile: fix restore backup keeps loading 2025-11-04 10:24:22 +05:00
01zulfi
92ba4d7624 Merge pull request #8907 from streetwriters/release/v3.3.4
web: bump version to 3.3.4
2025-11-03 17:13:35 +05:00
01zulfi
d44ee5ca05 web: bump version to 3.3.4
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-03 16:18:57 +05:00
Abdullah Atta
d7dde74675 Merge pull request #8905 from streetwriters/editor/fix-images-in-lists
editor: fix old inline images in bullet,numbered,task&check lists
2025-11-03 16:13:02 +05:00
01zulfi
5fde1c0f07 editor: fix old inline images in bullet,numbered,task&check lists
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-03 16:10:26 +05:00
Abdullah Atta
ffddbe2d7a Merge pull request #8857 from 01zulfi/core/escape-paranthesis-in-search
core: escape paranthesis in notes search query
2025-11-03 12:37:39 +05:00
01zulfi
eb3998d72d core: remove unnecessary only in tests
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-03 12:21:11 +05:00
01zulfi
fca61afd7c core: escape paranthesis in notes search query
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-03 11:02:02 +05:00
Abdullah Atta
33559250d8 Merge pull request #8904 from streetwriters/editor/fix-heading-plugin
editor: fix crash in heading plugin when pasting markdown content
2025-11-03 10:27:53 +05:00
01zulfi
38daca0099 editor: fix crash in heading plugin when pasting markdown content
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-03 09:01:09 +05:00
Abdullah Atta
36c8b8cc25 Merge pull request #8896 from streetwriters/webclipper/release/0.4.0
Update Web Clipper to 0.4.0
2025-11-01 19:43:00 +05:00
Abdullah Atta
dd4ca817c7 clipper(web): bump version to 0.4.0 2025-11-01 19:41:01 +05:00
Ammar Ahmed
594ceb1f52 Merge pull request #8894 from streetwriters/mobile-release-3.3.7
Mobile release 3.3.7
2025-11-01 09:43:14 +05:00
Ammar Ahmed
fbc74ec359 mobile: update pods 2025-10-31 21:55:22 +05:00
Ammar Ahmed
0dfc1dfa52 mobile: release v3.3.7 2025-10-31 20:02:43 +05:00
Ammar Ahmed
b9c14ef9aa Merge pull request #8888 from streetwriters/fix-clipper-mobile
mobile: fix share extension not able to clip webpage
2025-10-31 18:38:59 +05:00
Ammar Ahmed
3fec2b397f Merge pull request #8883 from streetwriters/fix-16kb-alignment-x64
mobile: fix 16kb alignment on x86_64 platform
2025-10-31 18:38:34 +05:00
Ammar Ahmed
ec841caef7 Merge pull request #8866 from streetwriters/feat-add-clear-button-search
Feat add clear button search
2025-10-31 18:38:16 +05:00
Ammar Ahmed
c163fc3be4 Merge pull request #8865 from streetwriters/mobile-fix-crash-sub-status
mobile: fix crash in subscription-status settings item
2025-10-31 18:37:59 +05:00
Ammar Ahmed
1e04315596 Merge pull request #8853 from streetwriters/fix-export-notes-free-users
mobile: allow free users to export note in all formats
2025-10-31 18:37:18 +05:00
Ammar Ahmed
9aef28b03d Merge pull request #8856 from streetwriters/fix-table-cell-selection-mobile
Fix table cell selection mobile
2025-10-31 18:36:56 +05:00
01zulfi
88628c650f Merge pull request #8890 from streetwriters/release/v3.3.3
web: bump version to 3.3.3
2025-10-31 16:37:09 +05:00
01zulfi
9523fd52d8 web: bump version to 3.3.3
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-31 14:40:34 +05:00
Abdullah Atta
8cc6df92f4 Merge pull request #8889 from 01zulfi/editor/fix-heading-level-change
editor: fix changing level of collapsed heading
2025-10-31 14:05:09 +05:00
01zulfi
042656856a editor: fix changing level of collapsed heading
* preserve collasped state
* reset visibility of hidden nodes under changed heading

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-31 13:14:06 +05:00
Abdullah Atta
c5cd214d97 Merge pull request #8882 from 01zulfi/editor/fix-collapsible-hidden-under
editor: fix disappearing nodes under collapsed headings if heading is edited
2025-10-30 13:29:05 +05:00
Abdullah Atta
b30c35e59d Merge pull request #8877 from 01zulfi/editor/fix-outline-images
editor: fix images from older version not rendering in outline list
2025-10-30 13:28:00 +05:00
01zulfi
ce4fa13758 editor: fix disappearing nodes under collapsed headings if heading is edited
* design flaw in using the hiddenUnder attribute on hidden nodes, a hidden attribute should be sufficient

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-30 12:59:50 +05:00
01zulfi
1f47df0f45 editor: add test case to assert migration of old inline images in outline list item
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-30 12:53:21 +05:00
Ammar Ahmed
91109278b6 mobile: fix 16kb alignment on x86_64 platform 2025-10-30 11:16:23 +05:00
01zulfi
78e4672871 editor: fix images from older version not rendering in outline list
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-29 13:12:39 +05:00
Ammar Ahmed
38600bf2a5 mobile: add tests 2025-10-28 12:00:38 +05:00
Ammar Ahmed
4720456d63 mobile: add clear button to search on mobile 2025-10-28 11:50:25 +05:00
Ammar Ahmed
5a2221c5d3 mobile: fix crash in subscription-status settings item 2025-10-28 10:52:11 +05:00
Ammar Ahmed
14c1567c07 editor: fix scrollLeft value on mobile when using scroll container 2025-10-27 12:50:16 +05:00
Ammar Ahmed
fb778f56ed editor: use default scroll container on mobile to fix selection bugs 2025-10-27 12:47:41 +05:00
Ammar Ahmed
50176d2b3a mobile: allow free users to export note in all formats 2025-10-24 13:47:02 +05:00
60 changed files with 1299 additions and 2138 deletions

View File

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

View File

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

View File

@@ -39,9 +39,7 @@ import {
presentSheet
} from "../../../services/event-manager";
import Exporter from "../../../services/exporter";
import PremiumService from "../../../services/premium";
import { useSettingStore } from "../../../stores/use-setting-store";
import { useUserStore } from "../../../stores/use-user-store";
import { getElevationStyle } from "../../../utils/elevation";
import { AppFontSize, defaultBorderRadius } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
@@ -83,7 +81,6 @@ const ExportNotesSheet = ({
type: "pdf" | "txt" | "md" | "html" | "md-frontmatter"
) => {
if (exporting) return;
if (!PremiumService.get() && type !== "txt") return;
setExporting(true);
update?.({ disableClosing: true } as PresentSheetOptions);
setComplete(false);
@@ -145,8 +142,7 @@ const ExportNotesSheet = ({
await exportNoteAs("txt");
},
icon: "card-text",
id: notesnook.ids.dialogs.export.text,
pro: true
id: notesnook.ids.dialogs.export.text
},
{
title: "HTML",
@@ -194,8 +190,7 @@ const ExportNotesSheet = ({
paddingVertical: DefaultAppStyles.GAP_VERTICAL,
justifyContent: "flex-start",
borderRadius: 0,
paddingHorizontal: DefaultAppStyles.GAP,
opacity: item.pro ? 1 : 0.5
paddingHorizontal: DefaultAppStyles.GAP
}}
>
<View

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useRef } from "react";
import React, { useRef, useState } from "react";
import { View } from "react-native";
import { TextInput } from "react-native-gesture-handler";
import { IconButton } from "../../components/ui/icon-button";
@@ -36,6 +36,7 @@ export const SearchBar = ({
onChangeText: (value: string) => void;
loading?: boolean;
}) => {
const [clearButton, setClearButton] = useState(false);
const selectionMode = useSelectionStore((state) => state.selectionMode);
const isFocused = useNavigationStore(
(state) => state.focusedRouteId === "Search"
@@ -45,6 +46,7 @@ export const SearchBar = ({
const inputRef = useRef<TextInput>(null);
const _onChangeText = (value: string) => {
onChangeText(value);
setClearButton(!!value);
};
return selectionMode && isFocused ? null : (
@@ -100,6 +102,23 @@ export const SearchBar = ({
autoCorrect={false}
placeholderTextColor={colors.primary.placeholder}
/>
{clearButton ? (
<IconButton
name="close"
size={AppFontSize.xxl}
top={10}
testID="clear-search"
bottom={10}
onPress={() => {
inputRef.current?.clear();
onChangeText("");
setClearButton(false);
}}
color={colors.primary.paragraph}
type="plain"
/>
) : null}
</View>
</View>
);

View File

@@ -322,6 +322,7 @@ export const RestoreBackup = () => {
});
setFiles(files);
setLoading(false);
BACKUP_FILES_CACHE.splice(0, BACKUP_FILES_CACHE.length, ...files);
} catch (e) {
e;
@@ -519,7 +520,11 @@ const BackupItem = ({
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}}
>
<View>
<View
style={{
flexShrink: 1
}}
>
<Paragraph size={AppFontSize.sm}>{itemName}</Paragraph>
<Paragraph
size={AppFontSize.xs}

View File

@@ -82,15 +82,22 @@ export const settingsGroups: SettingSection[] = [
{
id: "subscription-status",
useHook: () => useUserStore((state) => state.user),
hidden: (current) =>
!current ||
(current as User).subscription?.plan === SubscriptionPlan.FREE,
hidden: (current) => {
const user = current as User;
return (
!user ||
!user.subscription ||
user.subscription.provider === undefined ||
!strings.subscriptionProviderInfo[user?.subscription?.provider] ||
user.subscription?.plan === SubscriptionPlan.FREE
);
},
name: (current) => {
const user = (current as User) || useUserStore.getState().user;
return (
strings.subscriptionProviderInfo[
user?.subscription?.provider
].title() || "Unknown provider"
]?.title() || `Unknown provider id: ${user?.subscription?.provider}`
);
},
icon: "credit-card",
@@ -100,6 +107,8 @@ export const settingsGroups: SettingSection[] = [
const subscriptionProviderInfo =
strings.subscriptionProviderInfo[user?.subscription?.provider];
if (!subscriptionProviderInfo) return;
const isCurrentPlatform =
(user.subscription?.provider === SubscriptionProvider.APPLE &&
Platform.OS === "ios") ||
@@ -1427,7 +1436,7 @@ export const settingsGroups: SettingSection[] = [
id: "docs-link",
name: strings.documentation(),
modifer: async () => {
Linking.openURL("https://docs.notesnook.com");
Linking.openURL("https://help.notesnook.com/");
},
description: strings.documentationDesc(),
icon: "file-document"

View File

@@ -1,5 +1,5 @@
import { execSync } from "child_process";
//@ts-ignore
import { pathExists, ensureDir } from "fs-extra";
import { resolveConfig } from "detox/internals";

View File

@@ -28,6 +28,9 @@ describe("Search", () => {
.typeTextById("search-input", "Test")
.wait(1000)
.isVisibleByText("1")
.waitAndTapById("clear-search")
.wait(2000)
.isNotVisibleByText("1")
.run();
});
});

View File

@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { expect as jestExpect } from "@jest/globals";
import { device as _device, expect } from "detox";
import { readFileSync } from "fs";
//@ts-ignore
import { toMatchImageSnapshot } from "jest-image-snapshot";
import type { RouteName } from "../../app/stores/use-navigation-store";
import { notesnook } from "../test.ids";

View File

@@ -124,7 +124,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 3075
versionCode 3078
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

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

View File

@@ -654,6 +654,7 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Notesnook/Pods-Notesnook-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle",
@@ -665,6 +666,7 @@
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle",
@@ -821,6 +823,7 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Notesnook-NotesnookTests/Pods-Notesnook-NotesnookTests-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle",
@@ -832,6 +835,7 @@
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle",
@@ -1091,7 +1095,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2154;
CURRENT_PROJECT_VERSION = 2157;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1165,7 +1169,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3.6;
MARKETING_VERSION = 3.3.9;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1196,7 +1200,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2154;
CURRENT_PROJECT_VERSION = 2157;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1270,7 +1274,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3.6;
MARKETING_VERSION = 3.3.9;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1429,7 +1433,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2154;
CURRENT_PROJECT_VERSION = 2157;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1441,7 +1445,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.3.6;
MARKETING_VERSION = 3.3.9;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1472,7 +1476,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2154;
CURRENT_PROJECT_VERSION = 2157;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1485,7 +1489,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.3.6;
MARKETING_VERSION = 3.3.9;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1515,7 +1519,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2154;
CURRENT_PROJECT_VERSION = 2157;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1596,7 +1600,7 @@
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
MARKETING_VERSION = 3.3.6;
MARKETING_VERSION = 3.3.9;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1627,7 +1631,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2154;
CURRENT_PROJECT_VERSION = 2157;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1709,7 +1713,7 @@
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
MARKETING_VERSION = 3.3.6;
MARKETING_VERSION = 3.3.9;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1786,7 +1790,10 @@
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";
@@ -1851,7 +1858,10 @@
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;

View File

@@ -9,6 +9,7 @@
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>3B52.1</string>
<string>C617.1</string>
</array>
</dict>

View File

@@ -1273,11 +1273,11 @@ PODS:
- React-Core
- react-native-keep-awake (1.3.1):
- React-Core
- react-native-mmkv-storage (0.11.2):
- react-native-mmkv-storage (12.0.0):
- DoubleConversion
- glog
- hermes-engine
- MMKV (~> 1.3.9)
- MMKV (~> 1.3.14)
- RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
@@ -1372,7 +1372,7 @@ PODS:
- react-native-screenguard (1.0.0):
- React-Core
- SDWebImage (~> 5.11.1)
- react-native-share-extension (2.9.0):
- react-native-share-extension (2.9.5):
- React
- react-native-sodium (1.6.5):
- React
@@ -1770,15 +1770,53 @@ PODS:
- Yoga
- RNIap (12.16.2):
- React-Core
- RNImageCropPicker (0.40.2):
- RNImageCropPicker (0.51.1):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-RCTImage
- RNImageCropPicker/QBImagePickerController (= 0.40.2)
- TOCropViewController
- RNImageCropPicker/QBImagePickerController (0.40.2):
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNImageCropPicker/QBImagePickerController (= 0.51.1)
- TOCropViewController (~> 2.8.0)
- Yoga
- RNImageCropPicker/QBImagePickerController (0.51.1):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-RCTImage
- TOCropViewController
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- TOCropViewController (~> 2.8.0)
- Yoga
- RNKeychain (4.0.5):
- React
- RNNotifee (7.4.9):
@@ -1975,7 +2013,7 @@ PODS:
- SwiftyRSA (1.7.0):
- SwiftyRSA/ObjC (= 1.7.0)
- SwiftyRSA/ObjC (1.7.0)
- TOCropViewController (2.7.4)
- TOCropViewController (2.8.0)
- toolbar-android (0.2.1):
- React
- Yoga (0.0.0)
@@ -2425,7 +2463,7 @@ SPEC CHECKSUMS:
react-native-image-resizer: 24c5d06fae2176dc0caed4b6396e02befb44064a
react-native-in-app-review: b3d1eed3d1596ebf6539804778272c4c65e4a400
react-native-keep-awake: 03b74eebe4f2bb5e8478fc8f420651a92463b6f8
react-native-mmkv-storage: 51c33c6f6678d67061059cce8189a57c3e47192e
react-native-mmkv-storage: 935d661dc3913a41be70d041aa3ff860d6aa4873
react-native-netinfo: 66c9ac0d0ae92a57a9ed99ab8a75429740700c73
react-native-notification-sounds: ce106d58df0dd384bccbd2e84fb53accab7cc068
react-native-orientation-locker: cc6f357b289a2e0dd2210fea0c52cb8e0727fdaa
@@ -2434,7 +2472,7 @@ SPEC CHECKSUMS:
react-native-quick-sqlite: 1bfc7f1e9acbe9a5aa5c4cc81712e9bde3ab7672
react-native-safe-area-context: 9d72abf6d8473da73033b597090a80b709c0b2f1
react-native-screenguard: 82437eeb0086a90b5e5d7e54130bb04fb406373e
react-native-share-extension: bcb7e466390a9e50c742f4b1019d6f181aedd7ad
react-native-share-extension: fdc6aaab51591a2d445df239c446aaa3a99658ec
react-native-sodium: 285eec063e4232cb67347ef6a434b85e588d38cb
react-native-theme-switch-animation: d90fe2de0d9e87a63cd6235d98cba6e7054e9a10
react-native-webview: 079eca50edf657503318b66687dadfb903731aa8
@@ -2479,7 +2517,7 @@ SPEC CHECKSUMS:
RNFlashList: ff5a0b3113c4cda0eaf4b94df8572ccad3c40fd5
RNGestureHandler: 92e89a04cd0d1c77f383a55d14c15e7f423f4c00
RNIap: f94647b3a3dbd5fa08ad7ed847eb588aa5fe95fe
RNImageCropPicker: 30d770b383d84e1067d82ea7b5ed4fff851bbbb2
RNImageCropPicker: aa81bda9f887e20542b569938cde5ce50a4ec404
RNKeychain: ffd0513e676445c637410b47249460cbf56bc9cb
RNNotifee: dabf3cdd7bfd9340bb84358cc78f635af4bc80e2
RNPrivacySnapshot: ccad3a548338c2f526bb7b1789af3fb0618b7d1d
@@ -2495,7 +2533,7 @@ SPEC CHECKSUMS:
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
TOCropViewController: 797deaf39c90e6e9ddd848d88817f6b9a8a09888
toolbar-android: c426ed5bd3dcccfed20fd79533efc0d1ae0ef018
Yoga: 31a098f74c16780569aebd614a0f37a907de0189

View File

@@ -21,6 +21,7 @@
"@react-navigation/native": "^6.0.10",
"@react-navigation/native-stack": "6.6.2",
"@sayem314/react-native-keep-awake": "^1.3.1",
"react-native-image-crop-picker": "^0.51.1",
"react": "18.2.0",
"react-native": "0.77.2",
"react-native-actions-shortcuts": "^1.0.1",
@@ -48,7 +49,7 @@
"react-native-navigation-bar-color": "2.0.2",
"react-native-notification-sounds": "0.5.5",
"@shopify/flash-list": "^1.8.0",
"react-native-mmkv-storage": "^0.11.2",
"react-native-mmkv-storage": "^12.0.0",
"react-native-quick-sqlite": "^8.2.7",
"react-native-svg": "^15.12.0",
"react-native-webview": "^13.13.5",
@@ -61,7 +62,6 @@
"react-native-zip-archive": "6.0.9",
"react-native-theme-switch-animation": "^0.6.0",
"@ammarahmed/react-native-background-fetch": "^4.2.2",
"react-native-image-crop-picker": "^0.40.2",
"react-native-url-polyfill": "^2.0.0",
"react-native-screenguard": "^1.0.0",
"@formatjs/intl-locale": "4.0.0",

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "3.3.6",
"version": "3.3.9",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [

View File

@@ -1,27 +1,27 @@
diff --git a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
index 5de0845..1b158d8 100644
--- a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
+++ b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
@@ -692,6 +692,7 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi
image.putString("mime", options.outMimeType);
diff --git a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/ImageCropPicker.java b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/ImageCropPicker.java
index b9c494e..34ef800 100644
--- a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/ImageCropPicker.java
+++ b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/ImageCropPicker.java
@@ -679,6 +679,7 @@ class ImageCropPicker implements ActivityEventListener {
image.putInt("size", (int) new File(compressedImagePath).length());
image.putString("modificationDate", String.valueOf(modificationDate));
image.putString("filename", new File(path).getName());
+ image.putString("sourceURL", path);
if (includeBase64) {
image.putString("data", getBase64StringFromFile(compressedImagePath));
diff --git a/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m b/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m
index 9f20973..5e14da8 100644
--- a/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m
+++ b/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.m
@@ -595,8 +595,10 @@ - (void)qb_imagePickerController:
NSString *mimeType = [self determineMimeTypeFromImageData:imageData];
diff --git a/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.mm b/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.mm
index 354ad09..622f779 100644
--- a/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.mm
+++ b/node_modules/react-native-image-crop-picker/ios/src/ImageCropPicker.mm
@@ -603,7 +603,10 @@ - (void)qb_imagePickerController:(QBImagePickerController *)imagePickerControlle
Boolean isKnownMimeType = [mimeType length] > 0;
+ Boolean isHeicOrHeif = [mimeType isEqualToString:@"image/heic"] || [mimeType isEqualToString:@"image/heif"];
+
ImageResult *imageResult = [[ImageResult alloc] init];
- if (isLossless && useOriginalWidth && useOriginalHeight && isKnownMimeType && !forceJpg) {
+
+ Boolean isHeicOrHeif = [mimeType isEqualToString:@"image/heic"] || [mimeType isEqualToString:@"image/heif"];
+
+ if (isLossless && useOriginalWidth && useOriginalHeight && isKnownMimeType && !forceJpg && !isHeicOrHeif) {
// Use original, unmodified image
imageResult.data = imageData;

View File

@@ -0,0 +1,169 @@
diff --git a/node_modules/react-native-keychain/android/build.gradle b/node_modules/react-native-keychain/android/build.gradle
index bd2fe04..53ce4b1 100755
--- a/node_modules/react-native-keychain/android/build.gradle
+++ b/node_modules/react-native-keychain/android/build.gradle
@@ -51,5 +51,4 @@ dependencies {
//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+' // From node_modules
implementation 'androidx.annotation:annotation:1.1.0'
- implementation 'com.facebook.conceal:conceal:1.1.3@aar'
}
diff --git a/node_modules/react-native-keychain/android/src/main/java/com/oblador/keychain/KeychainModule.java b/node_modules/react-native-keychain/android/src/main/java/com/oblador/keychain/KeychainModule.java
index 61d061e..be49f8a 100644
--- a/node_modules/react-native-keychain/android/src/main/java/com/oblador/keychain/KeychainModule.java
+++ b/node_modules/react-native-keychain/android/src/main/java/com/oblador/keychain/KeychainModule.java
@@ -15,7 +15,6 @@ import com.oblador.keychain.PrefsStorage.ResultSet;
import com.oblador.keychain.cipherStorage.CipherStorage;
import com.oblador.keychain.cipherStorage.CipherStorage.DecryptionResult;
import com.oblador.keychain.cipherStorage.CipherStorage.EncryptionResult;
-import com.oblador.keychain.cipherStorage.CipherStorageFacebookConceal;
import com.oblador.keychain.cipherStorage.CipherStorageKeystoreAESCBC;
import com.oblador.keychain.exceptions.CryptoFailedException;
import com.oblador.keychain.exceptions.EmptyParameterException;
@@ -48,7 +47,6 @@ public class KeychainModule extends ReactContextBaseJavaModule {
super(reactContext);
prefsStorage = new PrefsStorage(reactContext);
- addCipherStorageToMap(new CipherStorageFacebookConceal(reactContext));
addCipherStorageToMap(new CipherStorageKeystoreAESCBC());
}
diff --git a/node_modules/react-native-keychain/android/src/main/java/com/oblador/keychain/PrefsStorage.java b/node_modules/react-native-keychain/android/src/main/java/com/oblador/keychain/PrefsStorage.java
index 99448b1..792c24c 100644
--- a/node_modules/react-native-keychain/android/src/main/java/com/oblador/keychain/PrefsStorage.java
+++ b/node_modules/react-native-keychain/android/src/main/java/com/oblador/keychain/PrefsStorage.java
@@ -7,7 +7,6 @@ import android.util.Base64;
import com.facebook.react.bridge.ReactApplicationContext;
import com.oblador.keychain.cipherStorage.CipherStorage.EncryptionResult;
-import com.oblador.keychain.cipherStorage.CipherStorageFacebookConceal;
public class PrefsStorage {
public static final String KEYCHAIN_DATA = "RN_KEYCHAIN";
@@ -35,10 +34,6 @@ public class PrefsStorage {
byte[] bytesForPassword = getBytesForPassword(service);
String cipherStorageName = getCipherStorageName(service);
if (bytesForUsername != null && bytesForPassword != null) {
- if (cipherStorageName == null) {
- // If the CipherStorage name is not found, we assume it is because the entry was written by an older version of this library. The older version used Facebook Conceal, so we default to that.
- cipherStorageName = CipherStorageFacebookConceal.CIPHER_STORAGE_NAME;
- }
return new ResultSet(cipherStorageName, bytesForUsername, bytesForPassword);
}
return null;
diff --git a/node_modules/react-native-keychain/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageFacebookConceal.java b/node_modules/react-native-keychain/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageFacebookConceal.java
deleted file mode 100644
index 3162f99..0000000
--- a/node_modules/react-native-keychain/android/src/main/java/com/oblador/keychain/cipherStorage/CipherStorageFacebookConceal.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package com.oblador.keychain.cipherStorage;
-
-import android.os.Build;
-import androidx.annotation.NonNull;
-
-import com.facebook.android.crypto.keychain.AndroidConceal;
-import com.facebook.android.crypto.keychain.SharedPrefsBackedKeyChain;
-import com.facebook.crypto.Crypto;
-import com.facebook.crypto.CryptoConfig;
-import com.facebook.crypto.Entity;
-import com.facebook.crypto.keychain.KeyChain;
-import com.facebook.react.bridge.ReactApplicationContext;
-import com.oblador.keychain.SecurityLevel;
-import com.oblador.keychain.exceptions.CryptoFailedException;
-
-import java.nio.charset.Charset;
-
-public class CipherStorageFacebookConceal implements CipherStorage {
- public static final String CIPHER_STORAGE_NAME = "FacebookConceal";
- public static final String KEYCHAIN_DATA = "RN_KEYCHAIN";
- private final Crypto crypto;
-
- public CipherStorageFacebookConceal(ReactApplicationContext reactContext) {
- KeyChain keyChain = new SharedPrefsBackedKeyChain(reactContext, CryptoConfig.KEY_256);
- this.crypto = AndroidConceal.get().createDefaultCrypto(keyChain);
- }
-
- @Override
- public String getCipherStorageName() {
- return CIPHER_STORAGE_NAME;
- }
-
- @Override
- public int getMinSupportedApiLevel() {
- return Build.VERSION_CODES.JELLY_BEAN;
- }
-
- @Override
- public SecurityLevel securityLevel() {
- return SecurityLevel.ANY;
- }
-
- @Override
- public boolean supportsSecureHardware() {
- return false;
- }
-
- @Override
- public EncryptionResult encrypt(@NonNull String service, @NonNull String username, @NonNull String password, SecurityLevel level) throws CryptoFailedException {
-
- if (!this.securityLevel().satisfiesSafetyThreshold(level)) {
- throw new CryptoFailedException(String.format("Insufficient security level (wants %s; got %s)", level, this.securityLevel()));
- }
-
- if (!crypto.isAvailable()) {
- throw new CryptoFailedException("Crypto is missing");
- }
- Entity usernameEntity = createUsernameEntity(service);
- Entity passwordEntity = createPasswordEntity(service);
-
- try {
- byte[] encryptedUsername = crypto.encrypt(username.getBytes(Charset.forName("UTF-8")), usernameEntity);
- byte[] encryptedPassword = crypto.encrypt(password.getBytes(Charset.forName("UTF-8")), passwordEntity);
-
- return new EncryptionResult(encryptedUsername, encryptedPassword, this);
- } catch (Exception e) {
- throw new CryptoFailedException("Encryption failed for service " + service, e);
- }
- }
-
- @Override
- public DecryptionResult decrypt(@NonNull String service, @NonNull byte[] username, @NonNull byte[] password) throws CryptoFailedException {
- if (!crypto.isAvailable()) {
- throw new CryptoFailedException("Crypto is missing");
- }
- Entity usernameEntity = createUsernameEntity(service);
- Entity passwordEntity = createPasswordEntity(service);
-
- try {
- byte[] decryptedUsername = crypto.decrypt(username, usernameEntity);
- byte[] decryptedPassword = crypto.decrypt(password, passwordEntity);
-
- return new DecryptionResult(
- new String(decryptedUsername, Charset.forName("UTF-8")),
- new String(decryptedPassword, Charset.forName("UTF-8")),
- SecurityLevel.ANY);
- } catch (Exception e) {
- throw new CryptoFailedException("Decryption failed for service " + service, e);
- }
- }
-
- @Override
- public void removeKey(@NonNull String service) {
- // Facebook Conceal stores only one key across all services, so we cannot delete the key (otherwise decryption will fail for encrypted data of other services).
- }
-
- private Entity createUsernameEntity(String service) {
- String prefix = getEntityPrefix(service);
- return Entity.create(prefix + "user");
- }
-
- private Entity createPasswordEntity(String service) {
- String prefix = getEntityPrefix(service);
- return Entity.create(prefix + "pass");
- }
-
- private String getEntityPrefix(String service) {
- return KEYCHAIN_DATA + ":" + service;
- }
-}

View File

@@ -1,14 +0,0 @@
diff --git a/node_modules/react-native-mmkv-storage/android/build.gradle b/node_modules/react-native-mmkv-storage/android/build.gradle
index abb696e..ba35baa 100644
--- a/node_modules/react-native-mmkv-storage/android/build.gradle
+++ b/node_modules/react-native-mmkv-storage/android/build.gradle
@@ -98,7 +98,8 @@ android {
cmake {
cppFlags "-O3 -frtti -fexceptions -Wall -fstack-protector-all"
arguments "-DANDROID_STL=c++_shared",
- "-DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}"
+ "-DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}",
+ "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
abiFilters (*reactNativeArchitectures())
}
}

File diff suppressed because one or more lines are too long

View File

@@ -141,7 +141,7 @@
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook-importer/core": "^2.2.2",
"@notesnook-importer/core": "^2.2.5",
"@notesnook/common": "file:../common",
"@notesnook/intl": "file:../intl",
"@notesnook/theme": "file:../theme",

View File

@@ -987,7 +987,7 @@
},
"../web": {
"name": "@notesnook/web",
"version": "3.3.1",
"version": "3.3.4",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
@@ -1001,7 +1001,7 @@
"@lingui/react": "5.1.2",
"@mdi/js": "7.4.47",
"@mdi/react": "1.6.1",
"@notesnook-importer/core": "^2.2.2",
"@notesnook-importer/core": "^2.2.5",
"@notesnook/common": "file:../../packages/common",
"@notesnook/core": "file:../../packages/core",
"@notesnook/crypto": "file:../../packages/crypto",

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/web-clipper",
"version": "0.3.4",
"version": "0.4.0",
"private": true,
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
@@ -105,7 +105,7 @@
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.1",
"ts-loader": "^9.2.6",
"web-ext": "^7.6.2",
"web-ext": "^7.12.0",
"webpack": "5.88.2",
"webpack-cli": "5.1.4",
"webpack-dev-server": "^4.7.4"

View File

@@ -138,7 +138,8 @@ var options = {
plugins: [
new CleanWebpackPlugin({
verbose: false,
dangerouslyAllowCleanPatternsOutsideProject: true
dangerouslyAllowCleanPatternsOutsideProject: true,
dry: false
}),
new webpack.ProgressPlugin(),
// expose and write the allowed env vars on the compiled bundle

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -104,6 +104,10 @@
"run-ios",
"build-ios",
"build-android",
"build-android-debug",
"e2e-android-debug",
"e2e-android",
"e2e-ios",
"release-android",
"release-android-bundle",
"release-ios",

View File

@@ -368,7 +368,6 @@
"integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "^2.8.0"
}

View File

@@ -139,3 +139,18 @@ test("search reminders", () =>
const descriptionSearch = await db.lookup.reminders("please do").ids();
expect(descriptionSearch).toHaveLength(1);
}));
describe("notesWithHighlighting", () => {
test("search notes with parentheses in query should load the item", () =>
noteTest({
title: "(with parantheses)"
}).then(async ({ db }) => {
await db.notes.add(TEST_NOTE);
const filtered = await db.lookup.notesWithHighlighting(
"(with parantheses)",
db.notes.all
);
const item = await filtered.item(0);
expect(item.item).toBeDefined();
}));
});

View File

@@ -1154,7 +1154,8 @@ function textContainsTokens(text: string, tokens: QueryTokens) {
const lowerCasedText = text.toLowerCase();
const createTagPattern = (token: string) => {
return `<${MATCH_TAG_NAME}\\s+id="(.+?)">${token}<\\/${MATCH_TAG_NAME}>`;
const escapedToken = token.replace(/[()]/g, "\\$&");
return `<${MATCH_TAG_NAME}\\s+id="(.+?)">${escapedToken}<\\/${MATCH_TAG_NAME}>`;
};
if (

View File

@@ -666,6 +666,15 @@ class UserManager {
usesFallback: await this.usesFallbackPWHash(old_password)
});
// retrieve user keys before deriving a new encryption key
const oldUserKeys = {
attachmentsKey: await this.getAttachmentsKey(),
monographPasswordsKey: await this.getMonographPasswordsKey(),
inboxKeys: (await this.hasInboxKeys())
? await this.getInboxKeys()
: undefined
} as const;
await this.db.storage().deriveCryptoKey({
password: new_password,
salt
@@ -678,27 +687,33 @@ class UserManager {
const userEncryptionKey = await this.getEncryptionKey();
if (userEncryptionKey) {
const updateUserPayload: Partial<User> = {};
const attachmentsKey = await this.getAttachmentsKey();
if (attachmentsKey) {
if (oldUserKeys.attachmentsKey) {
user.attachmentsKey = await this.db
.storage()
.encrypt(userEncryptionKey, JSON.stringify(attachmentsKey));
.encrypt(
userEncryptionKey,
JSON.stringify(oldUserKeys.attachmentsKey)
);
updateUserPayload.attachmentsKey = user.attachmentsKey;
}
const monographPasswordsKey = await this.getMonographPasswordsKey();
if (monographPasswordsKey) {
if (oldUserKeys.monographPasswordsKey) {
user.monographPasswordsKey = await this.db
.storage()
.encrypt(userEncryptionKey, JSON.stringify(monographPasswordsKey));
.encrypt(
userEncryptionKey,
JSON.stringify(oldUserKeys.monographPasswordsKey)
);
updateUserPayload.monographPasswordsKey = user.monographPasswordsKey;
}
const inboxKeys = await this.getInboxKeys();
if (inboxKeys) {
if (oldUserKeys.inboxKeys) {
user.inboxKeys = {
public: inboxKeys.publicKey,
public: oldUserKeys.inboxKeys.publicKey,
private: await this.db
.storage()
.encrypt(userEncryptionKey, JSON.stringify(inboxKeys.privateKey))
.encrypt(
userEncryptionKey,
JSON.stringify(oldUserKeys.inboxKeys.privateKey)
)
};
updateUserPayload.inboxKeys = user.inboxKeys;
}

View File

@@ -582,7 +582,8 @@ const Tiptap = ({
alignItems: "center",
padding: "0px 16px",
paddingBottom: "3px",
boxSizing: "border-box"
boxSizing: "border-box",
minHeight: "28px"
}}
>
<StatusBar

View File

@@ -258,10 +258,10 @@ export const Callout = Node.create({
container.onmousedown = onClick;
container.ontouchstart = onClick;
if (node.attrs.hiddenUnder) {
container.dataset.hiddenUnder = node.attrs.hiddenUnder;
if (node.attrs.hidden) {
container.dataset.hidden = node.attrs.hidden;
} else {
delete container.dataset.hiddenUnder;
delete container.dataset.hidden;
}
return {
@@ -275,9 +275,9 @@ export const Callout = Node.create({
if (updatedNode.attrs.collapsed) container.classList.add("collapsed");
else container.classList.remove("collapsed");
if (updatedNode.attrs.hiddenUnder)
container.dataset.hiddenUnder = updatedNode.attrs.hiddenUnder;
else delete container.dataset.hiddenUnder;
if (updatedNode.attrs.hidden)
container.dataset.hidden = updatedNode.attrs.hidden;
else delete container.dataset.hidden;
return true;
}

View File

@@ -0,0 +1,3 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`check list item > inline image as first child in check list item 1`] = `"<ul class="simple-checklist"><li class="simple-checklist--item"><p data-spacing="double">item 1</p></li><li class="simple-checklist--item"><p data-spacing="double"></p><img src="image.png" data-aspect-ratio="1"></li></ul>"`;

View File

@@ -0,0 +1,55 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { describe, expect, test } from "vitest";
import {
createEditor,
h,
p,
checkList,
checkListItem
} from "../../../../test-utils/index.js";
import { CheckList } from "../../check-list/check-list.js";
import { CheckListItem } from "../check-list-item.js";
import { Paragraph } from "../../paragraph/paragraph.js";
import { ImageNode } from "../../image/image.js";
describe("check list item", () => {
/**
* see https://github.com/streetwriters/notesnook/pull/8877 for more context
*/
test("inline image as first child in check list item", async () => {
const el = checkList(
checkListItem([p(["item 1"])]),
checkListItem([h("img", [], { src: "image.png" })])
);
const { editor } = createEditor({
initialContent: el.outerHTML,
extensions: {
checkList: CheckList,
checkListItem: CheckListItem.configure({ nested: true }),
paragraph: Paragraph,
image: ImageNode
}
});
expect(editor.getHTML()).toMatchSnapshot();
});
});

View File

@@ -25,6 +25,7 @@ import {
} from "@tiptap/core";
import { Node as ProseMirrorNode } from "@tiptap/pm/model";
import { CheckList } from "../check-list/check-list.js";
import { ensureLeadingParagraph } from "../../utils/prosemirror.js";
export interface CheckListItemOptions {
onReadOnlyChecked?: (node: ProseMirrorNode, checked: boolean) => boolean;
@@ -67,7 +68,8 @@ export const CheckListItem = Node.create<CheckListItemOptions>({
return [
{
tag: `li.simple-checklist--item`,
priority: 51
priority: 51,
getContent: ensureLeadingParagraph
}
];
},

View File

@@ -555,7 +555,7 @@ export const CodeBlock = Node.create<CodeBlockOptions>({
compareCaretPosition(prev.caretPosition, next.caretPosition) ||
prev.language !== next.language ||
prev.indentType !== next.indentType ||
prev.hiddenUnder !== next.hiddenUnder
prev.hidden !== next.hidden
);
}
});

View File

@@ -24,7 +24,7 @@ import {
} from "@tiptap/core";
import { Heading as TiptapHeading } from "@tiptap/extension-heading";
import { isClickWithinBounds } from "../../utils/prosemirror.js";
import { Selection, Transaction } from "@tiptap/pm/state";
import { Plugin, PluginKey, Selection, Transaction } from "@tiptap/pm/state";
import { Node } from "@tiptap/pm/model";
import { useToolbarStore } from "../../toolbar/stores/toolbar-store.js";
@@ -72,13 +72,14 @@ export const Heading = TiptapHeading.extend({
return false;
}
const { textAlign, textDirection } =
const { textAlign, textDirection, collapsed } =
state.selection.$from.parent.attrs;
return commands.setNode(this.name, {
...attributes,
textAlign,
textDirection
textDirection,
collapsed
});
}
};
@@ -89,14 +90,14 @@ export const Heading = TiptapHeading.extend({
{
types: COLLAPSIBLE_BLOCK_TYPES,
attributes: {
hiddenUnder: {
default: null,
hidden: {
default: false,
keepOnSplit: false,
parseHTML: (element) => element.dataset.hiddenUnder || null,
parseHTML: (element) => element.dataset.hidden === "true",
renderHTML: (attributes) => {
if (!attributes.hiddenUnder) return {};
if (!attributes.hidden) return {};
return {
"data-hidden-under": attributes.hiddenUnder
"data-hidden": attributes.hidden === true
};
}
}
@@ -151,15 +152,19 @@ export const Heading = TiptapHeading.extend({
find: HEADING_REGEX,
type: this.type,
getAttributes: (match) => {
const { textAlign, textDirection } =
const { textAlign, textDirection, collapsed } =
this.editor.state.selection.$from.parent?.attrs || {};
const level = match[1].length;
return { level, textAlign, textDirection };
return { level, textAlign, textDirection, collapsed };
}
})
];
},
addProseMirrorPlugins() {
return [headingUpdatePlugin];
},
addNodeView() {
return ({ node, getPos, editor, HTMLAttributes }) => {
const heading = document.createElement(`h${node.attrs.level}`);
@@ -179,11 +184,14 @@ export const Heading = TiptapHeading.extend({
if (typeof pos !== "number") return;
const resolvedPos = editor.state.doc.resolve(pos);
const calloutAncestor = findParentNodeClosestToPos(
resolvedPos,
(node) => node.type.name === "callout"
);
if (calloutAncestor) return;
const forbiddenParents = ["callout", "table"];
if (
findParentNodeClosestToPos(resolvedPos, (node) =>
forbiddenParents.includes(node.type.name)
)
) {
return;
}
if (
isClickWithinBounds(
@@ -200,16 +208,9 @@ export const Heading = TiptapHeading.extend({
if (currentNode && currentNode.type.name === "heading") {
const shouldCollapse = !currentNode.attrs.collapsed;
const headingLevel = currentNode.attrs.level;
const headingId = currentNode.attrs.blockId;
tr.setNodeAttribute(pos, "collapsed", shouldCollapse);
toggleNodesUnderHeading(
tr,
pos,
headingLevel,
shouldCollapse,
headingId
);
toggleNodesUnderHeading(tr, pos, headingLevel, shouldCollapse);
}
return true;
});
@@ -234,9 +235,9 @@ export const Heading = TiptapHeading.extend({
if (updatedNode.attrs.collapsed) heading.dataset.collapsed = "true";
else delete heading.dataset.collapsed;
if (updatedNode.attrs.hiddenUnder)
heading.dataset.hiddenUnder = updatedNode.attrs.hiddenUnder;
else delete heading.dataset.hiddenUnder;
if (updatedNode.attrs.hidden)
heading.dataset.hidden = updatedNode.attrs.hidden;
else delete heading.dataset.hidden;
if (updatedNode.attrs.textAlign)
heading.style.textAlign =
@@ -259,8 +260,7 @@ function toggleNodesUnderHeading(
tr: Transaction,
headingPos: number,
headingLevel: number,
isCollapsing: boolean,
headingId: string
isCollapsing: boolean
) {
const { doc } = tr;
const headingNode = doc.nodeAt(headingPos);
@@ -269,6 +269,8 @@ function toggleNodesUnderHeading(
let nextPos = headingPos + headingNode.nodeSize;
const cursorPos = tr.selection.from;
let shouldMoveCursor = false;
let insideCollapsedHeading = false;
let nestedHeadingLevel: number | null = null;
while (nextPos < doc.content.size) {
const nextNode = doc.nodeAt(nextPos);
@@ -289,15 +291,33 @@ function toggleNodesUnderHeading(
shouldMoveCursor = true;
}
const currentPos = nextPos;
nextPos += nextNode.nodeSize;
if (COLLAPSIBLE_BLOCK_TYPES.includes(nextNode.type.name)) {
if (isCollapsing && typeof nextNode.attrs.hiddenUnder !== "string") {
tr.setNodeAttribute(nextPos, "hiddenUnder", headingId);
} else if (!isCollapsing && nextNode.attrs.hiddenUnder === headingId) {
tr.setNodeAttribute(nextPos, "hiddenUnder", null);
if (isCollapsing) {
tr.setNodeAttribute(currentPos, "hidden", true);
} else {
if (insideCollapsedHeading) {
if (
nextNode.type.name === "heading" &&
nestedHeadingLevel !== null &&
nextNode.attrs.level <= nestedHeadingLevel
) {
insideCollapsedHeading = false;
nestedHeadingLevel = null;
} else {
continue;
}
}
tr.setNodeAttribute(currentPos, "hidden", false);
if (nextNode.type.name === "heading" && nextNode.attrs.collapsed) {
insideCollapsedHeading = true;
nestedHeadingLevel = nextNode.attrs.level;
}
}
}
nextPos += nextNode.nodeSize;
}
if (shouldMoveCursor) {
@@ -332,3 +352,45 @@ function findEndOfCollapsedSection(
return nextPos;
}
const headingUpdatePlugin = new Plugin({
key: new PluginKey("headingUpdate"),
appendTransaction(transactions, oldState, newState) {
const hasDocChanges = transactions.some(
(transaction) => transaction.docChanged
);
if (!hasDocChanges) return null;
const tr = newState.tr;
const oldDoc = oldState.doc;
const newDoc = newState.doc;
let modified = false;
newDoc.descendants((newNode, pos) => {
if (newNode.type.name === "heading") {
if (pos >= oldDoc.content.size) return;
const oldNode = oldDoc.nodeAt(pos);
if (
oldNode &&
oldNode.type.name === "heading" &&
oldNode.attrs.level !== newNode.attrs.level
) {
/**
* if the level of a collapsed heading is changed,
* we need to reset visibility of all the nodes under it as there
* might be a heading of same or higher level previously
* hidden under this heading
*/
if (newNode.attrs.collapsed) {
toggleNodesUnderHeading(tr, pos, oldNode.attrs.level, false);
toggleNodesUnderHeading(tr, pos, newNode.attrs.level, true);
modified = true;
}
}
}
});
return modified ? tr : null;
}
});

View File

@@ -18,8 +18,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ListItem as TiptapListItem } from "@tiptap/extension-list-item";
import { ensureLeadingParagraph } from "../../utils/prosemirror.js";
export const ListItem = TiptapListItem.extend({
parseHTML() {
return [
{
priority: 50,
tag: `li`,
getContent: ensureLeadingParagraph
}
];
},
addKeyboardShortcuts() {
return {
...this.parent?.(),

View File

@@ -5,3 +5,5 @@ exports[`hitting backspace at the start of first list item 1`] = `"<div><div con
exports[`hitting backspace at the start of the second (or next) list item 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><ul><li><p>item1item2</p></li></ul></div></div>"`;
exports[`hitting backspace at the start of the second (or next) paragraph inside the list item 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><ul><li><p>item 1item 2</p></li></ul></div></div>"`;
exports[`inline image as first child in list item 1`] = `"<ul><li><p data-spacing="double">item 1</p></li><li><p data-spacing="double"></p><img src="image.png" data-aspect-ratio="1"></li></ul>"`;

View File

@@ -23,6 +23,8 @@ import { createEditor, h, li, p, ul } from "../../../../test-utils/index.js";
import BulletList from "../../bullet-list/index.js";
import OrderedList from "../../ordered-list/index.js";
import { ListItem } from "../index.js";
import { Paragraph } from "../../paragraph/paragraph.js";
import { ImageNode } from "../../image/image.js";
test("hitting backspace at the start of first list item", async () => {
const el = ul([li([p(["item1"])]), li([p(["item2"])])]);
@@ -97,3 +99,24 @@ test("hitting backspace at the start of the second (or next) paragraph inside th
await new Promise((resolve) => setTimeout(resolve, 100));
expect(editorElement.outerHTML).toMatchSnapshot();
});
/**
* see https://github.com/streetwriters/notesnook/pull/8877 for more context
*/
test("inline image as first child in list item", async () => {
const el = ul([
li([p(["item 1"])]),
li([h("img", [], { src: "image.png" })])
]);
const { editor } = createEditor({
initialContent: el.outerHTML,
extensions: {
listItem: ListItem,
paragraph: Paragraph,
image: ImageNode
}
});
expect(editor.getHTML()).toMatchSnapshot();
});

View File

@@ -125,10 +125,10 @@ export class MathView implements NodeView, ICursorPosObserver {
if (options.className) this.dom.classList.add(options.className);
this.dom.classList.add("math-node");
if (node.attrs.hiddenUnder) {
this.dom.dataset.hiddenUnder = node.attrs.hiddenUnder;
if (node.attrs.hidden) {
this.dom.dataset.hidden = node.attrs.hidden;
} else {
delete this.dom.dataset.hiddenUnder;
delete this.dom.dataset.hidden;
}
this._mathRenderElt = document.createElement("span");

View File

@@ -128,3 +128,5 @@ exports[`outline list item > code block in outline list item 1`] = `
"type": "doc",
}
`;
exports[`outline list item > inline image as first child in the old outline list item 1`] = `"<ul data-type="outlineList"><li data-type="outlineListItem"><p data-spacing="double">item 1</p></li><li data-type="outlineListItem"><p data-spacing="double"></p><img src="image.png" data-aspect-ratio="1"></li></ul>"`;

View File

@@ -28,6 +28,8 @@ import { test, expect, describe, beforeAll, vi } from "vitest";
import { OutlineList } from "../../outline-list/outline-list.js";
import { OutlineListItem } from "../outline-list-item.js";
import { CodeBlock } from "../../code-block/code-block.js";
import { Paragraph } from "../../paragraph/paragraph.js";
import { ImageNode } from "../../image/image.js";
describe("outline list item", () => {
beforeAll(() => {
@@ -70,4 +72,30 @@ describe("outline list item", () => {
expect(editor.getJSON()).toMatchSnapshot();
});
/**
* Two changes happened:
* 1. Images were converted from inline nodes to block nodes (https://github.com/streetwriters/notesnook/pull/8563)
* 2. Outline list item's `content` schema was changed from `paragraph + list?` to `block+` to `paragraph block*` (https://github.com/streetwriters/notesnook/pull/8772 and https://github.com/streetwriters/notesnook/commit/0b943d8ecdf04fd7d996fd0a4b1d62ec9569f071)
*
* In the old editor, it was possible to have an inline image as the first item in the outline list item, but based on the new schema it is not possible anymore. So the editor should insert an empty paragraph before the image.
*/
test("inline image as first child in the old outline list item", async () => {
const el = outlineList(
outlineListItem(["item 1"]),
outlineListItem([h("img", [], { src: "image.png" })])
);
const { editor } = createEditor({
initialContent: el.outerHTML,
extensions: {
outlineList: OutlineList,
outlineListItem: OutlineListItem,
paragraph: Paragraph,
image: ImageNode
}
});
expect(editor.getHTML()).toMatchSnapshot();
});
});

View File

@@ -24,7 +24,8 @@ import {
} from "@tiptap/core";
import {
findParentNodeOfTypeClosestToPos,
isClickWithinBounds
isClickWithinBounds,
ensureLeadingParagraph
} from "../../utils/prosemirror.js";
import { OutlineList } from "../outline-list/outline-list.js";
import { keybindings, tiptapKeys } from "@notesnook/common";
@@ -64,7 +65,8 @@ export const OutlineListItem = Node.create<ListItemOptions>({
return [
{
priority: 100,
tag: `li[data-type="${this.name}"]`
tag: `li[data-type="${this.name}"]`,
getContent: ensureLeadingParagraph
}
];
},

View File

@@ -112,10 +112,10 @@ export class ReactNodeView<P extends ReactNodeViewProps> implements NodeView {
return;
}
if (this.node.attrs.hiddenUnder) {
this.domRef.dataset.hiddenUnder = this.node.attrs.hiddenUnder;
if (this.node.attrs.hidden) {
this.domRef.dataset.hidden = this.node.attrs.hidden;
} else {
delete this.domRef.dataset.hiddenUnder;
delete this.domRef.dataset.hidden;
}
portalProviderAPI.render(this.Component, this.domRef);

View File

@@ -22,7 +22,7 @@ import { ReactNodeView, ReactNodeViewProps } from "../react/index.js";
import { Node as ProsemirrorNode } from "prosemirror-model";
import { Editor } from "../../types.js";
import { Editor as TiptapEditor } from "@tiptap/core";
import { useEffect, useRef } from "react";
import { useCallback, useEffect, useRef } from "react";
import { updateColumnsOnResize } from "@tiptap/pm/tables";
import { EditorView, NodeView } from "prosemirror-view";
import {
@@ -32,7 +32,7 @@ import {
TableProperties
} from "../../toolbar/tools/table.js";
import { getToolDefinition } from "../../toolbar/tool-definitions.js";
import { getPosition } from "@notesnook/ui";
import { getPosition, ScrollContainer } from "@notesnook/ui";
import {
findSelectedDOMNode,
hasSameAttributes
@@ -41,12 +41,14 @@ import { DesktopOnly } from "../../components/responsive/index.js";
import { TextDirections } from "../text-direction/index.js";
import { strings } from "@notesnook/intl";
import SimpleBar from "simplebar-react";
import { useIsMobile } from "../../toolbar/stores/toolbar-store.js";
export function TableComponent(props: ReactNodeViewProps) {
const { editor, node, forwardRef } = props;
const colgroupRef = useRef<HTMLTableColElement>(null);
const tableRef = useRef<HTMLTableElement>();
const { textDirection } = node.attrs;
const isMobile = useIsMobile();
useEffect(() => {
if (!colgroupRef.current || !tableRef.current) return;
@@ -54,6 +56,22 @@ export function TableComponent(props: ReactNodeViewProps) {
updateColumnsOnResize(node, colgroupRef.current, tableRef.current, 50);
}, [node]);
const renderScrollContent = useCallback(() => {
return (
<div dir={textDirection}>
<table
ref={(ref) => {
forwardRef?.(ref);
tableRef.current = ref || undefined;
}}
>
<colgroup ref={colgroupRef} />
{/* <tbody /> */}
</table>
</div>
);
}, [forwardRef, textDirection]);
return (
<>
<DesktopOnly>
@@ -68,19 +86,12 @@ export function TableComponent(props: ReactNodeViewProps) {
textDirection={textDirection}
/>
</DesktopOnly>
<SimpleBar autoHide>
<Box dir={textDirection}>
<table
ref={(ref) => {
forwardRef?.(ref);
tableRef.current = ref || undefined;
}}
>
<colgroup ref={colgroupRef} />
{/* <tbody /> */}
</table>
</Box>
</SimpleBar>
{isMobile ? (
<ScrollContainer>{renderScrollContent()}</ScrollContainer>
) : (
<SimpleBar>{renderScrollContent()}</SimpleBar>
)}
</>
);
}
@@ -208,6 +219,7 @@ function TableRowToolbar(props: TableToolbarProps) {
function TableColumnToolbar(props: TableToolbarProps) {
const { editor, table } = props;
const columnToolsRef = useRef<HTMLDivElement>(null);
const isMobile = useIsMobile();
useEffect(() => {
function onSelectionUpdate() {
@@ -234,8 +246,9 @@ function TableColumnToolbar(props: TableToolbarProps) {
yOffset: 2
});
const scrollLeft =
table.current?.closest(".simplebar-content-wrapper")?.scrollLeft || 0;
const scrollLeft = isMobile
? table.current.parentElement?.parentElement?.scrollLeft || 0
: table.current?.closest(".simplebar-content-wrapper")?.scrollLeft || 0;
columnToolsRef.current.style.left = `${pos.left - scrollLeft}px`;
columnToolsRef.current.style.top = `${pos.top}px`;
@@ -245,7 +258,7 @@ function TableColumnToolbar(props: TableToolbarProps) {
return () => {
editor.off("selectionUpdate", onSelectionUpdate);
};
}, []);
}, [isMobile]);
return (
<Flex

View File

@@ -0,0 +1,3 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`task list item > inline image as first child in task list item 1`] = `"<ul class="checklist"><li class="checklist--item"><p data-spacing="double">item 1</p></li><li class="checklist--item"><p data-spacing="double"></p><img src="image.png" data-aspect-ratio="1"></li></ul>"`;

View File

@@ -0,0 +1,55 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { describe, expect, test } from "vitest";
import {
createEditor,
h,
p,
taskList,
taskItem
} from "../../../../test-utils/index.js";
import { TaskListNode } from "../../task-list/task-list.js";
import { TaskItemNode } from "../task-item.js";
import { Paragraph } from "../../paragraph/paragraph.js";
import { ImageNode } from "../../image/image.js";
describe("task list item", () => {
/**
* see https://github.com/streetwriters/notesnook/pull/8877 for more context
*/
test("inline image as first child in task list item", async () => {
const el = taskList(
taskItem([p(["item 1"])]),
taskItem([h("img", [], { src: "image.png" })])
);
const { editor } = createEditor({
initialContent: el.outerHTML,
extensions: {
taskList: TaskListNode,
taskListItem: TaskItemNode.configure({ nested: true }),
paragraph: Paragraph,
image: ImageNode
}
});
expect(editor.getHTML()).toMatchSnapshot();
});
});

View File

@@ -21,6 +21,7 @@ import { mergeAttributes } from "@tiptap/core";
import { TaskItem } from "@tiptap/extension-task-item";
import { TaskItemComponent } from "./component.js";
import { createNodeView } from "../react/index.js";
import { ensureLeadingParagraph } from "../../utils/prosemirror.js";
export type TaskItemAttributes = {
checked: boolean;
@@ -56,7 +57,8 @@ export const TaskItemNode = TaskItem.extend({
return [
{
tag: ".checklist > li",
priority: 51
priority: 100,
getContent: ensureLeadingParagraph
}
];
},

View File

@@ -17,52 +17,39 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { createEditor, h, ul, li } from "../../../../test-utils/index.js";
import {
createEditor,
taskItem,
taskList
} from "../../../../test-utils/index.js";
import { test, expect } from "vitest";
import { TaskListNode } from "../index.js";
import { TaskItemNode } from "../../task-item/index.js";
import { p, eq } from "prosemirror-test-builder";
import { countCheckedItems, deleteCheckedItems, sortList } from "../utils.js";
function taskList(...children: HTMLLIElement[]) {
return ul(children, { class: "checklist" });
}
function taskItem(
text: string,
attr: { checked?: boolean } = {},
subList?: HTMLUListElement
) {
const children: HTMLElement[] = [h("p", [text])];
if (subList) children.push(subList);
return li(children, {
class: "checklist--item " + (attr.checked ? "checked" : "")
});
}
const NESTED_TASK_LIST = taskList(
taskItem("Task item 1", { checked: true }),
taskItem("Task item 2"),
taskItem(["Task item 1"], { checked: true }),
taskItem(["Task item 2"]),
taskItem(
"Task item 3",
["Task item 3"],
{ checked: false },
taskList(
taskItem("Task item 4", { checked: true }),
taskItem("Task item 5"),
taskItem(["Task item 4"], { checked: true }),
taskItem(["Task item 5"]),
taskItem(
"Task item 6",
["Task item 6"],
{ checked: false },
taskList(
taskItem("Task item 7", { checked: true }),
taskItem("Task item 8", { checked: true }),
taskItem(["Task item 7"], { checked: true }),
taskItem(["Task item 8"], { checked: true }),
taskItem(
"Task item 9",
["Task item 9"],
{ checked: false },
taskList(
taskItem("Task item 10", { checked: true }),
taskItem("Task item 11", { checked: true }),
taskItem("Task item 12")
taskItem(["Task item 10"], { checked: true }),
taskItem(["Task item 11"], { checked: true }),
taskItem(["Task item 12"])
)
)
)
@@ -99,8 +86,8 @@ test(`count items in a task list`, async () => {
test(`delete checked items in a task list`, async () => {
const { editor } = createEditor({
initialContent: taskList(
taskItem("Task item 1", { checked: true }),
taskItem("Task item 2")
taskItem(["Task item 1"], { checked: true }),
taskItem(["Task item 2"])
).outerHTML,
extensions: {
taskItem: TaskItemNode.configure({ nested: true }),
@@ -132,15 +119,15 @@ test(`delete checked items in a nested task list`, async () => {
test(`delete checked items in a task list with no checked items should do nothing`, async () => {
const { editor } = createEditor({
initialContent: taskList(
taskItem("Task item 1", { checked: false }),
taskItem("Task item 2"),
taskItem(["Task item 1"], { checked: false }),
taskItem(["Task item 2"]),
taskItem(
"Task item 3",
["Task item 3"],
{ checked: false },
taskList(
taskItem("Task item 4", { checked: false }),
taskItem("Task item 5"),
taskItem("Task item 6", { checked: false })
taskItem(["Task item 4"], { checked: false }),
taskItem(["Task item 5"]),
taskItem(["Task item 6"], { checked: false })
)
)
).outerHTML,
@@ -172,8 +159,10 @@ test(`sort checked items to the bottom of the task list`, async () => {
test(`sorting a task list with no checked items should do nothing`, async () => {
const { editor } = createEditor({
initialContent: taskList(taskItem("Task item 1"), taskItem("Task item 2"))
.outerHTML,
initialContent: taskList(
taskItem(["Task item 1"]),
taskItem(["Task item 2"])
).outerHTML,
extensions: {
taskItem: TaskItemNode.configure({ nested: true }),
taskList: TaskListNode

View File

@@ -31,7 +31,10 @@ import {
NodeType,
ResolvedPos,
Attrs,
Slice
Slice,
DOMParser,
Schema,
Fragment
} from "prosemirror-model";
import { EditorState, Selection, Transaction } from "prosemirror-state";
import TextStyle from "@tiptap/extension-text-style";
@@ -394,3 +397,16 @@ export function isClickWithinBounds(
return false;
}
}
export function ensureLeadingParagraph(node: Node, schema: Schema): Fragment {
const parser = DOMParser.fromSchema(schema);
const fragment = parser.parse(node).content;
const firstNode = fragment.firstChild;
if (firstNode && firstNode.type.name !== "paragraph") {
const emptyParagraph = schema.nodes.paragraph.create();
return fragment.addToStart(emptyParagraph);
}
return fragment;
}

View File

@@ -27,10 +27,6 @@
pointer-events: none;
}
.ProseMirror p code {
background-color: var(--background-secondary);
}
.ProseMirror:first-child {
margin-top: 10px;
}
@@ -54,13 +50,14 @@
}
.ProseMirror p code {
background-color: var(--background);
background-color: var(--background-secondary);
border: 1px solid var(--border);
color: var(--paragraph-secondary);
border-radius: 5px;
padding: 3px 5px 0px 5px;
padding: 2px 5px 2px 5px;
font-family: ui-monospace, SFMono-Regular, SF Mono, Consolas, Liberation Mono,
Menlo, monospace !important;
font-size: 10pt !important;
font-size: 85%;
}
.ProseMirror code > span {
@@ -978,7 +975,22 @@ del.diffdel {
display: none;
}
[data-hidden-under] {
.ProseMirror table h1::before,
.ProseMirror table h2::before,
.ProseMirror table h3::before,
.ProseMirror table h4::before,
.ProseMirror table h5::before,
.ProseMirror table h6::before,
.ProseMirror table h1::after,
.ProseMirror table h2::after,
.ProseMirror table h3::after,
.ProseMirror table h4::after,
.ProseMirror table h5::after,
.ProseMirror table h6::after {
display: none;
}
[data-hidden="true"] {
display: none !important;
}

View File

@@ -108,3 +108,36 @@ export function outlineListItem(
"data-type": "outlineListItem"
});
}
export function taskList(...children: HTMLLIElement[]) {
return ul(children, { class: "checklist" });
}
export function taskItem(
paragraphChildren: (string | HTMLElement)[],
attr: { checked?: boolean } = {},
subList?: HTMLUListElement
) {
const children: HTMLElement[] = [h("p", paragraphChildren)];
if (subList) children.push(subList);
return li(children, {
class: "checklist--item " + (attr.checked ? "checked" : "")
});
}
export function checkList(...children: HTMLLIElement[]) {
return ul(children, { class: "simple-checklist" });
}
export function checkListItem(
paragraphChildren: (string | HTMLElement)[],
subList?: HTMLUListElement
) {
const children: HTMLElement[] = [h("p", paragraphChildren)];
if (subList) children.push(subList);
return li(children, {
class: "simple-checklist--item "
});
}