@youasked/react 0.4.1 → 0.6.0
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/YouAsked.d.ts +9 -2
- package/dist/client.d.ts +20 -2
- package/dist/context.d.ts +4 -1
- package/dist/icons.d.ts +4 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +558 -355
- package/dist/nudge.d.ts +31 -0
- package/dist/shared.d.ts +1 -1
- package/dist/styles.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/YouAsked.tsx
|
|
4
|
-
import { useCallback as
|
|
4
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useMemo, useRef as useRef4, useState as useState4, useSyncExternalStore } from "react";
|
|
5
5
|
import { createPortal } from "react-dom";
|
|
6
6
|
|
|
7
7
|
// src/client.ts
|
|
8
8
|
var YouAskedError = class extends Error {
|
|
9
|
-
constructor(code) {
|
|
9
|
+
constructor(code, detail) {
|
|
10
10
|
super(code);
|
|
11
11
|
this.code = code;
|
|
12
|
+
this.detail = detail;
|
|
12
13
|
}
|
|
13
14
|
};
|
|
14
15
|
var ANON_KEY = "youasked:anon";
|
|
@@ -48,14 +49,14 @@ function createClient({ appKey, apiUrl, user, signature }) {
|
|
|
48
49
|
if (json) h["content-type"] = "application/json";
|
|
49
50
|
return h;
|
|
50
51
|
}
|
|
51
|
-
async function call(path, body, raw) {
|
|
52
|
+
async function call(path, body, raw, method) {
|
|
52
53
|
const res = await fetch(base2 + path, {
|
|
53
|
-
method: body === void 0 && !raw ? "GET" : "POST",
|
|
54
|
+
method: method ?? (body === void 0 && !raw ? "GET" : "POST"),
|
|
54
55
|
headers: raw ? { ...headers(false), "content-type": raw.type } : headers(body !== void 0),
|
|
55
56
|
body: raw ?? (body === void 0 ? void 0 : JSON.stringify(body))
|
|
56
57
|
});
|
|
57
58
|
const data = await res.json().catch(() => ({}));
|
|
58
|
-
if (!res.ok) throw new YouAskedError(data.error ?? `http_${res.status}
|
|
59
|
+
if (!res.ok) throw new YouAskedError(data.error ?? `http_${res.status}`, data.message);
|
|
59
60
|
return data;
|
|
60
61
|
}
|
|
61
62
|
return {
|
|
@@ -68,6 +69,7 @@ function createClient({ appKey, apiUrl, user, signature }) {
|
|
|
68
69
|
attachments: d.attachments ?? []
|
|
69
70
|
})),
|
|
70
71
|
upload: (image) => call("/uploads", void 0, image),
|
|
72
|
+
remove: (id) => call(`/requests/${encodeURIComponent(id)}`, void 0, void 0, "DELETE"),
|
|
71
73
|
earlyAccess: (id) => call(`/requests/${encodeURIComponent(id)}/early-access`, {}),
|
|
72
74
|
whatsNewSeen: () => call("/whats-new/seen", {}),
|
|
73
75
|
vote: (id) => call(`/requests/${encodeURIComponent(id)}/vote`, {}),
|
|
@@ -118,7 +120,7 @@ var DAY = 24 * 60 * MIN;
|
|
|
118
120
|
var uploads = /* @__PURE__ */ new Map();
|
|
119
121
|
var wait = (ms = 380) => new Promise((r) => setTimeout(r, ms + Math.random() * ms * 0.5));
|
|
120
122
|
var WORDS = (s) => new Set(s.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 2));
|
|
121
|
-
function createDemoClient(appName = "
|
|
123
|
+
function createDemoClient(appName = "this app") {
|
|
122
124
|
const t0 = Date.now();
|
|
123
125
|
let seq = 100;
|
|
124
126
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -145,31 +147,31 @@ function createDemoClient(appName = "Acme") {
|
|
|
145
147
|
});
|
|
146
148
|
const team = (id, body, ago2) => ({ id, body, team: true, author: null, mine: false, createdAt: t0 - ago2 });
|
|
147
149
|
const store = [
|
|
148
|
-
seed(12, "
|
|
150
|
+
seed(12, "Dark mode", "Easier on the eyes at night, and it'd match the rest of my phone.", 14, "planned", {
|
|
149
151
|
comments_: [
|
|
150
|
-
{ id: "c1", body: "Yes
|
|
151
|
-
team("c2", "On the list for December. Would
|
|
152
|
+
{ id: "c1", body: "Yes please. I use it every night.", team: false, author: "Raj", mine: false, createdAt: t0 - 3 * DAY },
|
|
153
|
+
team("c2", "On the list for December. Would following your phone's setting be enough to start?", 2 * DAY)
|
|
152
154
|
]
|
|
153
155
|
}),
|
|
154
|
-
seed(13, "
|
|
155
|
-
seed(7, "
|
|
156
|
-
shipNote: "
|
|
156
|
+
seed(13, "Export to CSV", "So I can open everything in a spreadsheet.", 11, "in_progress"),
|
|
157
|
+
seed(7, "Keyboard shortcuts", "", 8, "shipped", {
|
|
158
|
+
shipNote: "Press ? anywhere to see them all.",
|
|
157
159
|
shippedAt: t0 - 2 * DAY,
|
|
158
160
|
creditName: "Sam"
|
|
159
161
|
}),
|
|
160
|
-
seed(14, "
|
|
161
|
-
seed(15, "
|
|
162
|
-
seed(16, "Widget for the home screen", "Just today's
|
|
163
|
-
seed(9, "
|
|
164
|
-
shipNote: "
|
|
162
|
+
seed(14, "Offline mode", "I use it on the train and keep losing signal.", 6, "open"),
|
|
163
|
+
seed(15, "Share a link with a friend", "", 4, "open"),
|
|
164
|
+
seed(16, "Widget for the home screen", "Just today's number would be enough.", 3, "open"),
|
|
165
|
+
seed(9, "Sign in with Apple", "", 5, "shipped", {
|
|
166
|
+
shipNote: "You can now sign in with your Apple ID.",
|
|
165
167
|
shippedAt: t0 - 4 * 60 * MIN,
|
|
166
168
|
mine: true,
|
|
167
169
|
voted: true
|
|
168
170
|
}),
|
|
169
|
-
seed(17, "
|
|
171
|
+
seed(17, "A reminder the day before", "A nudge the day before something is due would do.", 2, "open", {
|
|
170
172
|
mine: true,
|
|
171
173
|
voted: true,
|
|
172
|
-
comments_: [team("c3", "Good one. Would a
|
|
174
|
+
comments_: [team("c3", "Good one. Would a push notification work, or would you rather get an email?", 3 * 60 * MIN)]
|
|
173
175
|
})
|
|
174
176
|
];
|
|
175
177
|
const find = (id) => store.find((r) => r.id === id);
|
|
@@ -234,7 +236,20 @@ function createDemoClient(appName = "Acme") {
|
|
|
234
236
|
await wait();
|
|
235
237
|
const r = find(id);
|
|
236
238
|
if (!r) throw new YouAskedError("not_found");
|
|
237
|
-
|
|
239
|
+
const othersJoined = r.votes > (r.mine && r.voted ? 1 : 0) || r.comments_.some((c) => !c.mine);
|
|
240
|
+
return {
|
|
241
|
+
request: view(r),
|
|
242
|
+
comments: [...r.comments_],
|
|
243
|
+
attachments: [...r.attachments_],
|
|
244
|
+
earlyAccess: r.early,
|
|
245
|
+
canDelete: Boolean(r.mine) && (r.status === "open" || r.status === "pending") && !othersJoined
|
|
246
|
+
};
|
|
247
|
+
},
|
|
248
|
+
async remove(id) {
|
|
249
|
+
await wait();
|
|
250
|
+
const i = store.findIndex((r) => r.id === id);
|
|
251
|
+
if (i >= 0) store.splice(i, 1);
|
|
252
|
+
return { ok: true };
|
|
238
253
|
},
|
|
239
254
|
async create(input) {
|
|
240
255
|
await wait(650);
|
|
@@ -329,11 +344,120 @@ var IconSpark = () => /* @__PURE__ */ jsxs("svg", { ...base, children: [
|
|
|
329
344
|
/* @__PURE__ */ jsx("path", { d: "m12 3 1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9Z" }),
|
|
330
345
|
/* @__PURE__ */ jsx("path", { d: "M19 16v4M17 18h4" })
|
|
331
346
|
] });
|
|
332
|
-
var IconIdea = () => /* @__PURE__ */ jsxs("svg", {
|
|
333
|
-
/* @__PURE__ */ jsx("path", { d: "
|
|
334
|
-
/* @__PURE__ */ jsx("path", { d: "
|
|
347
|
+
var IconIdea = ({ ink = "var(--_bg)" }) => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: [
|
|
348
|
+
/* @__PURE__ */ jsx("path", { d: "M6 2.25h12a4.5 4.5 0 0 1 4.5 4.5V15a4.5 4.5 0 0 1-4.5 4.5h-6.75L6 23.25V19.5A4.5 4.5 0 0 1 1.5 15V6.75A4.5 4.5 0 0 1 6 2.25Z", fill: "currentColor" }),
|
|
349
|
+
/* @__PURE__ */ jsx("path", { d: "M7.9 13.9 12 9.8l4.1 4.1", fill: "none", stroke: ink, strokeWidth: 2.6, strokeLinecap: "round", strokeLinejoin: "round" })
|
|
335
350
|
] });
|
|
336
351
|
|
|
352
|
+
// src/nudge.tsx
|
|
353
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
354
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
355
|
+
var NUDGE_DEFAULTS = { everyDays: 7, activeSeconds: 30, skipVisits: 1, message: null, position: "bottom" };
|
|
356
|
+
var DAY2 = 24 * 60 * 60 * 1e3;
|
|
357
|
+
var VISIT_GAP = 30 * 60 * 1e3;
|
|
358
|
+
function resolveNudge(server, prop) {
|
|
359
|
+
if (prop === false) return null;
|
|
360
|
+
if (typeof prop === "object") return { ...NUDGE_DEFAULTS, ...server, ...prop };
|
|
361
|
+
if (prop === true) return server ?? NUDGE_DEFAULTS;
|
|
362
|
+
return server ?? null;
|
|
363
|
+
}
|
|
364
|
+
function load(key) {
|
|
365
|
+
const empty = { visits: 0, lastVisitAt: 0, lastShownAt: 0, lastEngagedAt: 0, dismissals: 0 };
|
|
366
|
+
if (!key) return empty;
|
|
367
|
+
try {
|
|
368
|
+
return { ...empty, ...JSON.parse(localStorage.getItem(key) ?? "{}") };
|
|
369
|
+
} catch {
|
|
370
|
+
return empty;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function save(key, m) {
|
|
374
|
+
if (!key) return;
|
|
375
|
+
try {
|
|
376
|
+
localStorage.setItem(key, JSON.stringify(m));
|
|
377
|
+
} catch {
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function useNudge(settings, storageKey, blocked) {
|
|
381
|
+
const memory = useRef(null);
|
|
382
|
+
const [show, setShow] = useState(false);
|
|
383
|
+
useEffect(() => {
|
|
384
|
+
if (!settings || memory.current) return;
|
|
385
|
+
const m = load(storageKey);
|
|
386
|
+
const now = Date.now();
|
|
387
|
+
if (now - m.lastVisitAt > VISIT_GAP) m.visits += 1;
|
|
388
|
+
m.lastVisitAt = now;
|
|
389
|
+
memory.current = m;
|
|
390
|
+
save(storageKey, m);
|
|
391
|
+
}, [settings, storageKey]);
|
|
392
|
+
useEffect(() => {
|
|
393
|
+
if (!settings || show) return;
|
|
394
|
+
let active = 0;
|
|
395
|
+
const timer = window.setInterval(() => {
|
|
396
|
+
if (document.visibilityState !== "visible" || blocked) return;
|
|
397
|
+
active += 1;
|
|
398
|
+
if (active < settings.activeSeconds) return;
|
|
399
|
+
window.clearInterval(timer);
|
|
400
|
+
const m = memory.current;
|
|
401
|
+
if (!m) return;
|
|
402
|
+
const now = Date.now();
|
|
403
|
+
const wait2 = settings.everyDays * DAY2 * 2 ** Math.min(m.dismissals, 3);
|
|
404
|
+
const due = !storageKey || m.visits > settings.skipVisits && now - m.lastShownAt >= wait2 && now - m.lastEngagedAt >= wait2;
|
|
405
|
+
if (!due) return;
|
|
406
|
+
m.lastShownAt = now;
|
|
407
|
+
save(storageKey, m);
|
|
408
|
+
setShow(true);
|
|
409
|
+
}, 1e3);
|
|
410
|
+
return () => window.clearInterval(timer);
|
|
411
|
+
}, [settings, storageKey, blocked, show]);
|
|
412
|
+
const engaged = useCallback(() => {
|
|
413
|
+
const m = memory.current;
|
|
414
|
+
if (m) {
|
|
415
|
+
m.lastEngagedAt = Date.now();
|
|
416
|
+
m.dismissals = 0;
|
|
417
|
+
save(storageKey, m);
|
|
418
|
+
}
|
|
419
|
+
setShow(false);
|
|
420
|
+
}, [storageKey]);
|
|
421
|
+
const dismiss = useCallback(() => {
|
|
422
|
+
const m = memory.current;
|
|
423
|
+
if (m) {
|
|
424
|
+
m.dismissals += 1;
|
|
425
|
+
save(storageKey, m);
|
|
426
|
+
}
|
|
427
|
+
setShow(false);
|
|
428
|
+
}, [storageKey]);
|
|
429
|
+
return { show, engaged, dismiss };
|
|
430
|
+
}
|
|
431
|
+
function NudgeToast({
|
|
432
|
+
settings,
|
|
433
|
+
appName,
|
|
434
|
+
position,
|
|
435
|
+
trigger,
|
|
436
|
+
onOpen,
|
|
437
|
+
onDismiss
|
|
438
|
+
}) {
|
|
439
|
+
return /* @__PURE__ */ jsxs2("div", { className: "ya-toast ya-nudge", role: "status", "data-pos": position, "data-trigger": trigger, "data-place": settings.position, children: [
|
|
440
|
+
/* @__PURE__ */ jsxs2("div", { className: "ya-toast-top", children: [
|
|
441
|
+
/* @__PURE__ */ jsxs2("p", { className: "ya-toast-kicker", children: [
|
|
442
|
+
"Help shape ",
|
|
443
|
+
appName
|
|
444
|
+
] }),
|
|
445
|
+
/* @__PURE__ */ jsx2("button", { type: "button", className: "ya-icon-btn", onClick: onDismiss, "aria-label": "Dismiss", children: /* @__PURE__ */ jsx2(IconClose, {}) })
|
|
446
|
+
] }),
|
|
447
|
+
/* @__PURE__ */ jsxs2("div", { className: "ya-nudge-body", children: [
|
|
448
|
+
/* @__PURE__ */ jsx2("span", { className: "ya-card-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(IconIdea, {}) }),
|
|
449
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
450
|
+
/* @__PURE__ */ jsx2("p", { className: "ya-toast-title", children: settings.message ?? `Got an idea for ${appName}?` }),
|
|
451
|
+
/* @__PURE__ */ jsx2("p", { className: "ya-toast-note", children: "Ask for a feature or vote on what others want." })
|
|
452
|
+
] })
|
|
453
|
+
] }),
|
|
454
|
+
/* @__PURE__ */ jsxs2("div", { className: "ya-toast-actions", children: [
|
|
455
|
+
/* @__PURE__ */ jsx2("button", { type: "button", className: "ya-btn ya-btn-sm", onClick: onOpen, children: "Share an idea" }),
|
|
456
|
+
/* @__PURE__ */ jsx2("button", { type: "button", className: "ya-btn ya-btn-sm ya-btn-ghost", onClick: onDismiss, children: "Not now" })
|
|
457
|
+
] })
|
|
458
|
+
] });
|
|
459
|
+
}
|
|
460
|
+
|
|
337
461
|
// src/styles.ts
|
|
338
462
|
var css = `
|
|
339
463
|
.ya-scope{--_bg:var(--ya-bg);--_fg:var(--ya-fg);--_accent:var(--ya-accent);--_accent-fg:var(--ya-accent-fg);
|
|
@@ -392,6 +516,9 @@ box-shadow:0 0 0 1px var(--_border),0 24px 70px rgba(0,0,0,.22);padding-bottom:0
|
|
|
392
516
|
.ya-link{display:inline-flex;align-items:center;gap:2px;font-size:14px;font-weight:600;color:var(--_accent);padding:6px 8px;border-radius:10px;flex:none}
|
|
393
517
|
.ya-link svg{width:15px;height:15px}
|
|
394
518
|
.ya-link:hover{background:var(--_tint)}
|
|
519
|
+
.ya-delete-row{display:flex;align-items:center;flex-wrap:wrap;gap:4px}
|
|
520
|
+
.ya-link-quiet{color:var(--_muted);font-weight:500;padding-left:0}
|
|
521
|
+
.ya-link-danger{color:#e5484d}
|
|
395
522
|
.ya-avatar{display:block;flex:none;border-radius:24%;object-fit:cover;background:var(--_surface)}
|
|
396
523
|
|
|
397
524
|
/* ---------- body, lists */
|
|
@@ -561,6 +688,10 @@ box-shadow:0 0 0 1px var(--_border),0 24px 70px rgba(0,0,0,.22);padding-bottom:0
|
|
|
561
688
|
.ya-toast .ya-live{margin-top:8px}
|
|
562
689
|
.ya-live svg{width:15px;height:15px}
|
|
563
690
|
.ya-toast-note{font-size:13.5px;color:var(--_muted);margin-top:6px;overflow-wrap:anywhere}
|
|
691
|
+
.ya-toast[data-place=top]{top:calc(16px + env(safe-area-inset-top));bottom:auto}
|
|
692
|
+
.ya-nudge-body{display:flex;gap:12px;align-items:flex-start;margin-top:6px}
|
|
693
|
+
.ya-nudge-body .ya-card-icon{flex:none;margin-top:2px}
|
|
694
|
+
.ya-nudge-body .ya-toast-title{margin-top:0}
|
|
564
695
|
.ya-toast-actions{display:flex;gap:8px;margin-top:12px}
|
|
565
696
|
.ya-toast-quote{color:var(--_fg);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
|
|
566
697
|
.ya-toast .ya-icon-btn{width:30px;height:30px;margin:-6px -6px 0 0;background:none}
|
|
@@ -746,13 +877,13 @@ function tokenStyle(t) {
|
|
|
746
877
|
}
|
|
747
878
|
|
|
748
879
|
// src/shared.tsx
|
|
749
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
750
|
-
import { jsx as
|
|
880
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
881
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
751
882
|
function useVote(client, initial) {
|
|
752
|
-
const [state, setState] =
|
|
753
|
-
const busy =
|
|
754
|
-
|
|
755
|
-
const toggle =
|
|
883
|
+
const [state, setState] = useState2({ voted: initial.voted, votes: initial.votes });
|
|
884
|
+
const busy = useRef2(false);
|
|
885
|
+
useEffect2(() => setState({ voted: initial.voted, votes: initial.votes }), [initial.voted, initial.votes]);
|
|
886
|
+
const toggle = useCallback2(async () => {
|
|
756
887
|
if (busy.current) return;
|
|
757
888
|
busy.current = true;
|
|
758
889
|
const before = state;
|
|
@@ -769,7 +900,7 @@ function useVote(client, initial) {
|
|
|
769
900
|
}
|
|
770
901
|
function VoteButton({ client, request, compact }) {
|
|
771
902
|
const { voted, votes, toggle } = useVote(client, request);
|
|
772
|
-
return /* @__PURE__ */
|
|
903
|
+
return /* @__PURE__ */ jsxs3(
|
|
773
904
|
"button",
|
|
774
905
|
{
|
|
775
906
|
type: "button",
|
|
@@ -781,7 +912,7 @@ function VoteButton({ client, request, compact }) {
|
|
|
781
912
|
toggle();
|
|
782
913
|
},
|
|
783
914
|
children: [
|
|
784
|
-
/* @__PURE__ */
|
|
915
|
+
/* @__PURE__ */ jsx3(IconUp, {}),
|
|
785
916
|
votes
|
|
786
917
|
]
|
|
787
918
|
}
|
|
@@ -793,9 +924,9 @@ function BigVote({
|
|
|
793
924
|
onChange
|
|
794
925
|
}) {
|
|
795
926
|
const { voted, votes, toggle } = useVote(client, request);
|
|
796
|
-
|
|
797
|
-
return /* @__PURE__ */
|
|
798
|
-
/* @__PURE__ */
|
|
927
|
+
useEffect2(() => onChange?.(voted), [voted, onChange]);
|
|
928
|
+
return /* @__PURE__ */ jsxs3("button", { type: "button", className: "ya-bigvote", "aria-pressed": voted, onClick: toggle, children: [
|
|
929
|
+
/* @__PURE__ */ jsx3(IconUp, {}),
|
|
799
930
|
voted ? "Voted" : "Upvote",
|
|
800
931
|
" \xB7 ",
|
|
801
932
|
votes
|
|
@@ -806,11 +937,12 @@ var STATUS_LABEL = {
|
|
|
806
937
|
planned: "Planned",
|
|
807
938
|
in_progress: "In progress",
|
|
808
939
|
shipped: "Shipped",
|
|
809
|
-
declined: "Not planned"
|
|
940
|
+
declined: "Not planned",
|
|
941
|
+
pending: "Awaiting approval"
|
|
810
942
|
};
|
|
811
943
|
function StatusPill({ status }) {
|
|
812
|
-
return /* @__PURE__ */
|
|
813
|
-
status === "shipped" && /* @__PURE__ */
|
|
944
|
+
return /* @__PURE__ */ jsxs3("span", { className: "ya-pill", "data-s": status, children: [
|
|
945
|
+
status === "shipped" && /* @__PURE__ */ jsx3(IconCheck, {}),
|
|
814
946
|
STATUS_LABEL[status]
|
|
815
947
|
] });
|
|
816
948
|
}
|
|
@@ -820,45 +952,48 @@ function RequestRow({
|
|
|
820
952
|
onOpen,
|
|
821
953
|
compact
|
|
822
954
|
}) {
|
|
823
|
-
return /* @__PURE__ */
|
|
824
|
-
/* @__PURE__ */
|
|
825
|
-
/* @__PURE__ */
|
|
826
|
-
/* @__PURE__ */
|
|
827
|
-
(request.status !== "open" || request.comments > 0 || request.mine) && /* @__PURE__ */
|
|
828
|
-
request.status !== "open" && /* @__PURE__ */
|
|
829
|
-
request.comments > 0 && /* @__PURE__ */
|
|
830
|
-
/* @__PURE__ */
|
|
955
|
+
return /* @__PURE__ */ jsxs3("li", { className: compact ? "ya-row ya-row-sm" : "ya-row", children: [
|
|
956
|
+
/* @__PURE__ */ jsx3(VoteButton, { client, request, compact }),
|
|
957
|
+
/* @__PURE__ */ jsxs3("button", { type: "button", className: "ya-row-main", onClick: onOpen, children: [
|
|
958
|
+
/* @__PURE__ */ jsx3("span", { className: "ya-row-title", children: request.title }),
|
|
959
|
+
(request.status !== "open" || request.comments > 0 || request.mine) && /* @__PURE__ */ jsxs3("span", { className: "ya-meta", children: [
|
|
960
|
+
request.status !== "open" && /* @__PURE__ */ jsx3(StatusPill, { status: request.status }),
|
|
961
|
+
request.comments > 0 && /* @__PURE__ */ jsxs3("span", { children: [
|
|
962
|
+
/* @__PURE__ */ jsx3(IconChat, {}),
|
|
831
963
|
request.comments
|
|
832
964
|
] }),
|
|
833
|
-
request.mine && /* @__PURE__ */
|
|
965
|
+
request.mine && /* @__PURE__ */ jsx3("span", { children: "You asked" })
|
|
834
966
|
] })
|
|
835
967
|
] })
|
|
836
968
|
] });
|
|
837
969
|
}
|
|
838
970
|
function Skeleton({ rows = 3 }) {
|
|
839
|
-
return /* @__PURE__ */
|
|
971
|
+
return /* @__PURE__ */ jsx3("div", { className: "ya-list", "aria-label": "Loading", role: "status", children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsx3("div", { className: "ya-skel" }, i)) });
|
|
840
972
|
}
|
|
841
|
-
function useLoad(
|
|
842
|
-
const [data, setData] =
|
|
843
|
-
const [error, setError] =
|
|
844
|
-
|
|
973
|
+
function useLoad(load2, deps) {
|
|
974
|
+
const [data, setData] = useState2(null);
|
|
975
|
+
const [error, setError] = useState2(null);
|
|
976
|
+
useEffect2(() => {
|
|
845
977
|
let alive = true;
|
|
846
978
|
setData(null);
|
|
847
979
|
setError(null);
|
|
848
|
-
|
|
980
|
+
load2().then((d) => alive && setData(d)).catch((e) => alive && setError(e instanceof YouAskedError ? e.code : "network"));
|
|
849
981
|
return () => {
|
|
850
982
|
alive = false;
|
|
851
983
|
};
|
|
852
984
|
}, deps);
|
|
853
985
|
return { data, error, setData };
|
|
854
986
|
}
|
|
855
|
-
function errorText(code) {
|
|
987
|
+
function errorText(code, detail) {
|
|
988
|
+
if (detail) return detail;
|
|
856
989
|
switch (code) {
|
|
857
990
|
case "signature_required":
|
|
858
991
|
case "bad_signature":
|
|
859
992
|
return "We couldn't confirm who you are. Refresh the page and try again.";
|
|
860
993
|
case "slow_down":
|
|
861
994
|
return "That's a lot of requests in an hour. Try again a bit later.";
|
|
995
|
+
case "looks_like_spam":
|
|
996
|
+
return "That looks like spam, so we didn't post it. Reword it and try again.";
|
|
862
997
|
case "title_too_short":
|
|
863
998
|
return "Add a few more words so others know what you mean.";
|
|
864
999
|
case "network":
|
|
@@ -882,9 +1017,9 @@ function ago(t) {
|
|
|
882
1017
|
return new Date(t).toLocaleDateString(void 0, { month: "short", day: "numeric" });
|
|
883
1018
|
}
|
|
884
1019
|
function AppAvatar({ icon, size = 36 }) {
|
|
885
|
-
const [failed, setFailed] =
|
|
1020
|
+
const [failed, setFailed] = useState2(false);
|
|
886
1021
|
if (!icon || failed) return null;
|
|
887
|
-
return /* @__PURE__ */
|
|
1022
|
+
return /* @__PURE__ */ jsx3(
|
|
888
1023
|
"img",
|
|
889
1024
|
{
|
|
890
1025
|
className: "ya-avatar",
|
|
@@ -905,31 +1040,31 @@ function Header({
|
|
|
905
1040
|
onClose,
|
|
906
1041
|
right
|
|
907
1042
|
}) {
|
|
908
|
-
return /* @__PURE__ */
|
|
909
|
-
onBack && /* @__PURE__ */
|
|
1043
|
+
return /* @__PURE__ */ jsxs3("div", { className: onBack ? "ya-head ya-head-back" : "ya-head", children: [
|
|
1044
|
+
onBack && /* @__PURE__ */ jsx3("button", { type: "button", className: "ya-icon-btn", onClick: onBack, "aria-label": "Back", children: /* @__PURE__ */ jsx3(IconBack, {}) }),
|
|
910
1045
|
leading,
|
|
911
|
-
/* @__PURE__ */
|
|
912
|
-
/* @__PURE__ */
|
|
913
|
-
subtitle && /* @__PURE__ */
|
|
1046
|
+
/* @__PURE__ */ jsxs3("div", { className: "ya-head-text", children: [
|
|
1047
|
+
/* @__PURE__ */ jsx3("h2", { className: "ya-h", children: title }),
|
|
1048
|
+
subtitle && /* @__PURE__ */ jsx3("p", { className: "ya-sub", children: subtitle })
|
|
914
1049
|
] }),
|
|
915
1050
|
right,
|
|
916
|
-
onClose && /* @__PURE__ */
|
|
1051
|
+
onClose && /* @__PURE__ */ jsx3("button", { type: "button", className: "ya-icon-btn", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx3(IconClose, {}) })
|
|
917
1052
|
] });
|
|
918
1053
|
}
|
|
919
1054
|
|
|
920
1055
|
// src/views.tsx
|
|
921
|
-
import { useEffect as
|
|
922
|
-
import { Fragment, jsx as
|
|
1056
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
1057
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
923
1058
|
function Navigator({ route, setRoute, onClose }) {
|
|
924
1059
|
switch (route.view) {
|
|
925
1060
|
case "home":
|
|
926
|
-
return /* @__PURE__ */
|
|
1061
|
+
return /* @__PURE__ */ jsx4(Home, { route, setRoute, onClose }, `${route.draft ?? ""}${route.focus ? "f" : ""}${route.topic ?? ""}`);
|
|
927
1062
|
case "board":
|
|
928
|
-
return /* @__PURE__ */
|
|
1063
|
+
return /* @__PURE__ */ jsx4(Board, { initialTab: route.tab, setRoute, onClose });
|
|
929
1064
|
case "detail":
|
|
930
|
-
return /* @__PURE__ */
|
|
1065
|
+
return /* @__PURE__ */ jsx4(Detail, { id: route.id, from: route.from, setRoute, onClose }, route.id);
|
|
931
1066
|
case "mine":
|
|
932
|
-
return /* @__PURE__ */
|
|
1067
|
+
return /* @__PURE__ */ jsx4(Mine, { setRoute, onClose });
|
|
933
1068
|
}
|
|
934
1069
|
}
|
|
935
1070
|
function splitMessage(text) {
|
|
@@ -941,23 +1076,23 @@ function splitMessage(text) {
|
|
|
941
1076
|
return { title, body: t };
|
|
942
1077
|
}
|
|
943
1078
|
function ReplyBubble({ notice, appName, onOpen }) {
|
|
944
|
-
return /* @__PURE__ */
|
|
945
|
-
/* @__PURE__ */
|
|
1079
|
+
return /* @__PURE__ */ jsxs4("div", { className: "ya-bubble ya-bubble-in", children: [
|
|
1080
|
+
/* @__PURE__ */ jsxs4("span", { className: "ya-bubble-kicker", children: [
|
|
946
1081
|
appName,
|
|
947
1082
|
" replied about ",
|
|
948
1083
|
notice.title
|
|
949
1084
|
] }),
|
|
950
|
-
/* @__PURE__ */
|
|
951
|
-
/* @__PURE__ */
|
|
1085
|
+
/* @__PURE__ */ jsx4("span", { children: notice.reply }),
|
|
1086
|
+
/* @__PURE__ */ jsx4("button", { type: "button", className: "ya-bubble-link", onClick: onOpen, children: "Reply" })
|
|
952
1087
|
] });
|
|
953
1088
|
}
|
|
954
1089
|
var MAX_SHOTS = 3;
|
|
955
1090
|
function Confetti() {
|
|
956
1091
|
const colors = ["var(--_accent)", "var(--_green)", "var(--_amber)", "#ff7eb3"];
|
|
957
|
-
return /* @__PURE__ */
|
|
1092
|
+
return /* @__PURE__ */ jsx4("span", { className: "ya-confetti", "aria-hidden": "true", children: Array.from({ length: 14 }, (_, i) => {
|
|
958
1093
|
const angle = Math.PI * (0.15 + i / 13 * 0.7) % Math.PI;
|
|
959
1094
|
const dist = 38 + i % 4 * 14;
|
|
960
|
-
return /* @__PURE__ */
|
|
1095
|
+
return /* @__PURE__ */ jsx4(
|
|
961
1096
|
"i",
|
|
962
1097
|
{
|
|
963
1098
|
style: {
|
|
@@ -976,33 +1111,33 @@ function Home({ route, setRoute, onClose }) {
|
|
|
976
1111
|
const { client, config, appName, appIcon, greeting, markSeen, whatsNew, dismissWhatsNew, pointAtIt } = useInternal();
|
|
977
1112
|
const top = useLoad(() => client.list("top", "active"), [client]);
|
|
978
1113
|
const shipped = useLoad(() => client.list("new", "shipped"), [client]);
|
|
979
|
-
const [text, setText] =
|
|
980
|
-
const [thread, setThread] =
|
|
981
|
-
const [similar, setSimilar] =
|
|
982
|
-
const [notices] =
|
|
983
|
-
const [news] =
|
|
984
|
-
const [shots, setShots] =
|
|
985
|
-
const [shotError, setShotError] =
|
|
986
|
-
const [credit, setCredit] =
|
|
987
|
-
const urls =
|
|
988
|
-
const inputRef =
|
|
989
|
-
const bodyRef =
|
|
1114
|
+
const [text, setText] = useState3(route.draft ?? "");
|
|
1115
|
+
const [thread, setThread] = useState3([]);
|
|
1116
|
+
const [similar, setSimilar] = useState3([]);
|
|
1117
|
+
const [notices] = useState3(() => config?.unseen ?? []);
|
|
1118
|
+
const [news] = useState3(() => whatsNew);
|
|
1119
|
+
const [shots, setShots] = useState3([]);
|
|
1120
|
+
const [shotError, setShotError] = useState3(null);
|
|
1121
|
+
const [credit, setCredit] = useState3(false);
|
|
1122
|
+
const urls = useRef3([]);
|
|
1123
|
+
const inputRef = useRef3(null);
|
|
1124
|
+
const bodyRef = useRef3(null);
|
|
990
1125
|
const sending = thread.some((t) => t.kind === "typing");
|
|
991
|
-
|
|
1126
|
+
useEffect3(() => {
|
|
992
1127
|
if (notices.length) markSeen(notices.map((n) => n.id));
|
|
993
1128
|
}, [notices, markSeen]);
|
|
994
|
-
|
|
1129
|
+
useEffect3(() => {
|
|
995
1130
|
if (news) dismissWhatsNew();
|
|
996
1131
|
}, [news, dismissWhatsNew]);
|
|
997
1132
|
const arrived = config?.unseen.filter((n) => n.kind === "reply" && !notices.some((o) => o.id === n.id)) ?? [];
|
|
998
1133
|
const arrivedKey = arrived.map((n) => n.id).join(",");
|
|
999
|
-
|
|
1134
|
+
useEffect3(() => {
|
|
1000
1135
|
if (!arrived.length) return;
|
|
1001
1136
|
setThread((th) => [...th, ...arrived.map((notice) => ({ kind: "reply", notice }))]);
|
|
1002
1137
|
markSeen(arrived.map((n) => n.id));
|
|
1003
1138
|
}, [arrivedKey]);
|
|
1004
|
-
|
|
1005
|
-
|
|
1139
|
+
useEffect3(() => () => urls.current.forEach((u) => URL.revokeObjectURL(u)), []);
|
|
1140
|
+
useEffect3(() => {
|
|
1006
1141
|
if (!route.focus) return;
|
|
1007
1142
|
const t = setTimeout(() => {
|
|
1008
1143
|
const el = inputRef.current;
|
|
@@ -1012,16 +1147,16 @@ function Home({ route, setRoute, onClose }) {
|
|
|
1012
1147
|
}, 60);
|
|
1013
1148
|
return () => clearTimeout(t);
|
|
1014
1149
|
}, [route.focus]);
|
|
1015
|
-
|
|
1150
|
+
useEffect3(() => {
|
|
1016
1151
|
const el = inputRef.current;
|
|
1017
1152
|
if (!el) return;
|
|
1018
1153
|
el.style.height = "";
|
|
1019
1154
|
if (text.includes("\n") || el.scrollHeight > el.clientHeight + 2) el.style.height = `${Math.min(el.scrollHeight, 132)}px`;
|
|
1020
1155
|
}, [text]);
|
|
1021
|
-
|
|
1156
|
+
useEffect3(() => {
|
|
1022
1157
|
if (thread.length) bodyRef.current?.scrollTo({ top: bodyRef.current.scrollHeight, behavior: "smooth" });
|
|
1023
1158
|
}, [thread]);
|
|
1024
|
-
|
|
1159
|
+
useEffect3(() => {
|
|
1025
1160
|
const q = text.trim();
|
|
1026
1161
|
if (q.length < 4) {
|
|
1027
1162
|
setSimilar([]);
|
|
@@ -1074,7 +1209,7 @@ function Home({ route, setRoute, onClose }) {
|
|
|
1074
1209
|
} catch (err) {
|
|
1075
1210
|
setThread((th) => [
|
|
1076
1211
|
...th.filter((t) => t.kind !== "typing" && !(t.kind === "out" && t.text === message)),
|
|
1077
|
-
{ kind: "error", text: errorText(err instanceof YouAskedError ? err.code : "network") }
|
|
1212
|
+
{ kind: "error", text: errorText(err instanceof YouAskedError ? err.code : "network", err instanceof YouAskedError ? err.detail : void 0) }
|
|
1078
1213
|
]);
|
|
1079
1214
|
setText(message);
|
|
1080
1215
|
setShots(attached);
|
|
@@ -1082,106 +1217,106 @@ function Home({ route, setRoute, onClose }) {
|
|
|
1082
1217
|
}
|
|
1083
1218
|
};
|
|
1084
1219
|
const chatting = thread.length > 0;
|
|
1085
|
-
const openRequest = (id) => setRoute({ view: "detail", id });
|
|
1220
|
+
const openRequest = (id) => setRoute({ view: "detail", id, from: "home" });
|
|
1086
1221
|
const asking = route.topic && route.placeholder ? route.topic : null;
|
|
1087
1222
|
const placeholder = route.placeholder ?? "Ask for a feature\u2026";
|
|
1088
1223
|
const yours = news?.items.find((i) => i.mine);
|
|
1089
1224
|
const listed = new Set(news?.items.map((i) => i.id));
|
|
1090
1225
|
const shownNotices = notices.filter((n) => !(n.kind === "shipped" && listed.has(n.requestId)));
|
|
1091
|
-
return /* @__PURE__ */
|
|
1092
|
-
/* @__PURE__ */
|
|
1226
|
+
return /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1227
|
+
/* @__PURE__ */ jsx4(
|
|
1093
1228
|
Header,
|
|
1094
1229
|
{
|
|
1095
|
-
leading: /* @__PURE__ */
|
|
1230
|
+
leading: /* @__PURE__ */ jsx4(AppAvatar, { icon: appIcon }),
|
|
1096
1231
|
title: appName,
|
|
1097
1232
|
subtitle: "Ideas, votes and what's new",
|
|
1098
1233
|
onClose
|
|
1099
1234
|
}
|
|
1100
1235
|
),
|
|
1101
|
-
/* @__PURE__ */
|
|
1102
|
-
/* @__PURE__ */
|
|
1103
|
-
news && /* @__PURE__ */
|
|
1104
|
-
yours && /* @__PURE__ */
|
|
1105
|
-
/* @__PURE__ */
|
|
1106
|
-
/* @__PURE__ */
|
|
1107
|
-
/* @__PURE__ */
|
|
1108
|
-
/* @__PURE__ */
|
|
1109
|
-
/* @__PURE__ */
|
|
1110
|
-
i.mine && /* @__PURE__ */
|
|
1236
|
+
/* @__PURE__ */ jsxs4("div", { className: "ya-body ya-chat", ref: bodyRef, children: [
|
|
1237
|
+
/* @__PURE__ */ jsx4("div", { className: "ya-bubble ya-bubble-in", children: asking ? `Let's talk about ${asking}. What's missing, or what bugs you?` : greeting ?? `Hey! What would make ${appName} better for you?` }),
|
|
1238
|
+
news && /* @__PURE__ */ jsxs4("div", { className: yours ? "ya-bubble ya-bubble-in ya-bubble-win ya-bubble-news" : "ya-bubble ya-bubble-in ya-bubble-news", children: [
|
|
1239
|
+
yours && /* @__PURE__ */ jsx4(Confetti, {}),
|
|
1240
|
+
/* @__PURE__ */ jsx4("span", { className: "ya-bubble-kicker", children: whatsNewHeading(news.total) }),
|
|
1241
|
+
/* @__PURE__ */ jsx4("ul", { className: "ya-news", children: news.items.map((i) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsxs4("button", { type: "button", onClick: () => openRequest(i.id), children: [
|
|
1242
|
+
/* @__PURE__ */ jsx4("span", { className: "ya-shipped-dot", children: /* @__PURE__ */ jsx4(IconCheck, {}) }),
|
|
1243
|
+
/* @__PURE__ */ jsxs4("span", { className: "ya-news-text", children: [
|
|
1244
|
+
/* @__PURE__ */ jsx4("span", { className: "ya-news-title", children: i.title }),
|
|
1245
|
+
i.mine && /* @__PURE__ */ jsx4("span", { className: "ya-news-you", children: "You asked for this" })
|
|
1111
1246
|
] }),
|
|
1112
|
-
/* @__PURE__ */
|
|
1247
|
+
/* @__PURE__ */ jsx4(IconChevron, {})
|
|
1113
1248
|
] }) }, i.id)) }),
|
|
1114
|
-
yours && /* @__PURE__ */
|
|
1115
|
-
news.total > news.items.length && /* @__PURE__ */
|
|
1249
|
+
yours && /* @__PURE__ */ jsx4("span", { children: "That one was yours. Thanks for asking." }),
|
|
1250
|
+
news.total > news.items.length && /* @__PURE__ */ jsxs4("button", { type: "button", className: "ya-bubble-link", onClick: () => setRoute({ view: "board", tab: "shipped" }), children: [
|
|
1116
1251
|
"See all ",
|
|
1117
1252
|
news.total
|
|
1118
1253
|
] })
|
|
1119
1254
|
] }),
|
|
1120
1255
|
shownNotices.map(
|
|
1121
|
-
(n) => n.kind === "reply" ? /* @__PURE__ */
|
|
1122
|
-
/* @__PURE__ */
|
|
1123
|
-
/* @__PURE__ */
|
|
1256
|
+
(n) => n.kind === "reply" ? /* @__PURE__ */ jsx4(ReplyBubble, { notice: n, appName, onOpen: () => openRequest(n.requestId) }, n.id) : /* @__PURE__ */ jsxs4("div", { className: "ya-bubble ya-bubble-in ya-bubble-win", children: [
|
|
1257
|
+
/* @__PURE__ */ jsxs4("span", { className: "ya-live", children: [
|
|
1258
|
+
/* @__PURE__ */ jsx4(IconCheck, {}),
|
|
1124
1259
|
"It's live"
|
|
1125
1260
|
] }),
|
|
1126
|
-
/* @__PURE__ */
|
|
1261
|
+
/* @__PURE__ */ jsxs4("span", { children: [
|
|
1127
1262
|
"You asked for ",
|
|
1128
|
-
/* @__PURE__ */
|
|
1263
|
+
/* @__PURE__ */ jsx4("b", { children: n.title }),
|
|
1129
1264
|
". ",
|
|
1130
1265
|
n.shipNote ?? "Thanks for asking."
|
|
1131
1266
|
] }),
|
|
1132
|
-
/* @__PURE__ */
|
|
1267
|
+
/* @__PURE__ */ jsx4("button", { type: "button", className: "ya-bubble-link", onClick: () => openRequest(n.requestId), children: "See it" })
|
|
1133
1268
|
] }, n.id)
|
|
1134
1269
|
),
|
|
1135
|
-
!chatting && /* @__PURE__ */
|
|
1136
|
-
/* @__PURE__ */
|
|
1137
|
-
shipped.data && shipped.data.length > 0 && /* @__PURE__ */
|
|
1138
|
-
/* @__PURE__ */
|
|
1139
|
-
/* @__PURE__ */
|
|
1140
|
-
/* @__PURE__ */
|
|
1270
|
+
!chatting && /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1271
|
+
/* @__PURE__ */ jsx4(Section, { title: "Popular right now", action: "See all", onAction: () => setRoute({ view: "board" }), children: top.error ? /* @__PURE__ */ jsx4("p", { className: "ya-note", children: errorText(top.error) }) : !top.data ? /* @__PURE__ */ jsx4(Skeleton, { rows: 2 }) : top.data.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "ya-note", children: "No requests yet. Yours could be the first." }) : /* @__PURE__ */ jsx4("ul", { className: "ya-list", children: top.data.slice(0, 3).map((r) => /* @__PURE__ */ jsx4(RequestRow, { client, request: r, compact: true, onOpen: () => openRequest(r.id) }, r.id)) }) }),
|
|
1272
|
+
shipped.data && shipped.data.length > 0 && /* @__PURE__ */ jsx4(Section, { title: "Recently shipped", action: "All", onAction: () => setRoute({ view: "board", tab: "shipped" }), children: /* @__PURE__ */ jsx4("ul", { className: "ya-shipped", children: shipped.data.slice(0, 3).map((r) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsxs4("button", { type: "button", onClick: () => openRequest(r.id), children: [
|
|
1273
|
+
/* @__PURE__ */ jsx4("span", { className: "ya-shipped-dot", children: /* @__PURE__ */ jsx4(IconCheck, {}) }),
|
|
1274
|
+
/* @__PURE__ */ jsx4("span", { className: "ya-shipped-title", children: r.title }),
|
|
1275
|
+
/* @__PURE__ */ jsx4("span", { className: "ya-shipped-when", children: r.shippedAt ? ago(r.shippedAt) : "" })
|
|
1141
1276
|
] }) }, r.id)) }) })
|
|
1142
1277
|
] }),
|
|
1143
1278
|
thread.map(
|
|
1144
|
-
(t, i) => t.kind === "out" ? /* @__PURE__ */
|
|
1145
|
-
t.images?.map((src) => /* @__PURE__ */
|
|
1279
|
+
(t, i) => t.kind === "out" ? /* @__PURE__ */ jsxs4("div", { className: "ya-bubble ya-bubble-out", children: [
|
|
1280
|
+
t.images?.map((src) => /* @__PURE__ */ jsx4("img", { className: "ya-bubble-img", src, alt: "Your screenshot" }, src)),
|
|
1146
1281
|
t.text
|
|
1147
|
-
] }, i) : t.kind === "typing" ? /* @__PURE__ */
|
|
1148
|
-
/* @__PURE__ */
|
|
1149
|
-
/* @__PURE__ */
|
|
1150
|
-
/* @__PURE__ */
|
|
1151
|
-
] }, i) : t.kind === "reply" ? /* @__PURE__ */
|
|
1152
|
-
/* @__PURE__ */
|
|
1282
|
+
] }, i) : t.kind === "typing" ? /* @__PURE__ */ jsxs4("div", { className: "ya-bubble ya-bubble-in ya-typing", "aria-label": "Posting", children: [
|
|
1283
|
+
/* @__PURE__ */ jsx4("span", {}),
|
|
1284
|
+
/* @__PURE__ */ jsx4("span", {}),
|
|
1285
|
+
/* @__PURE__ */ jsx4("span", {})
|
|
1286
|
+
] }, i) : t.kind === "reply" ? /* @__PURE__ */ jsx4(ReplyBubble, { notice: t.notice, appName, onOpen: () => openRequest(t.notice.requestId) }, i) : t.kind === "posted" ? /* @__PURE__ */ jsxs4("div", { className: "ya-bubble ya-bubble-in", children: [
|
|
1287
|
+
/* @__PURE__ */ jsxs4("span", { children: [
|
|
1153
1288
|
"Got it. ",
|
|
1154
|
-
/* @__PURE__ */
|
|
1289
|
+
/* @__PURE__ */ jsx4("b", { children: t.request.title }),
|
|
1155
1290
|
" is on the board, and we'll tell you right here when it ships."
|
|
1156
1291
|
] }),
|
|
1157
|
-
/* @__PURE__ */
|
|
1158
|
-
] }, i) : /* @__PURE__ */
|
|
1292
|
+
/* @__PURE__ */ jsx4("button", { type: "button", className: "ya-bubble-link", onClick: () => openRequest(t.request.id), children: "View request" })
|
|
1293
|
+
] }, i) : /* @__PURE__ */ jsx4("div", { className: "ya-bubble ya-bubble-in ya-bubble-error", role: "alert", children: t.text }, i)
|
|
1159
1294
|
),
|
|
1160
|
-
chatting && !sending && /* @__PURE__ */
|
|
1295
|
+
chatting && !sending && /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-chip", onClick: () => setRoute({ view: "board" }), children: "See what others asked for" })
|
|
1161
1296
|
] }),
|
|
1162
|
-
/* @__PURE__ */
|
|
1163
|
-
similar.length > 0 && /* @__PURE__ */
|
|
1164
|
-
/* @__PURE__ */
|
|
1165
|
-
/* @__PURE__ */
|
|
1297
|
+
/* @__PURE__ */ jsxs4("div", { className: "ya-composer-wrap", children: [
|
|
1298
|
+
similar.length > 0 && /* @__PURE__ */ jsxs4("div", { className: "ya-tray", "aria-live": "polite", children: [
|
|
1299
|
+
/* @__PURE__ */ jsx4("p", { className: "ya-tray-h", children: "Sounds like one of these. Vote instead?" }),
|
|
1300
|
+
/* @__PURE__ */ jsx4("ul", { className: "ya-list", children: similar.map((r) => /* @__PURE__ */ jsx4(RequestRow, { client, request: r, compact: true, onOpen: () => openRequest(r.id) }, r.id)) })
|
|
1166
1301
|
] }),
|
|
1167
|
-
shots.length > 0 && /* @__PURE__ */
|
|
1168
|
-
/* @__PURE__ */
|
|
1169
|
-
/* @__PURE__ */
|
|
1302
|
+
shots.length > 0 && /* @__PURE__ */ jsx4("ul", { className: "ya-shots", "aria-label": "Screenshots", children: shots.map((s, i) => /* @__PURE__ */ jsxs4("li", { className: "ya-shot", children: [
|
|
1303
|
+
/* @__PURE__ */ jsx4("img", { src: s.url, alt: "" }),
|
|
1304
|
+
/* @__PURE__ */ jsxs4("span", { children: [
|
|
1170
1305
|
"Screenshot",
|
|
1171
1306
|
shots.length > 1 ? ` ${i + 1}` : ""
|
|
1172
1307
|
] }),
|
|
1173
|
-
/* @__PURE__ */
|
|
1308
|
+
/* @__PURE__ */ jsx4(
|
|
1174
1309
|
"button",
|
|
1175
1310
|
{
|
|
1176
1311
|
type: "button",
|
|
1177
1312
|
className: "ya-shot-x",
|
|
1178
1313
|
"aria-label": `Remove screenshot${shots.length > 1 ? ` ${i + 1}` : ""}`,
|
|
1179
1314
|
onClick: () => setShots((all) => all.filter((x) => x !== s)),
|
|
1180
|
-
children: /* @__PURE__ */
|
|
1315
|
+
children: /* @__PURE__ */ jsx4(IconClose, {})
|
|
1181
1316
|
}
|
|
1182
1317
|
)
|
|
1183
1318
|
] }, s.url)) }),
|
|
1184
|
-
/* @__PURE__ */
|
|
1319
|
+
/* @__PURE__ */ jsxs4(
|
|
1185
1320
|
"form",
|
|
1186
1321
|
{
|
|
1187
1322
|
className: "ya-composer",
|
|
@@ -1190,7 +1325,7 @@ function Home({ route, setRoute, onClose }) {
|
|
|
1190
1325
|
send();
|
|
1191
1326
|
},
|
|
1192
1327
|
children: [
|
|
1193
|
-
/* @__PURE__ */
|
|
1328
|
+
/* @__PURE__ */ jsx4(
|
|
1194
1329
|
"textarea",
|
|
1195
1330
|
{
|
|
1196
1331
|
ref: inputRef,
|
|
@@ -1209,12 +1344,12 @@ function Home({ route, setRoute, onClose }) {
|
|
|
1209
1344
|
}
|
|
1210
1345
|
}
|
|
1211
1346
|
),
|
|
1212
|
-
/* @__PURE__ */
|
|
1347
|
+
/* @__PURE__ */ jsx4("button", { type: "submit", className: "ya-send", disabled: text.trim().length < 3 || sending, "aria-label": "Send", children: /* @__PURE__ */ jsx4(IconSend, {}) })
|
|
1213
1348
|
]
|
|
1214
1349
|
}
|
|
1215
1350
|
),
|
|
1216
|
-
/* @__PURE__ */
|
|
1217
|
-
/* @__PURE__ */
|
|
1351
|
+
/* @__PURE__ */ jsxs4("div", { className: "ya-tools", children: [
|
|
1352
|
+
/* @__PURE__ */ jsxs4(
|
|
1218
1353
|
"button",
|
|
1219
1354
|
{
|
|
1220
1355
|
type: "button",
|
|
@@ -1224,18 +1359,18 @@ function Home({ route, setRoute, onClose }) {
|
|
|
1224
1359
|
onFocus: () => void import("./capture-FYZGL54F.js"),
|
|
1225
1360
|
disabled: sending || shots.length >= MAX_SHOTS,
|
|
1226
1361
|
children: [
|
|
1227
|
-
/* @__PURE__ */
|
|
1362
|
+
/* @__PURE__ */ jsx4(IconFrame, {}),
|
|
1228
1363
|
"Show us"
|
|
1229
1364
|
]
|
|
1230
1365
|
}
|
|
1231
1366
|
),
|
|
1232
|
-
config?.canCredit && text.trim().length > 0 && /* @__PURE__ */
|
|
1367
|
+
config?.canCredit && text.trim().length > 0 && /* @__PURE__ */ jsxs4("button", { type: "button", className: "ya-switch-row", role: "switch", "aria-checked": credit, onClick: () => setCredit((c) => !c), children: [
|
|
1233
1368
|
"Credit me when it ships",
|
|
1234
|
-
/* @__PURE__ */
|
|
1369
|
+
/* @__PURE__ */ jsx4("span", { className: "ya-switch", "aria-hidden": "true" })
|
|
1235
1370
|
] })
|
|
1236
1371
|
] }),
|
|
1237
|
-
shotError && /* @__PURE__ */
|
|
1238
|
-
config?.watermark && /* @__PURE__ */
|
|
1372
|
+
shotError && /* @__PURE__ */ jsx4("p", { className: "ya-note", role: "alert", children: shotError }),
|
|
1373
|
+
config?.watermark && /* @__PURE__ */ jsx4("p", { className: "ya-mark", children: "Feedback by YouAsked" })
|
|
1239
1374
|
] })
|
|
1240
1375
|
] });
|
|
1241
1376
|
}
|
|
@@ -1245,12 +1380,12 @@ function Section({
|
|
|
1245
1380
|
onAction,
|
|
1246
1381
|
children
|
|
1247
1382
|
}) {
|
|
1248
|
-
return /* @__PURE__ */
|
|
1249
|
-
/* @__PURE__ */
|
|
1250
|
-
/* @__PURE__ */
|
|
1251
|
-
action && /* @__PURE__ */
|
|
1383
|
+
return /* @__PURE__ */ jsxs4("section", { className: "ya-section", children: [
|
|
1384
|
+
/* @__PURE__ */ jsxs4("div", { className: "ya-section-head", children: [
|
|
1385
|
+
/* @__PURE__ */ jsx4("h3", { children: title }),
|
|
1386
|
+
action && /* @__PURE__ */ jsxs4("button", { type: "button", className: "ya-link", onClick: onAction, children: [
|
|
1252
1387
|
action,
|
|
1253
|
-
/* @__PURE__ */
|
|
1388
|
+
/* @__PURE__ */ jsx4(IconChevron, {})
|
|
1254
1389
|
] })
|
|
1255
1390
|
] }),
|
|
1256
1391
|
children
|
|
@@ -1267,36 +1402,36 @@ function Board({
|
|
|
1267
1402
|
onClose
|
|
1268
1403
|
}) {
|
|
1269
1404
|
const { client, title } = useInternal();
|
|
1270
|
-
const [tab, setTab] =
|
|
1405
|
+
const [tab, setTab] = useState3(TABS.find((t) => t.key === initialTab) ?? TABS[0]);
|
|
1271
1406
|
const { data, error } = useLoad(() => client.list(tab.sort, tab.status), [client, tab.key]);
|
|
1272
|
-
return /* @__PURE__ */
|
|
1273
|
-
/* @__PURE__ */
|
|
1407
|
+
return /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1408
|
+
/* @__PURE__ */ jsx4(
|
|
1274
1409
|
Header,
|
|
1275
1410
|
{
|
|
1276
1411
|
title,
|
|
1277
1412
|
onBack: () => setRoute({ view: "home" }),
|
|
1278
1413
|
onClose,
|
|
1279
|
-
right: /* @__PURE__ */
|
|
1414
|
+
right: /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-link", onClick: () => setRoute({ view: "mine" }), children: "Yours" })
|
|
1280
1415
|
}
|
|
1281
1416
|
),
|
|
1282
|
-
/* @__PURE__ */
|
|
1283
|
-
/* @__PURE__ */
|
|
1284
|
-
/* @__PURE__ */
|
|
1417
|
+
/* @__PURE__ */ jsx4("div", { className: "ya-tabs", role: "tablist", "aria-label": "Sort requests", children: TABS.map((t) => /* @__PURE__ */ jsx4("button", { type: "button", role: "tab", className: "ya-tab", "aria-selected": tab.key === t.key, onClick: () => setTab(t), children: t.label }, t.key)) }),
|
|
1418
|
+
/* @__PURE__ */ jsx4("div", { className: "ya-body", children: error ? /* @__PURE__ */ jsx4("p", { className: "ya-empty", children: errorText(error) }) : !data ? /* @__PURE__ */ jsx4(Skeleton, {}) : data.length === 0 ? /* @__PURE__ */ jsxs4("div", { className: "ya-empty", children: [
|
|
1419
|
+
/* @__PURE__ */ jsx4("b", { children: tab.key === "shipped" ? "Nothing shipped yet" : "No requests yet" }),
|
|
1285
1420
|
tab.key === "shipped" ? "Requests show up here once they're live." : "Tell us what you'd like next. Others can vote on it."
|
|
1286
|
-
] }) : /* @__PURE__ */
|
|
1287
|
-
/* @__PURE__ */
|
|
1288
|
-
/* @__PURE__ */
|
|
1421
|
+
] }) : /* @__PURE__ */ jsx4("ul", { className: "ya-list", children: data.map((r) => /* @__PURE__ */ jsx4(RequestRow, { client, request: r, onOpen: () => setRoute({ view: "detail", id: r.id, from: "board" }) }, r.id)) }) }),
|
|
1422
|
+
/* @__PURE__ */ jsx4("div", { className: "ya-foot", children: /* @__PURE__ */ jsxs4("button", { type: "button", className: "ya-btn", onClick: () => setRoute({ view: "home", focus: true }), children: [
|
|
1423
|
+
/* @__PURE__ */ jsx4(IconPlus, {}),
|
|
1289
1424
|
"Request a feature"
|
|
1290
1425
|
] }) })
|
|
1291
1426
|
] });
|
|
1292
1427
|
}
|
|
1293
|
-
function Detail({ id, setRoute, onClose }) {
|
|
1428
|
+
function Detail({ id, from, setRoute, onClose }) {
|
|
1294
1429
|
const { client, config, markSeen, appName, appIcon } = useInternal();
|
|
1295
1430
|
const { data, error, setData } = useLoad(() => client.get(id), [client, id]);
|
|
1296
1431
|
const unseenHere = config?.unseen.filter((n) => n.requestId === id).map((n) => n.id).join(",") ?? "";
|
|
1297
|
-
const loaded =
|
|
1432
|
+
const loaded = useRef3(false);
|
|
1298
1433
|
loaded.current = Boolean(data);
|
|
1299
|
-
|
|
1434
|
+
useEffect3(() => {
|
|
1300
1435
|
if (!unseenHere) return;
|
|
1301
1436
|
markSeen(unseenHere.split(","));
|
|
1302
1437
|
if (loaded.current) {
|
|
@@ -1304,12 +1439,27 @@ function Detail({ id, setRoute, onClose }) {
|
|
|
1304
1439
|
});
|
|
1305
1440
|
}
|
|
1306
1441
|
}, [unseenHere, markSeen]);
|
|
1307
|
-
const [reply, setReply] =
|
|
1308
|
-
const [sending, setSending] =
|
|
1309
|
-
const [replyError, setReplyError] =
|
|
1310
|
-
const [voted, setVoted] =
|
|
1311
|
-
const [early, setEarly] =
|
|
1312
|
-
|
|
1442
|
+
const [reply, setReply] = useState3("");
|
|
1443
|
+
const [sending, setSending] = useState3(false);
|
|
1444
|
+
const [replyError, setReplyError] = useState3(null);
|
|
1445
|
+
const [voted, setVoted] = useState3(null);
|
|
1446
|
+
const [early, setEarly] = useState3(false);
|
|
1447
|
+
useEffect3(() => setEarly(Boolean(data?.earlyAccess)), [data?.earlyAccess]);
|
|
1448
|
+
const [confirmDelete, setConfirmDelete] = useState3(false);
|
|
1449
|
+
const [deleting, setDeleting] = useState3(false);
|
|
1450
|
+
const [deleteError, setDeleteError] = useState3(null);
|
|
1451
|
+
const remove = async () => {
|
|
1452
|
+
if (!data?.request || deleting) return;
|
|
1453
|
+
setDeleting(true);
|
|
1454
|
+
setDeleteError(null);
|
|
1455
|
+
try {
|
|
1456
|
+
await client.remove(data.request.id);
|
|
1457
|
+
setRoute({ view: "mine" });
|
|
1458
|
+
} catch (err) {
|
|
1459
|
+
setDeleteError(errorText(err instanceof YouAskedError ? err.code : "network", err instanceof YouAskedError ? err.detail : void 0));
|
|
1460
|
+
setDeleting(false);
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1313
1463
|
const toggleEarly = async () => {
|
|
1314
1464
|
if (!data?.request) return;
|
|
1315
1465
|
const before = early;
|
|
@@ -1320,7 +1470,7 @@ function Detail({ id, setRoute, onClose }) {
|
|
|
1320
1470
|
setEarly(before);
|
|
1321
1471
|
}
|
|
1322
1472
|
};
|
|
1323
|
-
|
|
1473
|
+
useEffect3(() => {
|
|
1324
1474
|
if (data?.redirect) setRoute({ view: "detail", id: data.redirect });
|
|
1325
1475
|
}, [data?.redirect, setRoute]);
|
|
1326
1476
|
const request = data?.request;
|
|
@@ -1335,51 +1485,63 @@ function Detail({ id, setRoute, onClose }) {
|
|
|
1335
1485
|
setData({ ...data, comments: [...comments, c], request: { ...request, comments: request.comments + 1 } });
|
|
1336
1486
|
setReply("");
|
|
1337
1487
|
} catch (err) {
|
|
1338
|
-
setReplyError(errorText(err instanceof YouAskedError ? err.code : "network"));
|
|
1488
|
+
setReplyError(errorText(err instanceof YouAskedError ? err.code : "network", err instanceof YouAskedError ? err.detail : void 0));
|
|
1339
1489
|
} finally {
|
|
1340
1490
|
setSending(false);
|
|
1341
1491
|
}
|
|
1342
1492
|
};
|
|
1343
|
-
return /* @__PURE__ */
|
|
1344
|
-
/* @__PURE__ */
|
|
1345
|
-
/* @__PURE__ */
|
|
1346
|
-
/* @__PURE__ */
|
|
1347
|
-
request.status !== "open" && /* @__PURE__ */
|
|
1348
|
-
/* @__PURE__ */
|
|
1493
|
+
return /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1494
|
+
/* @__PURE__ */ jsx4(Header, { title: "", onBack: () => setRoute({ view: from ?? "board" }), onClose }),
|
|
1495
|
+
/* @__PURE__ */ jsx4("div", { className: "ya-body", children: error ? /* @__PURE__ */ jsx4("p", { className: "ya-empty", children: error === "not_found" ? "This request was removed." : errorText(error) }) : !request ? /* @__PURE__ */ jsx4(Skeleton, {}) : /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1496
|
+
/* @__PURE__ */ jsxs4("div", { className: "ya-detail-meta", children: [
|
|
1497
|
+
request.status !== "open" && /* @__PURE__ */ jsx4(StatusPill, { status: request.status }),
|
|
1498
|
+
/* @__PURE__ */ jsxs4("span", { className: "ya-ref", children: [
|
|
1349
1499
|
"YA-",
|
|
1350
1500
|
request.number
|
|
1351
1501
|
] })
|
|
1352
1502
|
] }),
|
|
1353
|
-
/* @__PURE__ */
|
|
1354
|
-
request.body && request.body !== request.title && /* @__PURE__ */
|
|
1355
|
-
data?.attachments && data.attachments.length > 0 && /* @__PURE__ */
|
|
1356
|
-
/* @__PURE__ */
|
|
1357
|
-
(request.status === "planned" || request.status === "in_progress") && (voted ?? request.voted) && /* @__PURE__ */
|
|
1358
|
-
/* @__PURE__ */
|
|
1359
|
-
/* @__PURE__ */
|
|
1360
|
-
/* @__PURE__ */
|
|
1361
|
-
/* @__PURE__ */
|
|
1503
|
+
/* @__PURE__ */ jsx4("h3", { className: "ya-detail-title", children: request.title }),
|
|
1504
|
+
request.body && request.body !== request.title && /* @__PURE__ */ jsx4("p", { className: "ya-text", children: request.body }),
|
|
1505
|
+
data?.attachments && data.attachments.length > 0 && /* @__PURE__ */ jsx4("div", { className: "ya-attachments", children: data.attachments.map((a) => /* @__PURE__ */ jsx4("a", { href: a.url, target: "_blank", rel: "noopener noreferrer", children: /* @__PURE__ */ jsx4("img", { src: a.url, alt: "Screenshot attached to this request", loading: "lazy" }) }, a.id)) }),
|
|
1506
|
+
/* @__PURE__ */ jsx4(BigVote, { client, request, onChange: setVoted }),
|
|
1507
|
+
(request.status === "planned" || request.status === "in_progress") && (voted ?? request.voted) && /* @__PURE__ */ jsxs4("button", { type: "button", className: "ya-early", role: "switch", "aria-checked": early, onClick: toggleEarly, children: [
|
|
1508
|
+
/* @__PURE__ */ jsx4("span", { className: "ya-card-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx4(IconSpark, {}) }),
|
|
1509
|
+
/* @__PURE__ */ jsxs4("span", { className: "ya-menurow-text", children: [
|
|
1510
|
+
/* @__PURE__ */ jsx4("span", { className: "ya-menurow-label", children: "Try it early" }),
|
|
1511
|
+
/* @__PURE__ */ jsx4("span", { className: "ya-menurow-sub", children: early ? "You're on the list. We'll let you in first." : "Get access before everyone else" })
|
|
1362
1512
|
] }),
|
|
1363
|
-
/* @__PURE__ */
|
|
1513
|
+
/* @__PURE__ */ jsx4("span", { className: "ya-switch", "aria-hidden": "true" })
|
|
1364
1514
|
] }),
|
|
1365
|
-
request.
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
/* @__PURE__ */
|
|
1369
|
-
|
|
1515
|
+
request.source && /* @__PURE__ */ jsxs4("p", { className: "ya-note", children: [
|
|
1516
|
+
"Also asked",
|
|
1517
|
+
" ",
|
|
1518
|
+
request.sourceUrl ? /* @__PURE__ */ jsx4("a", { href: request.sourceUrl, target: "_blank", rel: "noopener noreferrer", children: request.source === "reddit" ? "on Reddit" : "in App Store reviews" }) : request.source === "reddit" ? "on Reddit" : "in App Store reviews",
|
|
1519
|
+
"."
|
|
1520
|
+
] }),
|
|
1521
|
+
request.mine && request.status !== "shipped" && /* @__PURE__ */ jsx4("p", { className: "ya-note", children: "You asked for this. We'll tell you here when it ships." }),
|
|
1522
|
+
data?.canDelete && (confirmDelete ? /* @__PURE__ */ jsxs4("p", { className: "ya-note ya-delete-row", children: [
|
|
1523
|
+
"Delete this request? This can't be undone.",
|
|
1524
|
+
/* @__PURE__ */ jsx4("button", { type: "button", className: "ya-link ya-link-danger", onClick: remove, disabled: deleting, children: deleting ? "Deleting\u2026" : "Delete" }),
|
|
1525
|
+
/* @__PURE__ */ jsx4("button", { type: "button", className: "ya-link", onClick: () => setConfirmDelete(false), disabled: deleting, children: "Keep it" })
|
|
1526
|
+
] }) : /* @__PURE__ */ jsx4("p", { className: "ya-note ya-delete-row", children: /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-link ya-link-quiet", onClick: () => setConfirmDelete(true), children: "Delete my request" }) })),
|
|
1527
|
+
deleteError && /* @__PURE__ */ jsx4("p", { className: "ya-note", role: "alert", children: deleteError }),
|
|
1528
|
+
request.status === "shipped" && /* @__PURE__ */ jsxs4("div", { className: "ya-callout", "data-tone": "success", children: [
|
|
1529
|
+
/* @__PURE__ */ jsx4(IconCheck, {}),
|
|
1530
|
+
/* @__PURE__ */ jsxs4("span", { children: [
|
|
1531
|
+
/* @__PURE__ */ jsx4("b", { children: "It's live." }),
|
|
1370
1532
|
request.shipNote ? ` ${request.shipNote}` : " Thanks for asking.",
|
|
1371
|
-
request.creditName && /* @__PURE__ */
|
|
1533
|
+
request.creditName && /* @__PURE__ */ jsxs4("span", { className: "ya-credit", children: [
|
|
1372
1534
|
"Thanks to ",
|
|
1373
1535
|
request.creditName,
|
|
1374
1536
|
" for asking."
|
|
1375
1537
|
] })
|
|
1376
1538
|
] })
|
|
1377
1539
|
] }),
|
|
1378
|
-
/* @__PURE__ */
|
|
1379
|
-
comments.length === 0 ? /* @__PURE__ */
|
|
1540
|
+
/* @__PURE__ */ jsx4("p", { className: "ya-section-h", children: "Conversation" }),
|
|
1541
|
+
comments.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "ya-text ya-text-muted", children: "No messages yet. Add details or ask a question below." }) : /* @__PURE__ */ jsx4("div", { className: "ya-thread", children: comments.map((c) => /* @__PURE__ */ jsx4(Message, { comment: c, appName, appIcon }, c.id)) })
|
|
1380
1542
|
] }) }),
|
|
1381
|
-
request && /* @__PURE__ */
|
|
1382
|
-
/* @__PURE__ */
|
|
1543
|
+
request && /* @__PURE__ */ jsxs4("form", { className: "ya-composer ya-composer-bar", onSubmit: send, children: [
|
|
1544
|
+
/* @__PURE__ */ jsx4(
|
|
1383
1545
|
"textarea",
|
|
1384
1546
|
{
|
|
1385
1547
|
className: "ya-composer-input",
|
|
@@ -1397,50 +1559,51 @@ function Detail({ id, setRoute, onClose }) {
|
|
|
1397
1559
|
}
|
|
1398
1560
|
}
|
|
1399
1561
|
),
|
|
1400
|
-
/* @__PURE__ */
|
|
1401
|
-
replyError && /* @__PURE__ */
|
|
1562
|
+
/* @__PURE__ */ jsx4("button", { type: "submit", className: "ya-send", disabled: !reply.trim() || sending, "aria-label": "Send comment", children: /* @__PURE__ */ jsx4(IconSend, {}) }),
|
|
1563
|
+
replyError && /* @__PURE__ */ jsx4("span", { className: "ya-sr", role: "alert", children: replyError })
|
|
1402
1564
|
] })
|
|
1403
1565
|
] });
|
|
1404
1566
|
}
|
|
1405
1567
|
function Message({ comment, appName, appIcon }) {
|
|
1406
|
-
const when = /* @__PURE__ */
|
|
1568
|
+
const when = /* @__PURE__ */ jsx4("time", { dateTime: new Date(comment.createdAt).toISOString(), children: ago(comment.createdAt) });
|
|
1407
1569
|
if (comment.mine) {
|
|
1408
|
-
return /* @__PURE__ */
|
|
1409
|
-
/* @__PURE__ */
|
|
1410
|
-
/* @__PURE__ */
|
|
1570
|
+
return /* @__PURE__ */ jsxs4("div", { className: "ya-msg ya-msg-out", children: [
|
|
1571
|
+
/* @__PURE__ */ jsx4("div", { className: "ya-bubble ya-bubble-out", children: comment.body }),
|
|
1572
|
+
/* @__PURE__ */ jsx4("span", { className: "ya-msg-meta", children: when })
|
|
1411
1573
|
] });
|
|
1412
1574
|
}
|
|
1413
|
-
return /* @__PURE__ */
|
|
1414
|
-
comment.team && appIcon ? /* @__PURE__ */
|
|
1415
|
-
/* @__PURE__ */
|
|
1416
|
-
/* @__PURE__ */
|
|
1417
|
-
/* @__PURE__ */
|
|
1418
|
-
comment.team && /* @__PURE__ */
|
|
1575
|
+
return /* @__PURE__ */ jsxs4("div", { className: "ya-msg", children: [
|
|
1576
|
+
comment.team && appIcon ? /* @__PURE__ */ jsx4(AppAvatar, { icon: appIcon, size: 26 }) : null,
|
|
1577
|
+
/* @__PURE__ */ jsxs4("div", { className: "ya-msg-body", children: [
|
|
1578
|
+
/* @__PURE__ */ jsxs4("span", { className: "ya-msg-meta", children: [
|
|
1579
|
+
/* @__PURE__ */ jsx4("b", { children: comment.team ? appName : comment.author || "Someone" }),
|
|
1580
|
+
comment.team && /* @__PURE__ */ jsx4("span", { className: "ya-team", children: "Team" }),
|
|
1419
1581
|
when
|
|
1420
1582
|
] }),
|
|
1421
|
-
/* @__PURE__ */
|
|
1583
|
+
/* @__PURE__ */ jsx4("div", { className: "ya-bubble ya-bubble-in", children: comment.body })
|
|
1422
1584
|
] })
|
|
1423
1585
|
] });
|
|
1424
1586
|
}
|
|
1425
1587
|
function Mine({ setRoute, onClose }) {
|
|
1426
1588
|
const { client } = useInternal();
|
|
1427
1589
|
const { data, error } = useLoad(() => client.mine(), [client]);
|
|
1428
|
-
return /* @__PURE__ */
|
|
1429
|
-
/* @__PURE__ */
|
|
1430
|
-
/* @__PURE__ */
|
|
1431
|
-
/* @__PURE__ */
|
|
1590
|
+
return /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1591
|
+
/* @__PURE__ */ jsx4(Header, { title: "Your requests", onBack: () => setRoute({ view: "board" }), onClose }),
|
|
1592
|
+
/* @__PURE__ */ jsx4("div", { className: "ya-body", children: error ? /* @__PURE__ */ jsx4("p", { className: "ya-empty", children: errorText(error) }) : !data ? /* @__PURE__ */ jsx4(Skeleton, {}) : data.length === 0 ? /* @__PURE__ */ jsxs4("div", { className: "ya-empty", children: [
|
|
1593
|
+
/* @__PURE__ */ jsx4("b", { children: "Nothing yet" }),
|
|
1432
1594
|
"Requests you post or vote on show up here, with their status."
|
|
1433
|
-
] }) : /* @__PURE__ */
|
|
1434
|
-
/* @__PURE__ */
|
|
1435
|
-
/* @__PURE__ */
|
|
1595
|
+
] }) : /* @__PURE__ */ jsx4("ul", { className: "ya-list", children: data.map((r) => /* @__PURE__ */ jsx4(RequestRow, { client, request: r, onOpen: () => setRoute({ view: "detail", id: r.id, from: "mine" }) }, r.id)) }) }),
|
|
1596
|
+
/* @__PURE__ */ jsx4("div", { className: "ya-foot", children: /* @__PURE__ */ jsxs4("button", { type: "button", className: "ya-btn", onClick: () => setRoute({ view: "home", focus: true }), children: [
|
|
1597
|
+
/* @__PURE__ */ jsx4(IconPlus, {}),
|
|
1436
1598
|
"Request a feature"
|
|
1437
1599
|
] }) })
|
|
1438
1600
|
] });
|
|
1439
1601
|
}
|
|
1440
1602
|
|
|
1441
1603
|
// src/YouAsked.tsx
|
|
1442
|
-
import { Fragment as Fragment2, jsx as
|
|
1443
|
-
var
|
|
1604
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1605
|
+
var warnedTestMode = false;
|
|
1606
|
+
var DEFAULT_API_URL = "https://api.youasked.dev";
|
|
1444
1607
|
function lastWhatsNew(key) {
|
|
1445
1608
|
try {
|
|
1446
1609
|
return key ? Number(localStorage.getItem(key)) || 0 : 0;
|
|
@@ -1455,6 +1618,7 @@ function YouAsked({
|
|
|
1455
1618
|
appKey = "",
|
|
1456
1619
|
demo = false,
|
|
1457
1620
|
cards = true,
|
|
1621
|
+
nudge: nudgeProp,
|
|
1458
1622
|
user,
|
|
1459
1623
|
signature,
|
|
1460
1624
|
ready = true,
|
|
@@ -1478,25 +1642,36 @@ function YouAsked({
|
|
|
1478
1642
|
[demo, appName, appKey, apiUrl, userId, userEmail, userName, signature]
|
|
1479
1643
|
);
|
|
1480
1644
|
const whatsNewKey = demo ? null : `youasked:whats-new:${appKey}:${userId ?? "anon"}`;
|
|
1481
|
-
const [mounted, setMounted] =
|
|
1482
|
-
const [route, setRoute] =
|
|
1483
|
-
const [config, setConfig] =
|
|
1484
|
-
const [
|
|
1485
|
-
const [
|
|
1486
|
-
const [
|
|
1645
|
+
const [mounted, setMounted] = useState4(false);
|
|
1646
|
+
const [route, setRoute] = useState4(null);
|
|
1647
|
+
const [config, setConfig] = useState4(null);
|
|
1648
|
+
const [testMode, setTestMode] = useState4(false);
|
|
1649
|
+
const [whatsNew, setWhatsNew] = useState4(null);
|
|
1650
|
+
const [whatsNewToast, setWhatsNewToast] = useState4(false);
|
|
1651
|
+
const [capture, setCapture] = useState4(null);
|
|
1487
1652
|
const tokens = useTokens(appearance, mounted);
|
|
1488
|
-
const openerRef =
|
|
1489
|
-
const [detectedIcon, setDetectedIcon] =
|
|
1490
|
-
|
|
1653
|
+
const openerRef = useRef4(null);
|
|
1654
|
+
const [detectedIcon, setDetectedIcon] = useState4(null);
|
|
1655
|
+
useEffect4(() => {
|
|
1491
1656
|
if (appIcon === void 0) setDetectedIcon(findPageIcon());
|
|
1492
1657
|
}, [appIcon]);
|
|
1493
|
-
|
|
1658
|
+
useEffect4(() => {
|
|
1494
1659
|
injectStyles();
|
|
1495
1660
|
setMounted(true);
|
|
1496
1661
|
}, []);
|
|
1497
|
-
|
|
1662
|
+
const [refresh, setRefresh] = useState4(0);
|
|
1663
|
+
const lastLoad = useRef4(0);
|
|
1664
|
+
useEffect4(() => {
|
|
1665
|
+
const onVisible = () => {
|
|
1666
|
+
if (document.visibilityState === "visible" && Date.now() - lastLoad.current > 6e4) setRefresh((n) => n + 1);
|
|
1667
|
+
};
|
|
1668
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
1669
|
+
return () => document.removeEventListener("visibilitychange", onVisible);
|
|
1670
|
+
}, []);
|
|
1671
|
+
useEffect4(() => {
|
|
1498
1672
|
if (!ready) return;
|
|
1499
1673
|
let alive = true;
|
|
1674
|
+
lastLoad.current = Date.now();
|
|
1500
1675
|
client.config().then((c) => {
|
|
1501
1676
|
if (!alive) return;
|
|
1502
1677
|
const seenAt = lastWhatsNew(whatsNewKey);
|
|
@@ -1505,19 +1680,25 @@ function YouAsked({
|
|
|
1505
1680
|
setConfig(c);
|
|
1506
1681
|
setWhatsNew(news);
|
|
1507
1682
|
setWhatsNewToast(Boolean(news) && c.unseen.filter((n) => !coveredBy(news, n)).length === 0);
|
|
1508
|
-
}).catch(() => {
|
|
1683
|
+
}).catch((err) => {
|
|
1684
|
+
if (!alive || !(err instanceof YouAskedError) || err.code !== "test_mode") return;
|
|
1685
|
+
setTestMode(true);
|
|
1686
|
+
if (!warnedTestMode) {
|
|
1687
|
+
warnedTestMode = true;
|
|
1688
|
+
console.warn(`[youasked] ${err.detail ?? "This app is in test mode. It works on localhost until it's on a paid plan."}`);
|
|
1689
|
+
}
|
|
1509
1690
|
});
|
|
1510
1691
|
return () => {
|
|
1511
1692
|
alive = false;
|
|
1512
1693
|
};
|
|
1513
|
-
}, [client, ready]);
|
|
1514
|
-
|
|
1694
|
+
}, [client, ready, refresh]);
|
|
1695
|
+
useEffect4(() => {
|
|
1515
1696
|
if (!("onNotice" in client)) return;
|
|
1516
1697
|
return client.onNotice(
|
|
1517
1698
|
(n) => setConfig((c) => c ? { ...c, unseen: [...c.unseen, n] } : c)
|
|
1518
1699
|
);
|
|
1519
1700
|
}, [client]);
|
|
1520
|
-
const dismissWhatsNew =
|
|
1701
|
+
const dismissWhatsNew = useCallback3(() => {
|
|
1521
1702
|
setWhatsNew(null);
|
|
1522
1703
|
setWhatsNewToast(false);
|
|
1523
1704
|
try {
|
|
@@ -1527,7 +1708,7 @@ function YouAsked({
|
|
|
1527
1708
|
client.whatsNewSeen().catch(() => {
|
|
1528
1709
|
});
|
|
1529
1710
|
}, [client, whatsNewKey]);
|
|
1530
|
-
const pointAtIt =
|
|
1711
|
+
const pointAtIt = useCallback3(
|
|
1531
1712
|
() => new Promise((resolve, reject) => {
|
|
1532
1713
|
import("./capture-FYZGL54F.js").then(
|
|
1533
1714
|
(m) => setCapture({ Layer: m.default, resolve }),
|
|
@@ -1536,20 +1717,20 @@ function YouAsked({
|
|
|
1536
1717
|
}),
|
|
1537
1718
|
[]
|
|
1538
1719
|
);
|
|
1539
|
-
const open =
|
|
1720
|
+
const open = useCallback3((options) => {
|
|
1540
1721
|
openerRef.current = document.activeElement;
|
|
1541
1722
|
setRoute(toRoute(options));
|
|
1542
1723
|
}, []);
|
|
1543
|
-
const close =
|
|
1724
|
+
const close = useCallback3(() => {
|
|
1544
1725
|
setRoute(null);
|
|
1545
1726
|
openerRef.current?.focus?.({ preventScroll: true });
|
|
1546
1727
|
}, []);
|
|
1547
|
-
|
|
1728
|
+
useEffect4(() => {
|
|
1548
1729
|
const onOpen = (e) => open(e.detail);
|
|
1549
1730
|
window.addEventListener("youasked:open", onOpen);
|
|
1550
1731
|
return () => window.removeEventListener("youasked:open", onOpen);
|
|
1551
1732
|
}, [open]);
|
|
1552
|
-
const markSeen =
|
|
1733
|
+
const markSeen = useCallback3(
|
|
1553
1734
|
(ids) => {
|
|
1554
1735
|
if (!ids.length) return;
|
|
1555
1736
|
setConfig((c) => c ? { ...c, unseen: c.unseen.filter((n) => !ids.includes(n.id)) } : c);
|
|
@@ -1588,11 +1769,22 @@ function YouAsked({
|
|
|
1588
1769
|
);
|
|
1589
1770
|
const standing = unseen.filter((n) => !coveredBy(whatsNew, n));
|
|
1590
1771
|
const notice = standing.find((n) => n.kind === "shipped") ?? standing.find((n) => n.kind === "reply");
|
|
1591
|
-
|
|
1772
|
+
const nudgeSettings = useMemo(
|
|
1773
|
+
() => config && cards && !testMode ? resolveNudge(config.nudge, nudgeProp) : null,
|
|
1774
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1775
|
+
[Boolean(config), JSON.stringify(config?.nudge ?? null), cards, testMode, typeof nudgeProp === "object" ? JSON.stringify(nudgeProp) : nudgeProp]
|
|
1776
|
+
);
|
|
1777
|
+
const busy = route !== null || Boolean(notice) || whatsNewToast && Boolean(whatsNew);
|
|
1778
|
+
const nudge = useNudge(nudgeSettings, demo ? null : `youasked:nudge:${appKey}:${userId ?? "anon"}`, busy);
|
|
1779
|
+
useEffect4(() => {
|
|
1780
|
+
if (route !== null) nudge.engaged();
|
|
1781
|
+
}, [route, nudge.engaged]);
|
|
1782
|
+
if (testMode) return /* @__PURE__ */ jsx5(YouAskedContext.Provider, { value, children });
|
|
1783
|
+
return /* @__PURE__ */ jsxs5(YouAskedContext.Provider, { value, children: [
|
|
1592
1784
|
children,
|
|
1593
1785
|
mounted && createPortal(
|
|
1594
|
-
/* @__PURE__ */
|
|
1595
|
-
trigger === "floating" && !capture && /* @__PURE__ */
|
|
1786
|
+
/* @__PURE__ */ jsxs5("div", { className: "ya-scope", "data-ya-mode": tokens?.mode, "data-ya-skip": "", style: scopeStyle, children: [
|
|
1787
|
+
trigger === "floating" && !capture && /* @__PURE__ */ jsx5(
|
|
1596
1788
|
Launcher,
|
|
1597
1789
|
{
|
|
1598
1790
|
position,
|
|
@@ -1602,7 +1794,7 @@ function YouAsked({
|
|
|
1602
1794
|
onClick: () => route ? close() : open()
|
|
1603
1795
|
}
|
|
1604
1796
|
),
|
|
1605
|
-
cards && !notice && whatsNewToast && whatsNew && route === null && /* @__PURE__ */
|
|
1797
|
+
cards && !notice && whatsNewToast && whatsNew && route === null && /* @__PURE__ */ jsx5(
|
|
1606
1798
|
WhatsNewToast,
|
|
1607
1799
|
{
|
|
1608
1800
|
news: whatsNew,
|
|
@@ -1612,7 +1804,18 @@ function YouAsked({
|
|
|
1612
1804
|
onDismiss: dismissWhatsNew
|
|
1613
1805
|
}
|
|
1614
1806
|
),
|
|
1615
|
-
|
|
1807
|
+
nudgeSettings && nudge.show && !busy && /* @__PURE__ */ jsx5(
|
|
1808
|
+
NudgeToast,
|
|
1809
|
+
{
|
|
1810
|
+
settings: nudgeSettings,
|
|
1811
|
+
appName: appName ?? config?.app.name ?? "us",
|
|
1812
|
+
position,
|
|
1813
|
+
trigger,
|
|
1814
|
+
onOpen: () => open({ view: "new" }),
|
|
1815
|
+
onDismiss: nudge.dismiss
|
|
1816
|
+
}
|
|
1817
|
+
),
|
|
1818
|
+
cards && notice && route === null && /* @__PURE__ */ jsx5(
|
|
1616
1819
|
ShippedToast,
|
|
1617
1820
|
{
|
|
1618
1821
|
notice,
|
|
@@ -1625,7 +1828,7 @@ function YouAsked({
|
|
|
1625
1828
|
onDismiss: () => markSeen([notice.id])
|
|
1626
1829
|
}
|
|
1627
1830
|
),
|
|
1628
|
-
route && /* @__PURE__ */
|
|
1831
|
+
route && /* @__PURE__ */ jsx5(
|
|
1629
1832
|
Panel,
|
|
1630
1833
|
{
|
|
1631
1834
|
route,
|
|
@@ -1636,7 +1839,7 @@ function YouAsked({
|
|
|
1636
1839
|
hidden: capture !== null
|
|
1637
1840
|
}
|
|
1638
1841
|
),
|
|
1639
|
-
capture && /* @__PURE__ */
|
|
1842
|
+
capture && /* @__PURE__ */ jsx5(
|
|
1640
1843
|
capture.Layer,
|
|
1641
1844
|
{
|
|
1642
1845
|
onDone: (image) => {
|
|
@@ -1663,8 +1866,8 @@ function findPageIcon() {
|
|
|
1663
1866
|
}
|
|
1664
1867
|
function useTokens(appearance, mounted) {
|
|
1665
1868
|
const key = JSON.stringify(appearance ?? {});
|
|
1666
|
-
const [tokens, setTokens] =
|
|
1667
|
-
|
|
1869
|
+
const [tokens, setTokens] = useState4(null);
|
|
1870
|
+
useEffect4(() => {
|
|
1668
1871
|
if (!mounted) return;
|
|
1669
1872
|
const parsed = JSON.parse(key);
|
|
1670
1873
|
const update = () => setTokens(resolveTokens(parsed));
|
|
@@ -1681,15 +1884,15 @@ function useTokens(appearance, mounted) {
|
|
|
1681
1884
|
return tokens;
|
|
1682
1885
|
}
|
|
1683
1886
|
function useMedia(query) {
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1887
|
+
return useSyncExternalStore(
|
|
1888
|
+
(onChange) => {
|
|
1889
|
+
const m = window.matchMedia(query);
|
|
1890
|
+
m.addEventListener("change", onChange);
|
|
1891
|
+
return () => m.removeEventListener("change", onChange);
|
|
1892
|
+
},
|
|
1893
|
+
() => window.matchMedia(query).matches,
|
|
1894
|
+
() => false
|
|
1895
|
+
);
|
|
1693
1896
|
}
|
|
1694
1897
|
function Panel({
|
|
1695
1898
|
route,
|
|
@@ -1699,20 +1902,20 @@ function Panel({
|
|
|
1699
1902
|
hasLauncher,
|
|
1700
1903
|
hidden
|
|
1701
1904
|
}) {
|
|
1702
|
-
const panelRef =
|
|
1703
|
-
const backdropRef =
|
|
1704
|
-
const [closing, setClosing] =
|
|
1905
|
+
const panelRef = useRef4(null);
|
|
1906
|
+
const backdropRef = useRef4(null);
|
|
1907
|
+
const [closing, setClosing] = useState4(false);
|
|
1705
1908
|
const isSheet = useMedia("(max-width: 639px)");
|
|
1706
1909
|
const reduceMotion = useMedia("(prefers-reduced-motion: reduce)");
|
|
1707
|
-
const hiddenRef =
|
|
1910
|
+
const hiddenRef = useRef4(hidden);
|
|
1708
1911
|
hiddenRef.current = hidden;
|
|
1709
|
-
const drag =
|
|
1710
|
-
const requestClose =
|
|
1912
|
+
const drag = useRef4(null);
|
|
1913
|
+
const requestClose = useCallback3(() => {
|
|
1711
1914
|
if (reduceMotion) return onClose();
|
|
1712
1915
|
setClosing(true);
|
|
1713
1916
|
setTimeout(onClose, 200);
|
|
1714
1917
|
}, [onClose, reduceMotion]);
|
|
1715
|
-
|
|
1918
|
+
useEffect4(() => {
|
|
1716
1919
|
if (!isSheet) return;
|
|
1717
1920
|
const html = document.documentElement;
|
|
1718
1921
|
const previous = html.style.overflow;
|
|
@@ -1721,10 +1924,10 @@ function Panel({
|
|
|
1721
1924
|
html.style.overflow = previous;
|
|
1722
1925
|
};
|
|
1723
1926
|
}, [isSheet]);
|
|
1724
|
-
|
|
1927
|
+
useEffect4(() => {
|
|
1725
1928
|
panelRef.current?.focus({ preventScroll: true });
|
|
1726
1929
|
}, []);
|
|
1727
|
-
|
|
1930
|
+
useEffect4(() => {
|
|
1728
1931
|
const onKey = (e) => {
|
|
1729
1932
|
if (hiddenRef.current) return;
|
|
1730
1933
|
if (e.key === "Escape") {
|
|
@@ -1778,7 +1981,7 @@ function Panel({
|
|
|
1778
1981
|
if (backdrop) backdrop.style.opacity = "";
|
|
1779
1982
|
}
|
|
1780
1983
|
};
|
|
1781
|
-
return /* @__PURE__ */
|
|
1984
|
+
return /* @__PURE__ */ jsxs5(
|
|
1782
1985
|
"div",
|
|
1783
1986
|
{
|
|
1784
1987
|
className: "ya-overlay",
|
|
@@ -1787,8 +1990,8 @@ function Panel({
|
|
|
1787
1990
|
"data-closing": closing || void 0,
|
|
1788
1991
|
hidden,
|
|
1789
1992
|
children: [
|
|
1790
|
-
/* @__PURE__ */
|
|
1791
|
-
/* @__PURE__ */
|
|
1993
|
+
/* @__PURE__ */ jsx5("div", { className: "ya-backdrop", ref: backdropRef, onClick: requestClose, "aria-hidden": "true" }),
|
|
1994
|
+
/* @__PURE__ */ jsxs5(
|
|
1792
1995
|
"div",
|
|
1793
1996
|
{
|
|
1794
1997
|
className: "ya-panel",
|
|
@@ -1802,8 +2005,8 @@ function Panel({
|
|
|
1802
2005
|
onPointerUp,
|
|
1803
2006
|
onPointerCancel: onPointerUp,
|
|
1804
2007
|
children: [
|
|
1805
|
-
/* @__PURE__ */
|
|
1806
|
-
/* @__PURE__ */
|
|
2008
|
+
/* @__PURE__ */ jsx5("div", { className: "ya-grab-zone", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("div", { className: "ya-grab" }) }),
|
|
2009
|
+
/* @__PURE__ */ jsx5(Navigator, { route, setRoute, onClose: requestClose })
|
|
1807
2010
|
]
|
|
1808
2011
|
}
|
|
1809
2012
|
)
|
|
@@ -1833,7 +2036,7 @@ function Launcher({
|
|
|
1833
2036
|
badge,
|
|
1834
2037
|
onClick
|
|
1835
2038
|
}) {
|
|
1836
|
-
return /* @__PURE__ */
|
|
2039
|
+
return /* @__PURE__ */ jsxs5(
|
|
1837
2040
|
"button",
|
|
1838
2041
|
{
|
|
1839
2042
|
type: "button",
|
|
@@ -1844,9 +2047,9 @@ function Launcher({
|
|
|
1844
2047
|
"aria-label": isOpen ? "Close feature requests" : label || "Feature requests",
|
|
1845
2048
|
"aria-expanded": isOpen,
|
|
1846
2049
|
children: [
|
|
1847
|
-
/* @__PURE__ */
|
|
1848
|
-
label && !isOpen && /* @__PURE__ */
|
|
1849
|
-
badge && !isOpen && /* @__PURE__ */
|
|
2050
|
+
/* @__PURE__ */ jsx5("span", { className: "ya-fab-icon", children: isOpen ? /* @__PURE__ */ jsx5(IconClose, {}) : /* @__PURE__ */ jsx5(IconIdea, { ink: "var(--_accent)" }) }),
|
|
2051
|
+
label && !isOpen && /* @__PURE__ */ jsx5("span", { children: label }),
|
|
2052
|
+
badge && !isOpen && /* @__PURE__ */ jsx5("span", { className: "ya-dot", "aria-hidden": "true" })
|
|
1850
2053
|
]
|
|
1851
2054
|
}
|
|
1852
2055
|
);
|
|
@@ -1858,18 +2061,18 @@ function ShippedToast({
|
|
|
1858
2061
|
onOpen,
|
|
1859
2062
|
onDismiss
|
|
1860
2063
|
}) {
|
|
1861
|
-
return /* @__PURE__ */
|
|
1862
|
-
/* @__PURE__ */
|
|
1863
|
-
/* @__PURE__ */
|
|
1864
|
-
/* @__PURE__ */
|
|
2064
|
+
return /* @__PURE__ */ jsx5("div", { className: "ya-toast", role: "status", "data-pos": position, "data-trigger": trigger, children: notice.kind === "reply" ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2065
|
+
/* @__PURE__ */ jsxs5("div", { className: "ya-toast-top", children: [
|
|
2066
|
+
/* @__PURE__ */ jsx5("p", { className: "ya-toast-kicker", children: "New reply about" }),
|
|
2067
|
+
/* @__PURE__ */ jsx5("button", { type: "button", className: "ya-icon-btn", onClick: onDismiss, "aria-label": "Dismiss", children: /* @__PURE__ */ jsx5(IconClose, {}) })
|
|
1865
2068
|
] }),
|
|
1866
|
-
/* @__PURE__ */
|
|
1867
|
-
/* @__PURE__ */
|
|
1868
|
-
/* @__PURE__ */
|
|
1869
|
-
/* @__PURE__ */
|
|
1870
|
-
/* @__PURE__ */
|
|
2069
|
+
/* @__PURE__ */ jsx5("p", { className: "ya-toast-title", children: notice.title }),
|
|
2070
|
+
/* @__PURE__ */ jsx5("p", { className: "ya-toast-note ya-toast-quote", children: notice.reply }),
|
|
2071
|
+
/* @__PURE__ */ jsxs5("div", { className: "ya-toast-actions", children: [
|
|
2072
|
+
/* @__PURE__ */ jsx5("button", { type: "button", className: "ya-btn ya-btn-sm", onClick: onOpen, children: "Reply" }),
|
|
2073
|
+
/* @__PURE__ */ jsx5("button", { type: "button", className: "ya-btn ya-btn-sm ya-btn-ghost", onClick: onDismiss, children: "Later" })
|
|
1871
2074
|
] })
|
|
1872
|
-
] }) : /* @__PURE__ */
|
|
2075
|
+
] }) : /* @__PURE__ */ jsx5(ShippedBody, { notice, onOpen, onDismiss }) });
|
|
1873
2076
|
}
|
|
1874
2077
|
function WhatsNewToast({
|
|
1875
2078
|
news,
|
|
@@ -1879,44 +2082,44 @@ function WhatsNewToast({
|
|
|
1879
2082
|
onDismiss
|
|
1880
2083
|
}) {
|
|
1881
2084
|
const mine = news.items.some((i) => i.mine);
|
|
1882
|
-
return /* @__PURE__ */
|
|
1883
|
-
/* @__PURE__ */
|
|
1884
|
-
/* @__PURE__ */
|
|
1885
|
-
/* @__PURE__ */
|
|
2085
|
+
return /* @__PURE__ */ jsxs5("div", { className: "ya-toast", role: "status", "data-pos": position, "data-trigger": trigger, children: [
|
|
2086
|
+
/* @__PURE__ */ jsxs5("div", { className: "ya-toast-top", children: [
|
|
2087
|
+
/* @__PURE__ */ jsx5("p", { className: "ya-toast-kicker", children: "What's new" }),
|
|
2088
|
+
/* @__PURE__ */ jsx5("button", { type: "button", className: "ya-icon-btn", onClick: onDismiss, "aria-label": "Dismiss", children: /* @__PURE__ */ jsx5(IconClose, {}) })
|
|
1886
2089
|
] }),
|
|
1887
|
-
/* @__PURE__ */
|
|
1888
|
-
mine && /* @__PURE__ */
|
|
1889
|
-
/* @__PURE__ */
|
|
2090
|
+
/* @__PURE__ */ jsx5("p", { className: "ya-toast-title", children: whatsNewHeading(news.total) }),
|
|
2091
|
+
mine && /* @__PURE__ */ jsxs5("div", { className: "ya-live", children: [
|
|
2092
|
+
/* @__PURE__ */ jsx5(IconCheck, {}),
|
|
1890
2093
|
"Including something you asked for"
|
|
1891
2094
|
] }),
|
|
1892
|
-
/* @__PURE__ */
|
|
1893
|
-
/* @__PURE__ */
|
|
1894
|
-
/* @__PURE__ */
|
|
2095
|
+
/* @__PURE__ */ jsxs5("div", { className: "ya-toast-actions", children: [
|
|
2096
|
+
/* @__PURE__ */ jsx5("button", { type: "button", className: "ya-btn ya-btn-sm", onClick: onOpen, children: "See what's new" }),
|
|
2097
|
+
/* @__PURE__ */ jsx5("button", { type: "button", className: "ya-btn ya-btn-sm ya-btn-ghost", onClick: onDismiss, children: "Not now" })
|
|
1895
2098
|
] })
|
|
1896
2099
|
] });
|
|
1897
2100
|
}
|
|
1898
2101
|
function ShippedBody({ notice, onOpen, onDismiss }) {
|
|
1899
|
-
return /* @__PURE__ */
|
|
1900
|
-
/* @__PURE__ */
|
|
1901
|
-
/* @__PURE__ */
|
|
1902
|
-
/* @__PURE__ */
|
|
2102
|
+
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2103
|
+
/* @__PURE__ */ jsxs5("div", { className: "ya-toast-top", children: [
|
|
2104
|
+
/* @__PURE__ */ jsx5("p", { className: "ya-toast-kicker", children: "You asked for" }),
|
|
2105
|
+
/* @__PURE__ */ jsx5("button", { type: "button", className: "ya-icon-btn", onClick: onDismiss, "aria-label": "Dismiss", children: /* @__PURE__ */ jsx5(IconClose, {}) })
|
|
1903
2106
|
] }),
|
|
1904
|
-
/* @__PURE__ */
|
|
1905
|
-
/* @__PURE__ */
|
|
1906
|
-
/* @__PURE__ */
|
|
2107
|
+
/* @__PURE__ */ jsx5("p", { className: "ya-toast-title", children: notice.title }),
|
|
2108
|
+
/* @__PURE__ */ jsxs5("div", { className: "ya-live", children: [
|
|
2109
|
+
/* @__PURE__ */ jsx5(IconCheck, {}),
|
|
1907
2110
|
"It's live"
|
|
1908
2111
|
] }),
|
|
1909
|
-
notice.shipNote && /* @__PURE__ */
|
|
1910
|
-
/* @__PURE__ */
|
|
1911
|
-
/* @__PURE__ */
|
|
1912
|
-
/* @__PURE__ */
|
|
2112
|
+
notice.shipNote && /* @__PURE__ */ jsx5("p", { className: "ya-toast-note", children: notice.shipNote }),
|
|
2113
|
+
/* @__PURE__ */ jsxs5("div", { className: "ya-toast-actions", children: [
|
|
2114
|
+
/* @__PURE__ */ jsx5("button", { type: "button", className: "ya-btn ya-btn-sm", onClick: onOpen, children: "See it" }),
|
|
2115
|
+
/* @__PURE__ */ jsx5("button", { type: "button", className: "ya-btn ya-btn-sm ya-btn-ghost", onClick: onDismiss, children: "Nice" })
|
|
1913
2116
|
] })
|
|
1914
2117
|
] });
|
|
1915
2118
|
}
|
|
1916
2119
|
|
|
1917
2120
|
// src/entries.tsx
|
|
1918
|
-
import { useState as
|
|
1919
|
-
import { jsx as
|
|
2121
|
+
import { useState as useState5 } from "react";
|
|
2122
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1920
2123
|
function useScope(className, style) {
|
|
1921
2124
|
const { scope } = useInternal();
|
|
1922
2125
|
return {
|
|
@@ -1927,11 +2130,11 @@ function useScope(className, style) {
|
|
|
1927
2130
|
}
|
|
1928
2131
|
function AskBar({ placeholder = "Ask for a feature\u2026", className, style }) {
|
|
1929
2132
|
const { open, appIcon, unseenCount } = useInternal();
|
|
1930
|
-
return /* @__PURE__ */
|
|
1931
|
-
appIcon ? /* @__PURE__ */
|
|
1932
|
-
/* @__PURE__ */
|
|
1933
|
-
unseenCount > 0 && /* @__PURE__ */
|
|
1934
|
-
/* @__PURE__ */
|
|
2133
|
+
return /* @__PURE__ */ jsx6("div", { ...useScope(className, style), children: /* @__PURE__ */ jsxs6("button", { type: "button", className: "ya-askbar", onClick: () => open({ view: "new" }), children: [
|
|
2134
|
+
appIcon ? /* @__PURE__ */ jsx6(AppAvatar, { icon: appIcon, size: 30 }) : /* @__PURE__ */ jsx6("span", { className: "ya-askbar-glyph", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(IconIdea, {}) }),
|
|
2135
|
+
/* @__PURE__ */ jsx6("span", { className: "ya-askbar-text", children: placeholder }),
|
|
2136
|
+
unseenCount > 0 && /* @__PURE__ */ jsx6("span", { className: "ya-badge", children: unseenCount }),
|
|
2137
|
+
/* @__PURE__ */ jsx6("span", { className: "ya-askbar-send", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(IconSend, {}) })
|
|
1935
2138
|
] }) });
|
|
1936
2139
|
}
|
|
1937
2140
|
function FeatureCard({
|
|
@@ -1944,15 +2147,15 @@ function FeatureCard({
|
|
|
1944
2147
|
const { client, open, config, markSeen } = useInternal();
|
|
1945
2148
|
const { data } = useLoad(() => client.list("top", "active"), [client]);
|
|
1946
2149
|
const notice = config?.unseen.find((n) => n.kind === "shipped");
|
|
1947
|
-
return /* @__PURE__ */
|
|
1948
|
-
/* @__PURE__ */
|
|
1949
|
-
/* @__PURE__ */
|
|
1950
|
-
/* @__PURE__ */
|
|
1951
|
-
/* @__PURE__ */
|
|
1952
|
-
/* @__PURE__ */
|
|
2150
|
+
return /* @__PURE__ */ jsx6("div", { ...useScope(className, style), children: /* @__PURE__ */ jsxs6("section", { className: "ya-card", children: [
|
|
2151
|
+
/* @__PURE__ */ jsxs6("div", { className: "ya-card-head", children: [
|
|
2152
|
+
/* @__PURE__ */ jsx6("span", { className: "ya-card-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(IconIdea, {}) }),
|
|
2153
|
+
/* @__PURE__ */ jsxs6("div", { children: [
|
|
2154
|
+
/* @__PURE__ */ jsx6("h3", { className: "ya-card-title", children: heading }),
|
|
2155
|
+
/* @__PURE__ */ jsx6("p", { className: "ya-card-sub", children: description })
|
|
1953
2156
|
] })
|
|
1954
2157
|
] }),
|
|
1955
|
-
notice && /* @__PURE__ */
|
|
2158
|
+
notice && /* @__PURE__ */ jsxs6(
|
|
1956
2159
|
"button",
|
|
1957
2160
|
{
|
|
1958
2161
|
type: "button",
|
|
@@ -1962,20 +2165,20 @@ function FeatureCard({
|
|
|
1962
2165
|
open({ requestId: notice.requestId });
|
|
1963
2166
|
},
|
|
1964
2167
|
children: [
|
|
1965
|
-
/* @__PURE__ */
|
|
1966
|
-
/* @__PURE__ */
|
|
2168
|
+
/* @__PURE__ */ jsx6("span", { className: "ya-shipped-dot", children: /* @__PURE__ */ jsx6(IconCheck, {}) }),
|
|
2169
|
+
/* @__PURE__ */ jsxs6("span", { children: [
|
|
1967
2170
|
"You asked for ",
|
|
1968
|
-
/* @__PURE__ */
|
|
2171
|
+
/* @__PURE__ */ jsx6("b", { children: notice.title }),
|
|
1969
2172
|
". It's live."
|
|
1970
2173
|
] }),
|
|
1971
|
-
/* @__PURE__ */
|
|
2174
|
+
/* @__PURE__ */ jsx6(IconChevron, {})
|
|
1972
2175
|
]
|
|
1973
2176
|
}
|
|
1974
2177
|
),
|
|
1975
|
-
!data ? /* @__PURE__ */
|
|
1976
|
-
/* @__PURE__ */
|
|
1977
|
-
/* @__PURE__ */
|
|
1978
|
-
data && data.length > limit && /* @__PURE__ */
|
|
2178
|
+
!data ? /* @__PURE__ */ jsx6(Skeleton, { rows: 2 }) : data.length > 0 ? /* @__PURE__ */ jsx6("ul", { className: "ya-list", children: data.slice(0, limit).map((r) => /* @__PURE__ */ jsx6(RequestRow, { client, request: r, compact: true, onOpen: () => open({ requestId: r.id }) }, r.id)) }) : null,
|
|
2179
|
+
/* @__PURE__ */ jsxs6("div", { className: "ya-card-actions", children: [
|
|
2180
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "ya-btn ya-btn-sm", onClick: () => open({ view: "new" }), children: "Suggest an idea" }),
|
|
2181
|
+
data && data.length > limit && /* @__PURE__ */ jsxs6("button", { type: "button", className: "ya-btn ya-btn-sm ya-btn-ghost", onClick: () => open({ view: "board" }), children: [
|
|
1979
2182
|
"See all ",
|
|
1980
2183
|
data.length
|
|
1981
2184
|
] })
|
|
@@ -1991,26 +2194,26 @@ function RequestButton({
|
|
|
1991
2194
|
}) {
|
|
1992
2195
|
const { open, unseenCount } = useInternal();
|
|
1993
2196
|
const scope = useScope(className, style);
|
|
1994
|
-
const badge = unseenCount > 0 && (variant === "icon" ? /* @__PURE__ */
|
|
2197
|
+
const badge = unseenCount > 0 && (variant === "icon" ? /* @__PURE__ */ jsx6("span", { className: "ya-dot", "aria-hidden": "true" }) : /* @__PURE__ */ jsx6("span", { className: "ya-badge", children: unseenCount }));
|
|
1995
2198
|
if (variant === "icon") {
|
|
1996
|
-
return /* @__PURE__ */
|
|
1997
|
-
/* @__PURE__ */
|
|
2199
|
+
return /* @__PURE__ */ jsx6("span", { ...scope, children: /* @__PURE__ */ jsxs6("button", { type: "button", className: "ya-iconbtn", onClick: () => open(), "aria-label": label, children: [
|
|
2200
|
+
/* @__PURE__ */ jsx6(IconIdea, {}),
|
|
1998
2201
|
badge
|
|
1999
2202
|
] }) });
|
|
2000
2203
|
}
|
|
2001
2204
|
if (variant === "row") {
|
|
2002
|
-
return /* @__PURE__ */
|
|
2003
|
-
/* @__PURE__ */
|
|
2004
|
-
/* @__PURE__ */
|
|
2005
|
-
/* @__PURE__ */
|
|
2006
|
-
sublabel && /* @__PURE__ */
|
|
2205
|
+
return /* @__PURE__ */ jsx6("div", { ...scope, children: /* @__PURE__ */ jsxs6("button", { type: "button", className: "ya-menurow", onClick: () => open(), children: [
|
|
2206
|
+
/* @__PURE__ */ jsx6("span", { className: "ya-card-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(IconIdea, {}) }),
|
|
2207
|
+
/* @__PURE__ */ jsxs6("span", { className: "ya-menurow-text", children: [
|
|
2208
|
+
/* @__PURE__ */ jsx6("span", { className: "ya-menurow-label", children: label }),
|
|
2209
|
+
sublabel && /* @__PURE__ */ jsx6("span", { className: "ya-menurow-sub", children: sublabel })
|
|
2007
2210
|
] }),
|
|
2008
2211
|
badge,
|
|
2009
|
-
/* @__PURE__ */
|
|
2212
|
+
/* @__PURE__ */ jsx6(IconChevron, {})
|
|
2010
2213
|
] }) });
|
|
2011
2214
|
}
|
|
2012
|
-
return /* @__PURE__ */
|
|
2013
|
-
/* @__PURE__ */
|
|
2215
|
+
return /* @__PURE__ */ jsx6("span", { ...scope, children: /* @__PURE__ */ jsxs6("button", { type: "button", className: "ya-pillbtn", onClick: () => open(), children: [
|
|
2216
|
+
/* @__PURE__ */ jsx6(IconIdea, {}),
|
|
2014
2217
|
label,
|
|
2015
2218
|
badge
|
|
2016
2219
|
] }) });
|
|
@@ -2022,8 +2225,8 @@ function AskAbout({
|
|
|
2022
2225
|
style
|
|
2023
2226
|
}) {
|
|
2024
2227
|
const { open } = useInternal();
|
|
2025
|
-
return /* @__PURE__ */
|
|
2026
|
-
/* @__PURE__ */
|
|
2228
|
+
return /* @__PURE__ */ jsx6("span", { ...useScope(className, style), children: /* @__PURE__ */ jsxs6("button", { type: "button", className: "ya-askabout", onClick: () => open({ view: "new", topic }), children: [
|
|
2229
|
+
/* @__PURE__ */ jsx6(IconIdea, {}),
|
|
2027
2230
|
prompt ?? `Want more from ${topic}?`
|
|
2028
2231
|
] }) });
|
|
2029
2232
|
}
|
|
@@ -2031,11 +2234,11 @@ function AskForIt({ query, className, style }) {
|
|
|
2031
2234
|
const { open } = useInternal();
|
|
2032
2235
|
const q = query.trim();
|
|
2033
2236
|
const shown = q.length > 40 ? `${q.slice(0, 39).trimEnd()}\u2026` : q;
|
|
2034
|
-
return /* @__PURE__ */
|
|
2035
|
-
/* @__PURE__ */
|
|
2036
|
-
/* @__PURE__ */
|
|
2237
|
+
return /* @__PURE__ */ jsx6("span", { ...useScope(className, style), children: /* @__PURE__ */ jsxs6("button", { type: "button", className: "ya-askforit", onClick: () => open({ view: "new", draft: q, topic: "search" }), children: [
|
|
2238
|
+
/* @__PURE__ */ jsx6("span", { children: shown ? `Can't find \u201C${shown}\u201D?` : "Can't find it?" }),
|
|
2239
|
+
/* @__PURE__ */ jsxs6("b", { children: [
|
|
2037
2240
|
"Ask for it",
|
|
2038
|
-
/* @__PURE__ */
|
|
2241
|
+
/* @__PURE__ */ jsx6(IconChevron, {})
|
|
2039
2242
|
] })
|
|
2040
2243
|
] }) });
|
|
2041
2244
|
}
|
|
@@ -2045,8 +2248,8 @@ function FeatureBoard({
|
|
|
2045
2248
|
className,
|
|
2046
2249
|
style
|
|
2047
2250
|
}) {
|
|
2048
|
-
const [route, setRoute] =
|
|
2049
|
-
return /* @__PURE__ */
|
|
2251
|
+
const [route, setRoute] = useState5(start === "board" ? { view: "board" } : { view: "home" });
|
|
2252
|
+
return /* @__PURE__ */ jsx6("div", { ...useScope(className, style), children: /* @__PURE__ */ jsx6("div", { className: "ya-embed", style: { height }, children: /* @__PURE__ */ jsx6(Navigator, { route, setRoute }) }) });
|
|
2050
2253
|
}
|
|
2051
2254
|
export {
|
|
2052
2255
|
AskAbout,
|