Merge pull request #8743 from 01zulfi/editor/restrict-attachment-for-notloggedin-users

editor: restrict attachment upload for not logged-in users
This commit is contained in:
Abdullah Atta
2025-10-29 12:35:53 +05:00
committed by GitHub
16 changed files with 84 additions and 12 deletions

View File

@@ -59,6 +59,7 @@ export type Settings = {
fontScale: number; fontScale: number;
markdownShortcuts: boolean; markdownShortcuts: boolean;
features: Record<any, any>; features: Record<any, any>;
loggedIn: boolean;
}; };
export type EditorProps = { export type EditorProps = {

View File

@@ -165,6 +165,7 @@ export const useEditorEvents = (
state.timeFormat state.timeFormat
]); ]);
const handleBack = useRef<NativeEventSubscription>(); const handleBack = useRef<NativeEventSubscription>();
const loggedIn = useUserStore((state) => !!state.user);
const { fontScale } = useWindowDimensions(); const { fontScale } = useWindowDimensions();
const doubleSpacedLines = useSettingStore( const doubleSpacedLines = useSettingStore(
@@ -224,7 +225,8 @@ export const useEditorEvents = (
timeFormat: db.settings?.getTimeFormat(), timeFormat: db.settings?.getTimeFormat(),
fontScale, fontScale,
markdownShortcuts, markdownShortcuts,
features features,
loggedIn
}); });
}, [ }, [
fullscreen, fullscreen,
@@ -242,7 +244,8 @@ export const useEditorEvents = (
timeFormat, timeFormat,
loading, loading,
fontScale, fontScale,
markdownShortcuts markdownShortcuts,
loggedIn
]); ]);
const onBackPress = useCallback(async () => { const onBackPress = useCallback(async () => {
@@ -556,9 +559,17 @@ export const useEditorEvents = (
if (editor.state.current?.isFocused) { if (editor.state.current?.isFocused) {
editor.state.current.isFocused = true; editor.state.current.isFocused = true;
} }
PaywallSheet.present( if (editorMessage.value.feature === "insertAttachment") {
await isFeatureAvailable(editorMessage.value.feature) ToastManager.show({
); type: "info",
message: strings.loginRequired()
});
} else {
PaywallSheet.present(
await isFeatureAvailable(editorMessage.value.feature)
);
}
break; break;
case EditorEvents.monograph: case EditorEvents.monograph:
publishNote(); publishNote();

View File

@@ -44,6 +44,7 @@ import {
useStore as useAppStore, useStore as useAppStore,
store as appstore store as appstore
} from "../../stores/app-store"; } from "../../stores/app-store";
import { useStore as useUserStore } from "../../stores/user-store";
import { useStore as useSearchStore } from "../../stores/search-store"; import { useStore as useSearchStore } from "../../stores/search-store";
import { AppEventManager, AppEvents } from "../../common/app-events"; import { AppEventManager, AppEvents } from "../../common/app-events";
import { FlexScrollContainer } from "../scroll-container"; import { FlexScrollContainer } from "../scroll-container";
@@ -77,6 +78,7 @@ import { Pane, SplitPane } from "../split-pane";
import { TITLE_BAR_HEIGHT } from "../title-bar"; import { TITLE_BAR_HEIGHT } from "../title-bar";
import { isMobile } from "../../hooks/use-mobile"; import { isMobile } from "../../hooks/use-mobile";
import { isTablet } from "../../hooks/use-tablet"; import { isTablet } from "../../hooks/use-tablet";
import { ConfirmDialog } from "../../dialogs/confirm";
const PDFPreview = React.lazy(() => import("../pdf-preview")); const PDFPreview = React.lazy(() => import("../pdf-preview"));
@@ -604,6 +606,15 @@ export function Editor(props: EditorProps) {
} }
}} }}
onInsertAttachment={async (type) => { 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 mime = type === "file" ? "*/*" : "image/*";
const attachments = await insertAttachments(mime); const attachments = await insertAttachments(mime);
const editor = useEditorManager.getState().getEditor(id)?.editor; const editor = useEditorManager.getState().getEditor(id)?.editor;

View File

@@ -51,6 +51,7 @@ import {
import { IEditor, MAX_AUTO_SAVEABLE_WORDS } from "./types"; import { IEditor, MAX_AUTO_SAVEABLE_WORDS } from "./types";
import { useEditorConfig, useToolbarConfig, useEditorManager } from "./manager"; import { useEditorConfig, useToolbarConfig, useEditorManager } from "./manager";
import { useStore as useSettingsStore } from "../../stores/setting-store"; import { useStore as useSettingsStore } from "../../stores/setting-store";
import { useStore as useUserStore } from "../../stores/user-store";
import { debounce, useAreFeaturesAvailable } from "@notesnook/common"; import { debounce, useAreFeaturesAvailable } from "@notesnook/common";
import { ScopedThemeProvider } from "../theme-provider"; import { ScopedThemeProvider } from "../theme-provider";
import { useStore as useThemeStore } from "../../stores/theme-store"; import { useStore as useThemeStore } from "../../stores/theme-store";
@@ -65,6 +66,8 @@ import { EDITOR_ZOOM } from "./common";
import { ScrollContainer } from "@notesnook/ui"; import { ScrollContainer } from "@notesnook/ui";
import { showFeatureNotAllowedToast } from "../../common/toasts"; import { showFeatureNotAllowedToast } from "../../common/toasts";
import { UpgradeDialog } from "../../dialogs/buy-dialog/upgrade-dialog"; import { UpgradeDialog } from "../../dialogs/buy-dialog/upgrade-dialog";
import { ConfirmDialog } from "../../dialogs/confirm";
import { strings } from "@notesnook/intl";
export type OnChangeHandler = ( export type OnChangeHandler = (
content: () => string, content: () => string,
@@ -185,7 +188,6 @@ function TipTap(props: TipTapProps) {
const autoSave = useRef(true); const autoSave = useRef(true);
const { toolbarConfig } = useToolbarConfig(); const { toolbarConfig } = useToolbarConfig();
const features = useAreFeaturesAvailable([ const features = useAreFeaturesAvailable([
"callout", "callout",
"outlineList", "outlineList",
@@ -196,9 +198,19 @@ function TipTap(props: TipTapProps) {
claims: { claims: {
callout: !!features?.callout?.isAllowed, callout: !!features?.callout?.isAllowed,
outlineList: !!features?.outlineList?.isAllowed, outlineList: !!features?.outlineList?.isAllowed,
taskList: !!features?.taskList?.isAllowed taskList: !!features?.taskList?.isAllowed,
insertAttachment: !!useUserStore.getState().isLoggedIn
}, },
onPermissionDenied: (claim, silent) => { onPermissionDenied: (claim, silent) => {
if (claim === "insertAttachment") {
ConfirmDialog.show({
title: strings.notLoggedIn(),
message: strings.loginToUploadAttachments(),
positiveButtonText: strings.okay()
});
return;
}
if (silent) { if (silent) {
console.log(features, features?.[claim]); console.log(features, features?.[claim]);
if (features?.[claim]) showFeatureNotAllowedToast(features[claim]); if (features?.[claim]) showFeatureNotAllowedToast(features[claim]);

View File

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

View File

@@ -84,3 +84,4 @@ navigation:
children: children:
- path: faqs/what-are-merge-conflicts.md - path: faqs/what-are-merge-conflicts.md
- path: faqs/is-there-an-eta.md - path: faqs/is-there-an-eta.md
- paht: faqs/login-to-upload-attachments.md

View File

@@ -113,7 +113,8 @@ const Tiptap = ({
claims: { claims: {
callout: !!settings.features?.callout?.isAllowed, callout: !!settings.features?.callout?.isAllowed,
outlineList: !!settings.features?.outlineList?.isAllowed, outlineList: !!settings.features?.outlineList?.isAllowed,
taskList: !!settings.features?.taskList?.isAllowed taskList: !!settings.features?.taskList?.isAllowed,
insertAttachment: settings.loggedIn
}, },
onPermissionDenied: (claim) => { onPermissionDenied: (claim) => {
post( post(

View File

@@ -34,7 +34,8 @@ const initialState = {
fontFamily: "sans-serif", fontFamily: "sans-serif",
fontSize: 16, fontSize: 16,
timeFormat: "12-hour", timeFormat: "12-hour",
dateFormat: "DD-MM-YYYY" dateFormat: "DD-MM-YYYY",
loggedIn: false
}; };
global.settingsController = { global.settingsController = {

View File

@@ -53,6 +53,7 @@ export type Settings = {
fontScale: number; fontScale: number;
markdownShortcuts: boolean; markdownShortcuts: boolean;
features: Record<any, any>; features: Record<any, any>;
loggedIn: boolean;
}; };
/* eslint-disable no-var */ /* eslint-disable no-var */

View File

@@ -23,6 +23,7 @@ import { createNodeView } from "../react/index.js";
import { AttachmentComponent } from "./component.js"; import { AttachmentComponent } from "./component.js";
import { Attachment } from "./types.js"; import { Attachment } from "./types.js";
import { tiptapKeys } from "@notesnook/common"; import { tiptapKeys } from "@notesnook/common";
import { hasPermission } from "../../types.js";
export type AttachmentType = "image" | "file" | "camera"; export type AttachmentType = "image" | "file" | "camera";
export interface AttachmentOptions { export interface AttachmentOptions {
@@ -110,6 +111,10 @@ export const AttachmentNode = Node.create<AttachmentOptions>({
insertAttachment: insertAttachment:
(attachment) => (attachment) =>
({ commands, state }) => { ({ commands, state }) => {
if (!hasPermission("insertAttachment")) {
return false;
}
const { $from } = state.selection; const { $from } = state.selection;
const maybeAttachmentNode = state.doc.nodeAt($from.pos); const maybeAttachmentNode = state.doc.nodeAt($from.pos);
if (maybeAttachmentNode?.type === this.type) { if (maybeAttachmentNode?.type === this.type) {

View File

@@ -28,7 +28,7 @@ import { createNodeView } from "../react/index.js";
import { TextDirections } from "../text-direction/index.js"; import { TextDirections } from "../text-direction/index.js";
import { ImageComponent } from "./component.js"; import { ImageComponent } from "./component.js";
import { tiptapKeys } from "@notesnook/common"; import { tiptapKeys } from "@notesnook/common";
import { DOMParser } from "@tiptap/pm/model"; import { hasPermission } from "../../types.js";
export interface ImageOptions { export interface ImageOptions {
inline: boolean; inline: boolean;
@@ -159,6 +159,10 @@ export const ImageNode = Node.create<ImageOptions>({
insertImage: insertImage:
(options) => (options) =>
({ commands, state }) => { ({ commands, state }) => {
if (!hasPermission("insertAttachment")) {
return false;
}
const { $from } = state.selection; const { $from } = state.selection;
const maybeImageNode = state.doc.nodeAt($from.pos); const maybeImageNode = state.doc.nodeAt($from.pos);
if (maybeImageNode?.type === this.type) { if (maybeImageNode?.type === this.type) {

View File

@@ -30,7 +30,8 @@ export type PermissionHandlerOptions = {
const ClaimsMap = { const ClaimsMap = {
callout: ["setCallout"] as (keyof UnionCommands)[], callout: ["setCallout"] as (keyof UnionCommands)[],
outlineList: ["toggleOutlineList"] 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) { export function usePermissionHandler(options: PermissionHandlerOptions) {

View File

@@ -25,6 +25,7 @@ import { Popup } from "../components/popup.js";
import { downloadImage, toDataURL } from "../../utils/downloader.js"; import { downloadImage, toDataURL } from "../../utils/downloader.js";
import { useToolbarStore } from "../stores/toolbar-store.js"; import { useToolbarStore } from "../stores/toolbar-store.js";
import { strings } from "@notesnook/intl"; import { strings } from "@notesnook/intl";
import { hasPermission } from "../../types.js";
export type ImageUploadPopupProps = { export type ImageUploadPopupProps = {
onInsert: (image: Partial<ImageAttributes>) => void; onInsert: (image: Partial<ImageAttributes>) => void;
@@ -46,6 +47,10 @@ export function ImageUploadPopup(props: ImageUploadPopupProps) {
title: strings.insert(), title: strings.insert(),
disabled: !url, disabled: !url,
onClick: async () => { onClick: async () => {
if (!hasPermission("insertAttachment")) {
return false;
}
setIsDownloading(true); setIsDownloading(true);
setError(undefined); setError(undefined);

View File

@@ -3699,6 +3699,10 @@ msgstr "Login successful"
msgid "Login to encrypt and sync notes" msgid "Login to encrypt and sync notes"
msgstr "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 #: src/strings.ts:541
msgid "Login to your account" msgid "Login to your account"
msgstr "Login to your account" msgstr "Login to your account"

View File

@@ -3679,6 +3679,10 @@ msgstr ""
msgid "Login to encrypt and sync notes" msgid "Login to encrypt and sync notes"
msgstr "" 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 #: src/strings.ts:541
msgid "Login to your account" msgid "Login to your account"
msgstr "" msgstr ""

View File

@@ -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.`, finishPurchaseInBrowser: () => t`Finish your purchase in the browser.`,
goBack: () => t`Go back`, goBack: () => t`Go back`,
clickToDirectlyClaimPromo: () => 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)`
}; };