@consilioweb/payload-support 0.8.1 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/components/RichTextEditor/index.cjs +233 -0
  2. package/dist/components/RichTextEditor/index.js +232 -0
  3. package/dist/components/TicketConversation/components/CodeBlock.cjs +24 -7
  4. package/dist/components/TicketConversation/components/CodeBlock.js +23 -9
  5. package/dist/index.cjs +19 -2
  6. package/dist/index.js +19 -2
  7. package/dist/views/BillingView/client.cjs +260 -103
  8. package/dist/views/BillingView/client.js +259 -103
  9. package/dist/views/ChatView/client.cjs +184 -137
  10. package/dist/views/ChatView/client.js +180 -137
  11. package/dist/views/CrmView/client.cjs +270 -122
  12. package/dist/views/CrmView/client.js +266 -122
  13. package/dist/views/EmailTrackingView/client.cjs +80 -69
  14. package/dist/views/EmailTrackingView/client.js +80 -70
  15. package/dist/views/ImportConversationView/client.cjs +127 -94
  16. package/dist/views/ImportConversationView/client.js +123 -94
  17. package/dist/views/LogsView/client.cjs +56 -58
  18. package/dist/views/LogsView/client.js +52 -58
  19. package/dist/views/NewTicketView/client.cjs +39 -55
  20. package/dist/views/NewTicketView/client.js +38 -55
  21. package/dist/views/PendingEmailsView/client.cjs +399 -102
  22. package/dist/views/PendingEmailsView/client.js +396 -103
  23. package/dist/views/SupportDashboardView/client.cjs +276 -137
  24. package/dist/views/SupportDashboardView/client.js +275 -137
  25. package/dist/views/TicketDetailView/client.cjs +487 -204
  26. package/dist/views/TicketDetailView/client.js +486 -204
  27. package/dist/views/TicketInboxView/client.cjs +62 -65
  28. package/dist/views/TicketInboxView/client.js +62 -66
  29. package/dist/views/TicketingSettingsView/client.cjs +10 -8
  30. package/dist/views/TicketingSettingsView/client.js +10 -8
  31. package/dist/views/TimeDashboardView/client.cjs +70 -59
  32. package/dist/views/TimeDashboardView/client.js +69 -59
  33. package/package.json +6 -2
  34. package/src/collections/ClientSummaries.ts +0 -1
  35. package/src/components/RichTextEditor/index.tsx +261 -0
  36. package/src/components/TicketConversation/components/CodeBlock.tsx +53 -14
  37. package/src/plugin.ts +2 -0
  38. package/src/utils/emailTemplate.ts +37 -0
  39. package/src/views/BillingView/client.tsx +362 -69
  40. package/src/views/ChatView/client.tsx +225 -140
  41. package/src/views/CrmView/client.tsx +447 -189
  42. package/src/views/EmailTrackingView/client.tsx +111 -71
  43. package/src/views/ImportConversationView/client.tsx +255 -70
  44. package/src/views/LogsView/client.tsx +85 -50
  45. package/src/views/NewTicketView/client.tsx +37 -53
  46. package/src/views/PendingEmailsView/client.tsx +512 -92
  47. package/src/views/SupportDashboardView/client.tsx +294 -134
  48. package/src/views/TicketDetailView/client.tsx +486 -213
  49. package/src/views/TicketInboxView/client.tsx +52 -61
  50. package/src/views/TicketingSettingsView/client.tsx +10 -9
  51. package/src/views/TimeDashboardView/client.tsx +184 -69
