feat(email): text colour menu in WYSIWYG (LinumIQ blue + presets)
This commit is contained in:
@@ -2,8 +2,19 @@ import { useEditor, EditorContent } from "@tiptap/react";
|
|||||||
import StarterKit from "@tiptap/starter-kit";
|
import StarterKit from "@tiptap/starter-kit";
|
||||||
import Link from "@tiptap/extension-link";
|
import Link from "@tiptap/extension-link";
|
||||||
import Image from "@tiptap/extension-image";
|
import Image from "@tiptap/extension-image";
|
||||||
|
import { TextStyle } from "@tiptap/extension-text-style";
|
||||||
|
import { Color } from "@tiptap/extension-color";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const PRESET_COLORS = [
|
||||||
|
{ name: "Default", value: null as string | null },
|
||||||
|
{ name: "LinumIQ blue", value: "#0883DA" },
|
||||||
|
{ name: "Black", value: "#000000" },
|
||||||
|
{ name: "Grey", value: "#6d7175" },
|
||||||
|
{ name: "Red", value: "#d72c0d" },
|
||||||
|
{ name: "Green", value: "#1a7e3a" },
|
||||||
|
];
|
||||||
|
|
||||||
interface RichTextEditorProps {
|
interface RichTextEditorProps {
|
||||||
/** Hidden form field name; the rendered HTML is mirrored into it. */
|
/** Hidden form field name; the rendered HTML is mirrored into it. */
|
||||||
name: string;
|
name: string;
|
||||||
@@ -70,6 +81,8 @@ export function RichTextEditor({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
}).configure({ inline: false, allowBase64: true }),
|
}).configure({ inline: false, allowBase64: true }),
|
||||||
|
TextStyle,
|
||||||
|
Color,
|
||||||
],
|
],
|
||||||
content: swapCidToLogo(defaultValue || "<p></p>", logoDataUrl),
|
content: swapCidToLogo(defaultValue || "<p></p>", logoDataUrl),
|
||||||
editorProps: {
|
editorProps: {
|
||||||
@@ -190,6 +203,8 @@ export function RichTextEditor({
|
|||||||
title="Insert/remove link"
|
title="Insert/remove link"
|
||||||
/>
|
/>
|
||||||
<Sep />
|
<Sep />
|
||||||
|
<ColorMenu editor={editor} />
|
||||||
|
<Sep />
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
onClick={() => editor?.chain().focus().undo().run()}
|
onClick={() => editor?.chain().focus().undo().run()}
|
||||||
active={false}
|
active={false}
|
||||||
@@ -276,6 +291,51 @@ function Sep() {
|
|||||||
return <span aria-hidden style={{ width: 1, background: "#c9cccf", margin: "2px 4px" }} />;
|
return <span aria-hidden style={{ width: 1, background: "#c9cccf", margin: "2px 4px" }} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ColorMenu({ editor }: { editor: ReturnType<typeof useEditor> | null }) {
|
||||||
|
if (!editor) return null;
|
||||||
|
return (
|
||||||
|
<span style={{ display: "inline-flex", gap: 2, alignItems: "center" }} title="Text colour">
|
||||||
|
{PRESET_COLORS.map((c) => (
|
||||||
|
<button
|
||||||
|
key={c.name}
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
c.value
|
||||||
|
? editor.chain().focus().setColor(c.value).run()
|
||||||
|
: editor.chain().focus().unsetColor().run()
|
||||||
|
}
|
||||||
|
title={c.name}
|
||||||
|
style={{
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
border: "1px solid #c9cccf",
|
||||||
|
borderRadius: 4,
|
||||||
|
background: c.value ?? "#fff",
|
||||||
|
cursor: "pointer",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{c.value === null ? (
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
inset: 0,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
fontSize: 11,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces `src="cid:invoice-logo"` with the supplied URL so the editor
|
* Replaces `src="cid:invoice-logo"` with the supplied URL so the editor
|
||||||
* can display the actual logo. Done as a string replace because TipTap
|
* can display the actual logo. Done as a string replace because TipTap
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const DE_HTML = `\
|
const DE_HTML = `\
|
||||||
<h2 style="color:#3070c0;margin:0 0 8px;font-family:Arial,Helvetica,sans-serif;">{{companyName}}</h2>
|
<h2 style="margin:0 0 8px;font-family:Arial,Helvetica,sans-serif;"><span style="color:#0883DA">{{companyName}}</span></h2>
|
||||||
<h3 style="color:#3070c0;margin:0 0 16px;font-family:Arial,Helvetica,sans-serif;">Danke für deinen Einkauf!</h3>
|
<h3 style="margin:0 0 16px;font-family:Arial,Helvetica,sans-serif;"><span style="color:#0883DA">Danke für deinen Einkauf!</span></h3>
|
||||||
<p style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:1.5;">
|
<p style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:1.5;">
|
||||||
Die Rechnung befindet sich im Anhang.
|
Die Rechnung befindet sich im Anhang.
|
||||||
</p>
|
</p>
|
||||||
@@ -22,14 +22,14 @@ Besten Dank!
|
|||||||
<p style="margin-top:24px;">
|
<p style="margin-top:24px;">
|
||||||
<img src="cid:invoice-logo" alt="{{companyName}}" style="max-height:48px;">
|
<img src="cid:invoice-logo" alt="{{companyName}}" style="max-height:48px;">
|
||||||
</p>
|
</p>
|
||||||
<p style="font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:1.6;color:#3070c0;">
|
<p style="font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:1.6;color:#0883DA;">
|
||||||
✉ <a href="mailto:{{shopEmail}}" style="color:#3070c0;">Kontakt</a><br>
|
✉ <a href="mailto:{{shopEmail}}" style="color:#0883DA;">Kontakt</a><br>
|
||||||
🌐 <a href="{{shopWebsite}}" style="color:#3070c0;">{{shopWebsite}}</a>
|
🌐 <a href="{{shopWebsite}}" style="color:#0883DA;">{{shopWebsite}}</a>
|
||||||
</p>`;
|
</p>`;
|
||||||
|
|
||||||
const EN_HTML = `\
|
const EN_HTML = `\
|
||||||
<h2 style="color:#3070c0;margin:0 0 8px;font-family:Arial,Helvetica,sans-serif;">{{companyName}}</h2>
|
<h2 style="margin:0 0 8px;font-family:Arial,Helvetica,sans-serif;"><span style="color:#0883DA">{{companyName}}</span></h2>
|
||||||
<h3 style="color:#3070c0;margin:0 0 16px;font-family:Arial,Helvetica,sans-serif;">Thank you for your purchase!</h3>
|
<h3 style="margin:0 0 16px;font-family:Arial,Helvetica,sans-serif;"><span style="color:#0883DA">Thank you for your purchase!</span></h3>
|
||||||
<p style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:1.5;">
|
<p style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:1.5;">
|
||||||
Please find the invoice attached.
|
Please find the invoice attached.
|
||||||
</p>
|
</p>
|
||||||
@@ -41,9 +41,9 @@ Thanks a lot!
|
|||||||
<p style="margin-top:24px;">
|
<p style="margin-top:24px;">
|
||||||
<img src="cid:invoice-logo" alt="{{companyName}}" style="max-height:48px;">
|
<img src="cid:invoice-logo" alt="{{companyName}}" style="max-height:48px;">
|
||||||
</p>
|
</p>
|
||||||
<p style="font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:1.6;color:#3070c0;">
|
<p style="font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:1.6;color:#0883DA;">
|
||||||
✉ <a href="mailto:{{shopEmail}}" style="color:#3070c0;">Contact</a><br>
|
✉ <a href="mailto:{{shopEmail}}" style="color:#0883DA;">Contact</a><br>
|
||||||
🌐 <a href="{{shopWebsite}}" style="color:#3070c0;">{{shopWebsite}}</a>
|
🌐 <a href="{{shopWebsite}}" style="color:#0883DA;">{{shopWebsite}}</a>
|
||||||
</p>`;
|
</p>`;
|
||||||
|
|
||||||
export const DEFAULT_EMAIL_SUBJECT_DE = "Rechnung {{invoiceNumber}} – {{companyName}}";
|
export const DEFAULT_EMAIL_SUBJECT_DE = "Rechnung {{invoiceNumber}} – {{companyName}}";
|
||||||
|
|||||||
Generated
+28
@@ -18,8 +18,10 @@
|
|||||||
"@shopify/app-bridge-react": "^4.2.4",
|
"@shopify/app-bridge-react": "^4.2.4",
|
||||||
"@shopify/shopify-app-react-router": "^1.1.0",
|
"@shopify/shopify-app-react-router": "^1.1.0",
|
||||||
"@shopify/shopify-app-session-storage-prisma": "^8.0.0",
|
"@shopify/shopify-app-session-storage-prisma": "^8.0.0",
|
||||||
|
"@tiptap/extension-color": "^3.23.1",
|
||||||
"@tiptap/extension-image": "^3.23.1",
|
"@tiptap/extension-image": "^3.23.1",
|
||||||
"@tiptap/extension-link": "^3.23.1",
|
"@tiptap/extension-link": "^3.23.1",
|
||||||
|
"@tiptap/extension-text-style": "^3.23.1",
|
||||||
"@tiptap/pm": "^3.23.1",
|
"@tiptap/pm": "^3.23.1",
|
||||||
"@tiptap/react": "^3.23.1",
|
"@tiptap/react": "^3.23.1",
|
||||||
"@tiptap/starter-kit": "^3.23.1",
|
"@tiptap/starter-kit": "^3.23.1",
|
||||||
@@ -4262,6 +4264,19 @@
|
|||||||
"@tiptap/pm": "3.23.1"
|
"@tiptap/pm": "3.23.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tiptap/extension-color": {
|
||||||
|
"version": "3.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-3.23.1.tgz",
|
||||||
|
"integrity": "sha512-OYk/fT3h8Bz4B6GUVTQDvKGpPnpI5d6QHkuqjVhdFsgH3oo58PdLE1TdIGgeavuYPLaFxgBtEXmm3oTY9jPWxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tiptap/extension-text-style": "3.23.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tiptap/extension-document": {
|
"node_modules/@tiptap/extension-document": {
|
||||||
"version": "3.23.1",
|
"version": "3.23.1",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.23.1.tgz",
|
||||||
@@ -4492,6 +4507,19 @@
|
|||||||
"@tiptap/core": "3.23.1"
|
"@tiptap/core": "3.23.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tiptap/extension-text-style": {
|
||||||
|
"version": "3.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-3.23.1.tgz",
|
||||||
|
"integrity": "sha512-q3GQQo+lBhrtNkqdbhYWnv/byG/RYAxVnNhYPQMubRzavGdXBU8NhpJ/47YYjPimG1sahzcs2aqy7amVd8ri/A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tiptap/core": "3.23.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tiptap/extension-underline": {
|
"node_modules/@tiptap/extension-underline": {
|
||||||
"version": "3.23.1",
|
"version": "3.23.1",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.23.1.tgz",
|
||||||
|
|||||||
@@ -33,8 +33,10 @@
|
|||||||
"@shopify/app-bridge-react": "^4.2.4",
|
"@shopify/app-bridge-react": "^4.2.4",
|
||||||
"@shopify/shopify-app-react-router": "^1.1.0",
|
"@shopify/shopify-app-react-router": "^1.1.0",
|
||||||
"@shopify/shopify-app-session-storage-prisma": "^8.0.0",
|
"@shopify/shopify-app-session-storage-prisma": "^8.0.0",
|
||||||
|
"@tiptap/extension-color": "^3.23.1",
|
||||||
"@tiptap/extension-image": "^3.23.1",
|
"@tiptap/extension-image": "^3.23.1",
|
||||||
"@tiptap/extension-link": "^3.23.1",
|
"@tiptap/extension-link": "^3.23.1",
|
||||||
|
"@tiptap/extension-text-style": "^3.23.1",
|
||||||
"@tiptap/pm": "^3.23.1",
|
"@tiptap/pm": "^3.23.1",
|
||||||
"@tiptap/react": "^3.23.1",
|
"@tiptap/react": "^3.23.1",
|
||||||
"@tiptap/starter-kit": "^3.23.1",
|
"@tiptap/starter-kit": "^3.23.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user