From d226a19255c85846ac40dc765c72893839aa988e Mon Sep 17 00:00:00 2001 From: Payam Date: Wed, 11 Feb 2026 11:53:00 +0400 Subject: [PATCH] feat: #210 add more extensions for the tiptap editor --- .../templates/emails/base.html | 11 ++ frontend/package-lock.json | 81 +++++++++++++ frontend/package.json | 5 + frontend/src/components/ContentEditor.jsx | 109 +++++++++++++++++- 4 files changed, 202 insertions(+), 4 deletions(-) diff --git a/django_email_learning/templates/emails/base.html b/django_email_learning/templates/emails/base.html index d9ee5520..1a76dc48 100644 --- a/django_email_learning/templates/emails/base.html +++ b/django_email_learning/templates/emails/base.html @@ -62,6 +62,17 @@ border: 1px solid #e0e0e0; } } + blockquote { + {% if IS_RTL %} + border-right: 4px solid #e0e0e0; + border-left: none; + {% else %} + border-left: 4px solid #e0e0e0; + border-right: none; + {% endif %} + margin: 0px !important; + padding: 0 16px; + } {% block extra_styles %}{% endblock %} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 20abc86c..ceb180df 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,13 +16,18 @@ "@mui/material": "^7.3.2", "@mui/x-charts": "^8.26.0", "@tiptap/core": "^3.13.0", + "@tiptap/extension-blockquote": "^3.19.0", "@tiptap/extension-bold": "^3.13.0", "@tiptap/extension-code-block": "^3.13.0", "@tiptap/extension-document": "^3.13.0", "@tiptap/extension-heading": "^3.13.0", "@tiptap/extension-image": "^3.13.0", + "@tiptap/extension-italic": "^3.19.0", + "@tiptap/extension-link": "^3.19.0", + "@tiptap/extension-list": "^3.19.0", "@tiptap/extension-paragraph": "^3.13.0", "@tiptap/extension-text": "^3.13.0", + "@tiptap/extension-text-align": "^3.19.0", "@tiptap/extensions": "^3.13.0", "@tiptap/pm": "^3.13.0", "@tiptap/react": "^3.13.0", @@ -2295,6 +2300,19 @@ "@tiptap/pm": "^3.19.0" } }, + "node_modules/@tiptap/extension-blockquote": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.19.0.tgz", + "integrity": "sha512-y3UfqY9KD5XwWz3ndiiJ089Ij2QKeiXy/g1/tlAN/F1AaWsnkHEHMLxCP1BIqmMpwsX7rZjMLN7G5Lp7c9682A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.19.0" + } + }, "node_modules/@tiptap/extension-bold": { "version": "3.19.0", "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.19.0.tgz", @@ -2395,6 +2413,50 @@ "@tiptap/core": "^3.19.0" } }, + "node_modules/@tiptap/extension-italic": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.19.0.tgz", + "integrity": "sha512-6GffxOnS/tWyCbDkirWNZITiXRta9wrCmrfa4rh+v32wfaOL1RRQNyqo9qN6Wjyl1R42Js+yXTzTTzZsOaLMYA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.19.0" + } + }, + "node_modules/@tiptap/extension-link": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.19.0.tgz", + "integrity": "sha512-HEGDJnnCPfr7KWu7Dsq+eRRe/mBCsv6DuI+7fhOCLDJjjKzNgrX2abbo/zG3D/4lCVFaVb+qawgJubgqXR/Smw==", + "license": "MIT", + "dependencies": { + "linkifyjs": "^4.3.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.19.0", + "@tiptap/pm": "^3.19.0" + } + }, + "node_modules/@tiptap/extension-list": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.19.0.tgz", + "integrity": "sha512-N6nKbFB2VwMsPlCw67RlAtYSK48TAsAUgjnD+vd3ieSlIufdQnLXDFUP6hFKx9mwoUVUgZGz02RA6bkxOdYyTw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.19.0", + "@tiptap/pm": "^3.19.0" + } + }, "node_modules/@tiptap/extension-paragraph": { "version": "3.19.0", "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.19.0.tgz", @@ -2421,6 +2483,19 @@ "@tiptap/core": "^3.19.0" } }, + "node_modules/@tiptap/extension-text-align": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.19.0.tgz", + "integrity": "sha512-cY8bHWYojLTHXZb2j2srdh7ltmDgnwXYvSxbPL4HK4j7XxQOGnOsTakgM/BNhxymOfEj2414i5Otyy8hlgviFA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.19.0" + } + }, "node_modules/@tiptap/extensions": { "version": "3.19.0", "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.19.0.tgz", @@ -3924,6 +3999,12 @@ "uc.micro": "^2.0.0" } }, + "node_modules/linkifyjs": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz", + "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "dev": true, diff --git a/frontend/package.json b/frontend/package.json index 22b20439..d8b162be 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,13 +18,18 @@ "@mui/material": "^7.3.2", "@mui/x-charts": "^8.26.0", "@tiptap/core": "^3.13.0", + "@tiptap/extension-blockquote": "^3.19.0", "@tiptap/extension-bold": "^3.13.0", "@tiptap/extension-code-block": "^3.13.0", "@tiptap/extension-document": "^3.13.0", "@tiptap/extension-heading": "^3.13.0", "@tiptap/extension-image": "^3.13.0", + "@tiptap/extension-italic": "^3.19.0", + "@tiptap/extension-link": "^3.19.0", + "@tiptap/extension-list": "^3.19.0", "@tiptap/extension-paragraph": "^3.13.0", "@tiptap/extension-text": "^3.13.0", + "@tiptap/extension-text-align": "^3.19.0", "@tiptap/extensions": "^3.13.0", "@tiptap/pm": "^3.13.0", "@tiptap/react": "^3.13.0", diff --git a/frontend/src/components/ContentEditor.jsx b/frontend/src/components/ContentEditor.jsx index 6cc44f83..62087a92 100644 --- a/frontend/src/components/ContentEditor.jsx +++ b/frontend/src/components/ContentEditor.jsx @@ -1,13 +1,24 @@ -import Text from "@tiptap/extension-text"; +import Text from '@tiptap/extension-text' import CodeBlock from '@tiptap/extension-code-block' import Document from '@tiptap/extension-document' import Paragraph from '@tiptap/extension-paragraph' import Bold from '@tiptap/extension-bold' +import Italic from '@tiptap/extension-italic' +import Link from '@tiptap/extension-link' +import BlockQuote from '@tiptap/extension-blockquote' +import { BulletList, ListItem } from '@tiptap/extension-list' +import InsertLinkIcon from '@mui/icons-material/InsertLink' +import FormatQuoteIcon from '@mui/icons-material/FormatQuote' +import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted' +import AlignHorizontalRightIcon from '@mui/icons-material/AlignHorizontalRight' +import AlignHorizontalLeftIcon from '@mui/icons-material/AlignHorizontalLeft' +import FormatAlignCenterIcon from '@mui/icons-material/FormatAlignCenter' +import TextAlign from '@tiptap/extension-text-align' import Image from "@tiptap/extension-image"; import Heading from '@tiptap/extension-heading' import { Dropcursor } from '@tiptap/extensions' -import { EditorContent, useEditor, EditorContext } from "@tiptap/react"; +import { EditorContent, useEditor, EditorContext } from "@tiptap/react" import { Paper, Toolbar, @@ -17,6 +28,7 @@ import { } from '@mui/material'; import { Code as CodeIcon } from '@mui/icons-material'; import FormatBoldIcon from '@mui/icons-material/FormatBold'; +import FormatItalicIcon from '@mui/icons-material/FormatItalic'; import ImageIcon from '@mui/icons-material/Image'; @@ -28,6 +40,14 @@ function ContentEditor({ initialContent, contentUpdateCallback, disabled = false Text, CodeBlock, Bold, + BlockQuote, + BulletList, + ListItem, + Italic, + Link, + TextAlign.configure({ + types: ['paragraph', 'heading'], + }), Image.configure({ allowBase64: false, resize: { @@ -89,6 +109,46 @@ function ContentEditor({ initialContent, contentUpdateCallback, disabled = false + + editor.chain().focus().toggleItalic().run()} + size="small" + > + + + + + editor.chain().focus().toggleBulletList().run()} + size="small" + > + + + + + editor.chain().focus().setTextAlign('left').run()} + size="small" + > + + + + + editor.chain().focus().setTextAlign('center').run()} + size="small" + > + + + + + editor.chain().focus().setTextAlign('right').run()} + size="small" + > + + + { @@ -103,6 +163,39 @@ function ContentEditor({ initialContent, contentUpdateCallback, disabled = false + + { + if (editor.isActive('link')) { + const currentHref = editor.getAttributes('link').href || ''; + const url = window.prompt('Update URL (leave empty to remove)', currentHref); + if (!url) { + editor.chain().focus().unsetLink().run(); + return; + } + editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run(); + return; + } + const url = window.prompt('Enter URL'); + if (url) { + editor.chain().focus().toggleLink({ href: url }).run(); + } + }} + size="small" + label="Insert Link" + > + + + + + editor.chain().focus().toggleBlockquote().run()} + size="small" + label="Block Quote" + > + + + editor.chain().focus().toggleCodeBlock().run()} @@ -135,17 +228,25 @@ function ContentEditor({ initialContent, contentUpdateCallback, disabled = false } }, '& pre': { - backgroundColor: 'grey.100', + backgroundColor: 'grey.50', borderRadius: 1, padding: 2, margin: '16px 0', fontFamily: 'Monaco, Consolas, monospace', fontSize: '14px', border: '1px solid', - borderColor: 'grey.300' + borderColor: 'grey.100' }, '& strong': { fontWeight: 'bold' + }, + 'blockquote': { + borderLeft: direction == 'rtl' ? 'none' : '4px solid', + borderRight: direction == 'rtl' ? '4px solid' : 'none', + margin: '0px !important', + padding: '0 16px', + borderColor: 'grey.100', + } } }}