Compare commits

..

52 Commits

Author SHA1 Message Date
Ammar Ahmed
070c90362a Merge pull request #8782 from streetwriters/fix-plans-showing-price
Fix prices on github release and open checkout link in browser
2025-10-17 09:58:29 +05:00
Ammar Ahmed
2685f60409 Merge pull request #8781 from streetwriters/mobile-release-3.3.3
mobile: release v3.3.3
2025-10-17 09:10:51 +05:00
Abdullah Atta
1427958eca Merge pull request #8457 from 01zulfi/editor/tasklist-checklist-styling
editor: improve checklist styling
2025-10-16 15:01:24 +05:00
Abdullah Atta
436e09c255 editor: fix text direction not working in check lists 2025-10-16 14:50:13 +05:00
Abdullah Atta
cc9b2dd5b0 editor: add back line through for checked items 2025-10-16 14:45:40 +05:00
Abdullah Atta
0ba25419f3 editor: use single li element for simple checklists 2025-10-16 14:43:51 +05:00
Abdullah Atta
9238de4cb4 Merge pull request #8772 from 01zulfi/editor/fix-images-in-outline-lists
editor: fix inserting images in outline lists
2025-10-16 11:48:34 +05:00
01zulfi
2d5fe1058c editor: add test for code block in outline list
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-16 10:46:31 +05:00
01zulfi
b01d1fc87e editor: improve checklist styling
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-16 09:28:02 +05:00
Abdullah Atta
9f4c0a4c9a editor: move outline list item indicator to li from first child 2025-10-16 09:23:19 +05:00
Abdullah Atta
9eebda86c2 editor: fix enter/tab not working in block nodes inside outline list item 2025-10-16 09:22:49 +05:00
Abdullah Atta
d43e856fea editor: add support for all block types in outline list item 2025-10-16 09:22:15 +05:00
Abdullah Atta
7af1596038 editor: remove custom inline image -> block image migration 2025-10-16 09:21:45 +05:00
01zulfi
652f6eab0b editor: fix inserting images in outline lists
* inserting images in outline list was splitting the outline list into two, this commit fixes it

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-15 11:14:24 +05:00
Abdullah Atta
9a1adf2065 Merge pull request #8767 from streetwriters/chore/update-electron-to-37
Update electron to v37
2025-10-13 14:12:01 +05:00
Abdullah Atta
f10b258f58 desktop: update electron to v37 2025-10-13 14:10:52 +05:00
Ammar Ahmed
641eb28ce7 mobile: open web purchase in browser. 2025-10-13 11:33:48 +05:00
Ammar Ahmed
2c59a5f6f9 mobile: fix plan price not showing properly on gh release 2025-10-13 09:30:48 +05:00
Abdullah Atta
e5ad405a23 Merge pull request #8754 from streetwriters/release/3.3.1
Release v3.3.1
2025-10-10 18:12:40 +05:00
Abdullah Atta
cd1b78d8df ci: fix desktop builds 2025-10-10 17:53:41 +05:00
Abdullah Atta
3b1ca46945 web: fix desktop tests 2025-10-10 16:23:11 +05:00
Abdullah Atta
96c1636f88 web: bump version to 3.3.1 2025-10-10 15:54:29 +05:00
Abdullah Atta
9ff4757ed6 Merge pull request #8753 from streetwriters/fix/make-table-with-adjustable
Make table width adjustable
2025-10-10 15:52:26 +05:00
Abdullah Atta
17c1e77f54 editor: make table width adjustable 2025-10-10 15:47:05 +05:00
Abdullah Atta
187a2a7262 Merge pull request #8735 from 01zulfi/editor/restrict-pasting-premium-features
editor: restrict pasting premium features
2025-10-10 15:38:11 +05:00
Abdullah Atta
4621230549 Merge pull request #8751 from streetwriters/fix/discount-price-on-checkout
Fix discounted price in checkout
2025-10-10 15:33:42 +05:00
Abdullah Atta
d7cb3317a2 web: fix discounted price in checkout 2025-10-10 15:09:41 +05:00
01zulfi
6d81f26864 editor: restrict pasting premium features
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-08 13:31:24 +05:00
Abdullah Atta
2341293d92 Merge pull request #8734 from 01zulfi/common/fix-paragraph-keybinding
common: fix insert paragraph keybinding
2025-10-08 13:20:32 +05:00
Abdullah Atta
215387f8a9 Merge pull request #8731 from streetwriters/chore/get-rid-of-nx
Get rid of nx
2025-10-08 13:20:00 +05:00
01zulfi
534b8c5ad0 common: fix insert paragraph keybinding
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-08 11:57:42 +05:00
Abdullah Atta
b7b2ead9eb setup(tx): minor refactor 2025-10-08 10:57:58 +05:00
Abdullah Atta
b3e34d68e1 setup(tx): add support for passing flags down to commands 2025-10-08 10:55:52 +05:00
Abdullah Atta
39079612aa docs: remove mention of Nx from README 2025-10-08 10:29:18 +05:00
Abdullah Atta
be1af3111f setup: add support for passing args down to command 2025-10-08 10:28:02 +05:00
Abdullah Atta
8dcff49797 core: fix flaky note history tests 2025-10-08 10:27:43 +05:00
Abdullah Atta
687fb4e105 setup: remove editor-mobile from build all exclusion filter 2025-10-08 09:57:40 +05:00
Abdullah Atta
25ae780ad8 ci: get rid of NX_CLOUD_ACCESS_TOKEN 2025-10-08 09:49:05 +05:00
Abdullah Atta
b695367e6c global: add dotenv package 2025-10-08 09:34:59 +05:00
Abdullah Atta
bb274a84fe ci: use node v22 2025-10-08 08:56:20 +05:00
Abdullah Atta
364dccbf7a global: get rid of nx 2025-10-08 08:42:00 +05:00
Ammar Ahmed
105fb0fc69 mobile: release v3.3.3 2025-10-06 13:13:42 +05:00
Ammar Ahmed
110534edec Merge pull request #8714 from streetwriters/fix-mobile-client-selfhosted
Fix mobile client selfhosted
2025-10-06 12:40:26 +05:00
Ammar Ahmed
00e2190b2d Merge pull request #8713 from streetwriters/mobile-fix-legacy-pro-users
mobile: add support for LEGACY_PRO subscription plan name
2025-10-06 12:40:10 +05:00
Ammar Ahmed
e5c3fc667e Merge pull request #8712 from streetwriters/mobile-refactor-e2e-tests
mobile: refactor e2e tests
2025-10-06 12:39:57 +05:00
Ammar Ahmed
9aef8194f9 mobile: refactor e2e tests
Uses a builder pattern for writing tests to reduce code and simplify writing tests
2025-10-06 12:11:02 +05:00
Abdullah Atta
f76e0b7931 Merge pull request #8711 from 01zulfi/web/ignore-autobackup-notice-when-ignored
web: don't show auto-backup notice again after dismissal
2025-10-06 11:39:35 +05:00
Ammar Ahmed
0457e4417f mobile: minor cleanup 2025-10-06 11:10:19 +05:00
Ammar Ahmed
dae8b5c542 mobile: hide pricing flows on self hosted clients 2025-10-06 10:58:39 +05:00
01zulfi
60107dd77f web: don't show auto-backup notice again after dismissal
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-06 10:24:46 +05:00
Ammar Ahmed
4f3e19aaa2 mobile: add support for LEGACY_PRO subscription plan name 2025-10-06 09:55:28 +05:00
Ammar Ahmed
c77050eccc Merge pull request #8710 from streetwriters/mobile-fix-3.3.1
mobile: release v3.3.2
2025-10-06 09:44:45 +05:00
89 changed files with 5551 additions and 5665 deletions

View File

@@ -7,7 +7,7 @@ runs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 22.x
cache: "npm"
cache-dependency-path: |
package-lock.json

View File

@@ -52,7 +52,6 @@ jobs:
echo "USER_PASSWORD=${{ secrets.USER_PASSWORD }}" >> $GITHUB_ENV
echo "USER_TOTP_SECRET=${{ secrets.USER_TOTP_SECRET }}" >> $GITHUB_ENV
echo "USER_HASHED_PASSWORD=${{ secrets.USER_HASHED_PASSWORD }}" >> $GITHUB_ENV
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
- name: Run all @notesnook/core tests
run: npx nx test:e2e @notesnook/core
run: npm run tx @notesnook/core:test:e2e

View File

@@ -65,17 +65,13 @@ jobs:
npm i --cpu x64 sqlite-better-trigram
working-directory: ./apps/desktop
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
- name: Generate desktop build (stable)
if: ${{ inputs.release-track == 'stable' }}
run: npx nx build:desktop @notesnook/web
run: npm run tx @notesnook/web:build:desktop
- name: Generate desktop build (beta)
if: ${{ inputs.release-track == 'beta' }}
run: BETA=true npx nx build:desktop @notesnook/web
run: BETA=true npm run tx @notesnook/web:build:desktop
- name: Build desktop bundle
working-directory: ./apps/desktop
@@ -199,9 +195,9 @@ jobs:
CSC_LINK: ${{ secrets.mac_certs }}
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
run: |
npx nx run release --project @notesnook/desktop -- --variant=mas
npm run tx @notesnook/desktop:release -- --variant=mas
cd apps/desktop
yarn electron-builder --config=electron-builder.config.js --mac mas --universal -p never
working-directory: ./apps/desktop
- name: Build zip and dmg
env:
@@ -212,13 +208,13 @@ jobs:
APPLE_API_KEY_ID: ${{ secrets.api_key_id }}
APPLE_API_ISSUER: ${{ secrets.api_key_issuer_id }}
run: |
npx nx run release --project @notesnook/desktop
npm run tx @notesnook/desktop:release
cd apps/desktop
if [ ${{ inputs.publish-github }} == true ]; then
yarn electron-builder --config=electron-builder.config.js --mac zip dmg --arm64 --x64 -p always
else
yarn electron-builder --config=electron-builder.config.js --mac zip dmg --arm64 --x64 -p never
fi
working-directory: ./apps/desktop
- name: Deploy to Testflight
if: inputs.publish-apple && steps.appstore.outputs.app-version-latest != steps.app_metadata.outputs.app_version
@@ -271,8 +267,7 @@ jobs:
working-directory: ./apps/desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
working-directory: ./apps/desktop
run: npm run tx @notesnook/desktop:release
- name: Build snap
if: inputs.publish-snap
@@ -339,8 +334,7 @@ jobs:
working-directory: ./apps/desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
working-directory: ./apps/desktop
run: npm run tx @notesnook/desktop:release
# - name: Build snap
# if: inputs.publish-snap

View File

@@ -33,12 +33,8 @@ jobs:
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
- name: Generate desktop build
run: npx nx build:desktop @notesnook/web
run: npm run tx @notesnook/web:build:desktop
- name: Archive build artifact
uses: actions/upload-artifact@v4
@@ -70,8 +66,7 @@ jobs:
npm run bootstrap -- --scope=desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
working-directory: ./apps/desktop
run: npm run tx @notesnook/desktop:release
- name: Build app
run: |
@@ -114,8 +109,7 @@ jobs:
npm run bootstrap -- --scope=desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
working-directory: ./apps/desktop
run: npm run tx @notesnook/desktop:release
- name: Build app
run: |
@@ -164,8 +158,7 @@ jobs:
working-directory: ./apps/desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
working-directory: ./apps/desktop
run: npm run tx @notesnook/desktop:release
- name: Build app
run: |

View File

@@ -38,12 +38,8 @@ jobs:
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=editor
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
- name: Build editor
run: npx nx build @notesnook/editor
run: npm run tx @notesnook/editor:build
- name: Run all @notesnook/editor tests
run: npx nx test @notesnook/editor
run: npm run tx @notesnook/editor:test

View File

@@ -25,7 +25,7 @@ jobs:
npm run bootstrap -- --scope=mobile
- name: Build packages
run: npx nx run @notesnook/mobile:build
run: npm run tx @notesnook/mobile:build
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.11

View File

@@ -39,7 +39,7 @@ jobs:
echo ::set-output name=app_version::$(cat package.json | jq -r .version)
- name: Generate build
run: npx nx build @notesnook/monograph
run: npm run tx @notesnook/monograph:build
# Setup Buildx
- name: Docker Setup Buildx

View File

@@ -20,7 +20,6 @@ jobs:
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV

View File

@@ -20,7 +20,6 @@ jobs:
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV

View File

@@ -27,10 +27,6 @@ jobs:
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
- name: Build
run: npm run build:web

View File

@@ -30,7 +30,6 @@ jobs:
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV

View File

@@ -49,10 +49,6 @@ jobs:
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
- name: Generate test build
run: npm run build:test:web

View File

