memi-board 0.45.1 → 0.46.1

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 (63) hide show
  1. package/README.md +21 -6
  2. package/dist/{ar-BCQdRw_S.js → ar-2vagHrlh.js} +17 -1
  3. package/dist/ar-2vagHrlh.js.map +1 -0
  4. package/dist/composables/boardLookup.d.ts +2 -0
  5. package/dist/composables/fetchPublicSeo.d.ts +1 -1
  6. package/dist/composables/useMemiBoardAuth.d.ts +5 -1
  7. package/dist/composables/useMemiBoardAutoTranslate.d.ts +6 -6
  8. package/dist/composables/useMemiBoardPosts.d.ts +6 -1
  9. package/dist/composables/useMemiBoardSettings.d.ts +5 -4
  10. package/dist/composables/useMemiBoardWritingAssistant.d.ts +8 -0
  11. package/dist/config.d.ts +2 -0
  12. package/dist/{de-ddWQNaqs.js → de-B6gqgrqZ.js} +17 -1
  13. package/dist/de-B6gqgrqZ.js.map +1 -0
  14. package/dist/{en-DxkqyZq2.js → en-BaP_ikG-.js} +20 -2
  15. package/dist/en-BaP_ikG-.js.map +1 -0
  16. package/dist/{es-7JHjGVlM.js → es-CGxYBYNH.js} +17 -1
  17. package/dist/es-CGxYBYNH.js.map +1 -0
  18. package/dist/{fr-C3NCMNoy.js → fr-DeP9Swrv.js} +17 -1
  19. package/dist/fr-DeP9Swrv.js.map +1 -0
  20. package/dist/{id-C8hCAMPJ.js → id-BIUlVuq8.js} +17 -1
  21. package/dist/id-BIUlVuq8.js.map +1 -0
  22. package/dist/index.d.ts +3 -2
  23. package/dist/index.js +1123 -757
  24. package/dist/index.js.map +1 -1
  25. package/dist/{ja-oTWri4hY.js → ja-0FOftjGv.js} +17 -1
  26. package/dist/{ja-oTWri4hY.js.map → ja-0FOftjGv.js.map} +1 -1
  27. package/dist/{pt-B9EH4scA.js → pt-D8IArHb-.js} +17 -1
  28. package/dist/pt-D8IArHb-.js.map +1 -0
  29. package/dist/runtime/components/CategorySettings.vue +39 -15
  30. package/dist/runtime/components/Detail.vue +9 -7
  31. package/dist/runtime/components/Editor.vue +79 -8
  32. package/dist/runtime/components/List.vue +2 -2
  33. package/dist/runtime/components/ListDefault.vue +16 -6
  34. package/dist/runtime/components/ListDense.vue +8 -4
  35. package/dist/runtime/components/ListImage.vue +13 -6
  36. package/dist/runtime/components/ListVideo.vue +14 -5
  37. package/dist/runtime/components/Section.vue +10 -3
  38. package/dist/runtime/components/Settings.vue +35 -11
  39. package/dist/runtime/components/Users.vue +4 -3
  40. package/dist/runtime/composables/useMemiBoardSeo.ts +3 -3
  41. package/dist/{slugify-BuOMFLxN.js → slugify-BR-FLuDb.js} +34 -13
  42. package/dist/slugify-BR-FLuDb.js.map +1 -0
  43. package/dist/storage.js +1 -1
  44. package/dist/types.d.ts +46 -1
  45. package/dist/utils/boardAccess.d.ts +3 -0
  46. package/dist/utils/localizedFields.d.ts +33 -0
  47. package/dist/utils/section.d.ts +1 -1
  48. package/dist/{zh-D5rDYV6E.js → zh-DlXIP7ju.js} +17 -1
  49. package/dist/zh-DlXIP7ju.js.map +1 -0
  50. package/docs/firebase-setup.md +30 -2
  51. package/docs/firestore.rules.example +24 -8
  52. package/docs/roles-migration.md +32 -0
  53. package/docs/storage.rules.example +4 -4
  54. package/package.json +1 -1
  55. package/dist/ar-BCQdRw_S.js.map +0 -1
  56. package/dist/de-ddWQNaqs.js.map +0 -1
  57. package/dist/en-DxkqyZq2.js.map +0 -1
  58. package/dist/es-7JHjGVlM.js.map +0 -1
  59. package/dist/fr-C3NCMNoy.js.map +0 -1
  60. package/dist/id-C8hCAMPJ.js.map +0 -1
  61. package/dist/pt-B9EH4scA.js.map +0 -1
  62. package/dist/slugify-BuOMFLxN.js.map +0 -1
  63. package/dist/zh-D5rDYV6E.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,21 +1,21 @@
1
- import { A as e, B as t, C as n, D as r, E as i, F as a, G as o, H as s, I as c, L as l, M as u, N as d, O as f, P as p, R as m, S as h, T as g, U as _, V as v, W as y, _ as b, a as x, b as S, c as C, d as w, f as T, g as E, h as D, i as O, j as k, k as ee, l as te, m as A, n as ne, o as re, p as ie, r as ae, s as oe, u as se, v as j, w as ce, x as le, y as ue, z as M } from "./slugify-BuOMFLxN.js";
1
+ import { A as e, B as t, C as n, D as r, E as i, F as a, G as o, H as s, I as c, L as l, M as u, N as d, O as f, P as p, R as m, S as h, T as g, U as _, V as v, W as y, _ as b, a as x, b as S, c as C, d as w, f as ee, g as T, h as E, i as D, j as O, k as te, l as k, m as A, n as ne, o as re, p as ie, r as ae, s as oe, u as se, v as j, w as ce, x as le, y as ue, z as M } from "./slugify-BR-FLuDb.js";
2
2
  import { computed as N, onScopeDispose as P, reactive as de, ref as F, toValue as I, watch as L } from "vue";
3
3
  import { deleteDoc as fe, deleteField as pe, doc as me, documentId as R, endAt as he, endBefore as ge, getDoc as z, getDocs as B, increment as V, limit as H, limitToLast as _e, onSnapshot as U, orderBy as W, query as G, runTransaction as ve, serverTimestamp as K, setDoc as q, startAfter as J, updateDoc as Y, where as X, writeBatch as ye } from "firebase/firestore";
4
4
  import { useCollection as be, useCurrentUser as xe, useDocument as Se, useFirebaseApp as Ce, useFirebaseAuth as we, useFirestore as Z } from "vuefire";
5
5
  import { GoogleAuthProvider as Te, OAuthProvider as Ee, createUserWithEmailAndPassword as De, signInWithEmailAndPassword as Oe, signInWithPopup as ke, signOut as Ae, updateProfile as je } from "firebase/auth";
6
6
  import { deleteObject as Me, getStorage as Ne, listAll as Pe, ref as Fe } from "firebase/storage";
7
- import { GoogleAIBackend as Ie, Schema as Q, getAI as Le, getGenerativeModel as Re } from "firebase/ai";
7
+ import { GoogleAIBackend as Ie, Schema as Le, getAI as Re, getGenerativeModel as ze } from "firebase/ai";
8
8
  //#region src/utils/boardSeo.ts
