@blogmcp/community 0.1.0 → 0.3.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.
Files changed (114) hide show
  1. package/README.md +162 -13
  2. package/dist/blogmcp-community.global.js +8 -8
  3. package/dist/chunks/mount--2_V2q3M.js +10063 -0
  4. package/dist/core/attrs.d.ts +42 -1
  5. package/dist/core/badge.d.ts +30 -0
  6. package/dist/core/centre.d.ts +28 -0
  7. package/dist/core/digest-token.d.ts +2 -0
  8. package/dist/core/discussion.d.ts +120 -15
  9. package/dist/core/follow.d.ts +26 -0
  10. package/dist/core/format.d.ts +7 -0
  11. package/dist/core/forum-category.d.ts +85 -0
  12. package/dist/core/forum-search.d.ts +35 -0
  13. package/dist/core/forum-state.d.ts +30 -0
  14. package/dist/core/forum-words.d.ts +8 -0
  15. package/dist/core/forum.d.ts +21 -0
  16. package/dist/core/moderation.d.ts +80 -0
  17. package/dist/core/onboarding.d.ts +49 -2
  18. package/dist/core/outcomes.d.ts +2 -1
  19. package/dist/core/panel.d.ts +17 -1
  20. package/dist/core/profile.d.ts +45 -0
  21. package/dist/core/push.d.ts +63 -0
  22. package/dist/core/realtime.d.ts +46 -0
  23. package/dist/core/settings.d.ts +53 -0
  24. package/dist/core/text.d.ts +13 -0
  25. package/dist/core/topic.d.ts +47 -0
  26. package/dist/element.d.ts +47 -1
  27. package/dist/global.d.ts +8 -4
  28. package/dist/index.d.ts +20 -3
  29. package/dist/index.js +62 -12
  30. package/dist/mount.d.ts +89 -0
  31. package/dist/react.d.ts +27 -3
  32. package/dist/react.js +48 -4
  33. package/dist/ui/BadgeView.vue.d.ts +13 -0
  34. package/dist/ui/DiscussionView.vue.d.ts +11 -0
  35. package/dist/ui/FollowButtonView.vue.d.ts +17 -0
  36. package/dist/ui/FollowToggle.vue.d.ts +6 -0
  37. package/dist/ui/ForumCategoryView.vue.d.ts +13 -0
  38. package/dist/ui/ForumModerationView.vue.d.ts +12 -0
  39. package/dist/ui/ForumNotices.vue.d.ts +7 -0
  40. package/dist/ui/ForumSearchView.vue.d.ts +11 -0
  41. package/dist/ui/ForumView.vue.d.ts +12 -0
  42. package/dist/ui/LinkedText.vue.d.ts +1 -0
  43. package/dist/ui/MemberSettingsView.vue.d.ts +13 -0
  44. package/dist/ui/MembersOnly.vue.d.ts +15 -0
  45. package/dist/ui/MessageComposer.vue.d.ts +8 -2
  46. package/dist/ui/MessageMenu.vue.d.ts +2 -0
  47. package/dist/ui/ModeratorDialog.vue.d.ts +11 -0
  48. package/dist/ui/NotificationCentreView.vue.d.ts +12 -0
  49. package/dist/ui/OnboardingSteps.vue.d.ts +1 -1
  50. package/dist/ui/ProfileView.vue.d.ts +13 -0
  51. package/dist/ui/TopicRow.vue.d.ts +10 -0
  52. package/dist/ui/TopicView.vue.d.ts +32 -0
  53. package/dist/ui/WelcomeView.vue.d.ts +12 -0
  54. package/dist/ui/context.d.ts +7 -1
  55. package/dist/ui/discussion-context.d.ts +11 -3
  56. package/dist/vue.d.ts +273 -3
  57. package/dist/vue.js +235 -5
  58. package/package.json +3 -3
  59. package/src/core/attrs.ts +61 -2
  60. package/src/core/badge.ts +120 -0
  61. package/src/core/centre.ts +136 -0
  62. package/src/core/digest-token.ts +26 -0
  63. package/src/core/discussion.ts +497 -46
  64. package/src/core/follow.ts +139 -0
  65. package/src/core/format.ts +22 -0
  66. package/src/core/forum-category.ts +323 -0
  67. package/src/core/forum-search.ts +153 -0
  68. package/src/core/forum-state.ts +59 -0
  69. package/src/core/forum-words.ts +38 -0
  70. package/src/core/forum.ts +83 -0
  71. package/src/core/moderation.ts +414 -0
  72. package/src/core/onboarding.ts +177 -18
  73. package/src/core/outcomes.ts +23 -1
  74. package/src/core/panel.ts +39 -4
  75. package/src/core/profile.ts +155 -0
  76. package/src/core/push.ts +174 -0
  77. package/src/core/realtime.ts +52 -0
  78. package/src/core/settings.ts +363 -0
  79. package/src/core/text.ts +32 -0
  80. package/src/core/topic.ts +234 -0
  81. package/src/element.ts +124 -6
  82. package/src/global.ts +40 -4
  83. package/src/index.ts +84 -2
  84. package/src/mount.ts +250 -1
  85. package/src/react.ts +90 -3
  86. package/src/ui/BadgeView.vue +51 -0
  87. package/src/ui/DiscussionView.vue +43 -3
  88. package/src/ui/FollowButtonView.vue +85 -0
  89. package/src/ui/FollowToggle.vue +39 -0
  90. package/src/ui/ForumCategoryView.vue +243 -0
  91. package/src/ui/ForumModerationView.vue +309 -0
  92. package/src/ui/ForumNotices.vue +23 -0
  93. package/src/ui/ForumSearchView.vue +171 -0
  94. package/src/ui/ForumView.vue +128 -0
  95. package/src/ui/LinkedText.vue +11 -5
  96. package/src/ui/MemberSettingsView.vue +175 -0
  97. package/src/ui/MembersOnly.vue +59 -0
  98. package/src/ui/MessageComposer.vue +64 -11
  99. package/src/ui/MessageItem.vue +36 -7
  100. package/src/ui/MessageMenu.vue +3 -2
  101. package/src/ui/ModeratorDialog.vue +102 -0
  102. package/src/ui/MyCommunityView.vue +12 -0
  103. package/src/ui/NoticeStatusView.vue +15 -2
  104. package/src/ui/NotificationCentreView.vue +193 -0
  105. package/src/ui/OnboardingSteps.vue +17 -6
  106. package/src/ui/ProfileView.vue +102 -0
  107. package/src/ui/TopicRow.vue +70 -0
  108. package/src/ui/TopicView.vue +395 -0
  109. package/src/ui/WelcomeView.vue +72 -0
  110. package/src/ui/context.ts +8 -3
  111. package/src/ui/discussion-context.ts +11 -3
  112. package/src/ui/styles.css +30 -0
  113. package/src/vue.ts +271 -3
  114. package/dist/chunks/mount-DzjwnHyx.js +0 -4597