@@ -0,0 +1,233 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var react = require('react');
5
+
6
+ const RichTextEditor = react.forwardRef(function RichTextEditor2({
7
+ initialValue = "",
8
+ onChange,
9
+ placeholder = "\xC9crivez votre message...",
10
+ minHeight = 150,
11
+ onFileUpload,
12
+ borderColor = "#000",
13
+ focusBorderColor = "#00E5FF"
14
+ }, ref) {
15
+ const editorRef = react.useRef(null);
16
+ const fileInputRef = react.useRef(null);
17
+ const [focused, setFocused] = react.useState(false);
18
+ const [isEmpty, setIsEmpty] = react.useState(!initialValue);
19
+ react.useEffect(() => {
20
+ if (editorRef.current && initialValue) {
21
+ editorRef.current.innerHTML = initialValue;
22
+ const text = editorRef.current.innerText?.trim() || "";
23
+ setIsEmpty(!text && !editorRef.current.querySelector("img"));
24
+ }
25
+ }, []);
26
+ const emitChange = react.useCallback(() => {
27
+ if (!editorRef.current) return;
28
+ const html = editorRef.current.innerHTML;
29
+ const text = editorRef.current.innerText?.trim() || "";
30
+ const empty = !text && !editorRef.current.querySelector("img");
31
+ setIsEmpty(empty);
32
+ onChange(empty ? "" : html, text);
33
+ }, [onChange]);
34
+ const exec = react.useCallback((command, value) => {
35
+ document.execCommand(command, false, value);
36
+ editorRef.current?.focus();
37
+ setTimeout(emitChange, 0);
38
+ }, [emitChange]);
39
+ react.useImperativeHandle(ref, () => ({
40
+ clear: () => {
41
+ if (editorRef.current) {
42
+ editorRef.current.innerHTML = "";
43
+ setIsEmpty(true);
44
+ onChange("", "");
45
+ }
46
+ },
47
+ setContent: (html) => {
48
+ if (editorRef.current) {
49
+ editorRef.current.innerHTML = html;
50
+ const text = editorRef.current.innerText?.trim() || "";
51
+ setIsEmpty(!text && !editorRef.current.querySelector("img"));
52
+ onChange(html, text);
53
+ }
54
+ },
55
+ getHtml: () => editorRef.current?.innerHTML || "",
56
+ getPlainText: () => editorRef.current?.innerText?.trim() || "",
57
+ focus: () => editorRef.current?.focus()
58
+ }));
59
+ const handleInsertLink = react.useCallback(() => {
60
+ const sel = window.getSelection();
61
+ if (!sel || sel.isCollapsed) {
62
+ const url = prompt("URL du lien :");
63
+ if (!url) return;
64
+ const text = prompt("Texte du lien :", url) || url;
65
+ const safeUrl = url.replace(/"/g, """);
66
+ const safeText = text.replace(/</g, "&lt;").replace(/>/g, "&gt;");
67
+ document.execCommand("insertHTML", false, `<a href="${safeUrl}" target="_blank" rel="noopener noreferrer">${safeText}</a>`);
68
+ editorRef.current?.focus();
69
+ setTimeout(emitChange, 0);
70
+ } else {
71
+ const url = prompt("URL du lien :");
72
+ if (url) exec("createLink", url);
73
+ }
74
+ }, [exec, emitChange]);
75
+ const handleImageClick = react.useCallback(() => {
76
+ if (onFileUpload) {
77
+ fileInputRef.current?.click();
78
+ } else {
79
+ const url = prompt("URL de l'image :");
80
+ if (url) {
81
+ const safeUrl = url.replace(/"/g, "&quot;");
82
+ document.execCommand("insertHTML", false, `<img src="${safeUrl}" alt="Image" style="max-width:100%;height:auto;border-radius:8px;margin:8px 0;" />`);
83
+ editorRef.current?.focus();
84
+ setTimeout(emitChange, 0);
85
+ }
86
+ }
87
+ }, [onFileUpload, emitChange]);
88
+ const handleFileChange = react.useCallback(async (e) => {
89
+ if (!e.target.files || !onFileUpload) return;
90
+ for (const file of Array.from(e.target.files)) {
91
+ if (!file.type.startsWith("image/")) continue;
92
+ const url = await onFileUpload(file);
93
+ if (url) {
94
+ const safeName = file.name.replace(/"/g, "&quot;");
95
+ document.execCommand("insertHTML", false, `<img src="${url}" alt="${safeName}" style="max-width:100%;height:auto;border-radius:8px;margin:8px 0;" />`);
96
+ editorRef.current?.focus();
97
+ setTimeout(emitChange, 0);
98
+ }
99
+ }
100
+ if (fileInputRef.current) fileInputRef.current.value = "";
101
+ }, [onFileUpload, emitChange]);
102
+ const handlePaste = react.useCallback(async (e) => {
103
+ if (!onFileUpload) return;
104
+ const items = e.clipboardData?.items;
105
+ if (!items) return;
106
+ for (const item of Array.from(items)) {
107
+ if (item.type.startsWith("image/")) {
108
+ e.preventDefault();
109
+ const file = item.getAsFile();
110
+ if (file) {
111
+ const url = await onFileUpload(file);
112
+ if (url) {
113
+ document.execCommand("insertHTML", false, `<img src="${url}" alt="Image coll\xE9e" style="max-width:100%;height:auto;border-radius:8px;margin:8px 0;" />`);
114
+ editorRef.current?.focus();
115
+ setTimeout(emitChange, 0);
116
+ }
117
+ }
118
+ return;
119
+ }
120
+ }
121
+ }, [onFileUpload, emitChange]);
122
+ const btn = {
123
+ border: "none",
124
+ background: "transparent",
125
+ cursor: "pointer",
126
+ padding: "8px 10px",
127
+ fontSize: "13px",
128
+ fontWeight: 700,
129
+ color: "#555",
130
+ borderRadius: "5px",
131
+ lineHeight: 1,
132
+ minHeight: "44px",
133
+ minWidth: "44px",
134
+ display: "inline-flex",
135
+ alignItems: "center",
136
+ justifyContent: "center"
137
+ };
138
+ const sep = {
139
+ width: "1px",
140
+ height: "18px",
141
+ background: "#d1d5db",
142
+ margin: "0 4px",
143
+ alignSelf: "center"
144
+ };
145
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { border: `3px solid ${focused ? focusBorderColor : borderColor}`, borderRadius: "12px", overflow: "hidden", transition: "border-color 0.15s", background: "#fff" }, children: [
146
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
147
+ .rte-toolbar button:hover { background: #e5e7eb !important; }
148
+ .rte-editor blockquote { border-left: 4px solid #00E5FF; margin: 8px 0; padding: 8px 16px; background: #f0f9ff; border-radius: 0 6px 6px 0; }
149
+ .rte-editor img { max-width: 100%; height: auto; border-radius: 8px; margin: 8px 0; }
150
+ .rte-editor a { color: #00838f; text-decoration: underline; }
151
+ .rte-editor ul, .rte-editor ol { margin: 8px 0; padding-left: 24px; }
152
+ .rte-editor li { margin: 2px 0; }
153
+ .rte-editor p { margin: 0 0 6px 0; }
154
+ ` }),
155
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rte-toolbar", style: { display: "flex", alignItems: "center", gap: "2px", padding: "6px 10px", borderBottom: "2px solid #e5e7eb", backgroundColor: "#f9fafb", flexWrap: "wrap" }, children: [
156
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: btn, onMouseDown: (e) => {
157
+ e.preventDefault();
158
+ exec("bold");
159
+ }, title: "Gras (Ctrl+B)", children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "B" }) }),
160
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: btn, onMouseDown: (e) => {
161
+ e.preventDefault();
162
+ exec("italic");
163
+ }, title: "Italique (Ctrl+I)", children: /* @__PURE__ */ jsxRuntime.jsx("em", { style: { fontStyle: "italic" }, children: "I" }) }),
164
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: btn, onMouseDown: (e) => {
165
+ e.preventDefault();
166
+ exec("underline");
167
+ }, title: "Soulign\xE9 (Ctrl+U)", children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { textDecoration: "underline" }, children: "S" }) }),
168
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: sep }),
169
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, fontSize: "16px" }, onMouseDown: (e) => {
170
+ e.preventDefault();
171
+ exec("formatBlock", "blockquote");
172
+ }, title: "Citation", children: "\u201C\u201D" }),
173
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, fontSize: "12px" }, onMouseDown: (e) => {
174
+ e.preventDefault();
175
+ exec("insertUnorderedList");
176
+ }, title: "Liste \xE0 puces", children: "\u2022 Liste" }),
177
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, fontSize: "12px" }, onMouseDown: (e) => {
178
+ e.preventDefault();
179
+ exec("insertOrderedList");
180
+ }, title: "Liste num\xE9rot\xE9e", children: "1. Liste" }),
181
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: sep }),
182
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: btn, onMouseDown: (e) => {
183
+ e.preventDefault();
184
+ handleInsertLink();
185
+ }, title: "Ins\xE9rer un lien", children: "\u{1F517}" }),
186
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: btn, onMouseDown: (e) => {
187
+ e.preventDefault();
188
+ handleImageClick();
189
+ }, title: "Ins\xE9rer une image", children: "\u{1F5BC}\uFE0F" })
190
+ ] }),
191
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
192
+ isEmpty && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", top: 0, left: 0, right: 0, padding: "14px 16px", color: "#9ca3af", fontSize: "14px", pointerEvents: "none", userSelect: "none" }, children: placeholder }),
193
+ /* @__PURE__ */ jsxRuntime.jsx(
194
+ "div",
195
+ {
196
+ ref: editorRef,
197
+ className: "rte-editor",
198
+ contentEditable: true,
199
+ suppressContentEditableWarning: true,
200
+ onInput: emitChange,
201
+ onFocus: () => setFocused(true),
202
+ onBlur: () => {
203
+ setFocused(false);
204
+ emitChange();
205
+ },
206
+ onPaste: handlePaste,
207
+ style: {
208
+ minHeight: `${minHeight}px`,
209
+ padding: "14px 16px",
210
+ fontSize: "14px",
211
+ lineHeight: 1.6,
212
+ color: "#1f2937",
213
+ outline: "none",
214
+ overflowY: "auto"
215
+ }
216
+ }
217
+ )
218
+ ] }),
219
+ onFileUpload && /* @__PURE__ */ jsxRuntime.jsx(
220
+ "input",
221
+ {
222
+ ref: fileInputRef,
223
+ type: "file",
224
+ accept: "image/*",
225
+ multiple: true,
226
+ onChange: handleFileChange,
227
+ style: { display: "none" }
228
+ }
229
+ )
230
+ ] });
231
+ });
232
+
233
+ exports.RichTextEditor = RichTextEditor;
@@ -0,0 +1,232 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { forwardRef, useRef, useState, useEffect, useCallback, useImperativeHandle } from 'react';
4
+
5
+ const RichTextEditor = forwardRef(function RichTextEditor2({
6
+ initialValue = "",
7
+ onChange,
8
+ placeholder = "\xC9crivez votre message...",
9
+ minHeight = 150,
10
+ onFileUpload,
11
+ borderColor = "#000",
12
+ focusBorderColor = "#00E5FF"
13
+ }, ref) {
14
+ const editorRef = useRef(null);
15
+ const fileInputRef = useRef(null);
16
+ const [focused, setFocused] = useState(false);
17
+ const [isEmpty, setIsEmpty] = useState(!initialValue);
18
+ useEffect(() => {
19
+ if (editorRef.current && initialValue) {
20
+ editorRef.current.innerHTML = initialValue;
21
+ const text = editorRef.current.innerText?.trim() || "";
22
+ setIsEmpty(!text && !editorRef.current.querySelector("img"));
23
+ }
24
+ }, []);
25
+ const emitChange = useCallback(() => {
26
+ if (!editorRef.current) return;
27
+ const html = editorRef.current.innerHTML;
28
+ const text = editorRef.current.innerText?.trim() || "";
29
+ const empty = !text && !editorRef.current.querySelector("img");
30
+ setIsEmpty(empty);
31
+ onChange(empty ? "" : html, text);
32
+ }, [onChange]);
33
+ const exec = useCallback((command, value) => {
34
+ document.execCommand(command, false, value);
35
+ editorRef.current?.focus();
36
+ setTimeout(emitChange, 0);
37
+ }, [emitChange]);
38
+ useImperativeHandle(ref, () => ({
39
+ clear: () => {
40
+ if (editorRef.current) {
41
+ editorRef.current.innerHTML = "";
42
+ setIsEmpty(true);
43
+ onChange("", "");
44
+ }
45
+ },
46
+ setContent: (html) => {
47
+ if (editorRef.current) {
48
+ editorRef.current.innerHTML = html;
49
+ const text = editorRef.current.innerText?.trim() || "";
50
+ setIsEmpty(!text && !editorRef.current.querySelector("img"));
51
+ onChange(html, text);
52
+ }
53
+ },
54
+ getHtml: () => editorRef.current?.innerHTML || "",
55
+ getPlainText: () => editorRef.current?.innerText?.trim() || "",
56
+ focus: () => editorRef.current?.focus()
57
+ }));
58
+ const handleInsertLink = useCallback(() => {
59
+ const sel = window.getSelection();
60
+ if (!sel || sel.isCollapsed) {
61
+ const url = prompt("URL du lien :");
62
+ if (!url) return;
63
+ const text = prompt("Texte du lien :", url) || url;
64
+ const safeUrl = url.replace(/"/g, "&quot;");
65
+ const safeText = text.replace(/</g, "&lt;").replace(/>/g, "&gt;");
66
+ document.execCommand("insertHTML", false, `<a href="${safeUrl}" target="_blank" rel="noopener noreferrer">${safeText}</a>`);
67
+ editorRef.current?.focus();
68
+ setTimeout(emitChange, 0);
69
+ } else {
70
+ const url = prompt("URL du lien :");
71
+ if (url) exec("createLink", url);
72
+ }
73
+ }, [exec, emitChange]);
74
+ const handleImageClick = useCallback(() => {
75
+ if (onFileUpload) {
76
+ fileInputRef.current?.click();
77
+ } else {
78
+ const url = prompt("URL de l'image :");
79
+ if (url) {
80
+ const safeUrl = url.replace(/"/g, "&quot;");
81
+ document.execCommand("insertHTML", false, `<img src="${safeUrl}" alt="Image" style="max-width:100%;height:auto;border-radius:8px;margin:8px 0;" />`);
82
+ editorRef.current?.focus();
83
+ setTimeout(emitChange, 0);
84
+ }
85
+ }
86
+ }, [onFileUpload, emitChange]);
87
+ const handleFileChange = useCallback(async (e) => {
88
+ if (!e.target.files || !onFileUpload) return;
89
+ for (const file of Array.from(e.target.files)) {
90
+ if (!file.type.startsWith("image/")) continue;
91
+ const url = await onFileUpload(file);
92
+ if (url) {
93
+ const safeName = file.name.replace(/"/g, "&quot;");
94
+ document.execCommand("insertHTML", false, `<img src="${url}" alt="${safeName}" style="max-width:100%;height:auto;border-radius:8px;margin:8px 0;" />`);
95
+ editorRef.current?.focus();
96
+ setTimeout(emitChange, 0);
97
+ }
98
+ }
99
+ if (fileInputRef.current) fileInputRef.current.value = "";
100
+ }, [onFileUpload, emitChange]);
101
+ const handlePaste = useCallback(async (e) => {
102
+ if (!onFileUpload) return;
103
+ const items = e.clipboardData?.items;
104
+ if (!items) return;
105
+ for (const item of Array.from(items)) {
106
+ if (item.type.startsWith("image/")) {
107
+ e.preventDefault();
108
+ const file = item.getAsFile();
109
+ if (file) {
110
+ const url = await onFileUpload(file);
111
+ if (url) {
112
+ document.execCommand("insertHTML", false, `<img src="${url}" alt="Image coll\xE9e" style="max-width:100%;height:auto;border-radius:8px;margin:8px 0;" />`);
113
+ editorRef.current?.focus();
114
+ setTimeout(emitChange, 0);
115
+ }
116
+ }
117
+ return;
118
+ }
119
+ }
120
+ }, [onFileUpload, emitChange]);
121
+ const btn = {
122
+ border: "none",
123
+ background: "transparent",
124
+ cursor: "pointer",
125
+ padding: "8px 10px",
126
+ fontSize: "13px",
127
+ fontWeight: 700,
128
+ color: "#555",
129
+ borderRadius: "5px",
130
+ lineHeight: 1,
131
+ minHeight: "44px",
132
+ minWidth: "44px",
133
+ display: "inline-flex",
134
+ alignItems: "center",
135
+ justifyContent: "center"
136
+ };
137
+ const sep = {
138
+ width: "1px",
139
+ height: "18px",
140
+ background: "#d1d5db",
141
+ margin: "0 4px",
142
+ alignSelf: "center"
143
+ };
144
+ return /* @__PURE__ */ jsxs("div", { style: { border: `3px solid ${focused ? focusBorderColor : borderColor}`, borderRadius: "12px", overflow: "hidden", transition: "border-color 0.15s", background: "#fff" }, children: [
145
+ /* @__PURE__ */ jsx("style", { children: `
146
+ .rte-toolbar button:hover { background: #e5e7eb !important; }
147
+ .rte-editor blockquote { border-left: 4px solid #00E5FF; margin: 8px 0; padding: 8px 16px; background: #f0f9ff; border-radius: 0 6px 6px 0; }
148
+ .rte-editor img { max-width: 100%; height: auto; border-radius: 8px; margin: 8px 0; }
149
+ .rte-editor a { color: #00838f; text-decoration: underline; }
150
+ .rte-editor ul, .rte-editor ol { margin: 8px 0; padding-left: 24px; }
151
+ .rte-editor li { margin: 2px 0; }
152
+ .rte-editor p { margin: 0 0 6px 0; }
153
+ ` }),
154
+ /* @__PURE__ */ jsxs("div", { className: "rte-toolbar", style: { display: "flex", alignItems: "center", gap: "2px", padding: "6px 10px", borderBottom: "2px solid #e5e7eb", backgroundColor: "#f9fafb", flexWrap: "wrap" }, children: [
155
+ /* @__PURE__ */ jsx("button", { type: "button", style: btn, onMouseDown: (e) => {
156
+ e.preventDefault();
157
+ exec("bold");
158
+ }, title: "Gras (Ctrl+B)", children: /* @__PURE__ */ jsx("strong", { children: "B" }) }),
159
+ /* @__PURE__ */ jsx("button", { type: "button", style: btn, onMouseDown: (e) => {
160
+ e.preventDefault();
161
+ exec("italic");
162
+ }, title: "Italique (Ctrl+I)", children: /* @__PURE__ */ jsx("em", { style: { fontStyle: "italic" }, children: "I" }) }),
163
+ /* @__PURE__ */ jsx("button", { type: "button", style: btn, onMouseDown: (e) => {
164
+ e.preventDefault();
165
+ exec("underline");
166
+ }, title: "Soulign\xE9 (Ctrl+U)", children: /* @__PURE__ */ jsx("span", { style: { textDecoration: "underline" }, children: "S" }) }),
167
+ /* @__PURE__ */ jsx("span", { style: sep }),
168
+ /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, fontSize: "16px" }, onMouseDown: (e) => {
169
+ e.preventDefault();
170
+ exec("formatBlock", "blockquote");
171
+ }, title: "Citation", children: "\u201C\u201D" }),
172
+ /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, fontSize: "12px" }, onMouseDown: (e) => {
173
+ e.preventDefault();
174
+ exec("insertUnorderedList");
175
+ }, title: "Liste \xE0 puces", children: "\u2022 Liste" }),
176
+ /* @__PURE__ */ jsx("button", { type: "button", style: { ...btn, fontSize: "12px" }, onMouseDown: (e) => {
177
+ e.preventDefault();
178
+ exec("insertOrderedList");
179
+ }, title: "Liste num\xE9rot\xE9e", children: "1. Liste" }),
180
+ /* @__PURE__ */ jsx("span", { style: sep }),
181
+ /* @__PURE__ */ jsx("button", { type: "button", style: btn, onMouseDown: (e) => {
182
+ e.preventDefault();
183
+ handleInsertLink();
184
+ }, title: "Ins\xE9rer un lien", children: "\u{1F517}" }),
185
+ /* @__PURE__ */ jsx("button", { type: "button", style: btn, onMouseDown: (e) => {
186
+ e.preventDefault();
187
+ handleImageClick();
188
+ }, title: "Ins\xE9rer une image", children: "\u{1F5BC}\uFE0F" })
189
+ ] }),
190
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
191
+ isEmpty && /* @__PURE__ */ jsx("div", { style: { position: "absolute", top: 0, left: 0, right: 0, padding: "14px 16px", color: "#9ca3af", fontSize: "14px", pointerEvents: "none", userSelect: "none" }, children: placeholder }),
192
+ /* @__PURE__ */ jsx(
193
+ "div",
194
+ {
195
+ ref: editorRef,
196
+ className: "rte-editor",
197
+ contentEditable: true,
198
+ suppressContentEditableWarning: true,
199
+ onInput: emitChange,
200
+ onFocus: () => setFocused(true),
201
+ onBlur: () => {
202
+ setFocused(false);
203
+ emitChange();
204
+ },
205
+ onPaste: handlePaste,
206
+ style: {
207
+ minHeight: `${minHeight}px`,
208
+ padding: "14px 16px",
209
+ fontSize: "14px",
210
+ lineHeight: 1.6,
211
+ color: "#1f2937",
212
+ outline: "none",
213
+ overflowY: "auto"
214
+ }
215
+ }
216
+ )
217
+ ] }),
218
+ onFileUpload && /* @__PURE__ */ jsx(
219
+ "input",
220
+ {
221
+ ref: fileInputRef,
222
+ type: "file",
223
+ accept: "image/*",
224
+ multiple: true,
225
+ onChange: handleFileChange,
226
+ style: { display: "none" }
227
+ }
228
+ )
229
+ ] });
230
+ });
231
+
232
+ export { RichTextEditor };
@@ -129,11 +129,12 @@ function SingleCodeBlock({ lang, code }) {
129
129
  ] })
130
130
  ] });
131
131
  }
132
- function CodeBlockRenderer({ text }) {
132
+ function hasCodeBlocks(text) {
133
+ return /```[\s\S]*?```/.test(text);
134
+ }
135
+ function MessageWithCodeBlocks({ text, style }) {
133
136
  const parts = text.split(/(```[\s\S]*?```)/g);
134
- const hasCodeBlock = parts.some((p) => p.startsWith("```"));
135
- if (!hasCodeBlock) return null;
136
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: parts.map((part, i) => {
137
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style, children: parts.map((part, i) => {
137
138
  if (part.startsWith("```")) {
138
139
  const match = part.match(/^```(\w*)\n?([\s\S]*?)```$/);
139
140
  if (match) {
@@ -142,13 +143,29 @@ function CodeBlockRenderer({ text }) {
142
143
  return /* @__PURE__ */ jsxRuntime.jsx(SingleCodeBlock, { lang, code }, i);
143
144
  }
144
145
  }
145
- return null;
146
+ if (!part) return null;
147
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { whiteSpace: "pre-wrap" }, children: part }, i);
146
148
  }) });
147
149
  }
150
+ function htmlToText(html) {
151
+ return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div|li|h[1-6]|tr|pre)>/gi, "\n").replace(/<\/?(ul|ol|table|tbody|thead)[^>]*>/gi, "").replace(/<[^>]+>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/\n{3,}/g, "\n\n").trim();
152
+ }
153
+ function MessageWithCodeBlocksHtml({ html, style }) {
154
+ const text = htmlToText(html);
155
+ if (!hasCodeBlocks(text)) return null;
156
+ return /* @__PURE__ */ jsxRuntime.jsx(MessageWithCodeBlocks, { text, style });
157
+ }
158
+ function CodeBlockRenderer({ text }) {
159
+ if (!hasCodeBlocks(text)) return null;
160
+ return /* @__PURE__ */ jsxRuntime.jsx(MessageWithCodeBlocks, { text });
161
+ }
148
162
  function CodeBlockRendererHtml({ html }) {
149
- const stripped = html.replace(/<[^>]+>/g, "");
150
- return /* @__PURE__ */ jsxRuntime.jsx(CodeBlockRenderer, { text: stripped });
163
+ const text = htmlToText(html);
164
+ return /* @__PURE__ */ jsxRuntime.jsx(CodeBlockRenderer, { text });
151
165
  }
152
166
 
153
167
  exports.CodeBlockRenderer = CodeBlockRenderer;
154
168
  exports.CodeBlockRendererHtml = CodeBlockRendererHtml;
169
+ exports.MessageWithCodeBlocks = MessageWithCodeBlocks;
170
+ exports.MessageWithCodeBlocksHtml = MessageWithCodeBlocksHtml;
171
+ exports.hasCodeBlocks = hasCodeBlocks;
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
3
  import { useState } from 'react';
4
4
 
5
5
  const LANG_CONFIG = {
@@ -128,11 +128,12 @@ function SingleCodeBlock({ lang, code }) {
128
128
  ] })
129
129
  ] });
130
130
  }
131
- function CodeBlockRenderer({ text }) {
131
+ function hasCodeBlocks(text) {
132
+ return /```[\s\S]*?```/.test(text);
133
+ }
134
+ function MessageWithCodeBlocks({ text, style }) {
132
135
  const parts = text.split(/(```[\s\S]*?```)/g);
133
- const hasCodeBlock = parts.some((p) => p.startsWith("```"));
134
- if (!hasCodeBlock) return null;
135
- return /* @__PURE__ */ jsx(Fragment, { children: parts.map((part, i) => {
136
+ return /* @__PURE__ */ jsx("div", { style, children: parts.map((part, i) => {
136
137
  if (part.startsWith("```")) {
137
138
  const match = part.match(/^```(\w*)\n?([\s\S]*?)```$/);
138
139
  if (match) {
@@ -141,12 +142,25 @@ function CodeBlockRenderer({ text }) {
141
142
  return /* @__PURE__ */ jsx(SingleCodeBlock, { lang, code }, i);
142
143
  }
143
144
  }
144
- return null;
145
+ if (!part) return null;
146
+ return /* @__PURE__ */ jsx("span", { style: { whiteSpace: "pre-wrap" }, children: part }, i);
145
147
  }) });
146
148
  }
149
+ function htmlToText(html) {
150
+ return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div|li|h[1-6]|tr|pre)>/gi, "\n").replace(/<\/?(ul|ol|table|tbody|thead)[^>]*>/gi, "").replace(/<[^>]+>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/\n{3,}/g, "\n\n").trim();
151
+ }
152
+ function MessageWithCodeBlocksHtml({ html, style }) {
153
+ const text = htmlToText(html);
154
+ if (!hasCodeBlocks(text)) return null;
155
+ return /* @__PURE__ */ jsx(MessageWithCodeBlocks, { text, style });
156
+ }
157
+ function CodeBlockRenderer({ text }) {
158
+ if (!hasCodeBlocks(text)) return null;
159
+ return /* @__PURE__ */ jsx(MessageWithCodeBlocks, { text });
160
+ }
147
161
  function CodeBlockRendererHtml({ html }) {
148
- const stripped = html.replace(/<[^>]+>/g, "");
149
- return /* @__PURE__ */ jsx(CodeBlockRenderer, { text: stripped });
162
+ const text = htmlToText(html);
163
+ return /* @__PURE__ */ jsx(CodeBlockRenderer, { text });
150
164
  }
151
165
 
152
- export { CodeBlockRenderer, CodeBlockRendererHtml };
166
+ export { CodeBlockRenderer, CodeBlockRendererHtml, MessageWithCodeBlocks, MessageWithCodeBlocksHtml, hasCodeBlocks };
package/dist/index.cjs CHANGED
@@ -1454,7 +1454,23 @@ function emailRichContent(html, config) {
1454
1454
  return `<${tag}${cleanAttrs} style="${style}">`;
1455
1455
  });
1456
1456
  }
1457
+ function convertFencedCodeBlocks(input) {
1458
+ if (!input.includes("```")) return input;
1459
+ const normalized = input.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div)>\s*<(p|div)[^>]*>/gi, "\n").replace(/<(p|div)[^>]*>/gi, "").replace(/<\/(p|div)>/gi, "\n");
1460
+ return normalized.replace(/```(\w*)\n?([\s\S]*?)```/g, (_match, lang, code) => {
1461
+ const cleanCode = escapeHtml((code || "").replace(/\n$/, ""));
1462
+ const label = lang ? lang.trim() : "code";
1463
+ return `<div style="margin: 16px 0; border: 1px solid #1f2937; border-radius: 8px; overflow: hidden; background: #0f172a;">
1464
+ <div style="padding: 6px 14px; background: #1e293b; border-bottom: 1px solid #334155; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em;">${escapeHtml(label)}</div>
1465
+ <pre style="margin: 0; padding: 14px 16px; overflow-x: auto; background: #0f172a; color: #e2e8f0; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 13px; line-height: 1.6; white-space: pre;"><code style="background: transparent; padding: 0; color: inherit; font-family: inherit; font-size: inherit;">${cleanCode}</code></pre>
1466
+ </div>`;
1467
+ }).split(/(<div style="margin: 16px 0[\s\S]*?<\/div>)/g).map((chunk) => {
1468
+ if (chunk.startsWith('<div style="margin: 16px 0')) return chunk;
1469
+ return chunk.split("\n").map((line) => line.trim() ? `<p>${line}</p>` : "").join("");
1470
+ }).join("");
1471
+ }
1457
1472
  let result = html.replace(/src="\/([^"]+)"/g, `src="${baseUrl}/$1"`);
1473
+ result = convertFencedCodeBlocks(result);
1458
1474
  result = styleTag(result, "blockquote", `border-left: 4px solid ${c.brandColor}; margin: 16px 0; padding: 12px 20px; background: #f0f9fa; border-radius: 0 8px 8px 0;`);
1459
1475
  result = styleTag(result, "img", "max-width: 100%; height: auto; border-radius: 8px; margin: 12px 0; display: block;");
1460
1476
  result = styleTag(result, "a", `color: ${c.brandColor}; text-decoration: underline; font-weight: 600;`);
@@ -7927,7 +7943,6 @@ function createClientSummariesCollection(slugs) {
7927
7943
  type: "relationship",
7928
7944
  relationTo: slugs.supportClients,
7929
7945
  required: true,
7930
- unique: true,
7931
7946
  index: true,
7932
7947
  label: "Client"
7933
7948
  },
@@ -8075,7 +8090,9 @@ function supportPlugin(config) {
8075
8090
  "support-new-ticket": viewConfig(`${viewsBase}#NewTicketView`, `${bp}/new-ticket`),
8076
8091
  "support-settings": viewConfig(`${viewsBase}#TicketingSettingsView`, `${bp}/settings`),
8077
8092
  "support-logs": viewConfig(`${viewsBase}#LogsView`, `${bp}/logs`),
8078
- "support-crm": viewConfig(`${viewsBase}#CrmView`, `${bp}/crm`)
8093
+ "support-crm": viewConfig(`${viewsBase}#CrmView`, `${bp}/crm`),
8094
+ "support-billing": viewConfig(`${viewsBase}#BillingView`, `${bp}/billing`),
8095
+ "support-import": viewConfig(`${viewsBase}#ImportConversationView`, `/import-conversation`)
8079
8096
  };
8080
8097
  if (features.chat) {
8081
8098
  supportViews["support-chat"] = viewConfig(`${viewsBase}#ChatView`, `${bp}/chat`);
package/dist/index.js CHANGED
@@ -1448,7 +1448,23 @@ function emailRichContent(html, config) {
1448
1448
  return `<${tag}${cleanAttrs} style="${style}">`;
1449
1449
  });
1450
1450
  }
