read only editor support added

This commit is contained in:
Palanikannan1437 2023-10-02 16:24:57 +05:30
parent 3d87a56e3b
commit de07f63089
17 changed files with 284 additions and 16 deletions

View File

@ -1,9 +1,16 @@
// styles
import "@/styles/tailwind.css";
import "@/styles/editor.css";
export { startImageUpload } from "@/ui/plugins/upload-image";
export { useEditor } from "@/ui/hooks/useEditor";
// utils
export { cn } from "@/lib/utils";
export { getEditorClassNames } from "@/lib/utils";
export { EditorContainer } from "@/ui/editor-container";
export { EditorContentWrapper } from "@/ui/editor-content";
export { startImageUpload } from "@/ui/plugins/upload-image";
// components
export { EditorContainer } from "@/ui/components/editor-container";
export { EditorContentWrapper } from "@/ui/components/editor-content";
// hooks
export { useEditor } from "@/ui/hooks/useEditor";
export { useReadOnlyEditor } from "@/ui/hooks/useReadOnlyEditor";

View File

@ -1,7 +1,7 @@
import { Editor, EditorContent } from "@tiptap/react";
import { ReactNode } from "react";
import { ImageResizer } from "@/ui/extensions/image/image-resize";
import { TableMenu } from "./menus/table-menu";
import { TableMenu } from "@/ui/menus/table-menu";
interface EditorContentProps {
editor: Editor | null;

View File

@ -0,0 +1,17 @@
import Image from "@tiptap/extension-image";
const ReadOnlyImageExtension = Image.extend({
addAttributes() {
return {
...this.parent?.(),
width: {
default: "35%",
},
height: {
default: null,
},
};
},
});
export default ReadOnlyImageExtension;

View File

@ -8,9 +8,9 @@ import TaskList from "@tiptap/extension-task-list";
import { Markdown } from "tiptap-markdown";
import Gapcursor from "@tiptap/extension-gapcursor";
import { CustomTableCell } from "./table/table-cell";
import { Table } from "./table/table";
import { TableHeader } from "./table/table-header";
import { CustomTableCell } from "@/ui/extensions/table/table-cell";
import { Table } from "@/ui/extensions/table";
import { TableHeader } from "@/ui/extensions/table/table-header";
import { TableRow } from "@tiptap/extension-table-row";
import ImageExtension from "@/ui/extensions/image";

View File

@ -0,0 +1,37 @@
import { useEditor as useCustomEditor, Editor } from "@tiptap/react";
import { useImperativeHandle, useRef, MutableRefObject } from "react";
import { CoreReadOnlyEditorExtensions } from "@/ui/read-only/extensions";
import { CoreReadOnlyEditorProps } from "@/ui/read-only/props";
interface CustomReadOnlyEditorProps {
value: string;
forwardedRef?: any;
}
export const useReadOnlyEditor = ({ value, forwardedRef }: CustomReadOnlyEditorProps) => {
const editor = useCustomEditor({
editable: false,
content: (typeof value === "string" && value.trim() !== "") ? value : "<p></p>",
editorProps: CoreReadOnlyEditorProps,
extensions: CoreReadOnlyEditorExtensions,
});
const editorRef: MutableRefObject<Editor | null> = useRef(null);
editorRef.current = editor;
useImperativeHandle(forwardedRef, () => ({
clearEditor: () => {
editorRef.current?.commands.clearContent();
},
setEditorValue: (content: string) => {
editorRef.current?.commands.setContent(content);
},
}));
if (!editor) {
return null;
}
return editor;
};

View File

@ -0,0 +1,92 @@
import StarterKit from "@tiptap/starter-kit";
import TiptapLink from "@tiptap/extension-link";
import TiptapUnderline from "@tiptap/extension-underline";
import TextStyle from "@tiptap/extension-text-style";
import { Color } from "@tiptap/extension-color";
import TaskItem from "@tiptap/extension-task-item";
import TaskList from "@tiptap/extension-task-list";
import { Markdown } from "tiptap-markdown";
import Gapcursor from "@tiptap/extension-gapcursor";
import { CustomTableCell } from "@/ui/extensions/table/table-cell";
import { Table } from "@/ui/extensions/table";
import { TableHeader } from "@/ui/extensions/table/table-header";
import { TableRow } from "@tiptap/extension-table-row";
import isValidHttpUrl from "@/ui/menus/bubble-menu/utils";
import ReadOnlyImageExtension from "@/ui/extensions/image/read-only-image";
export const CoreReadOnlyEditorExtensions = [
StarterKit.configure({
bulletList: {
HTMLAttributes: {
class: "list-disc list-outside leading-3 -mt-2",
},
},
orderedList: {
HTMLAttributes: {
class: "list-decimal list-outside leading-3 -mt-2",
},
},
listItem: {
HTMLAttributes: {
class: "leading-normal -mb-2",
},
},
blockquote: {
HTMLAttributes: {
class: "border-l-4 border-custom-border-300",
},
},
code: {
HTMLAttributes: {
class:
"rounded-md bg-custom-primary-30 mx-1 px-1 py-1 font-mono font-medium text-custom-text-1000",
spellcheck: "false",
},
},
codeBlock: false,
horizontalRule: false,
dropcursor: {
color: "rgba(var(--color-text-100))",
width: 2,
},
gapcursor: false,
}),
Gapcursor,
TiptapLink.configure({
protocols: ["http", "https"],
validate: (url) => isValidHttpUrl(url),
HTMLAttributes: {
class:
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
},
}),
ReadOnlyImageExtension.configure({
HTMLAttributes: {
class: "rounded-lg border border-custom-border-300",
},
}),
TiptapUnderline,
TextStyle,
Color,
TaskList.configure({
HTMLAttributes: {
class: "not-prose pl-2",
},
}),
TaskItem.configure({
HTMLAttributes: {
class: "flex items-start my-4",
},
nested: true,
}),
Markdown.configure({
html: true,
transformCopiedText: true,
}),
Table,
TableHeader,
CustomTableCell,
TableRow,
];

View File

@ -0,0 +1,8 @@
import { EditorProps } from "@tiptap/pm/view";
export const CoreReadOnlyEditorProps: EditorProps =
{
attributes: {
class: `prose prose-brand max-w-full prose-headings:font-display font-default focus:outline-none`,
},
};

View File

@ -3,7 +3,7 @@ import { defineConfig, Options } from "tsup";
export default defineConfig((options: Options) => ({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
dts: false,
dts: true,
clean: false,
external: ["react"],
injectStyle: true,

View File

@ -1 +1,2 @@
export { LiteTextEditor, LiteTextEditorWithRef } from "@/ui";
export { LiteReadOnlyEditor, LiteReadOnlyEditorWithRef } from "@/ui/read-only";

View File

@ -0,0 +1,54 @@
"use client"
import { EditorContainer, EditorContentWrapper, getEditorClassNames, useReadOnlyEditor } from '@plane/editor-core';
import * as React from 'react';
interface ICoreReadOnlyEditor {
value: string;
editorContentCustomClassNames?: string;
noBorder?: boolean;
borderOnFocus?: boolean;
customClassName?: string;
}
interface EditorCoreProps extends ICoreReadOnlyEditor {
forwardedRef?: React.Ref<EditorHandle>;
}
interface EditorHandle {
clearEditor: () => void;
setEditorValue: (content: string) => void;
}
const LiteReadOnlyEditor = ({
editorContentCustomClassNames,
noBorder,
borderOnFocus,
customClassName,
value,
forwardedRef,
}: EditorCoreProps) => {
const editor = useReadOnlyEditor({
value,
forwardedRef,
});
const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName });
if (!editor) return null;
return (
<EditorContainer editor={editor} editorClassNames={editorClassNames}>
<div className="flex flex-col">
<EditorContentWrapper editor={editor} editorContentCustomClassNames={editorContentCustomClassNames} />
</div>
</EditorContainer >
);
};
const LiteReadOnlyEditorWithRef = React.forwardRef<EditorHandle, ICoreReadOnlyEditor>((props, ref) => (
<LiteReadOnlyEditor {...props} forwardedRef={ref} />
));
LiteReadOnlyEditorWithRef.displayName = "LiteReadOnlyEditorWithRef";
export { LiteReadOnlyEditor , LiteReadOnlyEditorWithRef };

View File

@ -3,7 +3,7 @@ import { defineConfig, Options } from "tsup";
export default defineConfig((options: Options) => ({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
dts: false,
dts: true,
clean: false,
external: ["react"],
injectStyle: true,

View File

@ -1,3 +1,4 @@
import "@/styles/github-dark.css";
export { RichTextEditor, RichTextEditorWithRef } from "@/ui";
export { RichReadOnlyEditor, RichReadOnlyEditorWithRef } from "@/ui/read-only";

View File

@ -0,0 +1,54 @@
"use client"
import { EditorContainer, EditorContentWrapper, getEditorClassNames, useReadOnlyEditor } from '@plane/editor-core';
import * as React from 'react';
interface IRichTextReadOnlyEditor {
value: string;
editorContentCustomClassNames?: string;
noBorder?: boolean;
borderOnFocus?: boolean;
customClassName?: string;
}
interface RichTextReadOnlyEditorProps extends IRichTextReadOnlyEditor {
forwardedRef?: React.Ref<EditorHandle>;
}
interface EditorHandle {
clearEditor: () => void;
setEditorValue: (content: string) => void;
}
const RichReadOnlyEditor = ({
editorContentCustomClassNames,
noBorder,
borderOnFocus,
customClassName,
value,
forwardedRef,
}: RichTextReadOnlyEditorProps) => {
const editor = useReadOnlyEditor({
value,
forwardedRef,
});
const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName });
if (!editor) return null;
return (
<EditorContainer editor={editor} editorClassNames={editorClassNames}>
<div className="flex flex-col">
<EditorContentWrapper editor={editor} editorContentCustomClassNames={editorContentCustomClassNames} />
</div>
</EditorContainer >
);
};
const RichReadOnlyEditorWithRef = React.forwardRef<EditorHandle, IRichTextReadOnlyEditor>((props, ref) => (
<RichReadOnlyEditor {...props} forwardedRef={ref} />
));
RichReadOnlyEditorWithRef.displayName = "RichReadOnlyEditorWithRef";
export { RichReadOnlyEditor , RichReadOnlyEditorWithRef };

View File

@ -3,7 +3,7 @@ import { defineConfig, Options } from "tsup";
export default defineConfig((options: Options) => ({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
dts: false,
dts: true,
clean: false,
external: ["react"],
injectStyle: true,

View File

@ -9,7 +9,7 @@ import useUser from "hooks/use-user";
// ui
import { CustomMenu, Icon } from "components/ui";
import { CommentReaction } from "components/issues";
import { LiteTextEditorWithRef } from "@plane/lite-text-editor";
import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-text-editor";
// helpers
import { timeAgo } from "helpers/date-time.helper";
// types
@ -151,12 +151,9 @@ export const CommentCard: React.FC<Props> = ({
/>
</div>
)}
<LiteTextEditorWithRef
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
<LiteReadOnlyEditorWithRef
ref={showEditorRef}
value={comment.comment_html}
editable={false}
customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
/>
<CommentReaction projectId={comment.project} commentId={comment.id} />