@catandbox/schrodinger-web-adapter 0.1.32 → 0.1.34
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/client/portal.js
CHANGED
|
@@ -35,6 +35,7 @@ export async function renderSupportPortal(container, options = {}) {
|
|
|
35
35
|
</s-card>
|
|
36
36
|
`;
|
|
37
37
|
}
|
|
38
|
+
const locale = options.locale;
|
|
38
39
|
async function navigate(view, ticketId) {
|
|
39
40
|
currentView = view;
|
|
40
41
|
if (ticketId !== undefined) {
|
|
@@ -44,11 +45,11 @@ export async function renderSupportPortal(container, options = {}) {
|
|
|
44
45
|
showLoading();
|
|
45
46
|
switch (currentView) {
|
|
46
47
|
case "list":
|
|
47
|
-
await renderTicketList(container, client, emitter);
|
|
48
|
+
await renderTicketList(container, client, emitter, locale);
|
|
48
49
|
break;
|
|
49
50
|
case "detail":
|
|
50
51
|
if (selectedTicketId) {
|
|
51
|
-
await renderTicketDetail(container, client, selectedTicketId, emitter);
|
|
52
|
+
await renderTicketDetail(container, client, selectedTicketId, emitter, locale);
|
|
52
53
|
}
|
|
53
54
|
break;
|
|
54
55
|
case "create":
|
|
@@ -6,4 +6,4 @@ export interface TicketDetailEvents {
|
|
|
6
6
|
"ticket:closed": string;
|
|
7
7
|
"ticket:reopened": string;
|
|
8
8
|
}
|
|
9
|
-
export declare function renderTicketDetail(container: HTMLElement, client: SupportApiClient, ticketId: string, emitter: EventEmitter<TicketDetailEvents
|
|
9
|
+
export declare function renderTicketDetail(container: HTMLElement, client: SupportApiClient, ticketId: string, emitter: EventEmitter<TicketDetailEvents>, locale?: string): Promise<void>;
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { renderStatusBadge } from "./status-badge";
|
|
2
2
|
import { createFileUpload } from "./file-upload";
|
|
3
3
|
import { setInnerHtml, escapeHtml, formatBodyText, renderFormattingField, attachFormattingField } from "./dom-utils";
|
|
4
|
-
function formatTimestamp(timestamp) {
|
|
4
|
+
function formatTimestamp(timestamp, locale) {
|
|
5
5
|
const date = new Date(timestamp * 1000);
|
|
6
6
|
const now = new Date();
|
|
7
7
|
const isToday = date.toDateString() === now.toDateString();
|
|
8
8
|
if (isToday) {
|
|
9
|
-
return date.toLocaleTimeString(
|
|
9
|
+
return date.toLocaleTimeString(locale, { hour: "numeric", minute: "2-digit" });
|
|
10
10
|
}
|
|
11
|
-
return date.toLocaleDateString(
|
|
11
|
+
return date.toLocaleDateString(locale, {
|
|
12
12
|
month: "short",
|
|
13
13
|
day: "numeric",
|
|
14
14
|
hour: "numeric",
|
|
15
15
|
minute: "2-digit"
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
-
function formatFullDate(timestamp) {
|
|
19
|
-
return new Date(timestamp * 1000).toLocaleDateString(
|
|
18
|
+
function formatFullDate(timestamp, locale) {
|
|
19
|
+
return new Date(timestamp * 1000).toLocaleDateString(locale, {
|
|
20
20
|
month: "long",
|
|
21
21
|
day: "numeric",
|
|
22
22
|
year: "numeric",
|
|
@@ -24,7 +24,7 @@ function formatFullDate(timestamp) {
|
|
|
24
24
|
minute: "2-digit"
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
-
export async function renderTicketDetail(container, client, ticketId, emitter) {
|
|
27
|
+
export async function renderTicketDetail(container, client, ticketId, emitter, locale) {
|
|
28
28
|
container.innerHTML = `
|
|
29
29
|
<s-card>
|
|
30
30
|
<s-box padding="large">
|
|
@@ -64,7 +64,7 @@ export async function renderTicketDetail(container, client, ticketId, emitter) {
|
|
|
64
64
|
${renderStatusBadge(ticket.status)}
|
|
65
65
|
<s-text tone="neutral">#${ticket.id.slice(0, 8)}</s-text>
|
|
66
66
|
<s-text tone="neutral">·</s-text>
|
|
67
|
-
<s-text tone="neutral">Created ${formatFullDate(ticket.createdAt)}</s-text>
|
|
67
|
+
<s-text tone="neutral">Created ${formatFullDate(ticket.createdAt, locale)}</s-text>
|
|
68
68
|
</s-stack>
|
|
69
69
|
</div>
|
|
70
70
|
${!isOpen ? `<s-button id="sch-reopen-ticket-btn">Reopen</s-button>` : ""}
|
|
@@ -99,7 +99,7 @@ export async function renderTicketDetail(container, client, ticketId, emitter) {
|
|
|
99
99
|
</s-box>
|
|
100
100
|
</s-card>`
|
|
101
101
|
: `<s-stack gap="small-100">
|
|
102
|
-
${messages.map((msg) => renderMessage(msg, aliasMap.get(msg.authorAliasId ?? "") ?? null)).join("")}
|
|
102
|
+
${messages.map((msg) => renderMessage(msg, aliasMap.get(msg.authorAliasId ?? "") ?? null, locale)).join("")}
|
|
103
103
|
</s-stack>`}
|
|
104
104
|
</div>
|
|
105
105
|
|
|
@@ -223,7 +223,7 @@ function getInitials(displayName) {
|
|
|
223
223
|
}
|
|
224
224
|
return displayName.slice(0, 2).toUpperCase();
|
|
225
225
|
}
|
|
226
|
-
function renderMessage(msg, alias) {
|
|
226
|
+
function renderMessage(msg, alias, locale) {
|
|
227
227
|
const isSystem = msg.authorType === "system";
|
|
228
228
|
const isAgent = msg.authorType === "agent" || msg.authorType === "admin";
|
|
229
229
|
const isCustomer = !isSystem && !isAgent;
|
|
@@ -245,7 +245,7 @@ function renderMessage(msg, alias) {
|
|
|
245
245
|
<div style="min-width:0;">
|
|
246
246
|
<s-stack direction="inline" alignItems="baseline" gap="small" justifyContent="${isCustomer ? "end" : "start"}" style="margin-bottom:4px;">
|
|
247
247
|
<s-text type="strong">${label}</s-text>
|
|
248
|
-
<s-text tone="neutral">${formatTimestamp(msg.createdAt)}</s-text>
|
|
248
|
+
<s-text tone="neutral">${formatTimestamp(msg.createdAt, locale)}</s-text>
|
|
249
249
|
</s-stack>
|
|
250
250
|
<div style="background:${bubbleBg}; padding:10px 14px; border-radius:12px; display:inline-block; text-align:left; max-width:100%;">
|
|
251
251
|
<s-text style="white-space:pre-wrap; word-break:break-word;">${formatBodyText(msg.bodyPlain)}</s-text>
|
|
@@ -5,4 +5,4 @@ export interface TicketListEvents {
|
|
|
5
5
|
"ticket:select": string;
|
|
6
6
|
"ticket:create": void;
|
|
7
7
|
}
|
|
8
|
-
export declare function renderTicketList(container: HTMLElement, client: SupportApiClient, emitter: EventEmitter<TicketListEvents
|
|
8
|
+
export declare function renderTicketList(container: HTMLElement, client: SupportApiClient, emitter: EventEmitter<TicketListEvents>, locale?: string): Promise<void>;
|
|
@@ -164,27 +164,28 @@ function initHydrogenLogo(el) {
|
|
|
164
164
|
renderer.dispose();
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
|
-
function formatDate(timestamp) {
|
|
168
|
-
return new Date(timestamp * 1000).toLocaleDateString(
|
|
167
|
+
function formatDate(timestamp, locale) {
|
|
168
|
+
return new Date(timestamp * 1000).toLocaleDateString(locale, {
|
|
169
169
|
month: "short",
|
|
170
170
|
day: "numeric",
|
|
171
171
|
year: "numeric"
|
|
172
172
|
});
|
|
173
173
|
}
|
|
174
|
-
function timeAgo(timestamp) {
|
|
174
|
+
function timeAgo(timestamp, locale) {
|
|
175
|
+
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
|
|
175
176
|
const seconds = Math.floor(Date.now() / 1000 - timestamp);
|
|
176
177
|
if (seconds < 60)
|
|
177
|
-
return
|
|
178
|
+
return rtf.format(-seconds, "second");
|
|
178
179
|
const minutes = Math.floor(seconds / 60);
|
|
179
180
|
if (minutes < 60)
|
|
180
|
-
return
|
|
181
|
+
return rtf.format(-minutes, "minute");
|
|
181
182
|
const hours = Math.floor(minutes / 60);
|
|
182
183
|
if (hours < 24)
|
|
183
|
-
return
|
|
184
|
+
return rtf.format(-hours, "hour");
|
|
184
185
|
const days = Math.floor(hours / 24);
|
|
185
186
|
if (days < 7)
|
|
186
|
-
return
|
|
187
|
-
return formatDate(timestamp);
|
|
187
|
+
return rtf.format(-days, "day");
|
|
188
|
+
return formatDate(timestamp, locale);
|
|
188
189
|
}
|
|
189
190
|
const STATUS_OPTIONS = [
|
|
190
191
|
{ value: "", label: "All statuses" },
|
|
@@ -198,7 +199,7 @@ const ORDER_OPTIONS = [
|
|
|
198
199
|
{ value: "newest", label: "Latest first" },
|
|
199
200
|
{ value: "oldest", label: "Oldest first" }
|
|
200
201
|
];
|
|
201
|
-
export async function renderTicketList(container, client, emitter) {
|
|
202
|
+
export async function renderTicketList(container, client, emitter, locale) {
|
|
202
203
|
// Load tickets, categories and Three.js in parallel
|
|
203
204
|
const [result, portalConfig] = await Promise.all([
|
|
204
205
|
client.listTickets({}).catch(() => ({ items: [] })),
|
|
@@ -234,7 +235,7 @@ export async function renderTicketList(container, client, emitter) {
|
|
|
234
235
|
</s-button>
|
|
235
236
|
</div>
|
|
236
237
|
|
|
237
|
-
<div style="display:
|
|
238
|
+
<div id="sch-filter-bar" style="display:none; gap:12px; flex-wrap:wrap;">
|
|
238
239
|
${categories.length > 0 ? `
|
|
239
240
|
<div style="flex:1; min-width:140px;">
|
|
240
241
|
<s-select id="sch-filter-category" label="Category">
|
|
@@ -272,6 +273,11 @@ export async function renderTicketList(container, client, emitter) {
|
|
|
272
273
|
}
|
|
273
274
|
function renderBody() {
|
|
274
275
|
const body = container.querySelector("#sch-ticket-list-body");
|
|
276
|
+
const filterBar = container.querySelector("#sch-filter-bar");
|
|
277
|
+
const hasActiveFilter = !!(categoryFilter || statusFilter);
|
|
278
|
+
if (filterBar) {
|
|
279
|
+
filterBar.style.display = allTickets.length > 0 || hasActiveFilter ? "flex" : "none";
|
|
280
|
+
}
|
|
275
281
|
let filtered = allTickets.filter((t) => {
|
|
276
282
|
if (statusFilter && t.status !== statusFilter)
|
|
277
283
|
return false;
|
|
@@ -319,7 +325,7 @@ export async function renderTicketList(container, client, emitter) {
|
|
|
319
325
|
<s-text variant="bodyMd" fontWeight="semibold">${escapeHtml(ticket.title)}</s-text>
|
|
320
326
|
</div>
|
|
321
327
|
<div style="margin-top:2px;">
|
|
322
|
-
<s-text variant="bodySm" tone="subdued">#${ticket.id.slice(0, 8)} · Updated ${timeAgo(ticket.updatedAt)}</s-text>
|
|
328
|
+
<s-text variant="bodySm" tone="subdued">#${ticket.id.slice(0, 8)} · Updated ${timeAgo(ticket.updatedAt, locale)}</s-text>
|
|
323
329
|
</div>
|
|
324
330
|
</div>
|
|
325
331
|
</div>
|