1451
+ function convertFencedCodeBlocks(input) {
1452
+ if (!input.includes("```")) return input;
1453
+ const normalized = input.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div)>\s*<(p|div)[^>]*>/gi, "\n").replace(/<(p|div)[^>]*>/gi, "").replace(/<\/(p|div)>/gi, "\n");
1454
+ return normalized.replace(/```(\w*)\n?([\s\S]*?)```/g, (_match, lang, code) => {
1455
+ const cleanCode = escapeHtml((code || "").replace(/\n$/, ""));
1456
+ const label = lang ? lang.trim() : "code";
1457
+ return `<div style="margin: 16px 0; border: 1px solid #1f2937; border-radius: 8px; overflow: hidden; background: #0f172a;">
1458
+ <div style="padding: 6px 14px; background: #1e293b; border-bottom: 1px solid #334155; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em;">${escapeHtml(label)}</div>
1459
+ <pre style="margin: 0; padding: 14px 16px; overflow-x: auto; background: #0f172a; color: #e2e8f0; font-family: 'SF Mono', 'Fira Code', Consolas, monospace; font-size: 13px; line-height: 1.6; white-space: pre;"><code style="background: transparent; padding: 0; color: inherit; font-family: inherit; font-size: inherit;">${cleanCode}</code></pre>
1460
+ </div>`;
1461
+ }).split(/(<div style="margin: 16px 0[\s\S]*?<\/div>)/g).map((chunk) => {
1462
+ if (chunk.startsWith('<div style="margin: 16px 0')) return chunk;
1463
+ return chunk.split("\n").map((line) => line.trim() ? `<p>${line}</p>` : "").join("");
1464
+ }).join("");
1465
+ }
1451
1466
  let result = html.replace(/src="\/([^"]+)"/g, `src="${baseUrl}/$1"`);
