Compare commits

...

27 Commits

Author SHA1 Message Date
Abdullah Atta
caf217907c docs: add docs for self hosting 2025-10-09 13:23:10 +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
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
Ammar Ahmed
f40ccdae41 mobile: add null checks 2025-10-05 01:11:09 +05:00
Ammar Ahmed
0a0aa09eea mobile: update version to v3.3.2 2025-10-05 01:02:56 +05:00
Ammar Ahmed
e2c55fb758 mobile: fix crash on entering settings 2025-10-05 00:58:02 +05:00
72 changed files with 5267 additions and 5979 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,7 +267,7 @@ jobs:
working-directory: ./apps/desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
run: npm run tx @notesnook/desktop:release
working-directory: ./apps/desktop
- name: Build snap
@@ -339,7 +335,7 @@ jobs:
working-directory: ./apps/desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
run: npm run tx @notesnook/desktop:release
working-directory: ./apps/desktop
# - name: Build 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,7 +66,7 @@ jobs:
npm run bootstrap -- --scope=desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
run: npm run tx @notesnook/desktop:release
working-directory: ./apps/desktop
- name: Build app
@@ -114,7 +110,7 @@ jobs:
npm run bootstrap -- --scope=desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
run: npm run tx @notesnook/desktop:release
working-directory: ./apps/desktop
- name: Build app
@@ -164,7 +160,7 @@ jobs:
working-directory: ./apps/desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
run: npm run tx @notesnook/desktop:release
working-directory: ./apps/desktop
- name: Build app

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.**

