@tmls-ai/support 0.1.4 → 0.1.6

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 (2) hide show
  1. package/dist/index.js +122 -40
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -106,6 +106,16 @@ var SEVERITIES = [
106
106
  { id: "annoying", label: "Annoying" },
107
107
  { id: "minor", label: "Minor" }
108
108
  ];
109
+ var deeplinkTicket = (() => {
110
+ if (typeof window === "undefined") return null;
111
+ const params = new URLSearchParams(window.location.search);
112
+ const tn = params.get("support");
113
+ if (!tn) return null;
114
+ params.delete("support");
115
+ const qs = params.toString();
116
+ window.history.replaceState({}, "", window.location.pathname + (qs ? `?${qs}` : "") + window.location.hash);
117
+ return tn;
118
+ })();
109
119
  function SupportWidget(props) {
110
120
  const { productId, apiUrl, getToken, accent = "#0a84ff", getContext, appVersion } = props;
111
121
  const api = useMemo(() => new SupportApi(apiUrl, getToken), [apiUrl, getToken]);
@@ -125,6 +135,17 @@ function SupportWidget(props) {
125
135
  const [messages, setMessages] = useState([]);
126
136
  const [shotUrls, setShotUrls] = useState({});
127
137
  const [reply, setReply] = useState("");
138
+ const [isMobile, setIsMobile] = useState(
139
+ () => typeof window !== "undefined" && window.matchMedia("(max-width: 767px)").matches
140
+ );
141
+ useEffect(() => {
142
+ if (typeof window === "undefined") return;
143
+ const mq = window.matchMedia("(max-width: 767px)");
144
+ const onChange = () => setIsMobile(mq.matches);
145
+ onChange();
146
+ mq.addEventListener("change", onChange);
147
+ return () => mq.removeEventListener("change", onChange);
148
+ }, []);
128
149
  useEffect(() => {
129
150
  installErrorCapture();
130
151
  }, []);
@@ -137,7 +158,7 @@ function SupportWidget(props) {
137
158
  });
138
159
  try {
139
160
  const root = document.documentElement;
140
- const blob = await domToBlob(root, {
161
+ const shoot = () => domToBlob(root, {
141
162
  quality: 0.8,
142
163
  scale: 0.75,
143
164
  width: root.clientWidth || window.innerWidth,
@@ -147,6 +168,11 @@ function SupportWidget(props) {
147
168
  features: { restoreScrollPosition: true },
148
169
  filter: (node) => !(node instanceof HTMLElement && node.dataset.tmlsSupportRoot === "true")
149
170
  });
171
+ let blob = await shoot();
172
+ if (blob.size < 1024) {
173
+ await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r())));
174
+ blob = await shoot();
175
+ }
150
176
  setShotPreview(URL.createObjectURL(blob));
151
177
  captureShot._blob = blob;
152
178
  } catch {
@@ -214,6 +240,18 @@ function SupportWidget(props) {
214
240
  setMessages(detail.messages);
215
241
  }
216
242
  }, [api, reply, activeId]);