1467
+ result = convertFencedCodeBlocks(result);
1452
1468
  result = styleTag(result, "blockquote", `border-left: 4px solid ${c.brandColor}; margin: 16px 0; padding: 12px 20px; background: #f0f9fa; border-radius: 0 8px 8px 0;`);
1453
1469
  result = styleTag(result, "img", "max-width: 100%; height: auto; border-radius: 8px; margin: 12px 0; display: block;");
1454
1470
  result = styleTag(result, "a", `color: ${c.brandColor}; text-decoration: underline; font-weight: 600;`);
@@ -7921,7 +7937,6 @@ function createClientSummariesCollection(slugs) {
7921
7937
  type: "relationship",
7922
7938
  relationTo: slugs.supportClients,
7923
7939
  required: true,
7924
- unique: true,
7925
7940
  index: true,
7926
7941
  label: "Client"
7927
7942
  },
@@ -8069,7 +8084,9 @@ function supportPlugin(config) {
8069
8084
  "support-new-ticket": viewConfig(`${viewsBase}#NewTicketView`, `${bp}/new-ticket`),
8070
8085
  "support-settings": viewConfig(`${viewsBase}#TicketingSettingsView`, `${bp}/settings`),
8071
8086
  "support-logs": viewConfig(`${viewsBase}#LogsView`, `${bp}/logs`),
8072
- "support-crm": viewConfig(`${viewsBase}#CrmView`, `${bp}/crm`)
8087
+ "support-crm": viewConfig(`${viewsBase}#CrmView`, `${bp}/crm`),
8088
+ "support-billing": viewConfig(`${viewsBase}#BillingView`, `${bp}/billing`),
8089
+ "support-import": viewConfig(`${viewsBase}#ImportConversationView`, `/import-conversation`)
8073
8090
  };
8074
8091
  if (features.chat) {
8075
8092
  supportViews["support-chat"] = viewConfig(`${viewsBase}#ChatView`, `${bp}/chat`);