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',
+
}
}
}}