mobile: introduce plan limits

This commit is contained in:
Ammar Ahmed
2025-07-18 11:44:56 +05:00
committed by Abdullah Atta
parent 1be518510a
commit dc08a39173
29 changed files with 724 additions and 138 deletions

View File

@@ -16,7 +16,7 @@ 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 { database } from "@notesnook/common";
import { database, getFeatureLimit } from "@notesnook/common";
import { logger as dbLogger, ICompressor } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import {
@@ -73,6 +73,10 @@ export async function setupDatabase(password?: string) {
tempStore: "memory",
journalMode: Platform.OS === "ios" ? "DELETE" : "WAL",
password: key
},
maxNoteVersions: async () => {
const limit = await getFeatureLimit("maxNoteVersions");
return typeof limit.caption === "number" ? limit.caption : undefined;
}
});
}

View File

@@ -265,7 +265,7 @@ export const Login = ({ changeMode }) => {
style={{
alignSelf: "center",
marginTop: DefaultAppStyles.GAP_VERTICAL,
width: 250
width: "100%"
}}
onPress={() => {
if (loading) return;

View File

@@ -16,6 +16,9 @@ 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 { useIsFeatureAvailable } from "@notesnook/common";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useState } from "react";
import {
Image,
@@ -25,15 +28,13 @@ import {
View
} from "react-native";
import { Image as ImageType } from "react-native-image-crop-picker";
import { useThemeColors } from "@notesnook/theme";
import { presentSheet } from "../../../services/event-manager";
import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
import { presentSheet, ToastManager } from "../../../services/event-manager";
import { AppFontSize, defaultBorderRadius } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import { Notice } from "../../ui/notice";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../../utils/styles";
export default function AttachImage({
response,
@@ -44,6 +45,7 @@ export default function AttachImage({
onAttach: ({ compress }: { compress: boolean }) => void;
close: ((ctx?: string | undefined) => void) | undefined;
}) {
const fullQualityImagesResult = useIsFeatureAvailable("fullQualityImages");
const { colors } = useThemeColors();
const [compress, setCompress] = useState(true);
@@ -104,21 +106,42 @@ export default function AttachImage({
alignSelf: "center",
marginBottom: DefaultAppStyles.GAP_VERTICAL,
alignItems: "center",
width: "100%"
width: "100%",
opacity: fullQualityImagesResult?.isAllowed ? 1 : 0.5
}}
onPress={() => {
if (!fullQualityImagesResult?.isAllowed) {
ToastManager.show({
message: fullQualityImagesResult?.error,
type: "info",
context: "local"
});
return;
}
setCompress(!compress);
}}
>
<IconButton
size={AppFontSize.lg}
name={compress ? "checkbox-marked" : "checkbox-blank-outline"}
color={compress ? colors.primary.accent : colors.primary.icon}
color={
compress && fullQualityImagesResult?.isAllowed
? colors.primary.accent
: colors.primary.icon
}
style={{
width: 25,
height: 25
}}
onPress={() => {
if (!fullQualityImagesResult?.isAllowed) {
ToastManager.show({
message: fullQualityImagesResult?.error,
type: "info",
context: "local"
});
return;
}
setCompress(!compress);
}}
/>

View File

@@ -30,6 +30,10 @@ import { fluidTabsRef } from "../../utils/global-refs";
import { AppFontSize } from "../../utils/size";
import { useSideBarDraggingStore } from "../side-menu/dragging-store";
import { IconButton } from "../ui/icon-button";
import { isFeatureAvailable, useIsFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../sheets/paywall";
import { strings } from "@notesnook/intl";
import { ToastManager } from "../../services/event-manager";
interface ReorderableListProps<T extends { id: string }>
extends Omit<DraxListProps<T>, "renderItem" | "data" | "renderItemContent"> {
@@ -59,6 +63,9 @@ function ReorderableList<T extends { id: string }>({
const [hiddenItemsState, setHiddenItems] = useState(hiddenItems);
const dragging = useSideBarDraggingStore((state) => state.dragging);
const listRef = useRef<FlatList | null>(null);
const customizableSidebarFeature = useIsFeatureAvailable(
"customizableSidebar"
);
if (dragging) {
fluidTabsRef.current?.lock();
@@ -125,7 +132,7 @@ function ReorderableList<T extends { id: string }>({
);
function getOrderedItems() {
const items: T[] = [];
const items: T[] = customizableSidebarFeature?.isAllowed ? data : [];
itemOrderState.forEach((id) => {
const item = data.find((i) => i.id === id);
if (!item) return;
@@ -158,15 +165,40 @@ function ReorderableList<T extends { id: string }>({
}
}}
longPressDelay={500}
onItemDragStart={() =>
onItemDragStart={async () => {
if (
customizableSidebarFeature &&
!customizableSidebarFeature?.isAllowed
) {
ToastManager.show({
message: customizableSidebarFeature?.error,
type: "info",
actionText: strings.upgrade(),
func: () => PaywallSheet.present(customizableSidebarFeature)
});
return;
}
useSideBarDraggingStore.setState({
dragging: true
})
}
});
}}
disableVirtualization
itemsDraggable={disableDefaultDrag ? dragging : true}
lockItemDragsToMainAxis
onItemReorder={({ fromIndex, fromItem, toIndex, toItem }) => {
onItemReorder={async ({ fromIndex, fromItem, toIndex, toItem }) => {
if (
customizableSidebarFeature &&
!customizableSidebarFeature?.isAllowed
) {
ToastManager.show({
message: customizableSidebarFeature.error,
type: "info",
actionText: strings.upgrade(),
func: () => PaywallSheet.present(customizableSidebarFeature)
});
return;
}
const newOrder = getOrderedItems().map((item) => item.id);
const element = newOrder.splice(fromIndex, 1)[0];
if (toIndex === 0) {

View File

@@ -25,7 +25,7 @@ import { FlashList } from "@shopify/flash-list";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../e2e/test.ids";
import { db } from "../../common/database";
import { eSendEvent } from "../../services/event-manager";
import { eSendEvent, ToastManager } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import { useMenuStore } from "../../stores/use-menu-store";
import { useRelationStore } from "../../stores/use-relation-store";
@@ -38,6 +38,8 @@ import NativeTooltip from "../../utils/tooltip";
import { Pressable } from "../ui/pressable";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
import { isFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../sheets/paywall";
const ColorItem = ({ item, note }: { item: Color; note: Note }) => {
const { colors } = useThemeColors();
@@ -109,6 +111,25 @@ export const ColorTags = ({ item }: { item: Note }) => {
[note]
);
const onPress = React.useCallback(async () => {
const colorTagsAvailable = await isFeatureAvailable("colors");
if (!colorTagsAvailable.isAllowed) {
ToastManager.show({
message: colorTagsAvailable.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(colorTagsAvailable);
ToastManager.hide();
}
});
return;
}
useSettingStore.getState().setSheetKeyboardHandler(false);
setVisible(true);
}, []);
return (
<>
<ColorPicker
@@ -133,10 +154,7 @@ export const ColorTags = ({ item }: { item: Note }) => {
>
{!colorNotes || !colorNotes.length ? (
<Button
onPress={async () => {
useSettingStore.getState().setSheetKeyboardHandler(false);
setVisible(true);
}}
onPress={onPress}
buttonType={{
text: colors.primary.accent
}}
@@ -172,10 +190,7 @@ export const ColorTags = ({ item }: { item: Note }) => {
marginRight: 5
}}
type="secondary"
onPress={() => {
useSettingStore.getState().setSheetKeyboardHandler(false);
setVisible(true);
}}
onPress={onPress}
>
<Icon
testID="icon-plus"

View File

@@ -16,6 +16,7 @@ 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 { useIsFeatureAvailable } from "@notesnook/common";
import {
ContentBlock,
Note,
@@ -33,12 +34,12 @@ import { db } from "../../../common/database";
import { useDBItem } from "../../../hooks/use-db-item";
import { editorController } from "../../../screens/editor/tiptap/utils";
import { presentSheet } from "../../../services/event-manager";
import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
import { AppFontSize, defaultBorderRadius } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { DefaultAppStyles } from "../../../utils/styles";
const ListNoteItem = ({
id,
@@ -146,6 +147,7 @@ export default function LinkNote(props: {
onLinkCreated: () => void;
close?: (ctx?: string) => void;
}) {
const blockLinking = useIsFeatureAvailable("blockLinking");
const { colors } = useThemeColors();
const query = useRef<string>();
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
@@ -327,7 +329,31 @@ export default function LinkNote(props: {
keyboardShouldPersistTaps="handled"
windowSize={3}
keyExtractor={(item) => item.id}
data={nodes}
ListEmptyComponent={
<View
style={{
gap: DefaultAppStyles.GAP_VERTICAL,
backgroundColor: colors.secondary.background,
padding: DefaultAppStyles.GAP,
borderRadius: defaultBorderRadius,
borderWidth: 0.5,
borderColor: colors.secondary.border,
alignItems: "center"
}}
>
<Paragraph color={colors.secondary.paragraph}>
{blockLinking?.error}
</Paragraph>
<Button
title={strings.upgradePlan()}
style={{
width: "100%"
}}
type="accent"
/>
</View>
}
data={blockLinking?.isAllowed ? nodes : []}
/>
) : (
<FlatList

View File

@@ -21,7 +21,11 @@ import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import { eSendEvent, presentSheet } from "../../../services/event-manager";
import {
eSendEvent,
presentSheet,
ToastManager
} from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { eCloseSheet } from "../../../utils/events";
import { SideMenuItem } from "../../../utils/menu-items";
@@ -31,6 +35,8 @@ import { useSideBarDraggingStore } from "../../side-menu/dragging-store";
import AppIcon from "../../ui/AppIcon";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { isFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../paywall";
export const MenuItemProperties = ({ item }: { item: SideMenuItem }) => {
const { colors } = useThemeColors();
return (
@@ -44,18 +50,50 @@ export const MenuItemProperties = ({ item }: { item: SideMenuItem }) => {
{[
{
title: strings.setAsHomepage(),
onPress: () => {
onPress: async () => {
const customHomepageFeature = await isFeatureAvailable(
"customHomepage"
);
if (!customHomepageFeature.isAllowed) {
ToastManager.show({
message: customHomepageFeature.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(customHomepageFeature);
}
});
return;
}
SettingsService.setProperty("homepageV2", {
id: item.id,
type: "default"
});
eSendEvent(eCloseSheet);
},
icon: "home-outline"
icon: "home-outline",
type: "switch",
state: SettingsService.getProperty("homepageV2")?.id === item.id
},
{
title: strings.reorder(),
onPress: () => {
onPress: async () => {
const sidebarReorderFeature = await isFeatureAvailable(
"customizableSidebar"
);
if (!sidebarReorderFeature.isAllowed) {
ToastManager.show({
message: sidebarReorderFeature.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(sidebarReorderFeature);
}
});
return;
}
useSideBarDraggingStore.setState({
dragging: true
});
@@ -79,12 +117,28 @@ export const MenuItemProperties = ({ item }: { item: SideMenuItem }) => {
item.onPress();
}}
>
<AppIcon
color={colors.secondary.icon}
name={item.icon}
size={AppFontSize.xl}
/>
<Paragraph>{item.title}</Paragraph>
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: DefaultAppStyles.GAP_SMALL
}}
>
<AppIcon
color={colors.secondary.icon}
name={item.icon}
size={AppFontSize.xl}
/>
<Paragraph>{item.title}</Paragraph>
</View>
{item.type === "switch" && item.state ? (
<AppIcon
name="check"
size={AppFontSize.lg}
color={colors.primary.accent}
style={{ marginLeft: "auto" }}
/>
) : null}
</Pressable>
))}
</View>

View File

@@ -27,7 +27,8 @@ import NotebookScreen from "../../../screens/notebook";
import {
eSendEvent,
eSubscribeEvent,
presentSheet
presentSheet,
ToastManager
} from "../../../services/event-manager";
import {
createNotebookTreeStores,
@@ -44,6 +45,8 @@ import { NotebookItem } from "../../side-menu/notebook-item";
import { IconButton } from "../../ui/icon-button";
import Paragraph from "../../ui/typography/paragraph";
import { AddNotebookSheet } from "../add-notebook";
import { isFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../paywall";
const {
useNotebookExpandedStore,
@@ -167,7 +170,21 @@ export const Notebooks = (props: {
height: 30
}}
name="plus"
onPress={() => {
onPress={async () => {
const notebooksFeature = await isFeatureAvailable("notebooks");
if (!notebooksFeature.isAllowed) {
ToastManager.show({
message: notebooksFeature.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
ToastManager.hide();
PaywallSheet.present(notebooksFeature);
}
});
return;
}
AddNotebookSheet.present(undefined, props.rootNotebook, "local");
}}
/>

View File

@@ -1,7 +1,10 @@
import { View, Text } from "react-native";
import { presentSheet } from "../../../services/event-manager";
import { FeatureId, FeatureResult } from "@notesnook/common";
export default function PaywallSheet() {
export default function PaywallSheet<Tid extends FeatureId>(props: {
feature: FeatureResult<Tid>;
}) {
return (
<View
style={{
@@ -20,8 +23,8 @@ export default function PaywallSheet() {
);
}
PaywallSheet.present = () => {
PaywallSheet.present = <Tid extends FeatureId>(feature: FeatureResult<Tid>) => {
presentSheet({
component: <PaywallSheet />
component: <PaywallSheet feature={feature} />
});
};

View File

@@ -56,6 +56,8 @@ import {
import { useSideBarDraggingStore } from "./dragging-store";
import { Button } from "../ui/button";
import SettingsService from "../../services/settings";
import { isFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../sheets/paywall";
const renderScene = SceneMap({
home: SideMenuHome,
notebooks: SideMenuNotebooks,
@@ -347,10 +349,23 @@ const TabBar = (
testID="sidebar-add-button"
size={AppFontSize.lg - 2}
color={colors.primary.icon}
onPress={() => {
onPress={async () => {
if (props.navigationState.index === 1) {
const notebooksFeature = await isFeatureAvailable(
"notebooks"
);
if (!notebooksFeature.isAllowed) {
PaywallSheet.present(notebooksFeature);
return;
}
AddNotebookSheet.present();
} else {
const tagsFeature = await isFeatureAvailable("tags");
if (!tagsFeature.isAllowed) {
PaywallSheet.present(tagsFeature);
return;
}
presentDialog({
title: strings.addTag(),
paragraph: strings.addTagDesc(),

View File

@@ -18,32 +18,33 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { Fragment } from "react";
import React from "react";
import { FlatList, View } from "react-native";
import { DraxProvider, DraxScrollView } from "react-native-drax";
import { db } from "../../common/database";
import Navigation from "../../services/navigation";
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";
import { Button } from "../ui/button";
import { ColorSection } from "./color-section";
import { MenuItem } from "./menu-item";
import { PinnedSection } from "./pinned-section";
import { SideMenuHeader } from "./side-menu-header";
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import { eSendEvent } from "../../services/event-manager";
import { eOpenPremiumDialog } from "../../utils/events";
import { useUserStore } from "../../stores/use-user-store";
import { Button } from "../ui/button";
import { MenuItemProperties } from "../sheets/menu-item-properties";
const pro = {
title: strings.getNotesnookPro(),
title: strings.upgradePlan(),
icon: "crown",
id: "pro",
onPress: () => {
eSendEvent(eOpenPremiumDialog);
Navigation.navigate("PayWall", {
context: "logged-in"
});
}
};
@@ -141,15 +142,10 @@ export function SideMenuHome() {
subscriptionType === SUBSCRIPTION_STATUS.BASIC ? (
<Button
title={pro.title}
iconColor={colors.static.yellow}
textStyle={{
color: colors.static.white
}}
icon={pro.icon}
style={{
backgroundColor: colors.static.black,
width: "100%"
}}
type="accent"
onPress={pro.onPress}
/>
) : null}

View File

@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* eslint-disable no-inner-declarations */
import { isFeatureAvailable } from "@notesnook/common";
import {
createInternalLink,
Item,
@@ -38,11 +39,13 @@ import { presentDialog } from "../components/dialog/functions";
import NoteHistory from "../components/note-history";
import { AddNotebookSheet } from "../components/sheets/add-notebook";
import ExportNotesSheet from "../components/sheets/export-notes";
import PaywallSheet from "../components/sheets/paywall";
import PublishNoteSheet from "../components/sheets/publish-note";
import { ReferencesList } from "../components/sheets/references";
import { RelationsList } from "../components/sheets/relations-list/index";
import { useSideBarDraggingStore } from "../components/side-menu/dragging-store";
import { ButtonProps } from "../components/ui/button";
import AddReminder from "../screens/add-reminder";
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
import {
eSendEvent,
@@ -66,9 +69,6 @@ import { eUpdateNoteInEditor } from "../utils/events";
import { deleteItems } from "../utils/functions";
import { convertNoteToText } from "../utils/note-to-text";
import { sleep } from "../utils/time";
import AddReminder from "../screens/add-reminder";
import { isFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../components/sheets/paywall";
export type ActionId =
| "select"
@@ -216,7 +216,7 @@ export const useActions = ({
async function restoreTrashItem() {
close();
if ((await db.trash.restore(item.id)) === false) return;
await db.trash.restore(item.id);
Navigation.queueRoutesForUpdate();
const type = item.type === "trash" ? item.itemType : item.type;
ToastManager.show({
@@ -248,6 +248,19 @@ export const useActions = ({
if (isPinnedToMenu) {
await db.shortcuts.remove(item.id);
} else {
const shortcutsFeature = await isFeatureAvailable("shortcuts");
if (!shortcutsFeature.isAllowed) {
ToastManager.show({
message: shortcutsFeature.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(shortcutsFeature);
}
});
return;
}
await db.shortcuts.add({
itemId: item.id,
itemType: item.type
@@ -417,7 +430,22 @@ export const useActions = ({
id: "reorder",
title: strings.reorder(),
icon: "sort-ascending",
onPress: () => {
onPress: async () => {
const sidebarReorderFeature = await isFeatureAvailable(
"customizableSidebar"
);
if (!sidebarReorderFeature.isAllowed) {
ToastManager.show({
message: sidebarReorderFeature.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(sidebarReorderFeature);
}
});
return;
}
useSideBarDraggingStore.setState({
dragging: true
});
@@ -500,6 +528,22 @@ export const useActions = ({
await db.settings.setDefaultTag(undefined);
setDefaultTag(undefined);
} else {
const defaultNotebookAndTagFeature = await isFeatureAvailable(
"defaultNotebookAndTag"
);
if (!defaultNotebookAndTagFeature.isAllowed) {
ToastManager.show({
message: defaultNotebookAndTagFeature.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(defaultNotebookAndTagFeature);
}
});
return;
}
await db.settings.setDefaultTag(item.id);
setDefaultTag(item.id);
}
@@ -517,6 +561,20 @@ export const useActions = ({
title: strings.addNotebook(),
icon: "plus",
onPress: async () => {
const notebooksFeature = await isFeatureAvailable("notebooks");
if (!notebooksFeature.isAllowed) {
ToastManager.show({
message: notebooksFeature.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
ToastManager.hide();
PaywallSheet.present(notebooksFeature);
}
});
return;
}
close();
await sleep(300);
AddNotebookSheet.present(undefined, item);
@@ -545,6 +603,22 @@ export const useActions = ({
await db.settings.setDefaultNotebook(undefined);
setDefaultNotebook(undefined);
} else {
const defaultNotebookAndTagFeature = await isFeatureAvailable(
"defaultNotebookAndTag"
);
if (!defaultNotebookAndTagFeature.isAllowed) {
ToastManager.show({
message: defaultNotebookAndTagFeature.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(defaultNotebookAndTagFeature);
}
});
return;
}
const notebook = {
id: item.id
};
@@ -604,7 +678,24 @@ export const useActions = ({
icon: "home-outline",
isToggle: true,
checked: isHomepage,
onPress: () => {
onPress: async () => {
const customHomepageFeature = await isFeatureAvailable(
"customHomepage"
);
if (!customHomepageFeature.isAllowed) {
ToastManager.show({
message: customHomepageFeature.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(customHomepageFeature);
}
});
return;
}
SettingsService.setProperty(
"homepageV2",
isHomepage
@@ -688,7 +779,14 @@ export const useActions = ({
async function pinToNotifications() {
const result = await isFeatureAvailable("pinNoteInNotification");
if (!result.isAllowed) {
PaywallSheet.present();
ToastManager.show({
message: result.error,
type: "info",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(result);
}
});
return;
}
@@ -921,13 +1019,40 @@ export const useActions = ({
referenceType: "reminder",
relationType: "from",
title: strings.dataTypesPluralCamelCase.reminder(),
onAdd: () => {
onAdd: async () => {
const reminderFeature = await isFeatureAvailable(
"activeReminders"
);
if (!reminderFeature.isAllowed) {
ToastManager.show({
type: "info",
message: reminderFeature.error,
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(reminderFeature);
}
});
}
AddReminder.present(undefined, item);
close();
},
button: {
type: "plain",
onPress: () => {
onPress: async () => {
const reminderFeature = await isFeatureAvailable(
"activeReminders"
);
if (!reminderFeature.isAllowed) {
ToastManager.show({
type: "info",
message: reminderFeature.error,
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(reminderFeature);
}
});
return;
}
AddReminder.present(undefined, item);
close();
},

View File

@@ -103,6 +103,8 @@ import { fluidTabsRef } from "../utils/global-refs";
import { NotesnookModule } from "../utils/notesnook-module";
import { sleep } from "../utils/time";
import AddReminder from "../screens/add-reminder";
import { isFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../components/sheets/paywall";
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
const { disableSync, disableAutoSync } = SettingsService.get();
@@ -185,6 +187,18 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
if (reminder) AddReminder.present(reminder);
}
} else if (url.startsWith("https://notesnook.com/new_reminder")) {
const reminderFeature = await isFeatureAvailable("activeReminders");
if (!reminderFeature.isAllowed) {
ToastManager.show({
type: "info",
message: reminderFeature.error,
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(reminderFeature);
}
});
return;
}
AddReminder.present();
}
} catch (e) {

View File

@@ -31,6 +31,7 @@ import { useSelectionStore } from "../stores/use-selection-store";
import { useSettingStore } from "../stores/use-setting-store";
import { rootNavigatorRef } from "../utils/global-refs";
import Navigation from "../services/navigation";
import { useIsFeatureAvailable } from "@notesnook/common";
const RootStack = createNativeStackNavigator();
const AppStack = createNativeStackNavigator();
@@ -47,6 +48,7 @@ let ColoredNotes: any = null;
let Archive: any = null;
const AppNavigation = React.memo(
() => {
const customHomepageFeature = useIsFeatureAvailable("customHomepage");
const { colors } = useThemeColors();
const [home, setHome] = React.useState<{
name: string;
@@ -58,7 +60,7 @@ const AppNavigation = React.memo(
React.useEffect(() => {
(async () => {
if (loading) return;
if (!homepageV2) {
if (!homepageV2 || !customHomepageFeature?.isAllowed) {
setHome({
name: "Notes",
params: undefined

View File

@@ -47,7 +47,8 @@ import SettingsService from "../../services/settings";
import { useRelationStore } from "../../stores/use-relation-store";
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { getFormattedDate } from "@notesnook/common";
import { getFormattedDate, useIsFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../../components/sheets/paywall";
const ReminderModes =
Platform.OS === "ios"
@@ -107,6 +108,7 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
const [repeatFrequency, setRepeatFrequency] = useState(1);
const referencedItem = reference ? (reference as Note) : null;
const recurringReminderFeature = useIsFeatureAvailable("recurringReminders");
const title = useRef<string | undefined>(
!reminder ? referencedItem?.title : reminder?.title
@@ -304,7 +306,14 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
: "plain"
}
onPress={() => {
if (mode === "Repeat" && !PremiumService.get()) return;
if (
recurringReminderFeature &&
!recurringReminderFeature?.isAllowed
) {
PaywallSheet.present(recurringReminderFeature);
return;
}
setReminderMode(
ReminderModes[
mode as keyof typeof ReminderModes

View File

@@ -18,7 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import { isFeatureAvailable } from "@notesnook/common";
import { isImage } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { basename } from "pathe";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
@@ -38,12 +40,10 @@ import {
} from "../../../services/event-manager";
import PremiumService from "../../../services/premium";
import { useSettingStore } from "../../../stores/use-setting-store";
import { FILE_SIZE_LIMIT, IMAGE_SIZE_LIMIT } from "../../../utils/constants";
import { useUserStore } from "../../../stores/use-user-store";
import { eCloseSheet } from "../../../utils/events";
import { useTabStore } from "./use-tab-store";
import { editorController, editorState } from "./utils";
import { strings } from "@notesnook/intl";
import { useUserStore } from "../../../stores/use-user-store";
const showEncryptionSheet = (file: DocumentPickerResponse) => {
presentSheet({
@@ -92,13 +92,13 @@ const file = async (fileOptions: PickerOptions) => {
let uri = Platform.OS === "ios" ? file.fileCopyUri || file.uri : file.uri;
if ((file.size || 0) > FILE_SIZE_LIMIT) {
const featureResult = await isFeatureAvailable("fileSize", file.size || 0);
if (!featureResult.isAllowed) {
ToastManager.show({
heading: strings.fileTooLarge(),
message: strings.fileTooLargeDesc(500),
message: featureResult.error,
type: "error"
});
return;
}
if (file.copyError) {
@@ -290,14 +290,15 @@ const handleImageResponse = async (
Platform.OS === "ios" ? image.path.replace("file://", "") : image.path;
}
if (image.size > IMAGE_SIZE_LIMIT) {
const featureResult = await isFeatureAvailable("fileSize", image.size || 0);
if (!featureResult.isAllowed) {
ToastManager.show({
heading: strings.fileTooLarge(),
message: strings.fileTooLargeDesc(50),
message: featureResult.error,
type: "error"
});
return;
}
const b64 = `data:${image.mime};base64, ` + image.data;
const uri = decodeURI(image.path);
const hash = await Sodium.hashFile({

View File

@@ -80,7 +80,8 @@ import { EditorMessage, EditorProps, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store";
import { editorState, openInternalLink } from "./utils";
import AddReminder from "../../add-reminder";
import { useAreFeaturesAvailable } from "@notesnook/common";
import { isFeatureAvailable, useAreFeaturesAvailable } from "@notesnook/common";
import PaywallSheet from "../../../components/sheets/paywall";
const publishNote = async () => {
const user = useUserStore.getState().user;
@@ -445,7 +446,23 @@ export const useEditorEvents = (
referenceType: "reminder",
relationType: "from",
title: strings.dataTypesPluralCamelCase.reminder(),
onAdd: () => AddReminder.present(undefined, note)
onAdd: async () => {
const reminderFeature = await isFeatureAvailable(
"activeReminders"
);
if (!reminderFeature.isAllowed) {
ToastManager.show({
type: "info",
message: reminderFeature.error,
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(reminderFeature);
}
});
return;
}
AddReminder.present(undefined, note);
}
});
break;
case EditorEvents.newtag:
@@ -533,7 +550,9 @@ export const useEditorEvents = (
if (editor.state.current?.isFocused) {
editor.state.current.isFocused = true;
}
eSendEvent(eOpenPremiumDialog);
PaywallSheet.present(
await isFeatureAvailable(editorMessage.value.feature)
);
break;
case EditorEvents.monograph:
publishNote();

View File

@@ -44,6 +44,9 @@ import Paragraph from "../../components/ui/typography/paragraph";
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
import { Notice } from "../../components/ui/notice";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import { isFeatureAvailable } from "@notesnook/common";
import { ToastManager } from "../../services/event-manager";
import PaywallSheet from "../../components/sheets/paywall";
const {
useNotebookExpandedStore,
@@ -233,7 +236,23 @@ const LinkNotebooks = (props: NavigationProps<"LinkNotebooks">) => {
}}
button={{
icon: "plus",
onPress: () => {
onPress: async () => {
const notebooksFeature = await isFeatureAvailable(
"notebooks"
);
if (!notebooksFeature.isAllowed) {
ToastManager.show({
message: notebooksFeature.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
ToastManager.hide();
PaywallSheet.present(notebooksFeature);
}
});
return;
}
AddNotebookSheet.present(
undefined,
undefined,
@@ -376,7 +395,21 @@ const NotebookItemWrapper = React.memo(
disableExpand={disableExpand}
focused={false}
onPress={onPress}
onAddNotebook={() => {
onAddNotebook={async () => {
const notebooksFeature = await isFeatureAvailable("notebooks");
if (!notebooksFeature.isAllowed) {
ToastManager.show({
message: notebooksFeature.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
ToastManager.hide();
PaywallSheet.present(notebooksFeature);
}
});
return;
}
AddNotebookSheet.present(
undefined,
item.notebook,

View File

@@ -53,6 +53,7 @@ import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
import { Header } from "../../components/header";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import { isFeatureAvailable } from "@notesnook/common";
async function updateInitialSelectionState(items: string[]) {
const relations = await db.relations
@@ -152,11 +153,22 @@ const ManageTags = (props: NavigationProps<"ManageTags">) => {
v.and([v(`title`, "==", tag)])
);
const id = exists
? exists?.id
: await db.tags.add({
title: tag
let id = exists?.id;
if (!id) {
const tagsFeature = await isFeatureAvailable("tags");
if (!tagsFeature.isAllowed) {
ToastManager.show({
message: tagsFeature.error,
type: "info",
context: "local"
});
return;
}
id = await db.tags.add({
title: tag
});
}
if (id) {
for (const noteId of ids) {

View File

@@ -50,8 +50,10 @@ import {
} from "../../utils/notebooks";
import { DefaultAppStyles } from "../../utils/styles";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import { eSendEvent } from "../../services/event-manager";
import { eSendEvent, ToastManager } from "../../services/event-manager";
import { eUpdateNotebookRoute } from "../../utils/events";
import { isFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../../components/sheets/paywall";
const {
useNotebookExpandedStore,
@@ -239,7 +241,23 @@ export const MoveNotebook = (props: NavigationProps<"MoveNotebook">) => {
}}
button={{
icon: "plus",
onPress: () => {
onPress: async () => {
const notebooksFeature = await isFeatureAvailable(
"notebooks"
);
if (!notebooksFeature.isAllowed) {
ToastManager.show({
message: notebooksFeature.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
ToastManager.hide();
PaywallSheet.present(notebooksFeature);
}
});
return;
}
AddNotebookSheet.present(
undefined,
undefined,
@@ -387,7 +405,21 @@ const NotebookItemWrapper = React.memo(
onItemUpdate={onItemUpdate}
focused={false}
onPress={onPress}
onAddNotebook={() => {
onAddNotebook={async () => {
const notebooksFeature = await isFeatureAvailable("notebooks");
if (!notebooksFeature.isAllowed) {
ToastManager.show({
message: notebooksFeature.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
ToastManager.hide();
PaywallSheet.present(notebooksFeature);
}
});
return;
}
AddNotebookSheet.present(
undefined,
item.notebook,

View File

@@ -30,6 +30,9 @@ import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store";
import { useReminders } from "../../stores/use-reminder-store";
import AddReminder from "../add-reminder";
import { isFeatureAvailable } from "@notesnook/common";
import { ToastManager } from "../../services/event-manager";
import PaywallSheet from "../../components/sheets/paywall";
export const Reminders = ({
navigation,
@@ -67,7 +70,19 @@ export const Reminders = ({
});
}}
id={route.name}
onPressDefaultRightButton={() => {
onPressDefaultRightButton={async () => {
const reminderFeature = await isFeatureAvailable("activeReminders");
if (!reminderFeature.isAllowed) {
ToastManager.show({
type: "info",
message: reminderFeature.error,
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(reminderFeature);
}
});
return;
}
AddReminder.present();
}}
/>
@@ -83,7 +98,21 @@ export const Reminders = ({
title: strings.yourReminders(),
paragraph: strings.remindersEmpty(),
button: strings.setReminder(),
action: () => {
action: async () => {
const reminderFeature = await isFeatureAvailable(
"activeReminders"
);
if (!reminderFeature.isAllowed) {
ToastManager.show({
type: "info",
message: reminderFeature.error,
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(reminderFeature);
}
});
return;
}
AddReminder.present();
},
loading: strings.loadingReminders()
@@ -91,7 +120,19 @@ export const Reminders = ({
/>
<FloatingButton
onPress={() => {
onPress={async () => {
const reminderFeature = await isFeatureAvailable("activeReminders");
if (!reminderFeature.isAllowed) {
ToastManager.show({
type: "info",
message: reminderFeature.error,
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(reminderFeature);
}
});
return;
}
AddReminder.present();
}}
alwaysVisible

View File

@@ -31,6 +31,9 @@ import { Group } from "./group";
import { DragState, useDragState } from "./state";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../../utils/styles";
import { isFeatureAvailable } from "@notesnook/common";
import { ToastManager } from "../../../services/event-manager";
import PaywallSheet from "../../../components/sheets/paywall";
export const ConfigureToolbar = () => {
const data = useDragState((state) => state.data);
const preset = useDragState((state) => state.preset);
@@ -97,10 +100,21 @@ export const ConfigureToolbar = () => {
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
}}
proTag={item.pro}
onPress={() => {
if (item.id === "custom" && !PremiumService.get()) {
PremiumService.sheet("global");
return;
onPress={async () => {
if (item.id === "custom") {
const customToolbarPresetFeature = await isFeatureAvailable(
"customToolbarPreset"
);
if (!customToolbarPresetFeature.isAllowed) {
ToastManager.show({
message: customToolbarPresetFeature.error,
type: "info",
actionText: strings.upgrade(),
func: () =>
PaywallSheet.present(customToolbarPresetFeature)
});
return;
}
}
useDragState
.getState()

View File

@@ -39,8 +39,8 @@ interface PickerOptions<T> {
compareValue: (current: T, item: T) => boolean;
getItemKey: (item: T) => string;
options: T[];
premium?: boolean;
onCheckOptionIsPremium?: (item: T) => boolean;
isFeatureAvailable: () => Promise<boolean>;
isOptionAvailable: (item: T) => Promise<boolean>;
requiresVerification?: () => boolean;
onVerify?: () => Promise<boolean>;
}
@@ -52,8 +52,8 @@ export function SettingsPicker<T>({
compareValue,
options,
getItemKey,
premium,
onCheckOptionIsPremium = () => true,
isFeatureAvailable,
isOptionAvailable,
requiresVerification = () => false,
onVerify
}: PickerOptions<T>) {
@@ -63,19 +63,10 @@ export function SettingsPicker<T>({
const [currentValue, setCurrentValue] = useState(getValue());
const onChange = async (item: T) => {
if (premium && onCheckOptionIsPremium?.(item)) {
await PremiumService.verify(
async () => {
menuRef.current?.hide();
await updateValue(item);
setCurrentValue(item);
},
async () => {
menuRef.current?.hide();
await sleep(300);
PremiumService.sheet();
}
);
if ((await isFeatureAvailable()) && (await isOptionAvailable(item))) {
menuRef.current?.hide();
await updateValue(item);
setCurrentValue(item);
return;
}
@@ -115,7 +106,11 @@ export function SettingsPicker<T>({
anchor={
<Pressable
onPress={async () => {
if (onVerify && !(await onVerify())) return;
if (
(onVerify && !(await onVerify())) ||
!(await isFeatureAvailable())
)
return;
menuRef.current?.show();
}}
type="secondary"

View File

@@ -29,6 +29,8 @@ import { useUserStore } from "../../../stores/use-user-store";
import { MenuItemsList } from "../../../utils/menu-items";
import { verifyUserWithApplock } from "../functions";
import { strings } from "@notesnook/intl";
import { isFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../../../components/sheets/paywall";
export const FontPicker = createSettingsPicker({
getValue: () => useSettingStore.getState().settings.defaultFontFamily,
@@ -42,7 +44,9 @@ export const FontPicker = createSettingsPicker({
},
getItemKey: (item) => item.id,
options: getFonts(),
compareValue: (current, item) => current === item.id
compareValue: (current, item) => current === item.id,
isFeatureAvailable: () => true,
isOptionAvailable: () => true
});
export const HomePicker = createSettingsPicker({
@@ -61,7 +65,8 @@ export const HomePicker = createSettingsPicker({
getItemKey: (item) => item.title,
options: MenuItemsList.slice(0, MenuItemsList.length - 1),
compareValue: (current, item) => current === item.title,
premium: true
isFeatureAvailable: () => true,
isOptionAvailable: () => true
});
export const SidebarTabPicker = createSettingsPicker({
@@ -80,7 +85,21 @@ export const SidebarTabPicker = createSettingsPicker({
getItemKey: (item) => item,
options: [0, 1, 2],
compareValue: (current, item) => current === item,
premium: true
isFeatureAvailable: async () => {
const result = await isFeatureAvailable("defaultSidebarTab");
if (!result.isAllowed) {
ToastManager.show({
message: result.error,
type: "info",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(result);
}
});
}
return result.isAllowed;
},
isOptionAvailable: () => true
});
export const TrashIntervalPicker = createSettingsPicker({
@@ -98,7 +117,21 @@ export const TrashIntervalPicker = createSettingsPicker({
getItemKey: (item) => item.toString(),
options: [-1, 1, 7, 30, 365],
compareValue: (current, item) => current === item,
premium: true
isFeatureAvailable: () => true,
isOptionAvailable: async () => {
const disableTrashFeature = await isFeatureAvailable("disableTrashCleanup");
if (!disableTrashFeature.isAllowed) {
ToastManager.show({
message: disableTrashFeature.error,
type: "info",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(disableTrashFeature);
}
});
}
return disableTrashFeature.isAllowed;
}
});
export const DateFormatPicker = createSettingsPicker({
@@ -114,7 +147,9 @@ export const DateFormatPicker = createSettingsPicker({
},
getItemKey: (item) => item,
options: DATE_FORMATS,
compareValue: (current, item) => current === item
compareValue: (current, item) => current === item,
isFeatureAvailable: () => true,
isOptionAvailable: () => true
});
const TimeFormats = {
@@ -135,7 +170,9 @@ export const TimeFormatPicker = createSettingsPicker({
},
getItemKey: (item) => item,
options: TIME_FORMATS,
compareValue: (current, item) => current === item
compareValue: (current, item) => current === item,
isFeatureAvailable: () => true,
isOptionAvailable: () => true
});
export const BackupReminderPicker = createSettingsPicker({
@@ -149,16 +186,14 @@ export const BackupReminderPicker = createSettingsPicker({
getItemKey: (item) => item,
options: ["useroff", "daily", "weekly", "monthly"],
compareValue: (current, item) => current === item,
premium: true,
requiresVerification: () => {
return (
!useSettingStore.getState().settings.encryptedBackup &&
useUserStore.getState().user
);
},
onCheckOptionIsPremium: (item) => {
return item !== "useroff";
}
isFeatureAvailable: () => true,
isOptionAvailable: () => true
});
export const BackupWithAttachmentsReminderPicker = createSettingsPicker({
@@ -174,16 +209,14 @@ export const BackupWithAttachmentsReminderPicker = createSettingsPicker({
getItemKey: (item) => item,
options: ["never", "weekly", "monthly"],
compareValue: (current, item) => current === item,
premium: true,
requiresVerification: () => {
return (
!useSettingStore.getState().settings.encryptedBackup &&
useUserStore.getState().user
);
},
onCheckOptionIsPremium: (item) => {
return item !== "never";
}
isFeatureAvailable: () => true,
isOptionAvailable: () => true
});
export const ApplockTimerPicker = createSettingsPicker({
@@ -205,5 +238,7 @@ export const ApplockTimerPicker = createSettingsPicker({
compareValue: (current, item) => current === item,
onVerify: () => {
return verifyUserWithApplock();
}
},
isFeatureAvailable: () => true,
isOptionAvailable: () => true
});

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { formatBytes } from "@notesnook/common";
import { formatBytes, isFeatureAvailable } from "@notesnook/common";
import { User } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import notifee from "@notifee/react-native";
@@ -67,6 +67,7 @@ import { verifyUser, verifyUserWithApplock } from "./functions";
import { logoutUser } from "./logout";
import { SettingSection } from "./types";
import { getTimeLeft } from "./user-section";
import PaywallSheet from "../../components/sheets/paywall";
export const settingsGroups: SettingSection[] = [
{
@@ -749,7 +750,25 @@ export const settingsGroups: SettingSection[] = [
name: strings.mardownShortcuts(),
property: "markdownShortcuts",
description: strings.mardownShortcutsDesc(),
type: "switch"
type: "switch",
onVerify: async () => {
const markdownShortcuts = await isFeatureAvailable(
"markdownShortcuts"
);
if (!markdownShortcuts.isAllowed) {
ToastManager.show({
message: markdownShortcuts.error,
type: "info",
context: "local",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(markdownShortcuts);
}
});
}
return markdownShortcuts.isAllowed;
}
}
]
},
@@ -941,6 +960,19 @@ export const settingsGroups: SettingSection[] = [
SettingsService.setPrivacyScreen(SettingsService.get());
},
onVerify: async () => {
const appLockFeature = await isFeatureAvailable("appLock");
if (!appLockFeature.isAllowed) {
ToastManager.show({
message: appLockFeature.error,
type: "info",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(appLockFeature);
}
});
return;
}
const verified = await verifyUserWithApplock();
if (!verified) return false;
@@ -1244,11 +1276,23 @@ export const settingsGroups: SettingSection[] = [
description: strings.quickNoteNotificationDesc(),
property: "notifNotes",
icon: "form-textbox",
modifer: () => {
modifer: async () => {
const settings = SettingsService.get();
if (settings.notifNotes) {
Notifications.unpinQuickNote();
} else {
const createNoteFromNotificationDrawerFeature =
await isFeatureAvailable("createNoteFromNotificationDrawer");
if (!createNoteFromNotificationDrawerFeature.isAllowed) {
ToastManager.show({
message: createNoteFromNotificationDrawerFeature.error,
type: "info",
actionText: strings.upgrade(),
func: () => {
PaywallSheet.present(createNoteFromNotificationDrawerFeature);
}
});
}
Notifications.pinQuickNote(false);
}
SettingsService.set({

View File

@@ -115,8 +115,15 @@ const Tiptap = ({
outlineList: !!settings.features?.outlineList?.isAllowed,
taskList: !!settings.features?.taskList?.isAllowed
},
onPermissionDenied: () => {
post(EditorEvents.pro, undefined, tabRef.current.id, tab.session?.noteId);
onPermissionDenied: (claim) => {
post(
EditorEvents.pro,
{
feature: claim
},
tabRef.current.id,
tab.session?.noteId
);
}
});

View File

@@ -1,6 +1,6 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2025-06-21 11:05+0500\n"
"POT-Creation-Date: 2025-07-16 12:02+0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -6544,10 +6544,18 @@ msgstr "Update available"
msgid "Update now"
msgstr "Update now"
#: src/strings.ts:2491
msgid "Upgrade"
msgstr "Upgrade"
#: src/strings.ts:1917
msgid "Upgrade now"
msgstr "Upgrade now"
#: src/strings.ts:2490
msgid "Upgrade plan"
msgstr "Upgrade plan"
#: src/strings.ts:1938
msgid "Upgrade to Notesnook Pro to add colors."
msgstr "Upgrade to Notesnook Pro to add colors."

View File

@@ -1,6 +1,6 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2025-06-21 11:05+0500\n"
"POT-Creation-Date: 2025-07-16 12:02+0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -6503,10 +6503,18 @@ msgstr ""
msgid "Update now"
msgstr ""
#: src/strings.ts:2491
msgid "Upgrade"
msgstr ""
#: src/strings.ts:1917
msgid "Upgrade now"
msgstr ""
#: src/strings.ts:2490
msgid "Upgrade plan"
msgstr ""
#: src/strings.ts:1938
msgid "Upgrade to Notesnook Pro to add colors."
msgstr ""

View File

@@ -2494,5 +2494,7 @@ Use this if changes from other devices are not appearing on this device. This wi
emailConfirmedDesc: () =>
t`Your email has been confirmed. You can now securely sync your encrypted notes across all devices.`,
charactersCount: (count: number) => t`${count} characters`,
iAlreadyHaveAnAccount: () => t`I already have an account`
iAlreadyHaveAnAccount: () => t`I already have an account`,
upgradePlan: () => t`Upgrade plan`,
upgrade: () => t`Upgrade`
};