243
+ useEffect(() => {
244
+ if (!deeplinkTicket) return;
245
+ const tn = deeplinkTicket;
246
+ (async () => {
247
+ setOpen(true);
248
+ const convos = await api.listConversations();
249
+ setThreads(convos);
250
+ const match = convos.find((c) => String(c.ticket_number) === tn);
251
+ if (match) await openThread(match.id);
252
+ else setView("threads");
253
+ })();
254
+ }, [api, openThread]);
217
255
  const chip = (active) => ({
218
256
  padding: "6px 12px",
219
257
  borderRadius: 999,
@@ -224,27 +262,92 @@ function SupportWidget(props) {
224
262
  background: active ? accent : "transparent",
225
263
  color: active ? "#fff" : "#c8c8d0"
226
264
  });
265
+ const toggle = () => {
266
+ if (open) deeplinkTicket = null;
267
+ setOpen((o) => !o);
268
+ if (!open && withShot) captureShot();
269
+ };
270
+ const panelStyle = isMobile ? {
271
+ position: "fixed",
272
+ left: 0,
273
+ right: 0,
274
+ bottom: 0,
275
+ width: "auto",
276
+ maxHeight: "85vh",
277
+ background: "#141418",
278
+ color: "#e8e8ea",
279
+ borderTop: "0.5px solid rgba(255,255,255,0.12)",
280
+ borderRadius: "16px 16px 0 0",
281
+ boxShadow: "0 -16px 48px rgba(0,0,0,0.5)",
282
+ overflow: "hidden",
283
+ display: "flex",
284
+ flexDirection: "column",
285
+ zIndex: 2147483e3,
286
+ paddingBottom: "env(safe-area-inset-bottom, 0px)"
287
+ } : {
288
+ position: "absolute",
289
+ bottom: 60,
290
+ right: 0,
291
+ width: 360,
292
+ maxHeight: 560,
293
+ background: "#141418",
294
+ color: "#e8e8ea",
295
+ border: "0.5px solid rgba(255,255,255,0.12)",
296
+ borderRadius: 16,
297
+ boxShadow: "0 16px 48px rgba(0,0,0,0.5)",
298
+ overflow: "hidden",
299
+ display: "flex",
300
+ flexDirection: "column"
301
+ };
302
+ const launcherStyle = isMobile ? {
303
+ position: "fixed",
304
+ right: 0,
305
+ top: "50%",
306
+ transform: "translateY(-50%)",
307
+ width: 30,
308
+ height: 56,
309
+ borderRadius: "14px 0 0 14px",
310
+ border: "none",
311
+ cursor: "pointer",
312
+ background: accent,
313
+ color: "#fff",
314
+ fontSize: 20,
315
+ boxShadow: "-3px 0 14px rgba(0,0,0,0.4)",
316
+ display: "flex",
317
+ alignItems: "center",
318
+ justifyContent: "center",
319
+ paddingRight: 4,
320
+ zIndex: 2147483e3
321
+ } : {
322
+ width: 48,
323
+ height: 48,
324
+ borderRadius: 999,
325
+ border: "none",
326
+ cursor: "pointer",
327
+ background: accent,
328
+ color: "#fff",
329
+ fontSize: 22,
330
+ boxShadow: "0 4px 16px rgba(0,0,0,0.35)"
331
+ };
227
332
  return /* @__PURE__ */ jsxs("div", { "data-tmls-support-root": "true", style: { position: "fixed", bottom: 20, right: 20, zIndex: 2147483e3, fontFamily: "system-ui, sans-serif" }, children: [
228
- open && /* @__PURE__ */ jsxs("div", { style: {
229
- position: "absolute",
230
- bottom: 60,
231
- right: 0,
232
- width: 360,
233
- maxHeight: 560,
234
- background: "#141418",
235
- color: "#e8e8ea",
236
- border: "0.5px solid rgba(255,255,255,0.12)",
237
- borderRadius: 16,
238
- boxShadow: "0 16px 48px rgba(0,0,0,0.5)",
239
- overflow: "hidden",
240
- display: "flex",
241
- flexDirection: "column"
242
- }, children: [
333
+ open && /* @__PURE__ */ jsxs("div", { style: panelStyle, children: [
243
334
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "12px 14px", borderBottom: "0.5px solid rgba(255,255,255,0.08)" }, children: [
244
335
  /* @__PURE__ */ jsx("strong", { style: { fontSize: 14 }, children: "Support" }),
245
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, fontSize: 12 }, children: [
336
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, fontSize: 12, alignItems: "center" }, children: [
246
337
  /* @__PURE__ */ jsx("button", { onClick: () => setView("new"), style: tab(view === "new" || view === "sent", accent), children: "New" }),
247
- /* @__PURE__ */ jsx("button", { onClick: openThreads, style: tab(view === "threads" || view === "thread", accent), children: "My tickets" })
338
+ /* @__PURE__ */ jsx("button", { onClick: openThreads, style: tab(view === "threads" || view === "thread", accent), children: "My tickets" }),
339
+ isMobile && /* @__PURE__ */ jsx(
340
+ "button",
341
+ {
342
+ onClick: () => {
343
+ deeplinkTicket = null;
344
+ setOpen(false);
345
+ },
346
+ "aria-label": "Close",
347
+ style: { background: "transparent", border: "none", cursor: "pointer", color: "#8a8a92", fontSize: 20, lineHeight: 1, padding: "0 2px" },
348
+ children: "\xD7"
349
+ }
350
+ )
248
351
  ] })
249
352
  ] }),
250
353
  /* @__PURE__ */ jsxs("div", { style: { padding: 14, overflowY: "auto" }, children: [
@@ -335,28 +438,7 @@ function SupportWidget(props) {
335
438
  ] })
336
439
  ] })
337
440
  ] }),
338
- /* @__PURE__ */ jsx(
339
- "button",
340
- {
341
- onClick: () => {
342
- setOpen((o) => !o);
343
- if (!open && withShot) captureShot();
344
- },
345
- "aria-label": "Support",
346
- style: {
347
- width: 48,
348
- height: 48,
349
- borderRadius: 999,
350
- border: "none",
351
- cursor: "pointer",
352
- background: accent,
353
- color: "#fff",
354
- fontSize: 22,
355
- boxShadow: "0 4px 16px rgba(0,0,0,0.35)"
356
- },
357
- children: open ? "\xD7" : "?"
358
- }
359
- )
441
+ (!isMobile || !open) && /* @__PURE__ */ jsx("button", { onClick: toggle, "aria-label": "Support", style: launcherStyle, children: open && !isMobile ? "\xD7" : "?" })
360
442
  ] });
361
443
  }
362
444
  var tab = (active, accent) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmls-ai/support",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Embeddable Timeless support widget — bottom-right report overlay (type / severity / screenshot) + ticket thread. Auto-captures context, talks to tmls-support-api.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",