diff --git a/README.md b/README.md
index 52b435a..1c66358 100644
--- a/README.md
+++ b/README.md
@@ -111,16 +111,20 @@ function App() {
```
### Image upload
+Here is the corrected English version:
+
+```html
-The image can be uploaded to the server via an API call or directly into the content as base64 string.
-The image can be uploaded using upload button or pasted or dropped.
-Add or modify the alt text and the legend (title) of the image
-Delete the selected image using `Delete` keyboard key
+ The image can be uploaded to the server via an API call or inserted directly into the content as a base64 string.
+ The image can be uploaded using the upload button, or pasted or dropped.
+ Add or modify the alt text and the caption (title) of the image.
+ Delete the selected image using the `Delete` key on the keyboard.
+```
```tsx
-// example of API upload using fetch
-// the return data must be the image url (string) or image attributes (object) like src, alt, id, title, ...
+// Example of an API upload using fetch
+// The returned data must be the image URL (string) or image attributes (object) such as src, alt, id, title, etc.
const uploadFile = async (file: File) => {
const formData = new FormData();
formData.append("file", file);
@@ -160,7 +164,7 @@ import { TextEditorReadOnly } from 'mui-tiptap-editor';
```
-2. If it is just displaying the value without using the editor, you can use this library [`tiptap-parser`](https://www.npmjs.com/package/tiptap-parser). Example: The editor is used in the back office, but the content must be displayed on the website
+2. If you only need to display the value without using the editor, you can use this library: [`tiptap-parser`](https://www.npmjs.com/package/tiptap-parser). Example: The editor is used in the back office, but the content must be displayed on the website.
```tsx
```
@@ -168,7 +172,7 @@ import { TextEditorReadOnly } from 'mui-tiptap-editor';
## Customization
### Toolbar
- Can display the menus as needed
+Can display the menus as required.
```tsx
@@ -300,6 +304,14 @@ See [`here`](https://github.com/tiavina-mika/mui-tiptap-editor/blob/main/src/dev
Versions
Features
+
+
+ v0.9.19
+
+
+
v0.9.11
diff --git a/src/extensions/CodeBlockWithCopy.tsx b/src/extensions/CodeBlockWithCopy.tsx
new file mode 100644
index 0000000..254c81e
--- /dev/null
+++ b/src/extensions/CodeBlockWithCopy.tsx
@@ -0,0 +1,56 @@
+/**
+ * This file defines a custom CodeBlockWithCopy component for use with the TipTap editor.
+ * It includes a button to copy the code block content to the clipboard.
+ * The component uses lowlight for syntax highlighting and integrates with TipTap's NodeViewRenderer.
+ */
+
+import { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react';
+import { useState } from 'react';
+import { createLowlight, common } from "lowlight";
+import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
+import { CodeBlockWithCopyProps } from '../types';
+import Copy from '../icons/Copy';
+import Check from '../icons/Check';
+
+const CodeBlockWithCopy = ({ node }: any) => {
+ const [copied, setCopied] = useState(false);
+
+
+ const copyToClipboard = () => {
+ navigator.clipboard.writeText(node.textContent).then(() => {
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000); // "Copied!" message for 2 seconds
+ });
+ };
+
+ return (
+
+
+ {copied ? : }
+
+
+
+
+
+ );
+};
+
+export const getCodeBlockWithCopy = (props?: CodeBlockWithCopyProps) => {
+ const { language = 'javascript', className } = props || {};
+
+ return CodeBlockLowlight
+ .extend({
+ addNodeView() {
+ // Use ReactNodeViewRenderer to render the CodeBlockWithCopy component
+ return ReactNodeViewRenderer(
+ (props: any) => ,
+ { className }
+ );
+ },
+ })
+ .configure({
+ // Configure lowlight with common languages and set default language
+ lowlight: createLowlight(common),
+ defaultLanguage: language,
+ })
+}
diff --git a/src/hooks/useTextEditor.ts b/src/hooks/useTextEditor.ts
index 395fbbc..d6de6be 100644
--- a/src/hooks/useTextEditor.ts
+++ b/src/hooks/useTextEditor.ts
@@ -15,10 +15,8 @@ import Table from "@tiptap/extension-table";
import TableCell from "@tiptap/extension-table-cell";
import TableHeader from "@tiptap/extension-table-header";
import TableRow from "@tiptap/extension-table-row";
-import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
import Youtube from "@tiptap/extension-youtube";
import BubbleMenu from '@tiptap/extension-bubble-menu';
-import { createLowlight, common } from "lowlight";
import {
useEditor,
EditorOptions,
@@ -28,9 +26,10 @@ import {
import StarterKit from '@tiptap/starter-kit';
import { useEffect } from 'react';
import Heading from '@tiptap/extension-heading';
-import { ILabels, ImageUploadOptions, ITextEditorOption } from '../types.d';
+import { CodeBlockWithCopyProps, ILabels, ImageUploadOptions, ITextEditorOption } from '../types.d';
import getCustomImage from '../extensions/CustomImage';
import { getCustomMention } from '../extensions/CustomMention';
+import { getCodeBlockWithCopy } from '../extensions/CodeBlockWithCopy';
const extensions = [
Color.configure({ types: [TextStyle.name, ListItem.name] }),
@@ -86,15 +85,10 @@ const extensions = [
Youtube,
TextAlign.configure({
types: ["heading", "paragraph", "table", "image"]
- }),
- CodeBlockLowlight.configure({
- lowlight: createLowlight(common),
- defaultLanguage: "javascript"
- }),
+ }),,
BubbleMenu.configure({
element: document.querySelector('.bubble-menu'),
} as any),
- // History
];
export type TextEditorProps = {
@@ -108,6 +102,10 @@ export type TextEditorProps = {
userPathname?: string;
uploadFileOptions?: Omit;
uploadFileLabels?: ILabels['upload'];
+ /**
+ * props for the block code extension
+ */
+ codeBlock?: CodeBlockWithCopyProps;
} & Partial;
export const useTextEditor = ({
@@ -121,6 +119,7 @@ export const useTextEditor = ({
uploadFileLabels,
userPathname,
editable = true,
+ codeBlock,
...editorOptions
}: TextEditorProps) => {
const theme = useTheme();
@@ -136,6 +135,7 @@ export const useTextEditor = ({
getCustomMention({ pathname: userPathname, mentions }),
// upload image extension
getCustomImage(uploadFileOptions, uploadFileLabels, uploadFileOptions?.maxMediaLegendLength),
+ getCodeBlockWithCopy(codeBlock),
...extensions,
] as AnyExtension[],
/* The `onUpdate` function in the `useTextEditor` hook is a callback that is triggered whenever the
diff --git a/src/icons/Check.tsx b/src/icons/Check.tsx
new file mode 100644
index 0000000..f32e16f
--- /dev/null
+++ b/src/icons/Check.tsx
@@ -0,0 +1,14 @@
+
+import { SvgIcon } from "@mui/material";
+
+const Check = () => {
+ return (
+
+
+
+
+
+ );
+}
+
+export default Check;
diff --git a/src/icons/Copy.tsx b/src/icons/Copy.tsx
new file mode 100644
index 0000000..92fe892
--- /dev/null
+++ b/src/icons/Copy.tsx
@@ -0,0 +1,15 @@
+
+import { SvgIcon } from "@mui/material";
+
+const Copy = () => {
+ return (
+
+
+
+
+
+
+ );
+}
+
+export default Copy;
diff --git a/src/index.css b/src/index.css
index 496d6f1..9f5e1b5 100644
--- a/src/index.css
+++ b/src/index.css
@@ -127,20 +127,7 @@ code {
border-left: 3px solid rgba(13, 13, 13, 0.1);
padding-left: 1rem;
}
-.tiptap pre {
- background: #0d0d0d;
- color: #fff;
- font-family: "JetBrainsMono", monospace;
- padding: 0.75rem 1rem;
- border-radius: 0.5rem;
-}
-/* code highlight */
-.tiptap pre code {
- color: inherit;
- padding: 0;
- background: none;
- font-size: 0.8rem;
-}
+
.tiptap .hljs-comment,
.tiptap .hljs-quote {
@@ -310,3 +297,45 @@ code {
.tiptap-image {
display: flex;
}
+
+/* --------- code block ----------- */
+.tiptap .code-block-root {
+ position: relative;
+}
+/* important: code container */
+.tiptap .code-block-root pre {
+ background: #0d0d0d;
+ color: #fff;
+ font-family: "JetBrainsMono", monospace;
+ padding: 1rem;
+ border-radius: 0.5rem;
+}
+/* code highlight */
+.tiptap .code-block-root pre code {
+ color: inherit;
+ padding: 0;
+ background: none;
+ font-size: 0.8rem;
+}
+.tiptap .code-block-root button {
+ position: absolute;
+ right: 1rem;
+ top: 1rem;
+ background: transparent;
+ border: none;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid rgb(255 255 255 / 0.3);
+ width: 2rem;
+ height: 2rem;
+ cursor: pointer;
+ z-index: 1000;
+ color: #fff;
+ font-size: 12px;
+ border-radius: .25rem;
+ }
+
+.tiptap .code-block-root button svg {
+ width: 1rem;
+ }
diff --git a/src/types.d.ts b/src/types.d.ts
index fb2dc0e..0b3d34a 100644
--- a/src/types.d.ts
+++ b/src/types.d.ts
@@ -34,6 +34,11 @@ export type UploadResponse = {
id?: string;
alt?: string;
};
+
+export type CodeBlockWithCopyProps = {
+ language?: string;
+ className?: string;
+}
/**
* Image upload options from drop or paste event
* the image can be uploaded to the server via an API or saved inside as a base64 string