File diff suppressed because it is too large Load Diff

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]);
@@ -949,7 +942,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 +954,7 @@ const PricingPlanCard = ({
);
useEffect(() => {
if (pricingPlans?.isGithubRelease) return;
pricingPlans
?.getRegionalDiscount(
plan.id,
@@ -980,7 +973,7 @@ const PricingPlanCard = ({
pricingPlans.isSubscribed();
const isNotReady =
pricingPlans?.loadingPlans || (!price && !WebPlan?.price.gross);
pricingPlans?.loadingPlans || (!price && !WebPlan?.price?.gross);
return (
<TouchableOpacity
@@ -1112,7 +1105,7 @@ const PricingPlanCard = ({
</View>
</View>
{pricingPlans?.loadingPlans || (!price && !WebPlan?.price.gross) ? (
{pricingPlans?.loadingPlans || (!price && !WebPlan?.price?.gross) ? (
<ActivityIndicator size="small" color={colors.primary.accent} />
) : (
<View>
@@ -1120,7 +1113,7 @@ const PricingPlanCard = ({
{isFreePlan
? "0.00"
: price ||
`${WebPlan?.price.currency} ${WebPlan?.price.gross}`}{" "}
`${WebPlan?.price?.currency} ${WebPlan?.price?.gross}`}{" "}
<Paragraph>/{strings.month()}</Paragraph>
</Paragraph>

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,16 +55,17 @@ export default function PaywallSheet<Tid extends FeatureId>(props: {
}, []);
const isSubscribedOnWeb =
(PremiumService.get() &&
pricingPlans.user?.subscription.provider ===
SubscriptionProvider.PADDLE) ||
pricingPlans.user?.subscription.provider ===
SubscriptionProvider.STREETWRITERS;
PremiumService.get() &&
(pricingPlans.user?.subscription?.provider ===
SubscriptionProvider.PADDLE ||
pricingPlans.user?.subscription?.provider ===
SubscriptionProvider.STREETWRITERS);
const isCurrentPlatform =
(pricingPlans.user?.subscription.provider === SubscriptionProvider.APPLE &&
(pricingPlans.user?.subscription?.provider === SubscriptionProvider.APPLE &&
Platform.OS === "ios") ||
(pricingPlans.user?.subscription.provider === SubscriptionProvider.GOOGLE &&
(pricingPlans.user?.subscription?.provider ===
SubscriptionProvider.GOOGLE &&
Platform.OS === "android");
return !pricingPlans.currentPlan ? null : (
@@ -193,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", {
@@ -255,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();
@@ -35,9 +36,9 @@ export function PlanLimits() {
}, []);
const isCurrentPlatform =
(user?.subscription.provider === SubscriptionProvider.APPLE &&
(user?.subscription?.provider === SubscriptionProvider.APPLE &&
Platform.OS === "ios") ||
(user?.subscription.provider === SubscriptionProvider.GOOGLE &&
(user?.subscription?.provider === SubscriptionProvider.GOOGLE &&
Platform.OS === "android");
return (
@@ -84,10 +85,11 @@ export function PlanLimits() {
</View>
))}
{(user?.subscription.provider === SubscriptionProvider.PADDLE ||
user?.subscription.provider === SubscriptionProvider.STREETWRITERS ||
{((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

@@ -100,9 +100,9 @@ export const settingsGroups: SettingSection[] = [
strings.subscriptionProviderInfo[user?.subscription?.provider];
const isCurrentPlatform =
(user.subscription.provider === SubscriptionProvider.APPLE &&
(user.subscription?.provider === SubscriptionProvider.APPLE &&
Platform.OS === "ios") ||
(user.subscription.provider === SubscriptionProvider.GOOGLE &&
(user.subscription?.provider === SubscriptionProvider.GOOGLE &&
Platform.OS === "android");
if (
@@ -132,8 +132,8 @@ export const settingsGroups: SettingSection[] = [
"dddd, MMMM D, YYYY h:mm A"
);
if (user.subscription.plan !== SubscriptionPlan.FREE) {
const status = user.subscription.status;
if (user.subscription?.plan !== SubscriptionPlan.FREE) {
const status = user.subscription?.status;
return status === SubscriptionStatus.TRIAL
? strings.trialEndsOn(
dayjs(user?.subscription?.start)

View File

@@ -24,7 +24,7 @@ import { useThemeColors } from "@notesnook/theme";
import { useNetInfo } from "@react-native-community/netinfo";
import dayjs from "dayjs";
import React from "react";
import { Image, TouchableOpacity, View } from "react-native";
import { Image, Platform, TouchableOpacity, View } from "react-native";
import ImagePicker from "react-native-image-crop-picker";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
@@ -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");
@@ -133,9 +134,9 @@ const SettingsUserSection = ({ item }) => {
const total = user?.totalStorage || 0;
const isCurrentPlatform =
(user.subscription.provider === SubscriptionProvider.APPLE &&
(user?.subscription?.provider === SubscriptionProvider.APPLE &&
Platform.OS === "ios") ||
(user.subscription.provider === SubscriptionProvider.GOOGLE &&
(user?.subscription?.provider === SubscriptionProvider.GOOGLE &&
Platform.OS === "android");
return (
@@ -328,7 +329,7 @@ const SettingsUserSection = ({ item }) => {
}}
>
<Paragraph size={AppFontSize.sm}>
{planToDisplayName(user.subscription.plan)}
{planToDisplayName(user.subscription?.plan)}
</Paragraph>
<Paragraph
color={colors.secondary.paragraph}
@@ -339,14 +340,16 @@ const SettingsUserSection = ({ item }) => {
</Paragraph>
</TouchableOpacity>
{(user.subscription.provider === SubscriptionProvider.PADDLE ||
user.subscription.provider ===
{((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
user.subscription?.plan !== SubscriptionPlan.FREE
? strings.changePlan()
: strings.upgradePlan()
}
@@ -362,8 +365,9 @@ const SettingsUserSection = ({ item }) => {
}
if (
user.subscription.plan !== SubscriptionPlan.FREE &&
user.subscription.productId.includes("5year")
user.subscription?.plan !== SubscriptionPlan.FREE &&
user.subscription?.productId &&
user.subscription?.productId.includes("5year")
) {
ToastManager.show({
message:

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

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

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 = 2149;
CURRENT_PROJECT_VERSION = 2150;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1163,7 +1163,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3.1;
MARKETING_VERSION = 3.3.2;
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 = 2149;
CURRENT_PROJECT_VERSION = 2150;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1268,7 +1268,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3.1;
MARKETING_VERSION = 3.3.2;
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 = 2149;
CURRENT_PROJECT_VERSION = 2150;
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.1;
MARKETING_VERSION = 3.3.2;
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 = 2149;
CURRENT_PROJECT_VERSION = 2150;
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.1;
MARKETING_VERSION = 3.3.2;
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 = 2149;
CURRENT_PROJECT_VERSION = 2150;
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.1;
MARKETING_VERSION = 3.3.2;
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 = 2149;
CURRENT_PROJECT_VERSION = 2150;
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.1;
MARKETING_VERSION = 3.3.2;
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.1",
"version": "3.3.2",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [

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

@@ -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

@@ -0,0 +1 @@
# Self hosting

View File

@@ -0,0 +1,295 @@
# Getting started
## Minimum server requirements
- **Operating system**: Linux
- **RAM**: 1 GB
- **Storage**: 20 GB
- **CPU**: any x64 or ARM CPU will do
## Prerequisites
1. Docker
2. Docker Compose
3. wget
4. curl
5. (Optional) Caddy or Nginx for reverse proxying
You **must** have a working Linux server with SSH access.
> warn Disclaimer
>
> How you secure or setup your server is up to you, and this guide will NOT provide any recommendations outside what is directly relevant to Notesnook.
>
> Self hosting means managing everything on your own so **we will not be responsible if you lose any data** during setup, upgrade, or any other step. It is recommended that you create regular backups in both the app and on the server to avoid this.
## Installation
1. Create a directory where configuration files can be placed:
```sh
mkdir notesnook-sync-server
```
2. Navigate into the newly created directory:
```sh
cd notesnook-sync-server
```
3. Download the `docker-compose.yml` file:
```sh
wget https://raw.githubusercontent.com/streetwriters/notesnook-sync-server/master/docker-compose.yml
```
4. Download the `.env` file:
```sh
wget https://raw.githubusercontent.com/streetwriters/notesnook-sync-server/master/.env
```
You should end up with the following files in the `notesnook-sync-server` directory:
- docker-compose.yml
- .env
## Configuration
All configuration is done via the `.env` file. You'll need to change very few settings to get the self-hosted containers up and running.
### `INSTANCE_NAME` (required)
This is used by the client apps to identify which instance you are using. You can just leave it the default value if you like, or set it to a personalized value like `john-doe-notesnook-server`.
### `NOTESNOOK_API_SECRET` (required)
The `NOTESNOOK_API_SECRET` is used for authenticating & validating the access tokens. This must be a randomly generated value otherwise anyone will be able to make authenticated requests to your server.
You can use OpenSSL to generate a secure random secret like this:
```sh
openssl rand -hex 32
```
### SMTP settings (required)
SMTP settings are required to enable sending 2FA and account reset emails. You can either use your email provider's SMTP settings (most email providers allow sending emails via SMTP) or you can create a free account on [SendGrid](http://sendgrid.com/) or [Brevo/SendInBlue](https://www.brevo.com). Both of these providers allow sending emails via SMTP in their free tiers.
| Variable | Description | Required? | Example |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | -------------- |
| `SMTP_USERNAME` | Username for the SMTP connection. Usually it is the email address but check your email provider's documentation to see what you should put here. | yes |
| `SMTP_PASSWORD` | Password for the SMTP connection. Check your email provider's documentation to see what you should put here. | yes |
| `SMTP_HOST` | Host on which the the SMTP connection is accessible. | yes | smtp.gmail.com |
| `SMTP_PORT` | Port on which the the SMTP connection is accessible. | yes | 465 |
### Public URL settings (required)
Public URLs are how the servers can generate valid publicly accessible URLs for different things like email confirmation, password reset links etc. These URLs must be accessible from _outside_ of where you are hosting your servers (e.g. by using a reverse proxy like Nginx).
| Variable | Description | Required? | Example |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------- | ------------------------- |
| `NOTESNOOK_APP_PUBLIC_URL` | If you are self hosting the web app, you should put its URL here. Otherwise leave it as is. | yes | https://app.notesnook.com |
| `AUTH_SERVER_PUBLIC_URL` | Publicly accessible URL for the auth server. You usually expose this URL via a reverse proxy like Caddy/Nginx. | yes | https://auth.example.com |
| `MONOGRAPH_PUBLIC_URL` | Publicly accessible URL for the monographs. You usually expose this URL via a reverse proxy like Caddy/Nginx. | yes | https://monogr.ph |
| `ATTACHMENTS_SERVER_PUBLIC_URL` | Publicly accessible URL for the attachments (aka S3) server. You usually expose this URL via a reverse proxy like Caddy/Nginx. | yes | https://s3.example.com |
### `DISABLE_SIGNUPS` (required)
Set this to `true` to disable new accounts from being created on your server.
### Minio settings (optional)
| Variable | Description | Default |
| --------------------- | ------------------------------------------ | ---------- |
| `MINIO_ROOT_USER` | Custom username for the root Minio account | minioadmin |
| `MINIO_ROOT_PASSWORD` | Custom password for the root Minio account | minioadmin |
You should change these values to something random & secure just to be safe. The Minio internal API & dashboard ports are not exposed for external use so you are probably safe with the default values but don't take my word for it. Change the password. You won't regret it.
### `NOTESNOOK_CORS_ORIGINS` (optional)
CORS is used to prevent unknown and arbitrary origins from making requests to your server. This is useful, for example, if you want to allow requests only from `https://app.notesnook.com` and reject everything else.
By default, all origins are allowed which means anyone from any domain can make requests to your server. You probably don't want this if you are using self hosted clients.
For example, to restrict traffic only to `yourdomain.com`, you should set this to:
```
NOTESNOOK_CORS_ORIGINS=https://yourdomain.com
```
If you want to allow multiple domains, just separate them using commas:
```
NOTESNOOK_CORS_ORIGINS=https://yourdomain.com,https://yourotherdomain.com
```
### Twilio settings (optional)
Twilio is used for SMS based 2FA. If you don't plan on using 2FA via SMS then leave these settings empty.
| Variable | Description | Example |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- |
| `TWILIO_ACCOUNT_SID` | [Learn more here](https://help.twilio.com/articles/14726256820123-What-is-a-Twilio-Account-SID-and-where-can-I-find-it-) |
| `TWILIO_AUTH_TOKEN` | [Learn more here](https://help.twilio.com/articles/223136027-Auth-Tokens-and-How-to-Change-Them) |
| `TWILIO_SERVICE_SID` | The unique string that we created to identify the Service resource. | VAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
## Starting the server
Once you are done configuring your server, it's time to run it:
```sh
docker compose up -d
```
To make sure everything is working well, run:
```sh
docker ps
```
You should see something like this:
```
3c39da9194db streetwriters/sse:latest "./Streetwriters.Mes…" 38 minutes ago Up 38 minutes (healthy) 0.0.0.0:7264->7264/tcp, :::7264->7264/tcp notesnook-sse-server-1
19c4a6536578 streetwriters/monograph:latest "docker-entrypoint.s…" 38 minutes ago Up 38 minutes (healthy) 0.0.0.0:6264->3000/tcp, [::]:6264->3000/tcp notesnook-monograph-server-1
7b9db61b5d0d streetwriters/notesnook-sync:latest "./Notesnook.API" 38 minutes ago Up 38 minutes (healthy) 0.0.0.0:5264->5264/tcp, :::5264->5264/tcp notesnook-notesnook-server-1
6491b172817e streetwriters/identity:latest "./Streetwriters.Ide…" 38 minutes ago Up 38 minutes (healthy) 0.0.0.0:8264->8264/tcp, :::8264->8264/tcp notesnook-identity-server-1
bfb71f21e57b minio/minio:RELEASE.2024-07-29T22-14-52Z "/usr/bin/docker-ent…" 38 minutes ago Up 38 minutes (healthy) 0.0.0.0:9000->9000/tcp, :::9000->9000/tcp notesnook-notesnook-s3-1
d27f6207fb93 mongo:7.0.12 "docker-entrypoint.s…" 38 minutes ago Up 38 minutes (healthy) 27017/tcp notesnook-notesnook-db-1
2bde52e0102d willfarrell/autoheal:latest "/docker-entrypoint …" 38 minutes ago Up 38 minutes (healthy) notesnook-autoheal-1
```
In total, you should have 7 containers up and running. If there are less than 7 that means something failed. If you see `Unhealthy` anywhere, that means something went wrong. Refer to the troubleshooting guide to see how you can debug this.
## Exposing the servers to the Internet
Running the docker containers on device is all well and good but if you want to connect your other devices, sync your notes, you need to expose the servers over the Internet. Even if you only require local access, it is recommended that you use something like Tailscale or Cloudflare Tunnels to securely & reliably expose the Notesnook servers.
> error HTTPS is required
>
> You won't be able to sync or login without HTTPS. This is **not** mandated by Notesnook but by the vendors running the client apps (i.e. all major browsers diallow HTTP connections).
> info
>
> These instructions assume that you did not change the ports for the servers inside the `docker-compose.yml` file. If you did change the default ports, you'll have to use those instead.
### Using Caddy (recommended)
Caddy is one of the simplest ways to reverse proxy traffic from the Internet to your servers.
> info
>
> If you already have a `Caddyfile`, just use that.
1. Create or edit your `Caddyfile` (you can do this literally anywhere). For default installations, you'll find the `Caddyfile` in `/etc/caddy/Caddyfile`.
2. Paste the following configuration into the `Caddyfile`
```
sync.example.com {
reverse_proxy :5264
}
auth.example.com {
reverse_proxy :8264
}
sse.example.com {
reverse_proxy :7264
}
monograph.example.com {
reverse_proxy :6264
}
s3.example.com {
reverse_proxy :9000
}
```
3. Change the domain names as appropriate.
4. Restart Caddy (run `systemctl restart caddy` if you are running it as a daemon)
5. And done! You should now have a working SSL connection to all your servers.
### Using Nginx
Configuring Nginx is a bit more complicated than Caddy. If you are a beginner, you should probably stick to using Caddy. This guide assumes you already know how Nginx works and how to configure it.
1. Create an Nginx configuration file in `/etc/nginx/sites-available` and paste the following into it:
```nginx
# Sync server
server {
server_name sync.example.com;
location / {
proxy_pass http://127.0.0.1:5264;
include proxy_params;
proxy_redirect off; # required
proxy_http_version 1.1; # required
proxy_set_header Upgrade $http_upgrade; # required
proxy_set_header Connection "upgrade"; # required
}
}
# SSE server
server {
server_name sse.example.com;
location / {
proxy_pass http://127.0.0.1:7264;
include proxy_params;
proxy_set_header Connection ''; # required
proxy_http_version 1.1; # required
chunked_transfer_encoding off; # required
proxy_buffering off; # required
proxy_cache off; # required
}
}
# Auth server
server {
server_name auth.example.com;
location / {
proxy_pass http://127.0.0.1:8264;
include proxy_params;
}
}
# Monograph server
server {
server_name monograph.example.com;
location / {
proxy_pass http://127.0.0.1:6264;
include proxy_params;
}
}
# S3 server
server {
server_name s3.example.com;
location / {
proxy_pass http://127.0.0.1:9000;
include proxy_params;
client_max_body_size 1024M; # required
}
}
```
2. Change the `server_name`s as appropriate.
3. Run `nginx -t` and make sure there are no errors
4. Restart nginx (`systemctl restart nginx`)
5. And done! You should have a working HTTP connection to all your servers.
6. **To enable SSL, [use CertBot](https://certbot.eff.org/instructions?ws=nginx&os=snap).**
> info Cloudflare DNS users
>
> If you are using Cloudflare for DNS, make sure the DNS settings for these domains are set to DNS-only instead of Proxied. While the Proxied mode might work, it is not tested so use at your own risk.
## Testing the connection
You installed, configured, and set up everything but how can you be sure your devices can access your servers? That's where `curl` comes in.
All the above services provide a healthcheck endpoint to allow checking their status. You can run a healthcheck like this:
```sh
curl https://sync.example.com/health
```
You should see `Healthy` printed on the screen.
## Connecting the client apps

View File

@@ -0,0 +1,51 @@
# Overview
In addition to being 100% open source, Notesnook offers a complete setup for self hosting. This version is provided as-is without any kind of guarantees or dedicated support. We'll do our best to keep self hosting functional but if you face any issues specific to your hosting setup, you are on your own.
Self hosting is a niche art and it is expected that you have all the basic sysadmin knowledge required to run & secure your server. This guide will try to provide solutions for most common scenarios and setups but anything more complex is out of scope.
If you already have an account on Notesnook SaaS and want to switch to a self hosted setup, check out the following guide.
## Infrastructure
The self hosted setup consists of 5 services and 1 database:
1. `notesnook-db`: MonogDB for storing your data
2. `notesnook-s3`: Minio for storing your attachments
3. `identity-server`: For auth & login
4. `sync-server`: For syncing your notes
5. `sse-server`: For sending events & notifications to the client apps.
6. `monograph-server`: For serving published monographs
## Differences between self-hosted and SaaS
The following things are not included in the self-hosted setup:
1. Billing & subscriptions system
2. Issues & feedback system
3. Desktop, mobile & web clients
4. User administration sytem
Everything else is exactly the same.
## Intended audience
This guide is targeted towards people who want to self host Notesnook for personal use. While you can create multiple accounts on your self-hosted instance, we provide no support or guarantees on if/how it'll work.
## FAQs
### Do I need to purchase a subscription in order to self-host?
No. Self-hosting is completely independent and does not require any kind of connection or authentication with the official Notesnook instance.
### Will Notesnook (the company) know I am self-hosting?
No. There is no telemetry on either the server or the clients that'd let us know whether someone is self-hosting or not.
### Do I need to use the official apps if I am self-hosting?
No. You can compile all the apps from source and run them that way if you like.
### Can I get help from you if I face any issue during self-hosting?
No. This guide is provided with the assumption that _it works_. Any edge cases or specific setups are not handled or expected.

View File

@@ -80,6 +80,10 @@ navigation:
- path: custom-themes/create-a-theme-with-theme-builder.md
- path: custom-themes/install-a-theme-from-file.md
- path: custom-themes/publish-a-theme.md
- path: self-hosting
children:
- path: self-hosting/overview.md
- path: self-hosting/getting-started.md
- path: faqs
children:
- path: faqs/what-are-merge-conflicts.md

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

@@ -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

@@ -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

@@ -3795,8 +3795,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

@@ -3775,8 +3775,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?`,

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
}
});