@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.
@@ -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>): Promise<void>;
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(undefined, { hour: "numeric", minute: "2-digit" });
9
+ return date.toLocaleTimeString(locale, { hour: "numeric", minute: "2-digit" });
10
10
  }
11
- return date.toLocaleDateString(undefined, {
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(undefined, {
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">&middot;</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>): Promise<void>;
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(undefined, {
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 "just now";
178
+ return rtf.format(-seconds, "second");
178
179
  const minutes = Math.floor(seconds / 60);
179
180
  if (minutes < 60)
180
- return `${minutes}m ago`;
181
+ return rtf.format(-minutes, "minute");
181
182
  const hours = Math.floor(minutes / 60);
182
183
  if (hours < 24)
183
- return `${hours}h ago`;
184
+ return rtf.format(-hours, "hour");
184
185
  const days = Math.floor(hours / 24);
185
186
  if (days < 7)
186
- return `${days}d ago`;
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:flex; gap:12px; flex-wrap:wrap;">
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)} &middot; Updated ${timeAgo(ticket.updatedAt)}</s-text>
328
+ <s-text variant="bodySm" tone="subdued">#${ticket.id.slice(0, 8)} &middot; Updated ${timeAgo(ticket.updatedAt, locale)}</s-text>
323
329
  </div>
324
330
  </div>
325
331
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catandbox/schrodinger-web-adapter",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",