From 351d412fdf507763f3dff1d4b88485381f5b51e4 Mon Sep 17 00:00:00 2001 From: 01zulfi <85733202+01zulfi@users.noreply.github.com> Date: Thu, 9 Oct 2025 12:05:59 +0500 Subject: [PATCH 1/2] editor: restrict attachment upload for not logged-in users Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> --- apps/web/src/components/editor/index.tsx | 11 +++++++++++ apps/web/src/components/editor/tiptap.tsx | 16 ++++++++++++++-- .../contents/faqs/login-to-upload-attachments.md | 8 ++++++++ docs/help/docgen.yaml | 1 + .../src/extensions/attachment/attachment.ts | 5 +++++ packages/editor/src/extensions/image/image.ts | 6 +++++- .../editor/src/hooks/use-permission-handler.ts | 3 ++- .../editor/src/toolbar/popups/image-upload.tsx | 5 +++++ packages/intl/locale/en.po | 4 ++++ packages/intl/locale/pseudo-LOCALE.po | 4 ++++ packages/intl/src/strings.ts | 4 +++- 11 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 docs/help/contents/faqs/login-to-upload-attachments.md diff --git a/apps/web/src/components/editor/index.tsx b/apps/web/src/components/editor/index.tsx index 385a43308..c2ac8d340 100644 --- a/apps/web/src/components/editor/index.tsx +++ b/apps/web/src/components/editor/index.tsx @@ -44,6 +44,7 @@ import { useStore as useAppStore, store as appstore } from "../../stores/app-store"; +import { useStore as useUserStore } from "../../stores/user-store"; import { useStore as useSearchStore } from "../../stores/search-store"; import { AppEventManager, AppEvents } from "../../common/app-events"; import { FlexScrollContainer } from "../scroll-container"; @@ -77,6 +78,7 @@ import { Pane, SplitPane } from "../split-pane"; import { TITLE_BAR_HEIGHT } from "../title-bar"; import { isMobile } from "../../hooks/use-mobile"; import { isTablet } from "../../hooks/use-tablet"; +import { ConfirmDialog } from "../../dialogs/confirm"; const PDFPreview = React.lazy(() => import("../pdf-preview")); @@ -604,6 +606,15 @@ export function Editor(props: EditorProps) { } }} onInsertAttachment={async (type) => { + if (!useUserStore.getState().isLoggedIn) { + ConfirmDialog.show({ + title: strings.notLoggedIn(), + message: strings.loginToUploadAttachments(), + positiveButtonText: strings.okay() + }); + return; + } + const mime = type === "file" ? "*/*" : "image/*"; const attachments = await insertAttachments(mime); const editor = useEditorManager.getState().getEditor(id)?.editor; diff --git a/apps/web/src/components/editor/tiptap.tsx b/apps/web/src/components/editor/tiptap.tsx index ee9c72789..3463e4526 100644 --- a/apps/web/src/components/editor/tiptap.tsx +++ b/apps/web/src/components/editor/tiptap.tsx @@ -51,6 +51,7 @@ import { import { IEditor, MAX_AUTO_SAVEABLE_WORDS } from "./types"; import { useEditorConfig, useToolbarConfig, useEditorManager } from "./manager"; import { useStore as useSettingsStore } from "../../stores/setting-store"; +import { useStore as useUserStore } from "../../stores/user-store"; import { debounce, useAreFeaturesAvailable } from "@notesnook/common"; import { ScopedThemeProvider } from "../theme-provider"; import { useStore as useThemeStore } from "../../stores/theme-store"; @@ -65,6 +66,8 @@ import { EDITOR_ZOOM } from "./common"; import { ScrollContainer } from "@notesnook/ui"; import { showFeatureNotAllowedToast } from "../../common/toasts"; import { UpgradeDialog } from "../../dialogs/buy-dialog/upgrade-dialog"; +import { ConfirmDialog } from "../../dialogs/confirm"; +import { strings } from "@notesnook/intl"; export type OnChangeHandler = ( content: () => string, @@ -185,7 +188,6 @@ function TipTap(props: TipTapProps) { const autoSave = useRef(true); const { toolbarConfig } = useToolbarConfig(); - const features = useAreFeaturesAvailable([ "callout", "outlineList", @@ -196,9 +198,19 @@ function TipTap(props: TipTapProps) { claims: { callout: !!features?.callout?.isAllowed, outlineList: !!features?.outlineList?.isAllowed, - taskList: !!features?.taskList?.isAllowed + taskList: !!features?.taskList?.isAllowed, + insertAttachment: !!useUserStore.getState().isLoggedIn }, onPermissionDenied: (claim, silent) => { + if (claim === "insertAttachment") { + ConfirmDialog.show({ + title: strings.notLoggedIn(), + message: strings.loginToUploadAttachments(), + positiveButtonText: strings.okay() + }); + return; + } + if (silent) { console.log(features, features?.[claim]); if (features?.[claim]) showFeatureNotAllowedToast(features[claim]); diff --git a/docs/help/contents/faqs/login-to-upload-attachments.md b/docs/help/contents/faqs/login-to-upload-attachments.md new file mode 100644 index 000000000..061049cf0 --- /dev/null +++ b/docs/help/contents/faqs/login-to-upload-attachments.md @@ -0,0 +1,8 @@ +--- +title: Login to upload attachments +description: We require users to be logged in to upload attachments. +--- + +# Login to upload attachments + +We require users to be logged in to upload attachments. This is because attachments are encrypted using a sub-key derived from your database encryption key. Without a login, we cannot encrypt/upload/sync attachments. diff --git a/docs/help/docgen.yaml b/docs/help/docgen.yaml index 7f4415200..4db6cbbce 100644 --- a/docs/help/docgen.yaml +++ b/docs/help/docgen.yaml @@ -84,3 +84,4 @@ navigation: children: - path: faqs/what-are-merge-conflicts.md - path: faqs/is-there-an-eta.md + - paht: faqs/login-to-upload-attachments.md diff --git a/packages/editor/src/extensions/attachment/attachment.ts b/packages/editor/src/extensions/attachment/attachment.ts index 1fb21219c..b17bd72de 100644 --- a/packages/editor/src/extensions/attachment/attachment.ts +++ b/packages/editor/src/extensions/attachment/attachment.ts @@ -23,6 +23,7 @@ import { createNodeView } from "../react/index.js"; import { AttachmentComponent } from "./component.js"; import { Attachment } from "./types.js"; import { tiptapKeys } from "@notesnook/common"; +import { hasPermission } from "../../types.js"; export type AttachmentType = "image" | "file" | "camera"; export interface AttachmentOptions { @@ -110,6 +111,10 @@ export const AttachmentNode = Node.create({ insertAttachment: (attachment) => ({ commands, state }) => { + if (!hasPermission("insertAttachment")) { + return false; + } + const { $from } = state.selection; const maybeAttachmentNode = state.doc.nodeAt($from.pos); if (maybeAttachmentNode?.type === this.type) { diff --git a/packages/editor/src/extensions/image/image.ts b/packages/editor/src/extensions/image/image.ts index 85f7a8ce7..03f367be1 100644 --- a/packages/editor/src/extensions/image/image.ts +++ b/packages/editor/src/extensions/image/image.ts @@ -28,7 +28,7 @@ import { createNodeView } from "../react/index.js"; import { TextDirections } from "../text-direction/index.js"; import { ImageComponent } from "./component.js"; import { tiptapKeys } from "@notesnook/common"; -import { DOMParser } from "@tiptap/pm/model"; +import { hasPermission } from "../../types.js"; export interface ImageOptions { inline: boolean; @@ -159,6 +159,10 @@ export const ImageNode = Node.create({ insertImage: (options) => ({ commands, state }) => { + if (!hasPermission("insertAttachment")) { + return false; + } + const { $from } = state.selection; const maybeImageNode = state.doc.nodeAt($from.pos); if (maybeImageNode?.type === this.type) { diff --git a/packages/editor/src/hooks/use-permission-handler.ts b/packages/editor/src/hooks/use-permission-handler.ts index ba47a84c3..0cab5d31c 100644 --- a/packages/editor/src/hooks/use-permission-handler.ts +++ b/packages/editor/src/hooks/use-permission-handler.ts @@ -30,7 +30,8 @@ export type PermissionHandlerOptions = { const ClaimsMap = { callout: ["setCallout"] as (keyof UnionCommands)[], outlineList: ["toggleOutlineList"] as (keyof UnionCommands)[], - taskList: ["toggleTaskList"] as (keyof UnionCommands)[] + taskList: ["toggleTaskList"] as (keyof UnionCommands)[], + insertAttachment: ["insertAttachment"] as (keyof UnionCommands)[] }; export function usePermissionHandler(options: PermissionHandlerOptions) { diff --git a/packages/editor/src/toolbar/popups/image-upload.tsx b/packages/editor/src/toolbar/popups/image-upload.tsx index 06e10532b..a22cf247e 100644 --- a/packages/editor/src/toolbar/popups/image-upload.tsx +++ b/packages/editor/src/toolbar/popups/image-upload.tsx @@ -25,6 +25,7 @@ import { Popup } from "../components/popup.js"; import { downloadImage, toDataURL } from "../../utils/downloader.js"; import { useToolbarStore } from "../stores/toolbar-store.js"; import { strings } from "@notesnook/intl"; +import { hasPermission } from "../../types.js"; export type ImageUploadPopupProps = { onInsert: (image: Partial) => void; @@ -46,6 +47,10 @@ export function ImageUploadPopup(props: ImageUploadPopupProps) { title: strings.insert(), disabled: !url, onClick: async () => { + if (!hasPermission("insertAttachment")) { + return false; + } + setIsDownloading(true); setError(undefined); diff --git a/packages/intl/locale/en.po b/packages/intl/locale/en.po index 36b1bd9dc..43a2b8f5d 100644 --- a/packages/intl/locale/en.po +++ b/packages/intl/locale/en.po @@ -3699,6 +3699,10 @@ msgstr "Login successful" msgid "Login to encrypt and sync notes" msgstr "Login to encrypt and sync notes" +#: src/strings.ts:2608 +msgid "Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)" +msgstr "Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)" + #: src/strings.ts:541 msgid "Login to your account" msgstr "Login to your account" diff --git a/packages/intl/locale/pseudo-LOCALE.po b/packages/intl/locale/pseudo-LOCALE.po index 90866c7dc..2b257cbea 100644 --- a/packages/intl/locale/pseudo-LOCALE.po +++ b/packages/intl/locale/pseudo-LOCALE.po @@ -3679,6 +3679,10 @@ msgstr "" msgid "Login to encrypt and sync notes" msgstr "" +#: src/strings.ts:2608 +msgid "Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)" +msgstr "" + #: src/strings.ts:541 msgid "Login to your account" msgstr "" diff --git a/packages/intl/src/strings.ts b/packages/intl/src/strings.ts index 0882b2e4c..f9f9e0223 100644 --- a/packages/intl/src/strings.ts +++ b/packages/intl/src/strings.ts @@ -2603,5 +2603,7 @@ Use this if changes from other devices are not appearing on this device. This wi finishPurchaseInBrowser: () => t`Finish your purchase in the browser.`, goBack: () => t`Go back`, clickToDirectlyClaimPromo: () => - t`Click here to directly claim the promotion.` + t`Click here to directly claim the promotion.`, + loginToUploadAttachments: () => + t`Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)` }; From e690973107dc71e9cb80e2dee9fdca9d31283b07 Mon Sep 17 00:00:00 2001 From: Ammar Ahmed Date: Wed, 29 Oct 2025 12:33:20 +0500 Subject: [PATCH 2/2] mobile: restrict attachment upload by logged out users --- .../mobile/app/screens/editor/tiptap/types.ts | 1 + .../editor/tiptap/use-editor-events.tsx | 21 ++++++++++++++----- .../editor-mobile/src/components/editor.tsx | 3 ++- .../editor-mobile/src/hooks/useSettings.ts | 3 ++- packages/editor-mobile/src/utils/index.ts | 1 + 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/apps/mobile/app/screens/editor/tiptap/types.ts b/apps/mobile/app/screens/editor/tiptap/types.ts index 3248d27c7..d40cf1dee 100644 --- a/apps/mobile/app/screens/editor/tiptap/types.ts +++ b/apps/mobile/app/screens/editor/tiptap/types.ts @@ -59,6 +59,7 @@ export type Settings = { fontScale: number; markdownShortcuts: boolean; features: Record; + loggedIn: boolean; }; export type EditorProps = { diff --git a/apps/mobile/app/screens/editor/tiptap/use-editor-events.tsx b/apps/mobile/app/screens/editor/tiptap/use-editor-events.tsx index 9a86c2314..11906e3d7 100644 --- a/apps/mobile/app/screens/editor/tiptap/use-editor-events.tsx +++ b/apps/mobile/app/screens/editor/tiptap/use-editor-events.tsx @@ -165,6 +165,7 @@ export const useEditorEvents = ( state.timeFormat ]); const handleBack = useRef(); + const loggedIn = useUserStore((state) => !!state.user); const { fontScale } = useWindowDimensions(); const doubleSpacedLines = useSettingStore( @@ -224,7 +225,8 @@ export const useEditorEvents = ( timeFormat: db.settings?.getTimeFormat(), fontScale, markdownShortcuts, - features + features, + loggedIn }); }, [ fullscreen, @@ -242,7 +244,8 @@ export const useEditorEvents = ( timeFormat, loading, fontScale, - markdownShortcuts + markdownShortcuts, + loggedIn ]); const onBackPress = useCallback(async () => { @@ -556,9 +559,17 @@ export const useEditorEvents = ( if (editor.state.current?.isFocused) { editor.state.current.isFocused = true; } - PaywallSheet.present( - await isFeatureAvailable(editorMessage.value.feature) - ); + if (editorMessage.value.feature === "insertAttachment") { + ToastManager.show({ + type: "info", + message: strings.loginRequired() + }); + } else { + PaywallSheet.present( + await isFeatureAvailable(editorMessage.value.feature) + ); + } + break; case EditorEvents.monograph: publishNote(); diff --git a/packages/editor-mobile/src/components/editor.tsx b/packages/editor-mobile/src/components/editor.tsx index 917052441..9c80c9315 100644 --- a/packages/editor-mobile/src/components/editor.tsx +++ b/packages/editor-mobile/src/components/editor.tsx @@ -113,7 +113,8 @@ const Tiptap = ({ claims: { callout: !!settings.features?.callout?.isAllowed, outlineList: !!settings.features?.outlineList?.isAllowed, - taskList: !!settings.features?.taskList?.isAllowed + taskList: !!settings.features?.taskList?.isAllowed, + insertAttachment: settings.loggedIn }, onPermissionDenied: (claim) => { post( diff --git a/packages/editor-mobile/src/hooks/useSettings.ts b/packages/editor-mobile/src/hooks/useSettings.ts index 1ced7d836..584aca477 100644 --- a/packages/editor-mobile/src/hooks/useSettings.ts +++ b/packages/editor-mobile/src/hooks/useSettings.ts @@ -34,7 +34,8 @@ const initialState = { fontFamily: "sans-serif", fontSize: 16, timeFormat: "12-hour", - dateFormat: "DD-MM-YYYY" + dateFormat: "DD-MM-YYYY", + loggedIn: false }; global.settingsController = { diff --git a/packages/editor-mobile/src/utils/index.ts b/packages/editor-mobile/src/utils/index.ts index 6ffcb8e9b..6ffe87e85 100644 --- a/packages/editor-mobile/src/utils/index.ts +++ b/packages/editor-mobile/src/utils/index.ts @@ -53,6 +53,7 @@ export type Settings = { fontScale: number; markdownShortcuts: boolean; features: Record; + loggedIn: boolean; }; /* eslint-disable no-var */