@burdenoff/microfe-channel 2026.830.1 → 2026.1013.1
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.
- package/dist/channel/ChannelRoot.d.ts.map +1 -1
- package/dist/channel/ChannelRoot.js +32 -35
- package/dist/channel/ChannelRoot.js.map +1 -1
- package/dist/channel/components/AdapterCredentialsForm.d.ts.map +1 -1
- package/dist/channel/components/AdapterCredentialsForm.js +52 -58
- package/dist/channel/components/AdapterCredentialsForm.js.map +1 -1
- package/dist/channel/components/FeatureNotEnabled.d.ts.map +1 -1
- package/dist/channel/components/FeatureNotEnabled.js +27 -22
- package/dist/channel/components/FeatureNotEnabled.js.map +1 -1
- package/dist/channel/components/MarkdownEditor.d.ts.map +1 -1
- package/dist/channel/components/MarkdownEditor.js +78 -77
- package/dist/channel/components/MarkdownEditor.js.map +1 -1
- package/dist/channel/components/PermissionGuard.d.ts.map +1 -1
- package/dist/channel/components/PermissionGuard.js +6 -9
- package/dist/channel/components/PermissionGuard.js.map +1 -1
- package/dist/channel/hooks/useTr.d.ts +17 -0
- package/dist/channel/hooks/useTr.d.ts.map +1 -0
- package/dist/channel/hooks/useTr.js +14 -0
- package/dist/channel/hooks/useTr.js.map +1 -0
- package/dist/channel/modules/channels/i18n.js +24 -24
- package/dist/channel/modules/channels/i18n.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +2 -2
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.d.ts.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +232 -226
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.d.ts.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js +152 -146
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.d.ts.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +112 -114
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateChannelPage.d.ts.map +1 -1
- package/dist/channel/modules/channels/pages/CreateChannelPage.js +300 -269
- package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateConnectionPage.d.ts.map +1 -1
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js +108 -102
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditChannelPage.d.ts.map +1 -1
- package/dist/channel/modules/channels/pages/EditChannelPage.js +384 -343
- package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/SendMessagePage.d.ts.map +1 -1
- package/dist/channel/modules/channels/pages/SendMessagePage.js +170 -156
- package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
- package/dist/channel/pages/ChannelLandingPage.d.ts.map +1 -1
- package/dist/channel/pages/ChannelLandingPage.js +47 -50
- package/dist/channel/pages/ChannelLandingPage.js.map +1 -1
- package/dist/translations/en.d.ts +271 -4
- package/dist/translations/en.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { useTr as e } from "../hooks/useTr.js";
|
|
2
|
+
import { useRef as t, useState as n } from "react";
|
|
3
|
+
import { Bold as r, Code as i, Italic as a, Link as o, List as s } from "lucide-react";
|
|
4
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
5
|
+
import { Markdown as u } from "@burdenoff/fe-libs/shared/components/Markdown";
|
|
5
6
|
//#region src/channel/components/MarkdownEditor.tsx
|
|
6
|
-
var
|
|
7
|
+
var d = (e, t, n) => (r, i, a) => {
|
|
7
8
|
let o = r.slice(0, i), s = r.slice(i, a), c = r.slice(a);
|
|
8
9
|
if (s.length === 0) {
|
|
9
10
|
let r = `${e}${n}${t}`, a = i + e.length, s = a + n.length;
|
|
@@ -19,14 +20,14 @@ var u = (e, t, n) => (r, i, a) => {
|
|
|
19
20
|
cursorStart: u,
|
|
20
21
|
cursorEnd: u
|
|
21
22
|
};
|
|
22
|
-
},
|
|
23
|
+
}, f = (e, t, n) => {
|
|
23
24
|
let r = e.slice(0, t), i = e.slice(t, n), a = e.slice(n), o = `[${i.length > 0 ? i : "text"}](https://example.com)`, s = t + o.length - 1, c = s + 1;
|
|
24
25
|
return {
|
|
25
26
|
nextValue: `${r}${o}${a}`,
|
|
26
27
|
cursorStart: s,
|
|
27
28
|
cursorEnd: c
|
|
28
29
|
};
|
|
29
|
-
},
|
|
30
|
+
}, p = (e, t, n) => {
|
|
30
31
|
let r = e.slice(0, t), i = e.slice(t, n), a = e.slice(n);
|
|
31
32
|
if (i.length === 0) {
|
|
32
33
|
let e = t + 2;
|
|
@@ -43,123 +44,123 @@ var u = (e, t, n) => (r, i, a) => {
|
|
|
43
44
|
cursorEnd: t + o.length
|
|
44
45
|
};
|
|
45
46
|
};
|
|
46
|
-
function
|
|
47
|
-
let [
|
|
48
|
-
let t =
|
|
47
|
+
function m({ value: m, onChange: h, label: g, placeholder: _, rows: v = 5, "aria-label": y, className: b = "", textareaClassName: x = "", id: S }) {
|
|
48
|
+
let [C, w] = n("write"), T = t(null), E = e(), D = (e) => {
|
|
49
|
+
let t = T.current;
|
|
49
50
|
if (!t) return;
|
|
50
|
-
let { nextValue: n, cursorStart: r, cursorEnd: i } = e(
|
|
51
|
-
|
|
51
|
+
let { nextValue: n, cursorStart: r, cursorEnd: i } = e(m, t.selectionStart ?? 0, t.selectionEnd ?? 0);
|
|
52
|
+
h(n), requestAnimationFrame(() => {
|
|
52
53
|
t.focus(), t.setSelectionRange(r, i);
|
|
53
54
|
});
|
|
54
|
-
},
|
|
55
|
+
}, O = (e) => {
|
|
55
56
|
switch (e) {
|
|
56
57
|
case "bold":
|
|
57
|
-
|
|
58
|
+
D(d("**", "**", "bold text"));
|
|
58
59
|
break;
|
|
59
60
|
case "italic":
|
|
60
|
-
|
|
61
|
+
D(d("*", "*", "italic text"));
|
|
61
62
|
break;
|
|
62
63
|
case "link":
|
|
63
|
-
|
|
64
|
+
D(f);
|
|
64
65
|
break;
|
|
65
66
|
case "code":
|
|
66
|
-
|
|
67
|
+
D(d("`", "`", "code"));
|
|
67
68
|
break;
|
|
68
69
|
case "list":
|
|
69
|
-
|
|
70
|
+
D(p);
|
|
70
71
|
break;
|
|
71
72
|
}
|
|
72
73
|
};
|
|
73
|
-
return /* @__PURE__ */
|
|
74
|
+
return /* @__PURE__ */ l("div", {
|
|
74
75
|
className: "space-y-2",
|
|
75
|
-
children: [
|
|
76
|
-
htmlFor:
|
|
76
|
+
children: [g && /* @__PURE__ */ c("label", {
|
|
77
|
+
htmlFor: S,
|
|
77
78
|
className: "text-sm font-medium text-text-primary",
|
|
78
|
-
children:
|
|
79
|
-
}), /* @__PURE__ */
|
|
80
|
-
className: `border border-border-subtle rounded-lg overflow-hidden bg-bg-surface focus-within:ring-2 focus-within:ring-primary ${
|
|
81
|
-
children: [/* @__PURE__ */
|
|
79
|
+
children: g
|
|
80
|
+
}), /* @__PURE__ */ l("div", {
|
|
81
|
+
className: `border border-border-subtle rounded-lg overflow-hidden bg-bg-surface focus-within:ring-2 focus-within:ring-primary ${b}`,
|
|
82
|
+
children: [/* @__PURE__ */ l("div", {
|
|
82
83
|
className: "flex items-center justify-between border-b border-border-subtle bg-bg-sunken px-2 py-1",
|
|
83
|
-
children: [/* @__PURE__ */
|
|
84
|
+
children: [/* @__PURE__ */ l("div", {
|
|
84
85
|
className: "flex items-center gap-1",
|
|
85
86
|
children: [
|
|
86
|
-
/* @__PURE__ */
|
|
87
|
+
/* @__PURE__ */ c("button", {
|
|
87
88
|
type: "button",
|
|
88
|
-
onClick: () =>
|
|
89
|
-
"aria-label": "Bold",
|
|
90
|
-
title: "Bold",
|
|
89
|
+
onClick: () => O("bold"),
|
|
90
|
+
"aria-label": E("channel.markdown.bold", "Bold"),
|
|
91
|
+
title: E("channel.markdown.bold", "Bold"),
|
|
91
92
|
className: "p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors",
|
|
92
|
-
children: /* @__PURE__ */
|
|
93
|
+
children: /* @__PURE__ */ c(r, { className: "size-4" })
|
|
93
94
|
}),
|
|
94
|
-
/* @__PURE__ */
|
|
95
|
+
/* @__PURE__ */ c("button", {
|
|
95
96
|
type: "button",
|
|
96
|
-
onClick: () =>
|
|
97
|
-
"aria-label": "Italic",
|
|
98
|
-
title: "Italic",
|
|
97
|
+
onClick: () => O("italic"),
|
|
98
|
+
"aria-label": E("channel.markdown.italic", "Italic"),
|
|
99
|
+
title: E("channel.markdown.italic", "Italic"),
|
|
99
100
|
className: "p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors",
|
|
100
|
-
children: /* @__PURE__ */
|
|
101
|
+
children: /* @__PURE__ */ c(a, { className: "size-4" })
|
|
101
102
|
}),
|
|
102
|
-
/* @__PURE__ */
|
|
103
|
+
/* @__PURE__ */ c("button", {
|
|
103
104
|
type: "button",
|
|
104
|
-
onClick: () =>
|
|
105
|
-
"aria-label": "Link",
|
|
106
|
-
title: "Link",
|
|
105
|
+
onClick: () => O("link"),
|
|
106
|
+
"aria-label": E("channel.markdown.link", "Link"),
|
|
107
|
+
title: E("channel.markdown.link", "Link"),
|
|
107
108
|
className: "p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors",
|
|
108
|
-
children: /* @__PURE__ */
|
|
109
|
+
children: /* @__PURE__ */ c(o, { className: "size-4" })
|
|
109
110
|
}),
|
|
110
|
-
/* @__PURE__ */
|
|
111
|
+
/* @__PURE__ */ c("button", {
|
|
111
112
|
type: "button",
|
|
112
|
-
onClick: () =>
|
|
113
|
-
"aria-label": "Code",
|
|
114
|
-
title: "Code",
|
|
113
|
+
onClick: () => O("code"),
|
|
114
|
+
"aria-label": E("channel.markdown.code", "Code"),
|
|
115
|
+
title: E("channel.markdown.code", "Code"),
|
|
115
116
|
className: "p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors",
|
|
116
|
-
children: /* @__PURE__ */
|
|
117
|
+
children: /* @__PURE__ */ c(i, { className: "size-4" })
|
|
117
118
|
}),
|
|
118
|
-
/* @__PURE__ */
|
|
119
|
+
/* @__PURE__ */ c("button", {
|
|
119
120
|
type: "button",
|
|
120
|
-
onClick: () =>
|
|
121
|
-
"aria-label": "Bullet list",
|
|
122
|
-
title: "Bullet list",
|
|
121
|
+
onClick: () => O("list"),
|
|
122
|
+
"aria-label": E("channel.markdown.bulletList", "Bullet list"),
|
|
123
|
+
title: E("channel.markdown.bulletList", "Bullet list"),
|
|
123
124
|
className: "p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors",
|
|
124
|
-
children: /* @__PURE__ */ s
|
|
125
|
+
children: /* @__PURE__ */ c(s, { className: "size-4" })
|
|
125
126
|
})
|
|
126
127
|
]
|
|
127
|
-
}), /* @__PURE__ */
|
|
128
|
+
}), /* @__PURE__ */ l("div", {
|
|
128
129
|
className: "flex items-center gap-1",
|
|
129
130
|
role: "tablist",
|
|
130
|
-
"aria-label": "Markdown editor tabs",
|
|
131
|
-
children: [/* @__PURE__ */
|
|
131
|
+
"aria-label": E("channel.markdown.tabsLabel", "Markdown editor tabs"),
|
|
132
|
+
children: [/* @__PURE__ */ c("button", {
|
|
132
133
|
type: "button",
|
|
133
134
|
role: "tab",
|
|
134
|
-
"aria-selected":
|
|
135
|
-
onClick: () =>
|
|
136
|
-
className: `px-3 py-1 text-xs font-medium rounded-md transition-colors ${
|
|
137
|
-
children: "Write"
|
|
138
|
-
}), /* @__PURE__ */
|
|
135
|
+
"aria-selected": C === "write",
|
|
136
|
+
onClick: () => w("write"),
|
|
137
|
+
className: `px-3 py-1 text-xs font-medium rounded-md transition-colors ${C === "write" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary hover:bg-bg-surface"}`,
|
|
138
|
+
children: E("channel.markdown.write", "Write")
|
|
139
|
+
}), /* @__PURE__ */ c("button", {
|
|
139
140
|
type: "button",
|
|
140
141
|
role: "tab",
|
|
141
|
-
"aria-selected":
|
|
142
|
-
onClick: () =>
|
|
143
|
-
className: `px-3 py-1 text-xs font-medium rounded-md transition-colors ${
|
|
144
|
-
children: "Preview"
|
|
142
|
+
"aria-selected": C === "preview",
|
|
143
|
+
onClick: () => w("preview"),
|
|
144
|
+
className: `px-3 py-1 text-xs font-medium rounded-md transition-colors ${C === "preview" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary hover:bg-bg-surface"}`,
|
|
145
|
+
children: E("channel.markdown.preview", "Preview")
|
|
145
146
|
})]
|
|
146
147
|
})]
|
|
147
|
-
}), /* @__PURE__ */
|
|
148
|
+
}), /* @__PURE__ */ c("div", {
|
|
148
149
|
className: "relative",
|
|
149
|
-
children:
|
|
150
|
-
ref:
|
|
151
|
-
id:
|
|
152
|
-
"aria-label":
|
|
153
|
-
value:
|
|
154
|
-
onChange: (e) =>
|
|
155
|
-
placeholder:
|
|
156
|
-
rows:
|
|
157
|
-
className: `w-full px-4 py-2 bg-bg-surface text-text-primary placeholder:text-text-secondary resize-none focus:outline-none ${
|
|
158
|
-
}) : /* @__PURE__ */
|
|
150
|
+
children: C === "write" ? /* @__PURE__ */ c("textarea", {
|
|
151
|
+
ref: T,
|
|
152
|
+
id: S,
|
|
153
|
+
"aria-label": y,
|
|
154
|
+
value: m,
|
|
155
|
+
onChange: (e) => h(e.target.value),
|
|
156
|
+
placeholder: _,
|
|
157
|
+
rows: v,
|
|
158
|
+
className: `w-full px-4 py-2 bg-bg-surface text-text-primary placeholder:text-text-secondary resize-none focus:outline-none ${x}`
|
|
159
|
+
}) : /* @__PURE__ */ c("div", {
|
|
159
160
|
className: "min-h-[theme(spacing.32)] max-h-96 overflow-auto px-4 py-2 text-sm text-text-primary",
|
|
160
|
-
children:
|
|
161
|
+
children: m.trim() ? /* @__PURE__ */ c(u, { children: m }) : /* @__PURE__ */ c("p", {
|
|
161
162
|
className: "text-text-secondary italic",
|
|
162
|
-
children:
|
|
163
|
+
children: _ || E("channel.markdown.nothingToPreview", "Nothing to preview yet.")
|
|
163
164
|
})
|
|
164
165
|
})
|
|
165
166
|
})]
|
|
@@ -167,6 +168,6 @@ function p({ value: p, onChange: m, label: h, placeholder: g, rows: _ = 5, "aria
|
|
|
167
168
|
});
|
|
168
169
|
}
|
|
169
170
|
//#endregion
|
|
170
|
-
export {
|
|
171
|
+
export { m as MarkdownEditor };
|
|
171
172
|
|
|
172
173
|
//# sourceMappingURL=MarkdownEditor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkdownEditor.js","names":[],"sources":["../../../src/channel/components/MarkdownEditor.tsx"],"sourcesContent":["import { useRef, useState } from 'react';\nimport { Bold, Italic, Link, Code, List } from 'lucide-react';\nimport { Markdown } from '@burdenoff/fe-libs/shared/components/Markdown';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface MarkdownEditorProps {\n /** Current markdown value. */\n value: string;\n /** Called whenever the markdown value changes. */\n onChange: (value: string) => void;\n /** Optional visible label. */\n label?: string;\n /** Placeholder for the write tab textarea. */\n placeholder?: string;\n /** Approximate visible rows for the textarea. */\n rows?: number;\n /** Accessible label for the textarea (used when no visible label). */\n 'aria-label'?: string;\n /** Additional classes for the outer container. */\n className?: string;\n /** Additional classes for the textarea. */\n textareaClassName?: string;\n /** Unique id, forwarded to the textarea. */\n id?: string;\n}\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\ntype InsertAction = (\n value: string,\n selectionStart: number,\n selectionEnd: number\n) => { nextValue: string; cursorStart: number; cursorEnd: number };\n\nconst wrapWith = (prefix: string, suffix: string, fallback: string): InsertAction => {\n return (value, start, end) => {\n const before = value.slice(0, start);\n const selected = value.slice(start, end);\n const after = value.slice(end);\n\n if (selected.length === 0) {\n const insertion = `${prefix}${fallback}${suffix}`;\n const cursorStart = start + prefix.length;\n const cursorEnd = cursorStart + fallback.length;\n return { nextValue: `${before}${insertion}${after}`, cursorStart, cursorEnd };\n }\n\n const wrapped = `${prefix}${selected}${suffix}`;\n const cursorStart = start + wrapped.length;\n return { nextValue: `${before}${wrapped}${after}`, cursorStart, cursorEnd: cursorStart };\n };\n};\n\nconst insertLink: InsertAction = (value, start, end) => {\n const before = value.slice(0, start);\n const selected = value.slice(start, end);\n const after = value.slice(end);\n\n const linkText = selected.length > 0 ? selected : 'text';\n const insertion = `[${linkText}](https://example.com)`;\n const cursorStart = start + insertion.length - 1;\n const cursorEnd = cursorStart + 1;\n return { nextValue: `${before}${insertion}${after}`, cursorStart, cursorEnd };\n};\n\nconst insertList: InsertAction = (value, start, end) => {\n const before = value.slice(0, start);\n const selected = value.slice(start, end);\n const after = value.slice(end);\n\n if (selected.length === 0) {\n const insertion = '- ';\n const cursorStart = start + insertion.length;\n return { nextValue: `${before}${insertion}${after}`, cursorStart, cursorEnd: cursorStart };\n }\n\n const lines = selected.split('\\n');\n const listified = lines.map((line) => (line.startsWith('- ') ? line : `- ${line}`)).join('\\n');\n const nextValue = `${before}${listified}${after}`;\n const cursorStart = start;\n const cursorEnd = start + listified.length;\n return { nextValue, cursorStart, cursorEnd };\n};\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function MarkdownEditor({\n value,\n onChange,\n label,\n placeholder,\n rows = 5,\n 'aria-label': ariaLabel,\n className = '',\n textareaClassName = '',\n id,\n}: MarkdownEditorProps) {\n const [activeTab, setActiveTab] = useState<'write' | 'preview'>('write');\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n\n const apply = (action: InsertAction) => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n\n const start = textarea.selectionStart ?? 0;\n const end = textarea.selectionEnd ?? 0;\n const { nextValue, cursorStart, cursorEnd } = action(value, start, end);\n\n onChange(nextValue);\n\n // Re-focus and restore selection after the next render has applied the value.\n requestAnimationFrame(() => {\n textarea.focus();\n textarea.setSelectionRange(cursorStart, cursorEnd);\n });\n };\n\n const handleToolbarAction = (action: 'bold' | 'italic' | 'link' | 'code' | 'list') => {\n switch (action) {\n case 'bold':\n apply(wrapWith('**', '**', 'bold text'));\n break;\n case 'italic':\n apply(wrapWith('*', '*', 'italic text'));\n break;\n case 'link':\n apply(insertLink);\n break;\n case 'code':\n apply(wrapWith('`', '`', 'code'));\n break;\n case 'list':\n apply(insertList);\n break;\n }\n };\n\n const containerBase =\n 'border border-border-subtle rounded-lg overflow-hidden bg-bg-surface focus-within:ring-2 focus-within:ring-primary';\n const textareaBase =\n 'w-full px-4 py-2 bg-bg-surface text-text-primary placeholder:text-text-secondary resize-none focus:outline-none';\n\n return (\n <div className=\"space-y-2\">\n {label && (\n <label htmlFor={id} className=\"text-sm font-medium text-text-primary\">\n {label}\n </label>\n )}\n <div className={`${containerBase} ${className}`}>\n <div className=\"flex items-center justify-between border-b border-border-subtle bg-bg-sunken px-2 py-1\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('bold')}\n aria-label=\"Bold\"\n title=\"Bold\"\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Bold className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('italic')}\n aria-label=\"Italic\"\n title=\"Italic\"\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Italic className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('link')}\n aria-label=\"Link\"\n title=\"Link\"\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Link className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('code')}\n aria-label=\"Code\"\n title=\"Code\"\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Code className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('list')}\n aria-label=\"Bullet list\"\n title=\"Bullet list\"\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <List className=\"size-4\" />\n </button>\n </div>\n\n <div className=\"flex items-center gap-1\" role=\"tablist\" aria-label=\"Markdown editor tabs\">\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === 'write'}\n onClick={() => setActiveTab('write')}\n className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${\n activeTab === 'write'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface'\n }`}\n >\n Write\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === 'preview'}\n onClick={() => setActiveTab('preview')}\n className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${\n activeTab === 'preview'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface'\n }`}\n >\n Preview\n </button>\n </div>\n </div>\n\n <div className=\"relative\">\n {activeTab === 'write' ? (\n <textarea\n ref={textareaRef}\n id={id}\n aria-label={ariaLabel}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n placeholder={placeholder}\n rows={rows}\n className={`${textareaBase} ${textareaClassName}`}\n />\n ) : (\n <div className=\"min-h-[theme(spacing.32)] max-h-96 overflow-auto px-4 py-2 text-sm text-text-primary\">\n {value.trim() ? (\n <Markdown>{value}</Markdown>\n ) : (\n <p className=\"text-text-secondary italic\">\n {placeholder || 'Nothing to preview yet.'}\n </p>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MarkdownEditor.js","names":[],"sources":["../../../src/channel/components/MarkdownEditor.tsx"],"sourcesContent":["import { useRef, useState } from 'react';\nimport { Bold, Italic, Link, Code, List } from 'lucide-react';\nimport { Markdown } from '@burdenoff/fe-libs/shared/components/Markdown';\nimport { useTr } from '../hooks/useTr';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface MarkdownEditorProps {\n /** Current markdown value. */\n value: string;\n /** Called whenever the markdown value changes. */\n onChange: (value: string) => void;\n /** Optional visible label. */\n label?: string;\n /** Placeholder for the write tab textarea. */\n placeholder?: string;\n /** Approximate visible rows for the textarea. */\n rows?: number;\n /** Accessible label for the textarea (used when no visible label). */\n 'aria-label'?: string;\n /** Additional classes for the outer container. */\n className?: string;\n /** Additional classes for the textarea. */\n textareaClassName?: string;\n /** Unique id, forwarded to the textarea. */\n id?: string;\n}\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\ntype InsertAction = (\n value: string,\n selectionStart: number,\n selectionEnd: number\n) => { nextValue: string; cursorStart: number; cursorEnd: number };\n\nconst wrapWith = (prefix: string, suffix: string, fallback: string): InsertAction => {\n return (value, start, end) => {\n const before = value.slice(0, start);\n const selected = value.slice(start, end);\n const after = value.slice(end);\n\n if (selected.length === 0) {\n const insertion = `${prefix}${fallback}${suffix}`;\n const cursorStart = start + prefix.length;\n const cursorEnd = cursorStart + fallback.length;\n return { nextValue: `${before}${insertion}${after}`, cursorStart, cursorEnd };\n }\n\n const wrapped = `${prefix}${selected}${suffix}`;\n const cursorStart = start + wrapped.length;\n return { nextValue: `${before}${wrapped}${after}`, cursorStart, cursorEnd: cursorStart };\n };\n};\n\nconst insertLink: InsertAction = (value, start, end) => {\n const before = value.slice(0, start);\n const selected = value.slice(start, end);\n const after = value.slice(end);\n\n const linkText = selected.length > 0 ? selected : 'text';\n const insertion = `[${linkText}](https://example.com)`;\n const cursorStart = start + insertion.length - 1;\n const cursorEnd = cursorStart + 1;\n return { nextValue: `${before}${insertion}${after}`, cursorStart, cursorEnd };\n};\n\nconst insertList: InsertAction = (value, start, end) => {\n const before = value.slice(0, start);\n const selected = value.slice(start, end);\n const after = value.slice(end);\n\n if (selected.length === 0) {\n const insertion = '- ';\n const cursorStart = start + insertion.length;\n return { nextValue: `${before}${insertion}${after}`, cursorStart, cursorEnd: cursorStart };\n }\n\n const lines = selected.split('\\n');\n const listified = lines.map((line) => (line.startsWith('- ') ? line : `- ${line}`)).join('\\n');\n const nextValue = `${before}${listified}${after}`;\n const cursorStart = start;\n const cursorEnd = start + listified.length;\n return { nextValue, cursorStart, cursorEnd };\n};\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function MarkdownEditor({\n value,\n onChange,\n label,\n placeholder,\n rows = 5,\n 'aria-label': ariaLabel,\n className = '',\n textareaClassName = '',\n id,\n}: MarkdownEditorProps) {\n const [activeTab, setActiveTab] = useState<'write' | 'preview'>('write');\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const tr = useTr();\n\n const apply = (action: InsertAction) => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n\n const start = textarea.selectionStart ?? 0;\n const end = textarea.selectionEnd ?? 0;\n const { nextValue, cursorStart, cursorEnd } = action(value, start, end);\n\n onChange(nextValue);\n\n // Re-focus and restore selection after the next render has applied the value.\n requestAnimationFrame(() => {\n textarea.focus();\n textarea.setSelectionRange(cursorStart, cursorEnd);\n });\n };\n\n const handleToolbarAction = (action: 'bold' | 'italic' | 'link' | 'code' | 'list') => {\n switch (action) {\n case 'bold':\n apply(wrapWith('**', '**', 'bold text'));\n break;\n case 'italic':\n apply(wrapWith('*', '*', 'italic text'));\n break;\n case 'link':\n apply(insertLink);\n break;\n case 'code':\n apply(wrapWith('`', '`', 'code'));\n break;\n case 'list':\n apply(insertList);\n break;\n }\n };\n\n const containerBase =\n 'border border-border-subtle rounded-lg overflow-hidden bg-bg-surface focus-within:ring-2 focus-within:ring-primary';\n const textareaBase =\n 'w-full px-4 py-2 bg-bg-surface text-text-primary placeholder:text-text-secondary resize-none focus:outline-none';\n\n return (\n <div className=\"space-y-2\">\n {label && (\n <label htmlFor={id} className=\"text-sm font-medium text-text-primary\">\n {label}\n </label>\n )}\n <div className={`${containerBase} ${className}`}>\n <div className=\"flex items-center justify-between border-b border-border-subtle bg-bg-sunken px-2 py-1\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('bold')}\n aria-label={tr('channel.markdown.bold', 'Bold')}\n title={tr('channel.markdown.bold', 'Bold')}\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Bold className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('italic')}\n aria-label={tr('channel.markdown.italic', 'Italic')}\n title={tr('channel.markdown.italic', 'Italic')}\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Italic className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('link')}\n aria-label={tr('channel.markdown.link', 'Link')}\n title={tr('channel.markdown.link', 'Link')}\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Link className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('code')}\n aria-label={tr('channel.markdown.code', 'Code')}\n title={tr('channel.markdown.code', 'Code')}\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Code className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('list')}\n aria-label={tr('channel.markdown.bulletList', 'Bullet list')}\n title={tr('channel.markdown.bulletList', 'Bullet list')}\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <List className=\"size-4\" />\n </button>\n </div>\n\n <div\n className=\"flex items-center gap-1\"\n role=\"tablist\"\n aria-label={tr('channel.markdown.tabsLabel', 'Markdown editor tabs')}\n >\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === 'write'}\n onClick={() => setActiveTab('write')}\n className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${\n activeTab === 'write'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface'\n }`}\n >\n {tr('channel.markdown.write', 'Write')}\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === 'preview'}\n onClick={() => setActiveTab('preview')}\n className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${\n activeTab === 'preview'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface'\n }`}\n >\n {tr('channel.markdown.preview', 'Preview')}\n </button>\n </div>\n </div>\n\n <div className=\"relative\">\n {activeTab === 'write' ? (\n <textarea\n ref={textareaRef}\n id={id}\n aria-label={ariaLabel}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n placeholder={placeholder}\n rows={rows}\n className={`${textareaBase} ${textareaClassName}`}\n />\n ) : (\n <div className=\"min-h-[theme(spacing.32)] max-h-96 overflow-auto px-4 py-2 text-sm text-text-primary\">\n {value.trim() ? (\n <Markdown>{value}</Markdown>\n ) : (\n <p className=\"text-text-secondary italic\">\n {placeholder ||\n tr('channel.markdown.nothingToPreview', 'Nothing to preview yet.')}\n </p>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PermissionGuard.d.ts","sourceRoot":"","sources":["../../../src/channel/components/PermissionGuard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAK3C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,UAAU,oBAAoB;IAC5B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,EAAE,CAAC,oBAAoB,
|
|
1
|
+
{"version":3,"file":"PermissionGuard.d.ts","sourceRoot":"","sources":["../../../src/channel/components/PermissionGuard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAK3C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,UAAU,oBAAoB;IAC5B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,EAAE,CAAC,oBAAoB,CAsBpD,CAAC"}
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useTr as e } from "../hooks/useTr.js";
|
|
2
|
+
import { AccessDenied as t } from "@burdenoff/fe-libs/shared/components";
|
|
3
3
|
import { Loader2 as n } from "lucide-react";
|
|
4
4
|
import { usePermissions as r } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
5
5
|
import { Fragment as i, jsx as a } from "react/jsx-runtime";
|
|
6
6
|
//#region src/channel/components/PermissionGuard.tsx
|
|
7
7
|
var o = ({ permission: o, children: s }) => {
|
|
8
|
-
let { hasPermission: c, isLoading: l } = r(),
|
|
9
|
-
let n = u(e);
|
|
10
|
-
return n === e ? t : n;
|
|
11
|
-
};
|
|
8
|
+
let { hasPermission: c, isLoading: l } = r(), u = e();
|
|
12
9
|
return l ? /* @__PURE__ */ a("div", {
|
|
13
10
|
className: "flex min-h-screen items-center justify-center bg-bg-canvas",
|
|
14
11
|
children: /* @__PURE__ */ a(n, { className: "size-8 animate-spin text-primary" })
|
|
15
|
-
}) : c(o) ? /* @__PURE__ */ a(i, { children: s }) : /* @__PURE__ */ a(
|
|
16
|
-
title:
|
|
17
|
-
description:
|
|
12
|
+
}) : c(o) ? /* @__PURE__ */ a(i, { children: s }) : /* @__PURE__ */ a(t, {
|
|
13
|
+
title: u("channel.accessDenied", "Access Denied"),
|
|
14
|
+
description: u("channel.noPermissionView", "You don't have permission to view this page.")
|
|
18
15
|
});
|
|
19
16
|
};
|
|
20
17
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PermissionGuard.js","names":[],"sources":["../../../src/channel/components/PermissionGuard.tsx"],"sourcesContent":["import type { FC, ReactNode } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport {
|
|
1
|
+
{"version":3,"file":"PermissionGuard.js","names":[],"sources":["../../../src/channel/components/PermissionGuard.tsx"],"sourcesContent":["import type { FC, ReactNode } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { useTr } from '../hooks/useTr';\nimport type { ChannelPermission } from '../constants/permissions';\n\ninterface PermissionGuardProps {\n permission: ChannelPermission;\n children: ReactNode;\n}\n\n/**\n * Wraps page content with a permission check.\n * Shows a loading spinner while permissions resolve, then either\n * renders children (if permitted) or an AccessDenied screen.\n */\nexport const PermissionGuard: FC<PermissionGuardProps> = ({ permission, children }) => {\n const { hasPermission, isLoading } = usePermissions();\n const tr = useTr();\n\n if (isLoading) {\n return (\n <div className=\"flex min-h-screen items-center justify-center bg-bg-canvas\">\n <Loader2 className=\"size-8 animate-spin text-primary\" />\n </div>\n );\n }\n\n if (!hasPermission(permission)) {\n return (\n <AccessDenied\n title={tr('channel.accessDenied', 'Access Denied')}\n description={tr('channel.noPermissionView', \"You don't have permission to view this page.\")}\n />\n );\n }\n\n return <>{children}</>;\n};\n"],"mappings":";;;;;;AAiBA,IAAa,KAA6C,EAAE,eAAY,kBAAe;CACrF,IAAM,EAAE,kBAAe,iBAAc,GAAgB,EAC/C,IAAK,GAAO;AAmBlB,QAjBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,oCAAqC,CAAA;EACpD,CAAA,GAIL,EAAc,EAAW,GASvB,kBAAA,GAAA,EAAG,aAAY,CAAA,GAPlB,kBAAC,GAAD;EACE,OAAO,EAAG,wBAAwB,gBAAgB;EAClD,aAAa,EAAG,4BAA4B,+CAA+C;EAC3F,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useTr — translation hook with fallback semantics.
|
|
3
|
+
*
|
|
4
|
+
* Wraps `useI18n().t()` so that when a key has no translation (i.e. the
|
|
5
|
+
* provider returns the key itself), the caller's English fallback is used
|
|
6
|
+
* instead. This keeps consumer call sites consistent and avoids leaking
|
|
7
|
+
* untranslated keys into the UI.
|
|
8
|
+
*
|
|
9
|
+
* Supports optional interpolation params using `{{key}}` placeholders.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const tr = useTr();
|
|
13
|
+
* return <h1>{tr('channel.dashboard.title', 'Dashboard')}</h1>;
|
|
14
|
+
* return <p>{tr('channel.selectedCount', '{{count}} selected', { count: 5 })}</p>;
|
|
15
|
+
*/
|
|
16
|
+
export declare function useTr(): (key: string, fallback: string, params?: Record<string, string | number>) => string;
|
|
17
|
+
//# sourceMappingURL=useTr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTr.d.ts","sourceRoot":"","sources":["../../../src/channel/hooks/useTr.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,KAAK,IAAI,CACvB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KACrC,MAAM,CAcV"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useCallback as e } from "react";
|
|
2
|
+
import { useI18n as t } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
3
|
+
//#region src/channel/hooks/useTr.ts
|
|
4
|
+
function n() {
|
|
5
|
+
let { t: n } = t();
|
|
6
|
+
return e((e, t, r) => {
|
|
7
|
+
let i = n(e), a = i === e ? t : i;
|
|
8
|
+
return r ? Object.entries(r).reduce((e, [t, n]) => e.replace(RegExp(`\\{\\{${t}\\}\\}`, "g"), String(n)), a) : a;
|
|
9
|
+
}, [n]);
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { n as useTr };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=useTr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTr.js","names":[],"sources":["../../../src/channel/hooks/useTr.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n/**\n * useTr — translation hook with fallback semantics.\n *\n * Wraps `useI18n().t()` so that when a key has no translation (i.e. the\n * provider returns the key itself), the caller's English fallback is used\n * instead. This keeps consumer call sites consistent and avoids leaking\n * untranslated keys into the UI.\n *\n * Supports optional interpolation params using `{{key}}` placeholders.\n *\n * @example\n * const tr = useTr();\n * return <h1>{tr('channel.dashboard.title', 'Dashboard')}</h1>;\n * return <p>{tr('channel.selectedCount', '{{count}} selected', { count: 5 })}</p>;\n */\nexport function useTr(): (\n key: string,\n fallback: string,\n params?: Record<string, string | number>\n) => string {\n const { t } = useI18n();\n return useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key);\n const raw = translated === key ? fallback : translated;\n if (!params) return raw;\n return Object.entries(params).reduce(\n (str, [k, v]) => str.replace(new RegExp(`\\\\{\\\\{${k}\\\\}\\\\}`, 'g'), String(v)),\n raw\n );\n },\n [t]\n );\n}\n"],"mappings":";;;AAkBA,SAAgB,IAIJ;CACV,IAAM,EAAE,SAAM,GAAS;AACvB,QAAO,GACJ,GAAa,GAAkB,MAAqD;EACnF,IAAM,IAAa,EAAE,EAAI,EACnB,IAAM,MAAe,IAAM,IAAW;AAE5C,SADK,IACE,OAAO,QAAQ,EAAO,CAAC,QAC3B,GAAK,CAAC,GAAG,OAAO,EAAI,QAAY,OAAO,SAAS,EAAE,SAAS,IAAI,EAAE,OAAO,EAAE,CAAC,EAC5E,EACD,GAJmB;IAMtB,CAAC,EAAE,CACJ"}
|
|
@@ -1,102 +1,102 @@
|
|
|
1
1
|
//#region src/channel/modules/channels/i18n.ts
|
|
2
2
|
var e = {
|
|
3
3
|
EMAIL: {
|
|
4
|
-
key: "channel.
|
|
4
|
+
key: "channel.platformLabel.email",
|
|
5
5
|
fallback: "Email"
|
|
6
6
|
},
|
|
7
7
|
SMS: {
|
|
8
|
-
key: "channel.
|
|
8
|
+
key: "channel.platformLabel.sms",
|
|
9
9
|
fallback: "SMS"
|
|
10
10
|
},
|
|
11
11
|
PUSH: {
|
|
12
|
-
key: "channel.
|
|
12
|
+
key: "channel.platformLabel.push",
|
|
13
13
|
fallback: "Push"
|
|
14
14
|
},
|
|
15
15
|
INAPP: {
|
|
16
|
-
key: "channel.
|
|
16
|
+
key: "channel.platformLabel.inApp",
|
|
17
17
|
fallback: "In-App"
|
|
18
18
|
},
|
|
19
19
|
WEBHOOK: {
|
|
20
|
-
key: "channel.
|
|
20
|
+
key: "channel.platformLabel.webhook",
|
|
21
21
|
fallback: "Webhook"
|
|
22
22
|
},
|
|
23
23
|
SLACK: {
|
|
24
|
-
key: "channel.
|
|
24
|
+
key: "channel.platformLabel.slack",
|
|
25
25
|
fallback: "Slack"
|
|
26
26
|
},
|
|
27
27
|
TEAMS: {
|
|
28
|
-
key: "channel.
|
|
28
|
+
key: "channel.platformLabel.teams",
|
|
29
29
|
fallback: "Teams"
|
|
30
30
|
},
|
|
31
31
|
DISCORD: {
|
|
32
|
-
key: "channel.
|
|
32
|
+
key: "channel.platformLabel.discord",
|
|
33
33
|
fallback: "Discord"
|
|
34
34
|
},
|
|
35
35
|
WHATSAPP: {
|
|
36
|
-
key: "channel.
|
|
36
|
+
key: "channel.platformLabel.whatsapp",
|
|
37
37
|
fallback: "WhatsApp"
|
|
38
38
|
},
|
|
39
39
|
WEBCHAT: {
|
|
40
|
-
key: "channel.
|
|
40
|
+
key: "channel.platformLabel.webchat",
|
|
41
41
|
fallback: "Webchat"
|
|
42
42
|
}
|
|
43
43
|
}, t = {
|
|
44
44
|
ACTIVE: {
|
|
45
|
-
key: "channel.
|
|
45
|
+
key: "channel.statusLabel.active",
|
|
46
46
|
fallback: "Active"
|
|
47
47
|
},
|
|
48
48
|
INACTIVE: {
|
|
49
|
-
key: "channel.
|
|
49
|
+
key: "channel.statusLabel.inactive",
|
|
50
50
|
fallback: "Inactive"
|
|
51
51
|
},
|
|
52
52
|
ERROR: {
|
|
53
|
-
key: "channel.
|
|
53
|
+
key: "channel.statusLabel.error",
|
|
54
54
|
fallback: "Error"
|
|
55
55
|
},
|
|
56
56
|
SUSPENDED: {
|
|
57
|
-
key: "channel.
|
|
57
|
+
key: "channel.statusLabel.suspended",
|
|
58
58
|
fallback: "Suspended"
|
|
59
59
|
},
|
|
60
60
|
ARCHIVED: {
|
|
61
|
-
key: "channel.
|
|
61
|
+
key: "channel.statusLabel.archived",
|
|
62
62
|
fallback: "Archived"
|
|
63
63
|
}
|
|
64
64
|
}, n = {
|
|
65
65
|
HEALTHY: {
|
|
66
|
-
key: "channel.
|
|
66
|
+
key: "channel.healthLabel.healthy",
|
|
67
67
|
fallback: "Healthy"
|
|
68
68
|
},
|
|
69
69
|
DEGRADED: {
|
|
70
|
-
key: "channel.
|
|
70
|
+
key: "channel.healthLabel.degraded",
|
|
71
71
|
fallback: "Degraded"
|
|
72
72
|
},
|
|
73
73
|
UNHEALTHY: {
|
|
74
|
-
key: "channel.
|
|
74
|
+
key: "channel.healthLabel.unhealthy",
|
|
75
75
|
fallback: "Unhealthy"
|
|
76
76
|
},
|
|
77
77
|
UNKNOWN: {
|
|
78
|
-
key: "channel.
|
|
78
|
+
key: "channel.healthLabel.unknown",
|
|
79
79
|
fallback: "Unknown"
|
|
80
80
|
}
|
|
81
81
|
}, r = {
|
|
82
82
|
API_KEY: {
|
|
83
|
-
key: "channel.
|
|
83
|
+
key: "channel.authTypeLabel.apiKey",
|
|
84
84
|
fallback: "API Key"
|
|
85
85
|
},
|
|
86
86
|
OAUTH2: {
|
|
87
|
-
key: "channel.
|
|
87
|
+
key: "channel.authTypeLabel.oauth2",
|
|
88
88
|
fallback: "OAuth 2.0"
|
|
89
89
|
},
|
|
90
90
|
BOT_TOKEN: {
|
|
91
|
-
key: "channel.
|
|
91
|
+
key: "channel.authTypeLabel.botToken",
|
|
92
92
|
fallback: "Bot Token"
|
|
93
93
|
},
|
|
94
94
|
WEBHOOK: {
|
|
95
|
-
key: "channel.
|
|
95
|
+
key: "channel.authTypeLabel.webhook",
|
|
96
96
|
fallback: "Webhook"
|
|
97
97
|
},
|
|
98
98
|
SMTP_IMAP: {
|
|
99
|
-
key: "channel.
|
|
99
|
+
key: "channel.authTypeLabel.smtpImap",
|
|
100
100
|
fallback: "SMTP/IMAP"
|
|
101
101
|
}
|
|
102
102
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.js","names":[],"sources":["../../../../src/channel/modules/channels/i18n.ts"],"sourcesContent":["import type {\n ChannelAuthType,\n ChannelPlatform,\n ChannelStatus,\n ConnectionHealth,\n} from '../../hooks/useChannels';\n\nexport type TranslateFn = (key: string, fallback: string) => string;\n\ntype I18nLabel = {\n key: string;\n fallback: string;\n};\n\nexport const PLATFORM_I18N: Record<ChannelPlatform, I18nLabel> = {\n EMAIL: { key: 'channel.
|
|
1
|
+
{"version":3,"file":"i18n.js","names":[],"sources":["../../../../src/channel/modules/channels/i18n.ts"],"sourcesContent":["import type {\n ChannelAuthType,\n ChannelPlatform,\n ChannelStatus,\n ConnectionHealth,\n} from '../../hooks/useChannels';\n\nexport type TranslateFn = (key: string, fallback: string) => string;\n\ntype I18nLabel = {\n key: string;\n fallback: string;\n};\n\nexport const PLATFORM_I18N: Record<ChannelPlatform, I18nLabel> = {\n EMAIL: { key: 'channel.platformLabel.email', fallback: 'Email' },\n SMS: { key: 'channel.platformLabel.sms', fallback: 'SMS' },\n PUSH: { key: 'channel.platformLabel.push', fallback: 'Push' },\n INAPP: { key: 'channel.platformLabel.inApp', fallback: 'In-App' },\n WEBHOOK: { key: 'channel.platformLabel.webhook', fallback: 'Webhook' },\n SLACK: { key: 'channel.platformLabel.slack', fallback: 'Slack' },\n TEAMS: { key: 'channel.platformLabel.teams', fallback: 'Teams' },\n DISCORD: { key: 'channel.platformLabel.discord', fallback: 'Discord' },\n WHATSAPP: { key: 'channel.platformLabel.whatsapp', fallback: 'WhatsApp' },\n WEBCHAT: { key: 'channel.platformLabel.webchat', fallback: 'Webchat' },\n};\n\nexport const STATUS_I18N: Record<ChannelStatus, I18nLabel> = {\n ACTIVE: { key: 'channel.statusLabel.active', fallback: 'Active' },\n INACTIVE: { key: 'channel.statusLabel.inactive', fallback: 'Inactive' },\n ERROR: { key: 'channel.statusLabel.error', fallback: 'Error' },\n SUSPENDED: { key: 'channel.statusLabel.suspended', fallback: 'Suspended' },\n ARCHIVED: { key: 'channel.statusLabel.archived', fallback: 'Archived' },\n};\n\nexport const HEALTH_I18N: Record<ConnectionHealth, I18nLabel> = {\n HEALTHY: { key: 'channel.healthLabel.healthy', fallback: 'Healthy' },\n DEGRADED: { key: 'channel.healthLabel.degraded', fallback: 'Degraded' },\n UNHEALTHY: { key: 'channel.healthLabel.unhealthy', fallback: 'Unhealthy' },\n UNKNOWN: { key: 'channel.healthLabel.unknown', fallback: 'Unknown' },\n};\n\nexport const AUTH_TYPE_I18N: Record<ChannelAuthType, I18nLabel> = {\n API_KEY: { key: 'channel.authTypeLabel.apiKey', fallback: 'API Key' },\n OAUTH2: { key: 'channel.authTypeLabel.oauth2', fallback: 'OAuth 2.0' },\n BOT_TOKEN: { key: 'channel.authTypeLabel.botToken', fallback: 'Bot Token' },\n WEBHOOK: { key: 'channel.authTypeLabel.webhook', fallback: 'Webhook' },\n SMTP_IMAP: { key: 'channel.authTypeLabel.smtpImap', fallback: 'SMTP/IMAP' },\n};\n\nexport function translateLabel<T extends string>(\n value: T,\n labels: Partial<Record<T, I18nLabel>>,\n tr: TranslateFn\n): string {\n const label = labels[value];\n return label ? tr(label.key, label.fallback) : value;\n}\n"],"mappings":";AAcA,IAAa,IAAoD;CAC/D,OAAO;EAAE,KAAK;EAA+B,UAAU;EAAS;CAChE,KAAK;EAAE,KAAK;EAA6B,UAAU;EAAO;CAC1D,MAAM;EAAE,KAAK;EAA8B,UAAU;EAAQ;CAC7D,OAAO;EAAE,KAAK;EAA+B,UAAU;EAAU;CACjE,SAAS;EAAE,KAAK;EAAiC,UAAU;EAAW;CACtE,OAAO;EAAE,KAAK;EAA+B,UAAU;EAAS;CAChE,OAAO;EAAE,KAAK;EAA+B,UAAU;EAAS;CAChE,SAAS;EAAE,KAAK;EAAiC,UAAU;EAAW;CACtE,UAAU;EAAE,KAAK;EAAkC,UAAU;EAAY;CACzE,SAAS;EAAE,KAAK;EAAiC,UAAU;EAAW;CACvE,EAEY,IAAgD;CAC3D,QAAQ;EAAE,KAAK;EAA8B,UAAU;EAAU;CACjE,UAAU;EAAE,KAAK;EAAgC,UAAU;EAAY;CACvE,OAAO;EAAE,KAAK;EAA6B,UAAU;EAAS;CAC9D,WAAW;EAAE,KAAK;EAAiC,UAAU;EAAa;CAC1E,UAAU;EAAE,KAAK;EAAgC,UAAU;EAAY;CACxE,EAEY,IAAmD;CAC9D,SAAS;EAAE,KAAK;EAA+B,UAAU;EAAW;CACpE,UAAU;EAAE,KAAK;EAAgC,UAAU;EAAY;CACvE,WAAW;EAAE,KAAK;EAAiC,UAAU;EAAa;CAC1E,SAAS;EAAE,KAAK;EAA+B,UAAU;EAAW;CACrE,EAEY,IAAqD;CAChE,SAAS;EAAE,KAAK;EAAgC,UAAU;EAAW;CACrE,QAAQ;EAAE,KAAK;EAAgC,UAAU;EAAa;CACtE,WAAW;EAAE,KAAK;EAAkC,UAAU;EAAa;CAC3E,SAAS;EAAE,KAAK;EAAiC,UAAU;EAAW;CACtE,WAAW;EAAE,KAAK;EAAkC,UAAU;EAAa;CAC5E;AAED,SAAgB,EACd,GACA,GACA,GACQ;CACR,IAAM,IAAQ,EAAO;AACrB,QAAO,IAAQ,EAAG,EAAM,KAAK,EAAM,SAAS,GAAG"}
|
|
@@ -299,14 +299,14 @@ function j() {
|
|
|
299
299
|
iconClass: "text-accent-primary",
|
|
300
300
|
value: H,
|
|
301
301
|
label: j("channel.totalChannels", "Total Channels"),
|
|
302
|
-
sub: $.active > 0 ? `${$.active} ${j("channel.
|
|
302
|
+
sub: $.active > 0 ? `${$.active} ${j("channel.statusLabel.active", "Active").toLowerCase()}, ${H - $.active} ${j("channel.statusLabel.inactive", "Inactive").toLowerCase()}` : void 0
|
|
303
303
|
}),
|
|
304
304
|
/* @__PURE__ */ w(A, {
|
|
305
305
|
icon: fe,
|
|
306
306
|
iconClass: "text-status-success-text",
|
|
307
307
|
value: $.healthy,
|
|
308
308
|
label: j("channel.healthy", "Healthy"),
|
|
309
|
-
sub: $.unhealthy > 0 ? `${$.unhealthy} ${j("channel.
|
|
309
|
+
sub: $.unhealthy > 0 ? `${$.unhealthy} ${j("channel.healthLabel.unhealthy", "Unhealthy").toLowerCase()}` : H > 0 ? $.healthy === H ? j("channel.allSystemsNominal", "All systems nominal") : `${H - $.healthy} ${j("channel.healthLabel.unchecked", "unchecked")}` : void 0
|
|
310
310
|
}),
|
|
311
311
|
/* @__PURE__ */ w(A, {
|
|
312
312
|
icon: h,
|