@@ -27,7 +27,6 @@ Notesnook is built using the following technologies:
3. React Native — For mobile apps we are using React Native
4. Electron — For desktop app
5. NPM — listed here because we **don't** use Yarn or PNPM or XYZ across any of our projects.
6. Nx — maintaining monorepos is hard but Nx makes it easier.
> **Note: Each project in the monorepo contains its own architecture details which you can refer to.**

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "3.3.0",
"version": "3.3.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.3.0",
"version": "3.3.1",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
@@ -30,7 +30,7 @@
"@types/node": "22.15.3",
"@types/yargs": "^17.0.33",
"chokidar": "^4.0.3",
"electron": "^36.0.0",
"electron": "^37.0.0",
"electron-builder": "^26.0.12",
"esbuild": "0.21.5",
"node-abi": "^4.5.0",
@@ -3584,9 +3584,9 @@
}
},
"node_modules/electron": {
"version": "36.9.3",
"resolved": "https://registry.npmjs.org/electron/-/electron-36.9.3.tgz",
"integrity": "sha512-eR5yswsA55zVTPDEIA/PSdVNBLOp0q0Wsavgx0S3BmJYOqKoH1gqzS+hggf0/aY5OvUjVNSHiJJA1VsB5aJUug==",
"version": "37.6.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-37.6.1.tgz",
"integrity": "sha512-aHtJVNjqf0lk7dlPoc1X+fMBpZtLn+XGvP6IYc3gooTwsD1D/Ic2SBRC9SnIk6LkWTsDaSF9jgH1d9Q7eABy/Q==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.3.0",
"version": "3.3.1",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",
@@ -47,7 +47,7 @@
"@types/node": "22.15.3",
"@types/yargs": "^17.0.33",
"chokidar": "^4.0.3",
"electron": "^36.0.0",
"electron": "^37.0.0",
"electron-builder": "^26.0.12",
"esbuild": "0.21.5",
"node-abi": "^4.5.0",

View File

@@ -19,14 +19,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { useRoute } from "@react-navigation/native";
import React, { useEffect, useState } from "react";
import {
ScrollView,
TouchableOpacity,
View,
useWindowDimensions
} from "react-native";
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import { DDS } from "../../services/device-detection";
import { eSendEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
@@ -47,8 +44,7 @@ import { hideAuth } from "./common";
import { ForgotPassword } from "./forgot-password";
import { AuthHeader } from "./header";
import { useLogin } from "./use-login";
import { useRoute } from "@react-navigation/native";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import SettingsService from "../../services/settings";
const LoginSteps = {
emailAuth: 1,
@@ -81,7 +77,7 @@ export const Login = ({ changeMode }) => {
}
}, 5000);
if (!PremiumService.get()) {
if (!PremiumService.get() && !SettingsService.getProperty("serverUrls")) {
Navigation.navigate("PayWall", {
context: "signup",
state: route.params.state,

View File

@@ -19,6 +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 { useRoute } from "@react-navigation/native";
import React, { useRef, useState } from "react";
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
@@ -38,7 +39,6 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { AuthHeader } from "./header";
import { SignupContext } from "./signup-context";
import { useRoute } from "@react-navigation/native";
const SignupSteps = {
signup: 0,
@@ -91,11 +91,13 @@ export const Signup = ({ changeMode, welcome }) => {
setLastSynced(await db.lastSynced());
clearMessage();
setEmailVerifyMessage();
Navigation.navigate("PayWall", {
canGoBack: false,
state: route.params.state,
context: "signup"
});
if (!SettingsService.getProperty("serverUrls")) {
Navigation.navigate("PayWall", {
canGoBack: false,
state: route.params.state,
context: "signup"
});
}
return true;
} catch (e) {
setCurrentStep(SignupSteps.signup);

View File

@@ -60,6 +60,7 @@ import usePricingPlans, {
PricingPlan
} from "../../hooks/use-pricing-plans";
import Navigation, { NavigationProps } from "../../services/navigation";
import PremiumService from "../../services/premium";
import { getElevationStyle } from "../../utils/elevation";
import { openLinkInBrowser } from "../../utils/functions";
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
@@ -74,7 +75,6 @@ import { IconButton } from "../ui/icon-button";
import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import PremiumService from "../../services/premium";
const Steps = {
select: 1,
@@ -114,15 +114,8 @@ const PayWall = (props: NavigationProps<"PayWall">) => {
routeParams.state?.planId,
routeParams.state?.productId
);
console.log(
"selectPlan",
routeParams.state?.planId,
routeParams.state?.productId,
(pricingPlans.selectedProduct as any).productId
);
}
setStep(Steps.buy);
console.log("Buy step");
}
}, [routeParams.state]);
@@ -926,7 +919,6 @@ const ReviewItem = (props: {
</View>
);
};
const PricingPlanCard = ({
plan,
pricingPlans,
@@ -939,7 +931,6 @@ const PricingPlanCard = ({
setStep: (step: number) => void;
}) => {
const { colors } = useThemeColors();
const isFreePlan = plan.id === "free";
const [regionalDiscount, setRegionaDiscount] = useState<SKUResponse>();
const { width } = useWindowDimensions();
const isTablet = width > 600;
@@ -949,7 +940,6 @@ const PricingPlanCard = ({
regionalDiscount?.sku ||
`notesnook.${plan.id}.${annualBilling ? "yearly" : "monthly"}`
];
console.log(regionalDiscount?.sku);
const price = pricingPlans?.getPrice(
product as RNIap.Subscription,
pricingPlans.hasTrialOffer(plan.id, product?.productId) ? 1 : 0,
@@ -962,6 +952,7 @@ const PricingPlanCard = ({
);
useEffect(() => {
if (pricingPlans?.isGithubRelease) return;
pricingPlans
?.getRegionalDiscount(
plan.id,
@@ -980,7 +971,7 @@ const PricingPlanCard = ({
pricingPlans.isSubscribed();
const isNotReady =
pricingPlans?.loadingPlans || (!price && !WebPlan?.price.gross);
pricingPlans?.loadingPlans || (!price && !WebPlan?.price?.gross);
return (
<TouchableOpacity
@@ -1003,8 +994,8 @@ const PricingPlanCard = ({
: "monthly"
}`
: pricingPlans.isGithubRelease
? (WebPlan?.period as string)
: (product?.productId as string)
? (WebPlan?.period as string)
: (product?.productId as string)
);
setStep(Steps.buy);
}}
@@ -1112,29 +1103,26 @@ const PricingPlanCard = ({
</View>
</View>
{pricingPlans?.loadingPlans || (!price && !WebPlan?.price.gross) ? (
{pricingPlans?.loadingPlans || (!price && !WebPlan?.price?.gross) ? (
<ActivityIndicator size="small" color={colors.primary.accent} />
) : (
<View>
<Paragraph size={AppFontSize.lg}>
{isFreePlan
? "0.00"
: price ||
`${WebPlan?.price.currency} ${WebPlan?.price.gross}`}{" "}
{price || `${WebPlan?.price?.currency} ${WebPlan?.price?.gross}`}{" "}
<Paragraph>/{strings.month()}</Paragraph>
</Paragraph>
{isFreePlan || !product ? null : (
{!product && !WebPlan ? null : (
<Paragraph color={colors.secondary.paragraph} size={AppFontSize.xs}>
{annualBilling
? strings.billedAnnually(
pricingPlans?.getStandardPrice(
product as RNIap.Subscription
(product as RNIap.Subscription) || (WebPlan as Plan)
) as string
)
: strings.billedMonthly(
pricingPlans?.getStandardPrice(
product as RNIap.Subscription
(product as RNIap.Subscription) || (WebPlan as Plan)
) as string
)}
</Paragraph>

View File

@@ -21,7 +21,13 @@ import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import dayjs from "dayjs";
import React, { useEffect, useState } from "react";
import { ScrollView, Text, TouchableOpacity, View } from "react-native";
import {
Linking,
ScrollView,
Text,
TouchableOpacity,
View
} from "react-native";
import Config from "react-native-config";
import * as RNIap from "react-native-iap";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
@@ -60,27 +66,17 @@ export const BuyPlan = (props: {
return checkoutUrl ? (
<View
style={{
flex: 1
flex: 1,
justifyContent: "center",
alignItems: "center",
gap: DefaultAppStyles.GAP_VERTICAL
}}
>
<WebView
source={{
uri: checkoutUrl
}}
onMessage={(message) => {
try {
const data = JSON.parse(message.nativeEvent.data);
if (data.success) {
pricingPlans.finish();
}
} catch (e) {}
}}
domStorageEnabled
javaScriptEnabled
cacheEnabled
enabled
style={{
flex: 1
<Paragraph>{strings.finishPurchaseInBrowser()}</Paragraph>
<Button
title={strings.goBack()}
onPress={() => {
setCheckoutUrl(undefined);
}}
/>
</View>
@@ -245,12 +241,14 @@ export const BuyPlan = (props: {
}
onPress={async () => {
if (isGithubRelease) {
setCheckoutUrl(
await db.subscriptions.checkoutUrl(
(pricingPlans.selectedProduct as Plan).plan,
(pricingPlans.selectedProduct as Plan).period
)
const url = await db.subscriptions.checkoutUrl(
(pricingPlans.selectedProduct as Plan).plan,
(pricingPlans.selectedProduct as Plan).period
);
if (url) {
setCheckoutUrl(url);
Linking.openURL(url);
}
return;
}

View File

@@ -15,6 +15,7 @@ import {
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import PremiumService from "../../../services/premium";
import SettingsService from "../../../services/settings";
import { useUserStore } from "../../../stores/use-user-store";
import { eCloseSheet } from "../../../utils/events";
import { AppFontSize } from "../../../utils/size";
@@ -37,7 +38,6 @@ export default function PaywallSheet<Tid extends FeatureId>(props: {
const { colors } = useThemeColors();
const pricingPlans = usePricingPlans();
useEffect(() => {
console.log("PaywallSheet mounted with feature:", props.feature);
ToastManager.hide();
if (!props.feature.availableOn) return;
const plan = pricingPlans.pricingPlans.find(
@@ -55,11 +55,11 @@ export default function PaywallSheet<Tid extends FeatureId>(props: {
}, []);
const isSubscribedOnWeb =
(PremiumService.get() &&
PremiumService.get() &&
(pricingPlans.user?.subscription?.provider ===
SubscriptionProvider.PADDLE ||
pricingPlans.user?.subscription?.provider ===
SubscriptionProvider.PADDLE) ||
pricingPlans.user?.subscription?.provider ===
SubscriptionProvider.STREETWRITERS;
SubscriptionProvider.STREETWRITERS);
const isCurrentPlatform =
(pricingPlans.user?.subscription?.provider === SubscriptionProvider.APPLE &&
@@ -194,25 +194,28 @@ export default function PaywallSheet<Tid extends FeatureId>(props: {
width: "100%"
}}
onPress={() => {
if (
pricingPlans.user?.subscription.plan ===
SubscriptionPlan.LEGACY_PRO ||
!isCurrentPlatform
) {
ToastManager.show({
message: strings.cannotChangePlan(),
context: "local"
});
return;
if (PremiumService.get()) {
if (
pricingPlans.user?.subscription.plan ===
SubscriptionPlan.LEGACY_PRO ||
!isCurrentPlatform
) {
ToastManager.show({
message: strings.cannotChangePlan(),
context: "local"
});
return;
}
if (isSubscribedOnWeb) {
ToastManager.show({
message: strings.changePlanOnWeb(),
context: "local"
});
return;
}
}
if (isSubscribedOnWeb) {
ToastManager.show({
message: strings.changePlanOnWeb(),
context: "local"
});
return;
}
eSendEvent(eCloseSheet);
if (!useUserStore.getState().user) {
Navigation.navigate("Auth", {
@@ -256,6 +259,7 @@ export default function PaywallSheet<Tid extends FeatureId>(props: {
}
PaywallSheet.present = <Tid extends FeatureId>(feature: FeatureResult<Tid>) => {
if (SettingsService.getProperty("serverUrls")) return;
presentSheet({
component: <PaywallSheet feature={feature} />
});

View File

@@ -20,6 +20,7 @@ import Paragraph from "../../ui/typography/paragraph";
import { SubscriptionPlan, SubscriptionProvider } from "@notesnook/core";
import { useUserStore } from "../../../stores/use-user-store";
import PremiumService from "../../../services/premium";
import SettingsService from "../../../services/settings";
export function PlanLimits() {
const { colors } = useThemeColors();
@@ -84,10 +85,11 @@ export function PlanLimits() {
</View>
))}
{(user?.subscription?.provider === SubscriptionProvider.PADDLE ||
{((user?.subscription?.provider === SubscriptionProvider.PADDLE ||
user?.subscription?.provider === SubscriptionProvider.STREETWRITERS ||
!isCurrentPlatform) &&
PremiumService.get() ? null : (
PremiumService.get()) ||
SettingsService.getProperty("serverUrls") ? null : (
<Button
title={strings.changePlan()}
onPress={() => {

View File

@@ -24,10 +24,11 @@ import { FlatList, View } from "react-native";
import { DraxProvider, DraxScrollView } from "react-native-drax";
import { db } from "../../common/database";
import Navigation from "../../services/navigation";
import SettingsService from "../../services/settings";
import { useMenuStore } from "../../stores/use-menu-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useUserStore } from "../../stores/use-user-store";
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import { MenuItemsList } from "../../utils/menu-items";
import { DefaultAppStyles } from "../../utils/styles";
import ReorderableList from "../list/reorderable-list";
import { MenuItemProperties } from "../sheets/menu-item-properties";
@@ -36,7 +37,6 @@ import { ColorSection } from "./color-section";
import { MenuItem } from "./menu-item";
import { PinnedSection } from "./pinned-section";
import { SideMenuHeader } from "./side-menu-header";
import { MenuItemsList } from "../../utils/menu-items";
const pro = {
title: strings.upgradePlan(),
@@ -140,9 +140,10 @@ export function SideMenuHome() {
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}}
>
{subscriptionType === SubscriptionPlan.FREE ||
!subscriptionType ||
!user ? (
{(subscriptionType === SubscriptionPlan.FREE ||
!subscriptionType ||
!user) &&
!SettingsService.getProperty("serverUrls") ? (
<Button
title={pro.title}
style={{

View File

@@ -85,6 +85,7 @@ export default function useFeatureManager() {
negativeText: strings.cancel(),
positivePress: async () => {
eSendEvent(eCloseSimpleDialog);
if (SettingsService.getProperty("serverUrls")) return;
Navigation.navigate("PayWall", {
context: "logged-in"
});

View File

@@ -25,6 +25,7 @@ import { DatabaseLogger, db } from "../common/database";
import PremiumService from "../services/premium";
import { useSettingStore } from "../stores/use-setting-store";
import { useUserStore } from "../stores/use-user-store";
import SettingsService from "../services/settings";
function numberWithCommas(x: string) {
const parts = x.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
@@ -201,7 +202,7 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
if (productId?.includes("5year")) return false;
if (isGithubRelease) {
if (
user?.subscription.trialsAvailed?.some(
user?.subscription?.trialsAvailed?.some(
(plan) => plan === planIdToIndex(planId || currentPlan)
)
) {
@@ -246,7 +247,10 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
});
setPlans([...pricingPlans]);
setUserCanRequestTrial(hasTrialOffer());
if (Config.GITHUB_RELEASE === "true") {
if (
Config.GITHUB_RELEASE === "true" &&
!SettingsService.getProperty("serverUrls")
) {
try {
const products = WebPlanCache || (await db.pricing.products());
WebPlanCache = products;
@@ -264,7 +268,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
if (!product) return;
if (Platform.OS === "android") {
if (isGithubRelease)
if (isGithubRelease) {
if (!(product as Plan)?.price) return null;
return `${(product as Plan).price.currency} ${
(product as Plan).period === "yearly"
? ((product as Plan).price.gross / 12).toFixed(2)
@@ -272,6 +277,7 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
? ((product as Plan).price.gross / (12 * 5)).toFixed(2)
: (product as Plan).price.gross
}`;
}
const pricingPhaseListItem =
(product as RNIap.SubscriptionAndroid)?.subscriptionOfferDetails?.[0]
@@ -414,7 +420,7 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
offerIndex: number
) => {
if (isGithubRelease) {
return (product as Plan).period;
return (product as Plan)?.period;
}
if (Platform.OS === "android") {
@@ -444,7 +450,7 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
if (isGithubRelease) {
return {
duration: 1,
type: (product as Plan).period
type: (product as Plan)?.period
};
}
@@ -591,7 +597,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
) => {
if (!product) return null;
if (isGithubRelease)
if (isGithubRelease) {
if (!(product as Plan)?.price) return null;
return `${(product as Plan).price.currency} ${
(product as Plan).period === "yearly"
? ((product as Plan).price.gross / 12).toFixed(2)
@@ -599,6 +606,7 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
? ((product as Plan).price.gross / (12 * 5)).toFixed(2)
: (product as Plan).price.gross
}`;
}
const androidPricingPhase = (product as RNIap.SubscriptionAndroid)
?.subscriptionOfferDetails?.[0].pricingPhases?.pricingPhaseList?.[

View File

@@ -43,6 +43,7 @@ import { planToDisplayName } from "../../utils/constants";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { SectionItem } from "./section-item";
import SettingsService from "../../services/settings";
export const getTimeLeft = (t2) => {
let daysRemaining = dayjs(t2).diff(dayjs(), "days");
@@ -339,11 +340,13 @@ const SettingsUserSection = ({ item }) => {
</Paragraph>
</TouchableOpacity>
{(user.subscription?.provider === SubscriptionProvider.PADDLE ||
{((user.subscription?.provider ===
SubscriptionProvider.PADDLE ||
user.subscription?.provider ===
SubscriptionProvider.STREETWRITERS ||
!isCurrentPlatform) &&
PremiumService.get() ? null : (
PremiumService.get()) ||
SettingsService.getProperty("serverUrls") ? null : (
<Button
title={
user.subscription?.plan !== SubscriptionPlan.FREE

View File

@@ -71,6 +71,7 @@ export function planToDisplayName(plan: SubscriptionPlan): string {
return strings.freePlan();
case SubscriptionPlan.ESSENTIAL:
return strings.essentialPlan();
case SubscriptionPlan.LEGACY_PRO:
case SubscriptionPlan.PRO:
return strings.proPlan();
case SubscriptionPlan.BELIEVER:

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { authenticator } from "otplib";
import { Tests } from "./utils";
import { TestBuilder } from "./utils";
import dotenv from "dotenv";
import path from "path";
@@ -34,20 +34,6 @@ const USER = {
}
};
async function login() {
await Tests.fromText("Login to encrypt and sync notes").tap();
await Tests.fromId("input.email").element.typeText(USER.login.email!);
await Tests.fromText("Continue").tap();
await Tests.sleep(3000);
await Tests.fromId("input.totp").element.typeText(
authenticator.generate(USER.login.totpSecret!)
);
await Tests.fromText("Next").tap();
await Tests.sleep(3000);
await Tests.fromId("input.password").element.typeText(USER.login.password!);
await Tests.fromId("input.password").element.tapReturnKey();
}
// async function deleteAccount() {
// await tapByText("Account Settings");
// await sleep(2000);
@@ -67,11 +53,27 @@ async function login() {
// await elementById("input.confirmPassword").tapReturnKey();
// }
async function login() {
await TestBuilder.create()
.waitAndTapByText("Login to encrypt and sync notes")
.typeTextById("input.email", USER.login.email!)
.tapReturnKeyById("input.email")
.wait(3000)
.typeTextById("input.totp", authenticator.generate(USER.login.totpSecret!))
.waitAndTapByText("Next")
.wait(3000)
.typeTextById("input.password", USER.login.password!)
.tapReturnKeyById("input.password")
.run();
}
describe("AUTH", () => {
it("Login", async () => {
await Tests.prepare();
await login();
await Tests.sleep(10000);
await Tests.fromText("Login to encrypt and sync notes").isNotVisible();
await TestBuilder.create()
.prepare()
.addStep(login)
.wait(3000)
.isNotVisibleByText("Notesnook Plans")
.run();
});
});

View File

@@ -1,3 +1,22 @@
/*
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 { notesnook } from "../test.ids";
import { TestBuilder } from "./utils";

View File

@@ -18,266 +18,209 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { notesnook } from "../test.ids";
import { Tests } from "./utils";
import { TestBuilder } from "./utils";
describe("NOTEBOOKS", () => {
it("Create a notebook with title only", async () => {
await Tests.prepare();
await Tests.openSideMenu();
await Tests.fromId("tab-notebooks").waitAndTap();
await Tests.fromId("sidebar-add-button").waitAndTap();
await Tests.createNotebook("Notebook 1", false);
await Tests.sleep(500);
await Tests.fromText("Notebook 1").isVisible();
await TestBuilder.create()
.prepare()
.openSideMenu()
.waitAndTapById("tab-notebooks")
.waitAndTapById("sidebar-add-button")
.createNotebook("Notebook 1", false)
.wait(500)
.isVisibleByText("Notebook 1")
.run();
});
it("Create a notebook title & description", async () => {
await Tests.prepare();
await Tests.openSideMenu();
await Tests.fromId("tab-notebooks").waitAndTap();
await Tests.fromId("sidebar-add-button").waitAndTap();
await Tests.createNotebook("Notebook 1", true);
await Tests.sleep(500);
await Tests.fromText("Notebook 1").isVisible();
it("Create a notebook with title & description", async () => {
await TestBuilder.create()
.prepare()
.openSideMenu()
.waitAndTapById("tab-notebooks")
.waitAndTapById("sidebar-add-button")
.createNotebook("Notebook 1", true)
.wait(500)
.isVisibleByText("Notebook 1")
.run();
});
it("Add a sub notebook to a notebook", async () => {
await Tests.prepare();
await Tests.openSideMenu();
await Tests.fromId("tab-notebooks").waitAndTap();
await Tests.fromId("sidebar-add-button").waitAndTap();
await Tests.createNotebook("Notebook 1", true);
await Tests.sleep(500);
await Tests.fromText("Notebook 1").element.longPress();
await Tests.sleep(500);
await Tests.fromText("Add notebook").waitAndTap();
await Tests.createNotebook("Sub notebook", true);
await Tests.sleep(500);
await Tests.fromId("expand-notebook-0").waitAndTap();
await Tests.fromText("Sub notebook").isVisible();
await Tests.fromText("Sub notebook").element.longPress();
await Tests.sleep(500);
await Tests.fromText("Move to trash").waitAndTap();
await Tests.fromText("Delete").waitAndTap();
await Tests.fromText("Sub notebook").isNotVisible();
await TestBuilder.create()
.prepare()
.openSideMenu()
.waitAndTapById("tab-notebooks")
.waitAndTapById("sidebar-add-button")
.createNotebook("Notebook 1", true)
.wait(500)
.longPressByText("Notebook 1")
.wait(500)
.waitAndTapByText("Add notebook")
.createNotebook("Sub notebook", true)
.wait(500)
.waitAndTapById("expand-notebook-0")
.isVisibleByText("Sub notebook")
.longPressByText("Sub notebook")
.wait(500)
.waitAndTapByText("Move to trash")
.waitAndTapByText("Delete")
.isNotVisibleByText("Sub notebook")
.run();
});
it("Edit notebook", async () => {
await Tests.prepare();
await Tests.openSideMenu();
await Tests.fromId("tab-notebooks").waitAndTap();
await Tests.fromId("sidebar-add-button").waitAndTap();
await Tests.createNotebook("Notebook 1", true);
await Tests.sleep(500);
await Tests.fromText("Notebook 1").element.longPress();
await Tests.sleep(500);
await Tests.fromText("Edit notebook").waitAndTap();
await Tests.fromId(
notesnook.ids.dialogs.notebook.inputs.title
).element.typeText(" (edited)");
await Tests.fromText("Save").waitAndTap();
await Tests.fromText("Notebook 1 (edited)").isVisible();
await TestBuilder.create()
.prepare()
.openSideMenu()
.waitAndTapById("tab-notebooks")
.waitAndTapById("sidebar-add-button")
.createNotebook("Notebook 1", true)
.wait(500)
.longPressByText("Notebook 1")
.wait(500)
.waitAndTapByText("Edit notebook")
.typeTextById(notesnook.ids.dialogs.notebook.inputs.title, " (edited)")
.waitAndTapByText("Save")
.isVisibleByText("Notebook 1 (edited)")
.run();
});
it("Edit a sub notebook", async () => {
await Tests.prepare();
await Tests.openSideMenu();
await Tests.fromId("tab-notebooks").waitAndTap();
await Tests.fromId("sidebar-add-button").waitAndTap();
await Tests.createNotebook("Notebook 1", true);
await Tests.sleep(500);
await Tests.fromText("Notebook 1").element.longPress();
await Tests.sleep(500);
await Tests.fromText("Add notebook").waitAndTap();
await Tests.createNotebook("Sub notebook", true);
await Tests.sleep(500);
await Tests.fromId("expand-notebook-0").waitAndTap();
await Tests.fromText("Sub notebook").element.longPress();
await Tests.sleep(500);
await Tests.fromText("Edit notebook").waitAndTap();
await Tests.fromId(
notesnook.ids.dialogs.notebook.inputs.title
).element.typeText(" (edited)");
await Tests.fromText("Save").waitAndTap();
await Tests.fromText("Sub notebook (edited)").isVisible();
await TestBuilder.create()
.prepare()
.openSideMenu()
.waitAndTapById("tab-notebooks")
.waitAndTapById("sidebar-add-button")
.createNotebook("Notebook 1", true)
.wait(500)
.longPressByText("Notebook 1")
.wait(500)
.waitAndTapByText("Add notebook")
.createNotebook("Sub notebook", true)
.wait(500)
.waitAndTapById("expand-notebook-0")
.longPressByText("Sub notebook")
.wait(500)
.waitAndTapByText("Edit notebook")
.typeTextById(notesnook.ids.dialogs.notebook.inputs.title, " (edited)")
.waitAndTapByText("Save")
.isVisibleByText("Sub notebook (edited)")
.run();
});
it("Add a note to notebook", async () => {
await Tests.prepare();
await Tests.openSideMenu();
await Tests.fromId("tab-notebooks").waitAndTap();
await Tests.fromId("sidebar-add-button").waitAndTap();
await Tests.createNotebook("Notebook 1", true);
await Tests.sleep(500);
await Tests.fromText("Notebook 1").waitAndTap();
await Tests.createNote();
await TestBuilder.create()
.prepare()
.openSideMenu()
.waitAndTapById("tab-notebooks")
.waitAndTapById("sidebar-add-button")
.createNotebook("Notebook 1", true)
.wait(500)
.waitAndTapByText("Notebook 1")
.createNote()
.run();
});
it("Remove note from Notebook", async () => {
await Tests.prepare();
await Tests.openSideMenu();
await Tests.fromId("tab-notebooks").waitAndTap();
await Tests.fromId("sidebar-add-button").waitAndTap();
await Tests.createNotebook("Notebook 1", true);
await Tests.sleep(500);
await Tests.fromText("Notebook 1").waitAndTap();
await Tests.sleep(500);
let note = await Tests.createNote();
await Tests.fromText(note.body).element.longPress();
await Tests.sleep(500);
await Tests.fromId("select-minus").waitAndTap();
await Tests.fromId(note.title).isNotVisible();
it.only("Remove note from notebook", async () => {
await TestBuilder.create()
.prepare()
.openSideMenu()
.waitAndTapById("tab-notebooks")
.waitAndTapById("sidebar-add-button")
.createNotebook("Notebook 1", true)
.wait(500)
.waitAndTapByText("Notebook 1")
.wait(500)
.createNote()
.saveResult()
.processResult(async (note) => {
await TestBuilder.create()
.longPressByText(note.body)
.wait(500)
.waitAndTapById("select-minus")
.isNotVisibleById(note.title)
.run();
})
.run();
});
it("Add/Remove note to notebook from home", async () => {
await Tests.prepare();
await Tests.openSideMenu();
await Tests.fromId("tab-notebooks").waitAndTap();
await Tests.fromId("sidebar-add-button").waitAndTap();
await Tests.createNotebook("Notebook 1", true);
await Tests.sleep(500);
await Tests.fromId("tab-home").waitAndTap();
await Tests.fromText("Notes").waitAndTap();
await Tests.createNote();
await Tests.fromId(notesnook.listitem.menu).waitAndTap();
await Tests.sleep(500);
await Tests.fromId("icon-notebooks").waitAndTap();
await Tests.fromText("Notebook 1").waitAndTap();
await Tests.fromId("floating-save-button").waitAndTap();
await Tests.fromText("Notebook 1").isVisible();
await TestBuilder.create()
.prepare()
.openSideMenu()
.waitAndTapById("tab-notebooks")
.waitAndTapById("sidebar-add-button")
.createNotebook("Notebook 1", true)
.wait(500)
.waitAndTapById("tab-home")
.waitAndTapByText("Notes")
.createNote()
.waitAndTapById(notesnook.listitem.menu)
.wait(500)
.waitAndTapById("icon-notebooks")
.waitAndTapByText("Notebook 1")
.waitAndTapById("floating-save-button")
.isVisibleByText("Notebook 1")
.run();
});
it("Edit notebook title, description", async () => {
await Tests.prepare();
await Tests.openSideMenu();
await Tests.fromId("tab-notebooks").waitAndTap();
await Tests.fromId("sidebar-add-button").waitAndTap();
await Tests.createNotebook();
await Tests.sleep(500);
await Tests.fromText("Notebook 1").isVisible();
await Tests.fromText("Notebook 1").element.longPress();
await Tests.sleep(500);
await Tests.fromText("Edit notebook").waitAndTap();
await Tests.fromId(
notesnook.ids.dialogs.notebook.inputs.title
).element.typeText(" (Edited)");
await Tests.fromId(
notesnook.ids.dialogs.notebook.inputs.description
).element.clearText();
await Tests.fromId(
notesnook.ids.dialogs.notebook.inputs.description
).element.typeText("Description of Notebook 1 (Edited)");
await Tests.fromText("Save").waitAndTap();
await Tests.fromText("Notebook 1 (Edited)").isVisible();
it("Edit notebook title and description", async () => {
await TestBuilder.create()
.prepare()
.openSideMenu()
.waitAndTapById("tab-notebooks")
.waitAndTapById("sidebar-add-button")
.createNotebook()
.wait(500)
.isVisibleByText("Notebook 1")
.longPressByText("Notebook 1")
.wait(500)
.waitAndTapByText("Edit notebook")
.typeTextById(notesnook.ids.dialogs.notebook.inputs.title, " (Edited)")
.clearTextById(notesnook.ids.dialogs.notebook.inputs.description)
.typeTextById(
notesnook.ids.dialogs.notebook.inputs.description,
"Description of Notebook 1 (Edited)"
)
.waitAndTapByText("Save")
.isVisibleByText("Notebook 1 (Edited)")
.run();
});
it("Move notebook to trash", async () => {
await Tests.prepare();
await Tests.openSideMenu();
await Tests.fromId("tab-notebooks").waitAndTap();
await Tests.fromId("sidebar-add-button").waitAndTap();
await Tests.createNotebook("Notebook 1", false);
await Tests.sleep(500);
await Tests.fromText("Notebook 1").isVisible();
await Tests.fromText("Notebook 1").element.longPress();
await Tests.sleep(500);
await Tests.fromText("Move to trash").waitAndTap();
await Tests.fromText("Delete").waitAndTap();
await Tests.fromId("tab-home").waitAndTap();
await Tests.fromText("Trash").waitAndTap();
await Tests.fromText("Notebook 1").isVisible();
});
it.skip("Move notebook to trash with notes", async () => {
await Tests.prepare();
let note = await Tests.createNote();
await Tests.openSideMenu();
await Tests.fromId("tab-notebooks").waitAndTap();
await Tests.fromId("sidebar-add-button").waitAndTap();
await Tests.createNotebook("Notebook 1", false);
await device.pressBack();
await Tests.fromId(notesnook.ids.note.get(0)).element.longPress();
await Tests.sleep(500);
await Tests.fromId("select-plus").waitAndTap();
await Tests.fromText("Notebook 1").isVisible();
await Tests.fromText("Notebook 1").waitAndTap();
await Tests.fromId("floating-save-button").waitAndTap();
await Tests.sleep(500);
await Tests.openSideMenu();
await Tests.fromId("tab-home").waitAndTap();
await Tests.fromId("tab-notebooks").waitAndTap();
await Tests.sleep(500);
await Tests.fromText("Notebook 1").isVisible();
await Tests.fromText("Notebook 1").element.longPress();
await Tests.sleep(500);
await Tests.fromText("Move to trash").waitAndTap();
await Tests.fromText(
"Move all notes in this notebook to trash"
).waitAndTap();
await Tests.fromText("Delete").waitAndTap();
await Tests.sleep(500);
await Tests.fromId("tab-home").waitAndTap();
await Tests.fromText("Trash").waitAndTap();
await Tests.fromText("Notebook 1").isVisible();
await Tests.fromText(note.body).isVisible();
await TestBuilder.create()
.prepare()
.openSideMenu()
.waitAndTapById("tab-notebooks")
.waitAndTapById("sidebar-add-button")
.createNotebook("Notebook 1", false)
.wait(500)
.isVisibleByText("Notebook 1")
.longPressByText("Notebook 1")
.wait(500)
.waitAndTapByText("Move to trash")
.waitAndTapByText("Delete")
.waitAndTapById("tab-home")
.waitAndTapByText("Trash")
.isVisibleByText("Notebook 1")
.run();
});
it("Pin notebook to side menu", async () => {
await Tests.prepare();
await Tests.openSideMenu();
await Tests.fromId("tab-notebooks").waitAndTap();
await Tests.fromId("sidebar-add-button").waitAndTap();
await Tests.createNotebook("Notebook 1", false);
await Tests.sleep(500);
await Tests.fromText("Notebook 1").isVisible();
await Tests.fromText("Notebook 1").element.longPress();
await Tests.sleep(500);
await Tests.fromId("icon-add-shortcut").waitAndTap();
await Tests.sleep(500);
await Tests.fromId("tab-home").waitAndTap();
await Tests.fromText("Notebook 1").isVisible();
await TestBuilder.create()
.prepare()
.openSideMenu()
.waitAndTapById("tab-notebooks")
.waitAndTapById("sidebar-add-button")
.createNotebook("Notebook 1", false)
.wait(500)
.isVisibleByText("Notebook 1")
.longPressByText("Notebook 1")
.wait(500)
.waitAndTapById("icon-add-shortcut")
.wait(500)
.waitAndTapById("tab-home")
.isVisibleByText("Notebook 1")
.run();
});
});

View File

@@ -17,15 +17,17 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Tests } from "./utils";
import { TestBuilder } from "./utils";
describe("Search", () => {
it("Search for a note", async () => {
await Tests.prepare();
let note = await Tests.createNote();
await Tests.fromId("search-header").waitAndTap();
await Tests.fromId("search-input").element.typeText("Test");
await Tests.sleep(1000);
await Tests.fromText("1").isVisible();
await TestBuilder.create()
.prepare()
.createNote()
.waitAndTapById("search-header")
.typeTextById("search-input", "Test")
.wait(1000)
.isVisibleByText("1")
.run();
});
});

View File

@@ -18,74 +18,99 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { notesnook } from "../test.ids";
import { Tests } from "./utils";
async function sortBy(sorting: string) {
await Tests.fromId("icon-sort").waitAndTap();
await Tests.sleep(500);
await Tests.fromText(sorting).waitAndTap();
await device.pressBack();
}
import { TestBuilder } from "./utils";
describe("Sort & filter", () => {
it("Sort by date-edited/date-created", async () => {
await Tests.prepare();
let webview = web();
await Tests.createNote("Note 1", "Note 1");
await Tests.createNote("Note 2", "Note 2");
await Tests.fromText("Note 1").waitAndTap();
await webview.element(by.web.className("ProseMirror")).tap();
await webview
.element(by.web.className("ProseMirror"))
.typeText("Edited ", true);
await device.pressBack();
await sortBy("Date created");
await Tests.fromId(notesnook.listitem.menu).waitAndTap();
await device.pressBack();
await sortBy("Date edited");
await Tests.fromId(notesnook.listitem.menu).waitAndTap();
await Tests.sleep(500);
await device.pressBack();
await TestBuilder.create()
.prepare()
.createNote("Note 1", "Note 1")
.createNote("Note 2", "Note 2")
.waitAndTapByText("Note 1")
.addStep(async () => {
const webview = web();
await webview.element(by.web.className("ProseMirror")).tap();
await webview
.element(by.web.className("ProseMirror"))
.typeText("Edited ", true);
})
.pressBack()
.waitAndTapById("icon-sort")
.wait(500)
.waitAndTapByText("Date created")
.pressBack()
.waitAndTapById(notesnook.listitem.menu)
.pressBack()
.waitAndTapById("icon-sort")
.wait(500)
.waitAndTapByText("Date edited")
.pressBack()
.waitAndTapById(notesnook.listitem.menu)
.run();
});
it("Disable grouping", async () => {
await Tests.prepare();
await Tests.createNote("Note 1", "Note 1");
await sortBy("None");
await Tests.fromText("ALL").isVisible();
await TestBuilder.create()
.prepare()
.createNote("Note 1", "Note 1")
.waitAndTapById("icon-sort")
.wait(500)
.waitAndTapByText("None")
.pressBack()
.isVisibleByText("ALL")
.run();
});
it("Group by Abc", async () => {
await Tests.prepare();
await Tests.createNote("Note 1", "Note 1");
await sortBy("Abc");
await Tests.fromText("N").isVisible();
await TestBuilder.create()
.prepare()
.createNote("Note 1", "Note 1")
.waitAndTapById("icon-sort")
.wait(500)
.waitAndTapByText("Abc")
.pressBack()
.isVisibleByText("N")
.run();
});
it("Group by Year", async () => {
await Tests.prepare();
await Tests.createNote("Note 1", "Note 1");
await sortBy("Year");
await TestBuilder.create()
.prepare()
.createNote("Note 1", "Note 1")
.waitAndTapById("icon-sort")
.wait(500)
.waitAndTapByText("Year")
.run();
});
it("Group by Week", async () => {
await Tests.prepare();
await Tests.createNote("Note 1", "Note 1");
await sortBy("Week");
await TestBuilder.create()
.prepare()
.createNote("Note 1", "Note 1")
.waitAndTapById("icon-sort")
.wait(500)
.waitAndTapByText("Week")
.run();
});
it("Group by Month", async () => {
await Tests.prepare();
await Tests.createNote("Note 1", "Note 1");
await sortBy("Month");
await TestBuilder.create()
.prepare()
.createNote("Note 1", "Note 1")
.waitAndTapById("icon-sort")
.wait(500)
.waitAndTapByText("Month")
.run();
});
it("Compact mode", async () => {
await Tests.prepare();
await Tests.createNote("Note 1", "Note 1");
await Tests.fromId("icon-compact-mode").waitAndTap();
await Tests.fromText("Note 1").isNotVisible();
await Tests.fromId("icon-compact-mode").waitAndTap();
await Tests.fromText("Note 1").isVisible();
await TestBuilder.create()
.prepare()
.createNote("Note 1", "Note 1")
.waitAndTapById("icon-compact-mode")
.isNotVisibleByText("Note 1")
.waitAndTapById("icon-compact-mode")
.isVisibleByText("Note 1")
.run();
});
});

View File

@@ -18,95 +18,101 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { notesnook } from "../test.ids";
import { Tests } from "./utils";
import { TestBuilder } from "./utils";
describe("Tags", () => {
it("Create a tag", async () => {
await Tests.prepare();
await Tests.openSideMenu();
await Tests.fromId("tab-tags").waitAndTap();
await Tests.fromText("No tags").isVisible();
await Tests.fromId("sidebar-add-button").waitAndTap();
await Tests.fromId("input-value").element.typeText("testtag");
await Tests.fromText("Add").waitAndTap();
await Tests.fromText("testtag").isVisible();
await TestBuilder.create()
.prepare()
.openSideMenu()
.waitAndTapById("tab-tags")
.isVisibleByText("No tags")
.waitAndTapById("sidebar-add-button")
.typeTextById("input-value", "testtag")
.waitAndTapByText("Add")
.isVisibleByText("testtag")
.run();
});
it("Tag a note", async () => {
await Tests.prepare();
let note = await Tests.createNote();
await Tests.fromId(notesnook.listitem.menu).waitAndTap();
await Tests.sleep(500);
await Tests.fromText("Add tag").waitAndTap();
await Tests.fromId("tag-input").element.typeText("testtag");
await Tests.fromText('Add "#testtag"').waitAndTap();
await Tests.fromText("#testtag").isVisible();
await device.pressBack();
await device.pressBack();
await Tests.openSideMenu();
await Tests.fromId("tab-tags").waitAndTap();
await Tests.fromText("#testtag").waitAndTap();
await Tests.fromText(note.body).isVisible();
await TestBuilder.create()
.prepare()
.createNote()
.saveResult()
.waitAndTapById(notesnook.listitem.menu)
.wait(500)
.waitAndTapByText("Add tag")
.typeTextById("tag-input", "testtag")
.waitAndTapByText('Add "#testtag"')
.isVisibleByText("#testtag")
.pressBack(2)
.openSideMenu()
.waitAndTapById("tab-tags")
.waitAndTapByText("#testtag")
.processResult(async (note) => {
await TestBuilder.create().isVisibleByText(note.body).run();
})
.run();
});
it("Untag a note", async () => {
await Tests.prepare();
await Tests.createNote();
await Tests.fromId(notesnook.listitem.menu).waitAndTap();
await Tests.sleep(500);
await Tests.fromText("Add tag").waitAndTap();
await Tests.fromId("tag-input").element.typeText("testtag");
await Tests.fromText('Add "#testtag"').waitAndTap();
await Tests.fromText("#testtag").isVisible();
await Tests.fromText("#testtag").waitAndTap();
await device.pressBack();
await device.pressBack();
await Tests.fromText("#testtag").isNotVisible();
await TestBuilder.create()
.prepare()
.createNote()
.waitAndTapById(notesnook.listitem.menu)
.wait(500)
.waitAndTapByText("Add tag")
.typeTextById("tag-input", "testtag")
.waitAndTapByText('Add "#testtag"')
.isVisibleByText("#testtag")
.waitAndTapByText("#testtag")
.pressBack(2)
.isNotVisibleByText("#testtag")
.run();
});
it("Create shortcut of a tag", async () => {
await Tests.prepare();
await Tests.createNote();
await Tests.fromId(notesnook.listitem.menu).waitAndTap();
await Tests.sleep(500);
await Tests.fromText("Add tag").waitAndTap();
await Tests.fromId("tag-input").element.typeText("testtag");
await Tests.fromText('Add "#testtag"').waitAndTap();
await Tests.fromText("#testtag").isVisible();
await device.pressBack();
await device.pressBack();
await Tests.openSideMenu();
await Tests.fromId("tab-tags").waitAndTap();
await Tests.fromText("testtag").element.longPress();
await Tests.sleep(500);
await Tests.fromText("Add shortcut").waitAndTap();
await Tests.fromId("tab-home").waitAndTap();
await Tests.fromText("testtag").isVisible();
await TestBuilder.create()
.prepare()
.createNote()
.waitAndTapById(notesnook.listitem.menu)
.wait(500)
.waitAndTapByText("Add tag")
.typeTextById("tag-input", "testtag")
.waitAndTapByText('Add "#testtag"')
.isVisibleByText("#testtag")
.pressBack(2)
.openSideMenu()
.waitAndTapById("tab-tags")
.longPressByText("testtag")
.wait(500)
.waitAndTapByText("Add shortcut")
.waitAndTapById("tab-home")
.isVisibleByText("testtag")
.run();
});
it("Rename a tag", async () => {
await Tests.prepare();
await Tests.createNote();
await Tests.fromId(notesnook.listitem.menu).waitAndTap();
await Tests.sleep(500);
await Tests.fromText("Add tag").waitAndTap();
await Tests.fromId("tag-input").element.typeText("testtag");
await Tests.fromText('Add "#testtag"').waitAndTap();
await Tests.fromText("#testtag").isVisible();
await device.pressBack();
await device.pressBack();
await Tests.openSideMenu();
await Tests.fromId("tab-tags").waitAndTap();
await Tests.fromText("testtag").element.longPress();
await Tests.sleep(500);
await Tests.fromText("Rename").waitAndTap();
await Tests.sleep(100);
await Tests.fromId("input-value").element.clearText();
await Tests.fromId("input-value").element.typeText("testtag_edited");
await Tests.fromText("Save").waitAndTap();
await Tests.fromText("testtag_edited").isVisible();
await TestBuilder.create()
.prepare()
.createNote()
.waitAndTapById(notesnook.listitem.menu)
.wait(500)
.waitAndTapByText("Add tag")
.typeTextById("tag-input", "testtag")
.waitAndTapByText('Add "#testtag"')
.isVisibleByText("#testtag")
.pressBack(2)
.openSideMenu()
.waitAndTapById("tab-tags")
.longPressByText("testtag")
.wait(500)
.waitAndTapByText("Rename")
.wait(100)
.clearTextById("input-value")
.typeTextById("input-value", "testtag_edited")
.waitAndTapByText("Save")
.isVisibleByText("testtag_edited")
.run();
});
});

View File

@@ -171,7 +171,7 @@ class TestBuilder {
});
}
wait(duration: number) {
wait(duration = 500) {
return this.addStep(async () => {
await Tests.sleep(duration);
});
@@ -280,6 +280,13 @@ class TestBuilder {
});
}
tapReturnKeyById(id: string) {
return this.addStep(async () => {
const element = new Element("id", id);
await element.element.tapReturnKey();
});
}
tapByText(text: string, point?: Detox.Point2D) {
return this.addStep(async () => {
const element = new Element("text", text);
@@ -303,6 +310,12 @@ class TestBuilder {
});
}
clearTextById(id: string) {
return this.addStep(async () => {
await Element.fromId(id).element.clearText();
});
}
pressBack(count = 1) {
return this.addStep(async () => {
for (let i = 0; i < count; i++) {
@@ -311,6 +324,20 @@ class TestBuilder {
});
}
longPressByText(text: string) {
return this.addStep(async () => {
const element = new Element("text", text);
await element.element.longPress();
});
}
longPressById(id: string) {
return this.addStep(async () => {
const element = new Element("id", id);
await element.element.longPress();
});
}
async run() {
for (const step of this.steps) {
const result = step.call(this);

View File

@@ -18,134 +18,133 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { expect } from "detox";
import { notesnook } from "../test.ids";
import { Tests } from "./utils";
import { TestBuilder, Tests } from "./utils";
async function lockNote() {
await Tests.fromId(notesnook.listitem.menu).waitAndTap();
await Tests.sleep(500);
await Tests.fromId("icon-lock-unlock").waitAndTap();
await Tests.sleep(500);
await Tests.fromText("Lock").isVisible();
await Tests.fromId(notesnook.ids.dialogs.vault.pwd).element.typeText("1234");
await Tests.fromId(notesnook.ids.dialogs.vault.pwdAlt).element.typeText(
"1234"
);
await Tests.fromText("Lock").waitAndTap();
await Tests.fromId("note-locked-icon").isVisible();
await TestBuilder.create()
.waitAndTapById(notesnook.listitem.menu)
.wait()
.waitAndTapById("icon-lock-unlock")
.wait()
.isVisibleByText("Lock")
.typeTextById(notesnook.ids.dialogs.vault.pwd, "1234")
.typeTextById(notesnook.ids.dialogs.vault.pwdAlt, "1234")
.waitAndTapByText("Lock")
.isVisibleById("note-locked-icon")
.run();
}
async function removeFromVault() {
await Tests.fromId(notesnook.listitem.menu).waitAndTap();
await Tests.sleep(500);
await Tests.fromId("icon-lock-unlock").waitAndTap();
await Tests.sleep(500);
await Tests.fromId(notesnook.ids.dialogs.vault.pwd).element.typeText("1234");
await Tests.fromText("Unlock").waitAndTap();
await Tests.fromId("note-locked-icon").isNotVisible();
await TestBuilder.create()
.waitAndTapById(notesnook.listitem.menu)
.wait()
.waitAndTapById("icon-lock-unlock")
.wait()
.typeTextById(notesnook.ids.dialogs.vault.pwd, "1234")
.waitAndTapByText("Unlock")
.isNotVisibleById("note-locked-icon")
.run();
}
async function openLockedNote(pwd?: string) {
await Tests.fromId(notesnook.ids.note.get(0)).waitAndTap();
await Tests.sleep(500);
await web()
.element(by.web.name("password"))
.typeText(pwd || "1234", false);
await web().element(by.web.className("unlock-note")).tap();
await Tests.sleep(500);
await expect(web().element(by.web.className("unlock-note"))).not.toExist();
async function openLockedNote(pwd = "1234") {
await TestBuilder.create()
.waitAndTapById(notesnook.ids.note.get(0))
.wait()
.addStep(async () => {
await web().element(by.web.name("password")).typeText(pwd, false);
await web().element(by.web.className("unlock-note")).tap();
await Tests.sleep(500);
await expect(
web().element(by.web.className("unlock-note"))
).not.toExist();
})
.run();
}
async function goToPrivacySecuritySettings() {
await Tests.openSideMenu();
await Tests.fromId("sidemenu-settings-icon").waitAndTap();
await Tests.sleep(300);
await Tests.fromText("Settings").waitAndTap();
await Tests.fromText("Vault").waitAndTap();
await TestBuilder.create()
.openSideMenu()
.waitAndTapById("sidemenu-settings-icon")
.wait()
.waitAndTapByText("Settings")
.waitAndTapByText("Vault")
.run();
}
describe("VAULT", () => {
it("Create vault from settings", async () => {
await Tests.prepare();
await goToPrivacySecuritySettings();
await Tests.fromText("Create vault").waitAndTap();
await Tests.fromId(notesnook.ids.dialogs.vault.pwd).element.typeText(
"1234"
);
await Tests.fromId(notesnook.ids.dialogs.vault.pwdAlt).element.typeText(
"1234"
);
await Tests.fromText("Create").waitAndTap();
await Tests.fromText("Clear vault").isVisible();
await TestBuilder.create()
.prepare()
.addStep(goToPrivacySecuritySettings)
.waitAndTapByText("Create vault")
.typeTextById(notesnook.ids.dialogs.vault.pwd, "1234")
.typeTextById(notesnook.ids.dialogs.vault.pwdAlt, "1234")
.waitAndTapByText("Create")
.isVisibleByText("Clear vault")
.run();
});
it("Change vault password", async () => {
await Tests.prepare();
await Tests.createNote();
await lockNote();
await goToPrivacySecuritySettings();
await Tests.fromText("Change vault password").waitAndTap();
await Tests.fromId(notesnook.ids.dialogs.vault.pwd).element.typeText(
"1234"
);
await Tests.fromId(notesnook.ids.dialogs.vault.changePwd).element.typeText(
"2362"
);
await Tests.fromText("Change").waitAndTap();
await device.pressBack();
await device.pressBack();
await device.pressBack();
await Tests.sleep(500);
await openLockedNote("2362");
it.only("Change vault password", async () => {
await TestBuilder.create()
.prepare()
.createNote()
.addStep(lockNote)
.addStep(goToPrivacySecuritySettings)
.waitAndTapByText("Change vault password")
.typeTextById(notesnook.ids.dialogs.vault.pwd, "1234")
.typeTextById(notesnook.ids.dialogs.vault.changePwd, "2362")
.waitAndTapByText("Change")
.pressBack(3)
.addStep(async () => await openLockedNote("2362"))
.run();
});
it("Delete vault", async () => {
await Tests.prepare();
await Tests.createNote();
await lockNote();
await goToPrivacySecuritySettings();
await Tests.fromText("Delete vault").waitAndTap();
await Tests.fromId(notesnook.ids.dialogs.vault.pwd).element.typeText(
"1234"
);
await Tests.fromText("Delete").waitAndTap();
await Tests.sleep(300);
await Tests.fromText("Create vault").isVisible();
await device.pressBack();
await device.pressBack();
await device.pressBack();
await Tests.fromId(notesnook.listitem.menu).isVisible();
await TestBuilder.create()
.prepare()
.createNote()
.addStep(lockNote)
.addStep(goToPrivacySecuritySettings)
.waitAndTapByText("Delete vault")
.typeTextById(notesnook.ids.dialogs.vault.pwd, "1234")
.waitAndTapByText("Delete")
.isVisibleByText("Create vault")
.pressBack(3)
.isVisibleById(notesnook.listitem.menu)
.run();
});
it("Delete vault with locked notes", async () => {
await Tests.prepare();
await Tests.createNote();
await lockNote();
await goToPrivacySecuritySettings();
await Tests.fromText("Delete vault").waitAndTap();
await Tests.fromId(notesnook.ids.dialogs.vault.pwd).element.typeText(
"1234"
);
await Tests.fromText("Delete notes in this vault").waitAndTap();
await Tests.fromText("Delete").waitAndTap();
await Tests.sleep(300);
await Tests.fromText("Create vault").isVisible();
await device.pressBack();
await device.pressBack();
await device.pressBack();
await Tests.fromId(notesnook.listitem.menu).isNotVisible();
await TestBuilder.create()
.prepare()
.createNote()
.addStep(lockNote)
.addStep(goToPrivacySecuritySettings)
.waitAndTapByText("Delete vault")
.typeTextById(notesnook.ids.dialogs.vault.pwd, "1234")
.waitAndTapByText("Delete notes in this vault")
.waitAndTapByText("Delete")
.isVisibleByText("Create vault")
.pressBack(3)
.isNotVisibleById(notesnook.listitem.menu)
.run();
});
it("Add a note to vault", async () => {
await Tests.prepare();
await Tests.createNote();
await lockNote();
await openLockedNote();
await TestBuilder.create()
.prepare()
.createNote()
.addStep(lockNote)
.addStep(openLockedNote)
.run();
});
it("Remove note from vault", async () => {
await Tests.prepare();
await Tests.createNote();
await lockNote();
await removeFromVault();
await TestBuilder.create()
.prepare()
.createNote()
.addStep(lockNote)
.addStep(removeFromVault)
.run();
});
});

View File

@@ -1 +1 @@
GITHUB_RELEASE=false
GITHUB_RELEASE=true

View File

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

View File

@@ -152,7 +152,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.streetwriters.notesnook" />
<data android:scheme="notesnook" />
</intent-filter>
</activity>

View File

@@ -1089,7 +1089,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2150;
CURRENT_PROJECT_VERSION = 2151;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1163,7 +1163,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3.2;
MARKETING_VERSION = 3.3.3;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1194,7 +1194,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2150;
CURRENT_PROJECT_VERSION = 2151;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1268,7 +1268,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3.2;
MARKETING_VERSION = 3.3.3;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1427,7 +1427,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2150;
CURRENT_PROJECT_VERSION = 2151;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1439,7 +1439,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.3.2;
MARKETING_VERSION = 3.3.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1470,7 +1470,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2150;
CURRENT_PROJECT_VERSION = 2151;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1483,7 +1483,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.3.2;
MARKETING_VERSION = 3.3.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1513,7 +1513,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2150;
CURRENT_PROJECT_VERSION = 2151;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1594,7 +1594,7 @@
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
MARKETING_VERSION = 3.3.2;
MARKETING_VERSION = 3.3.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1625,7 +1625,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2150;
CURRENT_PROJECT_VERSION = 2151;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1707,7 +1707,7 @@
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
MARKETING_VERSION = 3.3.2;
MARKETING_VERSION = 3.3.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

@@ -87,6 +87,8 @@ export async function scheduleFullBackups() {
}
export async function shouldAddAutoBackupsDisabledNotice() {
if (isIgnored("autoBackupsOff")) return false;
const user = await db.user.getUser();
if (!user) return false;

View File

@@ -50,7 +50,6 @@ const TRIAL_PERIODS: Record<Period, number> = {
};
export function toPricingInfo(plan: Plan, user: User | undefined): PricingInfo {
console.log("TO PRICING INFO", plan);
return {
country: plan.country,
period: plan.period,
@@ -58,9 +57,9 @@ export function toPricingInfo(plan: Plan, user: User | undefined): PricingInfo {
currency: plan.currency,
id: plan.id,
period: plan.period,
subtotal: formatPrice(plan.price.gross, plan.currency),
subtotal: formatPrice(plan.price.net, plan.currency),
tax: formatPrice(plan.price.tax, plan.currency),
total: formatPrice(plan.price.net, plan.currency),
total: formatPrice(plan.price.gross, plan.currency),
trial_period: isTrialAvailableForPlan(plan.plan, user)
? {
frequency: TRIAL_PERIODS[plan.period]
@@ -74,9 +73,9 @@ export function toPricingInfo(plan: Plan, user: User | undefined): PricingInfo {
currency: plan.currency,
id: plan.id,
period: plan.period,
subtotal: formatPrice(plan.price.gross, plan.currency),
subtotal: formatPrice(plan.price.net, plan.currency),
tax: formatPrice(plan.price.tax, plan.currency),
total: formatPrice(plan.price.net, plan.currency)
total: formatPrice(plan.price.gross, plan.currency)
}
: undefined
};

View File

@@ -245,7 +245,7 @@ function getPricingInfo(
id: price.price_id,
period: options.period,
subtotal: formatPrice(
data.recurring_totals.subtotal,
data.recurring_totals.subtotal - data.recurring_totals.discount,
data.currency_code
),
total: formatPrice(data.recurring_totals.total, data.currency_code),
@@ -256,7 +256,10 @@ function getPricingInfo(
currency: data.currency_code,
id: price.price_id,
period: options.period,
subtotal: formatPrice(totals.subtotal, data.currency_code),
subtotal: formatPrice(
totals.subtotal - totals.discount,
data.currency_code
),
total: formatPrice(totals.total, data.currency_code),
tax: formatPrice(totals.tax, data.currency_code),
trial_period: price.trial_period

View File

@@ -22,7 +22,7 @@ import { updateStatus } from "../hooks/use-status";
import {
Gateway,
WEB_EXTENSION_CHANNEL_EVENTS
} from "@notesnook/web-clipper/dist/common/bridge";
} from "@notesnook/web-clipper/common/bridge.js";
import { Extension } from "../components/icons";
export class WebExtensionRelay {

View File

@@ -23,7 +23,7 @@ import {
NotebookReference,
Server,
Clip
} from "@notesnook/web-clipper/dist/common/bridge";
} from "@notesnook/web-clipper/common/bridge.js";
import { store as appstore } from "../stores/app-store";
import { h } from "./html";
import { sanitizeFilename } from "@notesnook/common";

View File

@@ -119,7 +119,7 @@ function Checkout() {
useEffect(() => {
if (currentStep === 2) {
var event = EV.subscribe(EVENTS.userSubscriptionUpdated, () => {
const event = EV.subscribe(EVENTS.userSubscriptionUpdated, () => {
hardNavigate("/notes#/welcome");
});
return () => {

View File

@@ -2,7 +2,31 @@
"name": "@notesnook/web-clipper",
"version": "0.3.4",
"private": true,
"main": "./dist/index.js",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/types/index.d.ts",
"exports": {
".": {
"require": {
"types": "./dist/types/index.d.ts",
"default": "./dist/cjs/index.js"
},
"import": {
"types": "./dist/types/index.d.ts",
"default": "./dist/esm/index.js"
}
},
"./*": {
"require": {
"types": "./dist/types/*",
"default": "./dist/cjs/*"
},
"import": {
"types": "./dist/types/*",
"default": "./dist/esm/*"
}
}
},
"dependencies": {
"@emotion/react": "11.11.1",
"@mdi/js": "7.4.47",
@@ -28,7 +52,7 @@
"build:chrome": "cross-env MANIFEST_VERSION=3 node build-utils/build.js",
"dev:chrome": "cross-env MANIFEST_VERSION=3 node build-utils/dev.js",
"dev:firefox": "cross-env MANIFEST_VERSION=2 node build-utils/dev.js",
"build": "tsc"
"build": "node ../../scripts/build.mjs"
},
"browserslist": {
"production": [
@@ -94,4 +118,4 @@
},
"homepage": "https://notesnook.com/",
"repository": "https://github.com/streetwriters/notesnook"
}
}

View File

@@ -0,0 +1,5 @@
- We have launches new pricing plans, Essential, Pro and Believer
- Free users can now upload attachments and create private vault
- Minor bug fixes and improvements
Thank you for using Notesnook!

View File

@@ -1,6 +0,0 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"useNx": true,
"version": "independent",
"packages": ["packages/*", "apps/*", "extensions/*", "servers/*"]
}

76
nx.json
View File

@@ -1,76 +0,0 @@
{
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"outputs": [
"{projectRoot}/build",
"{projectRoot}/dist",
"{projectRoot}/languages",
"{projectRoot}/locales",
"{projectRoot}/src/extensions/code-block/languages"
],
"cache": true
},
"release": {
"dependsOn": ["^build"],
"outputs": ["{projectRoot}/build"]
},
"build:test": {
"dependsOn": ["^build"],
"outputs": ["{projectRoot}/build"],
"cache": true
},
"build:beta": {
"dependsOn": ["^build"],
"outputs": ["{projectRoot}/build"]
},
"build:desktop": {
"dependsOn": ["^build"],
"outputs": ["{projectRoot}/build"],
"cache": true
},
"start": {
"dependsOn": ["^build"]
},
"start:desktop": {
"dependsOn": ["^build"]
},
"test:e2e": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["^build"]
},
"run-android": {
"dependsOn": ["^build"]
},
"run-ios": {
"dependsOn": ["^build"]
},
"build-ios": {
"dependsOn": ["^build"]
},
"build-android": {
"dependsOn": ["^build"]
},
"release-android": {
"dependsOn": ["^build"]
},
"release-android-bundle": {
"dependsOn": ["^build"]
},
"release-ios": {
"dependsOn": ["^build"]
},
"repack": {
"dependsOn": ["^build"]
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"sharedGlobals": [],
"production": ["default"]
},
"parallel": 4,
"nxCloudAccessToken": "ZWM3ODgxNDgtNGUzZC00MjQ0LWE3MzMtZDdhMzE3ZGY2MWFlfHJlYWQ="
}

4682
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,46 +5,49 @@
"prepare": "husky install && npm run bootstrap",
"bootstrap": "node scripts/bootstrap.mjs",
"clean": "node scripts/clean.mjs",
"build": "nx run-many --target=build --all --exclude=@notesnook/mobile,@notesnook/web",
"build:web": "nx build @notesnook/web",
"build:vericrypt": "nx build @notesnook/vericrypt",
"build:test:web": "nx build:test @notesnook/web",
"build:beta:web": "nx build:beta @notesnook/web",
"start:web": "nx start @notesnook/web",
"start:theme-builder": "nx start @notesnook/theme-builder",
"build:theme-builder": "nx build @notesnook/theme-builder",
"start:vericrypt": "nx start @notesnook/vericrypt",
"start:desktop": "nx start @notesnook/desktop",
"test:web": "nx test @notesnook/web",
"test:core": "nx test @notesnook/core",
"start:android": "nx run-android @notesnook/mobile",
"start:ios": "nx run-ios @notesnook/mobile",
"prepare:ios": "nx install-pods @notesnook/mobile",
"build:ios": "nx build-ios @notesnook/mobile",
"build:android": "nx build-android @notesnook/mobile",
"build:android:debug": "nx build-android-debug @notesnook/mobile",
"release:android": "nx release-android @notesnook/mobile",
"release:android:bundle": "nx release-android-bundle @notesnook/mobile",
"test:ios": "nx e2e-ios @notesnook/mobile",
"test:android": "nx e2e-android @notesnook/mobile",
"test:android:debug": "nx e2e-android-debug @notesnook/mobile",
"start:metro": "nx start @notesnook/mobile",
"start:repack": "nx repack @notesnook/mobile",
"start:editor-mobile": "nx start @notesnook/editor-mobile",
"start:editor": "nx start @notesnook/editor",
"start:server:themes": "nx start @notesnook/themes-server",
"build:intl": "nx build @notesnook/intl",
"start:intl": "nx start @notesnook/intl",
"build": "npm run tx -- build --all --exclude=mobile,web,monograph,theme-builder,vericrypt",
"build:web": "npm run tx web:build",
"build:vericrypt": "npm run tx vericrypt:build",
"build:test:web": "npm run tx web:build:test",
"build:beta:web": "npm run tx web:build:beta",
"start:web": "npm run tx web:start",
"start:theme-builder": "npm run tx theme-builder:start",
"build:theme-builder": "npm run tx theme-builder:build",
"start:vericrypt": "npm run tx vericrypt:start",
"start:desktop": "npm run tx desktop:start",
"test:web": "npm run tx web:test",
"test:core": "npm run tx core:test",
"start:android": "npm run tx mobile:run-android",
"start:ios": "npm run tx mobile:run-ios",
"prepare:ios": "npm run tx mobile:install-pods",
"build:ios": "npm run tx mobile:build-ios",
"build:android": "npm run tx mobile:build-android",
"build:android:debug": "npm run tx mobile:build-android-debug",
"release:android": "npm run tx mobile:release-android",
"release:android:bundle": "npm run tx mobile:release-android-bundle",
"test:ios": "npm run tx mobile:e2e-ios",
"test:android": "npm run tx mobile:e2e-android",
"test:android:debug": "npm run tx mobile:e2e-android-debug",
"start:metro": "npm run tx mobile:start",
"start:repack": "npm run tx mobile:repack",
"start:editor-mobile": "npm run tx editor-mobile:start",
"start:editor": "npm run tx editor:start",
"start:server:themes": "npm run tx themes-server:start",
"build:intl": "npm run tx intl:build",
"start:intl": "npm run tx intl:start",
"prettier": "npx prettier . --write",
"lint": "npx eslint ./apps/ ./packages/",
"linc": "npx eslint ./apps/ ./packages/ --cache"
"linc": "npx eslint ./apps/ ./packages/ --cache",
"tx": "node scripts/execute.mjs"
},
"devDependencies": {
"@commitlint/cli": "^17.1.1",
"@types/node": "^18.7.13",
"@typescript-eslint/eslint-plugin": "^5.59.9",
"@typescript-eslint/parser": "^5.59.9",
"@typescript/native-preview": "^7.0.0-dev.20251006.1",
"cross-env": "^7.0.3",
"dotenv": "^17.2.3",
"eslint": "^8.42.0",
"eslint-config-react-app": "^7.0.1",
"eslint-config-react-hooks": "^1.0.0",
@@ -58,13 +61,13 @@
"husky": "^8.0.1",
"prettier": "^2.8.8",
"serve": "^14.0.1",
"tsdown": "^0.15.6",
"tsup": "^8.1.0"
},
"dependencies": {
"eslint-plugin-header": "^3.1.1",
"fdir": "^6.0.2",
"fdir": "^6.5.0",
"listr2": "^8.2.3",
"nx": "^21.6.3",
"patch-package": "^7.0.0",
"rxjs": "^7.8.1",
"typescript": "^5.6.3",
@@ -72,26 +75,42 @@
},
"license": "GPL-3.0-or-later",
"volta": {
"node": "20.12.0"
"node": "22.20.0"
},
"taskRunner": {
"projects": [
"packages/**",
"apps/**",
"extensions/**",
"servers/**"
"packages/*",
"apps/*",
"extensions/*",
"servers/*"
],
"taskDependencies": {
"build": [
"build"
],
"release": [
"build"
],
"build:desktop": [
"build"
]
}
"tasks": [
{
"dependencies": [
"build"
],
"commands": [
"start",
"build",
"release",
"build:test",
"build:beta",
"build:desktop",
"start",
"start:desktop",
"test:e2e",
"test",
"run-android",
"run-ios",
"build-ios",
"build-android",
"release-android",
"release-android-bundle",
"release-ios",
"repack"
]
}
]
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,9 +11,8 @@
"main": "dist/index.js",
"devDependencies": {
"@playwright/test": "1.48.2",
"slugify": "1.6.6",
"webpack": "5.88.2",
"webpack-cli": "5.1.4"
"@rsbuild/core": "^1.5.13",
"slugify": "1.6.6"
},
"publishConfig": {
"access": "public"
@@ -23,7 +22,7 @@
"url": "git+https://github.com/streetwriters/notesnook.git"
},
"scripts": {
"build": "tsc && yarn webpack -c webpack.config.js",
"build": "tsc && npx rsbuild build",
"prepublishOnly": "npm run build",
"test": "playwright test",
"postinstall": "patch-package",
@@ -38,4 +37,4 @@
"hyperapp": "^2.0.22",
"specificity": "^0.4.1"
}
}
}

View File

@@ -17,15 +17,27 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const path = require("path");
module.exports = {
entry: ["./dist/index.global.js"],
import { defineConfig } from "@rsbuild/core";
import path from "path";
export default defineConfig({
source: {
entry: {
global: "./dist/index.global.js"
}
},
mode: "production",
output: {
filename: "clipper.bundle.js",
path: path.resolve(
__dirname,
"../../apps/mobile/native/ios/extension.bundle"
)
target: "node",
cleanDistPath: false,
filename: {
js: "clipper.bundle.js"
},
distPath: {
root: path.resolve(
__dirname,
"../../apps/mobile/native/ios/extension.bundle"
)
}
}
};
});

View File

@@ -222,7 +222,7 @@ export const tiptapKeys = {
type: "tiptap"
},
insertParagraph: {
keys: "Mod-Shift-0",
keys: "Mod-Alt-0",
description: "Insert paragraph",
category: "Editor",
type: "tiptap"

View File

@@ -68,20 +68,23 @@ test("editing the same note should create multiple history sessions", () =>
test("restoring an old session should replace note's content", () =>
noteTest({ ...TEST_NOTE, sessionId: Date.now() }).then(async ({ db, id }) => {
await delay(1000);
let editedContent = {
data: TEST_NOTE.content.data + "<p>Some new content</p>",
type: "tiptap"
};
const sessionId = `${Date.now() + 10000}`;
await db.notes.add({
id: id,
content: editedContent,
sessionId: Date.now() + 10000
sessionId
});
const [, firstVersion] = await db.noteHistory
.get(id)
.items(undefined, { sortBy: "dateModified", sortDirection: "desc" });
expect(firstVersion.id).not.toBe(`${id}_${sessionId}`);
await db.noteHistory.restore(firstVersion.id);
const contentId = (await db.notes.note(id)).contentId;
@@ -138,15 +141,17 @@ test("return empty array if no history available", () =>
test("auto clear sessions if they exceed the limit", () =>
noteTest({ ...TEST_NOTE, sessionId: Date.now() }).then(async ({ db, id }) => {
await delay(1000);
let editedContent = {
data: TEST_NOTE.content.data + "<p>Some new content</p>",
type: "tiptap"
};
const sessionId = `${Date.now() + 10000}`;
await db.notes.add({
id: id,
content: editedContent,
sessionId: `${Date.now() + 10000}`
sessionId
});
expect(await db.noteHistory.get(id).count()).toBe(2);
@@ -157,6 +162,7 @@ test("auto clear sessions if they exceed the limit", () =>
expect(sessions).toHaveLength(1);
const content = await db.noteHistory.content(sessions[0].id);
expect(sessions[0].id).toBe(`${id}_${sessionId}`);
expect(content.data).toBe(editedContent.data);
}));

View File

@@ -18,7 +18,7 @@
}
},
"scripts": {
"build": "tsup-node src/index.ts",
"build": "tsdown src/index.ts",
"prepublishOnly": "npm run build",
"watch": "npm run build -- --watch"
},

View File

@@ -66,6 +66,7 @@
"devDependencies": {
"@emotion/react": "11.11.1",
"@mdi/js": "7.4.47",
"@rslib/core": "^0.15.0",
"@theme-ui/components": "0.16.1",
"@theme-ui/core": "0.16.1",
"@types/katex": "0.16.7",
@@ -77,6 +78,7 @@
"happy-dom": "16.0.1",
"isomorphic-fetch": "^3.0.0",
"nodemon": "^3.1.7",
"prismjs": "^1.30.0",
"prosemirror-test-builder": "^1.1.1",
"react": "18.3.1",
"react-dom": "18.3.1",
@@ -260,6 +262,180 @@
"react-modal": ">=3"
}
},
"node_modules/@ast-grep/napi": {
"version": "0.37.0",
"resolved": "https://registry.npmjs.org/@ast-grep/napi/-/napi-0.37.0.tgz",
"integrity": "sha512-Hb4o6h1Pf6yRUAX07DR4JVY7dmQw+RVQMW5/m55GoiAT/VRoKCWBtIUPPOnqDVhbx1Cjfil9b6EDrgJsUAujEQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@ast-grep/napi-darwin-arm64": "0.37.0",
"@ast-grep/napi-darwin-x64": "0.37.0",
"@ast-grep/napi-linux-arm64-gnu": "0.37.0",
"@ast-grep/napi-linux-arm64-musl": "0.37.0",
"@ast-grep/napi-linux-x64-gnu": "0.37.0",
"@ast-grep/napi-linux-x64-musl": "0.37.0",
"@ast-grep/napi-win32-arm64-msvc": "0.37.0",
"@ast-grep/napi-win32-ia32-msvc": "0.37.0",
"@ast-grep/napi-win32-x64-msvc": "0.37.0"
}
},
"node_modules/@ast-grep/napi-darwin-arm64": {
"version": "0.37.0",
"resolved": "https://registry.npmjs.org/@ast-grep/napi-darwin-arm64/-/napi-darwin-arm64-0.37.0.tgz",
"integrity": "sha512-QAiIiaAbLvMEg/yBbyKn+p1gX2/FuaC0SMf7D7capm/oG4xGMzdeaQIcSosF4TCxxV+hIH4Bz9e4/u7w6Bnk3Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@ast-grep/napi-darwin-x64": {
"version": "0.37.0",
"resolved": "https://registry.npmjs.org/@ast-grep/napi-darwin-x64/-/napi-darwin-x64-0.37.0.tgz",
"integrity": "sha512-zvcvdgekd4ySV3zUbUp8HF5nk5zqwiMXTuVzTUdl/w08O7JjM6XPOIVT+d2o/MqwM9rsXdzdergY5oY2RdhSPA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@ast-grep/napi-linux-arm64-gnu": {
"version": "0.37.0",
"resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-arm64-gnu/-/napi-linux-arm64-gnu-0.37.0.tgz",
"integrity": "sha512-L7Sj0lXy8X+BqSMgr1LB8cCoWk0rericdeu+dC8/c8zpsav5Oo2IQKY1PmiZ7H8IHoFBbURLf8iklY9wsD+cyA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@ast-grep/napi-linux-arm64-musl": {
"version": "0.37.0",
"resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-arm64-musl/-/napi-linux-arm64-musl-0.37.0.tgz",
"integrity": "sha512-LF9sAvYy6es/OdyJDO3RwkX3I82Vkfsng1sqUBcoWC1jVb1wX5YVzHtpQox9JrEhGl+bNp7FYxB4Qba9OdA5GA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@ast-grep/napi-linux-x64-gnu": {
"version": "0.37.0",
"resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-x64-gnu/-/napi-linux-x64-gnu-0.37.0.tgz",
"integrity": "sha512-TViz5/klqre6aSmJzswEIjApnGjJzstG/SE8VDWsrftMBMYt2PTu3MeluZVwzSqDao8doT/P+6U11dU05UOgxw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@ast-grep/napi-linux-x64-musl": {
"version": "0.37.0",
"resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-x64-musl/-/napi-linux-x64-musl-0.37.0.tgz",
"integrity": "sha512-/BcCH33S9E3ovOAEoxYngUNXgb+JLg991sdyiNP2bSoYd30a9RHrG7CYwW6fMgua3ijQ474eV6cq9yZO1bCpXg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@ast-grep/napi-win32-arm64-msvc": {
"version": "0.37.0",
"resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-arm64-msvc/-/napi-win32-arm64-msvc-0.37.0.tgz",
"integrity": "sha512-TjQA4cFoIEW2bgjLkaL9yqT4XWuuLa5MCNd0VCDhGRDMNQ9+rhwi9eLOWRaap3xzT7g+nlbcEHL3AkVCD2+b3A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@ast-grep/napi-win32-ia32-msvc": {
"version": "0.37.0",
"resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-ia32-msvc/-/napi-win32-ia32-msvc-0.37.0.tgz",
"integrity": "sha512-uNmVka8fJCdYsyOlF9aZqQMLTatEYBynjChVTzUfFMDfmZ0bihs/YTqJVbkSm8TZM7CUX82apvn50z/dX5iWRA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@ast-grep/napi-win32-x64-msvc": {
"version": "0.37.0",
"resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-x64-msvc/-/napi-win32-x64-msvc-0.37.0.tgz",
"integrity": "sha512-vCiFOT3hSCQuHHfZ933GAwnPzmL0G04JxQEsBRfqONywyT8bSdDc/ECpAfr3S9VcS4JZ9/F6tkePKW/Om2Dq2g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@azure/msal-browser": {
"version": "3.28.1",
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.28.1.tgz",
@@ -489,6 +665,40 @@
"node": ">=6.9.0"
}
},
"node_modules/@emnapi/core": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz",
"integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.1.0",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
"integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emotion/babel-plugin": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz",
@@ -1074,6 +1284,78 @@
}
}
},
"node_modules/@module-federation/error-codes": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.18.0.tgz",
"integrity": "sha512-Woonm8ehyVIUPXChmbu80Zj6uJkC0dD9SJUZ/wOPtO8iiz/m+dkrOugAuKgoiR6qH4F+yorWila954tBz4uKsQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@module-federation/runtime": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.18.0.tgz",
"integrity": "sha512-+C4YtoSztM7nHwNyZl6dQKGUVJdsPrUdaf3HIKReg/GQbrt9uvOlUWo2NXMZ8vDAnf/QRrpSYAwXHmWDn9Obaw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@module-federation/error-codes": "0.18.0",
"@module-federation/runtime-core": "0.18.0",
"@module-federation/sdk": "0.18.0"
}
},
"node_modules/@module-federation/runtime-core": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.18.0.tgz",
"integrity": "sha512-ZyYhrDyVAhUzriOsVfgL6vwd+5ebYm595Y13KeMf6TKDRoUHBMTLGQ8WM4TDj8JNsy7LigncK8C03fn97of0QQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@module-federation/error-codes": "0.18.0",
"@module-federation/sdk": "0.18.0"
}
},
"node_modules/@module-federation/runtime-tools": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.18.0.tgz",
"integrity": "sha512-fSga9o4t1UfXNV/Kh6qFvRyZpPp3EHSPRISNeyT8ZoTpzDNiYzhtw0BPUSSD8m6C6XQh2s/11rI4g80UY+d+hA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@module-federation/runtime": "0.18.0",
"@module-federation/webpack-bundler-runtime": "0.18.0"
}
},
"node_modules/@module-federation/sdk": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.18.0.tgz",
"integrity": "sha512-Lo/Feq73tO2unjmpRfyyoUkTVoejhItXOk/h5C+4cistnHbTV8XHrW/13fD5e1Iu60heVdAhhelJd6F898Ve9A==",
"dev": true,
"license": "MIT"
},
"node_modules/@module-federation/webpack-bundler-runtime": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.18.0.tgz",
"integrity": "sha512-TEvErbF+YQ+6IFimhUYKK3a5wapD90d90sLsNpcu2kB3QGT7t4nIluE25duXuZDVUKLz86tEPrza/oaaCWTpvQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@module-federation/runtime": "0.18.0",
"@module-federation/sdk": "0.18.0"
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.6.tgz",
"integrity": "sha512-DXj75ewm11LIWUk198QSKUTxjyRjsBwk09MuMk5DGK+GDUtyPhhEHOGP/Xwwj3DjQXXkivoBirmOnKrLfc0+9g==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.5.0",
"@emnapi/runtime": "^1.5.0",
"@tybys/wasm-util": "^0.10.1"
}
},
"node_modules/@notesnook-importer/core": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/@notesnook-importer/core/-/core-2.2.2.tgz",
@@ -1445,6 +1727,247 @@
"win32"
]
},
"node_modules/@rsbuild/core": {
"version": "1.5.13",
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-1.5.13.tgz",
"integrity": "sha512-P+TCvZCVpBYZ3GDdnzR/tZKicE41khJIqIRlJYnEc9dwUfX1/eqRf8lA8yrsbB5iZbSfj1iOoH1N25cCQ3hhuA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rspack/core": "1.5.8",
"@rspack/lite-tapable": "~1.0.1",
"@swc/helpers": "^0.5.17",
"core-js": "~3.45.1",
"jiti": "^2.6.0"
},
"bin": {
"rsbuild": "bin/rsbuild.js"
},
"engines": {
"node": ">=18.12.0"
}
},
"node_modules/@rslib/core": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/@rslib/core/-/core-0.15.0.tgz",
"integrity": "sha512-ZxDwEU38qsM32VpW4Dy7oNf6MxgrRAgYxVsuv8v+aKm4pIOSs0nzDTJHsa2vhKjFdyRtgnHObZ6pjoJ0cmcU3A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rsbuild/core": "~1.5.13",
"rsbuild-plugin-dts": "0.15.0"
},
"bin": {
"rslib": "bin/rslib.js"
},
"engines": {
"node": ">=18.12.0"
},
"peerDependencies": {
"@microsoft/api-extractor": "^7",
"typescript": "^5"
},
"peerDependenciesMeta": {
"@microsoft/api-extractor": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/@rspack/binding": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.5.8.tgz",
"integrity": "sha512-/91CzhRl9r5BIQCgGsS7jA6MDbw1I2BQpbfcUUdkdKl2P79K3Zo/Mw/TvKzS86catwLaUQEgkGRmYawOfPg7ow==",
"dev": true,
"license": "MIT",
"optionalDependencies": {
"@rspack/binding-darwin-arm64": "1.5.8",
"@rspack/binding-darwin-x64": "1.5.8",
"@rspack/binding-linux-arm64-gnu": "1.5.8",
"@rspack/binding-linux-arm64-musl": "1.5.8",
"@rspack/binding-linux-x64-gnu": "1.5.8",
"@rspack/binding-linux-x64-musl": "1.5.8",
"@rspack/binding-wasm32-wasi": "1.5.8",
"@rspack/binding-win32-arm64-msvc": "1.5.8",
"@rspack/binding-win32-ia32-msvc": "1.5.8",
"@rspack/binding-win32-x64-msvc": "1.5.8"
}
},
"node_modules/@rspack/binding-darwin-arm64": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.5.8.tgz",
"integrity": "sha512-spJfpOSN3f7V90ic45/ET2NKB2ujAViCNmqb0iGurMNQtFRq+7Kd+jvVKKGXKBHBbsQrFhidSWbbqy2PBPGK8g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rspack/binding-darwin-x64": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.5.8.tgz",
"integrity": "sha512-YFOzeL1IBknBcri8vjUp43dfUBylCeQnD+9O9p0wZmLAw7DtpN5JEOe2AkGo8kdTqJjYKI+cczJPKIw6lu1LWw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rspack/binding-linux-arm64-gnu": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.5.8.tgz",
"integrity": "sha512-UAWCsOnpkvy8eAVRo0uipbHXDhnoDq5zmqWTMhpga0/a3yzCp2e+fnjZb/qnFNYb5MeL0O1mwMOYgn1M3oHILQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rspack/binding-linux-arm64-musl": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.5.8.tgz",
"integrity": "sha512-GnSvGT4GjokPSD45cTtE+g7LgghuxSP1MRmvd+Vp/I8pnxTVSTsebRod4TAqyiv+l11nuS8yqNveK9qiOkBLWw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rspack/binding-linux-x64-gnu": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.5.8.tgz",
"integrity": "sha512-XLxh5n/pzUfxsugz/8rVBv+Tx2nqEM+9rharK69kfooDsQNKyz7PANllBQ/v4svJ+W0BRHnDL4qXSGdteZeEjA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rspack/binding-linux-x64-musl": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.5.8.tgz",
"integrity": "sha512-gE0+MZmwF+01p9/svpEESkzkLpBkVUG2o03YMpwXYC/maeRRhWvF8BJ7R3i/Ls/jFGSE87dKX5NbRLVzqksq/w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rspack/binding-wasm32-wasi": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.5.8.tgz",
"integrity": "sha512-cfg3niNHeJuxuml1Vy9VvaJrI/5TakzoaZvKX2g5S24wfzR50Eyy4JAsZ+L2voWQQp1yMJbmPYPmnTCTxdJQBQ==",
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@napi-rs/wasm-runtime": "^1.0.5"
}
},
"node_modules/@rspack/binding-win32-arm64-msvc": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.5.8.tgz",
"integrity": "sha512-7i3ZTHFXKfU/9Jm9XhpMkrdkxO7lfeYMNVEGkuU5dyBfRMQj69dRgPL7zJwc2plXiqu9LUOl+TwDNTjap7Q36g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rspack/binding-win32-ia32-msvc": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.5.8.tgz",
"integrity": "sha512-7ZPPWO11J+soea1+mnfaPpQt7GIodBM7A86dx6PbXgVEoZmetcWPrCF2NBfXxQWOKJ9L3RYltC4z+ZyXRgMOrw==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rspack/binding-win32-x64-msvc": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.5.8.tgz",
"integrity": "sha512-N/zXQgzIxME3YUzXT8qnyzxjqcnXudWOeDh8CAG9zqTCnCiy16SFfQ/cQgEoLlD9geQntV6jx2GbDDI5kpDGMQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rspack/core": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.5.8.tgz",
"integrity": "sha512-sUd2LfiDhqYVfvknuoz0+/c+wSpn693xotnG5g1CSWKZArbtwiYzBIVnNlcHGmuoBRsnj/TkSq8dTQ7gwfBroQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@module-federation/runtime-tools": "0.18.0",
"@rspack/binding": "1.5.8",
"@rspack/lite-tapable": "1.0.1"
},
"engines": {
"node": ">=18.12.0"
},
"peerDependencies": {
"@swc/helpers": ">=0.5.1"
},
"peerDependenciesMeta": {
"@swc/helpers": {
"optional": true
}
}
},
"node_modules/@rspack/lite-tapable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.0.1.tgz",
"integrity": "sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=16.0.0"
}
},
"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",
@@ -1755,6 +2278,49 @@
"@styled-system/css": "^5.1.5"
}
},
"node_modules/@swc/helpers": {
"version": "0.5.17",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
"integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@theme-ui/color-modes": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/color-modes/-/color-modes-0.16.2.tgz",
"integrity": "sha512-jWEWx53lxNgWCT38i/kwLV2rsvJz8lVZgi5oImnVwYba9VejXD23q1ckbNFJHosQ8KKXY87ht0KPC6BQFIiHtQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/core": "^0.16.2",
"@theme-ui/css": "^0.16.2",
"deepmerge": "^4.2.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@theme-ui/color-modes/node_modules/@theme-ui/core": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/core/-/core-0.16.2.tgz",
"integrity": "sha512-bBd/ltbwO9vIUjF1jtlOX6XN0IIOdf1vzBp2JCKsSOqdfn84m+XL8OogIe/zOhQ+aM94Nrq4+32tFJc8sFav4Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/css": "^0.16.2",
"deepmerge": "^4.2.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@theme-ui/components": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@theme-ui/components/-/components-0.16.1.tgz",
@@ -1800,6 +2366,39 @@
"@emotion/react": "^11.11.1"
}
},
"node_modules/@theme-ui/theme-provider": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/theme-provider/-/theme-provider-0.16.2.tgz",
"integrity": "sha512-LRnVevODcGqO0JyLJ3wht+PV3ZoZcJ7XXLJAJWDoGeII4vZcPQKwVy4Lpz/juHsZppQxKcB3U+sQDGBnP25irQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/color-modes": "^0.16.2",
"@theme-ui/core": "^0.16.2",
"@theme-ui/css": "^0.16.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@theme-ui/theme-provider/node_modules/@theme-ui/core": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/core/-/core-0.16.2.tgz",
"integrity": "sha512-bBd/ltbwO9vIUjF1jtlOX6XN0IIOdf1vzBp2JCKsSOqdfn84m+XL8OogIe/zOhQ+aM94Nrq4+32tFJc8sFav4Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/css": "^0.16.2",
"deepmerge": "^4.2.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@tiptap/core": {
"version": "2.6.6",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.6.6.tgz",
@@ -2291,6 +2890,17 @@
"@tiptap/core": "^2.6.6"
}
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
@@ -2922,6 +3532,18 @@
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
"dev": true
},
"node_modules/core-js": {
"version": "3.45.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz",
"integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -3154,6 +3776,17 @@
"safe-buffer": "^5.0.1"
}
},
"node_modules/encoding": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"iconv-lite": "^0.6.2"
}
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@@ -3638,6 +4271,20 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -3865,11 +4512,20 @@
"whatwg-fetch": "^3.4.1"
}
},
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "3.14.1",
@@ -4085,7 +4741,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
@@ -5437,6 +6092,16 @@
"resolved": "https://registry.npmjs.org/prism-themes/-/prism-themes-1.9.0.tgz",
"integrity": "sha512-tX2AYsehKDw1EORwBps+WhBFKc2kxfoFpQAjxBndbZKr4fRmMkv47XN0BghC/K1qwodB1otbe4oF23vUTFDokw=="
},
"node_modules/prismjs": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -5708,7 +6373,6 @@
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -5729,7 +6393,6 @@
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -6042,6 +6705,36 @@
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ=="
},
"node_modules/rsbuild-plugin-dts": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/rsbuild-plugin-dts/-/rsbuild-plugin-dts-0.15.0.tgz",
"integrity": "sha512-qKGCtv4fxKxWZb3DxHpgmP6uY+JAckovBE/92APerORvrtEtqwY6sAEY2UOaRo2ErENOiWhf5yFLE5EhdUsj9A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ast-grep/napi": "0.37.0"
},
"engines": {
"node": ">=18.12.0"
},
"peerDependencies": {
"@microsoft/api-extractor": "^7",
"@rsbuild/core": "1.x",
"@typescript/native-preview": "7.x",
"typescript": "^5"
},
"peerDependenciesMeta": {
"@microsoft/api-extractor": {
"optional": true
},
"@typescript/native-preview": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/sade": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
@@ -6083,7 +6776,6 @@
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0"
}
@@ -6480,9 +7172,10 @@
}
},
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/tunnel-agent": {
"version": "0.6.0",

View File

@@ -97,6 +97,7 @@
"happy-dom": "16.0.1",
"isomorphic-fetch": "^3.0.0",
"nodemon": "^3.1.7",
"prismjs": "^1.30.0",
"prosemirror-test-builder": "^1.1.1",
"react": "18.3.1",
"react-dom": "18.3.1",

View File

@@ -23,15 +23,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import fs from "fs";
import { readFile } from "fs/promises";
import path from "path";
export async function langen(rootDirectory) {
const response = await fetch(
"https://github.com/PrismJS/prism/raw/master/components.json"
const json = JSON.parse(
await readFile(
path.join(rootDirectory, "node_modules", "prismjs", "components.json"),
"utf-8"
)
);
if (!response.ok) return {};
const json = await response.json();
let languages = [];
for (const key in json.languages) {
if (key === "meta") continue;

View File

@@ -17,8 +17,14 @@ 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 { keybindings } from "@notesnook/common";
import { KeyboardShortcutCommand, mergeAttributes, Node } from "@tiptap/core";
import {
findParentNodeClosestToPos,
KeyboardShortcutCommand,
mergeAttributes,
Node
} from "@tiptap/core";
import { Node as ProseMirrorNode } from "@tiptap/pm/model";
import { CheckList } from "../check-list/check-list";
export interface CheckListItemOptions {
onReadOnlyChecked?: (node: ProseMirrorNode, checked: boolean) => boolean;
@@ -97,94 +103,76 @@ export const CheckListItem = Node.create<CheckListItemOptions>({
addNodeView() {
return ({ node, getPos, editor }) => {
const listItem = document.createElement("li");
const checkboxWrapper = document.createElement("label");
const checkboxStyler = document.createElement("span");
const checkbox = document.createElement("input");
const content = document.createElement("div");
const li = document.createElement("li");
if (node.attrs.checked) li.classList.add("checked");
else li.classList.remove("checked");
checkboxWrapper.contentEditable = "false";
checkbox.type = "checkbox";
function onClick(e: MouseEvent | TouchEvent) {
if (e instanceof MouseEvent && e.button !== 0) return;
if (!(e.target instanceof HTMLElement)) return;
checkbox.addEventListener("mousedown", (event) => {
if (globalThis.keyboardShown) {
event.preventDefault();
}
});
const pos = typeof getPos === "function" ? getPos() : 0;
if (typeof pos !== "number") return;
const resolvedPos = editor.state.doc.resolve(pos);
checkbox.addEventListener("change", (event) => {
event.preventDefault();
// if the editor isnt editable and we don't have a handler for
// readonly checks we have to undo the latest change
if (!editor.isEditable && !this.options.onReadOnlyChecked) {
checkbox.checked = !checkbox.checked;
const { x, y, right } = li.getBoundingClientRect();
return;
const clientX =
e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
const clientY =
e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
const hitArea = { width: 40, height: 40 };
const isRtl =
e.target.dir === "rtl" ||
findParentNodeClosestToPos(
resolvedPos,
(node) => !!node.attrs.textDirection
)?.node.attrs.textDirection === "rtl";
let xStart = clientX >= x - hitArea.width;
let xEnd = clientX <= x;
const yStart = clientY >= y;
const yEnd = clientY <= y + hitArea.height;
if (isRtl) {
xEnd = clientX <= right + hitArea.width;
xStart = clientX >= right;
}
const { checked } = event.target as any;
if (editor.isEditable && typeof getPos === "function") {
editor
.chain()
.command(({ tr }) => {
const position = getPos();
const currentNode = tr.doc.nodeAt(position);
tr.setNodeMarkup(position, undefined, {
...currentNode?.attrs,
checked
});
return true;
})
.run();
if (xStart && xEnd && yStart && yEnd) {
e.preventDefault();
editor.commands.command(({ tr }) => {
tr.setNodeAttribute(
pos,
"checked",
!li.classList.contains("checked")
);
return true;
});
}
if (!editor.isEditable && this.options.onReadOnlyChecked) {
// Reset state if onReadOnlyChecked returns false
if (!this.options.onReadOnlyChecked(node, checked)) {
checkbox.checked = !checkbox.checked;
}
}
});
if (node.attrs.checked) {
checkbox.setAttribute("checked", "checked");
}
checkboxWrapper.append(checkbox, checkboxStyler);
listItem.append(checkboxWrapper, content);
li.onmousedown = onClick;
li.ontouchstart = onClick;
return {
dom: listItem,
contentDOM: content,
dom: li,
contentDOM: li,
update: (updatedNode) => {
if (updatedNode.type !== this.type) {
return false;
}
const isNested = updatedNode.lastChild?.type.name === CheckList.name;
listItem.dataset.checked = updatedNode.attrs.checked;
if (updatedNode.attrs.checked) {
checkbox.setAttribute("checked", "checked");
} else {
checkbox.removeAttribute("checked");
}
if (updatedNode.attrs.checked) li.classList.add("checked");
else li.classList.remove("checked");
return true;
}
};
};
}
// addInputRules() {
// return [
// wrappingInputRule({
// find: inputRegex,
// type: this.type,
// getAttributes: (match) => ({
// checked: match[match.length - 1] === "x"
// })
// })
// ];
// }
});

View File

@@ -42,7 +42,7 @@ export class ClipboardDOMParser extends ProsemirrorDOMParser {
convertGoogleDocsChecklist(dom);
formatCodeblocks(dom);
convertBrToSingleSpacedParagraphs(dom);
removeImages(dom);
removePremiumFeatures(dom);
removeBlockId(dom);
}
return super.parseSlice(dom, options);
@@ -53,7 +53,7 @@ export class ClipboardDOMParser extends ProsemirrorDOMParser {
convertGoogleDocsChecklist(dom);
formatCodeblocks(dom);
convertBrToSingleSpacedParagraphs(dom);
removeImages(dom);
removePremiumFeatures(dom);
removeBlockId(dom);
}
return super.parse(dom, options);
@@ -154,11 +154,29 @@ export function convertGoogleDocsChecklist(dom: HTMLElement | Document) {
}
}
export function removeImages(dom: HTMLElement | Document) {
let canInsertImages: boolean | null = null;
for (const img of dom.querySelectorAll(`img`)) {
canInsertImages = canInsertImages ?? hasPermission("insertImage");
if (!canInsertImages) img.remove();
const premiumFeatures = [
{
selector: "div.callout",
permission: "setCallout"
},
{
selector: 'ul[data-type="outlineList"]',
permission: "toggleOutlineList"
},
{
selector: "ul.checklist",
permission: "toggleTaskList"
}
] as const;
function removePremiumFeatures(dom: HTMLElement | Document) {
for (const feature of premiumFeatures) {
const elements = dom.querySelectorAll(feature.selector);
if (elements.length === 0) continue;
if (!hasPermission(feature.permission)) {
elements.forEach((element) => element.remove());
}
}
}

View File

@@ -0,0 +1,171 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`migration > inline image in outline list 1`] = `
{
"content": [
{
"content": [
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "item 1",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "hello",
"type": "text",
},
],
"type": "paragraph",
},
{
"attrs": {
"align": undefined,
"aspectRatio": 1,
"filename": undefined,
"hash": undefined,
"height": null,
"mime": undefined,
"progress": 0,
"size": undefined,
"src": "image.png",
"type": "image",
"width": null,
},
"type": "image",
},
{
"content": [
{
"text": "world",
"type": "text",
},
],
"type": "paragraph",
},
{
"content": [
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "sub item 2",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "sub item 3",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
],
"type": "outlineList",
},
],
"type": "outlineListItem",
},
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "item 4",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
],
"type": "outlineList",
},
],
"type": "doc",
}
`;
exports[`migration > inline image in paragraph 1`] = `
{
"content": [
{
"content": [
{
"text": "hello",
"type": "text",
},
],
"type": "paragraph",
},
{
"attrs": {
"align": undefined,
"aspectRatio": 1,
"filename": undefined,
"hash": undefined,
"height": null,
"mime": undefined,
"progress": 0,
"size": undefined,
"src": "image.png",
"type": "image",
"width": null,
},
"type": "image",
},
{
"content": [
{
"text": "world",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "doc",
}
`;

View File

@@ -0,0 +1,75 @@
/*
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 {
createEditor,
h,
p,
outlineList,
outlineListItem
} from "../../../../test-utils/index.js";
import { test, expect, describe } from "vitest";
import { ImageNode } from "../index.js";
import { OutlineList } from "../../outline-list/outline-list.js";
import { OutlineListItem } from "../../outline-list-item/outline-list-item.js";
describe("migration", () => {
test(`inline image in paragraph`, async () => {
const el = p(["hello", h("img", [], { src: "image.png" }), "world"]);
const {
builder: { image },
editor
} = createEditor({
initialContent: el.outerHTML,
extensions: {
image: ImageNode.configure({})
}
});
expect(editor.getJSON()).toMatchSnapshot();
});
test(`inline image in outline list`, async () => {
const el = outlineList(
outlineListItem(["item 1"]),
outlineListItem(
["hello", h("img", [], { src: "image.png" }), "world"],
outlineList(
outlineListItem(["sub item 2"]),
outlineListItem(["sub item 3"])
)
),
outlineListItem(["item 4"])
);
const {
builder: { image },
editor
} = createEditor({
initialContent: el.outerHTML,
extensions: {
outlineList: OutlineList,
outlineListItem: OutlineListItem,
image: ImageNode
}
});
expect(editor.getJSON()).toMatchSnapshot();
});
});

View File

@@ -131,39 +131,6 @@ export const ImageNode = Node.create<ImageOptions>({
getAttrs(node) {
if (node.querySelectorAll("img").length <= 0) return false;
return null;
},
getContent: (dom, schema) => {
const wrapper = document.createElement("div");
let buffer = "";
const flushBuffer = () => {
if (buffer.trim().length > 0) {
const pEl = document.createElement("p");
pEl.innerHTML = buffer;
wrapper.appendChild(pEl);
buffer = "";
}
};
for (const child of dom.childNodes) {
if (
child.nodeType === globalThis.Node.ELEMENT_NODE &&
(child as HTMLElement).tagName === "IMG"
) {
flushBuffer();
wrapper.appendChild(child);
} else {
if (child.nodeType === globalThis.Node.ELEMENT_NODE) {
buffer += (child as HTMLElement).outerHTML;
} else if (child.nodeType === globalThis.Node.TEXT_NODE) {
buffer += child.textContent;
}
}
}
flushBuffer();
const parser = DOMParser.fromSchema(schema);
return parser.parse(wrapper).content;
}
},
{

View File

@@ -0,0 +1,130 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`outline list item > code block in outline list item 1`] = `
{
"content": [
{
"content": [
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "item 1",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "hello",
"type": "text",
},
],
"type": "paragraph",
},
{
"attrs": {
"caretPosition": undefined,
"id": "codeblock-test-id-123456",
"indentLength": 2,
"indentType": "space",
"language": null,
"lines": [],
},
"content": [
{
"text": "const x = 1;",
"type": "text",
},
],
"type": "codeblock",
},
{
"content": [
{
"text": "world",
"type": "text",
},
],
"type": "paragraph",
},
{
"content": [
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "sub item 2",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "sub item 3",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
],
"type": "outlineList",
},
],
"type": "outlineListItem",
},
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "item 4",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
],
"type": "outlineList",
},
],
"type": "doc",
}
`;

View File

@@ -0,0 +1,73 @@
/*
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 {
createEditor,
h,
li,
outlineList,
outlineListItem
} from "../../../../test-utils/index.js";
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";
describe("outline list item", () => {
beforeAll(() => {
vi.mock("nanoid", () => ({
nanoid: () => "test-id-123456"
}));
});
test(`code block in outline list item`, async () => {
const subList = outlineList(
outlineListItem(["sub item 2"]),
outlineListItem(["sub item 3"])
);
const listItemWithCodeBlock = li(
[
h("p", ["hello"]),
h("pre", [h("code", ["const x = 1;"])]),
h("p", ["world"]),
subList
],
{ "data-type": "outlineListItem" }
);
const el = outlineList(
outlineListItem(["item 1"]),
listItemWithCodeBlock,
outlineListItem(["item 4"])
);
const {
builder: { codeBlock },
editor
} = createEditor({
initialContent: el.outerHTML,
extensions: {
outlineList: OutlineList,
outlineListItem: OutlineListItem,
codeBlock: CodeBlock
}
});
expect(editor.getJSON()).toMatchSnapshot();
});
});

View File

@@ -25,6 +25,7 @@ import {
import { findParentNodeOfTypeClosestToPos } from "../../utils/prosemirror.js";
import { OutlineList } from "../outline-list/outline-list.js";
import { keybindings, tiptapKeys } from "@notesnook/common";
import { Paragraph } from "../paragraph/paragraph.js";
export interface ListItemOptions {
HTMLAttributes: Record<string, unknown>;
@@ -52,13 +53,14 @@ export const OutlineListItem = Node.create<ListItemOptions>({
};
},
content: "paragraph+ list?",
content: "block+",
defining: true,
parseHTML() {
return [
{
priority: 100,
tag: `li[data-type="${this.name}"]`
}
];
@@ -92,21 +94,37 @@ export const OutlineListItem = Node.create<ListItemOptions>({
return true;
});
},
Enter: () => {
// const subList = findSublist(editor, this.type);
// if (!subList) return this.editor.commands.splitListItem(this.name);
// const { isCollapsed, subListPos } = subList;
// if (isCollapsed) {
// return this.editor.commands.toggleOutlineCollapse(subListPos, false);
// }
Enter: ({ editor }) => {
const { $anchor } = editor.state.selection;
if (
$anchor.parent.type.name !== Paragraph.name ||
($anchor.parent.type.name === Paragraph.name &&
$anchor.node($anchor.depth - 1)?.type.name !== this.type.name)
)
return false;
return this.editor.commands.splitListItem(this.name);
},
Tab: () => this.editor.commands.sinkListItem(this.name),
[keybindings.liftListItem.keys]: () =>
this.editor.commands.liftListItem(this.name)
Tab: ({ editor }) => {
const { $anchor } = editor.state.selection;
if (
$anchor.parent.type.name !== Paragraph.name ||
($anchor.parent.type.name === Paragraph.name &&
$anchor.node($anchor.depth - 1)?.type.name !== this.type.name)
)
return false;
return this.editor.commands.sinkListItem(this.name);
},
[keybindings.liftListItem.keys]: ({ editor }) => {
const { $anchor } = editor.state.selection;
if (
$anchor.parent.type.name !== Paragraph.name ||
($anchor.parent.type.name === Paragraph.name &&
$anchor.node($anchor.depth - 1)?.type.name !== this.type.name)
)
return false;
return this.editor.commands.liftListItem(this.name);
}
};
},
@@ -124,7 +142,7 @@ export const OutlineListItem = Node.create<ListItemOptions>({
function onClick(e: MouseEvent | TouchEvent) {
if (e instanceof MouseEvent && e.button !== 0) return;
if (!(e.target instanceof HTMLParagraphElement)) return;
if (!(e.target instanceof HTMLElement)) return;
if (!li.classList.contains("nested")) return;
const pos = typeof getPos === "function" ? getPos() : 0;

View File

@@ -69,10 +69,7 @@ export function TableComponent(props: ReactNodeViewProps) {
/>
</DesktopOnly>
<SimpleBar autoHide>
<Box
dir={textDirection}
sx={{ "& table": { width: "100% !important" } }}
>
<Box dir={textDirection}>
<table
ref={(ref) => {
forwardRef?.(ref);

View File

@@ -30,6 +30,7 @@ const TEXT_DIRECTION_TYPES = [
"orderedList",
"bulletList",
"outlineList",
"checkList",
"taskList",
"table",
"blockquote",

View File

@@ -28,6 +28,7 @@ import {
import { useMemo } from "react";
import { CodeBlock } from "../../extensions/code-block/index.js";
import { strings } from "@notesnook/intl";
import { keybindings } from "@notesnook/common";
const defaultLevels = [1, 2, 3, 4, 5, 6] as const;
@@ -70,7 +71,7 @@ function toMenuItems(
key: `heading-${level}`,
title: toolbarLocation === "bottom" ? `H${level}` : strings.heading(level),
isChecked: level === currentHeadingLevel,
modifier: `Mod-Alt-${level}`,
modifier: keybindings[`insertHeading${level}`].keys,
onClick: () =>
editor
?.chain()
@@ -84,7 +85,7 @@ function toMenuItems(
type: "button",
title: strings.paragraph(),
isChecked: !currentHeadingLevel,
modifier: `Mod-Alt-0`,
modifier: keybindings.insertParagraph.keys,
onClick: () => editor.chain().focus().setParagraph().run()
};
return [paragraph, ...menuItems];

View File

@@ -76,6 +76,14 @@
margin-bottom: 5px;
}
.ProseMirror li:last-of-type {
margin-bottom: 0px;
}
.ProseMirror li:first-of-type {
margin-top: 5px;
}
.ProseMirror ul.tasklist-content-wrapper {
padding-left: 0px;
}
@@ -536,11 +544,11 @@ p > *::selection {
.outline-list li.collapsed .outline-list {
display: none;
}
.outline-list li > :first-child {
.outline-list li {
position: relative;
}
.outline-list > li > :first-child::before {
.outline-list > li::before {
position: absolute;
top: 0px;
cursor: pointer;
@@ -560,13 +568,13 @@ p > *::selection {
left: -22px;
}
.outline-list li:not(.nested) > :first-child::before {
.outline-list li:not(.nested)::before {
mask: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9IiM4ODg4ODgiIGQ9Ik0xMiAyQTEwIDEwIDAgMCAwIDIgMTJhMTAgMTAgMCAwIDAgMTAgMTBhMTAgMTAgMCAwIDAgMTAtMTBBMTAgMTAgMCAwIDAgMTIgMloiLz48L3N2Zz4=)
no-repeat 50% 50%;
scale: 0.4;
}
.outline-list li.collapsed > :first-child::before {
.outline-list li.collapsed::before {
transform: rotate(-90deg);
}
@@ -638,40 +646,71 @@ p > *::selection {
transform: rotate(90deg);
}
.simple-checklist[dir="rtl"] li::after {
left: unset;
right: -24px;
}
.simple-checklist[dir="rtl"] li.checked::before {
left: unset;
right: -22px;
}
[dir="rtl"] .taskItemTools { right: unset; left: 0 }
/* Check list */
.ProseMirror ul.simple-checklist {
list-style: none;
padding: 0;
margin-block: 0px !important;
padding-inline: 0px !important;
padding-inline-start: 24px !important;
}
.ProseMirror ul.simple-checklist > li {
display: flex;
.ProseMirror li.nested > ul.simple-checklist {
padding-inline-start: 15px !important;
}
.ProseMirror ul.simple-checklist > li input {
flex: 0 0 auto;
margin-right: 0.5rem;
user-select: none;
height: 1rem;
width: 1rem;
accent-color: var(--accent);
.simple-checklist li {
position: relative;
}
.ProseMirror ul.simple-checklist > li > div {
flex: 1 1 auto;
.simple-checklist > li::after {
position: absolute;
top: 0px;
cursor: pointer;
content: "";
background-size: 18px;
width: 14px;
height: 14px;
border: 2px solid var(--icon);
border-radius: 5px;
left: -24px;
}
@media screen and (max-width: 480px) {
.ProseMirror ul.simple-checklist > li > input {
height: 21px;
width: 21px;
}
.simple-checklist > li.checked::after {
border: 2px solid var(--accent);
}
.ProseMirror ul.simple-checklist > li > div {
margin-top: 2px;
}
.simple-checklist > li.checked::before {
position: absolute;
top: 2px;
cursor: pointer;
content: "";
background-size: 18px;
width: 14px;
height: 14px;
left: -22px;
background-color: var(--accent);
mask: url(data:image/svg+xml;base64,ICA8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICAgIDxwYXRoIGQ9Ik0yMSw3TDksMTlMMy41LDEzLjVMNC45MSwxMi4wOUw5LDE2LjE3TDE5LjU5LDUuNTlMMjEsN1oiLz4KICA8L3N2Zz4K)
no-repeat 50% 50%;
mask-size: cover;
}
.simple-checklist > li.checked > p {
opacity: 0.8;
text-decoration-line: line-through;
}
/* Callout */

View File

@@ -92,3 +92,19 @@ export const p = elem("p");
export function text(text: string) {
return document.createTextNode(text);
}
export function outlineList(...children: HTMLLIElement[]) {
return ul(children, { "data-type": "outlineList" });
}
export function outlineListItem(
paragraphChildren: (string | HTMLElement)[],
subList?: HTMLUListElement
) {
const children: HTMLElement[] = [h("p", paragraphChildren)];
if (subList) children.push(subList);
return li(children, {
"data-type": "outlineListItem"
});
}

View File

@@ -2842,6 +2842,10 @@ msgstr "Filter attachments by filename, type or hash"
msgid "Filter languages"
msgstr "Filter languages"
#: src/strings.ts:2594
msgid "Finish your purchase in the browser."
msgstr "Finish your purchase in the browser."
#: src/strings.ts:1668
msgid "Fix it"
msgstr "Fix it"
@@ -3028,6 +3032,10 @@ msgstr "Getting recovery codes"
msgid "GNU GENERAL PUBLIC LICENSE Version 3"
msgstr "GNU GENERAL PUBLIC LICENSE Version 3"
#: src/strings.ts:2595
msgid "Go back"
msgstr "Go back"
#: src/strings.ts:2445
msgid "Go back in tab"
msgstr "Go back in tab"
@@ -3795,8 +3803,8 @@ msgstr "Member since {date}"
msgid "Merge cells"
msgstr "Merge cells"
#. placeholder {0}: progress ? `${progress?.collection}` : null
#. placeholder {1}: progress ? `(${progress.current}/${progress.total}) ` : null
#. placeholder {0}: progress ? `${progress?.collection}` : ""
#. placeholder {1}: progress ? `(${progress.current}/${progress.total}) ` : ""
#: src/strings.ts:246
msgid "Migrating {0} {1}... please wait"
msgstr "Migrating {0} {1}... please wait"

View File

@@ -2831,6 +2831,10 @@ msgstr ""
msgid "Filter languages"
msgstr ""
#: src/strings.ts:2594
msgid "Finish your purchase in the browser."
msgstr ""
#: src/strings.ts:1668
msgid "Fix it"
msgstr ""
@@ -3010,6 +3014,10 @@ msgstr ""
msgid "GNU GENERAL PUBLIC LICENSE Version 3"
msgstr ""
#: src/strings.ts:2595
msgid "Go back"
msgstr ""
#: src/strings.ts:2445
msgid "Go back in tab"
msgstr ""
@@ -3775,8 +3783,8 @@ msgstr ""
msgid "Merge cells"
msgstr ""
#. placeholder {0}: progress ? `${progress?.collection}` : null
#. placeholder {1}: progress ? `(${progress.current}/${progress.total}) ` : null
#. placeholder {0}: progress ? `${progress?.collection}` : ""
#. placeholder {1}: progress ? `(${progress.current}/${progress.total}) ` : ""
#: src/strings.ts:246
msgid "Migrating {0} {1}... please wait"
msgstr ""

View File

@@ -243,8 +243,8 @@ export const strings = {
collection: string;
current: number;
}) =>
t`Migrating ${progress ? `${progress?.collection}` : null} ${
progress ? `(${progress.current}/${progress.total}) ` : null
t`Migrating ${progress ? `${progress?.collection}` : ""} ${
progress ? `(${progress.current}/${progress.total}) ` : ""
}... please wait`,
migrationError: () =>
t`An error occurred while migrating your data. You can logout of your account and try to relogin. However this is not recommended as it may result in some data loss if your data was not synced.`,
@@ -1791,7 +1791,7 @@ For example:
],
shareWithFriendsDesc: () => t`Because where's the fun in nookin' alone?`,
notebooksAllCaps: () => t`NOTEBOOKS`,
authenticatedAs: (email?: string) => t`Authenticated as ${email}`,
authenticatedAs: (email: string) => t`Authenticated as ${email}`,
rememberedYourPassword: () => t`Remembered your password?`,
chooseRecoveryMethod: () => t`Choose a recovery method`,
chooseRecoveryMethodDesc: () => t`How do you want to recover your account?`,
@@ -2590,5 +2590,7 @@ Use this if changes from other devices are not appearing on this device. This wi
t`You can change your subscription plan from the web app`,
announcement: () => t`ANNOUNCEMENT`,
cannotChangePlan: () =>
t`Your current subscription does not allow changing plans`
t`Your current subscription does not allow changing plans`,
finishPurchaseInBrowser: () => t`Finish your purchase in the browser.`,
goBack: () => t`Go back`
};

View File

@@ -0,0 +1,58 @@
// vite.config.mts
import path, { resolve } from "path";
import { defineConfig } from "file:///Users/thecodrr/Sources/Repos/notesnook/packages/intl/node_modules/vite/dist/node/index.js";
import swc from "file:///Users/thecodrr/Sources/Repos/notesnook/packages/intl/node_modules/vite-plugin-swc-transform/dist/esm/index.js";
import dts from "file:///Users/thecodrr/Sources/Repos/notesnook/packages/intl/node_modules/vite-plugin-dts/dist/index.mjs";
import { fileURLToPath } from "url";
var __vite_injected_original_import_meta_url = "file:///Users/thecodrr/Sources/Repos/notesnook/packages/intl/vite.config.mts";
var __filename = fileURLToPath(__vite_injected_original_import_meta_url);
var __dirname = path.dirname(__filename);
var vite_config_default = defineConfig({
plugins: [
swc({
swcOptions: {
sourceMaps: true,
jsc: {
parser: {
syntax: "typescript",
tsx: true
},
baseUrl: __dirname,
paths: {
"$src/*": ["src/*"]
},
experimental: {
plugins: [
[
"@lingui/swc-plugin",
{
runtimeModules: {
i18n: ["$src/setup", "i18n"]
}
}
]
]
}
}
}
}),
dts({
exclude: ["**/locales/*.json"],
rollupTypes: true
})
],
build: {
lib: {
entry: resolve(__dirname, "src/index.ts"),
formats: ["cjs", "es"],
fileName(format) {
return format === "cjs" ? "index.js" : "index.mjs";
}
},
outDir: resolve(__dirname, "dist")
}
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcubXRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiL1VzZXJzL3RoZWNvZHJyL1NvdXJjZXMvUmVwb3Mvbm90ZXNub29rL3BhY2thZ2VzL2ludGxcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9Vc2Vycy90aGVjb2Ryci9Tb3VyY2VzL1JlcG9zL25vdGVzbm9vay9wYWNrYWdlcy9pbnRsL3ZpdGUuY29uZmlnLm10c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vVXNlcnMvdGhlY29kcnIvU291cmNlcy9SZXBvcy9ub3Rlc25vb2svcGFja2FnZXMvaW50bC92aXRlLmNvbmZpZy5tdHNcIjsvKlxuVGhpcyBmaWxlIGlzIHBhcnQgb2YgdGhlIE5vdGVzbm9vayBwcm9qZWN0IChodHRwczovL25vdGVzbm9vay5jb20vKVxuXG5Db3B5cmlnaHQgKEMpIDIwMjMgU3RyZWV0d3JpdGVycyAoUHJpdmF0ZSkgTGltaXRlZFxuXG5UaGlzIHByb2dyYW0gaXMgZnJlZSBzb2Z0d2FyZTogeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeVxuaXQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhcyBwdWJsaXNoZWQgYnlcbnRoZSBGcmVlIFNvZnR3YXJlIEZvdW5kYXRpb24sIGVpdGhlciB2ZXJzaW9uIDMgb2YgdGhlIExpY2Vuc2UsIG9yXG4oYXQgeW91ciBvcHRpb24pIGFueSBsYXRlciB2ZXJzaW9uLlxuXG5UaGlzIHByb2dyYW0gaXMgZGlzdHJpYnV0ZWQgaW4gdGhlIGhvcGUgdGhhdCBpdCB3aWxsIGJlIHVzZWZ1bCxcbmJ1dCBXSVRIT1VUIEFOWSBXQVJSQU5UWTsgd2l0aG91dCBldmVuIHRoZSBpbXBsaWVkIHdhcnJhbnR5IG9mXG5NRVJDSEFOVEFCSUxJVFkgb3IgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UuICBTZWUgdGhlXG5HTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBmb3IgbW9yZSBkZXRhaWxzLlxuXG5Zb3Ugc2hvdWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZVxuYWxvbmcgd2l0aCB0aGlzIHByb2dyYW0uICBJZiBub3QsIHNlZSA8aHR0cDovL3d3dy5nbnUub3JnL2xpY2Vuc2VzLz4uXG4qL1xuXG5pbXBvcnQgcGF0aCwgeyByZXNvbHZlIH0gZnJvbSBcInBhdGhcIjtcbmltcG9ydCB7IGRlZmluZUNvbmZpZyB9IGZyb20gXCJ2aXRlXCI7XG5pbXBvcnQgc3djIGZyb20gXCJ2aXRlLXBsdWdpbi1zd2MtdHJhbnNmb3JtXCI7XG5pbXBvcnQgZHRzIGZyb20gXCJ2aXRlLXBsdWdpbi1kdHNcIjtcbmltcG9ydCB7IGZpbGVVUkxUb1BhdGggfSBmcm9tIFwidXJsXCI7XG5cbmNvbnN0IF9fZmlsZW5hbWUgPSBmaWxlVVJMVG9QYXRoKGltcG9ydC5tZXRhLnVybCk7XG5jb25zdCBfX2Rpcm5hbWUgPSBwYXRoLmRpcm5hbWUoX19maWxlbmFtZSk7XG5cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gIHBsdWdpbnM6IFtcbiAgICBzd2Moe1xuICAgICAgc3djT3B0aW9uczoge1xuICAgICAgICBzb3VyY2VNYXBzOiB0cnVlLFxuICAgICAgICBqc2M6IHtcbiAgICAgICAgICBwYXJzZXI6IHtcbiAgICAgICAgICAgIHN5bnRheDogXCJ0eXBlc2NyaXB0XCIsXG4gICAgICAgICAgICB0c3g6IHRydWVcbiAgICAgICAgICB9LFxuICAgICAgICAgIGJhc2VVcmw6IF9fZGlybmFtZSxcbiAgICAgICAgICBwYXRoczoge1xuICAgICAgICAgICAgXCIkc3JjLypcIjogW1wic3JjLypcIl1cbiAgICAgICAgICB9LFxuICAgICAgICAgIGV4cGVyaW1lbnRhbDoge1xuICAgICAgICAgICAgcGx1Z2luczogW1xuICAgICAgICAgICAgICBbXG4gICAgICAgICAgICAgICAgXCJAbGluZ3VpL3N3Yy1wbHVnaW5cIixcbiAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICBydW50aW1lTW9kdWxlczoge1xuICAgICAgICAgICAgICAgICAgICBpMThuOiBbXCIkc3JjL3NldHVwXCIsIFwiaTE4blwiXVxuICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgXVxuICAgICAgICAgICAgXVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0pLFxuICAgIGR0cyh7XG4gICAgICBleGNsdWRlOiBbXCIqKi9sb2NhbGVzLyouanNvblwiXSxcbiAgICAgIHJvbGx1cFR5cGVzOiB0cnVlXG4gICAgfSlcbiAgXSxcbiAgYnVpbGQ6IHtcbiAgICBsaWI6IHtcbiAgICAgIGVudHJ5OiByZXNvbHZlKF9fZGlybmFtZSwgXCJzcmMvaW5kZXgudHNcIiksXG4gICAgICBmb3JtYXRzOiBbXCJjanNcIiwgXCJlc1wiXSxcbiAgICAgIGZpbGVOYW1lKGZvcm1hdCkge1xuICAgICAgICByZXR1cm4gZm9ybWF0ID09PSBcImNqc1wiID8gXCJpbmRleC5qc1wiIDogXCJpbmRleC5tanNcIjtcbiAgICAgIH1cbiAgICB9LFxuICAgIG91dERpcjogcmVzb2x2ZShfX2Rpcm5hbWUsIFwiZGlzdFwiKVxuICB9XG59KTtcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFtQkEsT0FBTyxRQUFRLGVBQWU7QUFDOUIsU0FBUyxvQkFBb0I7QUFDN0IsT0FBTyxTQUFTO0FBQ2hCLE9BQU8sU0FBUztBQUNoQixTQUFTLHFCQUFxQjtBQXZCcUwsSUFBTSwyQ0FBMkM7QUF5QnBRLElBQU0sYUFBYSxjQUFjLHdDQUFlO0FBQ2hELElBQU0sWUFBWSxLQUFLLFFBQVEsVUFBVTtBQUV6QyxJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTO0FBQUEsSUFDUCxJQUFJO0FBQUEsTUFDRixZQUFZO0FBQUEsUUFDVixZQUFZO0FBQUEsUUFDWixLQUFLO0FBQUEsVUFDSCxRQUFRO0FBQUEsWUFDTixRQUFRO0FBQUEsWUFDUixLQUFLO0FBQUEsVUFDUDtBQUFBLFVBQ0EsU0FBUztBQUFBLFVBQ1QsT0FBTztBQUFBLFlBQ0wsVUFBVSxDQUFDLE9BQU87QUFBQSxVQUNwQjtBQUFBLFVBQ0EsY0FBYztBQUFBLFlBQ1osU0FBUztBQUFBLGNBQ1A7QUFBQSxnQkFDRTtBQUFBLGdCQUNBO0FBQUEsa0JBQ0UsZ0JBQWdCO0FBQUEsb0JBQ2QsTUFBTSxDQUFDLGNBQWMsTUFBTTtBQUFBLGtCQUM3QjtBQUFBLGdCQUNGO0FBQUEsY0FDRjtBQUFBLFlBQ0Y7QUFBQSxVQUNGO0FBQUEsUUFDRjtBQUFBLE1BQ0Y7QUFBQSxJQUNGLENBQUM7QUFBQSxJQUNELElBQUk7QUFBQSxNQUNGLFNBQVMsQ0FBQyxtQkFBbUI7QUFBQSxNQUM3QixhQUFhO0FBQUEsSUFDZixDQUFDO0FBQUEsRUFDSDtBQUFBLEVBQ0EsT0FBTztBQUFBLElBQ0wsS0FBSztBQUFBLE1BQ0gsT0FBTyxRQUFRLFdBQVcsY0FBYztBQUFBLE1BQ3hDLFNBQVMsQ0FBQyxPQUFPLElBQUk7QUFBQSxNQUNyQixTQUFTLFFBQVE7QUFDZixlQUFPLFdBQVcsUUFBUSxhQUFhO0FBQUEsTUFDekM7QUFBQSxJQUNGO0FBQUEsSUFDQSxRQUFRLFFBQVEsV0FBVyxNQUFNO0FBQUEsRUFDbkM7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=

View File

@@ -16,7 +16,7 @@
}
},
"scripts": {
"build": "tsup-node src/index.ts",
"build": "tsdown src/index.ts",
"prepublishOnly": "npm run build",
"watch": "npm run build -- --watch"
},

View File

@@ -36,7 +36,7 @@
"postbuild": "node scripts/postbuild.mjs",
"prepublishOnly": "npm run build && npm run test",
"postinstall": "patch-package",
"build": "tsup-node src/browser.ts src/node.ts",
"build": "tsdown src/browser.ts src/node.ts",
"watch": "npm run build -- --watch"
},
"license": "GPL-3.0-or-later",

View File

@@ -16,7 +16,7 @@
}
},
"scripts": {
"build": "tsup-node src/index.ts",
"build": "tsdown src/index.ts",
"prepublishOnly": "npm run build",
"watch": "npm run build -- --watch"
},

View File

@@ -20,6 +20,7 @@ import "isomorphic-fetch";
import { mkdir, writeFile } from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
import { existsSync } from "fs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -36,6 +37,8 @@ async function main() {
await mkdir(THEMES_DIRECTORY, { recursive: true });
for (const themeId of DEFAULT_THEMES) {
const themePath = path.join(THEMES_DIRECTORY, `${themeId}.json`);
if (existsSync(themePath)) continue;
console.log("Getting", themeId);
const BASE_URL = `https://raw.githubusercontent.com/streetwriters/notesnook-themes/main/themes/${themeId}/v${THEME_COMPATIBILITY_VERSION}`;

View File

@@ -46,8 +46,8 @@
"react-dom": ">=18"
},
"scripts": {
"build": "tsup-node src/index.ts",
"build": "tsdown src/index.ts",
"prepublishOnly": "npm run build",
"watch": "npm run build -- --watch"
}
}
}

View File

@@ -30,6 +30,11 @@ const TSC =
? path.join(__dirname, "..", "node_modules", ".bin", "tsc.cmd")
: path.join(__dirname, "..", "node_modules", ".bin", "tsc");
const TSCGO =
process.platform === "win32"
? path.join(__dirname, "..", "node_modules", ".bin", "tsgo.cmd")
: path.join(__dirname, "..", "node_modules", ".bin", "tsgo");
const esmPackageJson = {
type: "module"
};
@@ -62,7 +67,7 @@ await Promise.all([
IS_WATCH ? "--watch" : ""
),
cmd(
TSC,
TSCGO,
"--emitDeclarationOnly",
"--outDir",
"dist/types",
@@ -78,9 +83,10 @@ await Promise.all([
)
]);
function cmd(...command) {
async function cmd(...command) {
let p = spawn(command[0], command.slice(1), { shell: true });
return new Promise((resolveFunc) => {
console.time(command.join(" "));
await new Promise((resolveFunc) => {
p.stdout.on("data", (x) => {
process.stdout.write(x.toString());
});
@@ -88,8 +94,9 @@ function cmd(...command) {
process.stderr.write(x.toString());
});
p.on("exit", (code) => {
console.log(command.join(" "), "exited with code", code);
// console.log(command.join(" "), "exited with code", code);
resolveFunc(code);
});
});
console.timeEnd(command.join(" "));
}

View File

@@ -18,189 +18,352 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { spawn } from "child_process";
import { fdir } from "fdir";
import { existsSync, readFileSync, writeFileSync } from "fs";
import { readFile } from "fs/promises";
import { existsSync, readFileSync } from "fs";
import { glob, readFile, rename, stat, writeFile } from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
import parser from "yargs-parser";
import { performance } from "perf_hooks";
import { createHash } from "crypto";
const args = parser(process.argv, {
alias: { force: ["-f"], verbose: ["-v"] }
});
const isVerbose = process.env.CI || args.verbose;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const config = JSON.parse(
readFileSync(path.join(__dirname, "..", "package.json"), "utf-8")
).taskRunner;
const cache = JSON.parse(
existsSync(path.join(__dirname, "..", ".taskcache"))
? readFileSync(path.join(__dirname, "..", ".taskcache"), "utf-8")
: "{}"
);
let [project, ...taskParts] = process.argv.slice(2)[0].split(":");
const task = taskParts.join(":");
const allPackages = (
await new fdir()
.onlyDirs()
.withMaxDepth(2)
.glob(...config.projects)
.crawl(".")
.withPromise()
).slice(4);
for (const pkg of allPackages) {
if (isPackage(pkg, project)) {
project = pkg;
break;
const args = { _: [], exclude: [], force: false, verbose: false, all: false };
const shortFlags = { f: "force", v: "verbose", a: "all", e: "exclude" };
for (let i = 2; i < process.argv.length; i++) {
const arg = process.argv[i];
if (!arg.startsWith("-")) args._.push(arg);
else {
const isLong = arg.startsWith("--");
const [key, value] = isLong ? arg.split("=") : [arg[1], null];
const flag = isLong ? key.slice(2) : shortFlags[key];
if (!args[flag]) args._.push(arg);
else if (Array.isArray(args[flag]))
args[flag] = value
? value.split(",")
: process.argv[++i]?.split(",") || [];
else if (flag) args[flag] = true;
}
}
const pipeline = await buildExecutionPipeline(
config.taskDependencies[task],
project
);
console.log("Found", pipeline.length, "dependencies to run.");
for (const item of pipeline) {
const checksum = await computeDirectoryChecksum(item.dep);
if (checksum === cache[item.dep + ":" + item.cmd]) continue;
await runScript(item.cmd, item.dep);
cache[item.dep + ":" + item.cmd] = checksum;
if (!args._.length) {
console.error("Missing required argument: package:task");
process.exit(1);
}
await runScript(task, project);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.join(__dirname, "..");
const isVerbose = !!(process.env.CI || args.verbose);
const rootPkg = readJson(path.join(root, "package.json"));
const config = rootPkg.taskRunner || { projects: ["packages/*"], tasks: [] };
const cachePath = path.join(root, ".taskcache");
const [project, ...taskParts] = args.all
? [null, ...args._[0].split(":")]
: args._[0].split(":");
const cmd = taskParts.join(":");
const pkgCache = new Map();
const depMemo = new Map();
writeFileSync(
path.join(__dirname, "..", ".taskcache"),
JSON.stringify(cache, null, 2)
);
async function runScript(command, pkg) {
console.log(`Running "${command}" for ${pkg}`);
return new Promise((resolve, reject) => {
const child = spawn("npm", ["run", command], {
cwd: pkg,
stdio: isVerbose ? "inherit" : "pipe",
shell: true
});
child.on("exit", (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Command failed with exit code ${code}`));
}
});
child.on("error", (err) => {
reject(err);
});
});
const allPkgs = await findPkgs();
const task = config.tasks?.find((t) => t.commands?.includes(cmd));
if (!task) {
console.error(`Task "${cmd}" not found in taskRunner config.`);
process.exit(1);
}
async function buildExecutionPipeline(commands, pkg) {
if (commands.length === 0) return [];
const resolvedPkgs = (
await Promise.all(
task.dependencies.flatMap((dep) =>
allPkgs.map((pkg) => resolvePkg(dep, pkg))
)
)
).filter(Boolean);
const executionPipeline = [];
const deps = await findDependencies(pkg);
for (const dep of deps) {
if (executionPipeline.some((item) => item.dep === dep)) continue;
const pipeline = await buildExecutionPipeline(commands, dep);
for (const item of pipeline) {
if (executionPipeline.some((i) => i.dep === item.dep)) continue;
executionPipeline.push(item);
}
const json = JSON.parse(
readFileSync(path.join(dep, "package.json"), "utf-8")
);
for (const cmd of commands) {
if (json.scripts && json.scripts[cmd]) {
executionPipeline.push({
cmd,
dep
});
break;
}
}
}
return executionPipeline;
let cache = {};
try {
cache = existsSync(cachePath) ? readJson(cachePath) : {};
} catch {
if (isVerbose) console.warn("Failed to parse .taskcache");
}
function isPackage(pkg, name) {
const json = JSON.parse(
readFileSync(path.join(pkg, "package.json"), "utf-8")
);
return json.name === name;
}
async function findDependencies(scope) {
function readPkg(pkgPath) {
const key = path.resolve(pkgPath);
if (pkgCache.has(key)) return pkgCache.get(key);
try {
const packageJsonPath = path.join(scope, "package.json");
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
const json = readJson(path.join(pkgPath, "package.json"));
pkgCache.set(key, json);
return json;
} catch {
return null;
}
}
const dependencies = new Set([
...filterDependencies(scope, packageJson.dependencies),
...filterDependencies(scope, packageJson.devDependencies),
...filterDependencies(scope, packageJson.optionalDependencies),
...filterDependencies(scope, packageJson.peerDependencies)
]);
function readJson(jsonPath) {
return JSON.parse(readFileSync(jsonPath, "utf-8"));
}
for (const dependency of dependencies) {
(await findDependencies(dependency)).forEach((v) => dependencies.add(v));
}
function getPkgName(pkgPath) {
return readPkg(pkgPath)?.name || path.basename(pkgPath);
}
return Array.from(dependencies.values());
} catch (e) {
console.error("Failed to find dependencies for", scope, "Error:", e);
function isPkg(path, name) {
const pkgName = getPkgName(path);
return pkgName === name || pkgName.endsWith(`/${name}`);
}
async function findPkgs() {
const pkgs = [];
for await (const entry of glob(config.projects)) {
const pkgPath = path.join(root, entry);
if (existsSync(path.join(pkgPath, "package.json")))
pkgs.push(path.resolve(pkgPath));
}
return pkgs;
}
function filterDeps(base, deps) {
if (!deps) return [];
let filteredDeps = [];
for (const [key, value] of Object.entries(deps))
if (key.startsWith("@notesnook/") && value?.startsWith("file:"))
filteredDeps.push(path.resolve(base, value.slice(5)));
return filteredDeps;
}
async function findDeps(scope) {
const key = path.resolve(scope);
if (depMemo.has(key)) return depMemo.get(key);
const pkg = readPkg(scope);
if (!pkg) {
depMemo.set(key, []);
return [];
}
const deps = new Set(
filterDeps(scope, { ...pkg.dependencies, ...pkg.devDependencies })
);
for (const d of deps) for (const c of await findDeps(d)) deps.add(c);
const result = Array.from(deps);
depMemo.set(key, result);
return result;
}
function filterDependencies(basePath, dependencies) {
if (!dependencies) return [];
return Object.entries(dependencies)
.filter(
([key, value]) =>
key.startsWith("@notesnook/") || value.startsWith("file:")
)
.map(([_, value]) =>
path.resolve(path.join(basePath, value.replace("file:", "")))
);
}
async function computeDirectoryChecksum(dir) {
const exclusions = [
"node_modules",
".git",
"dist",
"build",
"out",
"locales",
"coverage"
];
const crypto = await import("crypto");
const hash = crypto.createHash("sha256");
const files = (
await new fdir()
.withFullPaths()
.exclude((dir) => exclusions.some((ex) => dir.includes(ex)))
.crawl(path.join(dir))
.withPromise()
).sort();
for (const filePath of files) {
const fileBuffer = await readFile(filePath);
hash.update(fileBuffer);
async function resolvePkg(command, pkgPath) {
const json = readPkg(pkgPath);
if (!json?.scripts?.[command]) return null;
const deps = await findDeps(pkgPath);
const taskDeps = {};
for (const dep of deps) {
if (readPkg(dep)?.scripts?.[command]) {
taskDeps[command] = taskDeps[command] || [];
taskDeps[command].push(dep);
}
}
return { path: pkgPath, cmd: command, taskDependencies: taskDeps };
}
async function runScript(command, pkg, opts) {
opts = opts || {};
const start = performance.now();
const name = getPkgName(pkg);
try {
await new Promise((resolve, reject) => {
const verbose = opts.verbose || isVerbose;
const child = spawn("npm", ["run", command, ...(opts.args || [])], {
cwd: pkg,
stdio: verbose ? "inherit" : "pipe",
shell: true
});
let output = "";
if (!verbose && child.stdout) {
child.stdout.on("data", (data) => (output += data));
child.stderr.on("data", (data) => (output += data));
}
child.once("exit", (code) =>
code === 0
? resolve()
: reject(new Error(`Exit code ${code}${output ? "\n" + output : ""}`))
);
child.once("error", reject);
});
console.log(
`${name}:${command} took ${(performance.now() - start).toFixed(0)}ms`
);
} catch (err) {
console.error(`${name}:${command} failed:`, err.message);
throw err;
}
}
function normalizePath(pathStr) {
return path.resolve(pathStr).replace(/\/$/, "");
}
async function toBatches(pkgs, cmd) {
const batches = [pkgs.filter((pkg) => !pkg.taskDependencies[cmd]?.length)];
const remaining = pkgs
.filter((pkg) => !!pkg.taskDependencies[cmd]?.length)
.slice();
while (remaining.length > 0) {
const batch = [];
for (const pkg of remaining.slice()) {
if (
pkg.taskDependencies[cmd].every((dep) =>
batches.some((batch) =>
batch.some(
(batchPkg) => normalizePath(dep) === normalizePath(batchPkg.path)
)
)
)
) {
batch.push(pkg);
remaining.splice(remaining.indexOf(pkg), 1);
}
}
if (!batch.length) throw new Error("Cyclic dependencies detected");
batches.push(batch);
}
return batches;
}
async function resolveRecursive(cmd, pkg, visited) {
visited = visited || new Set();
const result = [];
const resolved = await resolvePkg(cmd, pkg);
if (!resolved) return [];
const rp = path.resolve(pkg);
if (visited.has(rp)) return [];
visited.add(rp);
for (const dep of resolved.taskDependencies[cmd] || []) {
for (const resolved of await resolveRecursive(cmd, dep, visited)) {
if (!result.some((pkg) => pkg.path === resolved.path))
result.push(resolved);
}
}
result.push(resolved);
return result;
}
const exclusions = new Set([
"node_modules",
".git",
"dist",
"build",
"out",
"locales",
"coverage",
"__e2e__",
"__mocks__",
"__tests__",
"__benches__",
"scripts",
"test-artifacts",
"output"
]);
async function computeChecksumFast(dir) {
const hash = createHash("sha256");
for await (const entry of glob(`${dir}/**/**`, {
withFileTypes: true,
exclude: (entry) => exclusions.has(entry.name)
})) {
if (entry.isFile()) {
const fullPath = path.join(entry.parentPath, entry.name);
const fileStat = await stat(fullPath).catch(() => null);
if (!fileStat) continue;
hash.update(fullPath);
hash.update(fileStat.mtimeMs.toString());
hash.update(fileStat.size.toString());
}
}
return hash.digest("hex");
}
async function computeChecksumSlow(dir) {
const hash = createHash("sha256");
const files = [];
for await (const entry of glob(`${dir}/**/**`, {
withFileTypes: true,
exclude: (entry) => exclusions.has(entry.name)
})) {
if (entry.isFile()) files.push(path.join(entry.parentPath, entry.name));
}
files.sort();
for (const file of files) {
hash.update(path.relative(dir, file));
hash.update(await readFile(file).catch(() => ""));
}
return hash.digest("hex");
}
function findKeyInObject(obj, key) {
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return;
if (key in obj) return obj[key];
for (const value of Object.values(obj)) {
const found = findKeyInObject(value, key);
if (found) return found;
}
}
function getBuildFolder(pkgPath) {
const pkg = readPkg(pkgPath);
if (!pkg) return null;
let artifact =
pkg.main ||
pkg.module ||
pkg.types ||
pkg.typings ||
findKeyInObject(pkg.exports, "default") ||
findKeyInObject(pkg.exports, "types");
if (!artifact) return null;
const dir = path.join(pkgPath, artifact.replace(/^\.\//, "").split("/")[0]);
return existsSync(dir) ? dir : null;
}
if (args.all) {
console.time("Took");
for (const dep of task.dependencies) {
for (const batch of await toBatches(resolvedPkgs, dep)) {
await Promise.all(
batch
.filter((pkg) => !args.exclude.some((name) => isPkg(pkg.path, name)))
.map((task) => runScript(task.cmd, task.path))
);
}
}
console.timeEnd("Took");
} else {
console.time("Ready in");
const pkg = resolvedPkgs.find((candidate) => isPkg(candidate.path, project));
if (!pkg) {
console.error(`Package "${project}" not found.`);
process.exit(1);
}
for (const dep of task.dependencies) {
for (const batch of await toBatches(
await resolveRecursive(dep, pkg.path),
dep
)) {
await Promise.all(
batch.map(async (item) => {
if (item.path === pkg.path) return;
const hasBuild = !!getBuildFolder(item.path);
const key = item.path + ":" + item.cmd;
const [fast, slow] = cache[key]?.split("|") || [];
const fastHash = await computeChecksumFast(item.path);
if (hasBuild && !args.force && fast === fastHash) return;
const slowHash = await computeChecksumSlow(item.path);
if (hasBuild && !args.force && slow === slowHash) return;
await runScript(item.cmd, item.path);
cache[key] = `${fastHash}|${slowHash}`;
})
);
}
}
const tmp = cachePath + ".tmp";
await writeFile(tmp, JSON.stringify(cache));
await rename(tmp, cachePath);
console.timeEnd("Ready in");
await runScript(cmd, pkg.path, {
verbose: true,
args: ["--", ...args._.slice(1)]
});
}

View File

@@ -3,6 +3,7 @@
"target": "es2016",
"module": "ESNext",
"moduleResolution": "node",
"incremental": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,

View File

@@ -17,12 +17,11 @@ 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 { defineConfig } from "tsup";
import { defineConfig } from "tsdown";
export default defineConfig({
clean: true,
cjsInterop: true,
dts: true,
splitting: false,
format: ["cjs", "esm"]
format: ["cjs", "esm"],
dts: {
tsgo: true
}
});