@@ -1,4597 +0,0 @@
1
- import { reactive, shallowRef, provide, inject, onMounted, onBeforeUnmount, nextTick, defineComponent, ref, createElementBlock, openBlock, unref, createElementVNode, createCommentVNode, toDisplayString, normalizeClass, computed, watch, withModifiers, withDirectives, vModelText, Fragment, renderList, normalizeStyle, createTextVNode, withKeys, resolveComponent, createVNode, createBlock, vModelRadio, vModelCheckbox, vModelSelect, useHost, renderSlot, defineCustomElement } from "vue";
2
- import { COMMUNITY_ROUTES, isRelativeCommunityBase, createCommunityClient, COMMUNITY_DISPLAY_NAME_MIN, COMMUNITY_DISPLAY_NAME_MAX, COMMUNITY_HANDLE_RE, isKnownNeed, communityRulesOf, hasCommunityMessage, communityMessage, createDiscussionStore, viewerNeeds, isOwnMessage, findMessage, COMMUNITY_REPORT_NOTE_MAX, reactionsFor, reactionAfter, newClientMsgId, COMMUNITY_MESSAGE_MAX, loadMoreDiscussion, refreshDiscussion, pollDiscussionHead, COMMUNITY_MESSAGE_LINKS_MAX, confirmMessageKey, noteMessageKey, warningMessageKey, AGE_BANDS, COMMUNITY_HANDLE_CHANGE_DAYS, CASE_CATEGORIES, statementPath, signInLink, COMMUNITY_APPEAL_MAX, isKnownNotificationCode, receiptFromHash, readReceiptFromHash, COMMUNITY_GOOD_FAITH, noticeStatusLink, COMMUNITY_NOTICE_DESCRIPTION_MAX, COMMUNITY_NOTIFIER_NAME_MAX, CATEGORY_WITHOUT_NOTIFIER, COMMUNITY_NOTICE_TURNSTILE_ACTION } from "@blogmcp/sdk/community";
3
- import { sanitizeUrl } from "@blogmcp/sanitize/url";
4
- const ROUTE_KEYS = ["statementPath", "myCommunityPath", "rulesPath", "reportPath", "noticesPath", "consultationPath"];
5
- function isSitePath(path) {
6
- return typeof path === "string" && /^\/(?![/\\])/.test(path) && !/[\s\\\u0000-\u001f\u007f]/.test(path);
7
- }
8
- const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
9
- function jsonOf(value) {
10
- if (typeof value !== "string") return value;
11
- if (value.trim() === "") return null;
12
- try {
13
- return JSON.parse(value);
14
- } catch {
15
- return null;
16
- }
17
- }
18
- function parseRoutes(value) {
19
- const raw = jsonOf(value);
20
- const out = {};
21
- if (!isRecord(raw)) return out;
22
- for (const key of ROUTE_KEYS) if (isSitePath(raw[key])) out[key] = raw[key];
23
- return out;
24
- }
25
- function siteBaseOf(value) {
26
- if (value === void 0 || value === null || value === "") return "";
27
- if (typeof value !== "string") return null;
28
- const base = value.replace(/\/+$/, "");
29
- if (base === "") return value.startsWith("/") ? "" : null;
30
- return /^(?:\/(?!\.{1,2}(?:\/|$))[A-Za-z0-9._~%-]+)+$/.test(base) ? base : null;
31
- }
32
- function routesWithDefaults(routes, tenant, siteBase = "") {
33
- const site = (path) => isSitePath(path) ? path : void 0;
34
- return {
35
- statementPath: routes.statementPath ?? site(tenant?.statementPath) ?? `${siteBase}${COMMUNITY_ROUTES.statement}`,
36
- myCommunityPath: routes.myCommunityPath ?? site(tenant?.myCommunityPath) ?? `${siteBase}${COMMUNITY_ROUTES.myCommunity}`,
37
- rulesPath: routes.rulesPath ?? `${siteBase}${COMMUNITY_ROUTES.rules}`,
38
- reportPath: routes.reportPath ?? `${siteBase}${COMMUNITY_ROUTES.report}`,
39
- noticesPath: routes.noticesPath ?? `${siteBase}${COMMUNITY_ROUTES.notices}`,
40
- consultationPath: routes.consultationPath ?? null
41
- };
42
- }
43
- const ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
44
- const HANDLE_RE = /^[a-z0-9][a-z0-9_]{2,29}$/;
45
- const isId = (v) => typeof v === "string" && ID_RE.test(v);
46
- const isSeq = (v) => typeof v === "number" && Number.isSafeInteger(v) && v > 0;
47
- function parseTarget(value) {
48
- const raw = jsonOf(value);
49
- if (!isRecord(raw)) return null;
50
- if ("conversationId" in raw) return isId(raw.conversationId) && isSeq(raw.seq) ? { conversationId: raw.conversationId.toLowerCase(), seq: raw.seq } : null;
51
- if ("memberId" in raw) return isId(raw.memberId) ? { memberId: raw.memberId.toLowerCase() } : null;
52
- if ("handle" in raw) return typeof raw.handle === "string" && HANDLE_RE.test(raw.handle) ? { handle: raw.handle } : null;
53
- if ("articleId" in raw) return isId(raw.articleId) ? { articleId: raw.articleId.toLowerCase() } : null;
54
- return null;
55
- }
56
- function targetKind(target) {
57
- if ("conversationId" in target) return "message";
58
- if ("articleId" in target) return "article";
59
- return "profile";
60
- }
61
- function reportLink(reportPath, target) {
62
- if (!isSitePath(reportPath) || reportPath.includes("#") || !isId(target.conversationId) || !isSeq(target.seq)) return null;
63
- return `${reportPath}#c=${target.conversationId.toLowerCase()}&s=${target.seq}`;
64
- }
65
- function targetFromHash(hash) {
66
- if (typeof hash !== "string" || !hash.startsWith("#") || hash.startsWith("#t=")) return null;
67
- const params = /* @__PURE__ */ new Map();
68
- for (const part of hash.slice(1).split("&")) {
69
- const eq = part.indexOf("=");
70
- if (eq > 0) params.set(part.slice(0, eq), part.slice(eq + 1));
71
- }
72
- const c = params.get("c");
73
- const s = params.get("s");
74
- if (c !== void 0 && s !== void 0 && /^\d{1,15}$/.test(s)) return parseTarget({ conversationId: c, seq: Number(s) });
75
- const a = params.get("a");
76
- if (a !== void 0) return parseTarget({ articleId: a });
77
- return null;
78
- }
79
- const DEFAULT_API_BASE = "/api/community";
80
- const warned = /* @__PURE__ */ new Set();
81
- function warnOnce(key, message) {
82
- if (warned.has(key)) return;
83
- warned.add(key);
84
- console.warn(message);
85
- }
86
- function siteBaseFor(value) {
87
- const base = siteBaseOf(value);
88
- if (base === null) warnOnce("site-base", '[blogmcp-community] site-base must be a path on this site (e.g. "/blog"), never a URL: the site’s pages and its proxy are found under it');
89
- return base;
90
- }
91
- function communityClientFor(apiBase, transport, siteBase = "") {
92
- const base = apiBase === void 0 || apiBase === null || apiBase === "" ? `${siteBase}${DEFAULT_API_BASE}` : apiBase;
93
- if (!isRelativeCommunityBase(base)) {
94
- warnOnce("api-base", '[blogmcp-community] api-base must be a path on this site (e.g. "/api/community"), never a URL: the community API is reached through the site’s own proxy or BFF');
95
- return null;
96
- }
97
- try {
98
- return createCommunityClient(transport ? { base, transport } : { base });
99
- } catch {
100
- warnOnce("api-base", "[blogmcp-community] api-base was refused by the community client");
101
- return null;
102
- }
103
- }
104
- const UGC_LINK_REL = "nofollow ugc noopener noreferrer";
105
- const URL_RE = /\b(?:https?:\/\/|www\.)[^\s<>"'`{}|\\^]+/gi;
106
- const TRAILING = `.,;:!?…'"»”’]`;
107
- function trimUrl(raw) {
108
- let url = raw;
109
- while (url.length > 0) {
110
- const last = url[url.length - 1];
111
- if (last === ")") {
112
- const opens = url.split("(").length - 1;
113
- const closes = url.split(")").length - 1;
114
- if (closes <= opens) break;
115
- } else if (!TRAILING.includes(last)) {
116
- break;
117
- }
118
- url = url.slice(0, -1);
119
- }
120
- return url;
121
- }
122
- function safeHref(url) {
123
- const candidate = /^www\./i.test(url) ? `https://${url}` : url;
124
- if (/^https?:\/\/$/i.test(candidate)) return null;
125
- const safe = sanitizeUrl(candidate);
126
- return safe !== "about:blank" && /^https?:\/\//i.test(safe) ? safe : null;
127
- }
128
- function linkify(text) {
129
- const out = [];
130
- if (typeof text !== "string" || text === "") return out;
131
- let at = 0;
132
- for (const m of text.matchAll(URL_RE)) {
133
- const start = m.index ?? 0;
134
- if (start < at) continue;
135
- const url = trimUrl(m[0]);
136
- const href = url ? safeHref(url) : null;
137
- if (!href) continue;
138
- if (start > at) out.push({ kind: "text", text: text.slice(at, start) });
139
- out.push({ kind: "link", text: url, href });
140
- at = start + url.length;
141
- }
142
- if (at < text.length) out.push({ kind: "text", text: text.slice(at) });
143
- return out;
144
- }
145
- function codePointLength(text) {
146
- let n = 0;
147
- for (const _ of text) n++;
148
- return n;
149
- }
150
- function normalizeBody(text) {
151
- return typeof text === "string" ? text.normalize("NFC").trim() : "";
152
- }
153
- const ENTITIES = Object.freeze({
154
- amp: "&",
155
- lt: "<",
156
- gt: ">",
157
- quot: '"',
158
- apos: "'",
159
- nbsp: " ",
160
- ndash: "–",
161
- mdash: "—",
162
- hellip: "…",
163
- laquo: "«",
164
- raquo: "»",
165
- bdquo: "„",
166
- ldquo: "“",
167
- rdquo: "”",
168
- lsquo: "‘",
169
- rsquo: "’",
170
- sbquo: "‚",
171
- copy: "©",
172
- reg: "®",
173
- euro: "€",
174
- shy: ""
175
- });
176
- function decodeEntities(text) {
177
- return text.replace(/&(#\d{1,7}|#x[0-9a-f]{1,6}|[a-z]{2,8});/gi, (match, entity) => {
178
- if (entity[0] === "#") {
179
- const hex = entity[1] === "x" || entity[1] === "X";
180
- const cp = Number.parseInt(entity.slice(hex ? 2 : 1), hex ? 16 : 10);
181
- return cp > 0 && cp <= 1114111 && !(cp >= 55296 && cp <= 57343) ? String.fromCodePoint(cp) : match;
182
- }
183
- return ENTITIES[entity.toLowerCase()] ?? match;
184
- });
185
- }
186
- const BLOCK_END = /<\/(?:p|div|li|h[1-6]|section|article|blockquote|tr|ul|ol|dl|dt|dd|header|footer|table|pre|figure)\s*>/gi;
187
- const BLOCK_START = /<(?:li|tr|dt|dd|h[1-6]|blockquote|pre|figure)\b[^>]*>/gi;
188
- function htmlToParagraphs(html) {
189
- if (typeof html !== "string" || html === "") return [];
190
- const text = html.replace(/<!--[\s\S]*?-->/g, "").replace(/<(script|style|template|noscript|svg|math)\b[\s\S]*?<\/\1\s*>/gi, "").replace(/[\t\n\r\f ]+/g, " ").replace(/<br\b[^>]*>/gi, "\n").replace(BLOCK_END, "\n\n").replace(BLOCK_START, "\n\n").replace(/<[^>]*>/g, "");
191
- return decodeEntities(text).split(/\n{2,}/).map(
192
- (p) => p.replace(/ *\n */g, "\n").replace(/[  ]{2,}/g, " ").trim()
193
- ).filter((p) => p !== "");
194
- }
195
- function normalizeName(name) {
196
- return typeof name === "string" ? name.normalize("NFKC").replace(/\s+/gu, " ").trim() : "";
197
- }
198
- function isDisplayNameOk(name) {
199
- const n = codePointLength(normalizeName(name));
200
- return n >= COMMUNITY_DISPLAY_NAME_MIN && n <= COMMUNITY_DISPLAY_NAME_MAX;
201
- }
202
- const HANDLE_MIN = 3;
203
- const HANDLE_MAX = 30;
204
- function normalizeHandle(handle) {
205
- return typeof handle === "string" ? handle.trim().toLowerCase() : "";
206
- }
207
- function isHandleOk(handle) {
208
- const h = normalizeHandle(handle);
209
- return h === "" || COMMUNITY_HANDLE_RE.test(h);
210
- }
211
- const ASCII = Object.freeze({ ß: "ss", ł: "l", đ: "d", ð: "d", ø: "o", ħ: "h", ŧ: "t", ı: "i", æ: "ae", œ: "oe", þ: "th", ŀ: "l" });
212
- function suggestedHandle(displayName) {
213
- const folded = normalizeName(displayName).toLowerCase().normalize("NFD").replace(new RegExp("\\p{M}+", "gu"), "").normalize("NFC");
214
- const ascii = folded.replace(/[ßłđðøħŧıæœþŀ]/g, (ch) => ASCII[ch] ?? ch);
215
- const stem = ascii.split(/[^a-z0-9]+/).filter((p) => p !== "").join("_").slice(0, 24).replace(/_+$/, "");
216
- return COMMUNITY_HANDLE_RE.test(stem) ? stem : null;
217
- }
218
- const PROFILE_FIELDS = /* @__PURE__ */ new Set(["displayName", "handle", "avatarPreset"]);
219
- const TEXT_FIELDS = /* @__PURE__ */ new Set(["rulesTextId", "art9TextId"]);
220
- function stepsOf(me, needs) {
221
- const steps = [];
222
- if (!me.member?.ageBand) steps.push("age");
223
- if (me.acceptances.rules !== "accepted") steps.push("rules");
224
- if (needs.includes("art9_consent")) steps.push("art9");
225
- if (!me.profile) steps.push("profile");
226
- return steps;
227
- }
228
- function createOnboarding(o) {
229
- const call = o.signal ? { signal: o.signal } : {};
230
- const state = reactive({
231
- phase: "loading",
232
- steps: [],
233
- index: 0,
234
- me: null,
235
- rulesText: null,
236
- rules: [],
237
- art9Text: null,
238
- art9Paragraphs: [],
239
- reconsent: false,
240
- ageBand: null,
241
- rulesAccepted: false,
242
- art9Accepted: false,
243
- displayName: "",
244
- handle: "",
245
- avatarPreset: null,
246
- failure: null,
247
- fields: [],
248
- minAge: null
249
- });
250
- const fail = (failure) => {
251
- state.failure = failure;
252
- state.phase = "failed";
253
- };
254
- async function textsFor(steps) {
255
- const [rules, art9] = await Promise.all([
256
- steps.includes("rules") ? o.client.legal.current({ kind: "rules", locale: o.lang }, call) : null,
257
- steps.includes("art9") ? o.client.legal.current({ kind: "art9_consent", locale: o.lang }, call) : null
258
- ]);
259
- for (const r of [rules, art9]) {
260
- if (r && !r.ok) {
261
- fail(r);
262
- return null;
263
- }
264
- }
265
- return { rules: rules?.ok ? rules.value : null, art9: art9?.ok ? art9.value : null };
266
- }
267
- async function read() {
268
- const me = await o.client.me.get(call);
269
- if (!me.ok) {
270
- fail(me);
271
- return null;
272
- }
273
- const needs = me.value.needs.filter(isKnownNeed);
274
- if (needs.includes("sign_in")) {
275
- o.onSignIn();
276
- return null;
277
- }
278
- const steps = stepsOf(me.value, needs);
279
- const texts = await textsFor(steps);
280
- return texts ? { me: me.value, steps, texts } : null;
281
- }
282
- function apply(r) {
283
- if (r.texts.rules?.textId !== state.rulesText?.textId) state.rulesAccepted = false;
284
- if (r.texts.art9?.textId !== state.art9Text?.textId) state.art9Accepted = false;
285
- state.me = r.me;
286
- state.steps = r.steps;
287
- state.reconsent = r.me.acceptances.rules === "reconsent_required";
288
- state.rulesText = r.texts.rules;
289
- state.rules = r.texts.rules ? communityRulesOf(r.texts.rules.bodyJson) : [];
290
- state.art9Text = r.texts.art9;
291
- state.art9Paragraphs = r.texts.art9 ? htmlToParagraphs(r.texts.art9.body) : [];
292
- }
293
- async function load() {
294
- state.phase = "loading";
295
- state.failure = null;
296
- const r = await read();
297
- if (!r) return;
298
- apply(r);
299
- state.index = 0;
300
- if (r.steps.length === 0) return submit();
301
- state.phase = "ready";
302
- }
303
- const step = () => state.steps[state.index] ?? null;
304
- function answered(s) {
305
- switch (s) {
306
- case "age":
307
- return state.ageBand !== null;
308
- case "rules":
309
- return state.rulesAccepted && state.rulesText !== null;
310
- case "art9":
311
- return state.art9Accepted && state.art9Text !== null;
312
- case "profile":
313
- return isDisplayNameOk(state.displayName) && isHandleOk(state.handle);
314
- }
315
- }
316
- function canContinue() {
317
- const s = step();
318
- return state.phase === "ready" && s !== null && answered(s);
319
- }
320
- function goTo(s) {
321
- const i = state.steps.indexOf(s);
322
- if (i >= 0) state.index = i;
323
- }
324
- const firstOpen = () => state.steps.findIndex((s) => !answered(s));
325
- async function submit() {
326
- const open = firstOpen();
327
- if (open >= 0) {
328
- state.index = open;
329
- return;
330
- }
331
- state.phase = "sending";
332
- state.failure = null;
333
- state.fields = [];
334
- const input = {
335
- ageBand: state.ageBand ?? state.me?.member?.ageBand ?? "18_plus",
336
- rulesTextId: state.steps.includes("rules") && state.rulesText ? state.rulesText.textId : ""
337
- };
338
- if (state.steps.includes("art9") && state.art9Text) input.art9TextId = state.art9Text.textId;
339
- if (state.steps.includes("profile")) {
340
- input.displayName = normalizeName(state.displayName);
341
- const handle = normalizeHandle(state.handle);
342
- if (handle !== "") input.handle = handle;
343
- input.avatarPreset = state.avatarPreset;
344
- }
345
- const r = await o.client.me.onboard(input, call);
346
- if (r.ok) {
347
- state.phase = "done";
348
- o.onDone(r.value);
349
- return;
350
- }
351
- state.failure = r;
352
- state.phase = "ready";
353
- switch (r.code) {
354
- case "age":
355
- state.minAge = 18;
356
- state.phase = "refused";
357
- o.onRefused?.(state.minAge);
358
- return;
359
- case "name":
360
- state.fields = normalizeHandle(state.handle) !== "" ? ["displayName", "handle"] : ["displayName"];
361
- return goTo("profile");
362
- case "handle":
363
- state.fields = ["handle"];
364
- return goTo("profile");
365
- case "invalid": {
366
- state.fields = r.fields ?? [];
367
- if (state.fields.some((f) => PROFILE_FIELDS.has(f))) return goTo("profile");
368
- if (state.fields.some((f) => TEXT_FIELDS.has(f))) return reread(r);
369
- if (state.fields.includes("ageBand")) return goTo("age");
370
- return;
371
- }
372
- case "signIn":
373
- return o.onSignIn();
374
- default:
375
- return;
376
- }
377
- }
378
- async function reread(refusal) {
379
- const named = [...state.fields];
380
- state.phase = "sending";
381
- const r = await read();
382
- if (!r) return;
383
- apply(r);
384
- if (named.includes("rulesTextId")) state.rulesAccepted = false;
385
- if (named.includes("art9TextId")) state.art9Accepted = false;
386
- state.failure = refusal;
387
- state.fields = named;
388
- const open = firstOpen();
389
- if (open >= 0) {
390
- state.index = open;
391
- state.phase = "ready";
392
- return;
393
- }
394
- if (r.me.needs.filter(isKnownNeed).length === 0) {
395
- state.phase = "done";
396
- o.onDone(r.me);
397
- return;
398
- }
399
- fail(refusal);
400
- }
401
- return {
402
- state,
403
- load,
404
- step,
405
- isLast: () => state.index >= state.steps.length - 1,
406
- canContinue,
407
- async next() {
408
- if (!canContinue()) return;
409
- state.failure = null;
410
- if (state.index < state.steps.length - 1) {
411
- state.index += 1;
412
- return;
413
- }
414
- await submit();
415
- },
416
- back() {
417
- if (state.phase !== "ready" || state.index === 0) return;
418
- state.failure = null;
419
- state.index -= 1;
420
- },
421
- suggestedHandle: () => normalizeHandle(state.handle) === "" ? suggestedHandle(state.displayName) : null,
422
- hasFieldError: (field) => state.fields.includes(field)
423
- };
424
- }
425
- function pickLang(...candidates) {
426
- for (const c of candidates) {
427
- if (!c) continue;
428
- const l = c.toLowerCase();
429
- if (l.startsWith("cs") || l.startsWith("sk")) return "cs";
430
- if (l.startsWith("en")) return "en";
431
- }
432
- return "en";
433
- }
434
- function documentLang() {
435
- return typeof document === "undefined" ? null : document.documentElement.lang || null;
436
- }
437
- function translator(lang) {
438
- return (key, params) => communityMessage(lang, key, params);
439
- }
440
- function knownKey(key) {
441
- return hasCommunityMessage(key) ? key : null;
442
- }
443
- const localeOf = (lang) => lang === "cs" ? "cs-CZ" : "en-GB";
444
- const formats = /* @__PURE__ */ new Map();
445
- function format(lang, withTime) {
446
- const key = `${lang}:${withTime ? "t" : "d"}`;
447
- if (!formats.has(key)) {
448
- let f = null;
449
- try {
450
- f = new Intl.DateTimeFormat(localeOf(lang), withTime ? { dateStyle: "medium", timeStyle: "short" } : { dateStyle: "long" });
451
- } catch {
452
- }
453
- formats.set(key, f);
454
- }
455
- return formats.get(key) ?? null;
456
- }
457
- function formatDate(lang, iso, withTime = true) {
458
- if (typeof iso !== "string" || iso === "") return "";
459
- const at = Date.parse(iso);
460
- if (!Number.isFinite(at)) return "";
461
- return format(lang, withTime)?.format(new Date(at)) ?? iso;
462
- }
463
- function isPast(iso, now = Date.now()) {
464
- if (typeof iso !== "string") return false;
465
- const at = Date.parse(iso);
466
- return Number.isFinite(at) && at <= now;
467
- }
468
- const REGION_LIST = /^[A-Z]{2}(?:,[A-Z]{2})*$/;
469
- const regionNames = /* @__PURE__ */ new Map();
470
- const regionLists = /* @__PURE__ */ new Map();
471
- function cached(cache, lang, make) {
472
- if (!cache.has(lang)) {
473
- let value = null;
474
- try {
475
- value = make(localeOf(lang));
476
- } catch {
477
- }
478
- cache.set(lang, value);
479
- }
480
- return cache.get(lang) ?? null;
481
- }
482
- function regionName(lang, code) {
483
- try {
484
- return cached(regionNames, lang, (locale) => new Intl.DisplayNames(locale, { type: "region" }))?.of(code) || code;
485
- } catch {
486
- return code;
487
- }
488
- }
489
- function formatTerritorialScope(lang, scope) {
490
- if (typeof scope !== "string") return "";
491
- const value = scope.trim();
492
- if (value === "all") {
493
- const t = translator(lang);
494
- return t("statement.scopeAll");
495
- }
496
- if (!REGION_LIST.test(value)) return value;
497
- const names = value.split(",").map((code) => regionName(lang, code));
498
- try {
499
- return cached(regionLists, lang, (locale) => new Intl.ListFormat(locale, { type: "conjunction" }))?.format(names) ?? names.join(", ");
500
- } catch {
501
- return names.join(", ");
502
- }
503
- }
504
- function failureText(failure, lang) {
505
- const until = failure.until ? formatDate(lang, failure.until) : "";
506
- if (failure.code === "restricted" && until) return { key: "sanction.restrictionUntil", params: { date: until } };
507
- if (failure.code === "suspended" && until) return { key: "sanction.suspensionUntil", params: { date: until } };
508
- return { key: `error.${failure.code}` };
509
- }
510
- const DEFAULT_WAIT_S = Object.freeze({ fast: 3, quota: 60 });
511
- function waitUntilOf(failure, now) {
512
- const fallback = DEFAULT_WAIT_S[failure.code];
513
- if (fallback === void 0) return null;
514
- const seconds = typeof failure.retryAfterS === "number" && failure.retryAfterS > 0 ? failure.retryAfterS : fallback;
515
- return now + seconds * 1e3;
516
- }
517
- const COUNTDOWN_MAX_S = 120;
518
- const EFFECTS = Object.freeze({
519
- method: "show",
520
- origin: "show",
521
- unavailable: "retry",
522
- invalid: "show",
523
- signIn: "reload",
524
- onboarding: "onboard",
525
- age: "show",
526
- name: "show",
527
- handle: "show",
528
- restricted: "reload",
529
- suspended: "reload",
530
- terminated: "reload",
531
- links: "show",
532
- fast: "wait",
533
- blocked: "show",
534
- confirm: "confirm",
535
- removed: "reload",
536
- quota: "wait",
537
- captcha: "show",
538
- notFound: "reload",
539
- surfaceOff: "hide",
540
- notMoved: "notMoved",
541
- ledger: "retry",
542
- submit: "show"
543
- });
544
- function failureEffect(code) {
545
- return EFFECTS[code] ?? "show";
546
- }
547
- const emptyDraft = (body = "") => ({ body, sending: false, failure: null, confirm: null, waitUntil: null, clientMsgId: null });
548
- const documentVisible = () => typeof document === "undefined" || document.visibilityState !== "hidden";
549
- function createDiscussionController(o) {
550
- const { client, lang } = o;
551
- const articleId = o.articleId.toLowerCase();
552
- const now = o.now ?? Date.now;
553
- const store = createDiscussionStore();
554
- const abort = new AbortController();
555
- const call = { signal: abort.signal };
556
- const state = reactive({
557
- phase: "loading",
558
- failure: null,
559
- thread: store.getState(),
560
- loadingMore: false,
561
- refreshing: false,
562
- newMessages: false,
563
- me: null,
564
- block: null,
565
- composer: emptyDraft(),
566
- inline: null,
567
- report: null,
568
- announcement: null,
569
- announceId: 0,
570
- clock: now(),
571
- busy: {},
572
- refusedAge: o.refusedAge ?? null
573
- });
574
- const unsubscribe = store.subscribe((s) => {
575
- state.thread = s;
576
- });
577
- const onboarding = shallowRef(null);
578
- let poller = null;
579
- let inView = true;
580
- let disposed = false;
581
- let lastSync = 0;
582
- let refreshing = null;
583
- let mePromise = null;
584
- let ticker = null;
585
- const interval = o.pollMs ?? 2e4;
586
- function announce(text) {
587
- state.announcement = text;
588
- state.announceId += 1;
589
- }
590
- function hide() {
591
- state.phase = "hidden";
592
- poller?.stop();
593
- poller = null;
594
- }
595
- function ensureMe() {
596
- if (state.me) return Promise.resolve();
597
- mePromise ??= client.me.get(call).then((r) => {
598
- mePromise = null;
599
- if (r.ok && !disposed) state.me = r.value;
600
- });
601
- return mePromise;
602
- }
603
- function decideBlock() {
604
- const s = store.getState();
605
- if (s.viewer.canPost || viewerNeeds(s).any || s.viewer.restrictions.length > 0) return;
606
- void ensureMe().then(() => {
607
- const me = state.me;
608
- if (disposed || !me || store.getState().viewer.canPost) return;
609
- state.block = !me.member || me.member.status === "active" ? "notMoved" : "restricted";
610
- });
611
- }
612
- function afterLoad() {
613
- const s = store.getState();
614
- if (s.viewer.canPost) state.block = null;
615
- if (s.viewer.memberId !== null) void ensureMe();
616
- decideBlock();
617
- if (onboarding.value && !viewerNeeds(s).any && onboarding.value.state.phase !== "sending") onboarding.value = null;
618
- }
619
- function loadFailed(f) {
620
- if (f.code === "surfaceOff") return hide();
621
- if (f.code === "notFound" && state.phase !== "ready") {
622
- warnOnce("not-found", "[blogmcp-community] the discussion of this article-id was not found (no such live article, or it is not readable here)");
623
- return hide();
624
- }
625
- if (state.phase === "loading" || state.phase === "failed") {
626
- state.phase = "failed";
627
- state.failure = f;
628
- }
629
- }
630
- function refresh() {
631
- if (disposed) return Promise.resolve();
632
- refreshing ??= (async () => {
633
- state.refreshing = true;
634
- const r = await refreshDiscussion(store, client, { articleId, languageCode: lang }, call);
635
- state.refreshing = false;
636
- if (disposed) return;
637
- lastSync = now();
638
- if (r.ok) {
639
- state.phase = "ready";
640
- state.failure = null;
641
- state.newMessages = false;
642
- afterLoad();
643
- } else {
644
- loadFailed(r);
645
- }
646
- })().finally(() => {
647
- refreshing = null;
648
- });
649
- return refreshing;
650
- }
651
- async function loadMore() {
652
- if (state.loadingMore || state.thread.nextCursor === null) return;
653
- state.loadingMore = true;
654
- const r = await loadMoreDiscussion(store, client, { articleId, languageCode: lang }, call);
655
- state.loadingMore = false;
656
- if (disposed || r.ok) return;
657
- if (r.code === "surfaceOff") return hide();
658
- announce(failureText(r, lang));
659
- }
660
- function onHeads(heads) {
661
- for (const head of heads) {
662
- if (typeof head?.articleId !== "string" || head.articleId.toLowerCase() !== articleId) continue;
663
- if (state.phase === "failed") {
664
- void refresh();
665
- continue;
666
- }
667
- const before = store.getState();
668
- const newer = typeof head.lastSeq === "number" && head.lastSeq > before.lastSeq;
669
- if (!store.applyHead(head)) continue;
670
- if (newer && before.loaded) state.newMessages = true;
671
- else void refresh();
672
- }
673
- }
674
- function startPolling() {
675
- if (poller || interval === 0 || disposed || state.phase === "hidden") return;
676
- poller = pollDiscussionHead(client, [articleId], {
677
- intervalMs: interval,
678
- visible: () => documentVisible() && inView,
679
- onChange: onHeads,
680
- onFailure: (f) => {
681
- if (f.code === "surfaceOff") hide();
682
- },
683
- signal: abort.signal
684
- });
685
- }
686
- function mode() {
687
- const s = state.thread;
688
- const n = viewerNeeds(s);
689
- if (n.signIn) return "signIn";
690
- if (state.block === "notMoved") return "notMoved";
691
- if (!s.viewer.canPost && s.viewer.restrictions.length > 0) return "sanction";
692
- if (n.any) return state.refusedAge !== null && !onboarding.value ? "ageRefused" : "onboarding";
693
- if (state.block === "restricted") return "restricted";
694
- return s.viewer.canPost ? "write" : "pending";
695
- }
696
- const isOwn = (m) => isOwnMessage(state.thread, m);
697
- const settled = (m) => !m.pending && m.seq > 0;
698
- const canWrite = () => mode() === "write";
699
- function ownAuthor() {
700
- const me = state.me;
701
- if (me?.member && me.profile) {
702
- return { kind: "member", memberId: me.member.memberId, handle: me.profile.handle, displayName: me.profile.displayName, avatarPreset: me.profile.avatarPreset };
703
- }
704
- const viewer = state.thread.viewer.memberId;
705
- if (viewer === null) return null;
706
- for (const m of state.thread.messages) {
707
- for (const x of [m, ...m.replies]) if (x.author.kind === "member" && x.author.memberId === viewer) return x.author;
708
- }
709
- return null;
710
- }
711
- function startClock() {
712
- state.clock = now();
713
- if (ticker) return;
714
- ticker = setInterval(() => {
715
- state.clock = now();
716
- let waiting = false;
717
- for (const d of [state.composer, state.inline]) {
718
- if (!d || d.waitUntil === null) continue;
719
- if (d.waitUntil <= state.clock) {
720
- d.waitUntil = null;
721
- if (d.failure && (d.failure.code === "fast" || d.failure.code === "quota")) d.failure = null;
722
- } else {
723
- waiting = true;
724
- }
725
- }
726
- if (!waiting && ticker) {
727
- clearInterval(ticker);
728
- ticker = null;
729
- }
730
- }, 1e3);
731
- }
732
- const waitLeft = (d) => d.waitUntil === null ? 0 : Math.max(0, Math.ceil((d.waitUntil - Math.max(state.clock, now())) / 1e3));
733
- function refused(d, f) {
734
- if (d) d.failure = f;
735
- switch (failureEffect(f.code)) {
736
- case "confirm":
737
- if (d && f.codes && f.codes.length > 0) d.confirm = { codes: [...f.codes], checked: [] };
738
- return;
739
- case "wait":
740
- if (d) {
741
- d.waitUntil = waitUntilOf(f, now());
742
- startClock();
743
- }
744
- return;
745
- case "onboard":
746
- beginOnboarding();
747
- void refresh();
748
- return;
749
- case "reload":
750
- void refresh();
751
- return;
752
- case "notMoved":
753
- state.block = "notMoved";
754
- return;
755
- case "hide":
756
- return hide();
757
- default:
758
- return;
759
- }
760
- }
761
- async function send(d, target, confirmations) {
762
- if (d.sending || waitLeft(d) > 0 || disposed) return;
763
- const body = normalizeBody(d.body);
764
- if (body === "" || codePointLength(body) > COMMUNITY_MESSAGE_MAX) return;
765
- const n = viewerNeeds(state.thread);
766
- if (n.signIn) return;
767
- if (n.any) return beginOnboarding();
768
- const parent = target.kind === "new" ? null : findMessage(state.thread, target.seq);
769
- if (target.kind !== "new" && (!parent || !settled(parent))) return;
770
- d.sending = true;
771
- d.failure = null;
772
- const author = target.kind === "edit" ? null : ownAuthor();
773
- const changeId = target.kind === "edit" ? newClientMsgId() : d.clientMsgId ??= newClientMsgId();
774
- if (target.kind === "edit") store.applyMessage({ type: "edit", clientMsgId: changeId, seq: target.seq, body });
775
- else if (author) store.applyMessage({ type: "post", clientMsgId: changeId, body, replyToSeq: target.kind === "reply" ? target.seq : null, author, languageCode: lang });
776
- const r = target.kind === "edit" ? await client.messages.edit({ conversationId: parent.conversationId, seq: target.seq, body, ...confirmations ? { confirmations } : {} }, call) : await client.messages.post(
777
- { articleId, body, replyToSeq: target.kind === "reply" ? target.seq : null, clientMsgId: changeId, languageCode: lang, ...confirmations ? { confirmations } : {} },
778
- call
779
- );
780
- if (disposed) return;
781
- d.sending = false;
782
- if (r.ok) {
783
- store.applyMessage({ type: "confirm", clientMsgId: changeId, message: r.value.message, warnings: r.value.warnings });
784
- const held = r.value.message.state === "held";
785
- d.body = "";
786
- d.clientMsgId = null;
787
- d.confirm = null;
788
- d.failure = null;
789
- if (target.kind !== "new" && state.inline === d) state.inline = null;
790
- announce({ key: held ? "composer.held" : "composer.posted" });
791
- if (target.kind !== "edit") o.onPosted?.(held ? "held" : "visible");
792
- return;
793
- }
794
- store.applyMessage({ type: "rollback", clientMsgId: changeId });
795
- refused(d, r);
796
- }
797
- function setConfirmed(d, code, checked) {
798
- if (!d.confirm || !d.confirm.codes.includes(code)) return;
799
- const rest = d.confirm.checked.filter((c) => c !== code);
800
- d.confirm.checked = checked ? [...rest, code] : rest;
801
- }
802
- async function sendConfirmed(d) {
803
- const confirm = d.confirm;
804
- if (!confirm || !confirm.codes.every((c) => confirm.checked.includes(c))) return;
805
- const target = d === state.composer ? { kind: "new" } : state.inline === d ? { kind: state.inline.kind, seq: state.inline.seq } : { kind: "new" };
806
- await send(d, target, [...confirm.codes]);
807
- }
808
- async function remove(seq) {
809
- const m = findMessage(state.thread, seq);
810
- const key = `delete:${seq}`;
811
- if (!m || !settled(m) || state.busy[key]) return false;
812
- state.busy[key] = true;
813
- const changeId = newClientMsgId();
814
- store.applyMessage({ type: "delete", clientMsgId: changeId, seq });
815
- const r = await client.messages.delete({ conversationId: m.conversationId, seq }, call);
816
- delete state.busy[key];
817
- if (disposed) return false;
818
- if (r.ok) {
819
- store.applyMessage({ type: "confirm", clientMsgId: changeId, message: r.value.message });
820
- return true;
821
- }
822
- store.applyMessage({ type: "rollback", clientMsgId: changeId });
823
- announce(failureText(r, lang));
824
- refused(null, r);
825
- return false;
826
- }
827
- const reactionKey = (t) => "articleId" in t ? "react:article" : `react:${t.seq}`;
828
- async function react(target, emoji) {
829
- const key = reactionKey(target);
830
- if (!canWrite() || state.busy[key] || !state.thread.reactionSet.includes(emoji)) return;
831
- const before = reactionsFor(state.thread, target);
832
- const next = before.mine === emoji ? null : emoji;
833
- state.busy[key] = true;
834
- store.applyReaction(target, reactionAfter(before, next));
835
- const r = await client.reactions.set({ target, emoji: next }, call);
836
- delete state.busy[key];
837
- if (disposed) return;
838
- if (r.ok) {
839
- store.applyReaction(target, r.value.reactions);
840
- return;
841
- }
842
- store.applyReaction(target, before);
843
- announce(failureText(r, lang));
844
- refused(null, r);
845
- }
846
- async function setBlocked(memberId, blocked) {
847
- const key = `block:${memberId}`;
848
- if (state.busy[key]) return;
849
- state.busy[key] = true;
850
- const r = await client.blocks.set({ memberId, blocked }, call);
851
- delete state.busy[key];
852
- if (disposed) return;
853
- if (r.ok) {
854
- store.collapse(memberId, r.value.blocked);
855
- announce({ key: r.value.blocked ? "discussion.blocked" : "discussion.unblocked" });
856
- return;
857
- }
858
- announce(failureText(r, lang));
859
- refused(null, r);
860
- }
861
- function openReport(m) {
862
- state.report = { conversationId: m.conversationId, seq: m.seq, category: "", ruleId: "", note: "", rules: null, rulesFailed: false, sending: false, failure: null, done: false };
863
- const report = state.report;
864
- void client.legal.current({ kind: "rules", locale: lang }, call).then((r) => {
865
- if (state.report !== report) return;
866
- if (r.ok) report.rules = communityRulesOf(r.value.bodyJson);
867
- else report.rulesFailed = true;
868
- });
869
- }
870
- async function submitReport() {
871
- const report = state.report;
872
- if (!report || report.sending || report.done || report.category === "") return;
873
- if (report.category === "rules" && report.ruleId === "") return;
874
- const note = normalizeBody(report.note);
875
- if (codePointLength(note) > COMMUNITY_REPORT_NOTE_MAX) return;
876
- report.sending = true;
877
- report.failure = null;
878
- const r = await client.messages.report(
879
- { conversationId: report.conversationId, seq: report.seq, category: report.category, ruleId: report.category === "rules" ? report.ruleId : null, note: note === "" ? null : note },
880
- call
881
- );
882
- report.sending = false;
883
- if (disposed) return;
884
- if (r.ok) {
885
- report.done = true;
886
- announce({ key: "report.done" });
887
- return;
888
- }
889
- report.failure = r;
890
- if (r.code === "surfaceOff") hide();
891
- }
892
- function beginOnboarding() {
893
- if (onboarding.value || disposed || state.refusedAge !== null) return;
894
- const model = createOnboarding({
895
- client,
896
- lang,
897
- signal: abort.signal,
898
- onRefused: (minAge) => {
899
- state.refusedAge = minAge;
900
- o.onAgeRefused?.(minAge);
901
- },
902
- onDone: (me) => {
903
- state.me = me;
904
- onboarding.value = null;
905
- announce({ key: "onboarding.done" });
906
- void refresh();
907
- },
908
- onSignIn: () => {
909
- onboarding.value = null;
910
- void refresh();
911
- }
912
- });
913
- onboarding.value = model;
914
- void model.load();
915
- }
916
- return {
917
- state,
918
- store,
919
- onboarding,
920
- async start() {
921
- await refresh();
922
- startPolling();
923
- },
924
- refresh,
925
- loadMore,
926
- mode,
927
- needs: () => viewerNeeds(state.thread),
928
- isOwn,
929
- canReply: (m) => {
930
- if (!canWrite() || !settled(m) || m.state !== "visible" || m.body === null) return false;
931
- if (m.replyToSeq === null) return true;
932
- const parent = findMessage(state.thread, m.replyToSeq);
933
- return parent !== null && settled(parent) && parent.state === "visible";
934
- },
935
- canEdit: (m) => canWrite() && isOwn(m) && settled(m) && m.body !== null && (m.state === "visible" || m.state === "held"),
936
- canDelete: (m) => state.thread.viewer.memberId !== null && isOwn(m) && settled(m) && m.body !== null && m.state !== "deleted",
937
- canReact: canWrite,
938
- canReport: (m) => !viewerNeeds(state.thread).signIn && !isOwn(m) && settled(m) && m.body !== null && m.author.kind !== "system",
939
- canReportAsNotifier: (m) => viewerNeeds(state.thread).signIn && settled(m) && m.body !== null && m.author.kind !== "system",
940
- canBlock: (m) => !viewerNeeds(state.thread).signIn && !isOwn(m) && settled(m) && m.author.kind === "member" && m.author.memberId !== null,
941
- setInView(v) {
942
- const was = inView;
943
- inView = v;
944
- if (v && !was && poller && now() - lastSync >= interval) poller.now();
945
- },
946
- submit: () => send(state.composer, { kind: "new" }),
947
- openReply(seq, replyingTo = null) {
948
- const m = findMessage(state.thread, seq);
949
- if (!m) return;
950
- const top = m.replyToSeq ?? m.seq;
951
- if (state.inline?.kind === "reply" && state.inline.seq === top) {
952
- state.inline.replyingTo = replyingTo;
953
- return;
954
- }
955
- state.inline = { ...emptyDraft(), kind: "reply", seq: top, replyingTo };
956
- },
957
- openEdit(seq) {
958
- const m = findMessage(state.thread, seq);
959
- if (!m || m.body === null) return;
960
- state.inline = { ...emptyDraft(m.body), kind: "edit", seq, replyingTo: null };
961
- },
962
- closeInline() {
963
- state.inline = null;
964
- },
965
- async submitInline() {
966
- const d = state.inline;
967
- if (d) await send(d, { kind: d.kind, seq: d.seq });
968
- },
969
- setConfirmed,
970
- sendConfirmed,
971
- remove,
972
- react,
973
- setBlocked,
974
- openReport,
975
- closeReport() {
976
- state.report = null;
977
- },
978
- submitReport,
979
- beginOnboarding,
980
- cancelOnboarding() {
981
- onboarding.value = null;
982
- },
983
- waitLeft,
984
- dispose() {
985
- if (disposed) return;
986
- disposed = true;
987
- abort.abort();
988
- poller?.stop();
989
- poller = null;
990
- unsubscribe();
991
- if (ticker) clearInterval(ticker);
992
- ticker = null;
993
- onboarding.value = null;
994
- }
995
- };
996
- }
997
- const TEXT = /* @__PURE__ */ Symbol("bcm-text");
998
- function provideText(lang) {
999
- const t = (key, params) => communityMessage(lang.value, key, params);
1000
- const ctx = { lang, t, text: (ref2) => ref2 ? t(ref2.key, ref2.params) : "" };
1001
- provide(TEXT, ctx);
1002
- return ctx;
1003
- }
1004
- function useText() {
1005
- const ctx = inject(TEXT, null);
1006
- if (!ctx) throw new Error("[blogmcp-community] a component rendered outside its element");
1007
- return ctx;
1008
- }
1009
- let counter = 0;
1010
- function uid(prefix) {
1011
- counter += 1;
1012
- return `${prefix}-${counter}`;
1013
- }
1014
- function useModal(dialog, onCancel, fallback) {
1015
- let previous = null;
1016
- const onCancelEvent = (e) => {
1017
- e.preventDefault();
1018
- onCancel();
1019
- };
1020
- onMounted(() => {
1021
- const el = dialog.value;
1022
- if (!el) return;
1023
- const root = el.getRootNode();
1024
- previous = root.activeElement ?? null;
1025
- el.addEventListener("cancel", onCancelEvent);
1026
- if (typeof el.showModal === "function") {
1027
- try {
1028
- el.showModal();
1029
- } catch {
1030
- el.setAttribute("open", "");
1031
- }
1032
- } else {
1033
- el.setAttribute("open", "");
1034
- }
1035
- });
1036
- onBeforeUnmount(() => {
1037
- const el = dialog.value;
1038
- el?.removeEventListener("cancel", onCancelEvent);
1039
- if (el?.open && typeof el.close === "function") el.close();
1040
- const back = previous;
1041
- void nextTick(() => {
1042
- const target = back && back.isConnected ? back : fallback?.() ?? null;
1043
- target?.focus();
1044
- });
1045
- });
1046
- }
1047
- const _hoisted_1$c = ["aria-labelledby", "aria-describedby"];
1048
- const _hoisted_2$b = { class: "bcm-dialog-body" };
1049
- const _hoisted_3$b = ["id"];
1050
- const _hoisted_4$b = ["id"];
1051
- const _hoisted_5$9 = { class: "bcm-row" };
1052
- const _hoisted_6$9 = ["disabled"];
1053
- const _hoisted_7$9 = ["disabled"];
1054
- const _sfc_main$d = /* @__PURE__ */ defineComponent({
1055
- __name: "ConfirmDialog",
1056
- props: {
1057
- title: {},
1058
- explain: {},
1059
- explainParams: {},
1060
- confirm: {},
1061
- cancel: {},
1062
- danger: { type: Boolean },
1063
- busy: { type: Boolean },
1064
- fallbackFocus: { type: Function }
1065
- },
1066
- emits: ["confirm", "cancel"],
1067
- setup(__props, { emit: __emit }) {
1068
- const props = __props;
1069
- const emit = __emit;
1070
- const { t } = useText();
1071
- const dialog = ref(null);
1072
- const id = uid("bcm-dialog");
1073
- useModal(dialog, () => emit("cancel"), props.fallbackFocus);
1074
- return (_ctx, _cache) => {
1075
- return openBlock(), createElementBlock("dialog", {
1076
- ref_key: "dialog",
1077
- ref: dialog,
1078
- class: "bcm-dialog",
1079
- "aria-labelledby": `${unref(id)}-title`,
1080
- "aria-describedby": __props.explain ? `${unref(id)}-explain` : void 0
1081
- }, [
1082
- createElementVNode("div", _hoisted_2$b, [
1083
- createElementVNode("h3", {
1084
- id: `${unref(id)}-title`
1085
- }, toDisplayString(unref(t)(__props.title)), 9, _hoisted_3$b),
1086
- __props.explain ? (openBlock(), createElementBlock("p", {
1087
- key: 0,
1088
- id: `${unref(id)}-explain`
1089
- }, toDisplayString(unref(t)(__props.explain, __props.explainParams)), 9, _hoisted_4$b)) : createCommentVNode("", true),
1090
- createElementVNode("div", _hoisted_5$9, [
1091
- _cache[2] || (_cache[2] = createElementVNode("span", { class: "bcm-spacer" }, null, -1)),
1092
- createElementVNode("button", {
1093
- type: "button",
1094
- class: "bcm-btn",
1095
- disabled: __props.busy,
1096
- onClick: _cache[0] || (_cache[0] = ($event) => emit("cancel"))
1097
- }, toDisplayString(unref(t)(__props.cancel ?? "common.cancel")), 9, _hoisted_6$9),
1098
- createElementVNode("button", {
1099
- type: "button",
1100
- class: normalizeClass(["bcm-btn bcm-primary", { "bcm-danger": __props.danger }]),
1101
- disabled: __props.busy,
1102
- onClick: _cache[1] || (_cache[1] = ($event) => emit("confirm"))
1103
- }, toDisplayString(unref(t)(__props.confirm)), 11, _hoisted_7$9)
1104
- ])
1105
- ])
1106
- ], 8, _hoisted_1$c);
1107
- };
1108
- }
1109
- });
1110
- const _hoisted_1$b = ["for"];
1111
- const _hoisted_2$a = {
1112
- key: 0,
1113
- class: "bcm-hint"
1114
- };
1115
- const _hoisted_3$a = ["id", "placeholder", "aria-describedby", "aria-invalid", "readonly"];
1116
- const _hoisted_4$a = { class: "bcm-composer-foot" };
1117
- const _hoisted_5$8 = ["id"];
1118
- const _hoisted_6$8 = {
1119
- class: "bcm-sr",
1120
- "aria-live": "polite"
1121
- };
1122
- const _hoisted_7$8 = ["disabled"];
1123
- const _hoisted_8$8 = ["disabled"];
1124
- const _hoisted_9$8 = ["id"];
1125
- const _hoisted_10$8 = { key: 0 };
1126
- const _hoisted_11$8 = ["href"];
1127
- const _hoisted_12$8 = {
1128
- key: 1,
1129
- class: "bcm-error"
1130
- };
1131
- const _hoisted_13$8 = {
1132
- key: 2,
1133
- class: "bcm-warning",
1134
- role: "status"
1135
- };
1136
- const _hoisted_14$8 = {
1137
- key: 3,
1138
- class: "bcm-error",
1139
- role: "alert"
1140
- };
1141
- const _hoisted_15$8 = {
1142
- key: 4,
1143
- class: "bcm-confirm"
1144
- };
1145
- const _hoisted_16$8 = { class: "bcm-label" };
1146
- const _hoisted_17$8 = ["checked", "onChange"];
1147
- const _hoisted_18$8 = { class: "bcm-row" };
1148
- const _hoisted_19$8 = ["disabled"];
1149
- const _sfc_main$c = /* @__PURE__ */ defineComponent({
1150
- __name: "MessageComposer",
1151
- props: {
1152
- draft: {},
1153
- kind: {},
1154
- wait: {},
1155
- replyingTo: {},
1156
- rulesHref: {}
1157
- },
1158
- emits: ["submit", "cancel", "confirmed", "confirm"],
1159
- setup(__props, { expose: __expose, emit: __emit }) {
1160
- const props = __props;
1161
- const emit = __emit;
1162
- const { t, lang } = useText();
1163
- const id = uid("bcm-composer");
1164
- const input = ref(null);
1165
- const length = computed(() => codePointLength(normalizeBody(props.draft.body)));
1166
- const over = computed(() => length.value > COMMUNITY_MESSAGE_MAX);
1167
- const canSend = computed(() => !props.draft.sending && length.value > 0 && !over.value && props.wait === 0 && props.draft.confirm === null);
1168
- const allConfirmed = computed(() => {
1169
- const c = props.draft.confirm;
1170
- return c !== null && c.codes.every((code) => c.checked.includes(code));
1171
- });
1172
- const label = computed(() => t(props.kind === "reply" ? "composer.replyLabel" : props.kind === "edit" ? "composer.editLabel" : "composer.label"));
1173
- const keys = typeof navigator !== "undefined" && /Mac|iPhone|iPad/i.test(navigator.platform || navigator.userAgent) ? "⌘ + Enter" : "Ctrl + Enter";
1174
- const failure = computed(() => {
1175
- const f = props.draft.failure;
1176
- if (!f || props.draft.confirm && f.code === "confirm") return "";
1177
- if ((f.code === "fast" || f.code === "quota") && props.wait > 0 && props.wait <= COUNTDOWN_MAX_S) return "";
1178
- const ref2 = failureText(f, lang.value);
1179
- return t(ref2.key, ref2.params);
1180
- });
1181
- const counterLive = ref("");
1182
- let liveTimer = null;
1183
- watch(length, (n) => {
1184
- if (liveTimer) clearTimeout(liveTimer);
1185
- liveTimer = setTimeout(() => {
1186
- counterLive.value = n >= COMMUNITY_MESSAGE_MAX * 0.9 ? t("composer.counter", { length: n, max: COMMUNITY_MESSAGE_MAX }) : "";
1187
- }, 700);
1188
- });
1189
- onBeforeUnmount(() => {
1190
- if (liveTimer) clearTimeout(liveTimer);
1191
- });
1192
- watch(
1193
- () => props.draft.body,
1194
- () => {
1195
- const d = props.draft;
1196
- if (d.confirm) d.confirm = null;
1197
- if (d.failure && d.failure.code !== "fast" && d.failure.code !== "quota") d.failure = null;
1198
- }
1199
- );
1200
- function onKey(e) {
1201
- if (e.key !== "Enter" || !(e.metaKey || e.ctrlKey)) return;
1202
- e.preventDefault();
1203
- if (canSend.value) emit("submit");
1204
- }
1205
- function onSubmit() {
1206
- if (canSend.value) emit("submit");
1207
- }
1208
- __expose({ focus: () => input.value?.focus() });
1209
- return (_ctx, _cache) => {
1210
- return openBlock(), createElementBlock("form", {
1211
- class: "bcm-composer",
1212
- onSubmit: withModifiers(onSubmit, ["prevent"])
1213
- }, [
1214
- createElementVNode("label", {
1215
- for: unref(id),
1216
- class: "bcm-label"
1217
- }, toDisplayString(label.value), 9, _hoisted_1$b),
1218
- __props.replyingTo ? (openBlock(), createElementBlock("p", _hoisted_2$a, toDisplayString(unref(t)("composer.replyingTo", { name: __props.replyingTo })), 1)) : createCommentVNode("", true),
1219
- withDirectives(createElementVNode("textarea", {
1220
- id: unref(id),
1221
- ref_key: "input",
1222
- ref: input,
1223
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => __props.draft.body = $event),
1224
- class: "bcm-input bcm-textarea",
1225
- rows: "4",
1226
- placeholder: unref(t)("composer.placeholder"),
1227
- "aria-describedby": `${unref(id)}-counter ${unref(id)}-hints`,
1228
- "aria-invalid": over.value,
1229
- readonly: __props.draft.sending,
1230
- onKeydown: onKey
1231
- }, null, 40, _hoisted_3$a), [
1232
- [vModelText, __props.draft.body]
1233
- ]),
1234
- createElementVNode("div", _hoisted_4$a, [
1235
- createElementVNode("span", {
1236
- id: `${unref(id)}-counter`,
1237
- class: normalizeClass(["bcm-counter", { "bcm-counter-over": over.value }])
1238
- }, toDisplayString(unref(t)("composer.counter", { length: length.value, max: unref(COMMUNITY_MESSAGE_MAX) })), 11, _hoisted_5$8),
1239
- createElementVNode("span", _hoisted_6$8, toDisplayString(counterLive.value), 1),
1240
- _cache[3] || (_cache[3] = createElementVNode("span", { class: "bcm-spacer" }, null, -1)),
1241
- __props.kind !== "new" ? (openBlock(), createElementBlock("button", {
1242
- key: 0,
1243
- type: "button",
1244
- class: "bcm-btn",
1245
- disabled: __props.draft.sending,
1246
- onClick: _cache[1] || (_cache[1] = ($event) => emit("cancel"))
1247
- }, toDisplayString(unref(t)("composer.cancel")), 9, _hoisted_7$8)) : createCommentVNode("", true),
1248
- createElementVNode("button", {
1249
- type: "submit",
1250
- class: "bcm-btn bcm-primary",
1251
- disabled: !canSend.value
1252
- }, toDisplayString(__props.draft.sending ? unref(t)("composer.sending") : __props.kind === "edit" ? unref(t)("composer.save") : unref(t)("composer.submit")), 9, _hoisted_8$8)
1253
- ]),
1254
- createElementVNode("div", {
1255
- id: `${unref(id)}-hints`,
1256
- class: "bcm-hint"
1257
- }, [
1258
- createElementVNode("p", null, toDisplayString(unref(t)("composer.linksNote", { count: unref(COMMUNITY_MESSAGE_LINKS_MAX) })), 1),
1259
- createElementVNode("p", null, toDisplayString(unref(t)("composer.shortcut", { keys: unref(keys) })), 1),
1260
- __props.rulesHref ? (openBlock(), createElementBlock("p", _hoisted_10$8, [
1261
- createElementVNode("a", {
1262
- href: __props.rulesHref,
1263
- target: "_blank",
1264
- rel: "noopener"
1265
- }, toDisplayString(unref(t)("onboarding.rulesTitle")), 9, _hoisted_11$8)
1266
- ])) : createCommentVNode("", true)
1267
- ], 8, _hoisted_9$8),
1268
- over.value ? (openBlock(), createElementBlock("p", _hoisted_12$8, toDisplayString(unref(t)("composer.tooLong", { max: unref(COMMUNITY_MESSAGE_MAX) })), 1)) : createCommentVNode("", true),
1269
- __props.wait > 0 && __props.wait <= unref(COUNTDOWN_MAX_S) ? (openBlock(), createElementBlock("p", _hoisted_13$8, toDisplayString(unref(t)("composer.cooldown", { count: __props.wait })), 1)) : createCommentVNode("", true),
1270
- failure.value ? (openBlock(), createElementBlock("p", _hoisted_14$8, toDisplayString(failure.value), 1)) : createCommentVNode("", true),
1271
- __props.draft.confirm ? (openBlock(), createElementBlock("fieldset", _hoisted_15$8, [
1272
- createElementVNode("legend", _hoisted_16$8, toDisplayString(unref(t)("confirm.title")), 1),
1273
- createElementVNode("p", null, toDisplayString(unref(t)("error.confirm")), 1),
1274
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.draft.confirm.codes, (code) => {
1275
- return openBlock(), createElementBlock("label", {
1276
- key: code,
1277
- class: "bcm-check"
1278
- }, [
1279
- createElementVNode("input", {
1280
- type: "checkbox",
1281
- checked: __props.draft.confirm.checked.includes(code),
1282
- onChange: ($event) => emit("confirm", code, $event.target.checked)
1283
- }, null, 40, _hoisted_17$8),
1284
- createElementVNode("span", null, toDisplayString(unref(t)(unref(confirmMessageKey)(code))), 1)
1285
- ]);
1286
- }), 128)),
1287
- createElementVNode("div", _hoisted_18$8, [
1288
- createElementVNode("button", {
1289
- type: "button",
1290
- class: "bcm-btn bcm-primary",
1291
- disabled: !allConfirmed.value || __props.draft.sending || __props.wait > 0,
1292
- onClick: _cache[2] || (_cache[2] = ($event) => emit("confirmed"))
1293
- }, toDisplayString(unref(t)("confirm.submit")), 9, _hoisted_19$8)
1294
- ])
1295
- ])) : createCommentVNode("", true)
1296
- ], 32);
1297
- };
1298
- }
1299
- });
1300
- const AVATAR_PRESETS = Object.freeze([
1301
- Object.freeze({ id: "fox", emoji: "🦊" }),
1302
- Object.freeze({ id: "owl", emoji: "🦉" }),
1303
- Object.freeze({ id: "bear", emoji: "🐻" }),
1304
- Object.freeze({ id: "hedgehog", emoji: "🦔" }),
1305
- Object.freeze({ id: "frog", emoji: "🐸" }),
1306
- Object.freeze({ id: "bee", emoji: "🐝" }),
1307
- Object.freeze({ id: "leaf", emoji: "🍃" }),
1308
- Object.freeze({ id: "flower", emoji: "🌼" }),
1309
- Object.freeze({ id: "tree", emoji: "🌲" }),
1310
- Object.freeze({ id: "star", emoji: "⭐" })
1311
- ]);
1312
- const BY_ID = new Map(AVATAR_PRESETS.map((p) => [p.id, p.emoji]));
1313
- function avatarEmoji(preset) {
1314
- return typeof preset === "string" ? BY_ID.get(preset) ?? null : null;
1315
- }
1316
- function initialOf(name) {
1317
- if (typeof name !== "string") return "";
1318
- for (const ch of name.trim()) return ch.toLocaleUpperCase();
1319
- return "";
1320
- }
1321
- function hueOf(id) {
1322
- if (typeof id !== "string" || id === "") return 210;
1323
- let h = 0;
1324
- for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) % 360;
1325
- return h;
1326
- }
1327
- const _sfc_main$b = /* @__PURE__ */ defineComponent({
1328
- __name: "AuthorAvatar",
1329
- props: {
1330
- author: {}
1331
- },
1332
- setup(__props) {
1333
- const props = __props;
1334
- const emoji = computed(() => props.author.kind === "member" ? avatarEmoji(props.author.avatarPreset) : null);
1335
- const initial = computed(() => {
1336
- const a = props.author;
1337
- if (a.kind === "member") return initialOf(a.displayName);
1338
- if (a.kind === "staff") return initialOf(a.displayName);
1339
- return "";
1340
- });
1341
- const background = computed(() => {
1342
- const a = props.author;
1343
- if (emoji.value || !initial.value) return void 0;
1344
- const id = a.kind === "member" ? a.memberId : a.kind === "staff" ? a.staffId : null;
1345
- return `hsl(${hueOf(id)} 45% 42%)`;
1346
- });
1347
- return (_ctx, _cache) => {
1348
- return openBlock(), createElementBlock("span", {
1349
- class: normalizeClass(["bcm-avatar", { "bcm-avatar-initial": !emoji.value && initial.value }]),
1350
- style: normalizeStyle(background.value ? { background: background.value } : void 0),
1351
- "aria-hidden": "true"
1352
- }, toDisplayString(emoji.value ?? initial.value), 7);
1353
- };
1354
- }
1355
- });
1356
- const _hoisted_1$a = ["href", "rel"];
1357
- const _sfc_main$a = /* @__PURE__ */ defineComponent({
1358
- __name: "LinkedText",
1359
- props: {
1360
- text: {}
1361
- },
1362
- setup(__props) {
1363
- const props = __props;
1364
- const segments = computed(() => linkify(props.text ?? ""));
1365
- return (_ctx, _cache) => {
1366
- return openBlock(true), createElementBlock(Fragment, null, renderList(segments.value, (s, i) => {
1367
- return openBlock(), createElementBlock(Fragment, { key: i }, [
1368
- s.kind === "link" ? (openBlock(), createElementBlock("a", {
1369
- key: 0,
1370
- href: s.href,
1371
- rel: unref(UGC_LINK_REL),
1372
- target: "_blank"
1373
- }, toDisplayString(s.text), 9, _hoisted_1$a)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
1374
- createTextVNode(toDisplayString(s.text), 1)
1375
- ], 64))
1376
- ], 64);
1377
- }), 128);
1378
- };
1379
- }
1380
- });
1381
- const _hoisted_1$9 = ["aria-expanded", "aria-controls"];
1382
- const _hoisted_2$9 = ["id"];
1383
- const _hoisted_3$9 = ["href"];
1384
- const _hoisted_4$9 = ["onClick"];
1385
- const _sfc_main$9 = /* @__PURE__ */ defineComponent({
1386
- __name: "MessageMenu",
1387
- props: {
1388
- items: {}
1389
- },
1390
- emits: ["select"],
1391
- setup(__props, { emit: __emit }) {
1392
- const emit = __emit;
1393
- const { t } = useText();
1394
- const open = ref(false);
1395
- const toggle = ref(null);
1396
- const root = ref(null);
1397
- const listId = uid("bcm-menu");
1398
- function onOutside(e) {
1399
- if (!root.value || e.composedPath().includes(root.value)) return;
1400
- close(false);
1401
- }
1402
- function setOpen(next) {
1403
- open.value = next;
1404
- if (next) {
1405
- document.addEventListener("pointerdown", onOutside, true);
1406
- void nextTick(() => root.value?.querySelector(".bcm-menu-list button, .bcm-menu-list a")?.focus());
1407
- } else {
1408
- document.removeEventListener("pointerdown", onOutside, true);
1409
- }
1410
- }
1411
- function close(focusToggle = true) {
1412
- setOpen(false);
1413
- if (focusToggle) toggle.value?.focus();
1414
- }
1415
- function pick(id) {
1416
- close(true);
1417
- emit("select", id);
1418
- }
1419
- onBeforeUnmount(() => document.removeEventListener("pointerdown", onOutside, true));
1420
- return (_ctx, _cache) => {
1421
- return __props.items.length ? (openBlock(), createElementBlock("div", {
1422
- key: 0,
1423
- ref_key: "root",
1424
- ref: root,
1425
- class: "bcm-menu",
1426
- onKeydown: _cache[2] || (_cache[2] = withKeys(withModifiers(($event) => close(true), ["stop"]), ["esc"]))
1427
- }, [
1428
- createElementVNode("button", {
1429
- ref_key: "toggle",
1430
- ref: toggle,
1431
- type: "button",
1432
- class: "bcm-quiet",
1433
- "aria-expanded": open.value,
1434
- "aria-controls": unref(listId),
1435
- onClick: _cache[0] || (_cache[0] = ($event) => setOpen(!open.value))
1436
- }, toDisplayString(unref(t)("discussion.menu")), 9, _hoisted_1$9),
1437
- open.value ? (openBlock(), createElementBlock("div", {
1438
- key: 0,
1439
- id: unref(listId),
1440
- class: "bcm-menu-list"
1441
- }, [
1442
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
1443
- return openBlock(), createElementBlock(Fragment, {
1444
- key: item.id
1445
- }, [
1446
- item.href ? (openBlock(), createElementBlock("a", {
1447
- key: 0,
1448
- href: item.href,
1449
- class: normalizeClass({ "bcm-danger": item.danger }),
1450
- onClick: _cache[1] || (_cache[1] = ($event) => close(false))
1451
- }, toDisplayString(item.label), 11, _hoisted_3$9)) : (openBlock(), createElementBlock("button", {
1452
- key: 1,
1453
- type: "button",
1454
- class: normalizeClass({ "bcm-danger": item.danger }),
1455
- onClick: ($event) => pick(item.id)
1456
- }, toDisplayString(item.label), 11, _hoisted_4$9))
1457
- ], 64);
1458
- }), 128))
1459
- ], 8, _hoisted_2$9)) : createCommentVNode("", true)
1460
- ], 544)) : createCommentVNode("", true);
1461
- };
1462
- }
1463
- });
1464
- const _hoisted_1$8 = ["aria-label"];
1465
- const _hoisted_2$8 = ["aria-pressed", "disabled", "title", "onClick"];
1466
- const _hoisted_3$8 = {
1467
- key: 0,
1468
- class: "bcm-sr"
1469
- };
1470
- const _hoisted_4$8 = {
1471
- key: 1,
1472
- class: "bcm-react"
1473
- };
1474
- const _sfc_main$8 = /* @__PURE__ */ defineComponent({
1475
- __name: "ReactionBar",
1476
- props: {
1477
- reactionSet: {},
1478
- summary: {},
1479
- interactive: { type: Boolean },
1480
- busy: { type: Boolean }
1481
- },
1482
- emits: ["react"],
1483
- setup(__props, { emit: __emit }) {
1484
- const props = __props;
1485
- const emit = __emit;
1486
- const { t } = useText();
1487
- const shown = computed(() => props.interactive ? props.reactionSet : props.reactionSet.filter((e) => (props.summary.counts[e] ?? 0) > 0));
1488
- return (_ctx, _cache) => {
1489
- return shown.value.length ? (openBlock(), createElementBlock("div", {
1490
- key: 0,
1491
- class: "bcm-reactions",
1492
- role: "group",
1493
- "aria-label": unref(t)("discussion.reactions")
1494
- }, [
1495
- (openBlock(true), createElementBlock(Fragment, null, renderList(shown.value, (emoji) => {
1496
- return openBlock(), createElementBlock(Fragment, { key: emoji }, [
1497
- __props.interactive ? (openBlock(), createElementBlock("button", {
1498
- key: 0,
1499
- type: "button",
1500
- class: "bcm-react",
1501
- "aria-pressed": __props.summary.mine === emoji,
1502
- disabled: __props.busy,
1503
- title: __props.summary.mine === emoji ? unref(t)("discussion.reactionRemove") : unref(t)("discussion.reactionAdd"),
1504
- onClick: ($event) => emit("react", emoji)
1505
- }, [
1506
- createElementVNode("span", null, toDisplayString(unref(t)("discussion.reactionCount", { emoji, count: __props.summary.counts[emoji] ?? 0 })), 1),
1507
- __props.summary.mine === emoji ? (openBlock(), createElementBlock("span", _hoisted_3$8, toDisplayString(unref(t)("discussion.reactionMine")), 1)) : createCommentVNode("", true)
1508
- ], 8, _hoisted_2$8)) : (openBlock(), createElementBlock("span", _hoisted_4$8, toDisplayString(unref(t)("discussion.reactionCount", { emoji, count: __props.summary.counts[emoji] ?? 0 })), 1))
1509
- ], 64);
1510
- }), 128))
1511
- ], 8, _hoisted_1$8)) : createCommentVNode("", true);
1512
- };
1513
- }
1514
- });
1515
- const KEY = /* @__PURE__ */ Symbol("bcm-discussion");
1516
- function provideDiscussion(ctx) {
1517
- provide(KEY, ctx);
1518
- }
1519
- function useDiscussion() {
1520
- const ctx = inject(KEY, null);
1521
- if (!ctx) throw new Error("[blogmcp-community] a discussion component rendered outside <blogmcp-discussion>");
1522
- return ctx;
1523
- }
1524
- const _hoisted_1$7 = { class: "bcm-msg-item" };
1525
- const _hoisted_2$7 = ["aria-busy"];
1526
- const _hoisted_3$7 = { class: "bcm-msg-main" };
1527
- const _hoisted_4$7 = { class: "bcm-meta" };
1528
- const _hoisted_5$7 = { class: "bcm-author" };
1529
- const _hoisted_6$7 = {
1530
- key: 0,
1531
- class: "bcm-badge bcm-badge-team"
1532
- };
1533
- const _hoisted_7$7 = {
1534
- key: 1,
1535
- class: "bcm-handle"
1536
- };
1537
- const _hoisted_8$7 = ["datetime"];
1538
- const _hoisted_9$7 = {
1539
- key: 3,
1540
- class: "bcm-muted"
1541
- };
1542
- const _hoisted_10$7 = {
1543
- key: 4,
1544
- class: "bcm-muted",
1545
- role: "status"
1546
- };
1547
- const _hoisted_11$7 = {
1548
- key: 0,
1549
- class: "bcm-placeholder"
1550
- };
1551
- const _hoisted_12$7 = {
1552
- key: 1,
1553
- class: "bcm-muted"
1554
- };
1555
- const _hoisted_13$7 = {
1556
- key: 0,
1557
- class: "bcm-own-state bcm-own-consent"
1558
- };
1559
- const _hoisted_14$7 = { class: "bcm-badge" };
1560
- const _hoisted_15$7 = { class: "bcm-hint" };
1561
- const _hoisted_16$7 = {
1562
- key: 1,
1563
- class: "bcm-own-state"
1564
- };
1565
- const _hoisted_17$7 = { class: "bcm-badge" };
1566
- const _hoisted_18$7 = { class: "bcm-hint" };
1567
- const _hoisted_19$7 = ["href"];
1568
- const _hoisted_20$7 = {
1569
- key: 3,
1570
- class: "bcm-text"
1571
- };
1572
- const _hoisted_21$6 = {
1573
- key: 4,
1574
- class: "bcm-hint"
1575
- };
1576
- const _hoisted_22$6 = ["href"];
1577
- const _hoisted_23$6 = {
1578
- key: 3,
1579
- class: "bcm-actions"
1580
- };
1581
- const _hoisted_24$5 = {
1582
- key: 0,
1583
- class: "bcm-replies"
1584
- };
1585
- const _hoisted_25$4 = {
1586
- key: 2,
1587
- class: "bcm-hint"
1588
- };
1589
- const REPLIES_SHOWN = 3;
1590
- const _sfc_main$7 = /* @__PURE__ */ defineComponent({
1591
- ...{ name: "MessageItem" },
1592
- __name: "MessageItem",
1593
- props: {
1594
- message: {}
1595
- },
1596
- setup(__props) {
1597
- const props = __props;
1598
- const { t, lang } = useText();
1599
- const { ctl, routes, opened, originals, allReplies, deleteMessage } = useDiscussion();
1600
- const m = computed(() => props.message);
1601
- const isReply = computed(() => m.value.replyToSeq !== null);
1602
- const own = computed(() => ctl.isOwn(m.value));
1603
- const placeholder = computed(() => m.value.body === null);
1604
- const untilConsent = computed(() => own.value && m.value.state === "held" && m.value.hiddenUntilConsent === true);
1605
- const canGiveConsent = computed(
1606
- () => untilConsent.value && ctl.mode() === "onboarding" && ctl.needs().art9 && !ctl.onboarding.value && ctl.state.refusedAge === null
1607
- );
1608
- const authorName = computed(() => {
1609
- const a = m.value.author;
1610
- if (a.kind === "system") return t("author.system");
1611
- if (a.kind === "staff") return a.displayName;
1612
- return a.displayName ?? t("author.deleted");
1613
- });
1614
- const handle = computed(() => m.value.author.kind === "member" ? m.value.author.handle : null);
1615
- const badge = computed(() => {
1616
- const a = m.value.author;
1617
- if (a.kind !== "staff") return null;
1618
- const key = `author.badge.${a.badge}`;
1619
- return hasCommunityMessage(key) ? t(key) : null;
1620
- });
1621
- const placeholderKey = computed(
1622
- () => m.value.state === "held" ? "discussion.placeholderHeld" : m.value.state === "removed" ? "discussion.placeholderRemoved" : "discussion.placeholderDeleted"
1623
- );
1624
- const hiddenByBlock = computed(() => m.value.collapsed && !opened.has(m.value.seq));
1625
- const showOriginal = computed(() => originals.has(m.value.seq));
1626
- const text = computed(() => (m.value.isTranslated && !showOriginal.value ? m.value.displayBody : m.value.body) ?? m.value.displayBody);
1627
- const notes = computed(
1628
- () => (own.value ? m.value.notes ?? [] : []).map((code) => noteMessageKey(code)).filter((k) => k !== null)
1629
- );
1630
- const warnings = computed(() => own.value ? [...new Set((m.value.warnings ?? []).map((code) => warningMessageKey(code)))] : []);
1631
- const editing = computed(() => ctl.state.inline?.kind === "edit" && ctl.state.inline.seq === m.value.seq);
1632
- const replying = computed(() => !isReply.value && ctl.state.inline?.kind === "reply" && ctl.state.inline.seq === m.value.seq);
1633
- const shownReplies = computed(() => allReplies.has(m.value.seq) ? m.value.replies : m.value.replies.slice(0, REPLIES_SHOWN));
1634
- const moreReplies = computed(() => m.value.replies.length - shownReplies.value.length);
1635
- const target = computed(() => ({ conversationId: m.value.conversationId, seq: m.value.seq }));
1636
- const menu = computed(() => {
1637
- const items = [];
1638
- const msg = m.value;
1639
- if (ctl.canEdit(msg)) items.push({ id: "edit", label: t("discussion.edit") });
1640
- if (ctl.canDelete(msg)) items.push({ id: "delete", label: t("discussion.delete"), danger: true });
1641
- if (ctl.canReport(msg)) items.push({ id: "report", label: t("discussion.report") });
1642
- else if (ctl.canReportAsNotifier(msg)) {
1643
- const href = reportLink(routes.value.reportPath, target.value);
1644
- if (href) items.push({ id: "report", label: t("discussion.report"), href });
1645
- }
1646
- if (ctl.canBlock(msg)) items.push({ id: msg.collapsed ? "unblock" : "block", label: msg.collapsed ? t("discussion.unblock") : t("discussion.block") });
1647
- return items;
1648
- });
1649
- function onMenu(id) {
1650
- const msg = m.value;
1651
- if (id === "edit") ctl.openEdit(msg.seq);
1652
- else if (id === "delete") deleteMessage(msg.seq);
1653
- else if (id === "report") ctl.openReport(msg);
1654
- else if ((id === "block" || id === "unblock") && msg.author.kind === "member" && msg.author.memberId) void ctl.setBlocked(msg.author.memberId, id === "block");
1655
- }
1656
- function reply() {
1657
- ctl.openReply(m.value.seq, isReply.value ? authorName.value : null);
1658
- }
1659
- function toggle(set) {
1660
- if (set.has(m.value.seq)) set.delete(m.value.seq);
1661
- else set.add(m.value.seq);
1662
- }
1663
- return (_ctx, _cache) => {
1664
- const _component_MessageItem = resolveComponent("MessageItem", true);
1665
- return openBlock(), createElementBlock("li", _hoisted_1$7, [
1666
- createElementVNode("article", {
1667
- class: normalizeClass(["bcm-msg", { "bcm-msg-pending": !!m.value.pending }]),
1668
- "aria-busy": !!m.value.pending
1669
- }, [
1670
- createVNode(_sfc_main$b, {
1671
- author: m.value.author
1672
- }, null, 8, ["author"]),
1673
- createElementVNode("div", _hoisted_3$7, [
1674
- createElementVNode("header", _hoisted_4$7, [
1675
- createElementVNode("span", _hoisted_5$7, toDisplayString(authorName.value), 1),
1676
- badge.value ? (openBlock(), createElementBlock("span", _hoisted_6$7, toDisplayString(badge.value), 1)) : createCommentVNode("", true),
1677
- handle.value ? (openBlock(), createElementBlock("span", _hoisted_7$7, toDisplayString(handle.value), 1)) : createCommentVNode("", true),
1678
- m.value.createdAt ? (openBlock(), createElementBlock("time", {
1679
- key: 2,
1680
- class: "bcm-time",
1681
- datetime: m.value.createdAt
1682
- }, toDisplayString(unref(formatDate)(unref(lang), m.value.createdAt)), 9, _hoisted_8$7)) : createCommentVNode("", true),
1683
- m.value.isEdited && !placeholder.value ? (openBlock(), createElementBlock("span", _hoisted_9$7, toDisplayString(unref(t)("discussion.edited")), 1)) : createCommentVNode("", true),
1684
- m.value.pending ? (openBlock(), createElementBlock("span", _hoisted_10$7, toDisplayString(unref(t)("discussion.pending")), 1)) : createCommentVNode("", true)
1685
- ]),
1686
- placeholder.value ? (openBlock(), createElementBlock("p", _hoisted_11$7, toDisplayString(unref(t)(placeholderKey.value)), 1)) : hiddenByBlock.value ? (openBlock(), createElementBlock("p", _hoisted_12$7, [
1687
- createTextVNode(toDisplayString(unref(t)("discussion.collapsed")) + " ", 1),
1688
- createElementVNode("button", {
1689
- type: "button",
1690
- class: "bcm-link",
1691
- onClick: _cache[0] || (_cache[0] = ($event) => toggle(unref(opened)))
1692
- }, toDisplayString(unref(t)("discussion.show")), 1)
1693
- ])) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
1694
- untilConsent.value ? (openBlock(), createElementBlock("div", _hoisted_13$7, [
1695
- createElementVNode("span", _hoisted_14$7, toDisplayString(unref(t)("state.hiddenUntilConsent")), 1),
1696
- createElementVNode("p", _hoisted_15$7, toDisplayString(unref(t)("discussion.hiddenUntilConsentNote")), 1),
1697
- canGiveConsent.value ? (openBlock(), createElementBlock("button", {
1698
- key: 0,
1699
- type: "button",
1700
- class: "bcm-link",
1701
- onClick: _cache[1] || (_cache[1] = ($event) => unref(ctl).beginOnboarding())
1702
- }, toDisplayString(unref(t)("discussion.giveConsent")), 1)) : createCommentVNode("", true)
1703
- ])) : own.value && (m.value.state === "held" || m.value.state === "removed") ? (openBlock(), createElementBlock("div", _hoisted_16$7, [
1704
- createElementVNode("span", _hoisted_17$7, toDisplayString(m.value.state === "held" ? unref(t)("state.held") : unref(t)("state.removed")), 1),
1705
- createElementVNode("p", _hoisted_18$7, toDisplayString(m.value.state === "held" ? unref(t)("discussion.heldNote") : unref(t)("discussion.removedNote")), 1),
1706
- m.value.state === "removed" ? (openBlock(), createElementBlock("a", {
1707
- key: 0,
1708
- href: unref(routes).myCommunityPath
1709
- }, toDisplayString(unref(t)("discussion.why")), 9, _hoisted_19$7)) : createCommentVNode("", true)
1710
- ])) : createCommentVNode("", true),
1711
- editing.value && unref(ctl).state.inline ? (openBlock(), createBlock(_sfc_main$c, {
1712
- key: 2,
1713
- kind: "edit",
1714
- draft: unref(ctl).state.inline,
1715
- wait: unref(ctl).waitLeft(unref(ctl).state.inline),
1716
- "rules-href": unref(routes).rulesPath,
1717
- onSubmit: _cache[2] || (_cache[2] = ($event) => unref(ctl).submitInline()),
1718
- onCancel: _cache[3] || (_cache[3] = ($event) => unref(ctl).closeInline()),
1719
- onConfirm: _cache[4] || (_cache[4] = (code, checked) => unref(ctl).state.inline && unref(ctl).setConfirmed(unref(ctl).state.inline, code, checked)),
1720
- onConfirmed: _cache[5] || (_cache[5] = ($event) => unref(ctl).state.inline && unref(ctl).sendConfirmed(unref(ctl).state.inline))
1721
- }, null, 8, ["draft", "wait", "rules-href"])) : (openBlock(), createElementBlock("p", _hoisted_20$7, [
1722
- createVNode(_sfc_main$a, { text: text.value }, null, 8, ["text"])
1723
- ])),
1724
- m.value.isTranslated && !editing.value ? (openBlock(), createElementBlock("p", _hoisted_21$6, [
1725
- createTextVNode(toDisplayString(unref(t)("discussion.translated")) + " ", 1),
1726
- createElementVNode("button", {
1727
- type: "button",
1728
- class: "bcm-link",
1729
- onClick: _cache[6] || (_cache[6] = ($event) => toggle(unref(originals)))
1730
- }, toDisplayString(showOriginal.value ? unref(t)("discussion.showTranslation") : unref(t)("discussion.showOriginal")), 1)
1731
- ])) : createCommentVNode("", true),
1732
- m.value.collapsed ? (openBlock(), createElementBlock("button", {
1733
- key: 5,
1734
- type: "button",
1735
- class: "bcm-link",
1736
- onClick: _cache[7] || (_cache[7] = ($event) => toggle(unref(opened)))
1737
- }, toDisplayString(unref(t)("discussion.hide")), 1)) : createCommentVNode("", true),
1738
- (openBlock(true), createElementBlock(Fragment, null, renderList(notes.value, (key) => {
1739
- return openBlock(), createElementBlock("p", {
1740
- key,
1741
- class: "bcm-note"
1742
- }, [
1743
- key === "note.report_to_manufacturer" && unref(routes).consultationPath ? (openBlock(), createElementBlock("a", {
1744
- key: 0,
1745
- href: unref(routes).consultationPath
1746
- }, toDisplayString(unref(t)(key)), 9, _hoisted_22$6)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
1747
- createTextVNode(toDisplayString(unref(t)(key)), 1)
1748
- ], 64))
1749
- ]);
1750
- }), 128)),
1751
- (openBlock(true), createElementBlock(Fragment, null, renderList(warnings.value, (key) => {
1752
- return openBlock(), createElementBlock("p", {
1753
- key,
1754
- class: "bcm-warning"
1755
- }, toDisplayString(unref(t)(key)), 1);
1756
- }), 128))
1757
- ], 64)),
1758
- !placeholder.value && !hiddenByBlock.value && !editing.value ? (openBlock(), createElementBlock("div", _hoisted_23$6, [
1759
- createVNode(_sfc_main$8, {
1760
- "reaction-set": unref(ctl).state.thread.reactionSet,
1761
- summary: m.value.reactions,
1762
- interactive: unref(ctl).canReact() && !m.value.pending && m.value.seq > 0 && m.value.state === "visible",
1763
- busy: !!unref(ctl).state.busy[`react:${m.value.seq}`],
1764
- onReact: _cache[8] || (_cache[8] = (emoji) => unref(ctl).react(target.value, emoji))
1765
- }, null, 8, ["reaction-set", "summary", "interactive", "busy"]),
1766
- unref(ctl).canReply(m.value) ? (openBlock(), createElementBlock("button", {
1767
- key: 0,
1768
- type: "button",
1769
- class: "bcm-quiet",
1770
- onClick: reply
1771
- }, toDisplayString(unref(t)("discussion.reply")), 1)) : createCommentVNode("", true),
1772
- createVNode(_sfc_main$9, {
1773
- items: menu.value,
1774
- onSelect: onMenu
1775
- }, null, 8, ["items"])
1776
- ])) : createCommentVNode("", true),
1777
- !isReply.value ? (openBlock(), createElementBlock(Fragment, { key: 4 }, [
1778
- m.value.replies.length ? (openBlock(), createElementBlock("ol", _hoisted_24$5, [
1779
- (openBlock(true), createElementBlock(Fragment, null, renderList(shownReplies.value, (r) => {
1780
- return openBlock(), createBlock(_component_MessageItem, {
1781
- key: r.id,
1782
- message: r
1783
- }, null, 8, ["message"]);
1784
- }), 128))
1785
- ])) : createCommentVNode("", true),
1786
- moreReplies.value > 0 ? (openBlock(), createElementBlock("button", {
1787
- key: 1,
1788
- type: "button",
1789
- class: "bcm-link",
1790
- onClick: _cache[9] || (_cache[9] = ($event) => unref(allReplies).add(m.value.seq))
1791
- }, toDisplayString(unref(t)("discussion.showReplies", { count: moreReplies.value })), 1)) : createCommentVNode("", true),
1792
- m.value.repliesTruncated ? (openBlock(), createElementBlock("p", _hoisted_25$4, toDisplayString(unref(t)("discussion.repliesTruncated")), 1)) : createCommentVNode("", true),
1793
- replying.value && unref(ctl).state.inline ? (openBlock(), createBlock(_sfc_main$c, {
1794
- key: 3,
1795
- kind: "reply",
1796
- draft: unref(ctl).state.inline,
1797
- wait: unref(ctl).waitLeft(unref(ctl).state.inline),
1798
- "rules-href": unref(routes).rulesPath,
1799
- "replying-to": unref(ctl).state.inline.replyingTo,
1800
- onSubmit: _cache[10] || (_cache[10] = ($event) => unref(ctl).submitInline()),
1801
- onCancel: _cache[11] || (_cache[11] = ($event) => unref(ctl).closeInline()),
1802
- onConfirm: _cache[12] || (_cache[12] = (code, checked) => unref(ctl).state.inline && unref(ctl).setConfirmed(unref(ctl).state.inline, code, checked)),
1803
- onConfirmed: _cache[13] || (_cache[13] = ($event) => unref(ctl).state.inline && unref(ctl).sendConfirmed(unref(ctl).state.inline))
1804
- }, null, 8, ["draft", "wait", "rules-href", "replying-to"])) : createCommentVNode("", true)
1805
- ], 64)) : createCommentVNode("", true)
1806
- ])
1807
- ], 10, _hoisted_2$7)
1808
- ]);
1809
- };
1810
- }
1811
- });
1812
- const _hoisted_1$6 = ["aria-labelledby"];
1813
- const _hoisted_2$6 = ["id"];
1814
- const _hoisted_3$6 = {
1815
- key: 0,
1816
- class: "bcm-muted",
1817
- role: "status"
1818
- };
1819
- const _hoisted_4$6 = {
1820
- class: "bcm-error",
1821
- role: "alert"
1822
- };
1823
- const _hoisted_5$6 = { class: "bcm-row" };
1824
- const _hoisted_6$6 = { role: "alert" };
1825
- const _hoisted_7$6 = { class: "bcm-row" };
1826
- const _hoisted_8$6 = {
1827
- key: 0,
1828
- class: "bcm-hint"
1829
- };
1830
- const _hoisted_9$6 = {
1831
- key: 1,
1832
- class: "bcm-choices"
1833
- };
1834
- const _hoisted_10$6 = ["name", "value"];
1835
- const _hoisted_11$6 = { class: "bcm-hint bcm-full" };
1836
- const _hoisted_12$6 = {
1837
- key: 2,
1838
- class: "bcm-section"
1839
- };
1840
- const _hoisted_13$6 = {
1841
- key: 0,
1842
- class: "bcm-rules"
1843
- };
1844
- const _hoisted_14$6 = {
1845
- key: 0,
1846
- class: "bcm-hint"
1847
- };
1848
- const _hoisted_15$6 = ["href"];
1849
- const _hoisted_16$6 = { class: "bcm-check" };
1850
- const _hoisted_17$6 = {
1851
- key: 3,
1852
- class: "bcm-section"
1853
- };
1854
- const _hoisted_18$6 = ["aria-label"];
1855
- const _hoisted_19$6 = { class: "bcm-check" };
1856
- const _hoisted_20$6 = {
1857
- key: 4,
1858
- class: "bcm-section"
1859
- };
1860
- const _hoisted_21$5 = { class: "bcm-field" };
1861
- const _hoisted_22$5 = ["for"];
1862
- const _hoisted_23$5 = ["id", "maxlength", "aria-invalid", "aria-describedby"];
1863
- const _hoisted_24$4 = ["id"];
1864
- const _hoisted_25$3 = { class: "bcm-field" };
1865
- const _hoisted_26$3 = ["for"];
1866
- const _hoisted_27$2 = ["id", "maxlength", "placeholder", "aria-invalid", "aria-describedby"];
1867
- const _hoisted_28$2 = ["id"];
1868
- const _hoisted_29$2 = {
1869
- key: 0,
1870
- class: "bcm-hint"
1871
- };
1872
- const _hoisted_30$2 = { class: "bcm-choices" };
1873
- const _hoisted_31$2 = { class: "bcm-choice" };
1874
- const _hoisted_32$2 = ["name"];
1875
- const _hoisted_33$2 = ["name", "value"];
1876
- const _hoisted_34$2 = {
1877
- key: 5,
1878
- class: "bcm-error",
1879
- role: "alert"
1880
- };
1881
- const _hoisted_35$2 = { class: "bcm-row" };
1882
- const _hoisted_36$2 = ["disabled"];
1883
- const _hoisted_37$1 = ["disabled"];
1884
- const _hoisted_38$1 = ["disabled"];
1885
- const _sfc_main$6 = /* @__PURE__ */ defineComponent({
1886
- __name: "OnboardingSteps",
1887
- props: {
1888
- model: {},
1889
- rulesHref: {}
1890
- },
1891
- emits: ["cancel"],
1892
- setup(__props, { emit: __emit }) {
1893
- const props = __props;
1894
- const emit = __emit;
1895
- const { t, lang } = useText();
1896
- const s = props.model.state;
1897
- const id = uid("bcm-onboarding");
1898
- const heading = ref(null);
1899
- const step = computed(() => props.model.step());
1900
- const fresh = computed(() => s.steps.includes("profile"));
1901
- const failure = computed(() => {
1902
- const f = s.failure;
1903
- if (!f) return "";
1904
- const ref2 = failureText(f, lang.value);
1905
- return t(ref2.key, ref2.params);
1906
- });
1907
- const ageKey = (band) => `onboarding.age.${band}`;
1908
- watch(
1909
- () => [s.index, s.phase, s.steps.join()],
1910
- ([index, phase, steps], [indexBefore, , stepsBefore]) => {
1911
- if (index !== indexBefore || steps !== stepsBefore || phase === "refused" || phase === "failed") void nextTick(() => heading.value?.focus());
1912
- }
1913
- );
1914
- onMounted(() => void nextTick(() => heading.value?.focus()));
1915
- return (_ctx, _cache) => {
1916
- return openBlock(), createElementBlock("section", {
1917
- class: "bcm-card",
1918
- "aria-labelledby": `${unref(id)}-title`
1919
- }, [
1920
- createElementVNode("h3", {
1921
- id: `${unref(id)}-title`,
1922
- ref_key: "heading",
1923
- ref: heading,
1924
- tabindex: "-1"
1925
- }, toDisplayString(unref(t)("onboarding.title")), 9, _hoisted_2$6),
1926
- unref(s).phase === "loading" ? (openBlock(), createElementBlock("p", _hoisted_3$6, toDisplayString(unref(t)("common.loading")), 1)) : unref(s).phase === "failed" ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
1927
- createElementVNode("p", _hoisted_4$6, toDisplayString(failure.value), 1),
1928
- createElementVNode("div", _hoisted_5$6, [
1929
- createElementVNode("button", {
1930
- type: "button",
1931
- class: "bcm-btn",
1932
- onClick: _cache[0] || (_cache[0] = ($event) => emit("cancel"))
1933
- }, toDisplayString(unref(t)("common.cancel")), 1),
1934
- createElementVNode("button", {
1935
- type: "button",
1936
- class: "bcm-btn bcm-primary",
1937
- onClick: _cache[1] || (_cache[1] = ($event) => __props.model.load())
1938
- }, toDisplayString(unref(t)("common.retry")), 1)
1939
- ])
1940
- ], 64)) : unref(s).phase === "refused" ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
1941
- createElementVNode("p", _hoisted_6$6, toDisplayString(unref(t)("onboarding.ageBelow", { min: unref(s).minAge ?? 18 })), 1),
1942
- createElementVNode("div", _hoisted_7$6, [
1943
- createElementVNode("button", {
1944
- type: "button",
1945
- class: "bcm-btn",
1946
- onClick: _cache[2] || (_cache[2] = ($event) => emit("cancel"))
1947
- }, toDisplayString(unref(t)("common.close")), 1)
1948
- ])
1949
- ], 64)) : (openBlock(), createElementBlock("form", {
1950
- key: 3,
1951
- class: "bcm-form",
1952
- novalidate: "",
1953
- onSubmit: _cache[12] || (_cache[12] = withModifiers(($event) => __props.model.next(), ["prevent"]))
1954
- }, [
1955
- unref(s).index === 0 && fresh.value ? (openBlock(), createElementBlock("p", _hoisted_8$6, toDisplayString(unref(t)("onboarding.lede")), 1)) : createCommentVNode("", true),
1956
- step.value === "age" ? (openBlock(), createElementBlock("fieldset", _hoisted_9$6, [
1957
- createElementVNode("legend", null, toDisplayString(unref(t)("onboarding.ageTitle")), 1),
1958
- (openBlock(true), createElementBlock(Fragment, null, renderList(unref(AGE_BANDS), (band) => {
1959
- return openBlock(), createElementBlock("label", {
1960
- key: band,
1961
- class: "bcm-choice"
1962
- }, [
1963
- withDirectives(createElementVNode("input", {
1964
- "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => unref(s).ageBand = $event),
1965
- type: "radio",
1966
- name: `${unref(id)}-age`,
1967
- value: band
1968
- }, null, 8, _hoisted_10$6), [
1969
- [vModelRadio, unref(s).ageBand]
1970
- ]),
1971
- createElementVNode("span", null, toDisplayString(unref(t)(ageKey(band))), 1)
1972
- ]);
1973
- }), 128)),
1974
- createElementVNode("p", _hoisted_11$6, toDisplayString(unref(t)("onboarding.ageHint")), 1)
1975
- ])) : step.value === "rules" ? (openBlock(), createElementBlock("div", _hoisted_12$6, [
1976
- createElementVNode("h4", null, toDisplayString(unref(t)("onboarding.rulesTitle")), 1),
1977
- createElementVNode("p", null, toDisplayString(unref(s).reconsent ? unref(t)("onboarding.rulesChanged") : unref(t)("onboarding.rulesLede")), 1),
1978
- unref(s).rules.length ? (openBlock(), createElementBlock("ol", _hoisted_13$6, [
1979
- (openBlock(true), createElementBlock(Fragment, null, renderList(unref(s).rules, (rule) => {
1980
- return openBlock(), createElementBlock("li", {
1981
- key: rule.id
1982
- }, [
1983
- createElementVNode("strong", null, toDisplayString(rule.title), 1),
1984
- rule.summary ? (openBlock(), createElementBlock("span", _hoisted_14$6, toDisplayString(rule.summary), 1)) : createCommentVNode("", true)
1985
- ]);
1986
- }), 128))
1987
- ])) : createCommentVNode("", true),
1988
- createElementVNode("p", null, [
1989
- createElementVNode("a", {
1990
- href: __props.rulesHref,
1991
- target: "_blank",
1992
- rel: "noopener"
1993
- }, toDisplayString(unref(t)("onboarding.rulesLink")), 9, _hoisted_15$6)
1994
- ]),
1995
- createElementVNode("label", _hoisted_16$6, [
1996
- withDirectives(createElementVNode("input", {
1997
- "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => unref(s).rulesAccepted = $event),
1998
- type: "checkbox"
1999
- }, null, 512), [
2000
- [vModelCheckbox, unref(s).rulesAccepted]
2001
- ]),
2002
- createElementVNode("span", null, toDisplayString(unref(t)("onboarding.rulesAccept")), 1)
2003
- ])
2004
- ])) : step.value === "art9" ? (openBlock(), createElementBlock("div", _hoisted_17$6, [
2005
- createElementVNode("h4", null, toDisplayString(unref(t)("onboarding.art9Title")), 1),
2006
- createElementVNode("p", null, toDisplayString(unref(t)("onboarding.art9Lede")), 1),
2007
- createElementVNode("div", {
2008
- class: "bcm-consent",
2009
- tabindex: "0",
2010
- role: "document",
2011
- "aria-label": unref(t)("onboarding.art9Title")
2012
- }, [
2013
- (openBlock(true), createElementBlock(Fragment, null, renderList(unref(s).art9Paragraphs, (paragraph, i) => {
2014
- return openBlock(), createElementBlock("p", {
2015
- key: i,
2016
- class: "bcm-text"
2017
- }, toDisplayString(paragraph), 1);
2018
- }), 128))
2019
- ], 8, _hoisted_18$6),
2020
- createElementVNode("label", _hoisted_19$6, [
2021
- withDirectives(createElementVNode("input", {
2022
- "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => unref(s).art9Accepted = $event),
2023
- type: "checkbox"
2024
- }, null, 512), [
2025
- [vModelCheckbox, unref(s).art9Accepted]
2026
- ]),
2027
- createElementVNode("span", null, toDisplayString(unref(t)("onboarding.art9Accept")), 1)
2028
- ])
2029
- ])) : step.value === "profile" ? (openBlock(), createElementBlock("div", _hoisted_20$6, [
2030
- createElementVNode("h4", null, toDisplayString(unref(t)("onboarding.profileTitle")), 1),
2031
- createElementVNode("div", _hoisted_21$5, [
2032
- createElementVNode("label", {
2033
- for: `${unref(id)}-name`
2034
- }, toDisplayString(unref(t)("onboarding.displayName")), 9, _hoisted_22$5),
2035
- withDirectives(createElementVNode("input", {
2036
- id: `${unref(id)}-name`,
2037
- "onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => unref(s).displayName = $event),
2038
- class: "bcm-input",
2039
- autocomplete: "nickname",
2040
- maxlength: unref(COMMUNITY_DISPLAY_NAME_MAX) * 2,
2041
- "aria-invalid": __props.model.hasFieldError("displayName"),
2042
- "aria-describedby": `${unref(id)}-name-hint`
2043
- }, null, 8, _hoisted_23$5), [
2044
- [vModelText, unref(s).displayName]
2045
- ]),
2046
- createElementVNode("p", {
2047
- id: `${unref(id)}-name-hint`,
2048
- class: "bcm-hint"
2049
- }, toDisplayString(unref(t)("onboarding.displayNameHint")), 9, _hoisted_24$4)
2050
- ]),
2051
- createElementVNode("div", _hoisted_25$3, [
2052
- createElementVNode("label", {
2053
- for: `${unref(id)}-handle`
2054
- }, toDisplayString(unref(t)("onboarding.handle")), 9, _hoisted_26$3),
2055
- withDirectives(createElementVNode("input", {
2056
- id: `${unref(id)}-handle`,
2057
- "onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => unref(s).handle = $event),
2058
- class: "bcm-input",
2059
- autocomplete: "username",
2060
- autocapitalize: "none",
2061
- spellcheck: "false",
2062
- maxlength: unref(HANDLE_MAX),
2063
- placeholder: __props.model.suggestedHandle() ?? "",
2064
- "aria-invalid": __props.model.hasFieldError("handle"),
2065
- "aria-describedby": `${unref(id)}-handle-hint`
2066
- }, null, 8, _hoisted_27$2), [
2067
- [vModelText, unref(s).handle]
2068
- ]),
2069
- createElementVNode("p", {
2070
- id: `${unref(id)}-handle-hint`,
2071
- class: "bcm-hint"
2072
- }, toDisplayString(unref(t)("onboarding.handleHint", { min: unref(HANDLE_MIN), max: unref(HANDLE_MAX), days: unref(COMMUNITY_HANDLE_CHANGE_DAYS) })), 9, _hoisted_28$2),
2073
- __props.model.suggestedHandle() ? (openBlock(), createElementBlock("p", _hoisted_29$2, toDisplayString(unref(t)("onboarding.handleSuggested", { handle: __props.model.suggestedHandle() ?? "" })), 1)) : createCommentVNode("", true)
2074
- ]),
2075
- createElementVNode("fieldset", _hoisted_30$2, [
2076
- createElementVNode("legend", null, toDisplayString(unref(t)("onboarding.avatar")), 1),
2077
- createElementVNode("label", _hoisted_31$2, [
2078
- withDirectives(createElementVNode("input", {
2079
- "onUpdate:modelValue": _cache[8] || (_cache[8] = ($event) => unref(s).avatarPreset = $event),
2080
- type: "radio",
2081
- name: `${unref(id)}-avatar`,
2082
- value: null
2083
- }, null, 8, _hoisted_32$2), [
2084
- [vModelRadio, unref(s).avatarPreset]
2085
- ]),
2086
- createElementVNode("span", null, toDisplayString(unref(t)("onboarding.avatarNone")), 1)
2087
- ]),
2088
- (openBlock(true), createElementBlock(Fragment, null, renderList(unref(AVATAR_PRESETS), (preset) => {
2089
- return openBlock(), createElementBlock("label", {
2090
- key: preset.id,
2091
- class: "bcm-choice"
2092
- }, [
2093
- withDirectives(createElementVNode("input", {
2094
- "onUpdate:modelValue": _cache[9] || (_cache[9] = ($event) => unref(s).avatarPreset = $event),
2095
- type: "radio",
2096
- name: `${unref(id)}-avatar`,
2097
- value: preset.id
2098
- }, null, 8, _hoisted_33$2), [
2099
- [vModelRadio, unref(s).avatarPreset]
2100
- ]),
2101
- createElementVNode("span", null, toDisplayString(preset.emoji), 1)
2102
- ]);
2103
- }), 128))
2104
- ])
2105
- ])) : createCommentVNode("", true),
2106
- failure.value ? (openBlock(), createElementBlock("p", _hoisted_34$2, toDisplayString(failure.value), 1)) : createCommentVNode("", true),
2107
- createElementVNode("div", _hoisted_35$2, [
2108
- unref(s).index > 0 ? (openBlock(), createElementBlock("button", {
2109
- key: 0,
2110
- type: "button",
2111
- class: "bcm-btn",
2112
- disabled: unref(s).phase === "sending",
2113
- onClick: _cache[10] || (_cache[10] = ($event) => __props.model.back())
2114
- }, toDisplayString(unref(t)("onboarding.back")), 9, _hoisted_36$2)) : createCommentVNode("", true),
2115
- createElementVNode("button", {
2116
- type: "button",
2117
- class: "bcm-btn",
2118
- disabled: unref(s).phase === "sending",
2119
- onClick: _cache[11] || (_cache[11] = ($event) => emit("cancel"))
2120
- }, toDisplayString(unref(t)("common.cancel")), 9, _hoisted_37$1),
2121
- _cache[13] || (_cache[13] = createElementVNode("span", { class: "bcm-spacer" }, null, -1)),
2122
- createElementVNode("button", {
2123
- type: "submit",
2124
- class: "bcm-btn bcm-primary",
2125
- disabled: !__props.model.canContinue()
2126
- }, toDisplayString(unref(s).phase === "sending" ? unref(t)("composer.sending") : __props.model.isLast() ? unref(t)("onboarding.submit") : unref(t)("onboarding.continue")), 9, _hoisted_38$1)
2127
- ])
2128
- ], 32))
2129
- ], 8, _hoisted_1$6);
2130
- };
2131
- }
2132
- });
2133
- const _hoisted_1$5 = ["aria-labelledby"];
2134
- const _hoisted_2$5 = ["id"];
2135
- const _hoisted_3$5 = { class: "bcm-hint" };
2136
- const _hoisted_4$5 = { key: 0 };
2137
- const _hoisted_5$5 = ["href"];
2138
- const _hoisted_6$5 = { class: "bcm-choices" };
2139
- const _hoisted_7$5 = ["name", "value"];
2140
- const _hoisted_8$5 = {
2141
- key: 1,
2142
- class: "bcm-field"
2143
- };
2144
- const _hoisted_9$5 = ["for"];
2145
- const _hoisted_10$5 = {
2146
- key: 0,
2147
- class: "bcm-error"
2148
- };
2149
- const _hoisted_11$5 = {
2150
- key: 1,
2151
- class: "bcm-muted",
2152
- role: "status"
2153
- };
2154
- const _hoisted_12$5 = ["id"];
2155
- const _hoisted_13$5 = ["value"];
2156
- const _hoisted_14$5 = { class: "bcm-field" };
2157
- const _hoisted_15$5 = ["for"];
2158
- const _hoisted_16$5 = ["id", "aria-describedby", "aria-invalid"];
2159
- const _hoisted_17$5 = ["id"];
2160
- const _hoisted_18$5 = {
2161
- key: 2,
2162
- class: "bcm-error",
2163
- role: "alert"
2164
- };
2165
- const _hoisted_19$5 = { class: "bcm-row" };
2166
- const _hoisted_20$5 = ["disabled"];
2167
- const _hoisted_21$4 = ["disabled"];
2168
- const _hoisted_22$4 = { role: "status" };
2169
- const _hoisted_23$4 = ["href"];
2170
- const _hoisted_24$3 = { class: "bcm-row" };
2171
- const _sfc_main$5 = /* @__PURE__ */ defineComponent({
2172
- __name: "ReportDialog",
2173
- props: {
2174
- report: {},
2175
- myCommunityHref: {},
2176
- rulesHref: {},
2177
- fallbackFocus: { type: Function }
2178
- },
2179
- emits: ["submit", "close"],
2180
- setup(__props, { emit: __emit }) {
2181
- const props = __props;
2182
- const emit = __emit;
2183
- const { t, lang } = useText();
2184
- const dialog = ref(null);
2185
- const id = uid("bcm-report");
2186
- useModal(dialog, () => emit("close"), props.fallbackFocus);
2187
- const categoryKey = (c) => `category.${c}`;
2188
- const noteLength = computed(() => codePointLength(normalizeBody(props.report.note)));
2189
- const canSend = computed(
2190
- () => !props.report.sending && props.report.category !== "" && (props.report.category !== "rules" || props.report.ruleId !== "") && noteLength.value <= COMMUNITY_REPORT_NOTE_MAX
2191
- );
2192
- const failure = computed(() => {
2193
- const f = props.report.failure;
2194
- if (!f) return "";
2195
- const ref2 = failureText(f, lang.value);
2196
- return t(ref2.key, ref2.params);
2197
- });
2198
- return (_ctx, _cache) => {
2199
- return openBlock(), createElementBlock("dialog", {
2200
- ref_key: "dialog",
2201
- ref: dialog,
2202
- class: "bcm-dialog",
2203
- "aria-labelledby": `${unref(id)}-title`
2204
- }, [
2205
- createElementVNode("form", {
2206
- class: "bcm-dialog-body",
2207
- novalidate: "",
2208
- onSubmit: _cache[5] || (_cache[5] = withModifiers(($event) => canSend.value && emit("submit"), ["prevent"]))
2209
- }, [
2210
- createElementVNode("h3", {
2211
- id: `${unref(id)}-title`
2212
- }, toDisplayString(unref(t)("report.title")), 9, _hoisted_2$5),
2213
- !__props.report.done ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
2214
- createElementVNode("p", _hoisted_3$5, toDisplayString(unref(t)("report.lede")), 1),
2215
- __props.rulesHref ? (openBlock(), createElementBlock("p", _hoisted_4$5, [
2216
- createElementVNode("a", {
2217
- href: __props.rulesHref,
2218
- target: "_blank",
2219
- rel: "noopener"
2220
- }, toDisplayString(unref(t)("onboarding.rulesTitle")), 9, _hoisted_5$5)
2221
- ])) : createCommentVNode("", true),
2222
- createElementVNode("fieldset", _hoisted_6$5, [
2223
- createElementVNode("legend", null, toDisplayString(unref(t)("report.category")), 1),
2224
- (openBlock(true), createElementBlock(Fragment, null, renderList(unref(CASE_CATEGORIES), (c) => {
2225
- return openBlock(), createElementBlock("label", {
2226
- key: c,
2227
- class: "bcm-choice"
2228
- }, [
2229
- withDirectives(createElementVNode("input", {
2230
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => __props.report.category = $event),
2231
- type: "radio",
2232
- name: `${unref(id)}-category`,
2233
- value: c
2234
- }, null, 8, _hoisted_7$5), [
2235
- [vModelRadio, __props.report.category]
2236
- ]),
2237
- createElementVNode("span", null, toDisplayString(unref(t)(categoryKey(c))), 1)
2238
- ]);
2239
- }), 128))
2240
- ]),
2241
- __props.report.category === "rules" ? (openBlock(), createElementBlock("div", _hoisted_8$5, [
2242
- createElementVNode("label", {
2243
- for: `${unref(id)}-rule`
2244
- }, toDisplayString(unref(t)("report.rule")), 9, _hoisted_9$5),
2245
- __props.report.rulesFailed ? (openBlock(), createElementBlock("p", _hoisted_10$5, toDisplayString(unref(t)("error.unavailable")), 1)) : __props.report.rules === null ? (openBlock(), createElementBlock("p", _hoisted_11$5, toDisplayString(unref(t)("common.loading")), 1)) : withDirectives((openBlock(), createElementBlock("select", {
2246
- key: 2,
2247
- id: `${unref(id)}-rule`,
2248
- "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => __props.report.ruleId = $event),
2249
- class: "bcm-input",
2250
- required: ""
2251
- }, [
2252
- _cache[6] || (_cache[6] = createElementVNode("option", {
2253
- value: "",
2254
- disabled: ""
2255
- }, null, -1)),
2256
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.report.rules, (rule) => {
2257
- return openBlock(), createElementBlock("option", {
2258
- key: rule.id,
2259
- value: rule.id
2260
- }, toDisplayString(rule.title), 9, _hoisted_13$5);
2261
- }), 128))
2262
- ], 8, _hoisted_12$5)), [
2263
- [vModelSelect, __props.report.ruleId]
2264
- ])
2265
- ])) : createCommentVNode("", true),
2266
- createElementVNode("div", _hoisted_14$5, [
2267
- createElementVNode("label", {
2268
- for: `${unref(id)}-note`
2269
- }, toDisplayString(unref(t)("report.note")), 9, _hoisted_15$5),
2270
- withDirectives(createElementVNode("textarea", {
2271
- id: `${unref(id)}-note`,
2272
- "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => __props.report.note = $event),
2273
- class: "bcm-input bcm-textarea",
2274
- rows: "3",
2275
- "aria-describedby": `${unref(id)}-note-hint`,
2276
- "aria-invalid": noteLength.value > unref(COMMUNITY_REPORT_NOTE_MAX)
2277
- }, null, 8, _hoisted_16$5), [
2278
- [vModelText, __props.report.note]
2279
- ]),
2280
- createElementVNode("p", {
2281
- id: `${unref(id)}-note-hint`,
2282
- class: "bcm-hint"
2283
- }, toDisplayString(unref(t)("report.noteHint")) + " " + toDisplayString(unref(t)("report.counter", { length: noteLength.value, max: unref(COMMUNITY_REPORT_NOTE_MAX) })), 9, _hoisted_17$5)
2284
- ]),
2285
- failure.value ? (openBlock(), createElementBlock("p", _hoisted_18$5, toDisplayString(failure.value), 1)) : createCommentVNode("", true),
2286
- createElementVNode("div", _hoisted_19$5, [
2287
- _cache[7] || (_cache[7] = createElementVNode("span", { class: "bcm-spacer" }, null, -1)),
2288
- createElementVNode("button", {
2289
- type: "button",
2290
- class: "bcm-btn",
2291
- disabled: __props.report.sending,
2292
- onClick: _cache[3] || (_cache[3] = ($event) => emit("close"))
2293
- }, toDisplayString(unref(t)("report.cancel")), 9, _hoisted_20$5),
2294
- createElementVNode("button", {
2295
- type: "submit",
2296
- class: "bcm-btn bcm-primary",
2297
- disabled: !canSend.value
2298
- }, toDisplayString(__props.report.sending ? unref(t)("report.sending") : unref(t)("report.submit")), 9, _hoisted_21$4)
2299
- ])
2300
- ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
2301
- createElementVNode("p", _hoisted_22$4, toDisplayString(unref(t)("report.done")), 1),
2302
- createElementVNode("p", null, [
2303
- createElementVNode("a", { href: __props.myCommunityHref }, toDisplayString(unref(t)("panel.title")), 9, _hoisted_23$4)
2304
- ]),
2305
- createElementVNode("div", _hoisted_24$3, [
2306
- _cache[8] || (_cache[8] = createElementVNode("span", { class: "bcm-spacer" }, null, -1)),
2307
- createElementVNode("button", {
2308
- type: "button",
2309
- class: "bcm-btn bcm-primary",
2310
- onClick: _cache[4] || (_cache[4] = ($event) => emit("close"))
2311
- }, toDisplayString(unref(t)("common.close")), 1)
2312
- ])
2313
- ], 64))
2314
- ], 32)
2315
- ], 8, _hoisted_1$5);
2316
- };
2317
- }
2318
- });
2319
- const _hoisted_1$4 = ["lang", "aria-labelledby"];
2320
- const _hoisted_2$4 = { class: "bcm-head" };
2321
- const _hoisted_3$4 = ["id"];
2322
- const _hoisted_4$4 = {
2323
- key: 0,
2324
- class: "bcm-count"
2325
- };
2326
- const _hoisted_5$4 = {
2327
- key: 0,
2328
- class: "bcm-muted",
2329
- role: "status"
2330
- };
2331
- const _hoisted_6$4 = {
2332
- key: 1,
2333
- class: "bcm-banner",
2334
- role: "alert"
2335
- };
2336
- const _hoisted_7$4 = {
2337
- key: 0,
2338
- class: "bcm-banner",
2339
- role: "status"
2340
- };
2341
- const _hoisted_8$4 = ["disabled"];
2342
- const _hoisted_9$4 = {
2343
- key: 1,
2344
- class: "bcm-muted"
2345
- };
2346
- const _hoisted_10$4 = {
2347
- key: 2,
2348
- class: "bcm-thread"
2349
- };
2350
- const _hoisted_11$4 = {
2351
- key: 3,
2352
- class: "bcm-row"
2353
- };
2354
- const _hoisted_12$4 = ["disabled"];
2355
- const _hoisted_13$4 = { class: "bcm-section" };
2356
- const _hoisted_14$4 = {
2357
- key: 1,
2358
- class: "bcm-row"
2359
- };
2360
- const _hoisted_15$4 = ["href"];
2361
- const _hoisted_16$4 = {
2362
- key: 1,
2363
- class: "bcm-row"
2364
- };
2365
- const _hoisted_17$4 = {
2366
- key: 3,
2367
- class: "bcm-note"
2368
- };
2369
- const _hoisted_18$4 = {
2370
- key: 4,
2371
- class: "bcm-section"
2372
- };
2373
- const _hoisted_19$4 = ["href"];
2374
- const _hoisted_20$4 = {
2375
- key: 5,
2376
- class: "bcm-note"
2377
- };
2378
- const _hoisted_21$3 = {
2379
- key: 6,
2380
- class: "bcm-note"
2381
- };
2382
- const _hoisted_22$3 = ["href"];
2383
- const _hoisted_23$3 = {
2384
- class: "bcm-sr",
2385
- "aria-live": "polite"
2386
- };
2387
- const refusedAges = /* @__PURE__ */ new WeakMap();
2388
- const _sfc_main$4 = /* @__PURE__ */ defineComponent({
2389
- ...{ inheritAttrs: false },
2390
- __name: "DiscussionView",
2391
- props: {
2392
- articleId: {},
2393
- apiBase: {},
2394
- siteBase: {},
2395
- lang: {},
2396
- signInHref: {},
2397
- routes: {},
2398
- pollMs: {},
2399
- transport: { type: Function }
2400
- },
2401
- setup(__props) {
2402
- const props = __props;
2403
- const host = useHost();
2404
- const lang = ref(pickLang(props.lang, documentLang()));
2405
- const { t, text } = provideText(lang);
2406
- watch(
2407
- () => props.lang,
2408
- (v) => {
2409
- lang.value = pickLang(v, documentLang());
2410
- }
2411
- );
2412
- const headingId = uid("bcm-discussion");
2413
- const heading = ref(null);
2414
- const generation = ref(0);
2415
- const current = shallowRef(null);
2416
- const siteBase = computed(() => siteBaseFor(props.siteBase));
2417
- function create() {
2418
- const articleId = props.articleId?.trim();
2419
- if (!articleId) {
2420
- warnOnce("article-id", "[blogmcp-community] <blogmcp-discussion> needs an article-id");
2421
- return null;
2422
- }
2423
- if (siteBase.value === null) return null;
2424
- const client = communityClientFor(props.apiBase, props.transport, siteBase.value);
2425
- if (!client) return null;
2426
- return createDiscussionController({
2427
- client,
2428
- articleId,
2429
- lang: lang.value,
2430
- pollMs: props.pollMs,
2431
- refusedAge: (host && refusedAges.get(host)) ?? null,
2432
- onAgeRefused: (minAge) => {
2433
- if (host) refusedAges.set(host, minAge);
2434
- },
2435
- onPosted: (state2) => host?.dispatchEvent(new CustomEvent("bcm-posted", { detail: { state: state2 }, bubbles: true, composed: true }))
2436
- });
2437
- }
2438
- const opened = reactive(/* @__PURE__ */ new Set());
2439
- const originals = reactive(/* @__PURE__ */ new Set());
2440
- const allReplies = reactive(/* @__PURE__ */ new Set());
2441
- const deleting = ref(null);
2442
- const parsedRoutes = computed(() => parseRoutes(props.routes));
2443
- const routes = computed(() => routesWithDefaults(parsedRoutes.value, current.value?.state.me?.links, siteBase.value ?? ""));
2444
- provideDiscussion({
2445
- get ctl() {
2446
- return current.value;
2447
- },
2448
- routes,
2449
- opened,
2450
- originals,
2451
- allReplies,
2452
- focusHeading: () => heading.value,
2453
- deleteMessage: (seq) => {
2454
- deleting.value = seq;
2455
- }
2456
- });
2457
- let observer = null;
2458
- function start() {
2459
- current.value?.dispose();
2460
- opened.clear();
2461
- originals.clear();
2462
- allReplies.clear();
2463
- deleting.value = null;
2464
- current.value = create();
2465
- generation.value += 1;
2466
- void current.value?.start();
2467
- }
2468
- onMounted(() => {
2469
- start();
2470
- if (host && typeof IntersectionObserver !== "undefined") {
2471
- observer = new IntersectionObserver((entries) => {
2472
- const e = entries[entries.length - 1];
2473
- if (e) current.value?.setInView(e.isIntersecting);
2474
- }, { rootMargin: "200px" });
2475
- observer.observe(host);
2476
- }
2477
- });
2478
- watch(() => [props.articleId, props.apiBase, siteBase.value, props.transport, props.pollMs, lang.value], () => start());
2479
- onBeforeUnmount(() => {
2480
- observer?.disconnect();
2481
- current.value?.dispose();
2482
- current.value = null;
2483
- });
2484
- const ctl = computed(() => current.value);
2485
- const state = computed(() => current.value?.state ?? null);
2486
- const mode = computed(() => current.value?.mode() ?? "pending");
2487
- const composer = ref(null);
2488
- watch(mode, (now, before) => {
2489
- if (now === "write" && before === "onboarding") void nextTick(() => composer.value?.focus());
2490
- if (now === "ageRefused" && before === "onboarding") void nextTick(() => heading.value?.focus());
2491
- });
2492
- const needKey = computed(() => {
2493
- const n = current.value?.needs();
2494
- return n?.onboarding ? "need.onboarding" : n?.rules ? "need.reconsent_rules" : "need.art9_consent";
2495
- });
2496
- function signInHref() {
2497
- return signInLink(props.signInHref, typeof location === "undefined" ? null : location.pathname);
2498
- }
2499
- function sanctionText(r) {
2500
- const date = r.endsAt ? formatDate(lang.value, r.endsAt) : "";
2501
- if (r.kind === "ban") return t("sanction.ban");
2502
- if (r.kind === "suspension") return date ? t("sanction.suspensionUntil", { date }) : t("sanction.suspension");
2503
- return date ? t("sanction.restrictionUntil", { date }) : t("sanction.restriction");
2504
- }
2505
- const loadFailure = computed(() => {
2506
- const f = state.value?.failure;
2507
- if (!f) return "";
2508
- const r = failureText(f, lang.value);
2509
- return t(r.key, r.params);
2510
- });
2511
- const liveText = ref("");
2512
- watch(
2513
- () => state.value?.announceId,
2514
- () => {
2515
- liveText.value = "";
2516
- const next = text(state.value?.announcement);
2517
- void nextTick(() => {
2518
- liveText.value = next;
2519
- });
2520
- }
2521
- );
2522
- async function confirmDelete() {
2523
- const seq = deleting.value;
2524
- if (seq === null || !current.value) return;
2525
- await current.value.remove(seq);
2526
- deleting.value = null;
2527
- }
2528
- return (_ctx, _cache) => {
2529
- return ctl.value && state.value && state.value.phase !== "hidden" ? (openBlock(), createElementBlock("section", {
2530
- key: generation.value,
2531
- class: "bcm bcm-discussion",
2532
- lang: lang.value,
2533
- "aria-labelledby": unref(headingId)
2534
- }, [
2535
- createElementVNode("header", _hoisted_2$4, [
2536
- createElementVNode("h2", {
2537
- id: unref(headingId),
2538
- ref_key: "heading",
2539
- ref: heading,
2540
- tabindex: "-1"
2541
- }, toDisplayString(unref(t)("discussion.title")), 9, _hoisted_3$4),
2542
- state.value.phase === "ready" ? (openBlock(), createElementBlock("span", _hoisted_4$4, toDisplayString(unref(t)("discussion.count", { count: state.value.thread.count })), 1)) : createCommentVNode("", true)
2543
- ]),
2544
- state.value.phase === "loading" ? (openBlock(), createElementBlock("p", _hoisted_5$4, toDisplayString(unref(t)("discussion.loading")), 1)) : state.value.phase === "failed" ? (openBlock(), createElementBlock("div", _hoisted_6$4, [
2545
- createElementVNode("span", null, toDisplayString(loadFailure.value), 1),
2546
- createElementVNode("button", {
2547
- type: "button",
2548
- class: "bcm-btn",
2549
- onClick: _cache[0] || (_cache[0] = ($event) => ctl.value.refresh())
2550
- }, toDisplayString(unref(t)("common.retry")), 1)
2551
- ])) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
2552
- createVNode(_sfc_main$8, {
2553
- "reaction-set": state.value.thread.reactionSet,
2554
- summary: state.value.thread.articleReactions,
2555
- interactive: ctl.value.canReact(),
2556
- busy: !!state.value.busy["react:article"],
2557
- onReact: _cache[1] || (_cache[1] = (emoji) => ctl.value && ctl.value.react({ articleId: state.value.thread.articleId ?? __props.articleId ?? "" }, emoji))
2558
- }, null, 8, ["reaction-set", "summary", "interactive", "busy"]),
2559
- state.value.newMessages ? (openBlock(), createElementBlock("div", _hoisted_7$4, [
2560
- createElementVNode("span", null, toDisplayString(unref(t)("discussion.newMessages")), 1),
2561
- createElementVNode("button", {
2562
- type: "button",
2563
- class: "bcm-btn",
2564
- disabled: state.value.refreshing,
2565
- onClick: _cache[2] || (_cache[2] = ($event) => ctl.value.refresh())
2566
- }, toDisplayString(unref(t)("discussion.refresh")), 9, _hoisted_8$4)
2567
- ])) : createCommentVNode("", true),
2568
- state.value.thread.messages.length === 0 ? (openBlock(), createElementBlock("p", _hoisted_9$4, toDisplayString(unref(t)("discussion.empty")), 1)) : (openBlock(), createElementBlock("ol", _hoisted_10$4, [
2569
- (openBlock(true), createElementBlock(Fragment, null, renderList(state.value.thread.messages, (m) => {
2570
- return openBlock(), createBlock(_sfc_main$7, {
2571
- key: m.id,
2572
- message: m
2573
- }, null, 8, ["message"]);
2574
- }), 128))
2575
- ])),
2576
- state.value.thread.nextCursor !== null ? (openBlock(), createElementBlock("div", _hoisted_11$4, [
2577
- createElementVNode("button", {
2578
- type: "button",
2579
- class: "bcm-btn",
2580
- disabled: state.value.loadingMore,
2581
- onClick: _cache[3] || (_cache[3] = ($event) => ctl.value.loadMore())
2582
- }, toDisplayString(unref(t)("discussion.loadMore")), 9, _hoisted_12$4)
2583
- ])) : createCommentVNode("", true),
2584
- createElementVNode("div", _hoisted_13$4, [
2585
- mode.value === "write" ? (openBlock(), createBlock(_sfc_main$c, {
2586
- key: 0,
2587
- ref_key: "composer",
2588
- ref: composer,
2589
- kind: "new",
2590
- draft: state.value.composer,
2591
- wait: ctl.value.waitLeft(state.value.composer),
2592
- "rules-href": routes.value.rulesPath,
2593
- onSubmit: _cache[4] || (_cache[4] = ($event) => ctl.value.submit()),
2594
- onConfirm: _cache[5] || (_cache[5] = (code, checked) => ctl.value && ctl.value.setConfirmed(state.value.composer, code, checked)),
2595
- onConfirmed: _cache[6] || (_cache[6] = ($event) => ctl.value.sendConfirmed(state.value.composer))
2596
- }, null, 8, ["draft", "wait", "rules-href"])) : mode.value === "signIn" ? (openBlock(), createElementBlock("div", _hoisted_14$4, [
2597
- createElementVNode("p", null, toDisplayString(unref(t)("discussion.signInPrompt")), 1),
2598
- signInHref() ? (openBlock(), createElementBlock("a", {
2599
- key: 0,
2600
- class: "bcm-btn bcm-primary",
2601
- href: signInHref() ?? void 0
2602
- }, toDisplayString(unref(t)("common.signIn")), 9, _hoisted_15$4)) : createCommentVNode("", true)
2603
- ])) : mode.value === "onboarding" ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
2604
- ctl.value.onboarding.value ? (openBlock(), createBlock(_sfc_main$6, {
2605
- key: 0,
2606
- model: ctl.value.onboarding.value,
2607
- "rules-href": routes.value.rulesPath,
2608
- onCancel: _cache[7] || (_cache[7] = ($event) => ctl.value.cancelOnboarding())
2609
- }, null, 8, ["model", "rules-href"])) : (openBlock(), createElementBlock("div", _hoisted_16$4, [
2610
- createElementVNode("p", null, toDisplayString(unref(t)(needKey.value)), 1),
2611
- createElementVNode("button", {
2612
- type: "button",
2613
- class: "bcm-btn bcm-primary",
2614
- onClick: _cache[8] || (_cache[8] = ($event) => ctl.value.beginOnboarding())
2615
- }, toDisplayString(unref(t)("onboarding.continue")), 1)
2616
- ]))
2617
- ], 64)) : mode.value === "ageRefused" ? (openBlock(), createElementBlock("p", _hoisted_17$4, toDisplayString(unref(t)("onboarding.ageBelow", { min: state.value.refusedAge ?? 18 })), 1)) : mode.value === "sanction" ? (openBlock(), createElementBlock("div", _hoisted_18$4, [
2618
- (openBlock(true), createElementBlock(Fragment, null, renderList(state.value.thread.viewer.restrictions, (r) => {
2619
- return openBlock(), createElementBlock("p", {
2620
- key: r.decisionId,
2621
- class: "bcm-note"
2622
- }, [
2623
- createTextVNode(toDisplayString(sanctionText(r)) + " ", 1),
2624
- unref(statementPath)(routes.value.statementPath, r.decisionId) ? (openBlock(), createElementBlock("a", {
2625
- key: 0,
2626
- href: unref(statementPath)(routes.value.statementPath, r.decisionId) ?? void 0
2627
- }, toDisplayString(unref(t)("sanction.statement")), 9, _hoisted_19$4)) : createCommentVNode("", true)
2628
- ]);
2629
- }), 128))
2630
- ])) : mode.value === "notMoved" ? (openBlock(), createElementBlock("p", _hoisted_20$4, toDisplayString(unref(t)("error.notMoved")), 1)) : mode.value === "restricted" ? (openBlock(), createElementBlock("p", _hoisted_21$3, [
2631
- createTextVNode(toDisplayString(unref(t)("error.restricted")) + " ", 1),
2632
- createElementVNode("a", {
2633
- href: routes.value.myCommunityPath
2634
- }, toDisplayString(unref(t)("panel.title")), 9, _hoisted_22$3)
2635
- ])) : createCommentVNode("", true)
2636
- ])
2637
- ], 64)),
2638
- createElementVNode("p", _hoisted_23$3, toDisplayString(liveText.value), 1),
2639
- state.value.report ? (openBlock(), createBlock(_sfc_main$5, {
2640
- key: 3,
2641
- report: state.value.report,
2642
- "my-community-href": routes.value.myCommunityPath,
2643
- "rules-href": routes.value.rulesPath,
2644
- "fallback-focus": () => heading.value,
2645
- onSubmit: _cache[9] || (_cache[9] = ($event) => ctl.value.submitReport()),
2646
- onClose: _cache[10] || (_cache[10] = ($event) => ctl.value.closeReport())
2647
- }, null, 8, ["report", "my-community-href", "rules-href", "fallback-focus"])) : createCommentVNode("", true),
2648
- deleting.value !== null ? (openBlock(), createBlock(_sfc_main$d, {
2649
- key: 4,
2650
- title: "discussion.deleteTitle",
2651
- explain: "discussion.deleteExplain",
2652
- confirm: "discussion.deleteConfirm",
2653
- danger: "",
2654
- busy: !!state.value.busy[`delete:${deleting.value}`],
2655
- "fallback-focus": () => heading.value,
2656
- onConfirm: _cache[11] || (_cache[11] = ($event) => confirmDelete()),
2657
- onCancel: _cache[12] || (_cache[12] = ($event) => deleting.value = null)
2658
- }, null, 8, ["busy", "fallback-focus"])) : createCommentVNode("", true)
2659
- ], 8, _hoisted_1$4)) : createCommentVNode("", true);
2660
- };
2661
- }
2662
- });
2663
- function saveJson(data, fileName) {
2664
- if (typeof document === "undefined" || typeof URL.createObjectURL !== "function") return;
2665
- const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
2666
- const url = URL.createObjectURL(blob);
2667
- const a = document.createElement("a");
2668
- a.href = url;
2669
- a.download = fileName;
2670
- a.style.display = "none";
2671
- document.body.appendChild(a);
2672
- a.click();
2673
- a.remove();
2674
- setTimeout(() => URL.revokeObjectURL(url), 1e3);
2675
- }
2676
- function exportFileName(name, now = /* @__PURE__ */ new Date()) {
2677
- const day = now.toISOString().slice(0, 10);
2678
- const safe = name.replace(/[^\p{L}\p{N}_-]+/gu, "-").replace(/^-+|-+$/g, "") || "export";
2679
- return `${safe}-${day}.json`;
2680
- }
2681
- const appealDraft = () => ({ body: "", sending: false, failure: null });
2682
- function createPanel(o) {
2683
- const { client } = o;
2684
- const abort = new AbortController();
2685
- const call = { signal: abort.signal };
2686
- const state = reactive({
2687
- phase: "idle",
2688
- failure: null,
2689
- me: null,
2690
- notifications: [],
2691
- decisions: [],
2692
- appeals: [],
2693
- reports: [],
2694
- blocks: [],
2695
- statement: { phase: "idle", value: null, failure: null, appeal: appealDraft() },
2696
- notice: { phase: "idle", value: null, failure: null, appeal: appealDraft() },
2697
- profile: null,
2698
- purgeAfter: null,
2699
- exporting: false,
2700
- busy: {},
2701
- announcement: null,
2702
- announceId: 0
2703
- });
2704
- let disposed = false;
2705
- const announce = (text) => {
2706
- state.announcement = text;
2707
- state.announceId += 1;
2708
- };
2709
- const failureRef = (f) => ({ key: `error.${f.code}` });
2710
- async function loadMember() {
2711
- state.phase = "loading";
2712
- state.failure = null;
2713
- const me = await client.me.get(call);
2714
- if (disposed) return;
2715
- if (!me.ok) {
2716
- if (me.code === "signIn") {
2717
- state.phase = "signIn";
2718
- return;
2719
- }
2720
- state.phase = "failed";
2721
- state.failure = me;
2722
- return;
2723
- }
2724
- state.me = me.value;
2725
- if (me.value.needs.includes("sign_in")) {
2726
- state.phase = "signIn";
2727
- return;
2728
- }
2729
- if (!me.value.member) {
2730
- state.phase = "ready";
2731
- return;
2732
- }
2733
- const [notifications, decisions, appeals, reports, blocks] = await Promise.all([
2734
- client.me.notifications.list({}, call),
2735
- client.me.decisions.list({}, call),
2736
- client.me.appeals.list(call),
2737
- client.me.reports.list(call),
2738
- client.blocks.list(call)
2739
- ]);
2740
- if (disposed) return;
2741
- if (notifications.ok) state.notifications = notifications.value.items.filter((n) => isKnownNotificationCode(n.code));
2742
- if (decisions.ok) state.decisions = [...decisions.value.items];
2743
- if (appeals.ok) state.appeals = [...appeals.value.items];
2744
- if (reports.ok) state.reports = [...reports.value.items];
2745
- if (blocks.ok) state.blocks = [...blocks.value.blocks];
2746
- const first = [notifications, decisions, appeals, reports, blocks].find((r) => !r.ok);
2747
- if (first && !first.ok) announce(failureRef(first));
2748
- state.phase = "ready";
2749
- }
2750
- let statementId = null;
2751
- let statementLoad = 0;
2752
- async function loadStatement(decisionId) {
2753
- const ticket = ++statementLoad;
2754
- if (decisionId !== statementId) {
2755
- statementId = decisionId;
2756
- state.statement.value = null;
2757
- state.statement.appeal = appealDraft();
2758
- }
2759
- state.statement.phase = "loading";
2760
- state.statement.failure = null;
2761
- const r = await client.me.decisions.get({ decisionId }, call);
2762
- if (disposed || ticket !== statementLoad) return;
2763
- if (r.ok) {
2764
- state.statement.value = r.value;
2765
- state.statement.phase = "ready";
2766
- return;
2767
- }
2768
- state.statement.failure = r;
2769
- state.statement.phase = r.code === "signIn" ? "signIn" : "failed";
2770
- }
2771
- async function loadNotice(receipt) {
2772
- if (receipt === null) {
2773
- state.notice.phase = "missing";
2774
- return;
2775
- }
2776
- state.notice.phase = "loading";
2777
- state.notice.failure = null;
2778
- const r = await client.notices.status({ receipt }, call);
2779
- if (disposed) return;
2780
- if (r.ok) {
2781
- state.notice.value = r.value;
2782
- state.notice.phase = "ready";
2783
- return;
2784
- }
2785
- state.notice.failure = r;
2786
- state.notice.phase = "failed";
2787
- }
2788
- let noticeReceipt = null;
2789
- const appealLength = (d) => codePointLength(normalizeBody(d.body));
2790
- const canAppealWith = (d) => !d.sending && appealLength(d) > 0 && appealLength(d) <= COMMUNITY_APPEAL_MAX;
2791
- return {
2792
- state,
2793
- loadMember,
2794
- loadStatement,
2795
- async loadNotice(receipt) {
2796
- noticeReceipt = receipt;
2797
- await loadNotice(receipt);
2798
- },
2799
- unreadCount: () => state.notifications.filter((n) => n.readAt === null).length,
2800
- async markAllRead() {
2801
- const ids = state.notifications.filter((n) => n.readAt === null).map((n) => n.id);
2802
- if (ids.length === 0 || state.busy.markRead) return;
2803
- state.busy.markRead = true;
2804
- const r = await client.me.notifications.markRead({ ids }, call);
2805
- delete state.busy.markRead;
2806
- if (disposed) return;
2807
- if (!r.ok) return announce(failureRef(r));
2808
- const at = (/* @__PURE__ */ new Date()).toISOString();
2809
- state.notifications = state.notifications.map((n) => ids.includes(n.id) ? { ...n, readAt: at } : n);
2810
- },
2811
- async fileAppeal() {
2812
- const s = state.statement;
2813
- const d = s.appeal;
2814
- if (!s.value || !s.value.canAppeal || !canAppealWith(d)) return;
2815
- const decisionId = s.value.decisionId;
2816
- d.sending = true;
2817
- d.failure = null;
2818
- const r = await client.me.appeals.file({ decisionId, body: normalizeBody(d.body) }, call);
2819
- d.sending = false;
2820
- if (disposed || decisionId !== statementId) return;
2821
- if (!r.ok) {
2822
- d.failure = r;
2823
- return;
2824
- }
2825
- d.body = "";
2826
- announce({ key: "statement.appealSent" });
2827
- await loadStatement(decisionId);
2828
- },
2829
- async withdrawAppeal() {
2830
- const s = state.statement;
2831
- const open = s.value?.appeal;
2832
- if (!s.value || !open || open.status !== "open" || state.busy.withdraw) return;
2833
- const decisionId = s.value.decisionId;
2834
- state.busy.withdraw = true;
2835
- const r = await client.me.appeals.withdraw({ appealId: open.appealId }, call);
2836
- delete state.busy.withdraw;
2837
- if (disposed || decisionId !== statementId) return;
2838
- if (!r.ok) return announce(failureRef(r));
2839
- announce({ key: "statement.appealWithdrawn" });
2840
- await loadStatement(decisionId);
2841
- },
2842
- async fileNoticeAppeal() {
2843
- const n = state.notice;
2844
- const d = n.appeal;
2845
- if (!n.value || !n.value.appeal.canAppeal || noticeReceipt === null || !canAppealWith(d)) return;
2846
- d.sending = true;
2847
- d.failure = null;
2848
- const r = await client.notices.appeal({ receipt: noticeReceipt, body: normalizeBody(d.body) }, call);
2849
- d.sending = false;
2850
- if (disposed) return;
2851
- if (!r.ok) {
2852
- d.failure = r;
2853
- return;
2854
- }
2855
- d.body = "";
2856
- announce({ key: "notice.appealSent" });
2857
- await loadNotice(noticeReceipt);
2858
- },
2859
- async withdrawNoticeAppeal() {
2860
- if (!state.notice.value?.appeal.open || noticeReceipt === null || state.busy.noticeWithdraw) return;
2861
- state.busy.noticeWithdraw = true;
2862
- const r = await client.notices.withdrawAppeal({ receipt: noticeReceipt }, call);
2863
- delete state.busy.noticeWithdraw;
2864
- if (disposed) return;
2865
- if (!r.ok) return announce(failureRef(r));
2866
- announce({ key: "statement.appealWithdrawn" });
2867
- await loadNotice(noticeReceipt);
2868
- },
2869
- async withdrawConsent() {
2870
- if (state.busy.consent) return false;
2871
- state.busy.consent = true;
2872
- const r = await client.me.consents.withdraw({ kind: "art9_consent" }, call);
2873
- delete state.busy.consent;
2874
- if (disposed) return false;
2875
- if (!r.ok) {
2876
- announce(failureRef(r));
2877
- return false;
2878
- }
2879
- if (state.me) state.me = { ...state.me, acceptances: { ...state.me.acceptances, art9Consent: r.value.state } };
2880
- announce({ key: "panel.art9Withdrawn" });
2881
- return true;
2882
- },
2883
- async unblock(memberId) {
2884
- const key = `block:${memberId}`;
2885
- if (state.busy[key]) return;
2886
- state.busy[key] = true;
2887
- const r = await client.blocks.set({ memberId, blocked: false }, call);
2888
- delete state.busy[key];
2889
- if (disposed) return;
2890
- if (!r.ok) return announce(failureRef(r));
2891
- state.blocks = state.blocks.filter((b) => b.memberId !== memberId);
2892
- announce({ key: "discussion.unblocked" });
2893
- },
2894
- editProfile() {
2895
- const p = state.me?.profile;
2896
- if (!p) return;
2897
- state.profile = { displayName: p.displayName, handle: p.handle, avatarPreset: p.avatarPreset, sending: false, failure: null, fields: [] };
2898
- },
2899
- cancelProfile() {
2900
- state.profile = null;
2901
- },
2902
- async saveProfile() {
2903
- const d = state.profile;
2904
- const current = state.me?.profile;
2905
- if (!d || !current || d.sending || !isDisplayNameOk(d.displayName) || !isHandleOk(d.handle) || normalizeHandle(d.handle) === "") return;
2906
- const input = {};
2907
- const name = normalizeName(d.displayName);
2908
- const handle = normalizeHandle(d.handle);
2909
- if (name !== current.displayName) input.displayName = name;
2910
- if (handle !== current.handle) input.handle = handle;
2911
- if (d.avatarPreset !== current.avatarPreset) input.avatarPreset = d.avatarPreset;
2912
- if (Object.keys(input).length === 0) {
2913
- state.profile = null;
2914
- return;
2915
- }
2916
- d.sending = true;
2917
- d.failure = null;
2918
- d.fields = [];
2919
- const r = await client.me.updateProfile(input, call);
2920
- d.sending = false;
2921
- if (disposed) return;
2922
- if (!r.ok) {
2923
- d.failure = r;
2924
- d.fields = r.code === "handle" ? ["handle"] : r.code === "name" ? ["displayName"] : r.fields ?? [];
2925
- return;
2926
- }
2927
- if (state.me) state.me = { ...state.me, profile: r.value.profile };
2928
- state.profile = null;
2929
- },
2930
- async exportData(save) {
2931
- if (state.exporting) return;
2932
- state.exporting = true;
2933
- const r = await client.me.export(call);
2934
- state.exporting = false;
2935
- if (disposed) return;
2936
- if (!r.ok) return announce(failureRef(r));
2937
- save(r.value);
2938
- },
2939
- async erase() {
2940
- if (state.busy.erase) return false;
2941
- state.busy.erase = true;
2942
- const r = await client.me.erase(call);
2943
- delete state.busy.erase;
2944
- if (disposed) return false;
2945
- if (!r.ok) {
2946
- announce(failureRef(r));
2947
- return false;
2948
- }
2949
- state.purgeAfter = r.value.purgeAfter;
2950
- if (state.me?.member) state.me = { ...state.me, member: { ...state.me.member, status: r.value.status } };
2951
- return true;
2952
- },
2953
- async cancelErase() {
2954
- if (state.busy.erase) return;
2955
- state.busy.erase = true;
2956
- const r = await client.me.cancelErase(call);
2957
- delete state.busy.erase;
2958
- if (disposed) return;
2959
- if (!r.ok) return announce(failureRef(r));
2960
- state.purgeAfter = null;
2961
- if (state.me?.member) state.me = { ...state.me, member: { ...state.me.member, status: r.value.status } };
2962
- announce({ key: "panel.eraseCancelled" });
2963
- },
2964
- erasing: () => state.me?.member?.status === "deletion_requested",
2965
- appealLength,
2966
- canAppealWith,
2967
- dispose() {
2968
- disposed = true;
2969
- abort.abort();
2970
- }
2971
- };
2972
- }
2973
- const taken = /* @__PURE__ */ new WeakMap();
2974
- function receiptForElement(owner) {
2975
- if (typeof location !== "undefined" && location.hash.startsWith("#t=")) {
2976
- const receipt = receiptFromHash(location.hash);
2977
- readReceiptFromHash();
2978
- if (owner) taken.set(owner, receipt);
2979
- return receipt;
2980
- }
2981
- return owner && taken.has(owner) ? taken.get(owner) ?? null : void 0;
2982
- }
2983
- const _hoisted_1$3 = ["aria-labelledby"];
2984
- const _hoisted_2$3 = ["id"];
2985
- const _hoisted_3$3 = { class: "bcm-dl" };
2986
- const _hoisted_4$3 = { class: "bcm-muted" };
2987
- const _hoisted_5$3 = {
2988
- key: 0,
2989
- class: "bcm-muted"
2990
- };
2991
- const _hoisted_6$3 = { class: "bcm-facts" };
2992
- const _hoisted_7$3 = ["aria-labelledby"];
2993
- const _hoisted_8$3 = ["id"];
2994
- const _hoisted_9$3 = { class: "bcm-row" };
2995
- const _hoisted_10$3 = ["disabled"];
2996
- const _hoisted_11$3 = {
2997
- key: 0,
2998
- class: "bcm-muted"
2999
- };
3000
- const _hoisted_12$3 = { class: "bcm-field" };
3001
- const _hoisted_13$3 = ["for"];
3002
- const _hoisted_14$3 = ["id", "aria-describedby", "aria-invalid"];
3003
- const _hoisted_15$3 = ["id"];
3004
- const _hoisted_16$3 = {
3005
- key: 0,
3006
- class: "bcm-error",
3007
- role: "alert"
3008
- };
3009
- const _hoisted_17$3 = { class: "bcm-row" };
3010
- const _hoisted_18$3 = ["disabled"];
3011
- const _hoisted_19$3 = { key: 2 };
3012
- const _hoisted_20$3 = { key: 3 };
3013
- const _sfc_main$3 = /* @__PURE__ */ defineComponent({
3014
- __name: "NoticeStatusView",
3015
- props: {
3016
- notice: {},
3017
- appeal: {},
3018
- withdrawing: { type: Boolean }
3019
- },
3020
- emits: ["appeal", "withdraw"],
3021
- setup(__props, { emit: __emit }) {
3022
- const props = __props;
3023
- const emit = __emit;
3024
- const { t, lang } = useText();
3025
- const id = uid("bcm-notice-status");
3026
- const n = computed(() => props.notice);
3027
- const category = computed(() => knownKey(`category.${n.value.category}`));
3028
- const status = computed(() => knownKey(`caseStatus.${n.value.status}`));
3029
- const length = computed(() => codePointLength(normalizeBody(props.appeal.body)));
3030
- const canSend = computed(() => !props.appeal.sending && length.value > 0 && length.value <= COMMUNITY_APPEAL_MAX);
3031
- const closed = computed(() => n.value.appeal.closesAt ? isPast(n.value.appeal.closesAt) : false);
3032
- const failure = computed(() => {
3033
- const f = props.appeal.failure;
3034
- if (!f) return "";
3035
- const r = failureText(f, lang.value);
3036
- return t(r.key, r.params);
3037
- });
3038
- return (_ctx, _cache) => {
3039
- return openBlock(), createElementBlock("section", {
3040
- class: "bcm-section",
3041
- "aria-labelledby": `${unref(id)}-title`
3042
- }, [
3043
- createElementVNode("h3", {
3044
- id: `${unref(id)}-title`
3045
- }, toDisplayString(unref(t)("notice.statusTitle")), 9, _hoisted_2$3),
3046
- createElementVNode("dl", _hoisted_3$3, [
3047
- category.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
3048
- createElementVNode("dt", null, toDisplayString(unref(t)("notice.category")), 1),
3049
- createElementVNode("dd", null, toDisplayString(unref(t)(category.value)), 1)
3050
- ], 64)) : createCommentVNode("", true),
3051
- status.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
3052
- createElementVNode("dt", null, toDisplayString(unref(t)("notice.statusTitle")), 1),
3053
- createElementVNode("dd", null, toDisplayString(unref(t)(status.value)), 1)
3054
- ], 64)) : createCommentVNode("", true)
3055
- ]),
3056
- createElementVNode("p", _hoisted_4$3, toDisplayString(unref(t)("notice.submittedAt", { date: unref(formatDate)(unref(lang), n.value.submittedAt) })), 1),
3057
- n.value.decidedAt ? (openBlock(), createElementBlock("p", _hoisted_5$3, toDisplayString(unref(t)("notice.decidedAt", { date: unref(formatDate)(unref(lang), n.value.decidedAt) })), 1)) : createCommentVNode("", true),
3058
- n.value.statement ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
3059
- createElementVNode("h4", null, toDisplayString(unref(t)("notice.statement")), 1),
3060
- createElementVNode("pre", _hoisted_6$3, toDisplayString(n.value.statement), 1)
3061
- ], 64)) : createCommentVNode("", true),
3062
- n.value.decidedAt ? (openBlock(), createElementBlock("section", {
3063
- key: 2,
3064
- class: "bcm-card",
3065
- "aria-labelledby": `${unref(id)}-appeal`
3066
- }, [
3067
- createElementVNode("h4", {
3068
- id: `${unref(id)}-appeal`
3069
- }, toDisplayString(unref(t)("notice.appealTitle")), 9, _hoisted_8$3),
3070
- n.value.appeal.open ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
3071
- createElementVNode("p", null, toDisplayString(unref(t)("notice.appealOpen")), 1),
3072
- createElementVNode("div", _hoisted_9$3, [
3073
- createElementVNode("button", {
3074
- type: "button",
3075
- class: "bcm-btn",
3076
- disabled: __props.withdrawing,
3077
- onClick: _cache[0] || (_cache[0] = ($event) => emit("withdraw"))
3078
- }, toDisplayString(unref(t)("notice.appealWithdraw")), 9, _hoisted_10$3)
3079
- ])
3080
- ], 64)) : n.value.appeal.canAppeal ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
3081
- createElementVNode("p", null, toDisplayString(unref(t)("notice.appealLede")), 1),
3082
- n.value.appeal.closesAt ? (openBlock(), createElementBlock("p", _hoisted_11$3, toDisplayString(unref(t)("notice.appealUntil", { date: unref(formatDate)(unref(lang), n.value.appeal.closesAt) })), 1)) : createCommentVNode("", true),
3083
- createElementVNode("form", {
3084
- class: "bcm-form",
3085
- novalidate: "",
3086
- onSubmit: _cache[2] || (_cache[2] = withModifiers(($event) => canSend.value && emit("appeal"), ["prevent"]))
3087
- }, [
3088
- createElementVNode("div", _hoisted_12$3, [
3089
- createElementVNode("label", {
3090
- for: `${unref(id)}-body`
3091
- }, toDisplayString(unref(t)("notice.appealBody")), 9, _hoisted_13$3),
3092
- withDirectives(createElementVNode("textarea", {
3093
- id: `${unref(id)}-body`,
3094
- "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => __props.appeal.body = $event),
3095
- class: "bcm-input bcm-textarea",
3096
- rows: "5",
3097
- "aria-describedby": `${unref(id)}-counter`,
3098
- "aria-invalid": length.value > unref(COMMUNITY_APPEAL_MAX)
3099
- }, null, 8, _hoisted_14$3), [
3100
- [vModelText, __props.appeal.body]
3101
- ]),
3102
- createElementVNode("p", {
3103
- id: `${unref(id)}-counter`,
3104
- class: "bcm-counter"
3105
- }, toDisplayString(unref(t)("statement.appealCounter", { length: length.value, max: unref(COMMUNITY_APPEAL_MAX) })), 9, _hoisted_15$3)
3106
- ]),
3107
- failure.value ? (openBlock(), createElementBlock("p", _hoisted_16$3, toDisplayString(failure.value), 1)) : createCommentVNode("", true),
3108
- createElementVNode("div", _hoisted_17$3, [
3109
- createElementVNode("button", {
3110
- type: "submit",
3111
- class: "bcm-btn bcm-primary",
3112
- disabled: !canSend.value
3113
- }, toDisplayString(__props.appeal.sending ? unref(t)("statement.appealSending") : unref(t)("notice.appealSubmit")), 9, _hoisted_18$3)
3114
- ])
3115
- ], 32)
3116
- ], 64)) : closed.value ? (openBlock(), createElementBlock("p", _hoisted_19$3, toDisplayString(unref(t)("notice.appealClosed")), 1)) : (openBlock(), createElementBlock("p", _hoisted_20$3, toDisplayString(unref(t)("statement.appealUnavailable")), 1))
3117
- ], 8, _hoisted_7$3)) : createCommentVNode("", true)
3118
- ], 8, _hoisted_1$3);
3119
- };
3120
- }
3121
- });
3122
- const _hoisted_1$2 = ["aria-labelledby"];
3123
- const _hoisted_2$2 = ["id"];
3124
- const _hoisted_3$2 = { class: "bcm-muted" };
3125
- const _hoisted_4$2 = { class: "bcm-dl" };
3126
- const _hoisted_5$2 = {
3127
- key: 0,
3128
- class: "bcm-muted"
3129
- };
3130
- const _hoisted_6$2 = { key: 0 };
3131
- const _hoisted_7$2 = {
3132
- key: 0,
3133
- class: "bcm-text"
3134
- };
3135
- const _hoisted_8$2 = {
3136
- key: 0,
3137
- class: "bcm-text"
3138
- };
3139
- const _hoisted_9$2 = {
3140
- key: 0,
3141
- class: "bcm-note bcm-text"
3142
- };
3143
- const _hoisted_10$2 = { class: "bcm-facts" };
3144
- const _hoisted_11$2 = { class: "bcm-text" };
3145
- const _hoisted_12$2 = { class: "bcm-facts" };
3146
- const _hoisted_13$2 = ["aria-labelledby"];
3147
- const _hoisted_14$2 = ["id"];
3148
- const _hoisted_15$2 = {
3149
- key: 0,
3150
- class: "bcm-muted"
3151
- };
3152
- const _hoisted_16$2 = { class: "bcm-row" };
3153
- const _hoisted_17$2 = ["disabled"];
3154
- const _hoisted_18$2 = { class: "bcm-muted" };
3155
- const _hoisted_19$2 = { class: "bcm-field" };
3156
- const _hoisted_20$2 = ["for"];
3157
- const _hoisted_21$2 = ["id", "aria-describedby", "aria-invalid"];
3158
- const _hoisted_22$2 = ["id"];
3159
- const _hoisted_23$2 = {
3160
- key: 0,
3161
- class: "bcm-error",
3162
- role: "alert"
3163
- };
3164
- const _hoisted_24$2 = { class: "bcm-row" };
3165
- const _hoisted_25$2 = ["disabled"];
3166
- const _hoisted_26$2 = {
3167
- key: 0,
3168
- class: "bcm-muted"
3169
- };
3170
- const _sfc_main$2 = /* @__PURE__ */ defineComponent({
3171
- __name: "StatementView",
3172
- props: {
3173
- statement: {},
3174
- appeal: {},
3175
- withdrawing: { type: Boolean }
3176
- },
3177
- emits: ["appeal", "withdraw"],
3178
- setup(__props, { emit: __emit }) {
3179
- const props = __props;
3180
- const emit = __emit;
3181
- const { t, lang } = useText();
3182
- const id = uid("bcm-statement");
3183
- const s = computed(() => props.statement);
3184
- const restriction = computed(() => knownKey(`restriction.${s.value.restriction}`));
3185
- const basis = computed(() => knownKey(`basis.${s.value.basis}`));
3186
- const category = computed(() => knownKey(`category.${s.value.category}`));
3187
- const scope = computed(() => formatTerritorialScope(lang.value, s.value.territorialScope));
3188
- const appealStatus = computed(() => s.value.appeal ? knownKey(`appealStatus.${s.value.appeal.status}`) : null);
3189
- const subjectKey = computed(
3190
- () => s.value.subject.kind === "message" ? "statement.subjectMessage" : s.value.subject.kind === "article" ? "statement.subjectArticle" : "statement.subjectProfile"
3191
- );
3192
- const subjectText = computed(() => {
3193
- const subject = s.value.subject;
3194
- return subject.kind === "message" && subject.message ? subject.message.body ?? subject.message.displayBody : null;
3195
- });
3196
- const appealOpen = computed(() => s.value.appeal?.status === "open");
3197
- const windowClosed = computed(() => isPast(s.value.appealWindowClosesAt));
3198
- const length = computed(() => codePointLength(normalizeBody(props.appeal.body)));
3199
- const canSend = computed(() => !props.appeal.sending && length.value > 0 && length.value <= COMMUNITY_APPEAL_MAX);
3200
- const failure = computed(() => {
3201
- const f = props.appeal.failure;
3202
- if (!f) return "";
3203
- const r = failureText(f, lang.value);
3204
- return t(r.key, r.params);
3205
- });
3206
- return (_ctx, _cache) => {
3207
- return openBlock(), createElementBlock("article", {
3208
- class: "bcm-section",
3209
- "aria-labelledby": `${unref(id)}-title`
3210
- }, [
3211
- createElementVNode("h3", {
3212
- id: `${unref(id)}-title`
3213
- }, toDisplayString(unref(t)("statement.title")), 9, _hoisted_2$2),
3214
- createElementVNode("p", _hoisted_3$2, toDisplayString(unref(t)("statement.decidedAt", { date: unref(formatDate)(unref(lang), s.value.decidedAt) })), 1),
3215
- createElementVNode("p", null, toDisplayString(unref(t)("statement.provider", { entity: s.value.providerEntity })), 1),
3216
- createElementVNode("dl", _hoisted_4$2, [
3217
- restriction.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
3218
- createElementVNode("dt", null, toDisplayString(unref(t)("statement.restriction")), 1),
3219
- createElementVNode("dd", null, [
3220
- createTextVNode(toDisplayString(unref(t)(restriction.value)) + " ", 1),
3221
- s.value.restrictionUntil ? (openBlock(), createElementBlock("span", _hoisted_5$2, toDisplayString(unref(t)("statement.until", { date: unref(formatDate)(unref(lang), s.value.restrictionUntil) })), 1)) : createCommentVNode("", true)
3222
- ])
3223
- ], 64)) : createCommentVNode("", true),
3224
- category.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
3225
- createElementVNode("dt", null, toDisplayString(unref(t)("report.category")), 1),
3226
- createElementVNode("dd", null, toDisplayString(unref(t)(category.value)), 1)
3227
- ], 64)) : createCommentVNode("", true),
3228
- scope.value ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
3229
- createElementVNode("dt", null, toDisplayString(unref(t)("statement.scope")), 1),
3230
- createElementVNode("dd", null, toDisplayString(scope.value), 1)
3231
- ], 64)) : createCommentVNode("", true),
3232
- basis.value ? (openBlock(), createElementBlock(Fragment, { key: 3 }, [
3233
- createElementVNode("dt", null, toDisplayString(unref(t)("statement.basis")), 1),
3234
- createElementVNode("dd", null, toDisplayString(unref(t)(basis.value)), 1)
3235
- ], 64)) : createCommentVNode("", true),
3236
- createElementVNode("dt", null, toDisplayString(unref(t)("statement.automation")), 1),
3237
- createElementVNode("dd", null, [
3238
- s.value.automatedDetection ? (openBlock(), createElementBlock("span", _hoisted_6$2, toDisplayString(unref(t)("statement.automatedDetection")), 1)) : createCommentVNode("", true),
3239
- createElementVNode("span", null, toDisplayString(s.value.automated ? unref(t)("statement.automatedDecision") : unref(t)("statement.humanDecision")), 1)
3240
- ]),
3241
- s.value.legalGround ? (openBlock(), createElementBlock(Fragment, { key: 4 }, [
3242
- createElementVNode("dt", null, toDisplayString(unref(t)("statement.legalGround")), 1),
3243
- createElementVNode("dd", null, [
3244
- createElementVNode("p", null, toDisplayString(s.value.legalGround), 1),
3245
- s.value.legalExplanation ? (openBlock(), createElementBlock("p", _hoisted_7$2, toDisplayString(s.value.legalExplanation), 1)) : createCommentVNode("", true)
3246
- ])
3247
- ], 64)) : createCommentVNode("", true),
3248
- s.value.contractualGround ? (openBlock(), createElementBlock(Fragment, { key: 5 }, [
3249
- createElementVNode("dt", null, toDisplayString(unref(t)("statement.contractualGround")), 1),
3250
- createElementVNode("dd", null, [
3251
- createElementVNode("p", null, toDisplayString(s.value.contractualGround), 1),
3252
- s.value.contractualExplanation ? (openBlock(), createElementBlock("p", _hoisted_8$2, toDisplayString(s.value.contractualExplanation), 1)) : createCommentVNode("", true)
3253
- ])
3254
- ], 64)) : createCommentVNode("", true),
3255
- createElementVNode("dt", null, toDisplayString(unref(t)("statement.subject")), 1),
3256
- createElementVNode("dd", null, [
3257
- createElementVNode("p", null, toDisplayString(unref(t)(subjectKey.value)), 1),
3258
- subjectText.value ? (openBlock(), createElementBlock("p", _hoisted_9$2, [
3259
- createVNode(_sfc_main$a, { text: subjectText.value }, null, 8, ["text"])
3260
- ])) : createCommentVNode("", true)
3261
- ])
3262
- ]),
3263
- createElementVNode("h4", null, toDisplayString(unref(t)("statement.facts")), 1),
3264
- createElementVNode("pre", _hoisted_10$2, toDisplayString(s.value.facts), 1),
3265
- createElementVNode("h4", null, toDisplayString(unref(t)("statement.redress")), 1),
3266
- createElementVNode("p", _hoisted_11$2, toDisplayString(s.value.redress), 1),
3267
- createElementVNode("details", null, [
3268
- createElementVNode("summary", null, toDisplayString(unref(t)("statement.full")), 1),
3269
- createElementVNode("pre", _hoisted_12$2, toDisplayString(s.value.statement), 1)
3270
- ]),
3271
- createElementVNode("section", {
3272
- class: "bcm-card",
3273
- "aria-labelledby": `${unref(id)}-appeal`
3274
- }, [
3275
- createElementVNode("h4", {
3276
- id: `${unref(id)}-appeal`
3277
- }, toDisplayString(unref(t)("statement.appealTitle")), 9, _hoisted_14$2),
3278
- appealOpen.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
3279
- createElementVNode("p", null, toDisplayString(unref(t)("statement.appealOpen")), 1),
3280
- appealStatus.value ? (openBlock(), createElementBlock("p", _hoisted_15$2, toDisplayString(unref(t)(appealStatus.value)), 1)) : createCommentVNode("", true),
3281
- createElementVNode("div", _hoisted_16$2, [
3282
- createElementVNode("button", {
3283
- type: "button",
3284
- class: "bcm-btn",
3285
- disabled: __props.withdrawing,
3286
- onClick: _cache[0] || (_cache[0] = ($event) => emit("withdraw"))
3287
- }, toDisplayString(unref(t)("statement.appealWithdraw")), 9, _hoisted_17$2)
3288
- ])
3289
- ], 64)) : s.value.canAppeal ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
3290
- createElementVNode("p", null, toDisplayString(unref(t)("statement.appealLede")), 1),
3291
- createElementVNode("p", _hoisted_18$2, toDisplayString(unref(t)("statement.appealUntil", { date: unref(formatDate)(unref(lang), s.value.appealWindowClosesAt) })), 1),
3292
- createElementVNode("form", {
3293
- class: "bcm-form",
3294
- novalidate: "",
3295
- onSubmit: _cache[2] || (_cache[2] = withModifiers(($event) => canSend.value && emit("appeal"), ["prevent"]))
3296
- }, [
3297
- createElementVNode("div", _hoisted_19$2, [
3298
- createElementVNode("label", {
3299
- for: `${unref(id)}-body`
3300
- }, toDisplayString(unref(t)("statement.appealBody")), 9, _hoisted_20$2),
3301
- withDirectives(createElementVNode("textarea", {
3302
- id: `${unref(id)}-body`,
3303
- "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => __props.appeal.body = $event),
3304
- class: "bcm-input bcm-textarea",
3305
- rows: "5",
3306
- "aria-describedby": `${unref(id)}-counter`,
3307
- "aria-invalid": length.value > unref(COMMUNITY_APPEAL_MAX)
3308
- }, null, 8, _hoisted_21$2), [
3309
- [vModelText, __props.appeal.body]
3310
- ]),
3311
- createElementVNode("p", {
3312
- id: `${unref(id)}-counter`,
3313
- class: "bcm-counter"
3314
- }, toDisplayString(unref(t)("statement.appealCounter", { length: length.value, max: unref(COMMUNITY_APPEAL_MAX) })), 9, _hoisted_22$2)
3315
- ]),
3316
- failure.value ? (openBlock(), createElementBlock("p", _hoisted_23$2, toDisplayString(failure.value), 1)) : createCommentVNode("", true),
3317
- createElementVNode("div", _hoisted_24$2, [
3318
- createElementVNode("button", {
3319
- type: "submit",
3320
- class: "bcm-btn bcm-primary",
3321
- disabled: !canSend.value
3322
- }, toDisplayString(__props.appeal.sending ? unref(t)("statement.appealSending") : unref(t)("statement.appealSubmit")), 9, _hoisted_25$2)
3323
- ])
3324
- ], 32)
3325
- ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
3326
- appealStatus.value ? (openBlock(), createElementBlock("p", _hoisted_26$2, toDisplayString(unref(t)(appealStatus.value)), 1)) : createCommentVNode("", true),
3327
- createElementVNode("p", null, toDisplayString(windowClosed.value ? unref(t)("statement.appealClosed") : unref(t)("statement.appealUnavailable")), 1)
3328
- ], 64))
3329
- ], 8, _hoisted_13$2)
3330
- ], 8, _hoisted_1$2);
3331
- };
3332
- }
3333
- });
3334
- const _hoisted_1$1 = ["lang", "aria-labelledby"];
3335
- const _hoisted_2$1 = ["id"];
3336
- const _hoisted_3$1 = {
3337
- key: 0,
3338
- class: "bcm-note",
3339
- role: "alert"
3340
- };
3341
- const _hoisted_4$1 = {
3342
- key: 1,
3343
- class: "bcm-muted",
3344
- role: "status"
3345
- };
3346
- const _hoisted_5$1 = {
3347
- key: 2,
3348
- class: "bcm-error",
3349
- role: "alert"
3350
- };
3351
- const _hoisted_6$1 = {
3352
- key: 0,
3353
- class: "bcm-muted",
3354
- role: "status"
3355
- };
3356
- const _hoisted_7$1 = {
3357
- key: 1,
3358
- class: "bcm-row"
3359
- };
3360
- const _hoisted_8$1 = ["href"];
3361
- const _hoisted_9$1 = {
3362
- key: 2,
3363
- class: "bcm-error",
3364
- role: "alert"
3365
- };
3366
- const _hoisted_10$1 = ["href"];
3367
- const _hoisted_11$1 = {
3368
- key: 0,
3369
- class: "bcm-muted",
3370
- role: "status"
3371
- };
3372
- const _hoisted_12$1 = {
3373
- key: 1,
3374
- class: "bcm-row"
3375
- };
3376
- const _hoisted_13$1 = ["href"];
3377
- const _hoisted_14$1 = {
3378
- key: 2,
3379
- class: "bcm-error",
3380
- role: "alert"
3381
- };
3382
- const _hoisted_15$1 = ["aria-labelledby"];
3383
- const _hoisted_16$1 = ["id"];
3384
- const _hoisted_17$1 = {
3385
- key: 0,
3386
- class: "bcm-msg"
3387
- };
3388
- const _hoisted_18$1 = { class: "bcm-msg-main" };
3389
- const _hoisted_19$1 = { class: "bcm-author" };
3390
- const _hoisted_20$1 = { class: "bcm-handle" };
3391
- const _hoisted_21$1 = { class: "bcm-row" };
3392
- const _hoisted_22$1 = { class: "bcm-field" };
3393
- const _hoisted_23$1 = ["for"];
3394
- const _hoisted_24$1 = ["id", "aria-invalid"];
3395
- const _hoisted_25$1 = { class: "bcm-hint" };
3396
- const _hoisted_26$1 = { class: "bcm-field" };
3397
- const _hoisted_27$1 = ["for"];
3398
- const _hoisted_28$1 = ["id", "maxlength", "aria-invalid"];
3399
- const _hoisted_29$1 = { class: "bcm-hint" };
3400
- const _hoisted_30$1 = { class: "bcm-choices" };
3401
- const _hoisted_31$1 = { class: "bcm-choice" };
3402
- const _hoisted_32$1 = ["name"];
3403
- const _hoisted_33$1 = ["name", "value"];
3404
- const _hoisted_34$1 = {
3405
- key: 0,
3406
- class: "bcm-error",
3407
- role: "alert"
3408
- };
3409
- const _hoisted_35$1 = { class: "bcm-row" };
3410
- const _hoisted_36$1 = ["disabled"];
3411
- const _hoisted_37 = ["disabled"];
3412
- const _hoisted_38 = ["aria-labelledby"];
3413
- const _hoisted_39 = { class: "bcm-row" };
3414
- const _hoisted_40 = ["id"];
3415
- const _hoisted_41 = {
3416
- key: 0,
3417
- class: "bcm-badge"
3418
- };
3419
- const _hoisted_42 = ["disabled"];
3420
- const _hoisted_43 = {
3421
- key: 0,
3422
- class: "bcm-muted"
3423
- };
3424
- const _hoisted_44 = {
3425
- key: 1,
3426
- class: "bcm-list"
3427
- };
3428
- const _hoisted_45 = { key: 0 };
3429
- const _hoisted_46 = ["datetime"];
3430
- const _hoisted_47 = ["href"];
3431
- const _hoisted_48 = ["aria-labelledby"];
3432
- const _hoisted_49 = ["id"];
3433
- const _hoisted_50 = {
3434
- key: 0,
3435
- class: "bcm-muted"
3436
- };
3437
- const _hoisted_51 = {
3438
- key: 1,
3439
- class: "bcm-list"
3440
- };
3441
- const _hoisted_52 = { key: 0 };
3442
- const _hoisted_53 = {
3443
- key: 1,
3444
- class: "bcm-muted"
3445
- };
3446
- const _hoisted_54 = { class: "bcm-muted" };
3447
- const _hoisted_55 = {
3448
- key: 2,
3449
- class: "bcm-muted"
3450
- };
3451
- const _hoisted_56 = {
3452
- key: 3,
3453
- class: "bcm-muted"
3454
- };
3455
- const _hoisted_57 = ["href"];
3456
- const _hoisted_58 = ["aria-labelledby"];
3457
- const _hoisted_59 = ["id"];
3458
- const _hoisted_60 = {
3459
- key: 0,
3460
- class: "bcm-muted"
3461
- };
3462
- const _hoisted_61 = {
3463
- key: 1,
3464
- class: "bcm-list"
3465
- };
3466
- const _hoisted_62 = { key: 0 };
3467
- const _hoisted_63 = { key: 1 };
3468
- const _hoisted_64 = ["datetime"];
3469
- const _hoisted_65 = {
3470
- key: 2,
3471
- class: "bcm-muted"
3472
- };
3473
- const _hoisted_66 = ["aria-labelledby"];
3474
- const _hoisted_67 = ["id"];
3475
- const _hoisted_68 = {
3476
- key: 0,
3477
- class: "bcm-muted"
3478
- };
3479
- const _hoisted_69 = {
3480
- key: 1,
3481
- class: "bcm-list"
3482
- };
3483
- const _hoisted_70 = ["disabled", "onClick"];
3484
- const _hoisted_71 = ["aria-labelledby"];
3485
- const _hoisted_72 = ["id"];
3486
- const _hoisted_73 = { class: "bcm-row" };
3487
- const _hoisted_74 = ["disabled"];
3488
- const _hoisted_75 = { key: 1 };
3489
- const _hoisted_76 = ["aria-labelledby"];
3490
- const _hoisted_77 = ["id"];
3491
- const _hoisted_78 = { class: "bcm-hint" };
3492
- const _hoisted_79 = { class: "bcm-row" };
3493
- const _hoisted_80 = ["disabled"];
3494
- const _hoisted_81 = ["aria-labelledby"];
3495
- const _hoisted_82 = ["id"];
3496
- const _hoisted_83 = { class: "bcm-row" };
3497
- const _hoisted_84 = ["disabled"];
3498
- const _hoisted_85 = { class: "bcm-hint" };
3499
- const _hoisted_86 = { class: "bcm-row" };
3500
- const _hoisted_87 = ["disabled"];
3501
- const _hoisted_88 = {
3502
- class: "bcm-sr",
3503
- "aria-live": "polite"
3504
- };
3505
- const _sfc_main$1 = /* @__PURE__ */ defineComponent({
3506
- ...{ inheritAttrs: false },
3507
- __name: "MyCommunityView",
3508
- props: {
3509
- apiBase: {},
3510
- siteBase: {},
3511
- lang: {},
3512
- decisionId: {},
3513
- routes: {},
3514
- signInHref: {},
3515
- transport: { type: Function }
3516
- },
3517
- setup(__props) {
3518
- const props = __props;
3519
- const lang = ref(pickLang(props.lang, documentLang()));
3520
- const { t, text } = provideText(lang);
3521
- const id = uid("bcm-panel");
3522
- const heading = ref(null);
3523
- const siteBase = siteBaseFor(props.siteBase);
3524
- const client = siteBase === null ? null : communityClientFor(props.apiBase, props.transport, siteBase);
3525
- const ctl = client ? createPanel({ client, lang: lang.value }) : null;
3526
- const state = ctl?.state ?? null;
3527
- const routes = computed(() => routesWithDefaults(parseRoutes(props.routes), state?.me?.links, siteBase ?? ""));
3528
- const decisionMode = computed(() => !!props.decisionId);
3529
- const noticeMode = ref(false);
3530
- const confirming = ref(null);
3531
- const host = useHost();
3532
- onMounted(() => {
3533
- if (!ctl) return;
3534
- const receipt = receiptForElement(host);
3535
- if (receipt !== void 0) {
3536
- noticeMode.value = true;
3537
- void ctl.loadNotice(receipt);
3538
- }
3539
- if (props.decisionId) void ctl.loadStatement(props.decisionId);
3540
- else void ctl.loadMember();
3541
- });
3542
- watch(
3543
- () => props.decisionId,
3544
- (id2, before) => {
3545
- if (!ctl || id2 === before) return;
3546
- if (id2) void ctl.loadStatement(id2);
3547
- else if (state?.phase === "idle") void ctl.loadMember();
3548
- }
3549
- );
3550
- onBeforeUnmount(() => ctl?.dispose());
3551
- const memberFailure = computed(() => {
3552
- const f = state?.failure;
3553
- if (!f) return "";
3554
- const r = failureText(f, lang.value);
3555
- return t(r.key, r.params);
3556
- });
3557
- const statementFailure = computed(() => {
3558
- const f = state?.statement.failure;
3559
- if (!f) return "";
3560
- const r = failureText(f, lang.value);
3561
- return t(r.key, r.params);
3562
- });
3563
- const noticeFailure = computed(() => {
3564
- const f = state?.notice.failure;
3565
- if (!f) return "";
3566
- const r = failureText(f, lang.value);
3567
- return t(r.key, r.params);
3568
- });
3569
- function signInHref() {
3570
- return signInLink(props.signInHref, typeof location === "undefined" ? null : location.pathname);
3571
- }
3572
- const statementHref = (decisionId) => statementPath(routes.value.statementPath, decisionId);
3573
- const notificationKey = (n) => knownKey(`notification.${n.code}`);
3574
- const restrictionKey = (code) => knownKey(`restriction.${code}`);
3575
- const categoryKey = (code) => knownKey(`category.${code}`);
3576
- const caseStatusKey = (code) => knownKey(`caseStatus.${code}`);
3577
- const appealStatusKey = (decisionId) => {
3578
- const a = state?.appeals.find((x) => x.decisionId === decisionId);
3579
- return a ? knownKey(`appealStatus.${a.status}`) : null;
3580
- };
3581
- const consent = computed(() => state?.me?.acceptances.art9Consent ?? null);
3582
- const member = computed(() => state?.me?.member ?? null);
3583
- const profile = computed(() => state?.me?.profile ?? null);
3584
- function exportNow() {
3585
- void ctl?.exportData((data) => saveJson(data, exportFileName(t("panel.exportFileName"))));
3586
- }
3587
- async function confirmAction() {
3588
- if (!ctl) return;
3589
- if (confirming.value === "consent") await ctl.withdrawConsent();
3590
- else if (confirming.value === "erase") await ctl.erase();
3591
- confirming.value = null;
3592
- }
3593
- const liveText = ref("");
3594
- watch(
3595
- () => state?.announceId,
3596
- () => {
3597
- liveText.value = "";
3598
- const next = text(state?.announcement);
3599
- void nextTick(() => {
3600
- liveText.value = next;
3601
- });
3602
- }
3603
- );
3604
- return (_ctx, _cache) => {
3605
- return unref(ctl) && unref(state) ? (openBlock(), createElementBlock("section", {
3606
- key: 0,
3607
- class: "bcm bcm-panel",
3608
- lang: lang.value,
3609
- "aria-labelledby": `${unref(id)}-title`
3610
- }, [
3611
- createElementVNode("h2", {
3612
- id: `${unref(id)}-title`,
3613
- ref_key: "heading",
3614
- ref: heading,
3615
- tabindex: "-1"
3616
- }, toDisplayString(decisionMode.value ? unref(t)("statement.title") : unref(t)("panel.title")), 9, _hoisted_2$1),
3617
- noticeMode.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
3618
- unref(state).notice.phase === "missing" ? (openBlock(), createElementBlock("p", _hoisted_3$1, toDisplayString(unref(t)("notice.receiptMissing")), 1)) : unref(state).notice.phase === "loading" ? (openBlock(), createElementBlock("p", _hoisted_4$1, toDisplayString(unref(t)("common.loading")), 1)) : unref(state).notice.phase === "failed" ? (openBlock(), createElementBlock("p", _hoisted_5$1, toDisplayString(noticeFailure.value), 1)) : unref(state).notice.value ? (openBlock(), createBlock(_sfc_main$3, {
3619
- key: 3,
3620
- notice: unref(state).notice.value,
3621
- appeal: unref(state).notice.appeal,
3622
- withdrawing: !!unref(state).busy.noticeWithdraw,
3623
- onAppeal: _cache[0] || (_cache[0] = ($event) => unref(ctl).fileNoticeAppeal()),
3624
- onWithdraw: _cache[1] || (_cache[1] = ($event) => unref(ctl).withdrawNoticeAppeal())
3625
- }, null, 8, ["notice", "appeal", "withdrawing"])) : createCommentVNode("", true)
3626
- ], 64)) : createCommentVNode("", true),
3627
- decisionMode.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
3628
- unref(state).statement.phase === "loading" ? (openBlock(), createElementBlock("p", _hoisted_6$1, toDisplayString(unref(t)("common.loading")), 1)) : unref(state).statement.phase === "signIn" ? (openBlock(), createElementBlock("div", _hoisted_7$1, [
3629
- createElementVNode("p", null, toDisplayString(unref(t)("panel.signIn")), 1),
3630
- signInHref() ? (openBlock(), createElementBlock("a", {
3631
- key: 0,
3632
- class: "bcm-btn bcm-primary",
3633
- href: signInHref() ?? void 0
3634
- }, toDisplayString(unref(t)("common.signIn")), 9, _hoisted_8$1)) : createCommentVNode("", true)
3635
- ])) : unref(state).statement.phase === "failed" ? (openBlock(), createElementBlock("p", _hoisted_9$1, toDisplayString(statementFailure.value), 1)) : unref(state).statement.value ? (openBlock(), createBlock(_sfc_main$2, {
3636
- key: 3,
3637
- statement: unref(state).statement.value,
3638
- appeal: unref(state).statement.appeal,
3639
- withdrawing: !!unref(state).busy.withdraw,
3640
- onAppeal: _cache[2] || (_cache[2] = ($event) => unref(ctl).fileAppeal()),
3641
- onWithdraw: _cache[3] || (_cache[3] = ($event) => unref(ctl).withdrawAppeal())
3642
- }, null, 8, ["statement", "appeal", "withdrawing"])) : createCommentVNode("", true),
3643
- createElementVNode("p", null, [
3644
- createElementVNode("a", {
3645
- href: routes.value.myCommunityPath
3646
- }, toDisplayString(unref(t)("common.back")), 9, _hoisted_10$1)
3647
- ])
3648
- ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
3649
- unref(state).phase === "loading" ? (openBlock(), createElementBlock("p", _hoisted_11$1, toDisplayString(unref(t)("common.loading")), 1)) : unref(state).phase === "signIn" && !noticeMode.value ? (openBlock(), createElementBlock("div", _hoisted_12$1, [
3650
- createElementVNode("p", null, toDisplayString(unref(t)("panel.signIn")), 1),
3651
- signInHref() ? (openBlock(), createElementBlock("a", {
3652
- key: 0,
3653
- class: "bcm-btn bcm-primary",
3654
- href: signInHref() ?? void 0
3655
- }, toDisplayString(unref(t)("common.signIn")), 9, _hoisted_13$1)) : createCommentVNode("", true)
3656
- ])) : unref(state).phase === "failed" ? (openBlock(), createElementBlock("p", _hoisted_14$1, toDisplayString(memberFailure.value), 1)) : unref(state).phase === "ready" ? (openBlock(), createElementBlock(Fragment, { key: 3 }, [
3657
- profile.value && member.value ? (openBlock(), createElementBlock("section", {
3658
- key: 0,
3659
- class: "bcm-section",
3660
- "aria-labelledby": `${unref(id)}-profile`
3661
- }, [
3662
- createElementVNode("h3", {
3663
- id: `${unref(id)}-profile`
3664
- }, toDisplayString(unref(t)("panel.profile")), 9, _hoisted_16$1),
3665
- !unref(state).profile ? (openBlock(), createElementBlock("div", _hoisted_17$1, [
3666
- createVNode(_sfc_main$b, {
3667
- author: { kind: "member", memberId: member.value.memberId, displayName: profile.value.displayName, avatarPreset: profile.value.avatarPreset }
3668
- }, null, 8, ["author"]),
3669
- createElementVNode("div", _hoisted_18$1, [
3670
- createElementVNode("span", _hoisted_19$1, toDisplayString(profile.value.displayName), 1),
3671
- createElementVNode("span", _hoisted_20$1, toDisplayString(profile.value.handle), 1),
3672
- createElementVNode("div", _hoisted_21$1, [
3673
- createElementVNode("button", {
3674
- type: "button",
3675
- class: "bcm-btn bcm-small",
3676
- onClick: _cache[4] || (_cache[4] = ($event) => unref(ctl).editProfile())
3677
- }, toDisplayString(unref(t)("panel.profileEdit")), 1)
3678
- ])
3679
- ])
3680
- ])) : (openBlock(), createElementBlock("form", {
3681
- key: 1,
3682
- class: "bcm-form bcm-card",
3683
- novalidate: "",
3684
- onSubmit: _cache[10] || (_cache[10] = withModifiers(($event) => unref(ctl).saveProfile(), ["prevent"]))
3685
- }, [
3686
- createElementVNode("div", _hoisted_22$1, [
3687
- createElementVNode("label", {
3688
- for: `${unref(id)}-name`
3689
- }, toDisplayString(unref(t)("onboarding.displayName")), 9, _hoisted_23$1),
3690
- withDirectives(createElementVNode("input", {
3691
- id: `${unref(id)}-name`,
3692
- "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => unref(state).profile.displayName = $event),
3693
- class: "bcm-input",
3694
- autocomplete: "nickname",
3695
- "aria-invalid": unref(state).profile.fields.includes("displayName")
3696
- }, null, 8, _hoisted_24$1), [
3697
- [vModelText, unref(state).profile.displayName]
3698
- ]),
3699
- createElementVNode("p", _hoisted_25$1, toDisplayString(unref(t)("onboarding.displayNameHint")), 1)
3700
- ]),
3701
- createElementVNode("div", _hoisted_26$1, [
3702
- createElementVNode("label", {
3703
- for: `${unref(id)}-handle`
3704
- }, toDisplayString(unref(t)("onboarding.handle")), 9, _hoisted_27$1),
3705
- withDirectives(createElementVNode("input", {
3706
- id: `${unref(id)}-handle`,
3707
- "onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => unref(state).profile.handle = $event),
3708
- class: "bcm-input",
3709
- autocomplete: "username",
3710
- autocapitalize: "none",
3711
- spellcheck: "false",
3712
- maxlength: unref(HANDLE_MAX),
3713
- "aria-invalid": unref(state).profile.fields.includes("handle")
3714
- }, null, 8, _hoisted_28$1), [
3715
- [vModelText, unref(state).profile.handle]
3716
- ]),
3717
- createElementVNode("p", _hoisted_29$1, toDisplayString(unref(t)("onboarding.handleHint", { min: unref(HANDLE_MIN), max: unref(HANDLE_MAX), days: unref(COMMUNITY_HANDLE_CHANGE_DAYS) })), 1)
3718
- ]),
3719
- createElementVNode("fieldset", _hoisted_30$1, [
3720
- createElementVNode("legend", null, toDisplayString(unref(t)("onboarding.avatar")), 1),
3721
- createElementVNode("label", _hoisted_31$1, [
3722
- withDirectives(createElementVNode("input", {
3723
- "onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => unref(state).profile.avatarPreset = $event),
3724
- type: "radio",
3725
- name: `${unref(id)}-avatar`,
3726
- value: null
3727
- }, null, 8, _hoisted_32$1), [
3728
- [vModelRadio, unref(state).profile.avatarPreset]
3729
- ]),
3730
- createElementVNode("span", null, toDisplayString(unref(t)("onboarding.avatarNone")), 1)
3731
- ]),
3732
- (openBlock(true), createElementBlock(Fragment, null, renderList(unref(AVATAR_PRESETS), (preset) => {
3733
- return openBlock(), createElementBlock("label", {
3734
- key: preset.id,
3735
- class: "bcm-choice"
3736
- }, [
3737
- withDirectives(createElementVNode("input", {
3738
- "onUpdate:modelValue": _cache[8] || (_cache[8] = ($event) => unref(state).profile.avatarPreset = $event),
3739
- type: "radio",
3740
- name: `${unref(id)}-avatar`,
3741
- value: preset.id
3742
- }, null, 8, _hoisted_33$1), [
3743
- [vModelRadio, unref(state).profile.avatarPreset]
3744
- ]),
3745
- createElementVNode("span", null, toDisplayString(preset.emoji), 1)
3746
- ]);
3747
- }), 128))
3748
- ]),
3749
- unref(state).profile.failure ? (openBlock(), createElementBlock("p", _hoisted_34$1, toDisplayString(unref(text)(unref(failureText)(unref(state).profile.failure, lang.value))), 1)) : createCommentVNode("", true),
3750
- createElementVNode("div", _hoisted_35$1, [
3751
- _cache[20] || (_cache[20] = createElementVNode("span", { class: "bcm-spacer" }, null, -1)),
3752
- createElementVNode("button", {
3753
- type: "button",
3754
- class: "bcm-btn",
3755
- disabled: unref(state).profile.sending,
3756
- onClick: _cache[9] || (_cache[9] = ($event) => unref(ctl).cancelProfile())
3757
- }, toDisplayString(unref(t)("composer.cancel")), 9, _hoisted_36$1),
3758
- createElementVNode("button", {
3759
- type: "submit",
3760
- class: "bcm-btn bcm-primary",
3761
- disabled: unref(state).profile.sending
3762
- }, toDisplayString(unref(state).profile.sending ? unref(t)("composer.sending") : unref(t)("composer.save")), 9, _hoisted_37)
3763
- ])
3764
- ], 32))
3765
- ], 8, _hoisted_15$1)) : createCommentVNode("", true),
3766
- createElementVNode("section", {
3767
- class: "bcm-section",
3768
- "aria-labelledby": `${unref(id)}-notifications`
3769
- }, [
3770
- createElementVNode("div", _hoisted_39, [
3771
- createElementVNode("h3", {
3772
- id: `${unref(id)}-notifications`
3773
- }, toDisplayString(unref(t)("panel.notifications")), 9, _hoisted_40),
3774
- unref(ctl).unreadCount() > 0 ? (openBlock(), createElementBlock("span", _hoisted_41, toDisplayString(unref(t)("panel.unread", { count: unref(ctl).unreadCount() })), 1)) : createCommentVNode("", true),
3775
- _cache[21] || (_cache[21] = createElementVNode("span", { class: "bcm-spacer" }, null, -1)),
3776
- unref(ctl).unreadCount() > 0 ? (openBlock(), createElementBlock("button", {
3777
- key: 1,
3778
- type: "button",
3779
- class: "bcm-btn bcm-small",
3780
- disabled: !!unref(state).busy.markRead,
3781
- onClick: _cache[11] || (_cache[11] = ($event) => unref(ctl).markAllRead())
3782
- }, toDisplayString(unref(t)("panel.markRead")), 9, _hoisted_42)) : createCommentVNode("", true)
3783
- ]),
3784
- unref(state).notifications.length === 0 ? (openBlock(), createElementBlock("p", _hoisted_43, toDisplayString(unref(t)("panel.notificationsEmpty")), 1)) : (openBlock(), createElementBlock("ul", _hoisted_44, [
3785
- (openBlock(true), createElementBlock(Fragment, null, renderList(unref(state).notifications, (n) => {
3786
- return openBlock(), createElementBlock("li", {
3787
- key: n.id,
3788
- class: normalizeClass({ "bcm-unread": n.readAt === null })
3789
- }, [
3790
- notificationKey(n) ? (openBlock(), createElementBlock("p", _hoisted_45, toDisplayString(unref(t)(notificationKey(n))), 1)) : createCommentVNode("", true),
3791
- createElementVNode("time", {
3792
- class: "bcm-time",
3793
- datetime: n.createdAt
3794
- }, toDisplayString(unref(formatDate)(lang.value, n.createdAt)), 9, _hoisted_46),
3795
- n.decisionId && statementHref(n.decisionId) ? (openBlock(), createElementBlock("a", {
3796
- key: 1,
3797
- href: statementHref(n.decisionId) ?? void 0
3798
- }, toDisplayString(unref(t)("sanction.statement")), 9, _hoisted_47)) : createCommentVNode("", true)
3799
- ], 2);
3800
- }), 128))
3801
- ]))
3802
- ], 8, _hoisted_38),
3803
- createElementVNode("section", {
3804
- class: "bcm-section",
3805
- "aria-labelledby": `${unref(id)}-statements`
3806
- }, [
3807
- createElementVNode("h3", {
3808
- id: `${unref(id)}-statements`
3809
- }, toDisplayString(unref(t)("panel.statements")), 9, _hoisted_49),
3810
- unref(state).decisions.length === 0 ? (openBlock(), createElementBlock("p", _hoisted_50, toDisplayString(unref(t)("panel.statementsEmpty")), 1)) : (openBlock(), createElementBlock("ul", _hoisted_51, [
3811
- (openBlock(true), createElementBlock(Fragment, null, renderList(unref(state).decisions, (d) => {
3812
- return openBlock(), createElementBlock("li", {
3813
- key: d.decisionId
3814
- }, [
3815
- restrictionKey(d.restriction) ? (openBlock(), createElementBlock("p", _hoisted_52, [
3816
- createElementVNode("strong", null, toDisplayString(unref(t)(restrictionKey(d.restriction))), 1)
3817
- ])) : createCommentVNode("", true),
3818
- categoryKey(d.category) ? (openBlock(), createElementBlock("p", _hoisted_53, toDisplayString(unref(t)(categoryKey(d.category))), 1)) : createCommentVNode("", true),
3819
- createElementVNode("p", _hoisted_54, toDisplayString(unref(t)("statement.decidedAt", { date: unref(formatDate)(lang.value, d.decidedAt) })), 1),
3820
- appealStatusKey(d.decisionId) ? (openBlock(), createElementBlock("p", _hoisted_55, toDisplayString(unref(t)(appealStatusKey(d.decisionId))), 1)) : d.canAppeal ? (openBlock(), createElementBlock("p", _hoisted_56, toDisplayString(unref(t)("statement.appealUntil", { date: unref(formatDate)(lang.value, d.appealWindowClosesAt) })), 1)) : createCommentVNode("", true),
3821
- statementHref(d.decisionId) ? (openBlock(), createElementBlock("a", {
3822
- key: 4,
3823
- href: statementHref(d.decisionId) ?? void 0
3824
- }, toDisplayString(unref(t)("sanction.statement")), 9, _hoisted_57)) : createCommentVNode("", true)
3825
- ]);
3826
- }), 128))
3827
- ]))
3828
- ], 8, _hoisted_48),
3829
- createElementVNode("section", {
3830
- class: "bcm-section",
3831
- "aria-labelledby": `${unref(id)}-reports`
3832
- }, [
3833
- createElementVNode("h3", {
3834
- id: `${unref(id)}-reports`
3835
- }, toDisplayString(unref(t)("panel.reports")), 9, _hoisted_59),
3836
- unref(state).reports.length === 0 ? (openBlock(), createElementBlock("p", _hoisted_60, toDisplayString(unref(t)("panel.reportsEmpty")), 1)) : (openBlock(), createElementBlock("ul", _hoisted_61, [
3837
- (openBlock(true), createElementBlock(Fragment, null, renderList(unref(state).reports, (r) => {
3838
- return openBlock(), createElementBlock("li", {
3839
- key: r.caseId
3840
- }, [
3841
- categoryKey(r.category) ? (openBlock(), createElementBlock("p", _hoisted_62, [
3842
- createElementVNode("strong", null, toDisplayString(unref(t)(categoryKey(r.category))), 1)
3843
- ])) : createCommentVNode("", true),
3844
- caseStatusKey(r.status) ? (openBlock(), createElementBlock("p", _hoisted_63, toDisplayString(unref(t)(caseStatusKey(r.status))), 1)) : createCommentVNode("", true),
3845
- createElementVNode("time", {
3846
- class: "bcm-time",
3847
- datetime: r.reportedAt
3848
- }, toDisplayString(unref(formatDate)(lang.value, r.reportedAt)), 9, _hoisted_64),
3849
- r.decidedAt ? (openBlock(), createElementBlock("p", _hoisted_65, toDisplayString(unref(t)("statement.decidedAt", { date: unref(formatDate)(lang.value, r.decidedAt) })), 1)) : createCommentVNode("", true)
3850
- ]);
3851
- }), 128))
3852
- ]))
3853
- ], 8, _hoisted_58),
3854
- member.value ? (openBlock(), createElementBlock("section", {
3855
- key: 1,
3856
- class: "bcm-section",
3857
- "aria-labelledby": `${unref(id)}-blocks`
3858
- }, [
3859
- createElementVNode("h3", {
3860
- id: `${unref(id)}-blocks`
3861
- }, toDisplayString(unref(t)("panel.blocks")), 9, _hoisted_67),
3862
- unref(state).blocks.length === 0 ? (openBlock(), createElementBlock("p", _hoisted_68, toDisplayString(unref(t)("panel.blocksEmpty")), 1)) : (openBlock(), createElementBlock("ul", _hoisted_69, [
3863
- (openBlock(true), createElementBlock(Fragment, null, renderList(unref(state).blocks, (b) => {
3864
- return openBlock(), createElementBlock("li", {
3865
- key: b.memberId,
3866
- class: "bcm-row"
3867
- }, [
3868
- createElementVNode("span", null, toDisplayString(b.author.kind === "member" && "displayName" in b.author && b.author.displayName ? b.author.displayName : unref(t)("author.deleted")), 1),
3869
- _cache[22] || (_cache[22] = createElementVNode("span", { class: "bcm-spacer" }, null, -1)),
3870
- createElementVNode("button", {
3871
- type: "button",
3872
- class: "bcm-btn bcm-small",
3873
- disabled: !!unref(state).busy[`block:${b.memberId}`],
3874
- onClick: ($event) => unref(ctl).unblock(b.memberId)
3875
- }, toDisplayString(unref(t)("discussion.unblock")), 9, _hoisted_70)
3876
- ]);
3877
- }), 128))
3878
- ]))
3879
- ], 8, _hoisted_66)) : createCommentVNode("", true),
3880
- member.value && consent.value !== null ? (openBlock(), createElementBlock("section", {
3881
- key: 2,
3882
- class: "bcm-section",
3883
- "aria-labelledby": `${unref(id)}-consents`
3884
- }, [
3885
- createElementVNode("h3", {
3886
- id: `${unref(id)}-consents`
3887
- }, toDisplayString(unref(t)("panel.consents")), 9, _hoisted_72),
3888
- consent.value === "accepted" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
3889
- createElementVNode("p", null, toDisplayString(unref(t)("panel.art9Given")), 1),
3890
- createElementVNode("div", _hoisted_73, [
3891
- createElementVNode("button", {
3892
- type: "button",
3893
- class: "bcm-btn",
3894
- disabled: !!unref(state).busy.consent,
3895
- onClick: _cache[12] || (_cache[12] = ($event) => confirming.value = "consent")
3896
- }, toDisplayString(unref(t)("panel.art9Withdraw")), 9, _hoisted_74)
3897
- ])
3898
- ], 64)) : (openBlock(), createElementBlock("p", _hoisted_75, toDisplayString(unref(t)("panel.art9NotGiven")), 1))
3899
- ], 8, _hoisted_71)) : createCommentVNode("", true),
3900
- member.value ? (openBlock(), createElementBlock("section", {
3901
- key: 3,
3902
- class: "bcm-section",
3903
- "aria-labelledby": `${unref(id)}-export`
3904
- }, [
3905
- createElementVNode("h3", {
3906
- id: `${unref(id)}-export`
3907
- }, toDisplayString(unref(t)("panel.export")), 9, _hoisted_77),
3908
- createElementVNode("p", _hoisted_78, toDisplayString(unref(t)("panel.exportHint")), 1),
3909
- createElementVNode("div", _hoisted_79, [
3910
- createElementVNode("button", {
3911
- type: "button",
3912
- class: "bcm-btn",
3913
- disabled: unref(state).exporting,
3914
- onClick: _cache[13] || (_cache[13] = ($event) => exportNow())
3915
- }, toDisplayString(unref(state).exporting ? unref(t)("panel.exporting") : unref(t)("panel.export")), 9, _hoisted_80)
3916
- ])
3917
- ], 8, _hoisted_76)) : createCommentVNode("", true),
3918
- member.value ? (openBlock(), createElementBlock("section", {
3919
- key: 4,
3920
- class: "bcm-section",
3921
- "aria-labelledby": `${unref(id)}-erase`
3922
- }, [
3923
- createElementVNode("h3", {
3924
- id: `${unref(id)}-erase`
3925
- }, toDisplayString(unref(t)("panel.erase")), 9, _hoisted_82),
3926
- unref(ctl).erasing() ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
3927
- createElementVNode("p", null, toDisplayString(unref(state).purgeAfter ? unref(t)("panel.eraseScheduled", { date: unref(formatDate)(lang.value, unref(state).purgeAfter) }) : unref(t)("notification.erasure_scheduled")), 1),
3928
- createElementVNode("div", _hoisted_83, [
3929
- createElementVNode("button", {
3930
- type: "button",
3931
- class: "bcm-btn bcm-primary",
3932
- disabled: !!unref(state).busy.erase,
3933
- onClick: _cache[14] || (_cache[14] = ($event) => unref(ctl).cancelErase())
3934
- }, toDisplayString(unref(t)("panel.eraseCancel")), 9, _hoisted_84)
3935
- ])
3936
- ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
3937
- createElementVNode("p", _hoisted_85, toDisplayString(unref(t)("panel.eraseExplain")), 1),
3938
- createElementVNode("div", _hoisted_86, [
3939
- createElementVNode("button", {
3940
- type: "button",
3941
- class: "bcm-btn bcm-danger",
3942
- disabled: !!unref(state).busy.erase,
3943
- onClick: _cache[15] || (_cache[15] = ($event) => confirming.value = "erase")
3944
- }, toDisplayString(unref(t)("panel.erase")), 9, _hoisted_87)
3945
- ])
3946
- ], 64))
3947
- ], 8, _hoisted_81)) : createCommentVNode("", true)
3948
- ], 64)) : createCommentVNode("", true)
3949
- ], 64)),
3950
- createElementVNode("p", _hoisted_88, toDisplayString(liveText.value), 1),
3951
- confirming.value === "consent" ? (openBlock(), createBlock(_sfc_main$d, {
3952
- key: 3,
3953
- title: "panel.art9WithdrawTitle",
3954
- explain: "panel.art9WithdrawExplain",
3955
- confirm: "panel.art9WithdrawConfirm",
3956
- cancel: "panel.art9WithdrawCancel",
3957
- busy: !!unref(state).busy.consent,
3958
- "fallback-focus": () => heading.value,
3959
- onConfirm: _cache[16] || (_cache[16] = ($event) => confirmAction()),
3960
- onCancel: _cache[17] || (_cache[17] = ($event) => confirming.value = null)
3961
- }, null, 8, ["busy", "fallback-focus"])) : createCommentVNode("", true),
3962
- confirming.value === "erase" ? (openBlock(), createBlock(_sfc_main$d, {
3963
- key: 4,
3964
- title: "panel.eraseTitle",
3965
- explain: "panel.eraseExplain",
3966
- confirm: "panel.eraseConfirm",
3967
- cancel: "panel.eraseKeep",
3968
- danger: "",
3969
- busy: !!unref(state).busy.erase,
3970
- "fallback-focus": () => heading.value,
3971
- onConfirm: _cache[18] || (_cache[18] = ($event) => confirmAction()),
3972
- onCancel: _cache[19] || (_cache[19] = ($event) => confirming.value = null)
3973
- }, null, 8, ["busy", "fallback-focus"])) : createCommentVNode("", true)
3974
- ], 8, _hoisted_1$1)) : createCommentVNode("", true);
3975
- };
3976
- }
3977
- });
3978
- function createNoticeForm(o) {
3979
- const abort = new AbortController();
3980
- const call = { signal: abort.signal };
3981
- const state = reactive({
3982
- target: o.target,
3983
- category: "",
3984
- ruleId: "",
3985
- rules: null,
3986
- description: "",
3987
- name: "",
3988
- email: "",
3989
- goodFaith: false,
3990
- captcha: o.captcha ? "loading" : "off",
3991
- token: null,
3992
- resetSignal: 0,
3993
- sending: false,
3994
- failure: null,
3995
- receipt: null,
3996
- statusLink: null
3997
- });
3998
- let rulesAsked = false;
3999
- const contactAllowed = () => state.category !== CATEGORY_WITHOUT_NOTIFIER;
4000
- const descriptionLength = () => codePointLength(normalizeBody(state.description));
4001
- function canSubmit() {
4002
- const length = descriptionLength();
4003
- return !state.sending && state.receipt === null && state.target !== null && state.category !== "" && length > 0 && length <= COMMUNITY_NOTICE_DESCRIPTION_MAX && codePointLength(state.name.trim()) <= COMMUNITY_NOTIFIER_NAME_MAX && state.goodFaith && (state.captcha === "off" || state.captcha === "ready" && state.token !== null);
4004
- }
4005
- function loadRules() {
4006
- if (rulesAsked) return;
4007
- rulesAsked = true;
4008
- void o.client.legal.current({ kind: "rules", locale: o.lang }, call).then((r) => {
4009
- state.rules = r.ok ? communityRulesOf(r.value.bodyJson) : [];
4010
- });
4011
- }
4012
- async function submit() {
4013
- if (!canSubmit() || !state.target || state.category === "") return;
4014
- state.sending = true;
4015
- state.failure = null;
4016
- const input = {
4017
- category: state.category,
4018
- target: state.target,
4019
- description: normalizeBody(state.description),
4020
- confirmations: [COMMUNITY_GOOD_FAITH]
4021
- };
4022
- if (state.category === "rules" && state.ruleId !== "") input.ruleId = state.ruleId;
4023
- const name = state.name.trim();
4024
- const email = state.email.trim();
4025
- if (contactAllowed() && (name !== "" || email !== "")) input.notifier = { name: name || null, email: email || null, locale: o.lang };
4026
- if (state.token !== null) input.turnstileToken = state.token;
4027
- state.token = null;
4028
- const r = await o.client.notices.submit(input, call);
4029
- state.sending = false;
4030
- if (r.ok) {
4031
- state.receipt = r.value;
4032
- state.statusLink = noticeStatusLink(o.noticesPath, r.value.receipt);
4033
- o.onSubmitted?.({ contactKept: r.value.contactKept });
4034
- return;
4035
- }
4036
- state.failure = r;
4037
- if (state.captcha !== "off") state.resetSignal += 1;
4038
- }
4039
- return {
4040
- state,
4041
- contactAllowed,
4042
- canSubmit,
4043
- descriptionLength,
4044
- setCategory(category) {
4045
- state.category = category;
4046
- if (category !== "rules") state.ruleId = "";
4047
- else loadRules();
4048
- },
4049
- setToken(token) {
4050
- state.token = token;
4051
- if (token !== null && state.captcha === "loading") state.captcha = "ready";
4052
- },
4053
- setCaptcha(captcha) {
4054
- if (state.captcha !== "off") state.captcha = captcha;
4055
- },
4056
- submit,
4057
- dispose() {
4058
- abort.abort();
4059
- }
4060
- };
4061
- }
4062
- const TURNSTILE_SCRIPT_SRC = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
4063
- let scriptPromise = null;
4064
- function loadTurnstile(nonce) {
4065
- const w = window;
4066
- if (w.turnstile) return Promise.resolve(w.turnstile);
4067
- if (scriptPromise) return scriptPromise;
4068
- scriptPromise = new Promise((resolve, reject) => {
4069
- const script = document.createElement("script");
4070
- script.src = TURNSTILE_SCRIPT_SRC;
4071
- script.async = true;
4072
- script.defer = true;
4073
- if (nonce) script.nonce = nonce;
4074
- script.onload = () => w.turnstile ? resolve(w.turnstile) : reject(new Error("[blogmcp-community] the Turnstile script loaded without its API"));
4075
- script.onerror = () => reject(new Error("[blogmcp-community] the Turnstile script failed to load"));
4076
- document.head.appendChild(script);
4077
- }).catch((err) => {
4078
- scriptPromise = null;
4079
- throw err;
4080
- });
4081
- return scriptPromise;
4082
- }
4083
- async function renderTurnstile(container, o) {
4084
- const api = await loadTurnstile(o.nonce);
4085
- const id = api.render(container, {
4086
- sitekey: o.siteKey,
4087
- // kb-api refuses a token issued for any other action.
4088
- action: COMMUNITY_NOTICE_TURNSTILE_ACTION,
4089
- language: o.language,
4090
- // Visible only when Cloudflare really wants an interaction.
4091
- appearance: "interaction-only",
4092
- callback: (token) => o.onToken(token),
4093
- "expired-callback": () => o.onToken(null),
4094
- "error-callback": () => o.onToken(null),
4095
- "timeout-callback": () => o.onToken(null)
4096
- });
4097
- let removed = false;
4098
- return {
4099
- reset() {
4100
- if (removed) return;
4101
- o.onToken(null);
4102
- api.reset(id);
4103
- },
4104
- remove() {
4105
- if (removed) return;
4106
- removed = true;
4107
- api.remove(id);
4108
- }
4109
- };
4110
- }
4111
- const _hoisted_1 = ["lang", "aria-labelledby"];
4112
- const _hoisted_2 = ["id"];
4113
- const _hoisted_3 = { key: 0 };
4114
- const _hoisted_4 = ["href"];
4115
- const _hoisted_5 = ["value", "aria-label"];
4116
- const _hoisted_6 = {
4117
- key: 2,
4118
- class: "bcm-note"
4119
- };
4120
- const _hoisted_7 = { class: "bcm-field" };
4121
- const _hoisted_8 = { class: "bcm-label" };
4122
- const _hoisted_9 = { class: "bcm-choices" };
4123
- const _hoisted_10 = ["name", "value", "checked", "onChange"];
4124
- const _hoisted_11 = {
4125
- key: 0,
4126
- class: "bcm-field"
4127
- };
4128
- const _hoisted_12 = ["for"];
4129
- const _hoisted_13 = ["id"];
4130
- const _hoisted_14 = ["value"];
4131
- const _hoisted_15 = { class: "bcm-field" };
4132
- const _hoisted_16 = ["for"];
4133
- const _hoisted_17 = ["id", "aria-describedby", "aria-invalid"];
4134
- const _hoisted_18 = ["id"];
4135
- const _hoisted_19 = {
4136
- key: 1,
4137
- class: "bcm-fieldset"
4138
- };
4139
- const _hoisted_20 = { class: "bcm-hint" };
4140
- const _hoisted_21 = { class: "bcm-field" };
4141
- const _hoisted_22 = ["for"];
4142
- const _hoisted_23 = ["id", "maxlength"];
4143
- const _hoisted_24 = { class: "bcm-field" };
4144
- const _hoisted_25 = ["for"];
4145
- const _hoisted_26 = ["id", "aria-invalid"];
4146
- const _hoisted_27 = {
4147
- key: 2,
4148
- class: "bcm-note"
4149
- };
4150
- const _hoisted_28 = {
4151
- key: 3,
4152
- class: "bcm-turnstile"
4153
- };
4154
- const _hoisted_29 = {
4155
- key: 0,
4156
- class: "bcm-error",
4157
- role: "alert"
4158
- };
4159
- const _hoisted_30 = { class: "bcm-check" };
4160
- const _hoisted_31 = {
4161
- key: 4,
4162
- class: "bcm-error",
4163
- role: "alert"
4164
- };
4165
- const _hoisted_32 = { class: "bcm-row" };
4166
- const _hoisted_33 = ["disabled"];
4167
- const _hoisted_34 = ["lang", "aria-labelledby"];
4168
- const _hoisted_35 = ["id"];
4169
- const _hoisted_36 = ["href"];
4170
- const _sfc_main = /* @__PURE__ */ defineComponent({
4171
- ...{ inheritAttrs: false },
4172
- __name: "NoticeFormView",
4173
- props: {
4174
- apiBase: {},
4175
- siteBase: {},
4176
- lang: {},
4177
- turnstileSiteKey: {},
4178
- target: {},
4179
- articleId: {},
4180
- routes: {},
4181
- transport: { type: Function }
4182
- },
4183
- setup(__props) {
4184
- const props = __props;
4185
- const host = useHost();
4186
- const lang = ref(pickLang(props.lang, documentLang()));
4187
- const { t } = provideText(lang);
4188
- const id = uid("bcm-notice");
4189
- const heading = ref(null);
4190
- const done = ref(null);
4191
- function resolveTarget() {
4192
- const fromAttr = parseTarget(props.target);
4193
- if (fromAttr) return fromAttr;
4194
- if (props.articleId) return parseTarget({ articleId: props.articleId });
4195
- return typeof location === "undefined" ? null : targetFromHash(location.hash);
4196
- }
4197
- const siteBase = siteBaseFor(props.siteBase);
4198
- const routes = computed(() => routesWithDefaults(parseRoutes(props.routes), null, siteBase ?? ""));
4199
- const ctl = shallowRef(null);
4200
- const client = siteBase === null ? null : communityClientFor(props.apiBase, props.transport, siteBase);
4201
- const target = resolveTarget();
4202
- const untargeted = !!client && !target;
4203
- if (untargeted) {
4204
- warnOnce(
4205
- "notice-target",
4206
- "[blogmcp-community] <blogmcp-notice-form> has no target (a target, an article-id or the report link’s #c=…&s= fragment): it shows where a report starts instead of the form"
4207
- );
4208
- }
4209
- if (client && target) {
4210
- ctl.value = createNoticeForm({
4211
- client,
4212
- lang: lang.value,
4213
- target,
4214
- captcha: !!props.turnstileSiteKey,
4215
- noticesPath: routes.value.noticesPath,
4216
- onSubmitted: (answer) => host?.dispatchEvent(new CustomEvent("bcm-notice-submitted", { detail: answer, bubbles: true, composed: true }))
4217
- });
4218
- }
4219
- const state = computed(() => ctl.value?.state ?? null);
4220
- const kind = computed(() => state.value?.target ? targetKind(state.value.target) : null);
4221
- const targetText = computed(() => kind.value === "message" ? t("notice.targetMessage") : kind.value === "article" ? t("notice.targetArticle") : t("notice.targetProfile"));
4222
- const categoryKey = (c) => `category.${c}`;
4223
- const failure = computed(() => {
4224
- const f = state.value?.failure;
4225
- if (!f) return "";
4226
- const r = failureText(f, lang.value);
4227
- return t(r.key, r.params);
4228
- });
4229
- const absoluteStatusLink = computed(() => {
4230
- const link = state.value?.statusLink;
4231
- return link && typeof location !== "undefined" ? `${location.origin}${link}` : link ?? "";
4232
- });
4233
- let widget = null;
4234
- let slotted = null;
4235
- let gone = false;
4236
- onMounted(async () => {
4237
- const c = ctl.value;
4238
- if (!c || !props.turnstileSiteKey || !host) return;
4239
- slotted = document.createElement("div");
4240
- slotted.slot = "turnstile";
4241
- host.appendChild(slotted);
4242
- try {
4243
- const nonce = host.nonce || host.getAttribute("nonce") || void 0;
4244
- const w = await renderTurnstile(slotted, { siteKey: props.turnstileSiteKey, language: lang.value, onToken: (token) => c.setToken(token), nonce });
4245
- if (gone) {
4246
- w.remove();
4247
- return;
4248
- }
4249
- widget = w;
4250
- c.setCaptcha("ready");
4251
- } catch {
4252
- c.setCaptcha("unavailable");
4253
- }
4254
- });
4255
- watch(
4256
- () => state.value?.resetSignal,
4257
- (n, before) => {
4258
- if (n !== void 0 && before !== void 0 && n !== before) widget?.reset();
4259
- }
4260
- );
4261
- watch(
4262
- () => state.value?.receipt,
4263
- (receipt) => {
4264
- if (!receipt) return;
4265
- widget?.remove();
4266
- widget = null;
4267
- void nextTick(() => done.value?.focus());
4268
- }
4269
- );
4270
- onBeforeUnmount(() => {
4271
- gone = true;
4272
- widget?.remove();
4273
- widget = null;
4274
- slotted?.remove();
4275
- slotted = null;
4276
- ctl.value?.dispose();
4277
- });
4278
- return (_ctx, _cache) => {
4279
- return ctl.value && state.value ? (openBlock(), createElementBlock("section", {
4280
- key: 0,
4281
- class: "bcm bcm-notice",
4282
- lang: lang.value,
4283
- "aria-labelledby": `${unref(id)}-title`
4284
- }, [
4285
- createElementVNode("h2", {
4286
- id: `${unref(id)}-title`,
4287
- ref_key: "heading",
4288
- ref: heading,
4289
- tabindex: "-1"
4290
- }, toDisplayString(unref(t)("notice.title")), 9, _hoisted_2),
4291
- state.value.receipt ? (openBlock(), createElementBlock("div", {
4292
- key: 0,
4293
- ref_key: "done",
4294
- ref: done,
4295
- class: "bcm-receipt",
4296
- role: "status",
4297
- tabindex: "-1"
4298
- }, [
4299
- createElementVNode("p", null, toDisplayString(unref(t)("notice.done")), 1),
4300
- createElementVNode("p", null, toDisplayString(unref(t)("notice.receiptLede")), 1),
4301
- state.value.statusLink ? (openBlock(), createElementBlock("p", _hoisted_3, [
4302
- createElementVNode("a", {
4303
- href: state.value.statusLink
4304
- }, toDisplayString(unref(t)("notice.statusLink")), 9, _hoisted_4)
4305
- ])) : createCommentVNode("", true),
4306
- absoluteStatusLink.value ? (openBlock(), createElementBlock("input", {
4307
- key: 1,
4308
- class: "bcm-input",
4309
- readonly: "",
4310
- value: absoluteStatusLink.value,
4311
- "aria-label": unref(t)("notice.statusLink"),
4312
- onFocus: _cache[0] || (_cache[0] = ($event) => $event.target.select())
4313
- }, null, 40, _hoisted_5)) : createCommentVNode("", true),
4314
- !state.value.receipt.contactKept ? (openBlock(), createElementBlock("p", _hoisted_6, toDisplayString(unref(t)("notice.noContactKept")), 1)) : createCommentVNode("", true)
4315
- ], 512)) : (openBlock(), createElementBlock("form", {
4316
- key: 1,
4317
- class: "bcm-form",
4318
- novalidate: "",
4319
- onSubmit: _cache[6] || (_cache[6] = withModifiers(($event) => ctl.value.submit(), ["prevent"]))
4320
- }, [
4321
- createElementVNode("p", null, toDisplayString(unref(t)("notice.lede")), 1),
4322
- createElementVNode("div", _hoisted_7, [
4323
- createElementVNode("span", _hoisted_8, toDisplayString(unref(t)("notice.target")), 1),
4324
- createElementVNode("span", null, toDisplayString(targetText.value), 1)
4325
- ]),
4326
- createElementVNode("fieldset", _hoisted_9, [
4327
- createElementVNode("legend", null, toDisplayString(unref(t)("notice.category")), 1),
4328
- (openBlock(true), createElementBlock(Fragment, null, renderList(unref(CASE_CATEGORIES), (c) => {
4329
- return openBlock(), createElementBlock("label", {
4330
- key: c,
4331
- class: "bcm-choice"
4332
- }, [
4333
- createElementVNode("input", {
4334
- type: "radio",
4335
- name: `${unref(id)}-category`,
4336
- value: c,
4337
- checked: state.value.category === c,
4338
- onChange: ($event) => ctl.value.setCategory(c)
4339
- }, null, 40, _hoisted_10),
4340
- createElementVNode("span", null, toDisplayString(unref(t)(categoryKey(c))), 1)
4341
- ]);
4342
- }), 128))
4343
- ]),
4344
- state.value.category === "rules" && state.value.rules && state.value.rules.length ? (openBlock(), createElementBlock("div", _hoisted_11, [
4345
- createElementVNode("label", {
4346
- for: `${unref(id)}-rule`
4347
- }, toDisplayString(unref(t)("notice.rule")), 9, _hoisted_12),
4348
- withDirectives(createElementVNode("select", {
4349
- id: `${unref(id)}-rule`,
4350
- "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => state.value.ruleId = $event),
4351
- class: "bcm-input"
4352
- }, [
4353
- _cache[7] || (_cache[7] = createElementVNode("option", { value: "" }, null, -1)),
4354
- (openBlock(true), createElementBlock(Fragment, null, renderList(state.value.rules, (rule) => {
4355
- return openBlock(), createElementBlock("option", {
4356
- key: rule.id,
4357
- value: rule.id
4358
- }, toDisplayString(rule.title), 9, _hoisted_14);
4359
- }), 128))
4360
- ], 8, _hoisted_13), [
4361
- [vModelSelect, state.value.ruleId]
4362
- ])
4363
- ])) : createCommentVNode("", true),
4364
- createElementVNode("div", _hoisted_15, [
4365
- createElementVNode("label", {
4366
- for: `${unref(id)}-description`
4367
- }, toDisplayString(unref(t)("notice.description")), 9, _hoisted_16),
4368
- withDirectives(createElementVNode("textarea", {
4369
- id: `${unref(id)}-description`,
4370
- "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => state.value.description = $event),
4371
- class: "bcm-input bcm-textarea",
4372
- rows: "5",
4373
- "aria-describedby": `${unref(id)}-description-hint`,
4374
- "aria-invalid": ctl.value.descriptionLength() > unref(COMMUNITY_NOTICE_DESCRIPTION_MAX)
4375
- }, null, 8, _hoisted_17), [
4376
- [vModelText, state.value.description]
4377
- ]),
4378
- createElementVNode("p", {
4379
- id: `${unref(id)}-description-hint`,
4380
- class: "bcm-hint"
4381
- }, toDisplayString(unref(t)("notice.descriptionHint")) + " " + toDisplayString(unref(t)("notice.counter", { length: ctl.value.descriptionLength(), max: unref(COMMUNITY_NOTICE_DESCRIPTION_MAX) })), 9, _hoisted_18)
4382
- ]),
4383
- ctl.value.contactAllowed() ? (openBlock(), createElementBlock("fieldset", _hoisted_19, [
4384
- createElementVNode("legend", null, toDisplayString(unref(t)("notice.contactTitle")), 1),
4385
- createElementVNode("p", _hoisted_20, toDisplayString(unref(t)("notice.contactHint")), 1),
4386
- createElementVNode("div", _hoisted_21, [
4387
- createElementVNode("label", {
4388
- for: `${unref(id)}-name`
4389
- }, toDisplayString(unref(t)("notice.name")), 9, _hoisted_22),
4390
- withDirectives(createElementVNode("input", {
4391
- id: `${unref(id)}-name`,
4392
- "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => state.value.name = $event),
4393
- class: "bcm-input",
4394
- autocomplete: "name",
4395
- maxlength: unref(COMMUNITY_NOTIFIER_NAME_MAX)
4396
- }, null, 8, _hoisted_23), [
4397
- [vModelText, state.value.name]
4398
- ])
4399
- ]),
4400
- createElementVNode("div", _hoisted_24, [
4401
- createElementVNode("label", {
4402
- for: `${unref(id)}-email`
4403
- }, toDisplayString(unref(t)("notice.email")), 9, _hoisted_25),
4404
- withDirectives(createElementVNode("input", {
4405
- id: `${unref(id)}-email`,
4406
- "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => state.value.email = $event),
4407
- class: "bcm-input",
4408
- type: "email",
4409
- autocomplete: "email",
4410
- "aria-invalid": !!state.value.failure?.fields?.includes("notifier.email")
4411
- }, null, 8, _hoisted_26), [
4412
- [vModelText, state.value.email]
4413
- ])
4414
- ])
4415
- ])) : (openBlock(), createElementBlock("p", _hoisted_27, toDisplayString(unref(t)("notice.childAtRisk")), 1)),
4416
- state.value.captcha !== "off" ? (openBlock(), createElementBlock("div", _hoisted_28, [
4417
- renderSlot(_ctx.$slots, "turnstile"),
4418
- state.value.captcha === "unavailable" ? (openBlock(), createElementBlock("p", _hoisted_29, toDisplayString(unref(t)("notice.captchaUnavailable")), 1)) : createCommentVNode("", true)
4419
- ])) : createCommentVNode("", true),
4420
- createElementVNode("label", _hoisted_30, [
4421
- withDirectives(createElementVNode("input", {
4422
- "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => state.value.goodFaith = $event),
4423
- type: "checkbox"
4424
- }, null, 512), [
4425
- [vModelCheckbox, state.value.goodFaith]
4426
- ]),
4427
- createElementVNode("span", null, toDisplayString(unref(t)("confirm.good_faith")), 1)
4428
- ]),
4429
- failure.value ? (openBlock(), createElementBlock("p", _hoisted_31, toDisplayString(failure.value), 1)) : createCommentVNode("", true),
4430
- createElementVNode("div", _hoisted_32, [
4431
- _cache[8] || (_cache[8] = createElementVNode("span", { class: "bcm-spacer" }, null, -1)),
4432
- createElementVNode("button", {
4433
- type: "submit",
4434
- class: "bcm-btn bcm-primary",
4435
- disabled: !ctl.value.canSubmit()
4436
- }, toDisplayString(state.value.sending ? unref(t)("notice.sending") : unref(t)("notice.submit")), 9, _hoisted_33)
4437
- ])
4438
- ], 32))
4439
- ], 8, _hoisted_1)) : unref(untargeted) ? (openBlock(), createElementBlock("section", {
4440
- key: 1,
4441
- class: "bcm bcm-notice",
4442
- lang: lang.value,
4443
- "aria-labelledby": `${unref(id)}-title`
4444
- }, [
4445
- createElementVNode("h2", {
4446
- id: `${unref(id)}-title`
4447
- }, toDisplayString(unref(t)("notice.title")), 9, _hoisted_35),
4448
- createElementVNode("p", null, toDisplayString(unref(t)("notice.noTarget")), 1),
4449
- createElementVNode("p", null, [
4450
- createElementVNode("a", {
4451
- href: routes.value.rulesPath
4452
- }, toDisplayString(unref(t)("onboarding.rulesTitle")), 9, _hoisted_36)
4453
- ])
4454
- ], 8, _hoisted_34)) : createCommentVNode("", true);
4455
- };
4456
- }
4457
- });
4458
- const css = "/* The community elements' one stylesheet. It lives in each element's shadow root: nothing here reaches the\n site, nothing of the site's reaches here but what :host lets through. A site themes the elements with the\n --bcm-* custom properties (set them on the element: `blogmcp-discussion { --bcm-accent: … }`). */\n\n:host {\n all: initial;\n display: block;\n --bcm-bg: #ffffff;\n --bcm-fg: #1d1f23;\n --bcm-muted: #5f6672;\n --bcm-line: #dfe2e7;\n --bcm-soft: #f4f5f7;\n --bcm-accent: #2457d6;\n --bcm-accent-fg: #ffffff;\n --bcm-danger: #b42318;\n --bcm-ok: #1f7a3f;\n --bcm-warn-bg: #fff6d6;\n --bcm-shadow: 0 8px 28px rgba(16, 24, 40, 0.18);\n --bcm-z: 20;\n color-scheme: light dark;\n font: var(--bcm-font-size, 16px) / 1.5 var(--bcm-font, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif);\n color: var(--bcm-fg);\n}\n:host([hidden]) { display: none; }\n\n@media (prefers-color-scheme: dark) {\n :host {\n --bcm-bg: #1c1f24;\n --bcm-fg: #eceef1;\n --bcm-muted: #a4abb6;\n --bcm-line: #343943;\n --bcm-soft: #262a31;\n --bcm-accent: #6d93ff;\n --bcm-accent-fg: #0d1117;\n --bcm-danger: #ff8a7a;\n --bcm-ok: #6fd08e;\n --bcm-warn-bg: #3a3420;\n --bcm-shadow: 0 8px 28px rgba(0, 0, 0, 0.5);\n }\n}\n\n* { box-sizing: border-box; }\nh2, h3, h4 { margin: 0; font-weight: 650; line-height: 1.3; }\nh2 { font-size: 1.25em; }\nh3 { font-size: 1.05em; }\nh4 { font-size: 1em; }\np { margin: 0; }\nul, ol { margin: 0; padding: 0; }\na { color: var(--bcm-accent); }\n\n.bcm {\n background: var(--bcm-bg);\n color: var(--bcm-fg);\n border-radius: 14px;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n max-width: 100%;\n overflow-wrap: anywhere;\n}\n\n/* visually hidden, read by screen readers */\n.bcm-sr {\n position: absolute !important;\n width: 1px; height: 1px; padding: 0; margin: -1px;\n overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;\n}\n\n/* ── controls ── */\n.bcm-btn {\n font: inherit;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n min-height: 36px;\n padding: 6px 14px;\n border-radius: 8px;\n border: 1px solid var(--bcm-line);\n background: var(--bcm-bg);\n color: var(--bcm-fg);\n cursor: pointer;\n text-decoration: none;\n}\n.bcm-btn:hover:not(:disabled) { background: var(--bcm-soft); }\n.bcm-btn:disabled { opacity: 0.55; cursor: default; }\n.bcm-primary { background: var(--bcm-accent); border-color: var(--bcm-accent); color: var(--bcm-accent-fg); }\n.bcm-primary:hover:not(:disabled) { background: var(--bcm-accent); filter: brightness(1.08); }\n.bcm-danger { color: var(--bcm-danger); }\n.bcm-primary.bcm-danger { background: var(--bcm-danger); border-color: var(--bcm-danger); color: var(--bcm-bg); }\n.bcm-small { min-height: 30px; padding: 2px 10px; font-size: 0.875em; }\n.bcm-link { font: inherit; border: 0; background: none; padding: 0; color: var(--bcm-accent); cursor: pointer; text-align: left; text-decoration: underline; }\n.bcm-quiet { font: inherit; border: 0; background: none; padding: 4px 6px; color: var(--bcm-muted); cursor: pointer; border-radius: 6px; min-height: 30px; }\n.bcm-quiet:hover:not(:disabled) { background: var(--bcm-soft); color: var(--bcm-fg); }\n.bcm-btn:focus-visible, .bcm-link:focus-visible, .bcm-quiet:focus-visible, .bcm-input:focus-visible, .bcm-react:focus-visible, a:focus-visible,\n.bcm-choice input:focus-visible + span {\n outline: 2px solid var(--bcm-accent);\n outline-offset: 2px;\n}\n\n.bcm-input {\n font: inherit;\n width: 100%;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--bcm-line);\n background: var(--bcm-bg);\n color: var(--bcm-fg);\n}\n.bcm-input[aria-invalid='true'] { border-color: var(--bcm-danger); border-width: 2px; }\n.bcm-textarea { resize: vertical; min-height: 96px; }\n.bcm-field { display: flex; flex-direction: column; gap: 4px; }\n.bcm-field > label, .bcm-label { font-weight: 600; }\n.bcm-check { display: flex; gap: 8px; align-items: flex-start; }\n.bcm-check input { margin-top: 0.3em; width: 18px; height: 18px; flex: none; }\n.bcm-choices { display: flex; flex-wrap: wrap; gap: 8px; border: 0; margin: 0; padding: 0; min-width: 0; }\n.bcm-choices legend, .bcm-fieldset legend { font-weight: 600; padding: 0; margin-bottom: 6px; }\n.bcm-fieldset { border: 0; margin: 0; padding: 0; min-width: 0; display: flex; flex-direction: column; gap: 8px; }\n.bcm-choice { position: relative; display: inline-flex; }\n.bcm-choice input { position: absolute; opacity: 0; width: 1px; height: 1px; }\n.bcm-choice span {\n display: inline-flex; align-items: center; justify-content: center; gap: 6px;\n min-height: 40px; min-width: 40px; padding: 6px 12px;\n border: 1px solid var(--bcm-line); border-radius: 999px; cursor: pointer; background: var(--bcm-bg);\n}\n/* The chosen one: a thicker border and a check, not colour alone. */\n.bcm-choice input:checked + span { border: 2px solid var(--bcm-accent); font-weight: 650; }\n.bcm-choice input:checked + span::before { content: '✓'; }\n\n/* ── text ── */\n.bcm-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }\n.bcm-spacer { flex: 1; }\n.bcm-full { flex-basis: 100%; }\n.bcm-muted, .bcm-hint { color: var(--bcm-muted); font-size: 0.875em; }\n.bcm-error { color: var(--bcm-danger); }\n.bcm-ok { color: var(--bcm-ok); }\n.bcm-note { background: var(--bcm-soft); border-radius: 8px; padding: 8px 12px; font-size: 0.9375em; }\n.bcm-warning { background: var(--bcm-warn-bg); border-radius: 8px; padding: 8px 12px; font-size: 0.9375em; }\n.bcm-banner {\n background: var(--bcm-warn-bg); border-radius: 10px; padding: 10px 12px;\n display: flex; align-items: center; gap: 10px; flex-wrap: wrap;\n}\n.bcm-text { white-space: pre-wrap; overflow-wrap: anywhere; }\n.bcm-facts {\n font: inherit; white-space: pre-wrap; overflow-wrap: anywhere; margin: 0;\n background: var(--bcm-soft); border-radius: 8px; padding: 10px 12px;\n}\n.bcm-badge {\n display: inline-flex; align-items: center; gap: 4px;\n border: 1px solid currentColor; border-radius: 999px; padding: 0 8px;\n font-size: 0.8125em; line-height: 1.6;\n}\n.bcm-badge-team { color: var(--bcm-accent); font-weight: 650; }\n\n/* ── the discussion ── */\n.bcm-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }\n.bcm-count { color: var(--bcm-muted); }\n.bcm-thread, .bcm-replies { list-style: none; display: flex; flex-direction: column; gap: 12px; }\n.bcm-replies { margin-top: 12px; padding-left: 20px; border-left: 2px solid var(--bcm-line); }\n.bcm-msg { display: flex; gap: 10px; align-items: flex-start; }\n.bcm-msg-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }\n.bcm-msg-pending { opacity: 0.65; }\n.bcm-meta { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 8px; font-size: 0.875em; }\n.bcm-author { font-weight: 650; }\n.bcm-handle, .bcm-time { color: var(--bcm-muted); }\n.bcm-handle::before { content: '@'; }\n.bcm-placeholder { color: var(--bcm-muted); font-style: italic; }\n.bcm-own-state { border-left: 3px solid var(--bcm-line); padding-left: 10px; display: flex; flex-direction: column; gap: 4px; }\n.bcm-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 2px 6px; }\n.bcm-avatar {\n flex: none; width: 36px; height: 36px; border-radius: 999px;\n display: inline-flex; align-items: center; justify-content: center;\n background: var(--bcm-soft); font-size: 20px; line-height: 1; user-select: none;\n}\n.bcm-avatar-initial { color: #fff; font-size: 16px; font-weight: 650; }\n.bcm-reactions { display: flex; flex-wrap: wrap; gap: 6px; }\n.bcm-react {\n font: inherit; display: inline-flex; align-items: center; gap: 4px;\n min-height: 32px; padding: 2px 10px; border-radius: 999px;\n border: 1px solid var(--bcm-line); background: var(--bcm-bg); color: var(--bcm-fg); cursor: pointer;\n}\nbutton.bcm-react:hover:not(:disabled) { background: var(--bcm-soft); }\n/* The viewer's own: a thicker border and a bold count, not colour alone. */\n.bcm-react[aria-pressed='true'] { border: 2px solid var(--bcm-accent); font-weight: 700; }\nspan.bcm-react { cursor: default; }\n.bcm-menu { position: relative; display: inline-flex; }\n.bcm-menu-list {\n position: absolute; right: 0; top: calc(100% + 4px); z-index: var(--bcm-z);\n min-width: 200px; display: flex; flex-direction: column; padding: 4px;\n background: var(--bcm-bg); border: 1px solid var(--bcm-line); border-radius: 10px; box-shadow: var(--bcm-shadow);\n}\n.bcm-menu-list button, .bcm-menu-list a {\n font: inherit; text-align: left; border: 0; background: none; color: var(--bcm-fg);\n padding: 8px 10px; border-radius: 6px; cursor: pointer; text-decoration: none; min-height: 36px;\n}\n.bcm-menu-list button:hover, .bcm-menu-list a:hover { background: var(--bcm-soft); }\n\n/* ── the composer ── */\n.bcm-composer { display: flex; flex-direction: column; gap: 8px; }\n.bcm-composer-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }\n.bcm-counter { color: var(--bcm-muted); font-size: 0.8125em; font-variant-numeric: tabular-nums; }\n.bcm-counter-over { color: var(--bcm-danger); font-weight: 650; }\n.bcm-confirm { border: 2px solid var(--bcm-accent); border-radius: 10px; padding: 12px; display: flex; flex-direction: column; gap: 10px; }\n\n/* ── onboarding, forms, the panel ── */\n.bcm-card { border: 1px solid var(--bcm-line); border-radius: 12px; padding: 14px; display: flex; flex-direction: column; gap: 12px; }\n.bcm-steps { color: var(--bcm-muted); font-size: 0.875em; }\n.bcm-rules { list-style: none; display: flex; flex-direction: column; gap: 8px; }\n.bcm-rules li { display: flex; flex-direction: column; gap: 2px; }\n.bcm-consent { max-height: 260px; overflow: auto; border: 1px solid var(--bcm-line); border-radius: 8px; padding: 10px 12px; display: flex; flex-direction: column; gap: 8px; }\n.bcm-form { display: flex; flex-direction: column; gap: 14px; }\n.bcm-section { display: flex; flex-direction: column; gap: 10px; }\n.bcm-list { list-style: none; display: flex; flex-direction: column; gap: 8px; }\n.bcm-list > li { border: 1px solid var(--bcm-line); border-radius: 10px; padding: 10px 12px; display: flex; flex-direction: column; gap: 4px; }\n.bcm-unread { border-left: 4px solid var(--bcm-accent) !important; }\n.bcm-dl { display: grid; grid-template-columns: minmax(120px, max-content) 1fr; gap: 6px 14px; margin: 0; }\n.bcm-dl dt { font-weight: 600; }\n.bcm-dl dd { margin: 0; }\n.bcm-receipt { display: flex; flex-direction: column; gap: 8px; }\n.bcm-turnstile { min-height: 0; }\n\n/* ── dialogs (native <dialog>: the top layer, Esc, focus kept inside) ── */\n.bcm-dialog {\n border: 1px solid var(--bcm-line); border-radius: 14px; padding: 0;\n width: min(520px, calc(100vw - 32px)); max-height: calc(100vh - 32px);\n background: var(--bcm-bg); color: var(--bcm-fg); box-shadow: var(--bcm-shadow);\n}\n.bcm-dialog::backdrop { background: rgba(10, 12, 16, 0.45); }\n.bcm-dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 12px; }\n\n@media (max-width: 480px) {\n .bcm { padding: 12px; border-radius: 0; }\n .bcm-replies { padding-left: 12px; }\n .bcm-avatar { width: 30px; height: 30px; font-size: 17px; }\n .bcm-dl { grid-template-columns: 1fr; }\n .bcm-dialog { width: 100vw; max-width: 100vw; margin: auto 0 0; border-radius: 16px 16px 0 0; }\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .bcm-dialog[open] { animation: bcm-in 140ms ease-out; }\n @keyframes bcm-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }\n}\n";
4459
- function ensureVueFeatureFlags() {
4460
- const g = globalThis;
4461
- const defaults = {
4462
- __VUE_OPTIONS_API__: true,
4463
- __VUE_PROD_DEVTOOLS__: false,
4464
- __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false
4465
- };
4466
- for (const [flag, value] of Object.entries(defaults)) if (typeof g[flag] === "undefined") g[flag] = value;
4467
- }
4468
- const DISCUSSION_ELEMENT_TAG = "blogmcp-discussion";
4469
- const NOTICE_FORM_ELEMENT_TAG = "blogmcp-notice-form";
4470
- const MY_COMMUNITY_ELEMENT_TAG = "blogmcp-my-community";
4471
- function ownNonce(el) {
4472
- const fromProperty = el.nonce;
4473
- if (typeof fromProperty === "string" && fromProperty !== "") return fromProperty;
4474
- return el.getAttribute("nonce") || void 0;
4475
- }
4476
- function communityElement(component, defineNonce) {
4477
- const Base = defineCustomElement(component);
4478
- return class CommunityElement extends Base {
4479
- connectedCallback() {
4480
- const own = ownNonce(this);
4481
- const nonce = defineNonce || own;
4482
- if (nonce && !own) this.nonce = nonce;
4483
- const root = this.shadowRoot;
4484
- if (root && !root.querySelector("style[data-bcm]")) {
4485
- const style = document.createElement("style");
4486
- style.setAttribute("data-bcm", "");
4487
- if (nonce) style.setAttribute("nonce", nonce);
4488
- style.textContent = css;
4489
- root.prepend(style);
4490
- }
4491
- super.connectedCallback();
4492
- }
4493
- };
4494
- }
4495
- function define(tag, component, nonce) {
4496
- if (typeof customElements === "undefined" || customElements.get(tag)) return;
4497
- ensureVueFeatureFlags();
4498
- customElements.define(tag, communityElement(component, nonce));
4499
- }
4500
- function defineDiscussionElement(opts = {}) {
4501
- define(opts.tag ?? DISCUSSION_ELEMENT_TAG, _sfc_main$4, opts.nonce);
4502
- }
4503
- function defineNoticeFormElement(opts = {}) {
4504
- define(opts.tag ?? NOTICE_FORM_ELEMENT_TAG, _sfc_main, opts.nonce);
4505
- }
4506
- function defineMyCommunityElement(opts = {}) {
4507
- define(opts.tag ?? MY_COMMUNITY_ELEMENT_TAG, _sfc_main$1, opts.nonce);
4508
- }
4509
- function defineCommunityElements(opts = {}) {
4510
- defineDiscussionElement({ tag: opts.tags?.discussion, nonce: opts.nonce });
4511
- defineNoticeFormElement({ tag: opts.tags?.noticeForm, nonce: opts.nonce });
4512
- defineMyCommunityElement({ tag: opts.tags?.myCommunity, nonce: opts.nonce });
4513
- }
4514
- function attrs(el, values) {
4515
- for (const [name, value] of Object.entries(values)) if (value !== void 0 && value !== null && value !== "") el.setAttribute(name, String(value));
4516
- }
4517
- function place(el, o) {
4518
- if (o.transport) el.transport = o.transport;
4519
- (o.container ?? document.body).appendChild(el);
4520
- return () => el.remove();
4521
- }
4522
- function mountDiscussion(o) {
4523
- if (typeof document === "undefined") return () => {
4524
- };
4525
- defineDiscussionElement({ nonce: o.nonce });
4526
- const el = document.createElement(DISCUSSION_ELEMENT_TAG);
4527
- attrs(el, {
4528
- "article-id": o.articleId,
4529
- "api-base": o.apiBase,
4530
- "site-base": o.siteBase,
4531
- lang: o.lang,
4532
- "sign-in-href": o.signInHref,
4533
- routes: o.routes ? JSON.stringify(o.routes) : void 0,
4534
- "poll-ms": o.pollMs,
4535
- nonce: o.nonce
4536
- });
4537
- return place(el, o);
4538
- }
4539
- function mountNoticeForm(o) {
4540
- if (typeof document === "undefined") return () => {
4541
- };
4542
- defineNoticeFormElement({ nonce: o.nonce });
4543
- const el = document.createElement(NOTICE_FORM_ELEMENT_TAG);
4544
- attrs(el, {
4545
- "api-base": o.apiBase,
4546
- "site-base": o.siteBase,
4547
- lang: o.lang,
4548
- "turnstile-site-key": o.turnstileSiteKey,
4549
- target: o.target ? JSON.stringify(o.target) : void 0,
4550
- "article-id": o.articleId,
4551
- routes: o.routes ? JSON.stringify(o.routes) : void 0,
4552
- nonce: o.nonce
4553
- });
4554
- return place(el, o);
4555
- }
4556
- function mountMyCommunity(o = {}) {
4557
- if (typeof document === "undefined") return () => {
4558
- };
4559
- defineMyCommunityElement({ nonce: o.nonce });
4560
- const el = document.createElement(MY_COMMUNITY_ELEMENT_TAG);
4561
- attrs(el, {
4562
- "api-base": o.apiBase,
4563
- "site-base": o.siteBase,
4564
- lang: o.lang,
4565
- "decision-id": o.decisionId,
4566
- routes: o.routes ? JSON.stringify(o.routes) : void 0,
4567
- "sign-in-href": o.signInHref,
4568
- nonce: o.nonce
4569
- });
4570
- return place(el, o);
4571
- }
4572
- const mount = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
4573
- __proto__: null,
4574
- mountDiscussion,
4575
- mountMyCommunity,
4576
- mountNoticeForm
4577
- }, Symbol.toStringTag, { value: "Module" }));
4578
- export {
4579
- AVATAR_PRESETS as A,
4580
- DISCUSSION_ELEMENT_TAG as D,
4581
- MY_COMMUNITY_ELEMENT_TAG as M,
4582
- NOTICE_FORM_ELEMENT_TAG as N,
4583
- defineDiscussionElement as a,
4584
- defineNoticeFormElement as b,
4585
- defineMyCommunityElement as c,
4586
- defineCommunityElements as d,
4587
- mountNoticeForm as e,
4588
- mountMyCommunity as f,
4589
- createDiscussionController as g,
4590
- createNoticeForm as h,
4591
- createPanel as i,
4592
- createOnboarding as j,
4593
- DEFAULT_API_BASE as k,
4594
- mount as l,
4595
- mountDiscussion as m,
4596
- pickLang as p
4597
- };