mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-21 14:09:34 +01:00
feat: add permission handling
This commit is contained in:
@@ -3,7 +3,8 @@ import { NodeView, Decoration, DecorationSource } from "prosemirror-view";
|
|||||||
import { Node as PMNode } from "prosemirror-model";
|
import { Node as PMNode } from "prosemirror-model";
|
||||||
import { PortalProviderAPI } from "./react-portal-provider";
|
import { PortalProviderAPI } from "./react-portal-provider";
|
||||||
import { ReactNodeViewProps, ReactNodeViewOptions, GetPosNode, ForwardRef, ContentDOM } from "./types";
|
import { ReactNodeViewProps, ReactNodeViewOptions, GetPosNode, ForwardRef, ContentDOM } from "./types";
|
||||||
import { Editor, NodeViewRendererProps } from "@tiptap/core";
|
import { NodeViewRendererProps } from "@tiptap/core";
|
||||||
|
import { Editor } from "../../types";
|
||||||
export declare class ReactNodeView<P extends ReactNodeViewProps> implements NodeView {
|
export declare class ReactNodeView<P extends ReactNodeViewProps> implements NodeView {
|
||||||
protected readonly editor: Editor;
|
protected readonly editor: Editor;
|
||||||
protected readonly getPos: GetPosNode;
|
protected readonly getPos: GetPosNode;
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import React from "react";
|
|||||||
import { Node as PMNode } from "prosemirror-model";
|
import { Node as PMNode } from "prosemirror-model";
|
||||||
import { ReactNodeViewOptions, GetPosNode, SelectionBasedReactNodeViewProps, ForwardRef } from "./types";
|
import { ReactNodeViewOptions, GetPosNode, SelectionBasedReactNodeViewProps, ForwardRef } from "./types";
|
||||||
import { ReactNodeView } from "./react-node-view";
|
import { ReactNodeView } from "./react-node-view";
|
||||||
import { Editor, NodeViewRendererProps } from "@tiptap/core";
|
import { NodeViewRendererProps } from "@tiptap/core";
|
||||||
|
import { Editor } from "../../types";
|
||||||
/**
|
/**
|
||||||
* A ReactNodeView that handles React components sensitive
|
* A ReactNodeView that handles React components sensitive
|
||||||
* to selection changes.
|
* to selection changes.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
import { Node as PMNode, Attrs } from "prosemirror-model";
|
import { Node as PMNode, Attrs } from "prosemirror-model";
|
||||||
export interface ReactNodeProps {
|
export interface ReactNodeProps {
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
import { SelectionBasedReactNodeViewProps } from "../react";
|
import { SelectionBasedReactNodeViewProps } from "../react";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
import { NodeView } from "prosemirror-view";
|
import { NodeView } from "prosemirror-view";
|
||||||
export declare function TableComponent(props: SelectionBasedReactNodeViewProps): JSX.Element;
|
export declare function TableComponent(props: SelectionBasedReactNodeViewProps): JSX.Element;
|
||||||
export declare function TableNodeView(editor: Editor): NodeView;
|
export declare function TableNodeView(editor: Editor): NodeView;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { EditorOptions } from "@tiptap/core";
|
import { EditorOptions } from "@tiptap/core";
|
||||||
import { DependencyList } from "react";
|
import { DependencyList } from "react";
|
||||||
import { Editor as EditorType } from "../types";
|
import { Editor } from "../types";
|
||||||
export declare const useEditor: (options?: Partial<EditorOptions>, deps?: DependencyList) => EditorType | null;
|
export declare const useEditor: (options?: Partial<EditorOptions>, deps?: DependencyList) => Editor | null;
|
||||||
|
|||||||
17
packages/editor/dist/cjs/hooks/useEditor.js
vendored
17
packages/editor/dist/cjs/hooks/useEditor.js
vendored
@@ -1,8 +1,8 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.useEditor = void 0;
|
exports.useEditor = void 0;
|
||||||
const core_1 = require("@tiptap/core");
|
|
||||||
const react_1 = require("react");
|
const react_1 = require("react");
|
||||||
|
const types_1 = require("../types");
|
||||||
function useForceUpdate() {
|
function useForceUpdate() {
|
||||||
const [, setValue] = (0, react_1.useState)(0);
|
const [, setValue] = (0, react_1.useState)(0);
|
||||||
return () => setValue((value) => value + 1);
|
return () => setValue((value) => value + 1);
|
||||||
@@ -13,7 +13,7 @@ const useEditor = (options = {}, deps = []) => {
|
|||||||
const editorRef = (0, react_1.useRef)(editor);
|
const editorRef = (0, react_1.useRef)(editor);
|
||||||
(0, react_1.useEffect)(() => {
|
(0, react_1.useEffect)(() => {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
const instance = new core_1.Editor(options);
|
const instance = new types_1.Editor(options);
|
||||||
setEditor(instance);
|
setEditor(instance);
|
||||||
instance.on("transaction", () => {
|
instance.on("transaction", () => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@@ -31,10 +31,21 @@ const useEditor = (options = {}, deps = []) => {
|
|||||||
}, deps);
|
}, deps);
|
||||||
(0, react_1.useEffect)(() => {
|
(0, react_1.useEffect)(() => {
|
||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
if (editor && !editor.current)
|
if (!editor)
|
||||||
|
return;
|
||||||
|
if (!editor.current) {
|
||||||
Object.defineProperty(editor, "current", {
|
Object.defineProperty(editor, "current", {
|
||||||
get: () => editorRef.current,
|
get: () => editorRef.current,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
// if (!editor.executor) {
|
||||||
|
// Object.defineProperty(editor, "executor", {
|
||||||
|
// get: () => (id?: string) => {
|
||||||
|
// console.log(id);
|
||||||
|
// return editorRef.current;
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// }
|
||||||
}, [editor]);
|
}, [editor]);
|
||||||
(0, react_1.useEffect)(() => {
|
(0, react_1.useEffect)(() => {
|
||||||
// this is required for the drag/drop to work properly
|
// this is required for the drag/drop to work properly
|
||||||
|
|||||||
7
packages/editor/dist/cjs/hooks/usePermissionHandler.d.ts
vendored
Normal file
7
packages/editor/dist/cjs/hooks/usePermissionHandler.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { UnionCommands } from "@tiptap/core";
|
||||||
|
export declare type Claims = "premium";
|
||||||
|
export declare type PermissionHandlerOptions = {
|
||||||
|
claims: Record<Claims, boolean>;
|
||||||
|
onPermissionDenied: (claim: Claims, id: keyof UnionCommands) => void;
|
||||||
|
};
|
||||||
|
export declare function usePermissionHandler(options: PermissionHandlerOptions): void;
|
||||||
32
packages/editor/dist/cjs/hooks/usePermissionHandler.js
vendored
Normal file
32
packages/editor/dist/cjs/hooks/usePermissionHandler.js
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.usePermissionHandler = void 0;
|
||||||
|
const react_1 = require("react");
|
||||||
|
const ClaimsMap = {
|
||||||
|
premium: ["insertImage"],
|
||||||
|
};
|
||||||
|
function usePermissionHandler(options) {
|
||||||
|
const { claims, onPermissionDenied } = options;
|
||||||
|
(0, react_1.useEffect)(() => {
|
||||||
|
function onPermissionRequested(ev) {
|
||||||
|
const { detail: { id }, } = ev;
|
||||||
|
for (const key in ClaimsMap) {
|
||||||
|
const claim = key;
|
||||||
|
const commands = ClaimsMap[claim];
|
||||||
|
if (commands.indexOf(id) <= -1)
|
||||||
|
continue;
|
||||||
|
if (claims[claim])
|
||||||
|
continue;
|
||||||
|
onPermissionDenied(claim, id);
|
||||||
|
ev.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
window.addEventListener("permissionrequest", onPermissionRequested);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("permissionrequest", onPermissionRequested);
|
||||||
|
};
|
||||||
|
}, [claims, onPermissionDenied]);
|
||||||
|
}
|
||||||
|
exports.usePermissionHandler = usePermissionHandler;
|
||||||
3
packages/editor/dist/cjs/index.d.ts
vendored
3
packages/editor/dist/cjs/index.d.ts
vendored
@@ -4,12 +4,13 @@ import Toolbar from "./toolbar";
|
|||||||
import { Theme } from "@notesnook/theme";
|
import { Theme } from "@notesnook/theme";
|
||||||
import { AttachmentOptions } from "./extensions/attachment";
|
import { AttachmentOptions } from "./extensions/attachment";
|
||||||
import { EditorOptions } from "@tiptap/core";
|
import { EditorOptions } from "@tiptap/core";
|
||||||
|
import { usePermissionHandler } from "./hooks/use-permission-handler";
|
||||||
declare type TiptapOptions = EditorOptions & AttachmentOptions & {
|
declare type TiptapOptions = EditorOptions & AttachmentOptions & {
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
};
|
};
|
||||||
declare const useTiptap: (options?: Partial<TiptapOptions>, deps?: import("react").DependencyList) => import("./types").Editor | null;
|
declare const useTiptap: (options?: Partial<TiptapOptions>, deps?: import("react").DependencyList) => import("./types").Editor | null;
|
||||||
export { useTiptap, Toolbar };
|
export { useTiptap, Toolbar, usePermissionHandler };
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
export * from "./extensions/react";
|
export * from "./extensions/react";
|
||||||
export * from "./toolbar";
|
export * from "./toolbar";
|
||||||
|
|||||||
10
packages/editor/dist/cjs/index.js
vendored
10
packages/editor/dist/cjs/index.js
vendored
@@ -28,7 +28,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.Toolbar = exports.useTiptap = void 0;
|
exports.usePermissionHandler = exports.Toolbar = exports.useTiptap = void 0;
|
||||||
require("./extensions");
|
require("./extensions");
|
||||||
const extension_character_count_1 = __importDefault(require("@tiptap/extension-character-count"));
|
const extension_character_count_1 = __importDefault(require("@tiptap/extension-character-count"));
|
||||||
const extension_placeholder_1 = __importDefault(require("@tiptap/extension-placeholder"));
|
const extension_placeholder_1 = __importDefault(require("@tiptap/extension-placeholder"));
|
||||||
@@ -70,6 +70,8 @@ const outlinelistitem_1 = require("./extensions/outlinelistitem");
|
|||||||
const table_1 = require("./extensions/table");
|
const table_1 = require("./extensions/table");
|
||||||
const toolbarstore_1 = require("./toolbar/stores/toolbarstore");
|
const toolbarstore_1 = require("./toolbar/stores/toolbarstore");
|
||||||
const useEditor_1 = require("./hooks/useEditor");
|
const useEditor_1 = require("./hooks/useEditor");
|
||||||
|
const usePermissionHandler_1 = require("./hooks/usePermissionHandler");
|
||||||
|
Object.defineProperty(exports, "usePermissionHandler", { enumerable: true, get: function () { return usePermissionHandler_1.usePermissionHandler; } });
|
||||||
prosemirror_view_1.EditorView.prototype.updateState = function updateState(state) {
|
prosemirror_view_1.EditorView.prototype.updateState = function updateState(state) {
|
||||||
if (!this.docView)
|
if (!this.docView)
|
||||||
return; // This prevents the matchesNode error on hot reloads
|
return; // This prevents the matchesNode error on hot reloads
|
||||||
@@ -79,6 +81,9 @@ const useTiptap = (options = {}, deps = []) => {
|
|||||||
const { theme, isMobile, onDownloadAttachment, onOpenAttachmentPicker } = options, restOptions = __rest(options, ["theme", "isMobile", "onDownloadAttachment", "onOpenAttachmentPicker"]);
|
const { theme, isMobile, onDownloadAttachment, onOpenAttachmentPicker } = options, restOptions = __rest(options, ["theme", "isMobile", "onDownloadAttachment", "onOpenAttachmentPicker"]);
|
||||||
const PortalProviderAPI = (0, react_2.usePortalProvider)();
|
const PortalProviderAPI = (0, react_2.usePortalProvider)();
|
||||||
const setIsMobile = (0, toolbarstore_1.useToolbarStore)((store) => store.setIsMobile);
|
const setIsMobile = (0, toolbarstore_1.useToolbarStore)((store) => store.setIsMobile);
|
||||||
|
(0, react_1.useEffect)(() => {
|
||||||
|
setIsMobile(isMobile || false);
|
||||||
|
}, [isMobile]);
|
||||||
const defaultOptions = (0, react_1.useMemo)(() => ({
|
const defaultOptions = (0, react_1.useMemo)(() => ({
|
||||||
extensions: [
|
extensions: [
|
||||||
react_2.NodeViewSelectionNotifier,
|
react_2.NodeViewSelectionNotifier,
|
||||||
@@ -159,9 +164,6 @@ const useTiptap = (options = {}, deps = []) => {
|
|||||||
isMobile,
|
isMobile,
|
||||||
]);
|
]);
|
||||||
const editor = (0, useEditor_1.useEditor)(Object.assign(Object.assign({}, defaultOptions), restOptions), deps);
|
const editor = (0, useEditor_1.useEditor)(Object.assign(Object.assign({}, defaultOptions), restOptions), deps);
|
||||||
(0, react_1.useEffect)(() => {
|
|
||||||
setIsMobile(isMobile || false);
|
|
||||||
}, [isMobile]);
|
|
||||||
return editor;
|
return editor;
|
||||||
};
|
};
|
||||||
exports.useTiptap = useTiptap;
|
exports.useTiptap = useTiptap;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
import { ToolbarGroupDefinition, ToolButtonVariant } from "../types";
|
import { ToolbarGroupDefinition, ToolButtonVariant } from "../types";
|
||||||
import { FlexProps } from "rebass";
|
import { FlexProps } from "rebass";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
import { NodeWithOffset } from "../utils/prosemirror";
|
import { NodeWithOffset } from "../utils/prosemirror";
|
||||||
export declare type ToolbarGroupProps = FlexProps & {
|
export declare type ToolbarGroupProps = FlexProps & {
|
||||||
tools: ToolbarGroupDefinition;
|
tools: ToolbarGroupDefinition;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
import { ImageAlignmentOptions, ImageSizeOptions } from "../../extensions/image";
|
import { ImageAlignmentOptions, ImageSizeOptions } from "../../extensions/image";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
export declare type ImagePropertiesProps = ImageSizeOptions & ImageAlignmentOptions & {
|
export declare type ImagePropertiesProps = ImageSizeOptions & ImageAlignmentOptions & {
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
export declare type SearchReplacePopupProps = {
|
export declare type SearchReplacePopupProps = {
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
import { Theme } from "@notesnook/theme";
|
import { Theme } from "@notesnook/theme";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../types";
|
||||||
import { ToolbarLocation } from "./stores/toolbar-store";
|
import { ToolbarLocation } from "./stores/toolbar-store";
|
||||||
import { ToolbarDefinition } from "./types";
|
import { ToolbarDefinition } from "./types";
|
||||||
declare type ToolbarProps = {
|
declare type ToolbarProps = {
|
||||||
|
|||||||
@@ -214,7 +214,8 @@ const uploadImageFromURLMobile = (editor) => ({
|
|||||||
type: "popup",
|
type: "popup",
|
||||||
component: ({ onClick }) => ((0, jsx_runtime_1.jsx)(imageupload_1.ImageUploadPopup, { onInsert: (image) => {
|
component: ({ onClick }) => ((0, jsx_runtime_1.jsx)(imageupload_1.ImageUploadPopup, { onInsert: (image) => {
|
||||||
var _a;
|
var _a;
|
||||||
(_a = editor.current) === null || _a === void 0 ? void 0 : _a.chain().focus().insertImage(image).run();
|
(_a = editor
|
||||||
|
.requestPermission("insertImage")) === null || _a === void 0 ? void 0 : _a.chain().focus().insertImage(image).run();
|
||||||
onClick === null || onClick === void 0 ? void 0 : onClick();
|
onClick === null || onClick === void 0 ? void 0 : onClick();
|
||||||
}, onClose: () => {
|
}, onClose: () => {
|
||||||
onClick === null || onClick === void 0 ? void 0 : onClick();
|
onClick === null || onClick === void 0 ? void 0 : onClick();
|
||||||
@@ -229,13 +230,12 @@ const uploadImageFromURL = (editor) => ({
|
|||||||
title: "Attach from URL",
|
title: "Attach from URL",
|
||||||
icon: "link",
|
icon: "link",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (!editor)
|
|
||||||
return;
|
|
||||||
(0, popuppresenter_1.showPopup)({
|
(0, popuppresenter_1.showPopup)({
|
||||||
theme: editor.storage.theme,
|
theme: editor.storage.theme,
|
||||||
popup: (hide) => ((0, jsx_runtime_1.jsx)(imageupload_1.ImageUploadPopup, { onInsert: (image) => {
|
popup: (hide) => ((0, jsx_runtime_1.jsx)(imageupload_1.ImageUploadPopup, { onInsert: (image) => {
|
||||||
var _a;
|
var _a;
|
||||||
(_a = editor.current) === null || _a === void 0 ? void 0 : _a.chain().focus().insertImage(image).run();
|
(_a = editor
|
||||||
|
.requestPermission("insertImage")) === null || _a === void 0 ? void 0 : _a.chain().focus().insertImage(image).run();
|
||||||
hide();
|
hide();
|
||||||
}, onClose: hide })),
|
}, onClose: hide })),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
import { MenuButton } from "../../components/menu/types";
|
import { MenuButton } from "../../components/menu/types";
|
||||||
import { ToolProps } from "../types";
|
import { ToolProps } from "../types";
|
||||||
export declare function menuButtonToTool(constructItem: (editor: Editor) => MenuButton): (props: ToolProps) => JSX.Element;
|
export declare function menuButtonToTool(constructItem: (editor: Editor) => MenuButton): (props: ToolProps) => JSX.Element;
|
||||||
|
|||||||
15
packages/editor/dist/cjs/types.d.ts
vendored
15
packages/editor/dist/cjs/types.d.ts
vendored
@@ -1,9 +1,20 @@
|
|||||||
import { Editor as TiptapEditor } from "@tiptap/core";
|
import { UnionCommands, Editor as TiptapEditor } from "@tiptap/core";
|
||||||
export interface Editor extends TiptapEditor {
|
export interface PermissionRequestEvent extends CustomEvent<{
|
||||||
|
id: keyof UnionCommands;
|
||||||
|
}> {
|
||||||
|
}
|
||||||
|
export declare class Editor extends TiptapEditor {
|
||||||
/**
|
/**
|
||||||
* Use this to get the latest instance of the editor.
|
* Use this to get the latest instance of the editor.
|
||||||
* This is required to reduce unnecessary rerenders of
|
* This is required to reduce unnecessary rerenders of
|
||||||
* toolbar elements.
|
* toolbar elements.
|
||||||
*/
|
*/
|
||||||
current?: TiptapEditor;
|
current?: TiptapEditor;
|
||||||
|
/**
|
||||||
|
* Request permission before executing a command to make sure user
|
||||||
|
* is allowed to perform the action.
|
||||||
|
* @param id the command id to get permission for
|
||||||
|
* @returns latest editor instance
|
||||||
|
*/
|
||||||
|
requestPermission(id: keyof UnionCommands): TiptapEditor | undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
20
packages/editor/dist/cjs/types.js
vendored
20
packages/editor/dist/cjs/types.js
vendored
@@ -1,2 +1,22 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.Editor = void 0;
|
||||||
|
const core_1 = require("@tiptap/core");
|
||||||
|
class Editor extends core_1.Editor {
|
||||||
|
/**
|
||||||
|
* Request permission before executing a command to make sure user
|
||||||
|
* is allowed to perform the action.
|
||||||
|
* @param id the command id to get permission for
|
||||||
|
* @returns latest editor instance
|
||||||
|
*/
|
||||||
|
requestPermission(id) {
|
||||||
|
const event = new CustomEvent("permissionrequest", {
|
||||||
|
detail: { id },
|
||||||
|
cancelable: true,
|
||||||
|
});
|
||||||
|
if (!window.dispatchEvent(event))
|
||||||
|
return undefined;
|
||||||
|
return this.current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Editor = Editor;
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { NodeView, Decoration, DecorationSource } from "prosemirror-view";
|
|||||||
import { Node as PMNode } from "prosemirror-model";
|
import { Node as PMNode } from "prosemirror-model";
|
||||||
import { PortalProviderAPI } from "./react-portal-provider";
|
import { PortalProviderAPI } from "./react-portal-provider";
|
||||||
import { ReactNodeViewProps, ReactNodeViewOptions, GetPosNode, ForwardRef, ContentDOM } from "./types";
|
import { ReactNodeViewProps, ReactNodeViewOptions, GetPosNode, ForwardRef, ContentDOM } from "./types";
|
||||||
import { Editor, NodeViewRendererProps } from "@tiptap/core";
|
import { NodeViewRendererProps } from "@tiptap/core";
|
||||||
|
import { Editor } from "../../types";
|
||||||
export declare class ReactNodeView<P extends ReactNodeViewProps> implements NodeView {
|
export declare class ReactNodeView<P extends ReactNodeViewProps> implements NodeView {
|
||||||
protected readonly editor: Editor;
|
protected readonly editor: Editor;
|
||||||
protected readonly getPos: GetPosNode;
|
protected readonly getPos: GetPosNode;
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import React from "react";
|
|||||||
import { Node as PMNode } from "prosemirror-model";
|
import { Node as PMNode } from "prosemirror-model";
|
||||||
import { ReactNodeViewOptions, GetPosNode, SelectionBasedReactNodeViewProps, ForwardRef } from "./types";
|
import { ReactNodeViewOptions, GetPosNode, SelectionBasedReactNodeViewProps, ForwardRef } from "./types";
|
||||||
import { ReactNodeView } from "./react-node-view";
|
import { ReactNodeView } from "./react-node-view";
|
||||||
import { Editor, NodeViewRendererProps } from "@tiptap/core";
|
import { NodeViewRendererProps } from "@tiptap/core";
|
||||||
|
import { Editor } from "../../types";
|
||||||
/**
|
/**
|
||||||
* A ReactNodeView that handles React components sensitive
|
* A ReactNodeView that handles React components sensitive
|
||||||
* to selection changes.
|
* to selection changes.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
import { Node as PMNode, Attrs } from "prosemirror-model";
|
import { Node as PMNode, Attrs } from "prosemirror-model";
|
||||||
export interface ReactNodeProps {
|
export interface ReactNodeProps {
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
import { SelectionBasedReactNodeViewProps } from "../react";
|
import { SelectionBasedReactNodeViewProps } from "../react";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
import { NodeView } from "prosemirror-view";
|
import { NodeView } from "prosemirror-view";
|
||||||
export declare function TableComponent(props: SelectionBasedReactNodeViewProps): JSX.Element;
|
export declare function TableComponent(props: SelectionBasedReactNodeViewProps): JSX.Element;
|
||||||
export declare function TableNodeView(editor: Editor): NodeView;
|
export declare function TableNodeView(editor: Editor): NodeView;
|
||||||
|
|||||||
4
packages/editor/dist/es/hooks/useEditor.d.ts
vendored
4
packages/editor/dist/es/hooks/useEditor.d.ts
vendored
@@ -1,4 +1,4 @@
|
|||||||
import { EditorOptions } from "@tiptap/core";
|
import { EditorOptions } from "@tiptap/core";
|
||||||
import { DependencyList } from "react";
|
import { DependencyList } from "react";
|
||||||
import { Editor as EditorType } from "../types";
|
import { Editor } from "../types";
|
||||||
export declare const useEditor: (options?: Partial<EditorOptions>, deps?: DependencyList) => EditorType | null;
|
export declare const useEditor: (options?: Partial<EditorOptions>, deps?: DependencyList) => Editor | null;
|
||||||
|
|||||||
15
packages/editor/dist/es/hooks/useEditor.js
vendored
15
packages/editor/dist/es/hooks/useEditor.js
vendored
@@ -1,5 +1,5 @@
|
|||||||
import { Editor } from "@tiptap/core";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { Editor } from "../types";
|
||||||
function useForceUpdate() {
|
function useForceUpdate() {
|
||||||
const [, setValue] = useState(0);
|
const [, setValue] = useState(0);
|
||||||
return () => setValue((value) => value + 1);
|
return () => setValue((value) => value + 1);
|
||||||
@@ -28,10 +28,21 @@ export const useEditor = (options = {}, deps = []) => {
|
|||||||
}, deps);
|
}, deps);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
if (editor && !editor.current)
|
if (!editor)
|
||||||
|
return;
|
||||||
|
if (!editor.current) {
|
||||||
Object.defineProperty(editor, "current", {
|
Object.defineProperty(editor, "current", {
|
||||||
get: () => editorRef.current,
|
get: () => editorRef.current,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
// if (!editor.executor) {
|
||||||
|
// Object.defineProperty(editor, "executor", {
|
||||||
|
// get: () => (id?: string) => {
|
||||||
|
// console.log(id);
|
||||||
|
// return editorRef.current;
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// }
|
||||||
}, [editor]);
|
}, [editor]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// this is required for the drag/drop to work properly
|
// this is required for the drag/drop to work properly
|
||||||
|
|||||||
7
packages/editor/dist/es/hooks/usePermissionHandler.d.ts
vendored
Normal file
7
packages/editor/dist/es/hooks/usePermissionHandler.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { UnionCommands } from "@tiptap/core";
|
||||||
|
export declare type Claims = "premium";
|
||||||
|
export declare type PermissionHandlerOptions = {
|
||||||
|
claims: Record<Claims, boolean>;
|
||||||
|
onPermissionDenied: (claim: Claims, id: keyof UnionCommands) => void;
|
||||||
|
};
|
||||||
|
export declare function usePermissionHandler(options: PermissionHandlerOptions): void;
|
||||||
28
packages/editor/dist/es/hooks/usePermissionHandler.js
vendored
Normal file
28
packages/editor/dist/es/hooks/usePermissionHandler.js
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
const ClaimsMap = {
|
||||||
|
premium: ["insertImage"],
|
||||||
|
};
|
||||||
|
export function usePermissionHandler(options) {
|
||||||
|
const { claims, onPermissionDenied } = options;
|
||||||
|
useEffect(() => {
|
||||||
|
function onPermissionRequested(ev) {
|
||||||
|
const { detail: { id }, } = ev;
|
||||||
|
for (const key in ClaimsMap) {
|
||||||
|
const claim = key;
|
||||||
|
const commands = ClaimsMap[claim];
|
||||||
|
if (commands.indexOf(id) <= -1)
|
||||||
|
continue;
|
||||||
|
if (claims[claim])
|
||||||
|
continue;
|
||||||
|
onPermissionDenied(claim, id);
|
||||||
|
ev.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
window.addEventListener("permissionrequest", onPermissionRequested);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("permissionrequest", onPermissionRequested);
|
||||||
|
};
|
||||||
|
}, [claims, onPermissionDenied]);
|
||||||
|
}
|
||||||
3
packages/editor/dist/es/index.d.ts
vendored
3
packages/editor/dist/es/index.d.ts
vendored
@@ -4,12 +4,13 @@ import Toolbar from "./toolbar";
|
|||||||
import { Theme } from "@notesnook/theme";
|
import { Theme } from "@notesnook/theme";
|
||||||
import { AttachmentOptions } from "./extensions/attachment";
|
import { AttachmentOptions } from "./extensions/attachment";
|
||||||
import { EditorOptions } from "@tiptap/core";
|
import { EditorOptions } from "@tiptap/core";
|
||||||
|
import { usePermissionHandler } from "./hooks/use-permission-handler";
|
||||||
declare type TiptapOptions = EditorOptions & AttachmentOptions & {
|
declare type TiptapOptions = EditorOptions & AttachmentOptions & {
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
};
|
};
|
||||||
declare const useTiptap: (options?: Partial<TiptapOptions>, deps?: import("react").DependencyList) => import("./types").Editor | null;
|
declare const useTiptap: (options?: Partial<TiptapOptions>, deps?: import("react").DependencyList) => import("./types").Editor | null;
|
||||||
export { useTiptap, Toolbar };
|
export { useTiptap, Toolbar, usePermissionHandler };
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
export * from "./extensions/react";
|
export * from "./extensions/react";
|
||||||
export * from "./toolbar";
|
export * from "./toolbar";
|
||||||
|
|||||||
9
packages/editor/dist/es/index.js
vendored
9
packages/editor/dist/es/index.js
vendored
@@ -49,6 +49,7 @@ import { OutlineListItem } from "./extensions/outline-list-item";
|
|||||||
import { Table } from "./extensions/table";
|
import { Table } from "./extensions/table";
|
||||||
import { useToolbarStore } from "./toolbar/stores/toolbar-store";
|
import { useToolbarStore } from "./toolbar/stores/toolbar-store";
|
||||||
import { useEditor } from "./hooks/use-editor";
|
import { useEditor } from "./hooks/use-editor";
|
||||||
|
import { usePermissionHandler } from "./hooks/use-permission-handler";
|
||||||
EditorView.prototype.updateState = function updateState(state) {
|
EditorView.prototype.updateState = function updateState(state) {
|
||||||
if (!this.docView)
|
if (!this.docView)
|
||||||
return; // This prevents the matchesNode error on hot reloads
|
return; // This prevents the matchesNode error on hot reloads
|
||||||
@@ -58,6 +59,9 @@ const useTiptap = (options = {}, deps = []) => {
|
|||||||
const { theme, isMobile, onDownloadAttachment, onOpenAttachmentPicker } = options, restOptions = __rest(options, ["theme", "isMobile", "onDownloadAttachment", "onOpenAttachmentPicker"]);
|
const { theme, isMobile, onDownloadAttachment, onOpenAttachmentPicker } = options, restOptions = __rest(options, ["theme", "isMobile", "onDownloadAttachment", "onOpenAttachmentPicker"]);
|
||||||
const PortalProviderAPI = usePortalProvider();
|
const PortalProviderAPI = usePortalProvider();
|
||||||
const setIsMobile = useToolbarStore((store) => store.setIsMobile);
|
const setIsMobile = useToolbarStore((store) => store.setIsMobile);
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMobile(isMobile || false);
|
||||||
|
}, [isMobile]);
|
||||||
const defaultOptions = useMemo(() => ({
|
const defaultOptions = useMemo(() => ({
|
||||||
extensions: [
|
extensions: [
|
||||||
NodeViewSelectionNotifier,
|
NodeViewSelectionNotifier,
|
||||||
@@ -138,12 +142,9 @@ const useTiptap = (options = {}, deps = []) => {
|
|||||||
isMobile,
|
isMobile,
|
||||||
]);
|
]);
|
||||||
const editor = useEditor(Object.assign(Object.assign({}, defaultOptions), restOptions), deps);
|
const editor = useEditor(Object.assign(Object.assign({}, defaultOptions), restOptions), deps);
|
||||||
useEffect(() => {
|
|
||||||
setIsMobile(isMobile || false);
|
|
||||||
}, [isMobile]);
|
|
||||||
return editor;
|
return editor;
|
||||||
};
|
};
|
||||||
export { useTiptap, Toolbar };
|
export { useTiptap, Toolbar, usePermissionHandler };
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
export * from "./extensions/react";
|
export * from "./extensions/react";
|
||||||
export * from "./toolbar";
|
export * from "./toolbar";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
import { ToolbarGroupDefinition, ToolButtonVariant } from "../types";
|
import { ToolbarGroupDefinition, ToolButtonVariant } from "../types";
|
||||||
import { FlexProps } from "rebass";
|
import { FlexProps } from "rebass";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
import { NodeWithOffset } from "../utils/prosemirror";
|
import { NodeWithOffset } from "../utils/prosemirror";
|
||||||
export declare type ToolbarGroupProps = FlexProps & {
|
export declare type ToolbarGroupProps = FlexProps & {
|
||||||
tools: ToolbarGroupDefinition;
|
tools: ToolbarGroupDefinition;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
import { ImageAlignmentOptions, ImageSizeOptions } from "../../extensions/image";
|
import { ImageAlignmentOptions, ImageSizeOptions } from "../../extensions/image";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
export declare type ImagePropertiesProps = ImageSizeOptions & ImageAlignmentOptions & {
|
export declare type ImagePropertiesProps = ImageSizeOptions & ImageAlignmentOptions & {
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
export declare type SearchReplacePopupProps = {
|
export declare type SearchReplacePopupProps = {
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
};
|
};
|
||||||
|
|||||||
2
packages/editor/dist/es/toolbar/toolbar.d.ts
vendored
2
packages/editor/dist/es/toolbar/toolbar.d.ts
vendored
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
import { Theme } from "@notesnook/theme";
|
import { Theme } from "@notesnook/theme";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../types";
|
||||||
import { ToolbarLocation } from "./stores/toolbar-store";
|
import { ToolbarLocation } from "./stores/toolbar-store";
|
||||||
import { ToolbarDefinition } from "./types";
|
import { ToolbarDefinition } from "./types";
|
||||||
declare type ToolbarProps = {
|
declare type ToolbarProps = {
|
||||||
|
|||||||
@@ -210,7 +210,8 @@ const uploadImageFromURLMobile = (editor) => ({
|
|||||||
type: "popup",
|
type: "popup",
|
||||||
component: ({ onClick }) => (_jsx(ImageUploadPopup, { onInsert: (image) => {
|
component: ({ onClick }) => (_jsx(ImageUploadPopup, { onInsert: (image) => {
|
||||||
var _a;
|
var _a;
|
||||||
(_a = editor.current) === null || _a === void 0 ? void 0 : _a.chain().focus().insertImage(image).run();
|
(_a = editor
|
||||||
|
.requestPermission("insertImage")) === null || _a === void 0 ? void 0 : _a.chain().focus().insertImage(image).run();
|
||||||
onClick === null || onClick === void 0 ? void 0 : onClick();
|
onClick === null || onClick === void 0 ? void 0 : onClick();
|
||||||
}, onClose: () => {
|
}, onClose: () => {
|
||||||
onClick === null || onClick === void 0 ? void 0 : onClick();
|
onClick === null || onClick === void 0 ? void 0 : onClick();
|
||||||
@@ -225,13 +226,12 @@ const uploadImageFromURL = (editor) => ({
|
|||||||
title: "Attach from URL",
|
title: "Attach from URL",
|
||||||
icon: "link",
|
icon: "link",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (!editor)
|
|
||||||
return;
|
|
||||||
showPopup({
|
showPopup({
|
||||||
theme: editor.storage.theme,
|
theme: editor.storage.theme,
|
||||||
popup: (hide) => (_jsx(ImageUploadPopup, { onInsert: (image) => {
|
popup: (hide) => (_jsx(ImageUploadPopup, { onInsert: (image) => {
|
||||||
var _a;
|
var _a;
|
||||||
(_a = editor.current) === null || _a === void 0 ? void 0 : _a.chain().focus().insertImage(image).run();
|
(_a = editor
|
||||||
|
.requestPermission("insertImage")) === null || _a === void 0 ? void 0 : _a.chain().focus().insertImage(image).run();
|
||||||
hide();
|
hide();
|
||||||
}, onClose: hide })),
|
}, onClose: hide })),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/// <reference types="react" />
|
/// <reference types="react" />
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
import { MenuButton } from "../../components/menu/types";
|
import { MenuButton } from "../../components/menu/types";
|
||||||
import { ToolProps } from "../types";
|
import { ToolProps } from "../types";
|
||||||
export declare function menuButtonToTool(constructItem: (editor: Editor) => MenuButton): (props: ToolProps) => JSX.Element;
|
export declare function menuButtonToTool(constructItem: (editor: Editor) => MenuButton): (props: ToolProps) => JSX.Element;
|
||||||
|
|||||||
15
packages/editor/dist/es/types.d.ts
vendored
15
packages/editor/dist/es/types.d.ts
vendored
@@ -1,9 +1,20 @@
|
|||||||
import { Editor as TiptapEditor } from "@tiptap/core";
|
import { UnionCommands, Editor as TiptapEditor } from "@tiptap/core";
|
||||||
export interface Editor extends TiptapEditor {
|
export interface PermissionRequestEvent extends CustomEvent<{
|
||||||
|
id: keyof UnionCommands;
|
||||||
|
}> {
|
||||||
|
}
|
||||||
|
export declare class Editor extends TiptapEditor {
|
||||||
/**
|
/**
|
||||||
* Use this to get the latest instance of the editor.
|
* Use this to get the latest instance of the editor.
|
||||||
* This is required to reduce unnecessary rerenders of
|
* This is required to reduce unnecessary rerenders of
|
||||||
* toolbar elements.
|
* toolbar elements.
|
||||||
*/
|
*/
|
||||||
current?: TiptapEditor;
|
current?: TiptapEditor;
|
||||||
|
/**
|
||||||
|
* Request permission before executing a command to make sure user
|
||||||
|
* is allowed to perform the action.
|
||||||
|
* @param id the command id to get permission for
|
||||||
|
* @returns latest editor instance
|
||||||
|
*/
|
||||||
|
requestPermission(id: keyof UnionCommands): TiptapEditor | undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
19
packages/editor/dist/es/types.js
vendored
19
packages/editor/dist/es/types.js
vendored
@@ -1 +1,18 @@
|
|||||||
export {};
|
import { Editor as TiptapEditor } from "@tiptap/core";
|
||||||
|
export class Editor extends TiptapEditor {
|
||||||
|
/**
|
||||||
|
* Request permission before executing a command to make sure user
|
||||||
|
* is allowed to perform the action.
|
||||||
|
* @param id the command id to get permission for
|
||||||
|
* @returns latest editor instance
|
||||||
|
*/
|
||||||
|
requestPermission(id) {
|
||||||
|
const event = new CustomEvent("permissionrequest", {
|
||||||
|
detail: { id },
|
||||||
|
cancelable: true,
|
||||||
|
});
|
||||||
|
if (!window.dispatchEvent(event))
|
||||||
|
return undefined;
|
||||||
|
return this.current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,11 +11,12 @@ import {
|
|||||||
ForwardRef,
|
ForwardRef,
|
||||||
ContentDOM,
|
ContentDOM,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { Editor, NodeViewRendererProps } from "@tiptap/core";
|
import { NodeViewRendererProps } from "@tiptap/core";
|
||||||
import { Theme } from "@notesnook/theme";
|
import { Theme } from "@notesnook/theme";
|
||||||
import { ThemeProvider } from "emotion-theming";
|
import { ThemeProvider } from "emotion-theming";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { __serializeForClipboard } from "prosemirror-view";
|
import { __serializeForClipboard } from "prosemirror-view";
|
||||||
|
import { Editor } from "../../types";
|
||||||
|
|
||||||
export class ReactNodeView<P extends ReactNodeViewProps> implements NodeView {
|
export class ReactNodeView<P extends ReactNodeViewProps> implements NodeView {
|
||||||
private domRef!: HTMLElement;
|
private domRef!: HTMLElement;
|
||||||
@@ -431,7 +432,7 @@ export function createNodeView<TProps extends ReactNodeViewProps>(
|
|||||||
return ({ node, getPos, editor }: NodeViewRendererProps) => {
|
return ({ node, getPos, editor }: NodeViewRendererProps) => {
|
||||||
const _getPos = () => (typeof getPos === "boolean" ? -1 : getPos());
|
const _getPos = () => (typeof getPos === "boolean" ? -1 : getPos());
|
||||||
|
|
||||||
return new ReactNodeView<TProps>(node, editor, _getPos, {
|
return new ReactNodeView<TProps>(node, editor as Editor, _getPos, {
|
||||||
...options,
|
...options,
|
||||||
component,
|
component,
|
||||||
}).init();
|
}).init();
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ import {
|
|||||||
ForwardRef,
|
ForwardRef,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { ReactNodeView } from "./react-node-view";
|
import { ReactNodeView } from "./react-node-view";
|
||||||
import { Editor, NodeViewRendererProps } from "@tiptap/core";
|
import { NodeViewRendererProps } from "@tiptap/core";
|
||||||
import { Theme } from "@notesnook/theme";
|
import { Theme } from "@notesnook/theme";
|
||||||
import { ThemeProvider } from "emotion-theming";
|
import { ThemeProvider } from "emotion-theming";
|
||||||
|
import { Editor } from "../../types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A ReactNodeView that handles React components sensitive
|
* A ReactNodeView that handles React components sensitive
|
||||||
@@ -251,7 +252,7 @@ export function createSelectionBasedNodeView<
|
|||||||
) {
|
) {
|
||||||
return ({ node, getPos, editor }: NodeViewRendererProps) => {
|
return ({ node, getPos, editor }: NodeViewRendererProps) => {
|
||||||
const _getPos = () => (typeof getPos === "boolean" ? -1 : getPos());
|
const _getPos = () => (typeof getPos === "boolean" ? -1 : getPos());
|
||||||
return new SelectionBasedNodeView(node, editor, _getPos, {
|
return new SelectionBasedNodeView(node, editor as Editor, _getPos, {
|
||||||
...options,
|
...options,
|
||||||
component,
|
component,
|
||||||
}).init();
|
}).init();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
import { Node as PMNode, Attrs } from "prosemirror-model";
|
import { Node as PMNode, Attrs } from "prosemirror-model";
|
||||||
|
|
||||||
export interface ReactNodeProps {
|
export interface ReactNodeProps {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
SelectionBasedReactNodeViewProps,
|
SelectionBasedReactNodeViewProps,
|
||||||
} from "../react";
|
} from "../react";
|
||||||
import { Node as ProsemirrorNode } from "prosemirror-model";
|
import { Node as ProsemirrorNode } from "prosemirror-model";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { updateColumnsOnResize } from "@_ueberdosis/prosemirror-tables";
|
import { updateColumnsOnResize } from "@_ueberdosis/prosemirror-tables";
|
||||||
import { NodeView } from "prosemirror-view";
|
import { NodeView } from "prosemirror-view";
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Table as TiptapTable, TableOptions } from "@tiptap/extension-table";
|
import { Table as TiptapTable, TableOptions } from "@tiptap/extension-table";
|
||||||
import { columnResizing, tableEditing } from "@_ueberdosis/prosemirror-tables";
|
import { columnResizing, tableEditing } from "@_ueberdosis/prosemirror-tables";
|
||||||
|
import { Editor } from "../../types";
|
||||||
import { TableNodeView } from "./component";
|
import { TableNodeView } from "./component";
|
||||||
|
|
||||||
export const Table = TiptapTable.extend<TableOptions>({
|
export const Table = TiptapTable.extend<TableOptions>({
|
||||||
@@ -12,7 +13,7 @@ export const Table = TiptapTable.extend<TableOptions>({
|
|||||||
columnResizing({
|
columnResizing({
|
||||||
handleWidth: this.options.handleWidth,
|
handleWidth: this.options.handleWidth,
|
||||||
cellMinWidth: this.options.cellMinWidth,
|
cellMinWidth: this.options.cellMinWidth,
|
||||||
View: TableNodeView(this.editor),
|
View: TableNodeView(this.editor as Editor),
|
||||||
// TODO: PR for @types/prosemirror-tables
|
// TODO: PR for @types/prosemirror-tables
|
||||||
// @ts-ignore (incorrect type)
|
// @ts-ignore (incorrect type)
|
||||||
lastColumnResizable: this.options.lastColumnResizable,
|
lastColumnResizable: this.options.lastColumnResizable,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EditorOptions, Editor } from "@tiptap/core";
|
import { EditorOptions } from "@tiptap/core";
|
||||||
import { DependencyList, useEffect, useRef, useState } from "react";
|
import { DependencyList, useEffect, useRef, useState } from "react";
|
||||||
import { Editor as EditorType } from "../types";
|
import { Editor } from "../types";
|
||||||
|
|
||||||
function useForceUpdate() {
|
function useForceUpdate() {
|
||||||
const [, setValue] = useState(0);
|
const [, setValue] = useState(0);
|
||||||
@@ -12,9 +12,9 @@ export const useEditor = (
|
|||||||
options: Partial<EditorOptions> = {},
|
options: Partial<EditorOptions> = {},
|
||||||
deps: DependencyList = []
|
deps: DependencyList = []
|
||||||
) => {
|
) => {
|
||||||
const [editor, setEditor] = useState<EditorType | null>(null);
|
const [editor, setEditor] = useState<Editor | null>(null);
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
const editorRef = useRef<EditorType | null>(editor);
|
const editorRef = useRef<Editor | null>(editor);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
@@ -42,10 +42,21 @@ export const useEditor = (
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
|
|
||||||
if (editor && !editor.current)
|
if (!editor) return;
|
||||||
|
|
||||||
|
if (!editor.current) {
|
||||||
Object.defineProperty(editor, "current", {
|
Object.defineProperty(editor, "current", {
|
||||||
get: () => editorRef.current,
|
get: () => editorRef.current,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
// if (!editor.executor) {
|
||||||
|
// Object.defineProperty(editor, "executor", {
|
||||||
|
// get: () => (id?: string) => {
|
||||||
|
// console.log(id);
|
||||||
|
// return editorRef.current;
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// }
|
||||||
}, [editor]);
|
}, [editor]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
43
packages/editor/src/hooks/use-permission-handler.ts
Normal file
43
packages/editor/src/hooks/use-permission-handler.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { ChainedCommands, UnionCommands } from "@tiptap/core";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { PermissionRequestEvent } from "../types";
|
||||||
|
|
||||||
|
export type Claims = "premium";
|
||||||
|
export type PermissionHandlerOptions = {
|
||||||
|
claims: Record<Claims, boolean>;
|
||||||
|
onPermissionDenied: (claim: Claims, id: keyof UnionCommands) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ClaimsMap: Record<Claims, (keyof UnionCommands)[]> = {
|
||||||
|
premium: ["insertImage"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export function usePermissionHandler(options: PermissionHandlerOptions) {
|
||||||
|
const { claims, onPermissionDenied } = options;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function onPermissionRequested(ev: Event) {
|
||||||
|
const {
|
||||||
|
detail: { id },
|
||||||
|
} = ev as PermissionRequestEvent;
|
||||||
|
|
||||||
|
for (const key in ClaimsMap) {
|
||||||
|
const claim = key as Claims;
|
||||||
|
const commands = ClaimsMap[claim];
|
||||||
|
|
||||||
|
if (commands.indexOf(id) <= -1) continue;
|
||||||
|
if (claims[claim]) continue;
|
||||||
|
|
||||||
|
onPermissionDenied(claim, id);
|
||||||
|
ev.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
window.addEventListener("permissionrequest", onPermissionRequested);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("permissionrequest", onPermissionRequested);
|
||||||
|
};
|
||||||
|
}, [claims, onPermissionDenied]);
|
||||||
|
}
|
||||||
@@ -40,9 +40,10 @@ import {
|
|||||||
import { OutlineList } from "./extensions/outline-list";
|
import { OutlineList } from "./extensions/outline-list";
|
||||||
import { OutlineListItem } from "./extensions/outline-list-item";
|
import { OutlineListItem } from "./extensions/outline-list-item";
|
||||||
import { Table } from "./extensions/table";
|
import { Table } from "./extensions/table";
|
||||||
import { useIsMobile, useToolbarStore } from "./toolbar/stores/toolbar-store";
|
import { useToolbarStore } from "./toolbar/stores/toolbar-store";
|
||||||
import { useEditor } from "./hooks/use-editor";
|
import { useEditor } from "./hooks/use-editor";
|
||||||
import { EditorOptions } from "@tiptap/core";
|
import { EditorOptions } from "@tiptap/core";
|
||||||
|
import { usePermissionHandler } from "./hooks/use-permission-handler";
|
||||||
|
|
||||||
EditorView.prototype.updateState = function updateState(state) {
|
EditorView.prototype.updateState = function updateState(state) {
|
||||||
if (!(this as any).docView) return; // This prevents the matchesNode error on hot reloads
|
if (!(this as any).docView) return; // This prevents the matchesNode error on hot reloads
|
||||||
@@ -50,7 +51,10 @@ EditorView.prototype.updateState = function updateState(state) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type TiptapOptions = EditorOptions &
|
type TiptapOptions = EditorOptions &
|
||||||
AttachmentOptions & { theme: Theme; isMobile?: boolean };
|
AttachmentOptions & {
|
||||||
|
theme: Theme;
|
||||||
|
isMobile?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const useTiptap = (
|
const useTiptap = (
|
||||||
options: Partial<TiptapOptions> = {},
|
options: Partial<TiptapOptions> = {},
|
||||||
@@ -66,6 +70,10 @@ const useTiptap = (
|
|||||||
const PortalProviderAPI = usePortalProvider();
|
const PortalProviderAPI = usePortalProvider();
|
||||||
const setIsMobile = useToolbarStore((store) => store.setIsMobile);
|
const setIsMobile = useToolbarStore((store) => store.setIsMobile);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMobile(isMobile || false);
|
||||||
|
}, [isMobile]);
|
||||||
|
|
||||||
const defaultOptions = useMemo<Partial<EditorOptions>>(
|
const defaultOptions = useMemo<Partial<EditorOptions>>(
|
||||||
() => ({
|
() => ({
|
||||||
extensions: [
|
extensions: [
|
||||||
@@ -158,14 +166,10 @@ const useTiptap = (
|
|||||||
deps
|
deps
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsMobile(isMobile || false);
|
|
||||||
}, [isMobile]);
|
|
||||||
|
|
||||||
return editor;
|
return editor;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { useTiptap, Toolbar };
|
export { useTiptap, Toolbar, usePermissionHandler };
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
export * from "./extensions/react";
|
export * from "./extensions/react";
|
||||||
export * from "./toolbar";
|
export * from "./toolbar";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ToolbarGroupDefinition, ToolButtonVariant } from "../types";
|
import { ToolbarGroupDefinition, ToolButtonVariant } from "../types";
|
||||||
import { findTool } from "../tools";
|
import { findTool } from "../tools";
|
||||||
import { Flex, FlexProps } from "rebass";
|
import { Flex, FlexProps } from "rebass";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
import { MoreTools } from "./more-tools";
|
import { MoreTools } from "./more-tools";
|
||||||
import { getToolDefinition } from "../tool-definitions";
|
import { getToolDefinition } from "../tool-definitions";
|
||||||
import { NodeWithOffset } from "../utils/prosemirror";
|
import { NodeWithOffset } from "../utils/prosemirror";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
ImageAlignmentOptions,
|
ImageAlignmentOptions,
|
||||||
ImageSizeOptions,
|
ImageSizeOptions,
|
||||||
} from "../../extensions/image";
|
} from "../../extensions/image";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
import { InlineInput } from "../../components/inline-input";
|
import { InlineInput } from "../../components/inline-input";
|
||||||
|
|
||||||
export type ImagePropertiesProps = ImageSizeOptions &
|
export type ImagePropertiesProps = ImageSizeOptions &
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|||||||
import { Flex, Text } from "rebass";
|
import { Flex, Text } from "rebass";
|
||||||
import { SearchStorage } from "../../extensions/search-replace";
|
import { SearchStorage } from "../../extensions/search-replace";
|
||||||
import { ToolButton } from "../components/tool-button";
|
import { ToolButton } from "../components/tool-button";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
|
|
||||||
export type SearchReplacePopupProps = { editor: Editor };
|
export type SearchReplacePopupProps = { editor: Editor };
|
||||||
export function SearchReplacePopup(props: SearchReplacePopupProps) {
|
export function SearchReplacePopup(props: SearchReplacePopupProps) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Theme, useTheme } from "@notesnook/theme";
|
import { Theme, useTheme } from "@notesnook/theme";
|
||||||
import { ThemeConfig } from "@notesnook/theme/dist/theme/types";
|
import { ThemeConfig } from "@notesnook/theme/dist/theme/types";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../types";
|
||||||
import { Flex, FlexProps } from "rebass";
|
import { Flex, FlexProps } from "rebass";
|
||||||
import { findTool, ToolId } from "./tools";
|
import { findTool, ToolId } from "./tools";
|
||||||
import { ThemeProvider } from "emotion-theming";
|
import { ThemeProvider } from "emotion-theming";
|
||||||
|
|||||||
@@ -264,7 +264,12 @@ const uploadImageFromURLMobile = (editor: Editor): MenuItem => ({
|
|||||||
component: ({ onClick }) => (
|
component: ({ onClick }) => (
|
||||||
<ImageUploadPopup
|
<ImageUploadPopup
|
||||||
onInsert={(image) => {
|
onInsert={(image) => {
|
||||||
editor.current?.chain().focus().insertImage(image).run();
|
editor
|
||||||
|
.requestPermission("insertImage")
|
||||||
|
?.chain()
|
||||||
|
.focus()
|
||||||
|
.insertImage(image)
|
||||||
|
.run();
|
||||||
onClick?.();
|
onClick?.();
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
@@ -283,13 +288,17 @@ const uploadImageFromURL = (editor: Editor): MenuItem => ({
|
|||||||
title: "Attach from URL",
|
title: "Attach from URL",
|
||||||
icon: "link",
|
icon: "link",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (!editor) return;
|
|
||||||
showPopup({
|
showPopup({
|
||||||
theme: editor.storage.theme,
|
theme: editor.storage.theme,
|
||||||
popup: (hide) => (
|
popup: (hide) => (
|
||||||
<ImageUploadPopup
|
<ImageUploadPopup
|
||||||
onInsert={(image) => {
|
onInsert={(image) => {
|
||||||
editor.current?.chain().focus().insertImage(image).run();
|
editor
|
||||||
|
.requestPermission("insertImage")
|
||||||
|
?.chain()
|
||||||
|
.focus()
|
||||||
|
.insertImage(image)
|
||||||
|
.run();
|
||||||
hide();
|
hide();
|
||||||
}}
|
}}
|
||||||
onClose={hide}
|
onClose={hide}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Editor } from "@tiptap/core";
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import tinycolor from "tinycolor2";
|
import tinycolor from "tinycolor2";
|
||||||
import { PopupWrapper } from "../../components/popup-presenter";
|
import { PopupWrapper } from "../../components/popup-presenter";
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ToolProps } from "../types";
|
import { ToolProps } from "../types";
|
||||||
import { Editor } from "@tiptap/core";
|
|
||||||
import { Box, Button, Flex } from "rebass";
|
import { Box, Button, Flex } from "rebass";
|
||||||
import { IconNames } from "../icons";
|
import { IconNames } from "../icons";
|
||||||
import { useCallback, useMemo, useRef, useState } from "react";
|
import { useCallback, useMemo, useRef, useState } from "react";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "../../types";
|
||||||
import { MenuButton } from "../../components/menu/types";
|
import { MenuButton } from "../../components/menu/types";
|
||||||
import { ToolButton } from "../components/tool-button";
|
import { ToolButton } from "../components/tool-button";
|
||||||
import { ToolProps } from "../types";
|
import { ToolProps } from "../types";
|
||||||
|
|||||||
@@ -1,9 +1,30 @@
|
|||||||
import { Editor as TiptapEditor } from "@tiptap/core";
|
import { UnionCommands, Editor as TiptapEditor } from "@tiptap/core";
|
||||||
export interface Editor extends TiptapEditor {
|
|
||||||
|
export interface PermissionRequestEvent
|
||||||
|
extends CustomEvent<{ id: keyof UnionCommands }> {}
|
||||||
|
|
||||||
|
export class Editor extends TiptapEditor {
|
||||||
/**
|
/**
|
||||||
* Use this to get the latest instance of the editor.
|
* Use this to get the latest instance of the editor.
|
||||||
* This is required to reduce unnecessary rerenders of
|
* This is required to reduce unnecessary rerenders of
|
||||||
* toolbar elements.
|
* toolbar elements.
|
||||||
*/
|
*/
|
||||||
current?: TiptapEditor;
|
current?: TiptapEditor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request permission before executing a command to make sure user
|
||||||
|
* is allowed to perform the action.
|
||||||
|
* @param id the command id to get permission for
|
||||||
|
* @returns latest editor instance
|
||||||
|
*/
|
||||||
|
requestPermission(id: keyof UnionCommands): TiptapEditor | undefined {
|
||||||
|
const event = new CustomEvent("permissionrequest", {
|
||||||
|
detail: { id },
|
||||||
|
cancelable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!window.dispatchEvent(event)) return undefined;
|
||||||
|
|
||||||
|
return this.current;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,3 +113,21 @@ What's next:
|
|||||||
|
|
||||||
1. Keyboard shouldn't close on tool click
|
1. Keyboard shouldn't close on tool click
|
||||||
2. Handle context toolbar menus on scroll
|
2. Handle context toolbar menus on scroll
|
||||||
|
|
||||||
|
## Premium restriction
|
||||||
|
|
||||||
|
1. `isAuthorized` method on the editor
|
||||||
|
2. `execCommand` method on the editor that emits an event (onBeforeExecCommand)
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
1. We will need to check the user status sometimes outside the editor commands
|
||||||
|
2. For this reason, we shouldn't attach this to the editor
|
||||||
|
3. Or if we do, we shouldn't make it command specific.
|
||||||
|
4. Secondly, we need a way to authorize user actions without spreading it all over the codebase.
|
||||||
|
5. One option is to put it in the base buttons etc. and pass an "isPro" prop
|
||||||
|
1. But I don't like this because not all buttons need it.
|
||||||
|
6. What we should do it emit an event before each command
|
||||||
|
1. We can have a `requestPermission` method on the editor that sends an id
|
||||||
|
2. This can be used to allow/deny user actions
|
||||||
|
7. And also have a function to check authorization for non-command actions.
|
||||||
|
|||||||
Reference in New Issue
Block a user