9
- function ze(e, t) {
9
+ function Be(e, t) {
10
10
  return !e || e === "전체" || e === w(t, "seo.allCategory");
11
11
  }
12
- function Be(e) {
12
+ function Ve(e) {
13
13
  if (typeof e != "string") return null;
14
14
  let t = e.trim();
15
15
  return /^https?:\/\//i.test(t) ? t : null;
16
16
  }
17
- function Ve(e) {
18
- let t = Be(e);
17
+ function He(e) {
18
+ let t = Ve(e);
19
19
  if (!t) return null;
20
20
  if (!/firebasestorage\.googleapis\.com/i.test(t)) return t;
21
21
  try {
@@ -32,55 +32,147 @@ function Ve(e) {
32
32
  return t;
33
33
  }
34
34
  }
35
- function He(e, t, n = "") {
35
+ function Ue(e, t, n = "") {
36
36
  let r = t.replace(/\/$/, ""), i = e.trim();
37
- return i ? /^https?:\/\//i.test(i) ? i : `${r}${i.startsWith("/") ? i : `/${i}`}` : n ? He(n, t) : r;
37
+ return i ? /^https?:\/\//i.test(i) ? i : `${r}${i.startsWith("/") ? i : `/${i}`}` : n ? Ue(n, t) : r;
38
38
  }
39
- function Ue(e, t, n, r, i) {
39
+ function We(e, t, n, r, i) {
40
40
  let a = r?.trim().slice(0, 40), o = e.trim() || a || w(i, "seo.photo"), s = t?.trim(), c = n.trim() || "Board";
41
41
  return s ? `${o} · ${s} | ${c}` : `${o} | ${c}`;
42
42
  }
43
- function We(e, t) {
43
+ function Ge(e, t) {
44
44
  let n = [];
45
45
  return e.summary?.trim() ? n.push(e.summary.trim().slice(0, 140)) : n.push(w(t, "seo.postDescriptionFallback")), e.categoryLabel?.trim() && n.push(e.categoryLabel.trim()), e.authorName?.trim() && n.push(e.authorName.trim()), n.join(" · ").slice(0, 200);
46
46
  }
47
- function Ge(e, t, n) {
47
+ function Ke(e, t, n) {
48
48
  let r = t.trim() || "Board", i = e?.trim();
49
- return ze(i, n) ? w(n, "seo.listTitleAll", { site: r }) : w(n, "seo.listTitle", {
49
+ return Be(i, n) ? w(n, "seo.listTitleAll", { site: r }) : w(n, "seo.listTitle", {
50
50
  name: i,
51
51
  site: r
52
52
  });
53
53
  }
54
- function Ke(e, t) {
54
+ function qe(e, t) {
55
55
  if (e.description?.trim()) return e.description.trim().slice(0, 200);
56
- let n = e.categoryLabel?.trim(), r = ze(n, t) ? w(t, "seo.listDescriptionAll") : w(t, "seo.listDescription", { name: n }), i = (e.recentTitles || []).filter(Boolean).slice(0, 3);
56
+ let n = e.categoryLabel?.trim(), r = Be(n, t) ? w(t, "seo.listDescriptionAll") : w(t, "seo.listDescription", { name: n }), i = (e.recentTitles || []).filter(Boolean).slice(0, 3);
57
57
  return i.length ? `${r} ${i.map((e) => `「${e}」`).join(" ")}`.slice(0, 200) : r.slice(0, 200);
58
58
  }
59
- function qe(e) {
59
+ function Je(e) {
60
60
  let t = (e || "/board").trim() || "/board";
61
61
  return (t.startsWith("/") ? t : `/${t}`).replace(/\/$/, "") || "/board";
62
62
  }
63
63
  //#endregion
64
+ //#region src/utils/localizedFields.ts
65
+ var Ye = [
66
+ "label",
67
+ "description",
68
+ "title",
69
+ "summary",
70
+ "contentHash"
71
+ ];
72
+ function Xe(e) {
73
+ let t = 5381;
74
+ for (let n = 0; n < e.length; n++) t = (t << 5) + t ^ e.charCodeAt(n);
75
+ return (t >>> 0).toString(36);
76
+ }
77
+ function Ze(e, t) {
78
+ let n = e?.[t];
79
+ return typeof n == "string" ? n.trim() : "";
80
+ }
81
+ function Q(e) {
82
+ if (!e || typeof e != "object") return;
83
+ let t = {};
84
+ for (let [n, r] of Object.entries(e)) {
85
+ let e = T(n);
86
+ if (!e || !r || typeof r != "object") continue;
87
+ let i = {};
88
+ for (let [e, t] of Object.entries(r)) typeof t == "string" && t.trim() && (i[e] = t.trim());
89
+ Object.keys(i).length && (t[e] = i);
90
+ }
91
+ return Object.keys(t).length ? t : void 0;
92
+ }
93
+ function Qe(e) {
94
+ if (!e || typeof e != "object") return;
95
+ let t = e, n = {}, r = T(t.locale);
96
+ r && (n.locale = r);
97
+ for (let e of Ye) typeof t[e] == "string" && (n[e] = t[e]);
98
+ return Object.keys(n).length ? n : void 0;
99
+ }
100
+ function $e(e) {
101
+ if (!e || typeof e != "object") return;
102
+ let t = e, n = {}, r = T(t.locale);
103
+ return r && (n.locale = r), typeof t.hash == "string" && t.hash && (n.hash = t.hash), n.locale || n.hash ? n : void 0;
104
+ }
105
+ function et(e, t, n) {
106
+ let r = {};
107
+ if (n) for (let e of A) {
108
+ let t = n[e.code];
109
+ if (!t) continue;
110
+ let i = {};
111
+ for (let [e, n] of Object.entries(t)) typeof n == "string" && n.trim() && (i[e] = n.trim());
112
+ Object.keys(i).length && (r[e.code] = i);
113
+ }
114
+ let i = {};
115
+ for (let [e, n] of Object.entries(t)) {
116
+ let t = n.trim();
117
+ t && (i[e] = t);
118
+ }
119
+ return Object.keys(i).length && (r[e] = {
120
+ ...r[e],
121
+ ...i
122
+ }), r;
123
+ }
124
+ function tt(e, t) {
125
+ return !e || !t.length ? !1 : A.every((n) => t.every((t) => !!Ze(e[n.code], t)));
126
+ }
127
+ function nt(e, t, n, r, i) {
128
+ let a = T(t), o = T(i);
129
+ if (!a || o && a === o) return r;
130
+ let s = Ze(e?.[a], n);
131
+ if (s) return s;
132
+ if (a !== "en" && o !== "en") {
133
+ let t = Ze(e?.en, n);
134
+ if (t) return t;
135
+ }
136
+ return r;
137
+ }
138
+ function rt(e, t, n, r, i) {
139
+ return r ? !t || t[n] !== r ? r : nt(e, i, n, r, t.locale) : "";
140
+ }
141
+ function it(e, t) {
142
+ return rt(e.i18n, e.i18nSource, "title", e.title ?? "", t);
143
+ }
144
+ function at(e, t) {
145
+ return rt(e.i18n, e.i18nSource, "summary", e.summary ?? "", t);
146
+ }
147
+ function ot(e, t) {
148
+ let n = e.content || "", r = e.contentI18nSource?.hash;
149
+ return !n || !r || r !== Xe(n) ? n : nt(e.contentI18n, t, "content", n, e.contentI18nSource?.locale) || n;
150
+ }
151
+ function st(e) {
152
+ let t = e.label.trim(), n = (e.description ?? "").trim();
153
+ return !t || !e.i18nSource || e.i18nSource.label !== t || (e.i18nSource.description ?? "") !== n ? !1 : tt(e.i18n, n ? ["label", "description"] : ["label"]);
154
+ }
155
+ //#endregion
64
156
  //#region src/composables/fetchPublicSeo.ts
65
- function Je() {
157
+ function ct() {
66
158
  return {
67
159
  db: Z(),
68
160
  paths: j()
69
161
  };
70
162
  }
71
- async function Ye(e, t) {
163
+ async function lt(e, t, n) {
72
164
  try {
73
- let n = await z(o(e.db, e.paths, t));
74
- if (!n.exists()) return {
165
+ let r = await z(o(e.db, e.paths, t));
166
+ if (!r.exists()) return {
75
167
  label: t,
76
168
  description: "",
77
169
  hidden: !1
78
170
  };
79
- let r = n.data() ?? {};
171
+ let i = r.data() ?? {}, a = typeof i.label == "string" && i.label.trim() ? i.label.trim() : t, s = typeof i.description == "string" ? i.description.trim() : "", c = Q(i.i18n), l = Qe(i.i18nSource);
80
172
  return {
81
- label: typeof r.label == "string" && r.label.trim() ? r.label.trim() : t,
82
- description: typeof r.description == "string" ? r.description.trim() : "",
83
- hidden: r.visibility === "hidden"
173
+ label: rt(c, l, "label", a, n) || a,
174
+ description: rt(c, l, "description", s, n),
175
+ hidden: i.visibility === "hidden"
84
176
  };
85
177
  } catch {
86
178
  return {
@@ -90,7 +182,7 @@ async function Ye(e, t) {
90
182
  };
91
183
  }
92
184
  }
93
- function Xe(e) {
185
+ function ut(e) {
94
186
  if (e && typeof e == "object" && "toDate" in e) try {
95
187
  let t = e.toDate();
96
188
  return t instanceof Date ? t.toISOString() : null;
@@ -99,37 +191,45 @@ function Xe(e) {
99
191
  }
100
192
  return null;
101
193
  }
102
- async function Ze(e, t, n) {
103
- let r = e.trim(), i = t.trim();
104
- if (!r || !i) return null;
105
- let a = n ?? Je();
106
- a.paths = _(a.paths);
107
- let o = await Ye(a, r);
108
- if (o.hidden) return null;
194
+ async function dt(e, t, n, r) {
195
+ let i = e.trim(), a = t.trim();
196
+ if (!i || !a) return null;
197
+ let o = n ?? ct();
198
+ o.paths = _(o.paths), r && await se(r);
199
+ let s = await lt(o, i, r);
200
+ if (s.hidden) return null;
109
201
  try {
110
- let e = (await B(G(M(a.db, a.paths), X("boardId", "==", r), X("slug", "==", i), X("listed", "==", !0), X("isPublished", "==", !0), H(1)))).docs[0];
202
+ let e = (await B(G(M(o.db, o.paths), X("boardId", "==", i), X("slug", "==", a), X("listed", "==", !0), X("isPublished", "==", !0), H(1)))).docs[0];
111
203
  if (!e) return null;
112
- let t = e.data() ?? {}, n = typeof t.title == "string" ? t.title.trim() : "";
113
- return n ? {
114
- id: e.id,
115
- slug: typeof t.slug == "string" ? t.slug : i,
116
- category: r,
117
- categoryLabel: o.label,
204
+ let t = e.data() ?? {}, n = typeof t.title == "string" ? t.title : "", c = typeof t.summary == "string" ? t.summary : "", l = Q(t.i18n), u = Qe(t.i18nSource), d = it({
118
205
  title: n,
119
- summary: typeof t.summary == "string" ? t.summary.trim().slice(0, 200) : "",
120
- previewImage: Ve(t.previewImage),
206
+ i18n: l,
207
+ i18nSource: u
208
+ }, r).trim();
209
+ return d ? {
210
+ id: e.id,
211
+ slug: typeof t.slug == "string" ? t.slug : a,
212
+ category: i,
213
+ categoryLabel: s.label,
214
+ title: d,
215
+ summary: at({
216
+ summary: c,
217
+ i18n: l,
218
+ i18nSource: u
219
+ }, r).trim().slice(0, 200),
220
+ previewImage: He(t.previewImage),
121
221
  authorName: typeof t.authorName == "string" && t.authorName.trim() ? t.authorName.trim() : null,
122
- createdAt: Xe(t.createdAt)
222
+ createdAt: ut(t.createdAt)
123
223
  } : null;
124
224
  } catch (e) {
125
225
  return import.meta.dev && console.warn("[memi-board] fetchPublicPostForSeo failed", e), null;
126
226
  }
127
227
  }
128
- async function Qe(e, t, n) {
228
+ async function ft(e, t, n) {
129
229
  n && await se(n);
130
- let r = e?.trim() || null, i = t ?? Je();
230
+ let r = e?.trim() || null, i = t ?? ct();
131
231
  if (i.paths = _(i.paths), r) {
132
- let e = await Ye(i, r);
232
+ let e = await lt(i, r, n);
133
233
  if (e.hidden) return {
134
234
  kind: "category",
135
235
  category: r,
@@ -138,7 +238,7 @@ async function Qe(e, t, n) {
138
238
  ogImage: null,
139
239
  recentTitles: []
140
240
  };
141
- let { recentTitles: t, ogImage: a } = await $e(i, r);
241
+ let { recentTitles: t, ogImage: a } = await pt(i, r, n);
142
242
  return {
143
243
  kind: "category",
144
244
  category: r,
@@ -157,49 +257,53 @@ async function Qe(e, t, n) {
157
257
  recentTitles: []
158
258
  };
159
259
  }
160
- async function $e(e, t) {
161
- let n = [], r = null;
260
+ async function pt(e, t, n) {
261
+ let r = [], i = null;
162
262
  try {
163
- let i = await B(G(M(e.db, e.paths), X("boardId", "==", t), X("listed", "==", !0), X("isPublished", "==", !0), W("createdAt", "desc"), H(12)));
164
- for (let e of i.docs) {
165
- let t = e.data() ?? {}, i = typeof t.title == "string" ? t.title.trim() : "";
166
- i && n.length < 5 && n.push(i), r ||= Ve(t.previewImage);
263
+ let a = await B(G(M(e.db, e.paths), X("boardId", "==", t), X("listed", "==", !0), X("isPublished", "==", !0), W("createdAt", "desc"), H(12)));
264
+ for (let e of a.docs) {
265
+ let t = e.data() ?? {}, a = it({
266
+ title: typeof t.title == "string" ? t.title : "",
267
+ i18n: Q(t.i18n),
268
+ i18nSource: Qe(t.i18nSource)
269
+ }, n).trim();
270
+ a && r.length < 5 && r.push(a), i ||= He(t.previewImage);
167
271
  }
168
272
  } catch (e) {
169
273
  import.meta.dev && console.warn("[memi-board] loadRecentListed failed", e);
170
274
  }
171
275
  return {
172
- recentTitles: n,
173
- ogImage: r
276
+ recentTitles: r,
277
+ ogImage: i
174
278
  };
175
279
  }
176
280
  //#endregion
177
281
  //#region src/utils/boardAccess.ts
178
- function et(e, t) {
179
- return !e || !t ? !1 : (t.allowedStaffUids ?? []).includes(e);
282
+ function mt(e, t) {
283
+ return !e || !t ? !1 : [...t.allowedStaffUids ?? [], ...t.allowedManagerUids ?? []].includes(e);
180
284
  }
181
- function tt(e, t, n, r) {
182
- return e ? t ? !0 : n ? et(e, r) : !1 : !1;
285
+ function ht(e, t, n, r) {
286
+ return e ? t ? !0 : n ? mt(e, r) : !1 : !1;
183
287
  }
184
- function nt(e, t, n, r) {
288
+ function gt(e, t, n, r) {
185
289
  if (!e) return !1;
186
- if (r?.visibility === "hidden") return tt(e, t, n, r);
290
+ if (r?.visibility === "hidden") return ht(e, t, n, r);
187
291
  let i = r?.commentWriteRole ?? "user";
188
- return i === "admin" ? t : i !== "staff" || tt(e, t, n, r);
292
+ return i === "admin" ? t : i !== "staff" || ht(e, t, n, r);
189
293
  }
190
294
  //#endregion
191
295
  //#region src/composables/boardLookup.ts
192
- var rt;
193
- function it(e) {
194
- rt = e;
296
+ var _t;
297
+ function vt(e) {
298
+ _t = e;
195
299
  }
196
- function at(e) {
197
- return rt?.(e);
300
+ function yt(e) {
301
+ return _t?.(e);
198
302
  }
199
303
  //#endregion
200
304
  //#region src/utils/moderation-strike.ts
201
- var ot = 10, st = 864e5;
202
- function ct(e, t, n = Date.now(), r = st) {
305
+ var bt = 10, xt = 864e5;
306
+ function St(e, t, n = Date.now(), r = xt) {
203
307
  let i = Math.max(0, Math.floor(Number(e) || 0));
204
308
  if (i <= 0) return 0;
205
309
  let a = typeof t == "number" && Number.isFinite(t) ? t : null;
@@ -207,7 +311,7 @@ function ct(e, t, n = Date.now(), r = st) {
207
311
  let o = Math.floor((n - a) / r);
208
312
  return Math.max(0, i - o);
209
313
  }
210
- function lt(e, t, n = Date.now(), r = 10, i = st) {
314
+ function Ct(e, t, n = Date.now(), r = 10, i = xt) {
211
315
  let a = Math.max(0, Math.floor(Number(e) || 0));
212
316
  if (a < r) return null;
213
317
  let o = typeof t == "number" && Number.isFinite(t) ? t : null;
@@ -215,10 +319,10 @@ function lt(e, t, n = Date.now(), r = 10, i = st) {
215
319
  let s = o + (a - (r - 1)) * i;
216
320
  return s > n ? s : null;
217
321
  }
218
- function ut(e, t, n = Date.now(), r = 10, i = st) {
219
- return lt(e, t, n, r, i) != null;
322
+ function wt(e, t, n = Date.now(), r = 10, i = xt) {
323
+ return Ct(e, t, n, r, i) != null;
220
324
  }
221
- function dt(e, t = Date.now(), n) {
325
+ function Tt(e, t = Date.now(), n) {
222
326
  let r = Math.max(0, e - t), i = Math.ceil(r / 36e5);
223
327
  if (i <= 0) return w(n, "moderationStrike.liftSoon");
224
328
  if (i < 24) return w(n, "moderationStrike.liftInHours", { hours: i });
@@ -228,7 +332,7 @@ function dt(e, t = Date.now(), n) {
228
332
  hours: o
229
333
  });
230
334
  }
231
- function ft(e) {
335
+ function Et(e) {
232
336
  if (e == null) return null;
233
337
  if (typeof e == "number" && Number.isFinite(e)) return e;
234
338
  if (typeof e == "object" && e && "toMillis" in e && typeof e.toMillis == "function") return e.toMillis();
@@ -240,39 +344,45 @@ function ft(e) {
240
344
  }
241
345
  //#endregion
242
346
  //#region src/composables/useMemiBoardAuth.ts
243
- var pt = "__MEMI_BOARD_AUTH_STATE__";
244
- function mt() {
347
+ var Dt = "__MEMI_BOARD_AUTH_STATE__";
348
+ function Ot() {
245
349
  let e = globalThis;
246
- return e[pt] || (e[pt] = {
350
+ return e[Dt] || (e[Dt] = {
247
351
  role: F("user"),
248
352
  rolePending: F(!0),
249
353
  rawBlockCount: F(0),
250
354
  blockAtMs: F(null),
251
355
  watchStarted: !1
252
- }), e[pt];
356
+ }), e[Dt];
253
357
  }
254
- function ht() {
255
- let e = ue(), { t, locale: n } = O(), r = mt(), i = Z(), a = we(), o = xe(), { role: s, rolePending: c, rawBlockCount: l, blockAtMs: u } = r;
358
+ function kt() {
359
+ let e = ue(), { t, locale: n } = D(), r = Ot(), i = Z(), a = we(), o = xe(), { role: s, rolePending: c, rawBlockCount: l, blockAtMs: u } = r;
256
360
  function d() {
257
361
  let t = e.moderation?.blockBanThreshold;
258
362
  return t === 0 ? 0 : typeof t == "number" && t > 0 ? Math.floor(t) : 10;
259
363
  }
260
364
  function p() {
261
365
  let t = e.moderation?.blockBanDecayMs;
262
- return typeof t == "number" && t > 0 ? t : st;
366
+ return typeof t == "number" && t > 0 ? t : xt;
263
367
  }
264
368
  function m(e) {
265
369
  return f(i, j(), e);
266
370
  }
267
371
  function h(e) {
268
- if (!e) {
372
+ if (s.value = [
373
+ "owner",
374
+ "admin",
375
+ "manager",
376
+ "staff",
377
+ "user"
378
+ ].includes(String(e?.role)) ? e?.role : "user", !e) {
269
379
  l.value = 0, u.value = null;
270
380
  return;
271
381
  }
272
- l.value = Math.max(0, Math.floor(Number(e.moderationBlockCount) || 0)), u.value = ft(e.moderationBlockAt);
382
+ l.value = Math.max(0, Math.floor(Number(e.moderationBlockCount) || 0)), u.value = Et(e.moderationBlockAt);
273
383
  }
274
384
  async function g(e) {
275
- let t = m(e.uid), n = ee(i, j(), e.uid), r = await z(t);
385
+ let t = m(e.uid), n = te(i, j(), e.uid), r = await z(t);
276
386
  if (!r.exists()) return await Promise.all([q(t, {
277
387
  role: "user",
278
388
  displayName: e.displayName,
@@ -288,7 +398,9 @@ function ht() {
288
398
  updatedAt: K()
289
399
  };
290
400
  return !o && e.displayName && (c.displayName = e.displayName), !s && e.photoURL && (c.photoURL = e.photoURL), await Promise.all([Y(t, c), q(n, { email: e.email }, { merge: !0 })]), [
401
+ "owner",
291
402
  "admin",
403
+ "manager",
292
404
  "staff",
293
405
  "user"
294
406
  ].includes(String(a.role)) ? a.role : "user";
@@ -315,20 +427,20 @@ function ht() {
315
427
  c.value = !1;
316
428
  }
317
429
  }, { immediate: !0 }));
318
- let v = N(() => !!o.value), y = N(() => s.value === "admin"), b = N(() => s.value === "staff"), x = N(() => y.value || b.value), S = N(() => ct(l.value, u.value, Date.now(), p())), C = N(() => {
430
+ let v = N(() => !!o.value), y = N(() => s.value === "owner"), b = N(() => s.value === "manager"), x = N(() => y.value || s.value === "admin"), S = N(() => b.value || s.value === "staff"), C = N(() => x.value || S.value), w = N(() => St(l.value, u.value, Date.now(), p())), ee = N(() => {
319
431
  let e = d();
320
- return e <= 0 ? !1 : ut(l.value, u.value, Date.now(), e, p());
321
- }), w = N(() => {
432
+ return e <= 0 ? !1 : wt(l.value, u.value, Date.now(), e, p());
433
+ }), T = N(() => {
322
434
  let e = d();
323
435
  if (e <= 0) return null;
324
- let t = lt(l.value, u.value, Date.now(), e, p());
325
- return t == null ? null : dt(t, Date.now(), n.value);
326
- }), T = N(() => {
327
- if (!C.value) return null;
328
- let e = w.value;
436
+ let t = Ct(l.value, u.value, Date.now(), e, p());
437
+ return t == null ? null : Tt(t, Date.now(), n.value);
438
+ }), E = N(() => {
439
+ if (!ee.value) return null;
440
+ let e = T.value;
329
441
  return e ? t("auth.writeRestrictedUntil", { until: e }) : t("auth.writeRestricted");
330
442
  });
331
- async function E() {
443
+ async function O() {
332
444
  let e = o.value?.uid, r = d();
333
445
  if (!e || r <= 0) return {
334
446
  effectiveCount: 0,
@@ -349,12 +461,12 @@ function ht() {
349
461
  });
350
462
  let s = (a.exists() ? Math.max(0, Math.floor(Number(a.data().moderationBlockCount) || 0)) : 0) + 1, c = Date.now();
351
463
  l.value = s, u.value = c;
352
- let f = ct(s, c, c, p()), h = ut(s, c, c, r, p()), g = t("auth.strikeSuffix", {
464
+ let f = St(s, c, c, p()), h = wt(s, c, c, r, p()), g = t("auth.strikeSuffix", {
353
465
  count: f,
354
466
  threshold: r
355
467
  });
356
468
  if (h) {
357
- let e = lt(s, c, c, r, p()), i = e == null ? null : dt(e, c, n.value);
469
+ let e = Ct(s, c, c, r, p()), i = e == null ? null : Tt(e, c, n.value);
358
470
  g = i ? t("auth.strikeRestrictedUntil", {
359
471
  count: f,
360
472
  until: i
@@ -366,94 +478,101 @@ function ht() {
366
478
  messageSuffix: g
367
479
  };
368
480
  }
369
- function D() {
481
+ function k() {
370
482
  if (!a) throw Error(t("auth.notInitialized"));
371
483
  return a;
372
484
  }
373
- async function k() {
374
- return ke(D(), new Te());
485
+ async function A() {
486
+ return ke(k(), new Te());
375
487
  }
376
- async function te() {
488
+ async function ne() {
377
489
  let e = new Ee("apple.com");
378
- return e.addScope("email"), e.addScope("name"), ke(D(), e);
490
+ return e.addScope("email"), e.addScope("name"), ke(k(), e);
379
491
  }
380
- async function A(e, t) {
381
- return Oe(D(), e, t);
492
+ async function re(e, t) {
493
+ return Oe(k(), e, t);
382
494
  }
383
- async function ne(e, t, n) {
384
- let r = await De(D(), e, t);
495
+ async function ie(e, t, n) {
496
+ let r = await De(k(), e, t);
385
497
  return n && await je(r.user, { displayName: n }), r;
386
498
  }
387
- async function re() {
388
- await Ae(D());
499
+ async function ae() {
500
+ await Ae(k());
501
+ }
502
+ function oe(e) {
503
+ return ht(o.value?.uid, x.value, S.value, e ? yt(e) : void 0);
389
504
  }
390
- function ie(e) {
391
- return tt(o.value?.uid, y.value, b.value, e ? at(e) : void 0);
505
+ function se(e) {
506
+ return o.value ? x.value ? !0 : b.value && !!e && (yt(e)?.allowedManagerUids ?? []).includes(o.value.uid) : !1;
392
507
  }
393
- function ae(e) {
394
- return v.value && (o.value?.uid === e.authorUid || ie(e.boardId));
508
+ function ce(e) {
509
+ return v.value && (o.value?.uid === e.authorUid || oe(e.boardId));
395
510
  }
396
- function oe(e) {
511
+ function le(e) {
397
512
  return v.value && o.value?.uid === e.authorUid;
398
513
  }
399
- function se(e) {
400
- return v.value && (o.value?.uid === e.authorUid || ie(e.boardId));
514
+ function M(e) {
515
+ return v.value && (o.value?.uid === e.authorUid || oe(e.boardId));
401
516
  }
402
517
  return {
403
518
  user: o,
519
+ role: s,
520
+ isOwner: y,
521
+ isManager: b,
522
+ canConfigureBoard: se,
404
523
  isSignedIn: v,
405
- isAdmin: y,
406
- isStaff: b,
407
- canManageContent: x,
408
- canManageBoard: ie,
524
+ isAdmin: x,
525
+ isStaff: S,
526
+ canManageContent: C,
527
+ canManageBoard: oe,
409
528
  rolePending: c,
410
- moderationBlockCount: S,
411
- isWriteRestricted: C,
412
- restrictedUntilLabel: w,
413
- restrictedMessage: T,
414
- signInWithGoogle: k,
415
- signInWithApple: te,
416
- signInWithEmail: A,
417
- signUpWithEmail: ne,
418
- signOut: re,
419
- canEdit: ae,
420
- canDelete: ae,
421
- canEditComment: oe,
422
- canDeleteComment: se,
423
- recordContentModerationBlock: E,
529
+ moderationBlockCount: w,
530
+ isWriteRestricted: ee,
531
+ restrictedUntilLabel: T,
532
+ restrictedMessage: E,
533
+ signInWithGoogle: A,
534
+ signInWithApple: ne,
535
+ signInWithEmail: re,
536
+ signUpWithEmail: ie,
537
+ signOut: ae,
538
+ canEdit: ce,
539
+ canDelete: ce,
540
+ canEditComment: le,
541
+ canDeleteComment: M,
542
+ recordContentModerationBlock: O,
424
543
  refreshBoardUser: _
425
544
  };
426
545
  }
427
546
  //#endregion
428
547
  //#region src/utils/imeSafeSubmit.ts
429
- var gt = /* @__PURE__ */ new WeakMap();
430
- function _t(e) {
548
+ var At = /* @__PURE__ */ new WeakMap();
549
+ function jt(e) {
431
550
  let t = e.currentTarget;
432
551
  return t instanceof HTMLButtonElement ? t : t instanceof Element ? t.closest("button") : null;
433
552
  }
434
- function vt(e) {
553
+ function Mt(e) {
435
554
  if (e.pointerType !== "mouse" || e.button !== 0) return;
436
- let t = _t(e), n = t?.form;
555
+ let t = jt(e), n = t?.form;
437
556
  if (!t || !n || t.disabled) return;
438
- gt.set(t, Date.now() + 1e3);
557
+ At.set(t, Date.now() + 1e3);
439
558
  let r = document.activeElement;
440
559
  r instanceof HTMLElement && r.blur(), setTimeout(() => n.requestSubmit(t), 0);
441
560
  }
442
- function yt(e) {
443
- let t = _t(e);
561
+ function Nt(e) {
562
+ let t = jt(e);
444
563
  if (!t) return;
445
- let n = gt.get(t);
446
- n && Date.now() < n && (e.preventDefault(), gt.delete(t));
564
+ let n = At.get(t);
565
+ n && Date.now() < n && (e.preventDefault(), At.delete(t));
447
566
  }
448
567
  //#endregion
449
568
  //#region src/utils/findReadablePost.ts
450
- async function bt(e, t, n, r, i, a) {
569
+ async function Pt(e, t, n, r, i, a) {
451
570
  let o = [X("boardId", "==", n), X("slug", "==", r)];
452
571
  return a ? (await B(G(M(e, t), ...o, H(1)))).docs[0] : (await Promise.all([B(G(M(e, t), ...o, X("listed", "==", !0), X("isPublished", "==", !0), H(1))), ...i ? [B(G(M(e, t), ...o, X("authorUid", "==", i), H(1)))] : []])).flatMap((e) => e.docs)[0];
453
572
  }
454
573
  //#endregion
455
574
  //#region src/utils/extractEditorImageUrls.ts
456
- function xt(e) {
575
+ function Ft(e) {
457
576
  if (!e) return [];
458
577
  let t = /* @__PURE__ */ new Set();
459
578
  for (let n of e.matchAll(/!\[[^\]]*]\((https?:\/\/[^)\s]+)\)/g)) n[1] && t.add(n[1].replaceAll("&amp;", "&"));
@@ -462,7 +581,7 @@ function xt(e) {
462
581
  }
463
582
  //#endregion
464
583
  //#region src/utils/storagePath.ts
465
- function St(e) {
584
+ function It(e) {
466
585
  try {
467
586
  let t = new URL(e), n = t.hostname.toLowerCase();
468
587
  if (!(n === "firebasestorage.googleapis.com" || n.endsWith(".firebasestorage.app"))) return null;
@@ -472,106 +591,106 @@ function St(e) {
472
591
  return null;
473
592
  }
474
593
  }
475
- function Ct(e) {
594
+ function Lt(e) {
476
595
  return e.match(/(?:^|\/)posts\/([^/]+)\//)?.[1] ?? null;
477
596
  }
478
597
  //#endregion
479
598
  //#region src/utils/deletePostCascade.ts
480
- async function wt(e) {
599
+ async function Rt(e) {
481
600
  try {
482
601
  let t = await Pe(e);
483
- await Promise.all([...t.items.map((e) => Me(e).catch(() => {})), ...t.prefixes.map((e) => wt(e))]);
602
+ await Promise.all([...t.items.map((e) => Me(e).catch(() => {})), ...t.prefixes.map((e) => Rt(e))]);
484
603
  } catch {}
485
604
  }
486
- async function Tt(e, t) {
487
- await wt(Fe(e, `${t}/images`)), await wt(Fe(e, `${t}/attachments`));
605
+ async function zt(e, t) {
606
+ await Rt(Fe(e, `${t}/images`)), await Rt(Fe(e, `${t}/attachments`));
488
607
  }
489
- async function Et(e, t) {
608
+ async function Bt(e, t) {
490
609
  let n = [...new Set([...t].filter(Boolean))];
491
610
  await Promise.all(n.map((t) => Me(Fe(e, t)).catch(() => {})));
492
611
  }
493
- var Dt = 450;
494
- async function Ot(e, t) {
612
+ var Vt = 450;
613
+ async function Ht(e, t) {
495
614
  let n = await B(t);
496
- for (let t = 0; t < n.docs.length; t += Dt) {
615
+ for (let t = 0; t < n.docs.length; t += Vt) {
497
616
  let r = ye(e);
498
- n.docs.slice(t, t + Dt).forEach((e) => r.delete(e.ref)), await r.commit();
617
+ n.docs.slice(t, t + Vt).forEach((e) => r.delete(e.ref)), await r.commit();
499
618
  }
500
619
  }
501
- async function kt(e, t, n) {
502
- await Ot(e, G(a(e, t), X("postId", "==", n)));
620
+ async function Ut(e, t, n) {
621
+ await Ht(e, G(a(e, t), X("postId", "==", n)));
503
622
  }
504
- async function At(e, t, n) {
505
- await Ot(e, G(a(e, t), X("commentId", "==", n), X("target", "==", "comment")));
623
+ async function Wt(e, t, n) {
624
+ await Ht(e, G(a(e, t), X("commentId", "==", n), X("target", "==", "comment")));
506
625
  }
507
- async function jt(e, t, n, r) {
626
+ async function Gt(e, t, n, r) {
508
627
  let i = l(e, n, r), a = c(e, n, r), [o, s] = await Promise.all([z(i), z(a)]);
509
628
  if (!o.exists()) return;
510
629
  let d = o.data(), f = s.exists() ? String(s.data().content ?? "") : "";
511
- await Ot(e, G(u(e, n), X("postId", "==", r))), await kt(e, n, r);
630
+ await Ht(e, G(u(e, n), X("postId", "==", r))), await Ut(e, n, r);
512
631
  let p = /* @__PURE__ */ new Set(), h = /* @__PURE__ */ new Set();
513
632
  for (let e of d.attachments ?? []) {
514
633
  if (!e?.path) continue;
515
634
  p.add(e.path);
516
- let t = Ct(e.path);
635
+ let t = Lt(e.path);
517
636
  t && t !== r && h.add(t);
518
637
  }
519
- for (let e of xt(f)) {
520
- let t = St(e);
638
+ for (let e of Ft(f)) {
639
+ let t = It(e);
521
640
  if (!t) continue;
522
641
  p.add(t);
523
642
  let n = t.replace(/\/images\/([^/]+)$/, "/images/thumbnails/$1").replace(/\.[^.]+$/, ".jpg");
524
643
  n !== t && n.includes("/images/thumbnails/") && p.add(n);
525
644
  let i = t.match(/^(.*\/images\/)([^/]+)\.[^.]+$/);
526
645
  i && !t.includes("/thumbnails/") && p.add(`${i[1]}thumbnails/${i[2]}.jpg`);
527
- let a = Ct(t);
646
+ let a = Lt(t);
528
647
  a && a !== r && h.add(a);
529
648
  }
530
- await fe(a), await Tt(t, m(n, r));
531
- for (let e of h) await Tt(t, m(n, e));
532
- await Et(t, p), await fe(i);
649
+ await fe(a), await zt(t, m(n, r));
650
+ for (let e of h) await zt(t, m(n, e));
651
+ await Bt(t, p), await fe(i);
533
652
  }
534
653
  //#endregion
535
654
  //#region src/utils/postBody.ts
536
- function Mt(e) {
655
+ function Kt(e) {
537
656
  return String(e || "").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<img\b[^>]*>/gi, " ").replace(/<iframe\b[^>]*>[\s\S]*?<\/iframe>/gi, " ").replace(/<video\b[\s\S]*?<\/video>/gi, " ").replace(/<audio\b[\s\S]*?<\/audio>/gi, " ").replace(/<[^>]+>/g, " ").replace(/&nbsp;/gi, " ").replace(/&#160;/g, " ").replace(/&[a-z]+;/gi, " ").replace(/&#\d+;/g, " ").replace(/[\u200B-\u200D\uFEFF\u00AD]/g, "").replace(/\s+/g, " ").trim();
538
657
  }
539
- function Nt(e, t = 40) {
540
- let n = Mt(e);
658
+ function qt(e, t = 40) {
659
+ let n = Kt(e);
541
660
  if (!n) return "";
542
661
  if (n.length <= t) return n;
543
662
  let r = n.slice(0, t), i = r.lastIndexOf(" ");
544
663
  return `${(i >= Math.floor(t * .6) ? r.slice(0, i) : r).trimEnd()}…`;
545
664
  }
546
- function Pt(e) {
547
- return Mt(e).length > 0;
665
+ function Jt(e) {
666
+ return Kt(e).length > 0;
548
667
  }
549
- function Ft(e, t) {
668
+ function Yt(e, t) {
550
669
  return /<img\b/i.test(String(e || "")) ? !0 : (t || []).some((e) => !!e?.type?.startsWith("image/"));
551
670
  }
552
- function It(e) {
553
- return !Pt(e);
671
+ function Xt(e) {
672
+ return !Jt(e);
554
673
  }
555
674
  //#endregion
556
675
  //#region src/utils/postPreview.ts
557
- function Lt(e) {
676
+ function Zt(e) {
558
677
  return e.replace(/&amp;/g, "&").replace(/&quot;/g, "\"").replace(/&#39;/g, "'");
559
678
  }
560
- function Rt(e) {
679
+ function Qt(e) {
561
680
  if (e) try {
562
- let t = new URL(Lt(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
681
+ let t = new URL(Zt(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
563
682
  if (n === "youtu.be") return t.pathname.split("/").filter(Boolean)[0];
564
683
  if (n.endsWith("youtube.com") || n === "youtube-nocookie.com") return t.pathname.startsWith("/embed/") || t.pathname.startsWith("/shorts/") ? t.pathname.split("/")[2] : t.searchParams.get("v") || void 0;
565
684
  } catch {
566
685
  return;
567
686
  }
568
687
  }
569
- function zt(e) {
570
- let t = Rt(e.videoUrl);
688
+ function $t(e) {
689
+ let t = Qt(e.videoUrl);
571
690
  return t ? `https://i.ytimg.com/vi/${t}/hqdefault.jpg` : e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
572
691
  }
573
- function Bt(e, t = []) {
574
- let n = e.match(/<img\b[^>]*\bsrc=["']([^"']+)["']/i), r = e.match(/<iframe\b[^>]*\bsrc=["']([^"']+)["']/i), i = t.find((e) => e.type.startsWith("image/"))?.url, a = t.find((e) => e.type.startsWith("video/"))?.url, o = r ? Lt(r[1]) : a, s = o ? Rt(o) : void 0, c = n ? Lt(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
692
+ function en(e, t = []) {
693
+ let n = e.match(/<img\b[^>]*\bsrc=["']([^"']+)["']/i), r = e.match(/<iframe\b[^>]*\bsrc=["']([^"']+)["']/i), i = t.find((e) => e.type.startsWith("image/"))?.url, a = t.find((e) => e.type.startsWith("video/"))?.url, o = r ? Zt(r[1]) : a, s = o ? Qt(o) : void 0, c = n ? Zt(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
575
694
  return {
576
695
  summary: e.replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div|li|h[1-6]|blockquote)>/gi, "\n").replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/[^\S\n]+/g, " ").replace(/ *\n */g, "\n").replace(/\n{2,}/g, "\n").trim().slice(0, 160),
577
696
  ...c ? { previewImage: c } : {},
@@ -580,10 +699,10 @@ function Bt(e, t = []) {
580
699
  }
581
700
  //#endregion
582
701
  //#region src/utils/saveBoardVisibility.ts
583
- function Vt(e) {
702
+ function tn(e) {
584
703
  return e === "hidden" ? "hidden" : "public";
585
704
  }
586
- async function Ht(e, t, n, r) {
705
+ async function nn(e, t, n, r) {
587
706
  let i;
588
707
  for (;;) {
589
708
  let a = await B(G(t, X("boardId", "==", n), W("__name__"), ...i ? [J(i)] : [], H(400)));
@@ -593,20 +712,20 @@ async function Ht(e, t, n, r) {
593
712
  if (s && await o.commit(), i = a.docs[a.docs.length - 1], a.docs.length < 400) break;
594
713
  }
595
714
  }
596
- async function Ut(e, t, n, r) {
597
- await Ht(e, M(e, t), n, r), await Ht(e, u(e, t), n, r);
715
+ async function rn(e, t, n, r) {
716
+ await nn(e, M(e, t), n, r), await nn(e, u(e, t), n, r);
598
717
  }
599
- async function Wt(e, t, n, r) {
600
- let i = o(e, t, n), a = Vt(r.visibility);
718
+ async function an(e, t, n, r) {
719
+ let i = o(e, t, n), a = tn(r.visibility);
601
720
  await ve(e, async (e) => {
602
721
  let t = await e.get(i), n = t.data(), o = n?.visibilityTransition || "";
603
722
  if (o && o !== a) throw Error("Finish the pending visibility change before changing it again.");
604
- return !t.exists() || !o && Vt(n?.visibility) === a ? (e.set(i, r, { merge: !0 }), !1) : (e.set(i, {
723
+ return !t.exists() || !o && tn(n?.visibility) === a ? (e.set(i, r, { merge: !0 }), !1) : (e.set(i, {
605
724
  ...r,
606
- visibility: a === "public" ? "public" : Vt(n?.visibility),
725
+ visibility: a === "public" ? "public" : tn(n?.visibility),
607
726
  visibilityTransition: a
608
727
  }, { merge: !0 }), !0);
609
- }) && (await Ut(e, t, n, a !== "hidden"), await ve(e, async (e) => {
728
+ }) && (await rn(e, t, n, a !== "hidden"), await ve(e, async (e) => {
610
729
  if ((await e.get(i)).data()?.visibilityTransition !== a) throw Error("The visibility transition changed. Reload and retry.");
611
730
  e.update(i, {
612
731
  visibility: a,
@@ -616,8 +735,267 @@ async function Wt(e, t, n, r) {
616
735
  }));
617
736
  }
618
737
  //#endregion
738
+ //#region src/utils/moderation-prompt.ts
739
+ var on = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\n아래 텍스트에 욕설·비속어·성희롱·혐오·위협·스팸·도배성 광고가 있으면 반드시 거부(flagged=true)합니다.\n\nflagged=true 예:\n- 욕설/비속어: 씨발, 시발, 병신, 지랄, 좆, 개새끼, 존나, fuck, shit, bitch 등\n- 성적 모욕, 혐오 발언, 협박\n- 게시판 주제와 무관한 도배성 광고/스팸\n\nflagged=false:\n- 게시판 주제에 맞는 정상적인 글/댓글 (강한 의견이나 정당한 비판은 허용)\n\nJSON만 출력:\n{\"flagged\":boolean,\"category\":\"none\"|\"abuse\"|\"spam\"|\"adult\"|\"violence\"|\"other\",\"reason\":string}\n- flagged=true면 category를 고르고, reason은 한국어로 **무엇이 문제인지** 구체적으로 1문장\n (예: \"욕설(비속어)이 포함되어 게시할 수 없습니다.\", \"광고·스팸성 문구가 있어 게시할 수 없습니다.\")\n 욕설 단어 전체를 그대로 길게 인용하지 말고, 유형(욕설/혐오/스팸 등)을 분명히 밝힌다.\n- flagged=false면 category는 \"none\", reason은 \"\"", sn = {
740
+ ko: "Korean",
741
+ en: "English",
742
+ ja: "Japanese",
743
+ de: "German",
744
+ fr: "French",
745
+ es: "Spanish",
746
+ pt: "Portuguese",
747
+ zh: "Simplified Chinese",
748
+ ar: "Arabic",
749
+ id: "Indonesian"
750
+ };
751
+ function cn(e) {
752
+ let t = sn[e] ?? "English", n = E(e).name;
753
+ return t === n ? t : `${t} (${n})`;
754
+ }
755
+ function ln(e) {
756
+ let t = cn(e);
757
+ return `You are a strict moderator reviewing content for a message board.
758
+ If the text below contains profanity, vulgar slang, sexual harassment, hate speech, threats, spam, or repetitive/flooding advertisements, you MUST reject it (flagged=true).
759
+
760
+ The content may be written in ANY language, or a mix of languages — not only the board's language.
761
+ Judge every language equally strictly, including slang, abbreviations, romanizations, deliberate misspellings,
762
+ inserted spaces/symbols, and other obfuscation (e.g. in Korean, Japanese, Chinese, Arabic, Spanish, etc.).
763
+
764
+ flagged=true examples:
765
+ - Profanity/vulgar slang: fuck, shit, bitch, asshole, f*ck, sh1t /
766
+ Korean: 씨발, 시발, ㅅㅂ, 병신, ㅂㅅ, 지랄, 좆, 개새끼, 존나 /
767
+ Japanese: クソ, 死ね, ちんこ / Chinese: 操你妈, 傻逼, 他妈的 /
768
+ Spanish: puta, mierda, pendejo / German: Scheiße, Hurensohn / French: putain, connard /
769
+ Portuguese: porra, caralho / Indonesian: anjing, bangsat, kontol / Arabic: كس, شرموطة, etc.
770
+ - Sexual insults, hate speech, threats
771
+ - Flooding advertisements/spam unrelated to the board's topic
772
+
773
+ flagged=false:
774
+ - Normal posts/comments that fit the board's topic (strong opinions and legitimate criticism are allowed)
775
+
776
+ Output JSON only:
777
+ {"flagged":boolean,"category":"none"|"abuse"|"spam"|"adult"|"violence"|"other","reason":string}
778
+ - If flagged=true, choose a category, and write reason as ONE concrete sentence in ${t} explaining **what the problem is**
779
+ (e.g. "It contains profanity, so it can't be posted.", "It contains advertising/spam, so it can't be posted." — but written in ${t}).
780
+ Do not quote the offensive words at length; clearly state the type (profanity/hate/spam, etc.).
781
+ - If flagged=false, category is "none" and reason is ""`;
782
+ }
783
+ function un(e = "ko") {
784
+ return e === "ko" ? on : ln(e);
785
+ }
786
+ function dn(e, t = "ko") {
787
+ let n = `"""${e.slice(0, 4e3)}"""`;
788
+ return t === "ko" ? `심사할 텍스트:\n${n}` : `Text to review:\n${n}`;
789
+ }
790
+ function fn(e = "ko") {
791
+ return e === "ko" ? "이 이미지를 심사해 주세요." : "Please review this image.";
792
+ }
793
+ var pn = {
794
+ abuse: "욕설·비속어·모욕",
795
+ spam: "스팸·광고",
796
+ adult: "선정적·성인 내용",
797
+ violence: "폭력·위협",
798
+ other: "부적절한 내용",
799
+ none: ""
800
+ };
801
+ function mn(e, t = "ko") {
802
+ return !e || e === "none" ? "" : e in pn ? w(t, `moderationPrompt.categories.${e}`) : e;
803
+ }
804
+ function hn(e) {
805
+ let t = e.locale ?? "ko";
806
+ if (e.via === "local" && e.localHit) {
807
+ let n = e.localHit.trim();
808
+ return n.length > 0 && n.length <= 12 ? w(t, "moderationPrompt.localHit", { hit: n }) : w(t, "moderationPrompt.localGeneric");
809
+ }
810
+ let n = (e.reason || "").trim();
811
+ if (n) return n;
812
+ let r = e.category ? mn(e.category, t) : "";
813
+ return r ? w(t, "moderationPrompt.categoryBlocked", { category: r }) : w(t, "moderationPrompt.genericBlocked");
814
+ }
815
+ var gn = [
816
+ "씨발",
817
+ "시발",
818
+ "시이발",
819
+ "씨팔",
820
+ "시팔",
821
+ "병신",
822
+ "븅신",
823
+ "지랄",
824
+ "좆",
825
+ "존나",
826
+ "개새끼",
827
+ "개새",
828
+ "새끼",
829
+ "꺼져",
830
+ "닥쳐",
831
+ "씹",
832
+ "니미",
833
+ "느금",
834
+ "애미",
835
+ "애비",
836
+ "fuck",
837
+ "shit",
838
+ "bitch",
839
+ "asshole"
840
+ ];
841
+ function _n(e) {
842
+ return new RegExp(e.join("|"), "i");
843
+ }
844
+ var vn = /* @__PURE__ */ new Set([
845
+ "none",
846
+ "abuse",
847
+ "spam",
848
+ "adult",
849
+ "violence",
850
+ "other"
851
+ ]);
852
+ function yn(e) {
853
+ let t = e.trim();
854
+ if (!t) return null;
855
+ let n;
856
+ try {
857
+ n = JSON.parse(t);
858
+ } catch {
859
+ let e = t.match(/\{[\s\S]*\}/);
860
+ if (!e) return null;
861
+ try {
862
+ n = JSON.parse(e[0]);
863
+ } catch {
864
+ return null;
865
+ }
866
+ }
867
+ if (typeof n != "object" || !n) return null;
868
+ let r = n;
869
+ if (typeof r.flagged != "boolean") return null;
870
+ let i = typeof r.category == "string" && vn.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
871
+ return {
872
+ flagged: r.flagged,
873
+ category: i,
874
+ reason: a
875
+ };
876
+ }
877
+ //#endregion
878
+ //#region src/composables/useMemiBoardAutoTranslate.ts
879
+ var bn = 3e4, xn = 45e3, Sn = 12e3, Cn = 3;
880
+ function wn(e) {
881
+ return e.trim().replace(/^```(?:html|text)?\s*/i, "").replace(/\s*```$/, "").trim();
882
+ }
883
+ async function Tn(e, t, n) {
884
+ let r = Array(e.length), i = 0;
885
+ async function a() {
886
+ for (; i < e.length;) {
887
+ let t = i++;
888
+ r[t] = await n(e[t]);
889
+ }
890
+ }
891
+ return await Promise.all(Array.from({ length: Math.min(t, e.length) }, () => a())), r;
892
+ }
893
+ function En(e, t) {
894
+ return new Promise((n, r) => {
895
+ let i = setTimeout(() => r(/* @__PURE__ */ Error(`translation timed out after ${t / 1e3}s`)), t);
896
+ e.then((e) => {
897
+ clearTimeout(i), n(e);
898
+ }, (e) => {
899
+ clearTimeout(i), r(e);
900
+ });
901
+ });
902
+ }
903
+ function Dn() {
904
+ let e = Ce(), { moderation: t } = ue();
905
+ function n(n, r) {
906
+ let i = Re(e, {
907
+ backend: new Ie(),
908
+ useLimitedUseAppCheckTokens: t.useLimitedUseAppCheckTokens !== !1
909
+ }), a = Le.object({ properties: Object.fromEntries(r.map((e) => [e, Le.string()])) });
910
+ return ze(i, {
911
+ model: t.model ?? "gemini-3.5-flash-lite",
912
+ systemInstruction: [
913
+ "You translate short user-interface strings for a website.",
914
+ "Return natural, concise UI wording a native speaker would expect in a menu or title — not literal word-for-word translations.",
915
+ "Keep proper nouns, brand names, product names and game titles as they are unless they have a well-known localized name.",
916
+ "Use the context only to understand meaning; never translate or output it."
917
+ ].join("\n"),
918
+ generationConfig: {
919
+ temperature: .2,
920
+ maxOutputTokens: 2048,
921
+ responseMimeType: "application/json",
922
+ responseSchema: Le.object({ properties: Object.fromEntries(n.map((e) => [e, a])) })
923
+ }
924
+ });
925
+ }
926
+ async function r(e, t, r, i) {
927
+ let a = Object.keys(e.fields), o = [
928
+ `Purpose: ${i}`,
929
+ `Source language: ${cn(t)} (${t})`,
930
+ `Translate into: ${r.map((e) => `${e} = ${cn(e)}`).join(", ")}`,
931
+ e.context ? `Context (do not translate): ${e.context}` : "",
932
+ "Text (JSON):",
933
+ JSON.stringify(e.fields)
934
+ ].filter(Boolean).join("\n"), s = await En(n(r, a).generateContent(o), bn), c = JSON.parse(s.response.text()), l = {};
935
+ for (let e of r) {
936
+ let t = c?.[e];
937
+ if (!t) continue;
938
+ let n = {};
939
+ for (let e of a) {
940
+ let r = typeof t[e] == "string" ? t[e].trim() : "";
941
+ r && (n[e] = r);
942
+ }
943
+ Object.keys(n).length && (l[e] = n);
944
+ }
945
+ return l;
946
+ }
947
+ async function i(e, t = {}) {
948
+ let n = t.source ?? "ko", i = (t.targets ?? A.map((e) => e.code)).filter((e) => e !== n), a = t.purpose ?? "website UI labels", o = e.map((e) => ({
949
+ ...e,
950
+ fields: Object.fromEntries(Object.entries(e.fields).map(([e, t]) => [e, t.trim()]).filter(([, e]) => e))
951
+ })).filter((e) => Object.keys(e.fields).length);
952
+ if (!o.length || !i.length) return {};
953
+ let s = await Promise.all(o.map((e) => r(e, n, i, a)));
954
+ return Object.fromEntries(o.map((e, t) => [e.id, s[t]]));
955
+ }
956
+ function a() {
957
+ let n = Re(e, {
958
+ backend: new Ie(),
959
+ useLimitedUseAppCheckTokens: t.useLimitedUseAppCheckTokens !== !1
960
+ });
961
+ return ze(n, {
962
+ model: t.model ?? "gemini-3.5-flash-lite",
963
+ systemInstruction: [
964
+ "You translate an HTML fragment of a message-board post.",
965
+ "Keep every tag, attribute, URL, class, and image source exactly as written.",
966
+ "Translate only human-readable text nodes.",
967
+ "Output only the HTML fragment. Do not add explanations or markdown code fences."
968
+ ].join("\n"),
969
+ generationConfig: {
970
+ temperature: .2,
971
+ maxOutputTokens: 8192
972
+ }
973
+ });
974
+ }
975
+ async function o(e, t = {}) {
976
+ let n = t.source ?? "ko", r = (t.targets ?? A.map((e) => e.code)).filter((e) => e !== n), i = e.trim();
977
+ if (!i || !r.length) return {};
978
+ if (i.length > 12e3) throw Error(`translation is limited to ${Sn} characters`);
979
+ let o = await Tn(r, Cn, async (e) => {
980
+ let t = [
981
+ `Source language: ${cn(n)} (${n})`,
982
+ `Translate into: ${e} = ${cn(e)}`,
983
+ "HTML:",
984
+ i
985
+ ].join("\n"), r = wn((await En(a().generateContent(t), xn)).response.text());
986
+ if (!r) throw Error(`empty translation for ${e}`);
987
+ return [e, r];
988
+ });
989
+ return Object.fromEntries(o);
990
+ }
991
+ return {
992
+ translateEntries: i,
993
+ translateHtml: o
994
+ };
995
+ }
996
+ //#endregion
619
997
  //#region src/composables/useMemiBoardSettings.ts
620
- var Gt = [
998
+ var On = [
621
999
  {
622
1000
  id: "free",
623
1001
  label: "자유",
@@ -645,109 +1023,154 @@ var Gt = [
645
1023
  visibility: "public",
646
1024
  order: 2
647
1025
  }
648
- ], Kt = Gt;
649
- function qt(e) {
1026
+ ], kn = On;
1027
+ function An(e) {
650
1028
  return e === "hidden" ? "hidden" : "public";
651
1029
  }
652
- function Jt(e, t, n) {
1030
+ function jn(e, t, n) {
653
1031
  return {
654
1032
  id: e,
655
1033
  label: typeof t.label == "string" && t.label.trim() ? t.label.trim() : e,
656
1034
  order: typeof t.order == "number" ? t.order : n,
657
1035
  description: (typeof t.description == "string" ? t.description : "") || "",
658
- visibility: qt(t.visibility),
1036
+ visibility: An(t.visibility),
659
1037
  visibilityTransition: t.visibilityTransition === "hidden" || t.visibilityTransition === "public" ? t.visibilityTransition : void 0,
660
1038
  listView: t.listView ?? "default",
661
1039
  editorType: t.editorType ?? (t.listView === "image" ? "image" : "default"),
662
1040
  writeRole: t.writeRole ?? "user",
663
1041
  commentWriteRole: t.commentWriteRole ?? "user",
664
1042
  allowedStaffUids: Array.isArray(t.allowedStaffUids) ? t.allowedStaffUids : [],
665
- locale: E(t.locale) ?? ""
1043
+ allowedManagerUids: Array.isArray(t.allowedManagerUids) ? t.allowedManagerUids : [],
1044
+ locale: T(t.locale) ?? "",
1045
+ i18n: Q(t.i18n),
1046
+ i18nSource: Qe(t.i18nSource)
666
1047
  };
667
1048
  }
668
- function Yt() {
669
- let e = () => j(), t = Z(), n = Ce(), { isSignedIn: i, isAdmin: a, isStaff: s, user: c } = ht(), { t: l } = O(), u = N(() => G(y(t, e()), W("order", "asc"))), { data: d, pending: f } = be(u, { ssrKey: r(e(), "boards") }), p = N(() => d.value.map((e, t) => Jt(e.id, e, t))), m = p, h = N(() => p.value.filter((e) => e.visibility !== "hidden")), g = h;
670
- function _(e) {
671
- if (e) return p.value.find((t) => t.id === e)?.label ?? e;
672
- }
673
- let v = _;
674
- function b(e) {
675
- if (e) return p.value.find((t) => t.id === e)?.description?.trim() || void 0;
1049
+ function Mn() {
1050
+ let e = () => j(), t = Z(), n = Ce(), { isSignedIn: i, isAdmin: a, isStaff: s, user: c } = kt(), { t: l, locale: u } = D(), { translateEntries: d } = Dn(), f = N(() => G(y(t, e()), W("order", "asc"))), { data: p, pending: m } = be(f, { ssrKey: r(e(), "boards") }), h = N(() => p.value.map((e, t) => jn(e.id, e, t))), g = h, _ = N(() => h.value.filter((e) => e.visibility !== "hidden")), v = _;
1051
+ function b(e, t) {
1052
+ if (!e) return;
1053
+ let n = h.value.find((t) => t.id === e);
1054
+ return n ? rt(n.i18n, n.i18nSource, "label", n.label, t ?? u.value) || n.label : e;
676
1055
  }
677
1056
  let x = b;
678
- function S(e) {
679
- return e ? p.value.find((t) => t.id === e)?.visibility ?? "public" : "public";
1057
+ function S(e, t) {
1058
+ if (!e) return;
1059
+ let n = h.value.find((t) => t.id === e), r = n?.description?.trim();
1060
+ if (n && r) return rt(n.i18n, n.i18nSource, "description", r, t ?? u.value) || void 0;
680
1061
  }
681
1062
  let C = S;
682
1063
  function w(e) {
683
- return S(e) === "hidden";
1064
+ return e ? h.value.find((t) => t.id === e)?.visibility ?? "public" : "public";
1065
+ }
1066
+ let ee = w;
1067
+ function E(e) {
1068
+ return w(e) === "hidden";
684
1069
  }
685
- let T = w;
686
- function D(e) {
687
- if (e) return p.value.find((t) => t.id === e);
1070
+ let O = E;
1071
+ function te(e) {
1072
+ if (e) return h.value.find((t) => t.id === e);
688
1073
  }
689
- it((e) => D(e));
1074
+ vt((e) => te(e));
690
1075
  function k(e) {
691
- return tt(c.value?.uid, a.value, s.value, e ? D(e) : void 0);
1076
+ return ht(c.value?.uid, a.value, s.value, e ? te(e) : void 0);
692
1077
  }
693
- let ee = N(() => p.value.filter((e) => k(e.id)));
694
- function te(e, t) {
1078
+ let A = N(() => h.value.filter((e) => k(e.id)));
1079
+ function re(e, t) {
695
1080
  return {
696
1081
  label: e.label.trim(),
697
1082
  description: (e.description ?? "").trim(),
698
- visibility: qt(e.visibility),
1083
+ visibility: An(e.visibility),
699
1084
  listView: e.listView ?? "default",
700
1085
  editorType: e.editorType ?? (e.listView === "image" ? "image" : "default"),
701
1086
  writeRole: e.writeRole ?? "user",
702
1087
  commentWriteRole: e.commentWriteRole ?? "user",
703
1088
  allowedStaffUids: e.allowedStaffUids ?? [],
704
- locale: E(e.locale) ?? "",
1089
+ allowedManagerUids: e.allowedManagerUids ?? [],
1090
+ locale: T(e.locale) ?? "",
1091
+ ...e.i18n ? { i18n: e.i18n } : {},
1092
+ ...e.i18nSource ? { i18nSource: e.i18nSource } : {},
705
1093
  order: t,
706
1094
  updatedAt: K()
707
1095
  };
708
1096
  }
709
- async function A(n, r = n.order ?? 0) {
1097
+ async function ie(e) {
1098
+ let t = e.label.trim(), n = (e.description ?? "").trim();
1099
+ if (!t) return e;
1100
+ if (st({
1101
+ ...e,
1102
+ label: t,
1103
+ description: n
1104
+ })) return {
1105
+ ...e,
1106
+ label: t,
1107
+ description: n
1108
+ };
1109
+ let r = u.value, i = { label: t };
1110
+ n && (i.description = n);
1111
+ let a = et(r, i, (await d([{
1112
+ id: e.id || "board",
1113
+ fields: i,
1114
+ context: e.id
1115
+ }], {
1116
+ source: r,
1117
+ purpose: "board name and short description shown in navigation and page headers"
1118
+ }))[e.id || "board"]);
1119
+ if (!tt(a, Object.keys(i))) throw Error(l("settings.autoTranslate.failed"));
1120
+ return {
1121
+ ...e,
1122
+ label: t,
1123
+ description: n,
1124
+ i18n: a,
1125
+ i18nSource: {
1126
+ locale: r,
1127
+ label: t,
1128
+ description: n
1129
+ }
1130
+ };
1131
+ }
1132
+ async function ae(n, r = n.order ?? 0) {
710
1133
  if (!i.value) throw Error(l("boardSettings.signInRequired"));
711
1134
  let a = n.id.trim(), o = n.label.trim();
712
1135
  if (!a || !o) throw Error(l("boardSettings.idAndNameRequired"));
713
- let s = qt(n.visibility), c = te({
1136
+ let s = An(n.visibility), c = re({
714
1137
  ...n,
715
1138
  label: o,
716
1139
  visibility: s
717
1140
  }, r);
718
- await Wt(t, e(), a, c);
1141
+ await an(t, e(), a, c);
719
1142
  }
720
- let re = A;
721
- async function ie(e) {
1143
+ let oe = ae;
1144
+ async function se(e) {
722
1145
  if (!i.value) throw Error(l("boardSettings.signInRequired"));
723
1146
  for (let t of e) if (!t.id.trim() || !t.label.trim()) throw Error(l("boardSettings.idAndNameRequired"));
724
- for (let [t, n] of e.entries()) await A(n, t);
1147
+ for (let [t, n] of e.entries()) await ae(n, t);
725
1148
  }
726
- let ae = ie;
727
- async function oe(r) {
1149
+ let ce = se;
1150
+ async function le(r) {
728
1151
  if (!i.value) throw Error(l("boardSettings.signInRequired"));
729
1152
  let a = r.trim();
730
1153
  if (!a) throw Error(l("boardSettings.deleteIdRequired"));
731
1154
  for (;;) {
732
1155
  let r = await B(G(M(t, e()), X("boardId", "==", a), H(25)));
733
1156
  if (r.empty) break;
734
- for (let i of r.docs) await jt(t, Ne(n), e(), i.id);
1157
+ for (let i of r.docs) await Gt(t, Ne(n), e(), i.id);
735
1158
  }
736
1159
  await fe(o(t, e(), r));
737
1160
  }
738
- let se = oe;
739
- async function ce() {
1161
+ let ue = le;
1162
+ async function P() {
740
1163
  return Promise.resolve();
741
1164
  }
742
- async function le(e) {
1165
+ async function de(e) {
743
1166
  let t = e.trim();
744
1167
  if (!t) throw Error(l("boardSettings.nameRequired"));
745
1168
  if (!i.value) throw Error(l("boardSettings.signInRequired"));
746
- let n = p.value.find((e) => e.label === t);
1169
+ let n = h.value.find((e) => e.label === t);
747
1170
  if (n) return n.id;
748
- let r = ne(t) || `board-${Date.now()}`, a = r, o = 2, s = new Set(p.value.map((e) => e.id));
1171
+ let r = ne(t) || `board-${Date.now()}`, a = r, o = 2, s = new Set(h.value.map((e) => e.id));
749
1172
  for (; s.has(a);) a = `${r}-${o++}`;
750
- return await A({
1173
+ return await ae({
751
1174
  id: a,
752
1175
  label: t,
753
1176
  visibility: "public",
@@ -755,62 +1178,74 @@ function Yt() {
755
1178
  editorType: "default",
756
1179
  writeRole: "user",
757
1180
  commentWriteRole: "user",
758
- order: p.value.length
1181
+ order: h.value.length
759
1182
  }), a;
760
1183
  }
761
1184
  return {
762
- boards: p,
763
- publicBoards: h,
764
- boardLabel: _,
765
- boardDescription: b,
766
- boardVisibility: S,
767
- isBoardHidden: w,
768
- getBoard: D,
1185
+ boards: h,
1186
+ publicBoards: _,
1187
+ boardLabel: b,
1188
+ boardDescription: S,
1189
+ boardVisibility: w,
1190
+ isBoardHidden: E,
1191
+ getBoard: te,
769
1192
  canManageBoard: k,
770
- managedBoards: ee,
771
- categories: m,
772
- publicCategories: g,
773
- categoryLabel: v,
774
- categoryDescription: x,
775
- categoryVisibility: C,
776
- isCategoryHidden: T,
777
- settingsPending: f,
778
- ensureSettings: ce,
779
- addBoard: le,
780
- addCategory: le,
781
- saveBoard: A,
782
- saveCategory: re,
783
- saveBoards: ie,
784
- saveCategories: ae,
785
- deleteBoard: oe,
786
- deleteCategory: se,
787
- DEFAULT_BOARDS: Gt,
788
- DEFAULT_CATEGORIES: Kt
1193
+ managedBoards: A,
1194
+ categories: g,
1195
+ publicCategories: v,
1196
+ categoryLabel: x,
1197
+ categoryDescription: C,
1198
+ categoryVisibility: ee,
1199
+ isCategoryHidden: O,
1200
+ settingsPending: m,
1201
+ ensureSettings: P,
1202
+ addBoard: de,
1203
+ addCategory: de,
1204
+ saveBoard: ae,
1205
+ saveCategory: oe,
1206
+ translateBoard: ie,
1207
+ saveBoards: se,
1208
+ saveCategories: ce,
1209
+ deleteBoard: le,
1210
+ deleteCategory: ue,
1211
+ DEFAULT_BOARDS: On,
1212
+ DEFAULT_CATEGORIES: kn
789
1213
  };
790
1214
  }
791
1215
  //#endregion
792
1216
  //#region src/composables/useMemiBoardPosts.ts
793
- var Xt = /* @__PURE__ */ new Map();
794
- function Zt(e) {
1217
+ var Nn = /* @__PURE__ */ new Map();
1218
+ function Pn(e) {
795
1219
  if (!e || typeof e != "object") return !1;
796
1220
  let t = "code" in e ? String(e.code) : "";
797
1221
  return t === "permission-denied" || t === "firestore/permission-denied";
798
1222
  }
799
- function Qt(e, t) {
1223
+ function Fn(e, t) {
800
1224
  let n = I(e)?.trim();
801
1225
  if (!n) throw Error(w(t, "posts.boardIdRequired"));
802
1226
  return n;
803
1227
  }
804
- function $t(e) {
805
- let t = Z(), n = Ce(), { isBoardHidden: r, getBoard: i } = Yt(), { user: a, canManageBoard: o } = ht(), { t: s, locale: u } = O(), d = () => j(), f = () => Qt(e, u.value), p = () => M(t, d()), m = (e) => l(t, d(), e), h = (e) => c(t, d(), e);
806
- function g() {
807
- return !r(f());
1228
+ function In(e) {
1229
+ let t = Z(), n = Ce(), { isBoardHidden: r, getBoard: i } = Mn(), { user: a, canManageBoard: o } = kt(), { t: s, locale: u } = D(), d = () => j(), f = () => Fn(e, u.value), p = () => M(t, d()), m = (e) => l(t, d(), e), h = (e) => c(t, d(), e);
1230
+ function g(e, t, n) {
1231
+ return {
1232
+ id: e,
1233
+ ...t,
1234
+ i18n: Q(t.i18n),
1235
+ i18nSource: Qe(t.i18nSource),
1236
+ content: typeof n?.content == "string" ? n.content : "",
1237
+ contentI18n: Q(n?.i18n),
1238
+ contentI18nSource: $e(n?.i18nSource)
1239
+ };
808
1240
  }
809
1241
  function _() {
1242
+ return !r(f());
1243
+ }
1244
+ function v() {
810
1245
  return i(f())?.editorType === "image";
811
1246
  }
812
- function v(e) {
813
- let t = _(), n = Pt(e.content), r = Ft(e.content, e.attachments);
1247
+ function y(e) {
1248
+ let t = v(), n = Jt(e.content), r = Yt(e.content, e.attachments);
814
1249
  if (t) {
815
1250
  if (!r) throw Error(s("posts.photoRequired"));
816
1251
  return;
@@ -818,13 +1253,13 @@ function $t(e) {
818
1253
  if (!n && !r) throw Error(s("posts.bodyRequired"));
819
1254
  if (!e.title?.trim()) throw Error(s("posts.titleRequired"));
820
1255
  }
821
- function y() {
1256
+ function b() {
822
1257
  return me(p()).id;
823
1258
  }
824
- function b(e) {
825
- return bt(t, d(), f(), e, a.value?.uid, o(f()));
1259
+ function x(e) {
1260
+ return Pt(t, d(), f(), e, a.value?.uid, o(f()));
826
1261
  }
827
- async function x(e = {}) {
1262
+ async function S(e = {}) {
828
1263
  let t = e.pageSize ?? 20, n = [X("boardId", "==", f()), X("isPublished", "==", !0)];
829
1264
  e.publicOnly !== !1 && n.push(X("listed", "==", !0)), n.push(W("createdAt", "desc")), e.cursor && n.push(J(e.cursor)), n.push(H(t + 1));
830
1265
  let r = await B(G(p(), ...n)), i = r.docs.slice(0, t);
@@ -837,58 +1272,54 @@ function $t(e) {
837
1272
  hasMore: r.docs.length > t
838
1273
  };
839
1274
  }
840
- async function S(e) {
1275
+ async function C(e) {
841
1276
  let [t, n] = await Promise.all([z(m(e)), z(h(e))]);
842
- return t.exists() ? {
843
- id: t.id,
844
- ...t.data(),
845
- content: n.exists() ? n.data().content ?? "" : ""
846
- } : null;
1277
+ return t.exists() ? g(t.id, t.data(), n.exists() ? n.data() : void 0) : null;
847
1278
  }
848
- async function C(e) {
1279
+ async function w(e) {
849
1280
  try {
850
- let t = await b(e.trim());
1281
+ let t = await x(e.trim());
851
1282
  if (!t) return { status: "not-found" };
852
1283
  try {
853
- let e = await S(t.id);
1284
+ let e = await C(t.id);
854
1285
  return e ? {
855
1286
  status: "ok",
856
1287
  post: e
857
1288
  } : { status: "not-found" };
858
1289
  } catch (e) {
859
- return Zt(e) ? { status: "permission-denied" } : {
1290
+ return Pn(e) ? { status: "permission-denied" } : {
860
1291
  status: "error",
861
1292
  message: e instanceof Error ? e.message : String(e)
862
1293
  };
863
1294
  }
864
1295
  } catch (e) {
865
- return Zt(e) ? { status: "permission-denied" } : {
1296
+ return Pn(e) ? { status: "permission-denied" } : {
866
1297
  status: "error",
867
1298
  message: e instanceof Error ? e.message : String(e)
868
1299
  };
869
1300
  }
870
1301
  }
871
- async function w(e) {
872
- let t = e.trim(), n = Xt.get(`${f()}/${t}`);
1302
+ async function ee(e) {
1303
+ let t = e.trim(), n = Nn.get(`${f()}/${t}`);
873
1304
  if (n) return {
874
1305
  status: "ok",
875
1306
  id: n
876
1307
  };
877
1308
  try {
878
- let e = await b(t);
1309
+ let e = await x(t);
879
1310
  return e ? {
880
1311
  status: "ok",
881
1312
  id: e.id
882
1313
  } : { status: r(f()) ? "permission-denied" : "not-found" };
883
1314
  } catch (e) {
884
- return Zt(e) ? { status: "permission-denied" } : {
1315
+ return Pn(e) ? { status: "permission-denied" } : {
885
1316
  status: "error",
886
1317
  message: e instanceof Error ? e.message : String(e)
887
1318
  };
888
1319
  }
889
1320
  }
890
1321
  async function T(e) {
891
- return `${ne(e.trim()) || "post"}-${y()}`;
1322
+ return `${ne(e.trim()) || "post"}-${b()}`;
892
1323
  }
893
1324
  async function E(e) {
894
1325
  if (!e.createdAt) return {
@@ -919,11 +1350,11 @@ function $t(e) {
919
1350
  };
920
1351
  }
921
1352
  }
922
- async function D(e) {
923
- v(e);
924
- let n = e.postId || y(), r = _(), i, a;
925
- r ? (i = n, a = Nt(e.content)) : (a = e.title.trim(), i = e.slug?.trim() || await T(a));
926
- let o = Bt(e.content, e.attachments), s = g(), c = ye(t);
1353
+ async function O(e) {
1354
+ y(e);
1355
+ let n = e.postId || b(), r = v(), i, a;
1356
+ r ? (i = n, a = qt(e.content)) : (a = e.title.trim(), i = e.slug?.trim() || await T(a));
1357
+ let o = en(e.content, e.attachments), s = _(), c = ye(t);
927
1358
  return c.set(m(n), {
928
1359
  boardId: f(),
929
1360
  slug: i,
@@ -942,12 +1373,22 @@ function $t(e) {
942
1373
  moderationStatus: "approved",
943
1374
  moderationModel: e.moderationModel ?? null,
944
1375
  createdAt: K(),
945
- updatedAt: K()
946
- }), c.set(h(n), { content: e.content }), await c.commit(), Xt.set(`${f()}/${i}`, n), i;
947
- }
948
- async function k(e, t) {
949
- v(t);
950
- let n = _() ? Nt(t.content) : t.title.trim(), r = Bt(t.content, t.attachments), i = g();
1376
+ updatedAt: K(),
1377
+ ...e.translations ? {
1378
+ i18n: e.translations.i18n,
1379
+ i18nSource: e.translations.i18nSource
1380
+ } : {}
1381
+ }), c.set(h(n), {
1382
+ content: e.content,
1383
+ ...e.translations?.contentI18n ? {
1384
+ i18n: e.translations.contentI18n,
1385
+ i18nSource: e.translations.contentI18nSource
1386
+ } : {}
1387
+ }), await c.commit(), Nn.set(`${f()}/${i}`, n), i;
1388
+ }
1389
+ async function te(e, t) {
1390
+ y(t);
1391
+ let n = v() ? qt(t.content) : t.title.trim(), r = en(t.content, t.attachments), i = _();
951
1392
  await Promise.all([Y(m(e), {
952
1393
  title: n,
953
1394
  ...r,
@@ -956,37 +1397,64 @@ function $t(e) {
956
1397
  tags: t.tags ?? [],
957
1398
  listed: i,
958
1399
  attachments: t.attachments ?? [],
1400
+ updatedAt: K(),
1401
+ ...t.translations ? {
1402
+ i18n: t.translations.i18n,
1403
+ i18nSource: t.translations.i18nSource
1404
+ } : {}
1405
+ }), q(h(e), {
1406
+ content: t.content,
1407
+ ...t.translations ? t.translations.contentI18n ? {
1408
+ i18n: t.translations.contentI18n,
1409
+ i18nSource: t.translations.contentI18nSource
1410
+ } : {
1411
+ i18n: pe(),
1412
+ i18nSource: pe()
1413
+ } : {}
1414
+ }, { merge: !0 })]);
1415
+ }
1416
+ async function k(e, t) {
1417
+ await Promise.all([Y(m(e), {
1418
+ i18n: t.i18n,
1419
+ i18nSource: t.i18nSource,
959
1420
  updatedAt: K()
960
- }), q(h(e), { content: t.content }, { merge: !0 })]);
1421
+ }), q(h(e), t.contentI18n ? {
1422
+ i18n: t.contentI18n,
1423
+ i18nSource: t.contentI18nSource
1424
+ } : {
1425
+ i18n: pe(),
1426
+ i18nSource: pe()
1427
+ }, { merge: !0 })]);
961
1428
  }
962
- async function ee(e) {
1429
+ async function A(e) {
963
1430
  await Y(m(e), {
964
1431
  isPublished: !0,
965
1432
  publishedAt: K(),
966
1433
  updatedAt: K()
967
1434
  });
968
1435
  }
969
- async function te(e) {
970
- await jt(t, Ne(n), d(), e);
971
- for (let [t, n] of Xt) n === e && Xt.delete(t);
1436
+ async function re(e) {
1437
+ await Gt(t, Ne(n), d(), e);
1438
+ for (let [t, n] of Nn) n === e && Nn.delete(t);
972
1439
  }
973
1440
  return {
974
- createPostId: y,
975
- getPosts: x,
976
- getPost: S,
977
- getPostBySlug: C,
978
- resolvePostIdBySlug: w,
1441
+ createPostId: b,
1442
+ getPosts: S,
1443
+ getPost: C,
1444
+ getPostBySlug: w,
1445
+ resolvePostIdBySlug: ee,
979
1446
  resolveUniqueSlug: T,
980
1447
  getAdjacentPosts: E,
981
- createPost: D,
982
- updatePost: k,
983
- publishPost: ee,
984
- deletePost: te
1448
+ createPost: O,
1449
+ updatePost: te,
1450
+ savePostTranslations: k,
1451
+ publishPost: A,
1452
+ deletePost: re
985
1453
  };
986
1454
  }
987
- var en = 10;
988
- function tn(e, t = {}) {
989
- let n = Z(), { isAdmin: r, user: i, canManageBoard: a } = ht(), o = () => j(), s = t.pageSize ?? en, c = N(() => i.value?.uid ?? ""), l = N(() => Qt(e)), u = () => M(n, o()), d = N(() => t.publicOnly === void 0 ? !(r.value || a(l.value)) : typeof t.publicOnly == "boolean" ? t.publicOnly : t.publicOnly.value), f = F([]), p = F([]), m = F([]), h = F(!0), g = F(!0), _ = F(!1), v = F(""), y = null, b = null, x, S;
1455
+ var Ln = 10;
1456
+ function Rn(e, t = {}) {
1457
+ let n = Z(), { isAdmin: r, user: i, canManageBoard: a } = kt(), o = () => j(), s = t.pageSize ?? Ln, c = N(() => i.value?.uid ?? ""), l = N(() => Fn(e)), u = () => M(n, o()), d = N(() => t.publicOnly === void 0 ? !(r.value || a(l.value)) : typeof t.publicOnly == "boolean" ? t.publicOnly : t.publicOnly.value), f = F([]), p = F([]), m = F([]), h = F(!0), g = F(!0), _ = F(!1), v = F(""), y = null, b = null, x, S;
990
1458
  function C(e) {
991
1459
  return e.createdAt?.toMillis?.() ?? 0;
992
1460
  }
@@ -999,12 +1467,12 @@ function tn(e, t = {}) {
999
1467
  ]) t.id && e.set(t.id, t);
1000
1468
  return [...e.values()].sort((e, t) => C(t) - C(e) || String(t.id).localeCompare(String(e.id)));
1001
1469
  });
1002
- function T() {
1470
+ function ee() {
1003
1471
  let e = [X("boardId", "==", l.value), X("isPublished", "==", !0)];
1004
1472
  return d.value && e.push(X("listed", "==", !0)), e;
1005
1473
  }
1006
- function E() {
1007
- x?.(), x = U(G(u(), ...T(), W("createdAt", "desc"), W(R(), "desc"), H(s)), (e) => {
1474
+ function T() {
1475
+ x?.(), x = U(G(u(), ...ee(), W("createdAt", "desc"), W(R(), "desc"), H(s)), (e) => {
1008
1476
  f.value = e.docs.map((e) => ({
1009
1477
  id: e.id,
1010
1478
  ...e.data()
@@ -1013,7 +1481,7 @@ function tn(e, t = {}) {
1013
1481
  console.error("[memi-board:postList] onSnapshot failed", e), v.value = e.message || String(e), h.value = !1;
1014
1482
  });
1015
1483
  }
1016
- function D() {
1484
+ function E() {
1017
1485
  S?.();
1018
1486
  let e = c.value;
1019
1487
  if (!e) {
@@ -1029,12 +1497,12 @@ function tn(e, t = {}) {
1029
1497
  console.error("[memi-board:postList] own draft onSnapshot failed", e);
1030
1498
  });
1031
1499
  }
1032
- async function O() {
1500
+ async function D() {
1033
1501
  if (!(_.value || !g.value || h.value)) {
1034
1502
  _.value = !0, v.value = "";
1035
1503
  try {
1036
1504
  let e = b ?? y, t = [
1037
- ...T(),
1505
+ ...ee(),
1038
1506
  W("createdAt", "desc"),
1039
1507
  W(R(), "desc"),
1040
1508
  ...e ? [J(e)] : [],
@@ -1051,14 +1519,14 @@ function tn(e, t = {}) {
1051
1519
  }
1052
1520
  }
1053
1521
  }
1054
- function k() {
1055
- x?.(), f.value = [], p.value = [], y = null, b = null, g.value = !0, v.value = "", h.value = !0, E(), D();
1522
+ function O() {
1523
+ x?.(), f.value = [], p.value = [], y = null, b = null, g.value = !0, v.value = "", h.value = !0, T(), E();
1056
1524
  }
1057
1525
  return L([
1058
1526
  l,
1059
1527
  d,
1060
1528
  c
1061
- ], k, { immediate: !0 }), P(() => {
1529
+ ], O, { immediate: !0 }), P(() => {
1062
1530
  x?.(), S?.();
1063
1531
  }), {
1064
1532
  posts: w,
@@ -1066,25 +1534,33 @@ function tn(e, t = {}) {
1066
1534
  hasMore: g,
1067
1535
  loadingMore: _,
1068
1536
  loadError: v,
1069
- loadMore: O
1537
+ loadMore: D
1070
1538
  };
1071
1539
  }
1072
- function nn(e, t) {
1073
- let n = Z(), r = () => j(), i = N(() => Qt(e)), a = N(() => typeof t == "string" ? t : t.value), o = N(() => l(n, r(), a.value)), s = N(() => c(n, r(), a.value)), { data: u, pending: d } = Se(o), { data: f, pending: p } = Se(s);
1540
+ function zn(e, t) {
1541
+ let n = Z(), r = () => j(), i = N(() => Fn(e)), a = N(() => typeof t == "string" ? t : t.value), o = N(() => l(n, r(), a.value)), s = N(() => c(n, r(), a.value)), { data: u, pending: d } = Se(o), { data: f, pending: p } = Se(s);
1074
1542
  return {
1075
- post: N(() => !u.value || u.value.boardId !== i.value ? null : {
1076
- ...u.value,
1077
- id: a.value,
1078
- content: f.value?.content ?? ""
1543
+ post: N(() => {
1544
+ if (!u.value || u.value.boardId !== i.value) return null;
1545
+ let e = f.value;
1546
+ return {
1547
+ ...u.value,
1548
+ id: a.value,
1549
+ i18n: Q(u.value.i18n),
1550
+ i18nSource: Qe(u.value.i18nSource),
1551
+ content: f.value?.content ?? "",
1552
+ contentI18n: Q(e?.i18n),
1553
+ contentI18nSource: $e(e?.i18nSource)
1554
+ };
1079
1555
  }),
1080
1556
  pending: N(() => d.value || p.value)
1081
1557
  };
1082
1558
  }
1083
1559
  //#endregion
1084
1560
  //#region src/composables/useMemiBoardUserPosts.ts
1085
- var rn = 10;
1086
- function an(e, t = {}) {
1087
- let n = Z(), r = () => j(), i = () => M(n, r()), a = t.pageSize ?? rn, o = N(() => I(e)?.trim() || ""), s = F([]), c = F([]), l = F(!0), u = F(!0), d = F(!1), f = F(""), p = null, m = null, h;
1561
+ var Bn = 10;
1562
+ function Vn(e, t = {}) {
1563
+ let n = Z(), r = () => j(), i = () => M(n, r()), a = t.pageSize ?? Bn, o = N(() => I(e)?.trim() || ""), s = F([]), c = F([]), l = F(!0), u = F(!0), d = F(!1), f = F(""), p = null, m = null, h;
1088
1564
  function g(e) {
1089
1565
  return {
1090
1566
  id: e.id,
@@ -1150,9 +1626,9 @@ function an(e, t = {}) {
1150
1626
  }
1151
1627
  //#endregion
1152
1628
  //#region src/composables/useMemiBoardUserComments.ts
1153
- var on = 10;
1154
- function sn(e, t = {}) {
1155
- let n = Z(), r = () => j(), i = () => u(n, r()), a = t.pageSize ?? on, o = N(() => I(e)?.trim() || ""), s = F([]), c = F([]), l = F(!0), d = F(!0), f = F(!1), p = F(""), m = null, h = null, g;
1629
+ var Hn = 10;
1630
+ function Un(e, t = {}) {
1631
+ let n = Z(), r = () => j(), i = () => u(n, r()), a = t.pageSize ?? Hn, o = N(() => I(e)?.trim() || ""), s = F([]), c = F([]), l = F(!0), d = F(!0), f = F(!1), p = F(""), m = null, h = null, g;
1156
1632
  function _(e) {
1157
1633
  return {
1158
1634
  id: e.id,
@@ -1214,11 +1690,11 @@ function sn(e, t = {}) {
1214
1690
  }
1215
1691
  //#endregion
1216
1692
  //#region src/composables/useMemiBoardCommentFeed.ts
1217
- var cn = 10;
1218
- function ln(e = "latest", t = {}) {
1693
+ var Wn = 10;
1694
+ function Gn(e = "latest", t = {}) {
1219
1695
  let n = Z(), r = () => j(), i = () => u(n, r()), a = N(() => {
1220
1696
  let e = Math.floor(Number(I(t.pageSize)));
1221
- return Number.isFinite(e) && e > 0 ? e : cn;
1697
+ return Number.isFinite(e) && e > 0 ? e : Wn;
1222
1698
  }), o = N(() => I(e) === "likes" ? "likes" : "latest"), s = F([]), c = F([]), l = F(!0), d = F(!0), f = F(!1), p = F(""), m = null, h = null, g;
1223
1699
  function _(e) {
1224
1700
  return {
@@ -1288,9 +1764,9 @@ function ln(e = "latest", t = {}) {
1288
1764
  }
1289
1765
  //#endregion
1290
1766
  //#region src/composables/useMemiBoardAuthorMemo.ts
1291
- var un = 100, dn = /* @__PURE__ */ new Map();
1767
+ var Kn = 100, qn = /* @__PURE__ */ new Map();
1292
1768
  function $(e) {
1293
- let t = dn.get(e);
1769
+ let t = qn.get(e);
1294
1770
  return t || (t = de({
1295
1771
  text: "",
1296
1772
  sentiment: null,
@@ -1298,10 +1774,10 @@ function $(e) {
1298
1774
  pending: !1,
1299
1775
  saving: !1,
1300
1776
  error: ""
1301
- }), dn.set(e, t)), t;
1777
+ }), qn.set(e, t)), t;
1302
1778
  }
1303
- function fn(e) {
1304
- let t = Z(), n = () => j(), { user: r } = ht(), { t: a, formatNumber: o } = O(), s = N(() => I(e)), c = N(() => `${r.value?.uid ?? ""}:${s.value}`), l = N(() => {
1779
+ function Jn(e) {
1780
+ let t = Z(), n = () => j(), { user: r } = kt(), { t: a, formatNumber: o } = D(), s = N(() => I(e)), c = N(() => `${r.value?.uid ?? ""}:${s.value}`), l = N(() => {
1305
1781
  let e = r.value?.uid, a = s.value;
1306
1782
  return !e || !a ? null : i(t, n(), e, a);
1307
1783
  }), u = N({
@@ -1370,7 +1846,7 @@ function fn(e) {
1370
1846
  }
1371
1847
  //#endregion
1372
1848
  //#region src/utils/subscribeCommentRange.ts
1373
- function pn(e, t, n, r, i, a) {
1849
+ function Yn(e, t, n, r, i, a) {
1374
1850
  return U(G(u(e, t), X("postId", "==", n), ...r.rootId ? [X("rootId", "==", r.rootId), X("isReply", "==", !0)] : [X("parentId", "==", null)], W("createdAt", "asc"), W(R(), "asc"), ...r.hasMore && r.cursor ? [he(r.cursor)] : []), (e) => i(e.docs.map((e) => ({
1375
1851
  ...e.data(),
1376
1852
  id: e.id
@@ -1378,19 +1854,19 @@ function pn(e, t, n, r, i, a) {
1378
1854
  }
1379
1855
  //#endregion
1380
1856
  //#region src/composables/useMemiBoardComments.ts
1381
- var mn = 10, hn = 5, gn = 1e3;
1382
- function _n(e, { t, formatNumber: n }) {
1857
+ var Xn = 10, Zn = 5, Qn = 1e3;
1858
+ function $n(e, { t, formatNumber: n }) {
1383
1859
  let r = e.trim();
1384
1860
  if (!r) throw Error(t("comments.bodyRequired"));
1385
- if (r.length > 1e3) throw Error(t("comments.bodyTooLong", { max: n(gn) }));
1861
+ if (r.length > 1e3) throw Error(t("comments.bodyTooLong", { max: n(Qn) }));
1386
1862
  return r;
1387
1863
  }
1388
- function vn(e, t, n = {}) {
1389
- let r = Z(), i = () => j(), { isBoardHidden: a } = Yt(), o = O(), { t: s } = o, c = () => u(r, i()), d = N(() => I(e)), f = N(() => I(t)), p = () => !a(d.value), m = F([]), h = F(n.subscribe !== !1), g = F([]), _ = F(!1), v = F(n.subscribe !== !1), y = F(n.subscribe !== !1), b = null, x = 0, S = 0, C;
1864
+ function er(e, t, n = {}) {
1865
+ let r = Z(), i = () => j(), { isBoardHidden: a } = Mn(), o = D(), { t: s } = o, c = () => u(r, i()), d = N(() => I(e)), f = N(() => I(t)), p = () => !a(d.value), m = F([]), h = F(n.subscribe !== !1), g = F([]), _ = F(!1), v = F(n.subscribe !== !1), y = F(n.subscribe !== !1), b = null, x = 0, S = 0, C;
1390
1866
  function w(e) {
1391
1867
  return e.createdAt?.toMillis?.() ?? 0;
1392
1868
  }
1393
- let T = N(() => {
1869
+ let ee = N(() => {
1394
1870
  let e = /* @__PURE__ */ new Map();
1395
1871
  for (let t of [...m.value, ...g.value]) t.id && e.set(t.id, t);
1396
1872
  return [...e.values()].sort((e, t) => w(e) - w(t) || String(e.id).localeCompare(String(t.id)));
@@ -1408,16 +1884,16 @@ function vn(e, t, n = {}) {
1408
1884
  ...e.data()
1409
1885
  }));
1410
1886
  m.value = n.filter((e) => e.parentId === void 0);
1411
- }), D()]), t !== x) return;
1887
+ }), E()]), t !== x) return;
1412
1888
  } finally {
1413
1889
  t === x && (h.value = !1, y.value = !1);
1414
1890
  }
1415
1891
  }
1416
1892
  }, { immediate: !0 });
1417
- function E() {
1893
+ function T() {
1418
1894
  C?.();
1419
1895
  let e = ++S;
1420
- C = pn(r, i(), f.value, {
1896
+ C = Yn(r, i(), f.value, {
1421
1897
  cursor: b,
1422
1898
  hasMore: v.value
1423
1899
  }, (t) => {
@@ -1426,7 +1902,7 @@ function vn(e, t, n = {}) {
1426
1902
  console.error("[memi-board:comments] onSnapshot failed", e);
1427
1903
  });
1428
1904
  }
1429
- async function D() {
1905
+ async function E() {
1430
1906
  if (_.value || !v.value) return;
1431
1907
  let e = x;
1432
1908
  _.value = !0;
@@ -1440,11 +1916,11 @@ function vn(e, t, n = {}) {
1440
1916
  H(11)
1441
1917
  ], n = await B(G(c(), ...t));
1442
1918
  if (e !== x) return;
1443
- let r = n.docs.slice(0, mn), i = r.map((e) => ({
1919
+ let r = n.docs.slice(0, Xn), i = r.map((e) => ({
1444
1920
  id: e.id,
1445
1921
  ...e.data()
1446
1922
  })), a = new Set(g.value.map((e) => e.id));
1447
- g.value.push(...i.filter((e) => !a.has(e.id))), b = r.at(-1) ?? b, v.value = n.size > mn, E();
1923
+ g.value.push(...i.filter((e) => !a.has(e.id))), b = r.at(-1) ?? b, v.value = n.size > Xn, T();
1448
1924
  } finally {
1449
1925
  e === x && (_.value = !1);
1450
1926
  }
@@ -1452,8 +1928,8 @@ function vn(e, t, n = {}) {
1452
1928
  P(() => {
1453
1929
  x++, S++, C?.();
1454
1930
  });
1455
- async function ee(e) {
1456
- let t = _n(e.body, o), n = ye(r), a = me(c());
1931
+ async function te(e) {
1932
+ let t = $n(e.body, o), n = ye(r), a = me(c());
1457
1933
  n.set(a, {
1458
1934
  postId: f.value,
1459
1935
  boardId: d.value,
@@ -1475,8 +1951,8 @@ function vn(e, t, n = {}) {
1475
1951
  updatedAt: K()
1476
1952
  }), n.update(l(r, i(), f.value), { commentCount: V(1) }), await n.commit();
1477
1953
  }
1478
- async function te(e) {
1479
- let t = _n(e.body, o);
1954
+ async function k(e) {
1955
+ let t = $n(e.body, o);
1480
1956
  if (!e.parent.id) throw Error(s("comments.replyTargetNotFound"));
1481
1957
  let n = ye(r), a = me(c()), u = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
1482
1958
  if (!u) throw Error(s("comments.threadNotFound"));
@@ -1500,43 +1976,43 @@ function vn(e, t, n = {}) {
1500
1976
  isReply: !0,
1501
1977
  createdAt: K(),
1502
1978
  updatedAt: K()
1503
- }), n.update(k(r, i(), u), { replyCount: V(1) }), n.update(l(r, i(), f.value), { commentCount: V(1) }), await n.commit();
1979
+ }), n.update(O(r, i(), u), { replyCount: V(1) }), n.update(l(r, i(), f.value), { commentCount: V(1) }), await n.commit();
1504
1980
  }
1505
1981
  async function A(e) {
1506
1982
  if (!e.id) return;
1507
1983
  if (e.parentId == null && (e.replyCount ?? 0) > 0) throw Error(s("comments.hasRepliesCannotDelete"));
1508
- await At(r, i(), e.id);
1984
+ await Wt(r, i(), e.id);
1509
1985
  let t = ye(r);
1510
- t.delete(k(r, i(), e.id)), e.parentId && e.rootId && t.update(k(r, i(), e.rootId), { replyCount: V(-1) }), t.update(l(r, i(), f.value), { commentCount: V(-1) }), await t.commit(), g.value = g.value.filter((t) => t.id !== e.id), m.value = m.value.filter((t) => t.id !== e.id);
1986
+ t.delete(O(r, i(), e.id)), e.parentId && e.rootId && t.update(O(r, i(), e.rootId), { replyCount: V(-1) }), t.update(l(r, i(), f.value), { commentCount: V(-1) }), await t.commit(), g.value = g.value.filter((t) => t.id !== e.id), m.value = m.value.filter((t) => t.id !== e.id);
1511
1987
  }
1512
1988
  async function ne(e, t) {
1513
- let n = _n(t, o);
1514
- await Y(k(r, i(), e), {
1989
+ let n = $n(t, o);
1990
+ await Y(O(r, i(), e), {
1515
1991
  body: n,
1516
1992
  updatedAt: K()
1517
1993
  });
1518
1994
  }
1519
1995
  async function re(e, t, n) {
1520
- await Y(k(r, i(), e), {
1996
+ await Y(O(r, i(), e), {
1521
1997
  isBlinded: t,
1522
1998
  moderatedAt: K(),
1523
1999
  moderatedBy: n
1524
2000
  });
1525
2001
  }
1526
2002
  return {
1527
- comments: T,
2003
+ comments: ee,
1528
2004
  commentsPending: N(() => y.value || h.value),
1529
2005
  hasMore: v,
1530
2006
  loadingMore: _,
1531
- loadMore: D,
1532
- addComment: ee,
1533
- addReply: te,
2007
+ loadMore: E,
2008
+ addComment: te,
2009
+ addReply: k,
1534
2010
  updateComment: ne,
1535
2011
  setCommentBlinded: re,
1536
2012
  deleteComment: A
1537
2013
  };
1538
2014
  }
1539
- function yn(e, t, n) {
2015
+ function tr(e, t, n) {
1540
2016
  let r = Z(), i = () => j(), a = () => u(r, i()), o = t.trim(), s = F([]), c = F(!1), l = F(!1), d = F(!0), f = null, p, m = 0, h = 0;
1541
2017
  function g(e) {
1542
2018
  return e.sort((e, t) => (e.createdAt?.toMillis?.() ?? 0) - (t.createdAt?.toMillis?.() ?? 0) || String(e.id).localeCompare(String(t.id)));
@@ -1544,7 +2020,7 @@ function yn(e, t, n) {
1544
2020
  function _() {
1545
2021
  p?.();
1546
2022
  let e = ++h;
1547
- p = pn(r, i(), o, {
2023
+ p = Yn(r, i(), o, {
1548
2024
  rootId: n,
1549
2025
  cursor: f,
1550
2026
  hasMore: d.value
@@ -1569,11 +2045,11 @@ function yn(e, t, n) {
1569
2045
  H(6)
1570
2046
  ], r = await B(G(a(), ...t));
1571
2047
  if (e !== m) return;
1572
- let i = new Set(s.value.map((e) => e.id)), c = r.docs.slice(0, hn), u = c.map((e) => ({
2048
+ let i = new Set(s.value.map((e) => e.id)), c = r.docs.slice(0, Zn), u = c.map((e) => ({
1573
2049
  id: e.id,
1574
2050
  ...e.data()
1575
2051
  })).filter((e) => !i.has(e.id));
1576
- s.value = g([...s.value, ...u]), f = c.at(-1) ?? f, l.value = !0, d.value = r.size > hn, _();
2052
+ s.value = g([...s.value, ...u]), f = c.at(-1) ?? f, l.value = !0, d.value = r.size > Zn, _();
1577
2053
  } finally {
1578
2054
  e === m && (c.value = !1);
1579
2055
  }
@@ -1597,8 +2073,8 @@ function yn(e, t, n) {
1597
2073
  }
1598
2074
  //#endregion
1599
2075
  //#region src/composables/useMemiBoardLikes.ts
1600
- function bn(e, t) {
1601
- let n = () => j(), r = Z(), i = xe(), { t: a } = O(), o = F(!1), s = F(!1), c;
2076
+ function nr(e, t) {
2077
+ let n = () => j(), r = Z(), i = xe(), { t: a } = D(), o = F(!1), s = F(!1), c;
1602
2078
  function u(e) {
1603
2079
  return d(r, n(), t, e);
1604
2080
  }
@@ -1636,9 +2112,9 @@ function bn(e, t) {
1636
2112
  toggleLike: f
1637
2113
  };
1638
2114
  }
1639
- var xn = Symbol.for("memi-board:comment-likes");
1640
- function Sn(e, t) {
1641
- let n = () => j(), r = Z(), i = xe(), { t: o } = O(), s = N(() => I(e)), c = N(() => I(t)), l = F(/* @__PURE__ */ new Set()), u = F(null), f;
2115
+ var rr = Symbol.for("memi-board:comment-likes");
2116
+ function ir(e, t) {
2117
+ let n = () => j(), r = Z(), i = xe(), { t: o } = D(), s = N(() => I(e)), c = N(() => I(t)), l = F(/* @__PURE__ */ new Set()), u = F(null), f;
1642
2118
  L([i, c], ([e, t]) => {
1643
2119
  f?.(), f = void 0, l.value = /* @__PURE__ */ new Set(), e && t && (f = U(G(a(r, n()), X("uid", "==", e.uid), X("postId", "==", t), X("target", "==", "comment")), (e) => {
1644
2120
  let t = /* @__PURE__ */ new Set();
@@ -1664,7 +2140,7 @@ function Sn(e, t) {
1664
2140
  if (u.value) return p(e);
1665
2141
  u.value = e;
1666
2142
  try {
1667
- let i = d(r, n(), e, t), a = k(r, n(), e), u = await ve(r, async (n) => {
2143
+ let i = d(r, n(), e, t), a = O(r, n(), e), u = await ve(r, async (n) => {
1668
2144
  let r = await n.get(i), l = await n.get(a);
1669
2145
  if (!l.exists()) throw Error(o("likes.commentNotFound"));
1670
2146
  let u = Number(l.data()?.likeCount ?? 0);
@@ -1690,7 +2166,7 @@ function Sn(e, t) {
1690
2166
  }
1691
2167
  //#endregion
1692
2168
  //#region src/composables/useMemiBoardReports.ts
1693
- var Cn = 200, wn = [
2169
+ var ar = 200, or = [
1694
2170
  {
1695
2171
  value: "spam",
1696
2172
  label: "스팸·광고"
@@ -1712,11 +2188,11 @@ var Cn = 200, wn = [
1712
2188
  label: "기타"
1713
2189
  }
1714
2190
  ];
1715
- function Tn(e, t) {
1716
- return wn.some((t) => t.value === e) ? w(t, `reports.reason.${e}`) : e || w(t, "reports.fallbackLabel");
2191
+ function sr(e, t) {
2192
+ return or.some((t) => t.value === e) ? w(t, `reports.reason.${e}`) : e || w(t, "reports.fallbackLabel");
1717
2193
  }
1718
- function En(e, n) {
1719
- let r = () => j(), i = Z(), a = xe(), { t: o } = O(), s = F(!1), c = F(!1), l;
2194
+ function cr(e, n) {
2195
+ let r = () => j(), i = Z(), a = xe(), { t: o } = D(), s = F(!1), c = F(!1), l;
1720
2196
  L(a, (e) => {
1721
2197
  if (l?.(), l = void 0, !e) {
1722
2198
  s.value = !1;
@@ -1733,7 +2209,7 @@ function En(e, n) {
1733
2209
  if (!u) throw Error(o("reports.signInRequired"));
1734
2210
  if (c.value) return;
1735
2211
  if (l.authorUid && l.authorUid === u) throw Error(o("reports.cannotReportOwn"));
1736
- let d = wn.some((e) => e.value === l.reason) ? l.reason : null;
2212
+ let d = or.some((e) => e.value === l.reason) ? l.reason : null;
1737
2213
  if (!d) throw Error(o("reports.reasonRequired"));
1738
2214
  let f = (l.detail || "").trim();
1739
2215
  if (f.length > 200) throw Error(o("reports.detailTooLong", { max: 200 }));
@@ -1762,63 +2238,63 @@ function En(e, n) {
1762
2238
  submitReport: u
1763
2239
  };
1764
2240
  }
1765
- var Dn = 20;
1766
- function On(e = {}) {
1767
- let n = () => j(), r = Z(), { canManageContent: i, canManageBoard: a } = ht(), o = e.pageSize ?? Dn, c = N(() => I(e.boardId)?.trim() || ""), l = F([]), u = F(!0), d = F(!1), f = F(!1), p = F(""), m = null, h;
1768
- function g(e) {
2241
+ var lr = 20;
2242
+ function ur(e = {}) {
2243
+ let n = () => j(), r = Z(), { isAdmin: i, canManageContent: a, canManageBoard: o } = kt(), c = e.pageSize ?? lr, l = N(() => I(e.boardId)?.trim() || ""), u = F([]), d = F(!0), f = F(!1), p = F(!1), m = F(""), h = null, g;
2244
+ function _(e) {
1769
2245
  return {
1770
2246
  id: e.id,
1771
2247
  ...e.data()
1772
2248
  };
1773
2249
  }
1774
- function _() {
1775
- if (h?.(), !i.value) {
1776
- l.value = [], u.value = !1;
2250
+ function v() {
2251
+ if (g?.(), !a.value || !i.value && !l.value) {
2252
+ u.value = [], d.value = !1;
1777
2253
  return;
1778
2254
  }
1779
- u.value = !0;
1780
- let e = c.value ? [X("status", "==", "open"), X("boardId", "==", c.value)] : [X("status", "==", "open"), X("target", "==", "post")];
1781
- h = U(G(s(r, n()), ...e, W("createdAt", "desc"), W(R(), "desc"), H(o)), (e) => {
1782
- l.value = e.docs.map(g).filter((e) => a(e.boardId)), m = e.docs.at(-1) ?? null, f.value = e.docs.length >= o, u.value = !1;
2255
+ d.value = !0;
2256
+ let e = l.value ? [X("status", "==", "open"), X("boardId", "==", l.value)] : [X("status", "==", "open"), X("target", "==", "post")];
2257
+ g = U(G(s(r, n()), ...e, W("createdAt", "desc"), W(R(), "desc"), H(c)), (e) => {
2258
+ u.value = e.docs.map(_).filter((e) => o(e.boardId)), h = e.docs.at(-1) ?? null, p.value = e.docs.length >= c, d.value = !1;
1783
2259
  }, (e) => {
1784
- console.error("[memi-board:report-queue] onSnapshot failed", e), p.value = e.message || String(e), u.value = !1;
2260
+ console.error("[memi-board:report-queue] onSnapshot failed", e), m.value = e.message || String(e), d.value = !1;
1785
2261
  });
1786
2262
  }
1787
- L([i, c], _, { immediate: !0 }), P(() => h?.());
1788
- async function v() {
1789
- if (!d.value && f.value && m && i.value) {
1790
- d.value = !0, p.value = "";
2263
+ L([a, l], v, { immediate: !0 }), P(() => g?.());
2264
+ async function y() {
2265
+ if (!f.value && p.value && h && a.value) {
2266
+ f.value = !0, m.value = "";
1791
2267
  try {
1792
- let e = c.value ? [X("status", "==", "open"), X("boardId", "==", c.value)] : [X("status", "==", "open"), X("target", "==", "post")], t = await B(G(s(r, n()), ...e, W("createdAt", "desc"), W(R(), "desc"), J(m), H(o + 1))), i = t.docs.slice(0, o), u = new Set(l.value.map((e) => e.id));
1793
- l.value.push(...i.map(g).filter((e) => !u.has(e.id) && a(e.boardId))), m = i.at(-1) ?? m, f.value = t.docs.length > o;
2268
+ let e = l.value ? [X("status", "==", "open"), X("boardId", "==", l.value)] : [X("status", "==", "open"), X("target", "==", "post")], t = await B(G(s(r, n()), ...e, W("createdAt", "desc"), W(R(), "desc"), J(h), H(c + 1))), i = t.docs.slice(0, c), a = new Set(u.value.map((e) => e.id));
2269
+ u.value.push(...i.map(_).filter((e) => !a.has(e.id) && o(e.boardId))), h = i.at(-1) ?? h, p.value = t.docs.length > c;
1794
2270
  } catch (e) {
1795
- p.value = e.message || String(e);
2271
+ m.value = e.message || String(e);
1796
2272
  } finally {
1797
- d.value = !1;
2273
+ f.value = !1;
1798
2274
  }
1799
2275
  }
1800
2276
  }
1801
- async function y(e, i) {
1802
- e.id && a(e.boardId) && await Y(t(r, n(), e.postId, e.uid), {
2277
+ async function b(e, i) {
2278
+ e.id && o(e.boardId) && await Y(t(r, n(), e.postId, e.uid), {
1803
2279
  status: i,
1804
2280
  reviewedAt: K(),
1805
2281
  reviewedBy: xe().value?.uid || ""
1806
2282
  });
1807
2283
  }
1808
2284
  return {
1809
- reports: l,
1810
- pending: u,
1811
- loadingMore: d,
1812
- hasMore: f,
1813
- loadError: p,
1814
- loadMore: v,
1815
- dismissReport: (e) => y(e, "dismissed"),
1816
- actionReport: (e) => y(e, "actioned")
2285
+ reports: u,
2286
+ pending: d,
2287
+ loadingMore: f,
2288
+ hasMore: p,
2289
+ loadError: m,
2290
+ loadMore: y,
2291
+ dismissReport: (e) => b(e, "dismissed"),
2292
+ actionReport: (e) => b(e, "actioned")
1817
2293
  };
1818
2294
  }
1819
2295
  //#endregion
1820
2296
  //#region src/composables/useMemiBoardViews.ts
1821
- function kn() {
2297
+ function dr() {
1822
2298
  let e = () => j(), t = Z();
1823
2299
  function n(t, n) {
1824
2300
  let { postsCollection: r } = e();
@@ -1857,161 +2333,21 @@ function kn() {
1857
2333
  };
1858
2334
  }
1859
2335
  //#endregion
1860
- //#region src/utils/moderation-prompt.ts
1861
- var An = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\n아래 텍스트에 욕설·비속어·성희롱·혐오·위협·스팸·도배성 광고가 있으면 반드시 거부(flagged=true)합니다.\n\nflagged=true 예:\n- 욕설/비속어: 씨발, 시발, 병신, 지랄, 좆, 개새끼, 존나, fuck, shit, bitch 등\n- 성적 모욕, 혐오 발언, 협박\n- 게시판 주제와 무관한 도배성 광고/스팸\n\nflagged=false:\n- 게시판 주제에 맞는 정상적인 글/댓글 (강한 의견이나 정당한 비판은 허용)\n\nJSON만 출력:\n{\"flagged\":boolean,\"category\":\"none\"|\"abuse\"|\"spam\"|\"adult\"|\"violence\"|\"other\",\"reason\":string}\n- flagged=true면 category를 고르고, reason은 한국어로 **무엇이 문제인지** 구체적으로 1문장\n (예: \"욕설(비속어)이 포함되어 게시할 수 없습니다.\", \"광고·스팸성 문구가 있어 게시할 수 없습니다.\")\n 욕설 단어 전체를 그대로 길게 인용하지 말고, 유형(욕설/혐오/스팸 등)을 분명히 밝힌다.\n- flagged=false면 category는 \"none\", reason은 \"\"", jn = {
1862
- ko: "Korean",
1863
- en: "English",
1864
- ja: "Japanese",
1865
- de: "German",
1866
- fr: "French",
1867
- es: "Spanish",
1868
- pt: "Portuguese",
1869
- zh: "Simplified Chinese",
1870
- ar: "Arabic",
1871
- id: "Indonesian"
1872
- };
1873
- function Mn(e) {
1874
- let t = jn[e] ?? "English", n = D(e).name;
1875
- return t === n ? t : `${t} (${n})`;
1876
- }
1877
- function Nn(e) {
1878
- let t = Mn(e);
1879
- return `You are a strict moderator reviewing content for a message board.
1880
- If the text below contains profanity, vulgar slang, sexual harassment, hate speech, threats, spam, or repetitive/flooding advertisements, you MUST reject it (flagged=true).
1881
-
1882
- The content may be written in ANY language, or a mix of languages — not only the board's language.
1883
- Judge every language equally strictly, including slang, abbreviations, romanizations, deliberate misspellings,
1884
- inserted spaces/symbols, and other obfuscation (e.g. in Korean, Japanese, Chinese, Arabic, Spanish, etc.).
1885
-
1886
- flagged=true examples:
1887
- - Profanity/vulgar slang: fuck, shit, bitch, asshole, f*ck, sh1t /
1888
- Korean: 씨발, 시발, ㅅㅂ, 병신, ㅂㅅ, 지랄, 좆, 개새끼, 존나 /
1889
- Japanese: クソ, 死ね, ちんこ / Chinese: 操你妈, 傻逼, 他妈的 /
1890
- Spanish: puta, mierda, pendejo / German: Scheiße, Hurensohn / French: putain, connard /
1891
- Portuguese: porra, caralho / Indonesian: anjing, bangsat, kontol / Arabic: كس, شرموطة, etc.
1892
- - Sexual insults, hate speech, threats
1893
- - Flooding advertisements/spam unrelated to the board's topic
1894
-
1895
- flagged=false:
1896
- - Normal posts/comments that fit the board's topic (strong opinions and legitimate criticism are allowed)
1897
-
1898
- Output JSON only:
1899
- {"flagged":boolean,"category":"none"|"abuse"|"spam"|"adult"|"violence"|"other","reason":string}
1900
- - If flagged=true, choose a category, and write reason as ONE concrete sentence in ${t} explaining **what the problem is**
1901
- (e.g. "It contains profanity, so it can't be posted.", "It contains advertising/spam, so it can't be posted." — but written in ${t}).
1902
- Do not quote the offensive words at length; clearly state the type (profanity/hate/spam, etc.).
1903
- - If flagged=false, category is "none" and reason is ""`;
1904
- }
1905
- function Pn(e = "ko") {
1906
- return e === "ko" ? An : Nn(e);
1907
- }
1908
- function Fn(e, t = "ko") {
1909
- let n = `"""${e.slice(0, 4e3)}"""`;
1910
- return t === "ko" ? `심사할 텍스트:\n${n}` : `Text to review:\n${n}`;
1911
- }
1912
- function In(e = "ko") {
1913
- return e === "ko" ? "이 이미지를 심사해 주세요." : "Please review this image.";
1914
- }
1915
- var Ln = {
1916
- abuse: "욕설·비속어·모욕",
1917
- spam: "스팸·광고",
1918
- adult: "선정적·성인 내용",
1919
- violence: "폭력·위협",
1920
- other: "부적절한 내용",
1921
- none: ""
1922
- };
1923
- function Rn(e, t = "ko") {
1924
- return !e || e === "none" ? "" : e in Ln ? w(t, `moderationPrompt.categories.${e}`) : e;
1925
- }
1926
- function zn(e) {
1927
- let t = e.locale ?? "ko";
1928
- if (e.via === "local" && e.localHit) {
1929
- let n = e.localHit.trim();
1930
- return n.length > 0 && n.length <= 12 ? w(t, "moderationPrompt.localHit", { hit: n }) : w(t, "moderationPrompt.localGeneric");
1931
- }
1932
- let n = (e.reason || "").trim();
1933
- if (n) return n;
1934
- let r = e.category ? Rn(e.category, t) : "";
1935
- return r ? w(t, "moderationPrompt.categoryBlocked", { category: r }) : w(t, "moderationPrompt.genericBlocked");
1936
- }
1937
- var Bn = [
1938
- "씨발",
1939
- "시발",
1940
- "시이발",
1941
- "씨팔",
1942
- "시팔",
1943
- "병신",
1944
- "븅신",
1945
- "지랄",
1946
- "좆",
1947
- "존나",
1948
- "개새끼",
1949
- "개새",
1950
- "새끼",
1951
- "꺼져",
1952
- "닥쳐",
1953
- "씹",
1954
- "니미",
1955
- "느금",
1956
- "애미",
1957
- "애비",
1958
- "fuck",
1959
- "shit",
1960
- "bitch",
1961
- "asshole"
1962
- ];
1963
- function Vn(e) {
1964
- return new RegExp(e.join("|"), "i");
1965
- }
1966
- var Hn = /* @__PURE__ */ new Set([
1967
- "none",
1968
- "abuse",
1969
- "spam",
1970
- "adult",
1971
- "violence",
1972
- "other"
1973
- ]);
1974
- function Un(e) {
1975
- let t = e.trim();
1976
- if (!t) return null;
1977
- let n;
1978
- try {
1979
- n = JSON.parse(t);
1980
- } catch {
1981
- let e = t.match(/\{[\s\S]*\}/);
1982
- if (!e) return null;
1983
- try {
1984
- n = JSON.parse(e[0]);
1985
- } catch {
1986
- return null;
1987
- }
1988
- }
1989
- if (typeof n != "object" || !n) return null;
1990
- let r = n;
1991
- if (typeof r.flagged != "boolean") return null;
1992
- let i = typeof r.category == "string" && Hn.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
1993
- return {
1994
- flagged: r.flagged,
1995
- category: i,
1996
- reason: a
1997
- };
1998
- }
1999
- //#endregion
2000
2336
  //#region src/composables/useMemiBoardModeration.ts
2001
- var Wn = {
2337
+ var fr = {
2002
2338
  flagged: !1,
2003
2339
  category: "none",
2004
2340
  reason: "",
2005
2341
  via: "empty"
2006
2342
  };
2007
- function Gn(e = {}) {
2008
- let t = O(), n = N(() => E(I(e.locale)) ?? t.locale.value), r = ue().moderation ?? {}, i = Ce(), a = (e) => w(n.value, e), { isWriteRestricted: o, restrictedMessage: s, recordContentModerationBlock: c } = ht(), l = Vn([...Bn, ...r.localBlocklist ?? []]);
2343
+ function pr(e = {}) {
2344
+ let t = D(), n = N(() => T(I(e.locale)) ?? t.locale.value), r = ue().moderation ?? {}, i = Ce(), a = (e) => w(n.value, e), { isWriteRestricted: o, restrictedMessage: s, recordContentModerationBlock: c } = kt(), l = _n([...gn, ...r.localBlocklist ?? []]);
2009
2345
  function u(e) {
2010
2346
  let t = e.match(l);
2011
2347
  return t ? {
2012
2348
  flagged: !0,
2013
2349
  category: "abuse",
2014
- reason: zn({
2350
+ reason: hn({
2015
2351
  via: "local",
2016
2352
  localHit: t[0] || "",
2017
2353
  category: "abuse"
@@ -2020,21 +2356,21 @@ function Gn(e = {}) {
2020
2356
  } : null;
2021
2357
  }
2022
2358
  function d() {
2023
- let e = r.model ?? "gemini-3.5-flash-lite", t = r.useLimitedUseAppCheckTokens !== !1, a = Le(i, {
2359
+ let e = r.model ?? "gemini-3.5-flash-lite", t = r.useLimitedUseAppCheckTokens !== !1, a = Re(i, {
2024
2360
  backend: new Ie(),
2025
2361
  useLimitedUseAppCheckTokens: t
2026
2362
  });
2027
- return Re(a, {
2363
+ return ze(a, {
2028
2364
  model: e,
2029
- systemInstruction: Pn(n.value),
2365
+ systemInstruction: un(n.value),
2030
2366
  generationConfig: {
2031
2367
  temperature: 0,
2032
2368
  maxOutputTokens: 256,
2033
2369
  responseMimeType: "application/json",
2034
- responseSchema: Q.object({ properties: {
2035
- flagged: Q.boolean(),
2036
- category: Q.string(),
2037
- reason: Q.string()
2370
+ responseSchema: Le.object({ properties: {
2371
+ flagged: Le.boolean(),
2372
+ category: Le.string(),
2373
+ reason: Le.string()
2038
2374
  } })
2039
2375
  }
2040
2376
  });
@@ -2055,7 +2391,7 @@ function Gn(e = {}) {
2055
2391
  };
2056
2392
  }
2057
2393
  function p(e, t = "") {
2058
- let n = zn({
2394
+ let n = hn({
2059
2395
  reason: e.reason,
2060
2396
  category: e.category,
2061
2397
  via: e.via
@@ -2083,26 +2419,26 @@ function Gn(e = {}) {
2083
2419
  };
2084
2420
  let i = e.trim();
2085
2421
  if (!i) return {
2086
- ...Wn,
2422
+ ...fr,
2087
2423
  via: "empty"
2088
2424
  };
2089
2425
  if (t?.skipFilter) return {
2090
- ...Wn,
2426
+ ...fr,
2091
2427
  via: "admin-override"
2092
2428
  };
2093
2429
  let c = u(i);
2094
2430
  if (c) return m(c);
2095
2431
  if (r.enabled === !1) return {
2096
- ...Wn,
2432
+ ...fr,
2097
2433
  via: "disabled"
2098
2434
  };
2099
2435
  try {
2100
- let e = Un((await d().generateContent(Fn(i, n.value))).response.text());
2436
+ let e = yn((await d().generateContent(dn(i, n.value))).response.text());
2101
2437
  if (!e) return f();
2102
2438
  let t = {
2103
2439
  flagged: e.flagged,
2104
2440
  category: e.category,
2105
- reason: zn({
2441
+ reason: hn({
2106
2442
  reason: e.reason,
2107
2443
  category: e.category,
2108
2444
  via: "ai"
@@ -2122,22 +2458,22 @@ function Gn(e = {}) {
2122
2458
  via: "restricted"
2123
2459
  };
2124
2460
  if (r.enabled === !1 || !r.moderateImages) return {
2125
- ...Wn,
2461
+ ...fr,
2126
2462
  via: "disabled"
2127
2463
  };
2128
2464
  try {
2129
2465
  let t = await new Promise((t, n) => {
2130
2466
  let r = new FileReader();
2131
2467
  r.onload = () => t(r.result.split(",")[1] ?? ""), r.onerror = n, r.readAsDataURL(e);
2132
- }), r = Un((await d().generateContent([{ inlineData: {
2468
+ }), r = yn((await d().generateContent([{ inlineData: {
2133
2469
  mimeType: e.type || "image/jpeg",
2134
2470
  data: t
2135
- } }, In(n.value)])).response.text());
2471
+ } }, fn(n.value)])).response.text());
2136
2472
  if (!r) return f();
2137
2473
  let i = {
2138
2474
  flagged: r.flagged,
2139
2475
  category: r.category,
2140
- reason: zn({
2476
+ reason: hn({
2141
2477
  reason: r.reason,
2142
2478
  category: r.category,
2143
2479
  via: "ai"
@@ -2156,8 +2492,10 @@ function Gn(e = {}) {
2156
2492
  }
2157
2493
  //#endregion
2158
2494
  //#region src/composables/useMemiBoardUsers.ts
2159
- var Kn = [
2495
+ var mr = [
2496
+ "owner",
2160
2497
  "admin",
2498
+ "manager",
2161
2499
  "staff",
2162
2500
  "user"
2163
2501
  ].map((e) => ({
@@ -2165,10 +2503,10 @@ var Kn = [
2165
2503
  label: w("ko", `common.role.${e}`),
2166
2504
  description: w("ko", `boardUsers.roleDescription.${e}`)
2167
2505
  }));
2168
- function qn(t = {}) {
2169
- let n = () => j(), i = Z(), { t: a } = O(), o = N(() => I(t.enabled) ?? !0 ? e(i, n()) : null), s = be(o, { ssrKey: r(n(), "users-management") });
2506
+ function hr(t = {}) {
2507
+ let n = () => j(), i = Z(), { t: a } = D(), o = N(() => I(t.enabled) ?? !0 ? e(i, n()) : null), s = be(o, { ssrKey: r(n(), "users-management") });
2170
2508
  async function c(e, t) {
2171
- if (!Kn.some((e) => e.value === t)) throw Error(a("boardUsers.unsupportedRole"));
2509
+ if (!mr.some((e) => e.value === t)) throw Error(a("boardUsers.unsupportedRole"));
2172
2510
  await Y(f(i, n(), e), {
2173
2511
  role: t,
2174
2512
  updatedAt: K()
@@ -2182,7 +2520,7 @@ function qn(t = {}) {
2182
2520
  }
2183
2521
  //#endregion
2184
2522
  //#region src/composables/useMemiBoardUserProfile.ts
2185
- function Jn(e) {
2523
+ function gr(e) {
2186
2524
  let t = Z(), n = () => j(), r = N(() => I(e)), i = N(() => {
2187
2525
  let e = r.value;
2188
2526
  return e ? f(t, n(), e) : null;
@@ -2194,7 +2532,7 @@ function Jn(e) {
2194
2532
  }
2195
2533
  //#endregion
2196
2534
  //#region src/utils/section.ts
2197
- var Yn = [
2535
+ var _r = [
2198
2536
  3,
2199
2537
  4,
2200
2538
  5,
@@ -2204,7 +2542,7 @@ var Yn = [
2204
2542
  9,
2205
2543
  12
2206
2544
  ];
2207
- function Xn(e) {
2545
+ function vr(e) {
2208
2546
  return [
2209
2547
  {
2210
2548
  value: "list",
@@ -2220,7 +2558,7 @@ function Xn(e) {
2220
2558
  }
2221
2559
  ];
2222
2560
  }
2223
- function Zn(e) {
2561
+ function yr(e) {
2224
2562
  return [{
2225
2563
  value: "latest",
2226
2564
  label: w(e, "sectionUtil.sort.latest")
@@ -2229,18 +2567,18 @@ function Zn(e) {
2229
2567
  label: w(e, "sectionUtil.sort.likes")
2230
2568
  }];
2231
2569
  }
2232
- var Qn = Xn("ko"), $n = Zn("ko");
2233
- function er(e) {
2570
+ var br = vr("ko"), xr = yr("ko");
2571
+ function Sr(e) {
2234
2572
  return e === "list" ? 5 : 4;
2235
2573
  }
2236
- function tr(e, t) {
2574
+ function Cr(e, t) {
2237
2575
  let n = Math.floor(Number(t));
2238
- return Number.isFinite(n) ? Math.min(e === "list" ? 20 : 10, Math.max(1, n)) : er(e);
2576
+ return Number.isFinite(n) ? Math.min(e === "list" ? 20 : 10, Math.max(1, n)) : Sr(e);
2239
2577
  }
2240
- function nr(e) {
2578
+ function wr(e) {
2241
2579
  return e === 12 ? null : 12 - e;
2242
2580
  }
2243
- function rr(e) {
2581
+ function Tr(e) {
2244
2582
  return {
2245
2583
  3: "col-span-12 md:col-span-3",
2246
2584
  4: "col-span-12 md:col-span-4",
@@ -2252,36 +2590,36 @@ function rr(e) {
2252
2590
  12: "col-span-12"
2253
2591
  }[e];
2254
2592
  }
2255
- function ir(e) {
2593
+ function Er(e) {
2256
2594
  return e === "sm" ? "min-h-40" : e === "lg" ? "min-h-80" : "min-h-56";
2257
2595
  }
2258
- function ar(e) {
2596
+ function Dr(e) {
2259
2597
  let t = e.boardId || e.category || "free";
2260
2598
  return e.slug ? `/board/${t}/${e.slug}` : `/board/${t}`;
2261
2599
  }
2262
- function or(e) {
2600
+ function Or(e) {
2263
2601
  return e.kind === "comments" ? e.sort === "likes" ? "/boardComments?sort=likes" : "/boardComments" : e.kind === "list" && e.boardId ? `/board/${e.boardId}` : "/board";
2264
2602
  }
2265
- function sr(e) {
2603
+ function kr(e) {
2266
2604
  if (e.boardId && e.postId) return `/board/${e.boardId}/post/${e.postId}`;
2267
2605
  }
2268
- function cr(e) {
2606
+ function Ar(e) {
2269
2607
  return e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
2270
2608
  }
2271
- function lr(e, t) {
2272
- return e.title?.trim() || e.summary || w(t, "sectionUtil.untitledPost");
2609
+ function jr(e, t) {
2610
+ return it(e, t).trim() || at(e, t).trim() || w(t, "sectionUtil.untitledPost");
2273
2611
  }
2274
- function ur(e) {
2612
+ function Mr(e) {
2275
2613
  return e === "image" || e === "video" ? "media" : "list";
2276
2614
  }
2277
- function dr(e, t) {
2278
- return w(t, ur(e) === "media" ? "sectionUtil.view.media" : "sectionUtil.view.list");
2615
+ function Nr(e, t) {
2616
+ return w(t, Mr(e) === "media" ? "sectionUtil.view.media" : "sectionUtil.view.list");
2279
2617
  }
2280
- function fr(e, t) {
2618
+ function Pr(e, t) {
2281
2619
  return e ? t(e)?.listView ?? "default" : "default";
2282
2620
  }
2283
- function pr(e, t) {
2284
- let n = e.map((e) => fr(e.boardId, t));
2621
+ function Fr(e, t) {
2622
+ let n = e.map((e) => Pr(e.boardId, t));
2285
2623
  if (!n.length) return "default";
2286
2624
  if (n.every((e) => e === n[0])) return n[0];
2287
2625
  let r = n.filter((e) => e === "image").length, i = n.filter((e) => e === "video").length;
@@ -2289,27 +2627,27 @@ function pr(e, t) {
2289
2627
  }
2290
2628
  //#endregion
2291
2629
  //#region src/composables/useMemiBoardSections.ts
2292
- var mr = [];
2293
- function hr(e) {
2630
+ var Ir = [];
2631
+ function Lr(e) {
2294
2632
  let t = Number(e.cols);
2295
- if (Yn.includes(t)) return t;
2633
+ if (_r.includes(t)) return t;
2296
2634
  let n = e.width;
2297
2635
  return n === "xs" ? 3 : n === "sm" ? 4 : n === "lg" || Number(e.span) === 2 ? 12 : 6;
2298
2636
  }
2299
- function gr(e) {
2637
+ function Rr(e) {
2300
2638
  let t = e.height ?? e.size;
2301
2639
  return t === "sm" || t === "lg" ? t : "md";
2302
2640
  }
2303
- function _r(e) {
2641
+ function zr(e) {
2304
2642
  return e.kind === "post" || e.kind === "comments" ? e.kind : "list";
2305
2643
  }
2306
- function vr(e) {
2644
+ function Br(e) {
2307
2645
  return e.sort === "likes" ? "likes" : "latest";
2308
2646
  }
2309
- function yr(e) {
2647
+ function Vr(e) {
2310
2648
  return (typeof e.boardId == "string" ? e.boardId.trim() : "") || null;
2311
2649
  }
2312
- function br(e, t) {
2650
+ function Hr(e, t) {
2313
2651
  let n = Array.isArray(e.postIds) ? e.postIds : [], r = [];
2314
2652
  for (let e of n) {
2315
2653
  let n = String(e || "").trim();
@@ -2317,62 +2655,102 @@ function br(e, t) {
2317
2655
  }
2318
2656
  return r;
2319
2657
  }
2320
- function xr(e, t) {
2658
+ function Ur(e, t) {
2321
2659
  return w(t, "sections.defaultTitle", { n: e + 1 });
2322
2660
  }
2323
- function Sr(e, t, n) {
2324
- let r = _r(e), i = tr(r, e.count);
2661
+ function Wr(e, t, n) {
2662
+ let r = zr(e), i = Cr(r, e.count), a = Q(e.i18n), o = Qe(e.i18nSource);
2325
2663
  return {
2326
2664
  id: String(e.id || `box-${t + 1}`),
2327
- title: String(e.title || xr(t, n)),
2665
+ title: String(e.title || Ur(t, n)),
2328
2666
  showTitle: e.showTitle !== !1,
2329
2667
  kind: r,
2330
- boardId: yr(e),
2668
+ boardId: Vr(e),
2331
2669
  count: i,
2332
- postIds: br(e, i),
2333
- sort: vr(e),
2334
- cols: hr(e),
2335
- height: gr(e),
2336
- order: t
2670
+ postIds: Hr(e, i),
2671
+ sort: Br(e),
2672
+ cols: Lr(e),
2673
+ height: Rr(e),
2674
+ order: t,
2675
+ ...a ? { i18n: a } : {},
2676
+ ...o ? { i18nSource: o } : {}
2337
2677
  };
2338
2678
  }
2339
- function Cr() {
2340
- let { locale: e } = O(), t = Z(), n = me(t, "settings", "homeLayout"), { data: r, pending: i } = Se(n), a = N(() => (Array.isArray(r.value?.blocks) ? r.value.blocks : mr).map((t, n) => Sr(t, n, e.value)));
2341
- async function o(t) {
2342
- await q(n, {
2679
+ function Gr() {
2680
+ let { locale: e } = D(), { translateEntries: t } = Dn(), n = Z(), r = me(n, "settings", "homeLayout"), { data: i, pending: a } = Se(r), o = N(() => (Array.isArray(i.value?.blocks) ? i.value.blocks : Ir).map((t, n) => Wr(t, n, e.value)));
2681
+ async function s(n) {
2682
+ let r = e.value, i = n.filter((e) => {
2683
+ let t = e.title.trim();
2684
+ return t ? !(e.i18nSource?.title === t && tt(e.i18n, ["title"])) : !1;
2685
+ });
2686
+ if (!i.length) return n;
2687
+ let a = await t(i.map((e) => ({
2688
+ id: e.id,
2689
+ fields: { title: e.title.trim() },
2690
+ context: e.kind
2691
+ })), {
2692
+ source: r,
2693
+ purpose: "section heading on a website home page"
2694
+ });
2695
+ return n.map((e) => {
2696
+ let t = a[e.id];
2697
+ if (!t) return e;
2698
+ let n = e.title.trim(), i = et(r, { title: n }, t);
2699
+ return tt(i, ["title"]) ? {
2700
+ ...e,
2701
+ title: n,
2702
+ i18n: i,
2703
+ i18nSource: {
2704
+ locale: r,
2705
+ title: n
2706
+ }
2707
+ } : e;
2708
+ });
2709
+ }
2710
+ function c(t, n) {
2711
+ let r = t, i = zr(r), a = Cr(i, t.count), o = t.title.trim() || Ur(n, e.value);
2712
+ return {
2713
+ id: t.id,
2714
+ title: o,
2715
+ showTitle: t.showTitle !== !1,
2716
+ kind: i,
2717
+ boardId: Vr(r),
2718
+ count: a,
2719
+ postIds: Hr(r, a),
2720
+ sort: Br(r),
2721
+ cols: Lr(r),
2722
+ height: Rr(r),
2723
+ order: n,
2724
+ ...t.i18n ? { i18n: t.i18n } : {},
2725
+ ...t.i18nSource?.title === o ? { i18nSource: t.i18nSource } : {}
2726
+ };
2727
+ }
2728
+ async function l(e) {
2729
+ let t = e;
2730
+ try {
2731
+ t = await s(e);
2732
+ } catch (e) {
2733
+ console.warn("[memi-board] section translation skipped", e);
2734
+ }
2735
+ await q(r, {
2343
2736
  columns: 12,
2344
- blocks: t.map((t, n) => {
2345
- let r = t, i = _r(r), a = tr(i, t.count);
2346
- return {
2347
- id: t.id,
2348
- title: t.title.trim() || xr(n, e.value),
2349
- showTitle: t.showTitle !== !1,
2350
- kind: i,
2351
- boardId: yr(r),
2352
- count: a,
2353
- postIds: br(r, a),
2354
- sort: vr(r),
2355
- cols: hr(r),
2356
- height: gr(r),
2357
- order: n
2358
- };
2359
- }),
2737
+ blocks: t.map((e, t) => c(e, t)),
2360
2738
  updatedAt: K()
2361
2739
  });
2362
2740
  }
2363
2741
  return {
2364
- settings: r,
2365
- pending: i,
2366
- sections: a,
2367
- blocks: a,
2368
- hasSavedLayout: N(() => Array.isArray(r.value?.blocks)),
2369
- saveSections: o,
2370
- saveBlocks: o
2742
+ settings: i,
2743
+ pending: a,
2744
+ sections: o,
2745
+ blocks: o,
2746
+ hasSavedLayout: N(() => Array.isArray(i.value?.blocks)),
2747
+ saveSections: l,
2748
+ saveBlocks: l
2371
2749
  };
2372
2750
  }
2373
2751
  //#endregion
2374
2752
  //#region src/composables/useMemiBoardMiniPosts.ts
2375
- function wr(e, t) {
2753
+ function Kr(e, t) {
2376
2754
  let n = Z(), i = () => j(), a = I(e)?.trim() || "all", o = N(() => {
2377
2755
  let r = Math.max(1, Math.floor(Number(I(t)) || 5)), a = I(e)?.trim() || "", o = [X("isPublished", "==", !0), X("listed", "==", !0)];
2378
2756
  return a && o.unshift(X("boardId", "==", a)), G(M(n, i()), ...o, W("createdAt", "desc"), H(r));
@@ -2383,7 +2761,7 @@ function wr(e, t) {
2383
2761
  error: l
2384
2762
  };
2385
2763
  }
2386
- function Tr(e) {
2764
+ function qr(e) {
2387
2765
  let t = Z(), n = () => j(), i = N(() => [...new Set(I(e).map((e) => e.trim()).filter(Boolean))].slice(0, 10)), a = i.value.join(",") || "none", o = N(() => i.value.length ? G(M(t, n()), X(R(), "in", i.value)) : null), { data: s, pending: c, error: l } = be(o, { ssrKey: r(n(), `mini-picked/${a}`) });
2388
2766
  return {
2389
2767
  posts: N(() => {
@@ -2400,7 +2778,25 @@ function Tr(e) {
2400
2778
  }
2401
2779
  //#endregion
2402
2780
  //#region src/data/versionHistory.ts
2403
- var Er = [
2781
+ var Jr = [
2782
+ {
2783
+ version: "0.46.1",
2784
+ date: "2026-10-22",
2785
+ highlights: [
2786
+ "게시판 이름과 설명을 저장하면 10개 언어로 번역해 둬요",
2787
+ "글쓰기 AI에서 한 번에 10개 언어로 번역해 저장할 수 있어요",
2788
+ "보는 언어로 제목과 본문이 보이고, 원문을 고치면 번역 대신 원문이 보여요"
2789
+ ]
2790
+ },
2791
+ {
2792
+ version: "0.46.0",
2793
+ date: "2026-10-21",
2794
+ highlights: [
2795
+ "일반 사용자·스태프·매니저·관리자·최고 관리자, 다섯 가지 역할을 지원해요",
2796
+ "게시판별 매니저가 설정과 스태프 배정을 관리할 수 있어요",
2797
+ "서비스에 맞게 역할 이름을 바꿀 수 있어요"
2798
+ ]
2799
+ },
2404
2800
  {
2405
2801
  version: "0.45.1",
2406
2802
  date: "2026-10-15",
@@ -2694,7 +3090,7 @@ var Er = [
2694
3090
  highlights: ["글쓰기·댓글·첨부·로그인 권한으로 게시판을 바로 시작할 수 있어요", "부적절한 글·댓글은 AI 검열로 막을 수 있어요"],
2695
3091
  note: "실제 보안은 호스트 Firebase 규칙 등록이 필요합니다."
2696
3092
  }
2697
- ], Dr = 10, Or = 864e5, kr = 200, Ar = {
3093
+ ], Yr = 10, Xr = 864e5, Zr = 200, Qr = {
2698
3094
  proofread: "맞춤법, 띄어쓰기, 문법 오류만 고쳐라. 의미와 말투는 바꾸지 마라.",
2699
3095
  polish: "뜻과 작성자의 말투를 유지하면서 더 자연스럽고 읽기 좋은 문장으로 다듬어라.",
2700
3096
  continue: "앞의 맥락과 말투에 맞는 내용을 과장 없이 한두 문단 이어서 작성하라.",
@@ -2703,7 +3099,7 @@ var Er = [
2703
3099
  "translate-ko": "자연스러운 한국어로 번역하라. 고유명사와 기술 용어는 정확히 유지하라.",
2704
3100
  title: "본문을 대표하는 간결한 한국어 제목 하나를 만들어라. 따옴표나 설명 없이 제목만 출력하라."
2705
3101
  };
2706
- function jr(e) {
3102
+ function $r(e) {
2707
3103
  return {
2708
3104
  proofread: "Fix only spelling, spacing, and grammar errors. Do not change the meaning or tone.",
2709
3105
  polish: "Refine the text into more natural, readable sentences while keeping the meaning and the author's tone.",
@@ -2714,34 +3110,34 @@ function jr(e) {
2714
3110
  title: `Create one concise title in ${e} that represents the body. Output only the title, without quotes or explanation.`
2715
3111
  };
2716
3112
  }
2717
- function Mr(e, t, n) {
2718
- let r = t === "ko", i = Mn(t);
3113
+ function ei(e, t, n) {
3114
+ let r = t === "ko", i = cn(t);
2719
3115
  if (e.action !== "custom") {
2720
- if (r) return Ar[e.action];
2721
- let t = jr(i)[e.action];
3116
+ if (r) return Qr[e.action];
3117
+ let t = $r(i)[e.action];
2722
3118
  return e.action === "translate-en" || e.action === "translate-ko" || e.action === "title" ? t : `${t} Write the result in the same language as the original text; if unclear, write in ${i}.`;
2723
3119
  }
2724
- let a = e.customInstruction?.trim().slice(0, kr);
3120
+ let a = e.customInstruction?.trim().slice(0, Zr);
2725
3121
  if (!a) throw Error(n("writingAssistant.customRequired"));
2726
3122
  return r ? `뜻은 유지하면서 다음 스타일 지시를 반영해 다시 써라: ${a}` : `Rewrite the text applying the following style instruction while keeping the meaning: ${a}`;
2727
3123
  }
2728
- function Nr(e) {
3124
+ function ti(e) {
2729
3125
  return e.trim().replace(/^```(?:html|text)?\s*/i, "").replace(/\s*```$/, "").trim();
2730
3126
  }
2731
- function Pr(e) {
3127
+ function ni(e) {
2732
3128
  return !e || typeof e != "object" ? null : "toMillis" in e && typeof e.toMillis == "function" ? e.toMillis() : null;
2733
3129
  }
2734
- function Fr(e = {}) {
2735
- let t = O(), n = N(() => E(I(e.locale)) ?? t.locale.value), r = (e, t) => w(n.value, e, t), i = Ce(), a = Z(), o = xe(), s = ue();
2736
- async function c() {
3130
+ function ri(e = {}) {
3131
+ let t = D(), n = N(() => T(I(e.locale)) ?? t.locale.value), r = (e, t) => w(n.value, e, t), i = Ce(), a = Z(), o = xe(), s = ue(), { translateEntries: c, translateHtml: l } = Dn();
3132
+ async function u() {
2737
3133
  let e = o.value?.uid;
2738
3134
  if (!e) throw Error(r("writingAssistant.loginRequired"));
2739
3135
  await ve(a, async (t) => {
2740
3136
  let n = f(a, j(), e), i = await t.get(n);
2741
3137
  if (!i.exists()) throw Error(r("writingAssistant.userMissing"));
2742
- let o = i.data(), s = Pr(o.aiWritingWindowAt), c = Math.max(0, Math.floor(Number(o.aiWritingCount) || 0)), l = Date.now(), u = s != null && l - s < Or;
3138
+ let o = i.data(), s = ni(o.aiWritingWindowAt), c = Math.max(0, Math.floor(Number(o.aiWritingCount) || 0)), l = Date.now(), u = s != null && l - s < Xr;
2743
3139
  if (u && c >= 10) {
2744
- let e = s + Or, t = Math.max(1, Math.ceil((e - l) / 36e5));
3140
+ let e = s + Xr, t = Math.max(1, Math.ceil((e - l) / 36e5));
2745
3141
  throw Error(r("writingAssistant.dailyLimit", {
2746
3142
  limit: 10,
2747
3143
  hours: t
@@ -2754,15 +3150,15 @@ function Fr(e = {}) {
2754
3150
  });
2755
3151
  });
2756
3152
  }
2757
- async function l(e) {
2758
- let t = n.value, a = t === "ko", o = Mr(e, t, r);
3153
+ async function d(e) {
3154
+ let t = n.value, a = t === "ko", o = ei(e, t, r);
2759
3155
  if (!e.content.trim()) throw Error(r("writingAssistant.contentRequired"));
2760
- await c();
2761
- let l = s.moderation ?? {}, u = Le(i, {
3156
+ await u();
3157
+ let c = s.moderation ?? {}, l = Re(i, {
2762
3158
  backend: new Ie(),
2763
- useLimitedUseAppCheckTokens: l.useLimitedUseAppCheckTokens !== !1
2764
- }), d = Re(u, {
2765
- model: l.model ?? "gemini-3.5-flash-lite",
3159
+ useLimitedUseAppCheckTokens: c.useLimitedUseAppCheckTokens !== !1
3160
+ }), d = ze(l, {
3161
+ model: c.model ?? "gemini-3.5-flash-lite",
2766
3162
  systemInstruction: (a ? [
2767
3163
  "너는 게시판 글쓰기 편집 도우미다.",
2768
3164
  "사용자가 요청한 편집 결과만 출력하고 설명, 인사, 마크다운 코드 펜스를 붙이지 마라.",
@@ -2784,94 +3180,64 @@ function Fr(e = {}) {
2784
3180
  `Task: ${o}`,
2785
3181
  e.title ? `Current title: ${e.title}` : "",
2786
3182
  `Body:\n${f.slice(0, 12e3)}`
2787
- ]).filter(Boolean).join("\n\n"), m = Nr((await d.generateContent(p)).response.text());
3183
+ ]).filter(Boolean).join("\n\n"), m = ti((await d.generateContent(p)).response.text());
2788
3184
  if (!m) throw Error(r("writingAssistant.emptyResult"));
2789
3185
  return m;
2790
3186
  }
2791
- return { assist: l };
2792
- }
2793
- //#endregion
2794
- //#region src/composables/useMemiBoardAutoTranslate.ts
2795
- var Ir = 3e4;
2796
- function Lr(e, t) {
2797
- return new Promise((n, r) => {
2798
- let i = setTimeout(() => r(/* @__PURE__ */ Error(`translation timed out after ${t / 1e3}s`)), t);
2799
- e.then((e) => {
2800
- clearTimeout(i), n(e);
2801
- }, (e) => {
2802
- clearTimeout(i), r(e);
2803
- });
2804
- });
2805
- }
2806
- function Rr() {
2807
- let e = Ce(), { moderation: t } = ue();
2808
- function n(n, r) {
2809
- let i = Le(e, {
2810
- backend: new Ie(),
2811
- useLimitedUseAppCheckTokens: t.useLimitedUseAppCheckTokens !== !1
2812
- }), a = Q.object({ properties: Object.fromEntries(r.map((e) => [e, Q.string()])) });
2813
- return Re(i, {
2814
- model: t.model ?? "gemini-3.5-flash-lite",
2815
- systemInstruction: [
2816
- "You translate short user-interface strings for a website.",
2817
- "Return natural, concise UI wording a native speaker would expect in a menu or title — not literal word-for-word translations.",
2818
- "Keep proper nouns, brand names, product names and game titles as they are unless they have a well-known localized name.",
2819
- "Use the context only to understand meaning; never translate or output it."
2820
- ].join("\n"),
2821
- generationConfig: {
2822
- temperature: .2,
2823
- maxOutputTokens: 2048,
2824
- responseMimeType: "application/json",
2825
- responseSchema: Q.object({ properties: Object.fromEntries(n.map((e) => [e, a])) })
3187
+ async function p(e) {
3188
+ let t = e.source ?? n.value, i = e.title?.trim() ?? "", a = e.summary?.trim() ?? "", o = e.content ?? "", s = {};
3189
+ i && (s.title = i), a && (s.summary = a);
3190
+ let d = !!Kt(o);
3191
+ if (!Object.keys(s).length && !d) throw Error(r("writingAssistant.translateEmpty"));
3192
+ if (d && o.length > 12e3) throw Error(r("writingAssistant.translateTooLong", { limit: Sn }));
3193
+ await u();
3194
+ let [f, p] = await Promise.all([Object.keys(s).length ? c([{
3195
+ id: "post",
3196
+ fields: s,
3197
+ context: "message board post title and short summary"
3198
+ }], {
3199
+ source: t,
3200
+ purpose: "message board post title and short summary"
3201
+ }) : Promise.resolve({}), d ? l(o, { source: t }) : Promise.resolve({})]), m = et(t, s, f.post);
3202
+ if (Object.keys(s).length && !tt(m, Object.keys(s))) throw Error(r("writingAssistant.translateIncomplete"));
3203
+ let h = {
3204
+ i18n: m,
3205
+ i18nSource: {
3206
+ locale: t,
3207
+ title: i,
3208
+ summary: a
2826
3209
  }
2827
- });
2828
- }
2829
- async function r(e, t, r, i) {
2830
- let a = Object.keys(e.fields), o = [
2831
- `Purpose: ${i}`,
2832
- `Source language: ${Mn(t)} (${t})`,
2833
- `Translate into: ${r.map((e) => `${e} = ${Mn(e)}`).join(", ")}`,
2834
- e.context ? `Context (do not translate): ${e.context}` : "",
2835
- "Text (JSON):",
2836
- JSON.stringify(e.fields)
2837
- ].filter(Boolean).join("\n"), s = await Lr(n(r, a).generateContent(o), Ir), c = JSON.parse(s.response.text()), l = {};
2838
- for (let e of r) {
2839
- let t = c?.[e];
2840
- if (!t) continue;
2841
- let n = {};
2842
- for (let e of a) {
2843
- let r = typeof t[e] == "string" ? t[e].trim() : "";
2844
- r && (n[e] = r);
3210
+ };
3211
+ if (d) {
3212
+ let e = {};
3213
+ for (let [t, n] of Object.entries(p)) {
3214
+ let r = T(t);
3215
+ r && typeof n == "string" && n.trim() && (e[r] = { content: n.trim() });
2845
3216
  }
2846
- Object.keys(n).length && (l[e] = n);
3217
+ let n = et(t, { content: o }, e);
3218
+ if (!tt(n, ["content"])) throw Error(r("writingAssistant.translateIncomplete"));
3219
+ h.contentI18n = n, h.contentI18nSource = {
3220
+ locale: t,
3221
+ hash: Xe(o)
3222
+ };
2847
3223
  }
2848
- return l;
2849
- }
2850
- async function i(e, t = {}) {
2851
- let n = t.source ?? "ko", i = (t.targets ?? A.map((e) => e.code)).filter((e) => e !== n), a = t.purpose ?? "website UI labels", o = e.map((e) => ({
2852
- ...e,
2853
- fields: Object.fromEntries(Object.entries(e.fields).map(([e, t]) => [e, t.trim()]).filter(([, e]) => e))
2854
- })).filter((e) => Object.keys(e.fields).length);
2855
- if (!o.length || !i.length) return {};
2856
- let s = await Promise.all(o.map((e) => r(e, n, i, a)));
2857
- return Object.fromEntries(o.map((e, t) => [e.id, s[t]]));
3224
+ return h;
2858
3225
  }
2859
- return { translateEntries: i };
2860
- }
2861
- function zr(e, t, n, r) {
2862
- let i = E(t);
2863
- return !i || i === "ko" ? r : e?.[i]?.[n] || e?.en?.[n] || r;
3226
+ return {
3227
+ assist: d,
3228
+ translatePost: p
3229
+ };
2864
3230
  }
2865
3231
  //#endregion
2866
3232
  //#region src/utils/renderMarkdown.ts
2867
- function Br(e) {
3233
+ function ii(e) {
2868
3234
  return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2869
3235
  }
2870
- function Vr(e) {
3236
+ function ai(e) {
2871
3237
  let t = e;
2872
3238
  return t = t.replace(/`([^`]+)`/g, "<code>$1</code>"), t = t.replace(/!\[([^\]]*)\]\((https?:\/\/[^)\s]+)\)/g, "<img src=\"$2\" alt=\"$1\" class=\"max-w-full h-auto rounded-lg my-3\" loading=\"lazy\" />"), t = t.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, "<a href=\"$2\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-primary underline\">$1</a>"), t = t.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>"), t = t.replace(/\*([^*]+)\*/g, "<em>$1</em>"), t = t.replace(/~~([^~]+)~~/g, "<del>$1</del>"), t;
2873
3239
  }
2874
- function Hr(e) {
3240
+ function oi(e) {
2875
3241
  if (!e?.trim()) return "";
2876
3242
  let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = 0, i = !1, a = [], o = null, s = [];
2877
3243
  function c() {
@@ -2880,7 +3246,7 @@ function Hr(e) {
2880
3246
  return;
2881
3247
  }
2882
3248
  let e = o;
2883
- n.push(`<${e}>${s.map((e) => `<li>${Vr(e)}</li>`).join("")}</${e}>`), o = null, s = [];
3249
+ n.push(`<${e}>${s.map((e) => `<li>${ai(e)}</li>`).join("")}</${e}>`), o = null, s = [];
2884
3250
  }
2885
3251
  for (; r < t.length;) {
2886
3252
  let e = t[r] ?? "", l = e.match(/^```(\w*)\s*$/);
@@ -2889,7 +3255,7 @@ function Hr(e) {
2889
3255
  continue;
2890
3256
  }
2891
3257
  if (i) {
2892
- a.push(Br(e)), r++;
3258
+ a.push(ii(e)), r++;
2893
3259
  continue;
2894
3260
  }
2895
3261
  if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(e)) {
@@ -2900,24 +3266,24 @@ function Hr(e) {
2900
3266
  if (u) {
2901
3267
  c();
2902
3268
  let e = u[1].length;
2903
- n.push(`<h${e} class="font-semibold mt-4 mb-2">${Vr(Br(u[2]))}</h${e}>`), r++;
3269
+ n.push(`<h${e} class="font-semibold mt-4 mb-2">${ai(ii(u[2]))}</h${e}>`), r++;
2904
3270
  continue;
2905
3271
  }
2906
3272
  if (e.startsWith("> ")) {
2907
3273
  c();
2908
3274
  let e = [];
2909
- for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push(Br((t[r] ?? "").slice(2))), r++;
2910
- n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${Vr(e.join("<br />"))}</blockquote>`);
3275
+ for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push(ii((t[r] ?? "").slice(2))), r++;
3276
+ n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${ai(e.join("<br />"))}</blockquote>`);
2911
3277
  continue;
2912
3278
  }
2913
3279
  let d = e.match(/^[-*+]\s+(.+)$/);
2914
3280
  if (d) {
2915
- o && o !== "ul" && c(), o = "ul", s.push(Br(d[1])), r++;
3281
+ o && o !== "ul" && c(), o = "ul", s.push(ii(d[1])), r++;
2916
3282
  continue;
2917
3283
  }
2918
3284
  let f = e.match(/^\d+\.\s+(.+)$/);
2919
3285
  if (f) {
2920
- o && o !== "ol" && c(), o = "ol", s.push(Br(f[1])), r++;
3286
+ o && o !== "ol" && c(), o = "ol", s.push(ii(f[1])), r++;
2921
3287
  continue;
2922
3288
  }
2923
3289
  if (!e.trim()) {
@@ -2929,13 +3295,13 @@ function Hr(e) {
2929
3295
  for (; r < t.length;) {
2930
3296
  let e = t[r] ?? "";
2931
3297
  if (!e.trim() || /^(#{1,3}\s|```|[-*+]\s|\d+\.\s|> |(-{3,}|\*{3,})\s*$)/.test(e)) break;
2932
- p.push(Br(e)), r++;
3298
+ p.push(ii(e)), r++;
2933
3299
  }
2934
- n.push(`<p class="my-2 leading-relaxed">${Vr(p.join("<br />"))}</p>`);
3300
+ n.push(`<p class="my-2 leading-relaxed">${ai(p.join("<br />"))}</p>`);
2935
3301
  }
2936
3302
  return c(), i && a.length && n.push(`<pre class="overflow-x-auto rounded-lg bg-muted p-3 text-sm"><code>${a.join("\n")}</code></pre>`), n.join("\n");
2937
3303
  }
2938
3304
  //#endregion
2939
- export { un as AUTHOR_MEMO_MAX_LENGTH, Yn as BOARD_SECTION_COLS, Qn as BOARD_SECTION_KINDS, $n as BOARD_SECTION_SORTS, Kn as BOARD_USER_ROLES, gn as COMMENT_BODY_MAX_LENGTH, st as DEFAULT_BLOCK_BAN_DECAY_MS, ot as DEFAULT_BLOCK_BAN_THRESHOLD, Gt as DEFAULT_BOARDS, Kt as DEFAULT_CATEGORIES, S as DEFAULT_COMMENTS_COLLECTION, le as DEFAULT_LIKES_COLLECTION, T as DEFAULT_MEMI_BOARD_LOCALE, h as DEFAULT_POSTS_COLLECTION, n as DEFAULT_REPORTS_COLLECTION, ce as DEFAULT_SETTINGS_COLLECTION, g as DEFAULT_USERS_COLLECTION, ie as FALLBACK_MEMI_BOARD_LOCALE, A as MEMI_BOARD_LOCALES, Cn as REPORT_DETAIL_MAX_LENGTH, wn as REPORT_REASONS, Dr as WRITING_ASSISTANT_DAILY_LIMIT, Be as asHttpUrl, Ke as boardListOgDescription, Ge as boardListOgTitle, We as boardPostOgDescription, Ue as boardPostOgTitle, Xn as boardSectionKinds, Zn as boardSectionSorts, r as boardSsrKey, f as boardUserDoc, e as boardUsersCol, Bt as buildPostPreview, tt as canManageBoardByRole, nt as canWriteCommentByRole, tr as clampSectionCount, k as commentDoc, u as commentsCol, b as configureMemiBoard, er as defaultSectionCount, ct as effectiveModerationBlockCount, xt as extractEditorImageUrls, Qe as fetchPublicListForSeo, Ze as fetchPublicPostForSeo, x as formatDate, re as formatFullDate, oe as formatRelativeDate, dt as formatRestrictedUntilLabel, C as formatTimestampDetails, Ft as hasBodyImage, Pt as hasBodyText, yt as imeSafeSubmitClick, vt as imeSafeSubmitPointerDown, et as isAssignedStaff, It as isContentEmpty, Zt as isFirestorePermissionDenied, te as isMemiBoardLocaleLoaded, ut as isModerationWriteRestricted, d as likeDoc, p as likeDocId, a as likesCol, se as loadMemiBoardLocale, zr as localizedText, xn as memiBoardCommentLikesKey, ae as memiBoardHostLocaleKey, D as memiBoardLocaleInfo, fr as miniBoardView, sr as miniCommentLink, or as miniMoreLink, cr as miniPostImage, ar as miniPostLink, lr as miniPostTitle, pr as miniPostsView, ur as miniViewGroup, dr as miniViewLabel, lt as moderationWriteRestrictedUntilMs, qe as normalizeBasePath, Sr as normalizeBoardSection, E as normalizeMemiBoardLocale, nr as pairSectionCols, Mt as plainTextFromHtml, c as postBodyDoc, l as postDoc, Ct as postNamespaceFromStoragePath, m as postStorageFolder, M as postsCol, Hr as renderMarkdownToHtml, t as reportDoc, v as reportDocId, Tn as reportReasonLabel, s as reportsCol, _ as resolveBoardPathConfig, Je as resolvePublicSeoDb, rr as sectionColClass, ir as sectionMinHeight, y as settingsCol, o as settingsDoc, ne as slugify, St as storagePathFromDownloadUrl, Nt as titleFromBody, He as toAbsoluteUrl, Ve as toBoardOgImageUrl, w as translate, j as useBoardPathConfig, ht as useMemiBoardAuth, fn as useMemiBoardAuthorMemo, Rr as useMemiBoardAutoTranslate, ln as useMemiBoardCommentFeed, Sn as useMemiBoardCommentLikes, vn as useMemiBoardComments, ue as useMemiBoardConfig, O as useMemiBoardI18n, bn as useMemiBoardLikes, wr as useMemiBoardMiniLatest, Tr as useMemiBoardMiniPicked, Gn as useMemiBoardModeration, nn as useMemiBoardPost, tn as useMemiBoardPostList, $t as useMemiBoardPosts, yn as useMemiBoardReplies, On as useMemiBoardReportQueue, En as useMemiBoardReports, Cr as useMemiBoardSections, Yt as useMemiBoardSettings, sn as useMemiBoardUserComments, an as useMemiBoardUserPosts, Jn as useMemiBoardUserProfile, qn as useMemiBoardUsers, kn as useMemiBoardViews, Fr as useMemiBoardWritingAssistant, Er as versionHistory, zt as videoListCoverUrl, Rt as youtubeId };
3305
+ export { Kn as AUTHOR_MEMO_MAX_LENGTH, _r as BOARD_SECTION_COLS, br as BOARD_SECTION_KINDS, xr as BOARD_SECTION_SORTS, mr as BOARD_USER_ROLES, Qn as COMMENT_BODY_MAX_LENGTH, xt as DEFAULT_BLOCK_BAN_DECAY_MS, bt as DEFAULT_BLOCK_BAN_THRESHOLD, On as DEFAULT_BOARDS, kn as DEFAULT_CATEGORIES, S as DEFAULT_COMMENTS_COLLECTION, le as DEFAULT_LIKES_COLLECTION, ee as DEFAULT_MEMI_BOARD_LOCALE, h as DEFAULT_POSTS_COLLECTION, n as DEFAULT_REPORTS_COLLECTION, ce as DEFAULT_SETTINGS_COLLECTION, g as DEFAULT_USERS_COLLECTION, ie as FALLBACK_MEMI_BOARD_LOCALE, Sn as HTML_MAX_CHARS, A as MEMI_BOARD_LOCALES, ar as REPORT_DETAIL_MAX_LENGTH, or as REPORT_REASONS, Yr as WRITING_ASSISTANT_DAILY_LIMIT, Ve as asHttpUrl, st as boardCopyIsTranslated, qe as boardListOgDescription, Ke as boardListOgTitle, Ge as boardPostOgDescription, We as boardPostOgTitle, vr as boardSectionKinds, yr as boardSectionSorts, r as boardSsrKey, f as boardUserDoc, e as boardUsersCol, en as buildPostPreview, ht as canManageBoardByRole, gt as canWriteCommentByRole, Cr as clampSectionCount, O as commentDoc, u as commentsCol, b as configureMemiBoard, tt as coversLocales, Sr as defaultSectionCount, ot as displayPostContent, at as displayPostSummary, it as displayPostTitle, rt as displayStoredField, St as effectiveModerationBlockCount, Ft as extractEditorImageUrls, ft as fetchPublicListForSeo, dt as fetchPublicPostForSeo, x as formatDate, re as formatFullDate, oe as formatRelativeDate, Tt as formatRestrictedUntilLabel, C as formatTimestampDetails, Yt as hasBodyImage, Jt as hasBodyText, Xe as hashText, Nt as imeSafeSubmitClick, Mt as imeSafeSubmitPointerDown, mt as isAssignedStaff, Xt as isContentEmpty, Pn as isFirestorePermissionDenied, k as isMemiBoardLocaleLoaded, wt as isModerationWriteRestricted, d as likeDoc, p as likeDocId, a as likesCol, se as loadMemiBoardLocale, nt as localizedText, rr as memiBoardCommentLikesKey, ae as memiBoardHostLocaleKey, E as memiBoardLocaleInfo, et as mergeSourceFields, Pr as miniBoardView, kr as miniCommentLink, Or as miniMoreLink, Ar as miniPostImage, Dr as miniPostLink, jr as miniPostTitle, Fr as miniPostsView, Mr as miniViewGroup, Nr as miniViewLabel, Ct as moderationWriteRestrictedUntilMs, Je as normalizeBasePath, Wr as normalizeBoardSection, T as normalizeMemiBoardLocale, wr as pairSectionCols, Kt as plainTextFromHtml, c as postBodyDoc, l as postDoc, Lt as postNamespaceFromStoragePath, m as postStorageFolder, M as postsCol, Qe as readI18nSource, Q as readLocalizedFields, oi as renderMarkdownToHtml, t as reportDoc, v as reportDocId, sr as reportReasonLabel, s as reportsCol, _ as resolveBoardPathConfig, ct as resolvePublicSeoDb, Tr as sectionColClass, Er as sectionMinHeight, y as settingsCol, o as settingsDoc, ne as slugify, It as storagePathFromDownloadUrl, qt as titleFromBody, Ue as toAbsoluteUrl, He as toBoardOgImageUrl, w as translate, j as useBoardPathConfig, kt as useMemiBoardAuth, Jn as useMemiBoardAuthorMemo, Dn as useMemiBoardAutoTranslate, Gn as useMemiBoardCommentFeed, ir as useMemiBoardCommentLikes, er as useMemiBoardComments, ue as useMemiBoardConfig, D as useMemiBoardI18n, nr as useMemiBoardLikes, Kr as useMemiBoardMiniLatest, qr as useMemiBoardMiniPicked, pr as useMemiBoardModeration, zn as useMemiBoardPost, Rn as useMemiBoardPostList, In as useMemiBoardPosts, tr as useMemiBoardReplies, ur as useMemiBoardReportQueue, cr as useMemiBoardReports, Gr as useMemiBoardSections, Mn as useMemiBoardSettings, Un as useMemiBoardUserComments, Vn as useMemiBoardUserPosts, gr as useMemiBoardUserProfile, hr as useMemiBoardUsers, dr as useMemiBoardViews, ri as useMemiBoardWritingAssistant, Jr as versionHistory, $t as videoListCoverUrl, Qt as youtubeId };
2940
3306
 
2941
3307
  //# sourceMappingURL=index.js.map