memi-board 0.46.0 → 0.46.2

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 (53) hide show
  1. package/README.md +1 -0
  2. package/dist/{ar-BCQdRw_S.js → ar-D_6hknjh.js} +22 -1
  3. package/dist/ar-D_6hknjh.js.map +1 -0
  4. package/dist/composables/fetchPublicSeo.d.ts +1 -1
  5. package/dist/composables/useMemiBoardAutoTranslate.d.ts +6 -6
  6. package/dist/composables/useMemiBoardPosts.d.ts +6 -1
  7. package/dist/composables/useMemiBoardSettings.d.ts +5 -4
  8. package/dist/composables/useMemiBoardWritingAssistant.d.ts +8 -0
  9. package/dist/{de-ddWQNaqs.js → de-DQqSsBsZ.js} +22 -1
  10. package/dist/de-DQqSsBsZ.js.map +1 -0
  11. package/dist/{en-CaPfPNDy.js → en-Broh-9df.js} +19 -1
  12. package/dist/en-Broh-9df.js.map +1 -0
  13. package/dist/{es-7JHjGVlM.js → es-D3eEYN6w.js} +22 -1
  14. package/dist/es-D3eEYN6w.js.map +1 -0
  15. package/dist/{fr-C3NCMNoy.js → fr-CbXPRKeU.js} +22 -1
  16. package/dist/fr-CbXPRKeU.js.map +1 -0
  17. package/dist/{id-C8hCAMPJ.js → id-DxVqvPpo.js} +22 -1
  18. package/dist/id-DxVqvPpo.js.map +1 -0
  19. package/dist/index.d.ts +3 -2
  20. package/dist/index.js +1266 -921
  21. package/dist/index.js.map +1 -1
  22. package/dist/{ja-oTWri4hY.js → ja-D2dIWUse.js} +22 -1
  23. package/dist/{ja-oTWri4hY.js.map → ja-D2dIWUse.js.map} +1 -1
  24. package/dist/{pt-B9EH4scA.js → pt-BAuHdglU.js} +22 -1
  25. package/dist/pt-BAuHdglU.js.map +1 -0
  26. package/dist/runtime/components/CategorySettings.vue +80 -14
  27. package/dist/runtime/components/Detail.vue +9 -7
  28. package/dist/runtime/components/Editor.vue +79 -8
  29. package/dist/runtime/components/List.vue +2 -2
  30. package/dist/runtime/components/ListDefault.vue +16 -6
  31. package/dist/runtime/components/ListDense.vue +8 -4
  32. package/dist/runtime/components/ListImage.vue +13 -6
  33. package/dist/runtime/components/ListVideo.vue +14 -5
  34. package/dist/runtime/components/Section.vue +10 -3
  35. package/dist/runtime/components/Settings.vue +77 -11
  36. package/dist/runtime/composables/useMemiBoardSeo.ts +3 -3
  37. package/dist/{slugify-DJ82ifCd.js → slugify-B05Qykf_.js} +28 -10
  38. package/dist/slugify-B05Qykf_.js.map +1 -0
  39. package/dist/storage.js +1 -1
  40. package/dist/types.d.ts +48 -0
  41. package/dist/utils/localizedFields.d.ts +33 -0
  42. package/dist/{zh-D5rDYV6E.js → zh-CMyzTdQ5.js} +22 -1
  43. package/dist/zh-CMyzTdQ5.js.map +1 -0
  44. package/package.json +1 -1
  45. package/dist/ar-BCQdRw_S.js.map +0 -1
  46. package/dist/de-ddWQNaqs.js.map +0 -1
  47. package/dist/en-CaPfPNDy.js.map +0 -1
  48. package/dist/es-7JHjGVlM.js.map +0 -1
  49. package/dist/fr-C3NCMNoy.js.map +0 -1
  50. package/dist/id-C8hCAMPJ.js.map +0 -1
  51. package/dist/pt-B9EH4scA.js.map +0 -1
  52. package/dist/slugify-DJ82ifCd.js.map +0 -1
  53. 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 A, m as j, n as te, o as ne, p as re, r as ie, s as ae, u as oe, v as M, w as se, x as ce, y as le, z as N } from "./slugify-DJ82ifCd.js";
2
- import { computed as P, onScopeDispose as F, reactive as ue, ref as I, toValue as L, watch as R } from "vue";
3
- import { deleteDoc as de, deleteField as fe, doc as pe, documentId as z, endAt as me, endBefore as he, getDoc as B, getDocs as V, increment as H, limit as U, limitToLast as ge, onSnapshot as W, orderBy as G, query as K, runTransaction as _e, serverTimestamp as q, setDoc as J, startAfter as Y, updateDoc as X, where as Z, writeBatch as ve } from "firebase/firestore";
4
- import { useCollection as ye, useCurrentUser as be, useDocument as xe, useFirebaseApp as Se, useFirebaseAuth as Ce, useFirestore as Q } from "vuefire";
5
- import { GoogleAuthProvider as we, OAuthProvider as Te, createUserWithEmailAndPassword as Ee, signInWithEmailAndPassword as De, signInWithPopup as Oe, signOut as ke, updateProfile as Ae } from "firebase/auth";
6
- import { deleteObject as je, getStorage as Me, listAll as Ne, ref as Pe } from "firebase/storage";
7
- import { GoogleAIBackend as Fe, Schema as Ie, getAI as Le, getGenerativeModel as Re } from "firebase/ai";
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-B05Qykf_.js";
2
+ import { computed as N, onScopeDispose as P, reactive as de, ref as F, toValue as I, watch as L } from "vue";
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
+ import { useCollection as be, useCurrentUser as xe, useDocument as Se, useFirebaseApp as Ce, useFirebaseAuth as we, useFirestore as Z } from "vuefire";
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
+ import { deleteObject as Me, getStorage as Ne, listAll as Pe, ref as Fe } from "firebase/storage";
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
- db: Q(),
68
- paths: M()
159
+ db: Z(),
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 B(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 V(K(N(a.db, a.paths), Z("boardId", "==", r), Z("slug", "==", i), Z("listed", "==", !0), Z("isPublished", "==", !0), U(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) {
129
- n && await oe(n);
130
- let r = e?.trim() || null, i = t ?? Je();
228
+ async function ft(e, t, n) {
229
+ n && await se(n);
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 V(K(N(e.db, e.paths), Z("boardId", "==", t), Z("listed", "==", !0), Z("isPublished", "==", !0), G("createdAt", "desc"), U(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) {
282
+ function mt(e, t) {
179
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,29 +344,29 @@ 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] = {
247
- role: I("user"),
248
- rolePending: I(!0),
249
- rawBlockCount: I(0),
250
- blockAtMs: I(null),
350
+ return e[Dt] || (e[Dt] = {
351
+ role: F("user"),
352
+ rolePending: F(!0),
353
+ rawBlockCount: F(0),
354
+ blockAtMs: F(null),
251
355
  watchStarted: !1
252
- }), e[pt];
356
+ }), e[Dt];
253
357
  }
254
- function ht() {
255
- let e = le(), { t, locale: n } = O(), r = mt(), i = Q(), a = Ce(), o = be(), { 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
- return f(i, M(), e);
369
+ return f(i, j(), e);
266
370
  }
267
371
  function h(e) {
268
372
  if (s.value = [
@@ -275,25 +379,25 @@ function ht() {
275
379
  l.value = 0, u.value = null;
276
380
  return;
277
381
  }
278
- 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);
279
383
  }
280
384
  async function g(e) {
281
- let t = m(e.uid), n = ee(i, M(), e.uid), r = await B(t);
282
- if (!r.exists()) return await Promise.all([J(t, {
385
+ let t = m(e.uid), n = te(i, j(), e.uid), r = await z(t);
386
+ if (!r.exists()) return await Promise.all([q(t, {
283
387
  role: "user",
284
388
  displayName: e.displayName,
285
389
  photoURL: e.photoURL,
286
- joinedAt: q(),
287
- lastVisitAt: q(),
288
- updatedAt: q()
289
- }), J(n, { email: e.email })]), h({ moderationBlockCount: 0 }), "user";
390
+ joinedAt: K(),
391
+ lastVisitAt: K(),
392
+ updatedAt: K()
393
+ }), q(n, { email: e.email })]), h({ moderationBlockCount: 0 }), "user";
290
394
  let a = r.data();
291
395
  h(a);
292
396
  let o = typeof a.displayName == "string" && a.displayName.trim(), s = typeof a.photoURL == "string" && a.photoURL.trim(), c = {
293
- lastVisitAt: q(),
294
- updatedAt: q()
397
+ lastVisitAt: K(),
398
+ updatedAt: K()
295
399
  };
296
- return !o && e.displayName && (c.displayName = e.displayName), !s && e.photoURL && (c.photoURL = e.photoURL), await Promise.all([X(t, c), J(n, { email: e.email }, { merge: !0 })]), [
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 })]), [
297
401
  "owner",
298
402
  "admin",
299
403
  "manager",
@@ -307,10 +411,10 @@ function ht() {
307
411
  h(void 0);
308
412
  return;
309
413
  }
310
- let t = await B(m(e));
414
+ let t = await z(m(e));
311
415
  h(t.exists() ? t.data() : void 0);
312
416
  }
313
- r.watchStarted || (r.watchStarted = !0, R(o, async (e) => {
417
+ r.watchStarted || (r.watchStarted = !0, L(o, async (e) => {
314
418
  if (c.value = !0, !e) {
315
419
  s.value = "user", h(void 0), c.value = !1;
316
420
  return;
@@ -323,46 +427,46 @@ function ht() {
323
427
  c.value = !1;
324
428
  }
325
429
  }, { immediate: !0 }));
326
- let v = P(() => !!o.value), y = P(() => s.value === "owner"), b = P(() => s.value === "manager"), x = P(() => y.value || s.value === "admin"), S = P(() => b.value || s.value === "staff"), C = P(() => x.value || S.value), w = P(() => ct(l.value, u.value, Date.now(), p())), T = P(() => {
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(() => {
327
431
  let e = d();
328
- return e <= 0 ? !1 : ut(l.value, u.value, Date.now(), e, p());
329
- }), E = P(() => {
432
+ return e <= 0 ? !1 : wt(l.value, u.value, Date.now(), e, p());
433
+ }), T = N(() => {
330
434
  let e = d();
331
435
  if (e <= 0) return null;
332
- let t = lt(l.value, u.value, Date.now(), e, p());
333
- return t == null ? null : dt(t, Date.now(), n.value);
334
- }), D = P(() => {
335
- if (!T.value) return null;
336
- let e = E.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;
337
441
  return e ? t("auth.writeRestrictedUntil", { until: e }) : t("auth.writeRestricted");
338
442
  });
339
- async function k() {
443
+ async function O() {
340
444
  let e = o.value?.uid, r = d();
341
445
  if (!e || r <= 0) return {
342
446
  effectiveCount: 0,
343
447
  restricted: !1,
344
448
  messageSuffix: ""
345
449
  };
346
- let i = m(e), a = await B(i);
347
- a.exists() ? await X(i, {
348
- moderationBlockCount: H(1),
349
- moderationBlockAt: q(),
350
- updatedAt: q()
351
- }) : await J(i, {
450
+ let i = m(e), a = await z(i);
451
+ a.exists() ? await Y(i, {
452
+ moderationBlockCount: V(1),
453
+ moderationBlockAt: K(),
454
+ updatedAt: K()
455
+ }) : await q(i, {
352
456
  role: "user",
353
457
  displayName: o.value?.displayName ?? null,
354
458
  moderationBlockCount: 1,
355
- moderationBlockAt: q(),
356
- updatedAt: q()
459
+ moderationBlockAt: K(),
460
+ updatedAt: K()
357
461
  });
358
462
  let s = (a.exists() ? Math.max(0, Math.floor(Number(a.data().moderationBlockCount) || 0)) : 0) + 1, c = Date.now();
359
463
  l.value = s, u.value = c;
360
- 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", {
361
465
  count: f,
362
466
  threshold: r
363
467
  });
364
468
  if (h) {
365
- 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);
366
470
  g = i ? t("auth.strikeRestrictedUntil", {
367
471
  count: f,
368
472
  until: i
@@ -374,101 +478,101 @@ function ht() {
374
478
  messageSuffix: g
375
479
  };
376
480
  }
377
- function A() {
481
+ function k() {
378
482
  if (!a) throw Error(t("auth.notInitialized"));
379
483
  return a;
380
484
  }
381
- async function j() {
382
- return Oe(A(), new we());
485
+ async function A() {
486
+ return ke(k(), new Te());
383
487
  }
384
- async function te() {
385
- let e = new Te("apple.com");
386
- return e.addScope("email"), e.addScope("name"), Oe(A(), e);
387
- }
388
- async function ne(e, t) {
389
- return De(A(), e, t);
488
+ async function ne() {
489
+ let e = new Ee("apple.com");
490
+ return e.addScope("email"), e.addScope("name"), ke(k(), e);
390
491
  }
391
- async function re(e, t, n) {
392
- let r = await Ee(A(), e, t);
393
- return n && await Ae(r.user, { displayName: n }), r;
492
+ async function re(e, t) {
493
+ return Oe(k(), e, t);
394
494
  }
395
- async function ie() {
396
- await ke(A());
495
+ async function ie(e, t, n) {
496
+ let r = await De(k(), e, t);
497
+ return n && await je(r.user, { displayName: n }), r;
397
498
  }
398
- function ae(e) {
399
- return tt(o.value?.uid, x.value, S.value, e ? at(e) : void 0);
499
+ async function ae() {
500
+ await Ae(k());
400
501
  }
401
502
  function oe(e) {
402
- return o.value ? x.value ? !0 : b.value && !!e && (at(e)?.allowedManagerUids ?? []).includes(o.value.uid) : !1;
503
+ return ht(o.value?.uid, x.value, S.value, e ? yt(e) : void 0);
403
504
  }
404
505
  function se(e) {
405
- return v.value && (o.value?.uid === e.authorUid || ae(e.boardId));
506
+ return o.value ? x.value ? !0 : b.value && !!e && (yt(e)?.allowedManagerUids ?? []).includes(o.value.uid) : !1;
406
507
  }
407
508
  function ce(e) {
509
+ return v.value && (o.value?.uid === e.authorUid || oe(e.boardId));
510
+ }
511
+ function le(e) {
408
512
  return v.value && o.value?.uid === e.authorUid;
409
513
  }
410
- function N(e) {
411
- return v.value && (o.value?.uid === e.authorUid || ae(e.boardId));
514
+ function M(e) {
515
+ return v.value && (o.value?.uid === e.authorUid || oe(e.boardId));
412
516
  }
413
517
  return {
414
518
  user: o,
415
519
  role: s,
416
520
  isOwner: y,
417
521
  isManager: b,
418
- canConfigureBoard: oe,
522
+ canConfigureBoard: se,
419
523
  isSignedIn: v,
420
524
  isAdmin: x,
421
525
  isStaff: S,
422
526
  canManageContent: C,
423
- canManageBoard: ae,
527
+ canManageBoard: oe,
424
528
  rolePending: c,
425
529
  moderationBlockCount: w,
426
- isWriteRestricted: T,
427
- restrictedUntilLabel: E,
428
- restrictedMessage: D,
429
- signInWithGoogle: j,
430
- signInWithApple: te,
431
- signInWithEmail: ne,
432
- signUpWithEmail: re,
433
- signOut: ie,
434
- canEdit: se,
435
- canDelete: se,
436
- canEditComment: ce,
437
- canDeleteComment: N,
438
- recordContentModerationBlock: k,
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,
439
543
  refreshBoardUser: _
440
544
  };
441
545
  }
442
546
  //#endregion
443
547
  //#region src/utils/imeSafeSubmit.ts
444
- var gt = /* @__PURE__ */ new WeakMap();
445
- function _t(e) {
548
+ var At = /* @__PURE__ */ new WeakMap();
549
+ function jt(e) {
446
550
  let t = e.currentTarget;
447
551
  return t instanceof HTMLButtonElement ? t : t instanceof Element ? t.closest("button") : null;
448
552
  }
449
- function vt(e) {
553
+ function Mt(e) {
450
554
  if (e.pointerType !== "mouse" || e.button !== 0) return;
451
- let t = _t(e), n = t?.form;
555
+ let t = jt(e), n = t?.form;
452
556
  if (!t || !n || t.disabled) return;
453
- gt.set(t, Date.now() + 1e3);
557
+ At.set(t, Date.now() + 1e3);
454
558
  let r = document.activeElement;
455
559
  r instanceof HTMLElement && r.blur(), setTimeout(() => n.requestSubmit(t), 0);
456
560
  }
457
- function yt(e) {
458
- let t = _t(e);
561
+ function Nt(e) {
562
+ let t = jt(e);
459
563
  if (!t) return;
460
- let n = gt.get(t);
461
- 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));
462
566
  }
463
567
  //#endregion
464
568
  //#region src/utils/findReadablePost.ts
465
- async function bt(e, t, n, r, i, a) {
466
- let o = [Z("boardId", "==", n), Z("slug", "==", r)];
467
- return a ? (await V(K(N(e, t), ...o, U(1)))).docs[0] : (await Promise.all([V(K(N(e, t), ...o, Z("listed", "==", !0), Z("isPublished", "==", !0), U(1))), ...i ? [V(K(N(e, t), ...o, Z("authorUid", "==", i), U(1)))] : []])).flatMap((e) => e.docs)[0];
569
+ async function Pt(e, t, n, r, i, a) {
570
+ let o = [X("boardId", "==", n), X("slug", "==", r)];
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];
468
572
  }
469
573
  //#endregion
470
574
  //#region src/utils/extractEditorImageUrls.ts
471
- function xt(e) {
575
+ function Ft(e) {
472
576
  if (!e) return [];
473
577
  let t = /* @__PURE__ */ new Set();
474
578
  for (let n of e.matchAll(/!\[[^\]]*]\((https?:\/\/[^)\s]+)\)/g)) n[1] && t.add(n[1].replaceAll("&amp;", "&"));
@@ -477,7 +581,7 @@ function xt(e) {
477
581
  }
478
582
  //#endregion
479
583
  //#region src/utils/storagePath.ts
480
- function St(e) {
584
+ function It(e) {
481
585
  try {
482
586
  let t = new URL(e), n = t.hostname.toLowerCase();
483
587
  if (!(n === "firebasestorage.googleapis.com" || n.endsWith(".firebasestorage.app"))) return null;
@@ -487,106 +591,106 @@ function St(e) {
487
591
  return null;
488
592
  }
489
593
  }
490
- function Ct(e) {
594
+ function Lt(e) {
491
595
  return e.match(/(?:^|\/)posts\/([^/]+)\//)?.[1] ?? null;
492
596
  }
493
597
  //#endregion
494
598
  //#region src/utils/deletePostCascade.ts
495
- async function wt(e) {
599
+ async function Rt(e) {
496
600
  try {
497
- let t = await Ne(e);
498
- await Promise.all([...t.items.map((e) => je(e).catch(() => {})), ...t.prefixes.map((e) => wt(e))]);
601
+ let t = await Pe(e);
602
+ await Promise.all([...t.items.map((e) => Me(e).catch(() => {})), ...t.prefixes.map((e) => Rt(e))]);
499
603
  } catch {}
500
604
  }
501
- async function Tt(e, t) {
502
- await wt(Pe(e, `${t}/images`)), await wt(Pe(e, `${t}/attachments`));
605
+ async function zt(e, t) {
606
+ await Rt(Fe(e, `${t}/images`)), await Rt(Fe(e, `${t}/attachments`));
503
607
  }
504
- async function Et(e, t) {
608
+ async function Bt(e, t) {
505
609
  let n = [...new Set([...t].filter(Boolean))];
506
- await Promise.all(n.map((t) => je(Pe(e, t)).catch(() => {})));
610
+ await Promise.all(n.map((t) => Me(Fe(e, t)).catch(() => {})));
507
611
  }
508
- var Dt = 450;
509
- async function Ot(e, t) {
510
- let n = await V(t);
511
- for (let t = 0; t < n.docs.length; t += Dt) {
512
- let r = ve(e);
513
- n.docs.slice(t, t + Dt).forEach((e) => r.delete(e.ref)), await r.commit();
612
+ var Vt = 450;
613
+ async function Ht(e, t) {
614
+ let n = await B(t);
615
+ for (let t = 0; t < n.docs.length; t += Vt) {
616
+ let r = ye(e);
617
+ n.docs.slice(t, t + Vt).forEach((e) => r.delete(e.ref)), await r.commit();
514
618
  }
515
619
  }
516
- async function kt(e, t, n) {
517
- await Ot(e, K(a(e, t), Z("postId", "==", n)));
620
+ async function Ut(e, t, n) {
621
+ await Ht(e, G(a(e, t), X("postId", "==", n)));
518
622
  }
519
- async function At(e, t, n) {
520
- await Ot(e, K(a(e, t), Z("commentId", "==", n), Z("target", "==", "comment")));
623
+ async function Wt(e, t, n) {
624
+ await Ht(e, G(a(e, t), X("commentId", "==", n), X("target", "==", "comment")));
521
625
  }
522
- async function jt(e, t, n, r) {
523
- let i = l(e, n, r), a = c(e, n, r), [o, s] = await Promise.all([B(i), B(a)]);
626
+ async function Gt(e, t, n, r) {
627
+ let i = l(e, n, r), a = c(e, n, r), [o, s] = await Promise.all([z(i), z(a)]);
524
628
  if (!o.exists()) return;
525
629
  let d = o.data(), f = s.exists() ? String(s.data().content ?? "") : "";
526
- await Ot(e, K(u(e, n), Z("postId", "==", r))), await kt(e, n, r);
630
+ await Ht(e, G(u(e, n), X("postId", "==", r))), await Ut(e, n, r);
527
631
  let p = /* @__PURE__ */ new Set(), h = /* @__PURE__ */ new Set();
528
632
  for (let e of d.attachments ?? []) {
529
633
  if (!e?.path) continue;
530
634
  p.add(e.path);
531
- let t = Ct(e.path);
635
+ let t = Lt(e.path);
532
636
  t && t !== r && h.add(t);
533
637
  }
534
- for (let e of xt(f)) {
535
- let t = St(e);
638
+ for (let e of Ft(f)) {
639
+ let t = It(e);
536
640
  if (!t) continue;
537
641
  p.add(t);
538
642
  let n = t.replace(/\/images\/([^/]+)$/, "/images/thumbnails/$1").replace(/\.[^.]+$/, ".jpg");
539
643
  n !== t && n.includes("/images/thumbnails/") && p.add(n);
540
644
  let i = t.match(/^(.*\/images\/)([^/]+)\.[^.]+$/);
541
645
  i && !t.includes("/thumbnails/") && p.add(`${i[1]}thumbnails/${i[2]}.jpg`);
542
- let a = Ct(t);
646
+ let a = Lt(t);
543
647
  a && a !== r && h.add(a);
544
648
  }
545
- await de(a), await Tt(t, m(n, r));
546
- for (let e of h) await Tt(t, m(n, e));
547
- await Et(t, p), await de(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);
548
652
  }
549
653
  //#endregion
550
654
  //#region src/utils/postBody.ts
551
- function Mt(e) {
655
+ function Kt(e) {
552
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();
553
657
  }
554
- function Nt(e, t = 40) {
555
- let n = Mt(e);
658
+ function qt(e, t = 40) {
659
+ let n = Kt(e);
556
660
  if (!n) return "";
557
661
  if (n.length <= t) return n;
558
662
  let r = n.slice(0, t), i = r.lastIndexOf(" ");
559
663
  return `${(i >= Math.floor(t * .6) ? r.slice(0, i) : r).trimEnd()}…`;
560
664
  }
561
- function Pt(e) {
562
- return Mt(e).length > 0;
665
+ function Jt(e) {
666
+ return Kt(e).length > 0;
563
667
  }
564
- function Ft(e, t) {
668
+ function Yt(e, t) {
565
669
  return /<img\b/i.test(String(e || "")) ? !0 : (t || []).some((e) => !!e?.type?.startsWith("image/"));
566
670
  }
567
- function It(e) {
568
- return !Pt(e);
671
+ function Xt(e) {
672
+ return !Jt(e);
569
673
  }
570
674
  //#endregion
571
675
  //#region src/utils/postPreview.ts
572
- function Lt(e) {
676
+ function Zt(e) {
573
677
  return e.replace(/&amp;/g, "&").replace(/&quot;/g, "\"").replace(/&#39;/g, "'");
574
678
  }
575
- function Rt(e) {
679
+ function Qt(e) {
576
680
  if (e) try {
577
- 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\./, "");
578
682
  if (n === "youtu.be") return t.pathname.split("/").filter(Boolean)[0];
579
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;
580
684
  } catch {
581
685
  return;
582
686
  }
583
687
  }
584
- function zt(e) {
585
- let t = Rt(e.videoUrl);
688
+ function $t(e) {
689
+ let t = Qt(e.videoUrl);
586
690
  return t ? `https://i.ytimg.com/vi/${t}/hqdefault.jpg` : e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
587
691
  }
588
- function Bt(e, t = []) {
589
- 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);
590
694
  return {
591
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),
592
696
  ...c ? { previewImage: c } : {},
@@ -595,44 +699,303 @@ function Bt(e, t = []) {
595
699
  }
596
700
  //#endregion
597
701
  //#region src/utils/saveBoardVisibility.ts
598
- function Vt(e) {
702
+ function tn(e) {
599
703
  return e === "hidden" ? "hidden" : "public";
600
704
  }
601
- async function Ht(e, t, n, r) {
705
+ async function nn(e, t, n, r) {
602
706
  let i;
603
707
  for (;;) {
604
- let a = await V(K(t, Z("boardId", "==", n), G("__name__"), ...i ? [Y(i)] : [], U(400)));
708
+ let a = await B(G(t, X("boardId", "==", n), W("__name__"), ...i ? [J(i)] : [], H(400)));
605
709
  if (a.empty) break;
606
- let o = ve(e), s = 0;
710
+ let o = ye(e), s = 0;
607
711
  for (let e of a.docs) e.data().listed !== r && (o.update(e.ref, { listed: r }), s++);
608
712
  if (s && await o.commit(), i = a.docs[a.docs.length - 1], a.docs.length < 400) break;
609
713
  }
610
714
  }
611
- async function Ut(e, t, n, r) {
612
- await Ht(e, N(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);
613
717
  }
614
- async function Wt(e, t, n, r) {
615
- let i = o(e, t, n), a = Vt(r.visibility);
616
- await _e(e, async (e) => {
718
+ async function an(e, t, n, r) {
719
+ let i = o(e, t, n), a = tn(r.visibility);
720
+ await ve(e, async (e) => {
617
721
  let t = await e.get(i), n = t.data(), o = n?.visibilityTransition || "";
618
722
  if (o && o !== a) throw Error("Finish the pending visibility change before changing it again.");
619
- 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, {
620
724
  ...r,
621
- visibility: a === "public" ? "public" : Vt(n?.visibility),
725
+ visibility: a === "public" ? "public" : tn(n?.visibility),
622
726
  visibilityTransition: a
623
727
  }, { merge: !0 }), !0);
624
- }) && (await Ut(e, t, n, a !== "hidden"), await _e(e, async (e) => {
728
+ }) && (await rn(e, t, n, a !== "hidden"), await ve(e, async (e) => {
625
729
  if ((await e.get(i)).data()?.visibilityTransition !== a) throw Error("The visibility transition changed. Reload and retry.");
626
730
  e.update(i, {
627
731
  visibility: a,
628
732
  visibilityTransition: "",
629
- updatedAt: q()
733
+ updatedAt: K()
630
734
  });
631
735
  }));
632
736
  }
633
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
634
997
  //#region src/composables/useMemiBoardSettings.ts
635
- var Gt = [
998
+ var On = [
636
999
  {
637
1000
  id: "free",
638
1001
  label: "자유",
@@ -660,17 +1023,17 @@ var Gt = [
660
1023
  visibility: "public",
661
1024
  order: 2
662
1025
  }
663
- ], Kt = Gt;
664
- function qt(e) {
1026
+ ], kn = On;
1027
+ function An(e) {
665
1028
  return e === "hidden" ? "hidden" : "public";
666
1029
  }
667
- function Jt(e, t, n) {
1030
+ function jn(e, t, n) {
668
1031
  return {
669
1032
  id: e,
670
1033
  label: typeof t.label == "string" && t.label.trim() ? t.label.trim() : e,
671
1034
  order: typeof t.order == "number" ? t.order : n,
672
1035
  description: (typeof t.description == "string" ? t.description : "") || "",
673
- visibility: qt(t.visibility),
1036
+ visibility: An(t.visibility),
674
1037
  visibilityTransition: t.visibilityTransition === "hidden" || t.visibilityTransition === "public" ? t.visibilityTransition : void 0,
675
1038
  listView: t.listView ?? "default",
676
1039
  editorType: t.editorType ?? (t.listView === "image" ? "image" : "default"),
@@ -678,93 +1041,138 @@ function Jt(e, t, n) {
678
1041
  commentWriteRole: t.commentWriteRole ?? "user",
679
1042
  allowedStaffUids: Array.isArray(t.allowedStaffUids) ? t.allowedStaffUids : [],
680
1043
  allowedManagerUids: Array.isArray(t.allowedManagerUids) ? t.allowedManagerUids : [],
681
- locale: E(t.locale) ?? ""
1044
+ locale: T(t.locale) ?? "",
1045
+ autoTranslate: t.autoTranslate === !0,
1046
+ i18n: Q(t.i18n),
1047
+ i18nSource: Qe(t.i18nSource)
682
1048
  };
683
1049
  }
684
- function Yt() {
685
- let e = () => M(), t = Q(), n = Se(), { isSignedIn: i, isAdmin: a, isStaff: s, user: c } = ht(), { t: l } = O(), u = P(() => K(y(t, e()), G("order", "asc"))), { data: d, pending: f } = ye(u, { ssrKey: r(e(), "boards") }), p = P(() => d.value.map((e, t) => Jt(e.id, e, t))), m = p, h = P(() => p.value.filter((e) => e.visibility !== "hidden")), g = h;
686
- function _(e) {
687
- if (e) return p.value.find((t) => t.id === e)?.label ?? e;
688
- }
689
- let v = _;
690
- function b(e) {
691
- if (e) return p.value.find((t) => t.id === e)?.description?.trim() || void 0;
1050
+ function Mn() {
1051
+ 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 = _;
1052
+ function b(e, t) {
1053
+ if (!e) return;
1054
+ let n = h.value.find((t) => t.id === e);
1055
+ return n ? rt(n.i18n, n.i18nSource, "label", n.label, t ?? u.value) || n.label : e;
692
1056
  }
693
1057
  let x = b;
694
- function S(e) {
695
- return e ? p.value.find((t) => t.id === e)?.visibility ?? "public" : "public";
1058
+ function S(e, t) {
1059
+ if (!e) return;
1060
+ let n = h.value.find((t) => t.id === e), r = n?.description?.trim();
1061
+ if (n && r) return rt(n.i18n, n.i18nSource, "description", r, t ?? u.value) || void 0;
696
1062
  }
697
1063
  let C = S;
698
1064
  function w(e) {
699
- return S(e) === "hidden";
1065
+ return e ? h.value.find((t) => t.id === e)?.visibility ?? "public" : "public";
1066
+ }
1067
+ let ee = w;
1068
+ function E(e) {
1069
+ return w(e) === "hidden";
700
1070
  }
701
- let T = w;
702
- function D(e) {
703
- if (e) return p.value.find((t) => t.id === e);
1071
+ let O = E;
1072
+ function te(e) {
1073
+ if (e) return h.value.find((t) => t.id === e);
704
1074
  }
705
- it((e) => D(e));
1075
+ vt((e) => te(e));
706
1076
  function k(e) {
707
- return tt(c.value?.uid, a.value, s.value, e ? D(e) : void 0);
1077
+ return ht(c.value?.uid, a.value, s.value, e ? te(e) : void 0);
708
1078
  }
709
- let ee = P(() => p.value.filter((e) => k(e.id)));
710
- function A(e, t) {
1079
+ let A = N(() => h.value.filter((e) => k(e.id)));
1080
+ function re(e, t) {
711
1081
  return {
712
1082
  label: e.label.trim(),
713
1083
  description: (e.description ?? "").trim(),
714
- visibility: qt(e.visibility),
1084
+ visibility: An(e.visibility),
715
1085
  listView: e.listView ?? "default",
716
1086
  editorType: e.editorType ?? (e.listView === "image" ? "image" : "default"),
717
1087
  writeRole: e.writeRole ?? "user",
718
1088
  commentWriteRole: e.commentWriteRole ?? "user",
719
1089
  allowedStaffUids: e.allowedStaffUids ?? [],
720
1090
  allowedManagerUids: e.allowedManagerUids ?? [],
721
- locale: E(e.locale) ?? "",
1091
+ locale: T(e.locale) ?? "",
1092
+ autoTranslate: e.autoTranslate === !0,
1093
+ ...e.i18n ? { i18n: e.i18n } : {},
1094
+ ...e.i18nSource ? { i18nSource: e.i18nSource } : {},
722
1095
  order: t,
723
- updatedAt: q()
1096
+ updatedAt: K()
724
1097
  };
725
1098
  }
726
- async function j(n, r = n.order ?? 0) {
1099
+ async function ie(e) {
1100
+ let t = e.label.trim(), n = (e.description ?? "").trim();
1101
+ if (!t) return e;
1102
+ if (st({
1103
+ ...e,
1104
+ label: t,
1105
+ description: n
1106
+ })) return {
1107
+ ...e,
1108
+ label: t,
1109
+ description: n
1110
+ };
1111
+ let r = u.value, i = { label: t };
1112
+ n && (i.description = n);
1113
+ let a = et(r, i, (await d([{
1114
+ id: e.id || "board",
1115
+ fields: i,
1116
+ context: e.id
1117
+ }], {
1118
+ source: r,
1119
+ purpose: "board name and short description shown in navigation and page headers"
1120
+ }))[e.id || "board"]);
1121
+ if (!tt(a, Object.keys(i))) throw Error(l("settings.autoTranslate.failed"));
1122
+ return {
1123
+ ...e,
1124
+ label: t,
1125
+ description: n,
1126
+ i18n: a,
1127
+ i18nSource: {
1128
+ locale: r,
1129
+ label: t,
1130
+ description: n
1131
+ }
1132
+ };
1133
+ }
1134
+ async function ae(n, r = n.order ?? 0) {
727
1135
  if (!i.value) throw Error(l("boardSettings.signInRequired"));
728
1136
  let a = n.id.trim(), o = n.label.trim();
729
1137
  if (!a || !o) throw Error(l("boardSettings.idAndNameRequired"));
730
- let s = qt(n.visibility), c = A({
1138
+ let s = An(n.visibility), c = re({
731
1139
  ...n,
732
1140
  label: o,
733
1141
  visibility: s
734
1142
  }, r);
735
- await Wt(t, e(), a, c);
1143
+ await an(t, e(), a, c);
736
1144
  }
737
- let ne = j;
738
- async function re(e) {
1145
+ let oe = ae;
1146
+ async function se(e) {
739
1147
  if (!i.value) throw Error(l("boardSettings.signInRequired"));
740
1148
  for (let t of e) if (!t.id.trim() || !t.label.trim()) throw Error(l("boardSettings.idAndNameRequired"));
741
- for (let [t, n] of e.entries()) await j(n, t);
1149
+ for (let [t, n] of e.entries()) await ae(n, t);
742
1150
  }
743
- let ie = re;
744
- async function ae(r) {
1151
+ let ce = se;
1152
+ async function le(r) {
745
1153
  if (!i.value) throw Error(l("boardSettings.signInRequired"));
746
1154
  let a = r.trim();
747
1155
  if (!a) throw Error(l("boardSettings.deleteIdRequired"));
748
1156
  for (;;) {
749
- let r = await V(K(N(t, e()), Z("boardId", "==", a), U(25)));
1157
+ let r = await B(G(M(t, e()), X("boardId", "==", a), H(25)));
750
1158
  if (r.empty) break;
751
- for (let i of r.docs) await jt(t, Me(n), e(), i.id);
1159
+ for (let i of r.docs) await Gt(t, Ne(n), e(), i.id);
752
1160
  }
753
- await de(o(t, e(), r));
1161
+ await fe(o(t, e(), r));
754
1162
  }
755
- let oe = ae;
756
- async function se() {
1163
+ let ue = le;
1164
+ async function P() {
757
1165
  return Promise.resolve();
758
1166
  }
759
- async function ce(e) {
1167
+ async function de(e) {
760
1168
  let t = e.trim();
761
1169
  if (!t) throw Error(l("boardSettings.nameRequired"));
762
1170
  if (!i.value) throw Error(l("boardSettings.signInRequired"));
763
- let n = p.value.find((e) => e.label === t);
1171
+ let n = h.value.find((e) => e.label === t);
764
1172
  if (n) return n.id;
765
- let r = te(t) || `board-${Date.now()}`, a = r, o = 2, s = new Set(p.value.map((e) => e.id));
1173
+ let r = ne(t) || `board-${Date.now()}`, a = r, o = 2, s = new Set(h.value.map((e) => e.id));
766
1174
  for (; s.has(a);) a = `${r}-${o++}`;
767
- return await j({
1175
+ return await ae({
768
1176
  id: a,
769
1177
  label: t,
770
1178
  visibility: "public",
@@ -772,62 +1180,74 @@ function Yt() {
772
1180
  editorType: "default",
773
1181
  writeRole: "user",
774
1182
  commentWriteRole: "user",
775
- order: p.value.length
1183
+ order: h.value.length
776
1184
  }), a;
777
1185
  }
778
1186
  return {
779
- boards: p,
780
- publicBoards: h,
781
- boardLabel: _,
782
- boardDescription: b,
783
- boardVisibility: S,
784
- isBoardHidden: w,
785
- getBoard: D,
1187
+ boards: h,
1188
+ publicBoards: _,
1189
+ boardLabel: b,
1190
+ boardDescription: S,
1191
+ boardVisibility: w,
1192
+ isBoardHidden: E,
1193
+ getBoard: te,
786
1194
  canManageBoard: k,
787
- managedBoards: ee,
788
- categories: m,
789
- publicCategories: g,
790
- categoryLabel: v,
791
- categoryDescription: x,
792
- categoryVisibility: C,
793
- isCategoryHidden: T,
794
- settingsPending: f,
795
- ensureSettings: se,
796
- addBoard: ce,
797
- addCategory: ce,
798
- saveBoard: j,
799
- saveCategory: ne,
800
- saveBoards: re,
801
- saveCategories: ie,
802
- deleteBoard: ae,
803
- deleteCategory: oe,
804
- DEFAULT_BOARDS: Gt,
805
- DEFAULT_CATEGORIES: Kt
1195
+ managedBoards: A,
1196
+ categories: g,
1197
+ publicCategories: v,
1198
+ categoryLabel: x,
1199
+ categoryDescription: C,
1200
+ categoryVisibility: ee,
1201
+ isCategoryHidden: O,
1202
+ settingsPending: m,
1203
+ ensureSettings: P,
1204
+ addBoard: de,
1205
+ addCategory: de,
1206
+ saveBoard: ae,
1207
+ saveCategory: oe,
1208
+ translateBoard: ie,
1209
+ saveBoards: se,
1210
+ saveCategories: ce,
1211
+ deleteBoard: le,
1212
+ deleteCategory: ue,
1213
+ DEFAULT_BOARDS: On,
1214
+ DEFAULT_CATEGORIES: kn
806
1215
  };
807
1216
  }
808
1217
  //#endregion
809
1218
  //#region src/composables/useMemiBoardPosts.ts
810
- var Xt = /* @__PURE__ */ new Map();
811
- function Zt(e) {
1219
+ var Nn = /* @__PURE__ */ new Map();
1220
+ function Pn(e) {
812
1221
  if (!e || typeof e != "object") return !1;
813
1222
  let t = "code" in e ? String(e.code) : "";
814
1223
  return t === "permission-denied" || t === "firestore/permission-denied";
815
1224
  }
816
- function Qt(e, t) {
817
- let n = L(e)?.trim();
1225
+ function Fn(e, t) {
1226
+ let n = I(e)?.trim();
818
1227
  if (!n) throw Error(w(t, "posts.boardIdRequired"));
819
1228
  return n;
820
1229
  }
821
- function $t(e) {
822
- let t = Q(), n = Se(), { isBoardHidden: r, getBoard: i } = Yt(), { user: a, canManageBoard: o } = ht(), { t: s, locale: u } = O(), d = () => M(), f = () => Qt(e, u.value), p = () => N(t, d()), m = (e) => l(t, d(), e), h = (e) => c(t, d(), e);
823
- function g() {
824
- return !r(f());
1230
+ function In(e) {
1231
+ 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);
1232
+ function g(e, t, n) {
1233
+ return {
1234
+ id: e,
1235
+ ...t,
1236
+ i18n: Q(t.i18n),
1237
+ i18nSource: Qe(t.i18nSource),
1238
+ content: typeof n?.content == "string" ? n.content : "",
1239
+ contentI18n: Q(n?.i18n),
1240
+ contentI18nSource: $e(n?.i18nSource)
1241
+ };
825
1242
  }
826
1243
  function _() {
1244
+ return !r(f());
1245
+ }
1246
+ function v() {
827
1247
  return i(f())?.editorType === "image";
828
1248
  }
829
- function v(e) {
830
- let t = _(), n = Pt(e.content), r = Ft(e.content, e.attachments);
1249
+ function y(e) {
1250
+ let t = v(), n = Jt(e.content), r = Yt(e.content, e.attachments);
831
1251
  if (t) {
832
1252
  if (!r) throw Error(s("posts.photoRequired"));
833
1253
  return;
@@ -835,16 +1255,16 @@ function $t(e) {
835
1255
  if (!n && !r) throw Error(s("posts.bodyRequired"));
836
1256
  if (!e.title?.trim()) throw Error(s("posts.titleRequired"));
837
1257
  }
838
- function y() {
839
- return pe(p()).id;
1258
+ function b() {
1259
+ return me(p()).id;
840
1260
  }
841
- function b(e) {
842
- return bt(t, d(), f(), e, a.value?.uid, o(f()));
1261
+ function x(e) {
1262
+ return Pt(t, d(), f(), e, a.value?.uid, o(f()));
843
1263
  }
844
- async function x(e = {}) {
845
- let t = e.pageSize ?? 20, n = [Z("boardId", "==", f()), Z("isPublished", "==", !0)];
846
- e.publicOnly !== !1 && n.push(Z("listed", "==", !0)), n.push(G("createdAt", "desc")), e.cursor && n.push(Y(e.cursor)), n.push(U(t + 1));
847
- let r = await V(K(p(), ...n)), i = r.docs.slice(0, t);
1264
+ async function S(e = {}) {
1265
+ let t = e.pageSize ?? 20, n = [X("boardId", "==", f()), X("isPublished", "==", !0)];
1266
+ 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));
1267
+ let r = await B(G(p(), ...n)), i = r.docs.slice(0, t);
848
1268
  return {
849
1269
  posts: i.map((e) => ({
850
1270
  id: e.id,
@@ -854,58 +1274,54 @@ function $t(e) {
854
1274
  hasMore: r.docs.length > t
855
1275
  };
856
1276
  }
857
- async function S(e) {
858
- let [t, n] = await Promise.all([B(m(e)), B(h(e))]);
859
- return t.exists() ? {
860
- id: t.id,
861
- ...t.data(),
862
- content: n.exists() ? n.data().content ?? "" : ""
863
- } : null;
864
- }
865
1277
  async function C(e) {
1278
+ let [t, n] = await Promise.all([z(m(e)), z(h(e))]);
1279
+ return t.exists() ? g(t.id, t.data(), n.exists() ? n.data() : void 0) : null;
1280
+ }
1281
+ async function w(e) {
866
1282
  try {
867
- let t = await b(e.trim());
1283
+ let t = await x(e.trim());
868
1284
  if (!t) return { status: "not-found" };
869
1285
  try {
870
- let e = await S(t.id);
1286
+ let e = await C(t.id);
871
1287
  return e ? {
872
1288
  status: "ok",
873
1289
  post: e
874
1290
  } : { status: "not-found" };
875
1291
  } catch (e) {
876
- return Zt(e) ? { status: "permission-denied" } : {
1292
+ return Pn(e) ? { status: "permission-denied" } : {
877
1293
  status: "error",
878
1294
  message: e instanceof Error ? e.message : String(e)
879
1295
  };
880
1296
  }
881
1297
  } catch (e) {
882
- return Zt(e) ? { status: "permission-denied" } : {
1298
+ return Pn(e) ? { status: "permission-denied" } : {
883
1299
  status: "error",
884
1300
  message: e instanceof Error ? e.message : String(e)
885
1301
  };
886
1302
  }
887
1303
  }
888
- async function w(e) {
889
- let t = e.trim(), n = Xt.get(`${f()}/${t}`);
1304
+ async function ee(e) {
1305
+ let t = e.trim(), n = Nn.get(`${f()}/${t}`);
890
1306
  if (n) return {
891
1307
  status: "ok",
892
1308
  id: n
893
1309
  };
894
1310
  try {
895
- let e = await b(t);
1311
+ let e = await x(t);
896
1312
  return e ? {
897
1313
  status: "ok",
898
1314
  id: e.id
899
1315
  } : { status: r(f()) ? "permission-denied" : "not-found" };
900
1316
  } catch (e) {
901
- return Zt(e) ? { status: "permission-denied" } : {
1317
+ return Pn(e) ? { status: "permission-denied" } : {
902
1318
  status: "error",
903
1319
  message: e instanceof Error ? e.message : String(e)
904
1320
  };
905
1321
  }
906
1322
  }
907
1323
  async function T(e) {
908
- return `${te(e.trim()) || "post"}-${y()}`;
1324
+ return `${ne(e.trim()) || "post"}-${b()}`;
909
1325
  }
910
1326
  async function E(e) {
911
1327
  if (!e.createdAt) return {
@@ -914,11 +1330,11 @@ function $t(e) {
914
1330
  };
915
1331
  try {
916
1332
  let t = [
917
- Z("boardId", "==", f()),
918
- Z("isPublished", "==", !0),
919
- ...e.listed === !1 ? [] : [Z("listed", "==", !0)],
920
- G("createdAt", "desc")
921
- ], [n, r] = await Promise.all([V(K(p(), ...t, Y(e.createdAt), U(1))), V(K(p(), ...t, he(e.createdAt), ge(1)))]), i = (e) => {
1333
+ X("boardId", "==", f()),
1334
+ X("isPublished", "==", !0),
1335
+ ...e.listed === !1 ? [] : [X("listed", "==", !0)],
1336
+ W("createdAt", "desc")
1337
+ ], [n, r] = await Promise.all([B(G(p(), ...t, J(e.createdAt), H(1))), B(G(p(), ...t, ge(e.createdAt), _e(1)))]), i = (e) => {
922
1338
  let t = e.docs[0];
923
1339
  return t ? {
924
1340
  id: t.id,
@@ -936,11 +1352,11 @@ function $t(e) {
936
1352
  };
937
1353
  }
938
1354
  }
939
- async function D(e) {
940
- v(e);
941
- let n = e.postId || y(), r = _(), i, a;
942
- r ? (i = n, a = Nt(e.content)) : (a = e.title.trim(), i = e.slug?.trim() || await T(a));
943
- let o = Bt(e.content, e.attachments), s = g(), c = ve(t);
1355
+ async function O(e) {
1356
+ y(e);
1357
+ let n = e.postId || b(), r = v(), i, a;
1358
+ r ? (i = n, a = qt(e.content)) : (a = e.title.trim(), i = e.slug?.trim() || await T(a));
1359
+ let o = en(e.content, e.attachments), s = _(), c = ye(t);
944
1360
  return c.set(m(n), {
945
1361
  boardId: f(),
946
1362
  slug: i,
@@ -958,56 +1374,93 @@ function $t(e) {
958
1374
  authorPhoto: e.authorPhoto,
959
1375
  moderationStatus: "approved",
960
1376
  moderationModel: e.moderationModel ?? null,
961
- createdAt: q(),
962
- updatedAt: q()
963
- }), c.set(h(n), { content: e.content }), await c.commit(), Xt.set(`${f()}/${i}`, n), i;
1377
+ createdAt: K(),
1378
+ updatedAt: K(),
1379
+ ...e.translations ? {
1380
+ i18n: e.translations.i18n,
1381
+ i18nSource: e.translations.i18nSource
1382
+ } : {}
1383
+ }), c.set(h(n), {
1384
+ content: e.content,
1385
+ ...e.translations?.contentI18n ? {
1386
+ i18n: e.translations.contentI18n,
1387
+ i18nSource: e.translations.contentI18nSource
1388
+ } : {}
1389
+ }), await c.commit(), Nn.set(`${f()}/${i}`, n), i;
964
1390
  }
965
- async function k(e, t) {
966
- v(t);
967
- let n = _() ? Nt(t.content) : t.title.trim(), r = Bt(t.content, t.attachments), i = g();
968
- await Promise.all([X(m(e), {
1391
+ async function te(e, t) {
1392
+ y(t);
1393
+ let n = v() ? qt(t.content) : t.title.trim(), r = en(t.content, t.attachments), i = _();
1394
+ await Promise.all([Y(m(e), {
969
1395
  title: n,
970
1396
  ...r,
971
- previewImage: r.previewImage ? r.previewImage : fe(),
972
- videoUrl: r.videoUrl ? r.videoUrl : fe(),
1397
+ previewImage: r.previewImage ? r.previewImage : pe(),
1398
+ videoUrl: r.videoUrl ? r.videoUrl : pe(),
973
1399
  tags: t.tags ?? [],
974
1400
  listed: i,
975
1401
  attachments: t.attachments ?? [],
976
- updatedAt: q()
977
- }), J(h(e), { content: t.content }, { merge: !0 })]);
1402
+ updatedAt: K(),
1403
+ ...t.translations ? {
1404
+ i18n: t.translations.i18n,
1405
+ i18nSource: t.translations.i18nSource
1406
+ } : {}
1407
+ }), q(h(e), {
1408
+ content: t.content,
1409
+ ...t.translations ? t.translations.contentI18n ? {
1410
+ i18n: t.translations.contentI18n,
1411
+ i18nSource: t.translations.contentI18nSource
1412
+ } : {
1413
+ i18n: pe(),
1414
+ i18nSource: pe()
1415
+ } : {}
1416
+ }, { merge: !0 })]);
978
1417
  }
979
- async function ee(e) {
980
- await X(m(e), {
1418
+ async function k(e, t) {
1419
+ await Promise.all([Y(m(e), {
1420
+ i18n: t.i18n,
1421
+ i18nSource: t.i18nSource,
1422
+ updatedAt: K()
1423
+ }), q(h(e), t.contentI18n ? {
1424
+ i18n: t.contentI18n,
1425
+ i18nSource: t.contentI18nSource
1426
+ } : {
1427
+ i18n: pe(),
1428
+ i18nSource: pe()
1429
+ }, { merge: !0 })]);
1430
+ }
1431
+ async function A(e) {
1432
+ await Y(m(e), {
981
1433
  isPublished: !0,
982
- publishedAt: q(),
983
- updatedAt: q()
1434
+ publishedAt: K(),
1435
+ updatedAt: K()
984
1436
  });
985
1437
  }
986
- async function A(e) {
987
- await jt(t, Me(n), d(), e);
988
- for (let [t, n] of Xt) n === e && Xt.delete(t);
1438
+ async function re(e) {
1439
+ await Gt(t, Ne(n), d(), e);
1440
+ for (let [t, n] of Nn) n === e && Nn.delete(t);
989
1441
  }
990
1442
  return {
991
- createPostId: y,
992
- getPosts: x,
993
- getPost: S,
994
- getPostBySlug: C,
995
- resolvePostIdBySlug: w,
1443
+ createPostId: b,
1444
+ getPosts: S,
1445
+ getPost: C,
1446
+ getPostBySlug: w,
1447
+ resolvePostIdBySlug: ee,
996
1448
  resolveUniqueSlug: T,
997
1449
  getAdjacentPosts: E,
998
- createPost: D,
999
- updatePost: k,
1000
- publishPost: ee,
1001
- deletePost: A
1450
+ createPost: O,
1451
+ updatePost: te,
1452
+ savePostTranslations: k,
1453
+ publishPost: A,
1454
+ deletePost: re
1002
1455
  };
1003
1456
  }
1004
- var en = 10;
1005
- function tn(e, t = {}) {
1006
- let n = Q(), { isAdmin: r, user: i, canManageBoard: a } = ht(), o = () => M(), s = t.pageSize ?? en, c = P(() => i.value?.uid ?? ""), l = P(() => Qt(e)), u = () => N(n, o()), d = P(() => t.publicOnly === void 0 ? !(r.value || a(l.value)) : typeof t.publicOnly == "boolean" ? t.publicOnly : t.publicOnly.value), f = I([]), p = I([]), m = I([]), h = I(!0), g = I(!0), _ = I(!1), v = I(""), y = null, b = null, x, S;
1457
+ var Ln = 10;
1458
+ function Rn(e, t = {}) {
1459
+ 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;
1007
1460
  function C(e) {
1008
1461
  return e.createdAt?.toMillis?.() ?? 0;
1009
1462
  }
1010
- let w = P(() => {
1463
+ let w = N(() => {
1011
1464
  let e = /* @__PURE__ */ new Map();
1012
1465
  for (let t of [
1013
1466
  ...p.value,
@@ -1016,12 +1469,12 @@ function tn(e, t = {}) {
1016
1469
  ]) t.id && e.set(t.id, t);
1017
1470
  return [...e.values()].sort((e, t) => C(t) - C(e) || String(t.id).localeCompare(String(e.id)));
1018
1471
  });
1019
- function T() {
1020
- let e = [Z("boardId", "==", l.value), Z("isPublished", "==", !0)];
1021
- return d.value && e.push(Z("listed", "==", !0)), e;
1472
+ function ee() {
1473
+ let e = [X("boardId", "==", l.value), X("isPublished", "==", !0)];
1474
+ return d.value && e.push(X("listed", "==", !0)), e;
1022
1475
  }
1023
- function E() {
1024
- x?.(), x = W(K(u(), ...T(), G("createdAt", "desc"), G(z(), "desc"), U(s)), (e) => {
1476
+ function T() {
1477
+ x?.(), x = U(G(u(), ...ee(), W("createdAt", "desc"), W(R(), "desc"), H(s)), (e) => {
1025
1478
  f.value = e.docs.map((e) => ({
1026
1479
  id: e.id,
1027
1480
  ...e.data()
@@ -1030,14 +1483,14 @@ function tn(e, t = {}) {
1030
1483
  console.error("[memi-board:postList] onSnapshot failed", e), v.value = e.message || String(e), h.value = !1;
1031
1484
  });
1032
1485
  }
1033
- function D() {
1486
+ function E() {
1034
1487
  S?.();
1035
1488
  let e = c.value;
1036
1489
  if (!e) {
1037
1490
  m.value = [];
1038
1491
  return;
1039
1492
  }
1040
- S = W(K(u(), Z("boardId", "==", l.value), Z("authorUid", "==", e), Z("isPublished", "==", !1), G("createdAt", "desc"), U(20)), (e) => {
1493
+ S = U(G(u(), X("boardId", "==", l.value), X("authorUid", "==", e), X("isPublished", "==", !1), W("createdAt", "desc"), H(20)), (e) => {
1041
1494
  m.value = e.docs.map((e) => ({
1042
1495
  id: e.id,
1043
1496
  ...e.data()
@@ -1046,17 +1499,17 @@ function tn(e, t = {}) {
1046
1499
  console.error("[memi-board:postList] own draft onSnapshot failed", e);
1047
1500
  });
1048
1501
  }
1049
- async function O() {
1502
+ async function D() {
1050
1503
  if (!(_.value || !g.value || h.value)) {
1051
1504
  _.value = !0, v.value = "";
1052
1505
  try {
1053
1506
  let e = b ?? y, t = [
1054
- ...T(),
1055
- G("createdAt", "desc"),
1056
- G(z(), "desc"),
1057
- ...e ? [Y(e)] : [],
1058
- U(s + 1)
1059
- ], n = await V(K(u(), ...t)), r = n.docs.slice(0, s), i = r.map((e) => ({
1507
+ ...ee(),
1508
+ W("createdAt", "desc"),
1509
+ W(R(), "desc"),
1510
+ ...e ? [J(e)] : [],
1511
+ H(s + 1)
1512
+ ], n = await B(G(u(), ...t)), r = n.docs.slice(0, s), i = r.map((e) => ({
1060
1513
  id: e.id,
1061
1514
  ...e.data()
1062
1515
  })), a = new Set([...p.value, ...f.value].map((e) => e.id));
@@ -1068,14 +1521,14 @@ function tn(e, t = {}) {
1068
1521
  }
1069
1522
  }
1070
1523
  }
1071
- function k() {
1072
- x?.(), f.value = [], p.value = [], y = null, b = null, g.value = !0, v.value = "", h.value = !0, E(), D();
1524
+ function O() {
1525
+ x?.(), f.value = [], p.value = [], y = null, b = null, g.value = !0, v.value = "", h.value = !0, T(), E();
1073
1526
  }
1074
- return R([
1527
+ return L([
1075
1528
  l,
1076
1529
  d,
1077
1530
  c
1078
- ], k, { immediate: !0 }), F(() => {
1531
+ ], O, { immediate: !0 }), P(() => {
1079
1532
  x?.(), S?.();
1080
1533
  }), {
1081
1534
  posts: w,
@@ -1083,25 +1536,33 @@ function tn(e, t = {}) {
1083
1536
  hasMore: g,
1084
1537
  loadingMore: _,
1085
1538
  loadError: v,
1086
- loadMore: O
1539
+ loadMore: D
1087
1540
  };
1088
1541
  }
1089
- function nn(e, t) {
1090
- let n = Q(), r = () => M(), i = P(() => Qt(e)), a = P(() => typeof t == "string" ? t : t.value), o = P(() => l(n, r(), a.value)), s = P(() => c(n, r(), a.value)), { data: u, pending: d } = xe(o), { data: f, pending: p } = xe(s);
1542
+ function zn(e, t) {
1543
+ 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);
1091
1544
  return {
1092
- post: P(() => !u.value || u.value.boardId !== i.value ? null : {
1093
- ...u.value,
1094
- id: a.value,
1095
- content: f.value?.content ?? ""
1545
+ post: N(() => {
1546
+ if (!u.value || u.value.boardId !== i.value) return null;
1547
+ let e = f.value;
1548
+ return {
1549
+ ...u.value,
1550
+ id: a.value,
1551
+ i18n: Q(u.value.i18n),
1552
+ i18nSource: Qe(u.value.i18nSource),
1553
+ content: f.value?.content ?? "",
1554
+ contentI18n: Q(e?.i18n),
1555
+ contentI18nSource: $e(e?.i18nSource)
1556
+ };
1096
1557
  }),
1097
- pending: P(() => d.value || p.value)
1558
+ pending: N(() => d.value || p.value)
1098
1559
  };
1099
1560
  }
1100
1561
  //#endregion
1101
1562
  //#region src/composables/useMemiBoardUserPosts.ts
1102
- var rn = 10;
1103
- function an(e, t = {}) {
1104
- let n = Q(), r = () => M(), i = () => N(n, r()), a = t.pageSize ?? rn, o = P(() => L(e)?.trim() || ""), s = I([]), c = I([]), l = I(!0), u = I(!0), d = I(!1), f = I(""), p = null, m = null, h;
1563
+ var Bn = 10;
1564
+ function Vn(e, t = {}) {
1565
+ 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;
1105
1566
  function g(e) {
1106
1567
  return {
1107
1568
  id: e.id,
@@ -1111,16 +1572,16 @@ function an(e, t = {}) {
1111
1572
  function _(e) {
1112
1573
  return e.createdAt?.toMillis?.() ?? 0;
1113
1574
  }
1114
- let v = P(() => {
1575
+ let v = N(() => {
1115
1576
  let e = /* @__PURE__ */ new Map();
1116
1577
  for (let t of [...c.value, ...s.value]) t.id && e.set(t.id, t);
1117
1578
  return [...e.values()].sort((e, t) => _(t) - _(e) || String(t.id).localeCompare(String(e.id)));
1118
1579
  });
1119
1580
  function y() {
1120
1581
  return [
1121
- Z("authorUid", "==", o.value),
1122
- Z("isPublished", "==", !0),
1123
- Z("listed", "==", !0)
1582
+ X("authorUid", "==", o.value),
1583
+ X("isPublished", "==", !0),
1584
+ X("listed", "==", !0)
1124
1585
  ];
1125
1586
  }
1126
1587
  function b() {
@@ -1128,7 +1589,7 @@ function an(e, t = {}) {
1128
1589
  l.value = !1;
1129
1590
  return;
1130
1591
  }
1131
- h = W(K(i(), ...y(), G("createdAt", "desc"), G(z(), "desc"), U(a)), (e) => {
1592
+ h = U(G(i(), ...y(), W("createdAt", "desc"), W(R(), "desc"), H(a)), (e) => {
1132
1593
  s.value = e.docs.map(g), p = e.docs.at(-1) ?? null, m || (u.value = e.docs.length >= a), l.value = !1;
1133
1594
  }, (e) => {
1134
1595
  console.error("[memi-board:userPosts] onSnapshot failed", e), f.value = e.message || String(e), l.value = !1;
@@ -1140,11 +1601,11 @@ function an(e, t = {}) {
1140
1601
  try {
1141
1602
  let e = m ?? p, t = [
1142
1603
  ...y(),
1143
- G("createdAt", "desc"),
1144
- G(z(), "desc"),
1145
- ...e ? [Y(e)] : [],
1146
- U(a + 1)
1147
- ], n = await V(K(i(), ...t)), r = n.docs.slice(0, a), o = r.map(g), l = new Set([...c.value, ...s.value].map((e) => e.id));
1604
+ W("createdAt", "desc"),
1605
+ W(R(), "desc"),
1606
+ ...e ? [J(e)] : [],
1607
+ H(a + 1)
1608
+ ], n = await B(G(i(), ...t)), r = n.docs.slice(0, a), o = r.map(g), l = new Set([...c.value, ...s.value].map((e) => e.id));
1148
1609
  c.value.push(...o.filter((e) => !l.has(e.id))), m = r.at(-1) ?? m, u.value = n.docs.length > a;
1149
1610
  } catch (e) {
1150
1611
  f.value = e.message || String(e), console.error("[memi-board:userPosts] loadMore failed", e);
@@ -1156,7 +1617,7 @@ function an(e, t = {}) {
1156
1617
  function S() {
1157
1618
  h?.(), s.value = [], c.value = [], p = null, m = null, u.value = !0, f.value = "", l.value = !0, b();
1158
1619
  }
1159
- return R(o, S, { immediate: !0 }), F(() => h?.()), {
1620
+ return L(o, S, { immediate: !0 }), P(() => h?.()), {
1160
1621
  posts: v,
1161
1622
  postsPending: l,
1162
1623
  hasMore: u,
@@ -1167,9 +1628,9 @@ function an(e, t = {}) {
1167
1628
  }
1168
1629
  //#endregion
1169
1630
  //#region src/composables/useMemiBoardUserComments.ts
1170
- var on = 10;
1171
- function sn(e, t = {}) {
1172
- let n = Q(), r = () => M(), i = () => u(n, r()), a = t.pageSize ?? on, o = P(() => L(e)?.trim() || ""), s = I([]), c = I([]), l = I(!0), d = I(!0), f = I(!1), p = I(""), m = null, h = null, g;
1631
+ var Hn = 10;
1632
+ function Un(e, t = {}) {
1633
+ 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;
1173
1634
  function _(e) {
1174
1635
  return {
1175
1636
  id: e.id,
@@ -1179,20 +1640,20 @@ function sn(e, t = {}) {
1179
1640
  function v(e) {
1180
1641
  return e.createdAt?.toMillis?.() ?? 0;
1181
1642
  }
1182
- let y = P(() => {
1643
+ let y = N(() => {
1183
1644
  let e = /* @__PURE__ */ new Map();
1184
1645
  for (let t of [...c.value, ...s.value]) t.id && e.set(t.id, t);
1185
1646
  return [...e.values()].sort((e, t) => v(t) - v(e) || String(t.id).localeCompare(String(e.id)));
1186
1647
  });
1187
1648
  function b() {
1188
- return [Z("authorUid", "==", o.value), Z("listed", "==", !0)];
1649
+ return [X("authorUid", "==", o.value), X("listed", "==", !0)];
1189
1650
  }
1190
1651
  function x() {
1191
1652
  if (g?.(), !o.value) {
1192
1653
  l.value = !1;
1193
1654
  return;
1194
1655
  }
1195
- g = W(K(i(), ...b(), G("createdAt", "desc"), G(z(), "desc"), U(a)), (e) => {
1656
+ g = U(G(i(), ...b(), W("createdAt", "desc"), W(R(), "desc"), H(a)), (e) => {
1196
1657
  s.value = e.docs.map(_), m = e.docs.at(-1) ?? null, h || (d.value = e.docs.length >= a), l.value = !1;
1197
1658
  }, (e) => {
1198
1659
  console.error("[memi-board:userComments] onSnapshot failed", e), p.value = e.message || String(e), l.value = !1;
@@ -1204,11 +1665,11 @@ function sn(e, t = {}) {
1204
1665
  try {
1205
1666
  let e = h ?? m, t = [
1206
1667
  ...b(),
1207
- G("createdAt", "desc"),
1208
- G(z(), "desc"),
1209
- ...e ? [Y(e)] : [],
1210
- U(a + 1)
1211
- ], n = await V(K(i(), ...t)), r = n.docs.slice(0, a), o = r.map(_), l = new Set([...c.value, ...s.value].map((e) => e.id));
1668
+ W("createdAt", "desc"),
1669
+ W(R(), "desc"),
1670
+ ...e ? [J(e)] : [],
1671
+ H(a + 1)
1672
+ ], n = await B(G(i(), ...t)), r = n.docs.slice(0, a), o = r.map(_), l = new Set([...c.value, ...s.value].map((e) => e.id));
1212
1673
  c.value.push(...o.filter((e) => !l.has(e.id))), h = r.at(-1) ?? h, d.value = n.docs.length > a;
1213
1674
  } catch (e) {
1214
1675
  p.value = e.message || String(e), console.error("[memi-board:userComments] loadMore failed", e);
@@ -1220,7 +1681,7 @@ function sn(e, t = {}) {
1220
1681
  function C() {
1221
1682
  g?.(), s.value = [], c.value = [], m = null, h = null, d.value = !0, p.value = "", l.value = !0, x();
1222
1683
  }
1223
- return R(o, C, { immediate: !0 }), F(() => g?.()), {
1684
+ return L(o, C, { immediate: !0 }), P(() => g?.()), {
1224
1685
  comments: y,
1225
1686
  commentsPending: l,
1226
1687
  hasMore: d,
@@ -1231,12 +1692,12 @@ function sn(e, t = {}) {
1231
1692
  }
1232
1693
  //#endregion
1233
1694
  //#region src/composables/useMemiBoardCommentFeed.ts
1234
- var cn = 10;
1235
- function ln(e = "latest", t = {}) {
1236
- let n = Q(), r = () => M(), i = () => u(n, r()), a = P(() => {
1237
- let e = Math.floor(Number(L(t.pageSize)));
1238
- return Number.isFinite(e) && e > 0 ? e : cn;
1239
- }), o = P(() => L(e) === "likes" ? "likes" : "latest"), s = I([]), c = I([]), l = I(!0), d = I(!0), f = I(!1), p = I(""), m = null, h = null, g;
1695
+ var Wn = 10;
1696
+ function Gn(e = "latest", t = {}) {
1697
+ let n = Z(), r = () => j(), i = () => u(n, r()), a = N(() => {
1698
+ let e = Math.floor(Number(I(t.pageSize)));
1699
+ return Number.isFinite(e) && e > 0 ? e : Wn;
1700
+ }), 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;
1240
1701
  function _(e) {
1241
1702
  return {
1242
1703
  id: e.id,
@@ -1253,25 +1714,25 @@ function ln(e = "latest", t = {}) {
1253
1714
  }
1254
1715
  return v(t) - v(e) || String(t.id).localeCompare(String(e.id));
1255
1716
  }
1256
- let b = P(() => {
1717
+ let b = N(() => {
1257
1718
  let e = /* @__PURE__ */ new Map();
1258
1719
  for (let t of [...c.value, ...s.value]) t.id && e.set(t.id, t);
1259
1720
  return [...e.values()].sort(y);
1260
1721
  });
1261
1722
  function x() {
1262
1723
  return o.value === "likes" ? [
1263
- Z("listed", "==", !0),
1264
- G("likeCount", "desc"),
1265
- G("createdAt", "desc"),
1266
- G(z(), "desc")
1724
+ X("listed", "==", !0),
1725
+ W("likeCount", "desc"),
1726
+ W("createdAt", "desc"),
1727
+ W(R(), "desc")
1267
1728
  ] : [
1268
- Z("listed", "==", !0),
1269
- G("createdAt", "desc"),
1270
- G(z(), "desc")
1729
+ X("listed", "==", !0),
1730
+ W("createdAt", "desc"),
1731
+ W(R(), "desc")
1271
1732
  ];
1272
1733
  }
1273
1734
  function S() {
1274
- g?.(), g = W(K(i(), ...x(), U(a.value)), (e) => {
1735
+ g?.(), g = U(G(i(), ...x(), H(a.value)), (e) => {
1275
1736
  s.value = e.docs.map(_), m = e.docs.at(-1) ?? null, h || (d.value = e.docs.length >= a.value), l.value = !1;
1276
1737
  }, (e) => {
1277
1738
  console.error("[memi-board:commentFeed] onSnapshot failed", e), p.value = e.message || String(e), l.value = !1;
@@ -1281,7 +1742,7 @@ function ln(e = "latest", t = {}) {
1281
1742
  if (!(f.value || !d.value || l.value)) {
1282
1743
  f.value = !0, p.value = "";
1283
1744
  try {
1284
- let e = h ?? m, t = await V(K(i(), ...x(), ...e ? [Y(e)] : [], U(a.value + 1))), n = t.docs.slice(0, a.value), r = n.map(_), o = new Set([...c.value, ...s.value].map((e) => e.id));
1745
+ let e = h ?? m, t = await B(G(i(), ...x(), ...e ? [J(e)] : [], H(a.value + 1))), n = t.docs.slice(0, a.value), r = n.map(_), o = new Set([...c.value, ...s.value].map((e) => e.id));
1285
1746
  c.value.push(...r.filter((e) => !o.has(e.id))), h = n.at(-1) ?? h, d.value = t.docs.length > a.value;
1286
1747
  } catch (e) {
1287
1748
  p.value = e.message || String(e), console.error("[memi-board:commentFeed] loadMore failed", e);
@@ -1293,7 +1754,7 @@ function ln(e = "latest", t = {}) {
1293
1754
  function w() {
1294
1755
  g?.(), s.value = [], c.value = [], m = null, h = null, d.value = !0, p.value = "", l.value = !0, S();
1295
1756
  }
1296
- return R([o, a], w, { immediate: !0 }), F(() => g?.()), {
1757
+ return L([o, a], w, { immediate: !0 }), P(() => g?.()), {
1297
1758
  comments: b,
1298
1759
  commentsPending: l,
1299
1760
  hasMore: d,
@@ -1305,35 +1766,35 @@ function ln(e = "latest", t = {}) {
1305
1766
  }
1306
1767
  //#endregion
1307
1768
  //#region src/composables/useMemiBoardAuthorMemo.ts
1308
- var un = 100, dn = /* @__PURE__ */ new Map();
1769
+ var Kn = 100, qn = /* @__PURE__ */ new Map();
1309
1770
  function $(e) {
1310
- let t = dn.get(e);
1311
- return t || (t = ue({
1771
+ let t = qn.get(e);
1772
+ return t || (t = de({
1312
1773
  text: "",
1313
1774
  sentiment: null,
1314
1775
  loaded: !1,
1315
1776
  pending: !1,
1316
1777
  saving: !1,
1317
1778
  error: ""
1318
- }), dn.set(e, t)), t;
1779
+ }), qn.set(e, t)), t;
1319
1780
  }
1320
- function fn(e) {
1321
- let t = Q(), n = () => M(), { user: r } = ht(), { t: a, formatNumber: o } = O(), s = P(() => L(e)), c = P(() => `${r.value?.uid ?? ""}:${s.value}`), l = P(() => {
1781
+ function Jn(e) {
1782
+ 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(() => {
1322
1783
  let e = r.value?.uid, a = s.value;
1323
1784
  return !e || !a ? null : i(t, n(), e, a);
1324
- }), u = P({
1785
+ }), u = N({
1325
1786
  get: () => $(c.value).text,
1326
1787
  set: (e) => {
1327
1788
  $(c.value).text = e;
1328
1789
  }
1329
- }), d = P(() => $(c.value).sentiment), f = P(() => $(c.value).loaded), p = P(() => $(c.value).pending), m = P(() => $(c.value).saving), h = P(() => $(c.value).error);
1790
+ }), d = N(() => $(c.value).sentiment), f = N(() => $(c.value).loaded), p = N(() => $(c.value).pending), m = N(() => $(c.value).saving), h = N(() => $(c.value).error);
1330
1791
  async function g() {
1331
1792
  let e = l.value;
1332
1793
  if (!e) return;
1333
1794
  let t = $(c.value);
1334
1795
  t.pending = !0, t.error = "";
1335
1796
  try {
1336
- let n = (await B(e)).data();
1797
+ let n = (await z(e)).data();
1337
1798
  t.text = n ? n.text ?? "" : "", t.sentiment = n ? n.sentiment ?? null : null, t.loaded = !0;
1338
1799
  } catch (e) {
1339
1800
  t.error = e.message || String(e), console.error("[memi-board:authorMemo] load failed", e);
@@ -1349,10 +1810,10 @@ function fn(e) {
1349
1810
  let i = $(c.value);
1350
1811
  i.saving = !0, i.error = "";
1351
1812
  try {
1352
- await J(n, {
1813
+ await q(n, {
1353
1814
  text: r,
1354
1815
  sentiment: t,
1355
- updatedAt: q()
1816
+ updatedAt: K()
1356
1817
  }), i.text = r, i.sentiment = t, i.loaded = !0;
1357
1818
  } catch (e) {
1358
1819
  throw i.error = e.message || String(e), console.error("[memi-board:authorMemo] save failed", e), e;
@@ -1366,7 +1827,7 @@ function fn(e) {
1366
1827
  let t = $(c.value);
1367
1828
  t.saving = !0, t.error = "";
1368
1829
  try {
1369
- await de(e), t.text = "", t.sentiment = null, t.loaded = !0;
1830
+ await fe(e), t.text = "", t.sentiment = null, t.loaded = !0;
1370
1831
  } catch (e) {
1371
1832
  throw t.error = e.message || String(e), console.error("[memi-board:authorMemo] remove failed", e), e;
1372
1833
  } finally {
@@ -1387,37 +1848,37 @@ function fn(e) {
1387
1848
  }
1388
1849
  //#endregion
1389
1850
  //#region src/utils/subscribeCommentRange.ts
1390
- function pn(e, t, n, r, i, a) {
1391
- return W(K(u(e, t), Z("postId", "==", n), ...r.rootId ? [Z("rootId", "==", r.rootId), Z("isReply", "==", !0)] : [Z("parentId", "==", null)], G("createdAt", "asc"), G(z(), "asc"), ...r.hasMore && r.cursor ? [me(r.cursor)] : []), (e) => i(e.docs.map((e) => ({
1851
+ function Yn(e, t, n, r, i, a) {
1852
+ 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) => ({
1392
1853
  ...e.data(),
1393
1854
  id: e.id
1394
1855
  }))), a);
1395
1856
  }
1396
1857
  //#endregion
1397
1858
  //#region src/composables/useMemiBoardComments.ts
1398
- var mn = 10, hn = 5, gn = 1e3;
1399
- function _n(e, { t, formatNumber: n }) {
1859
+ var Xn = 10, Zn = 5, Qn = 1e3;
1860
+ function $n(e, { t, formatNumber: n }) {
1400
1861
  let r = e.trim();
1401
1862
  if (!r) throw Error(t("comments.bodyRequired"));
1402
- if (r.length > 1e3) throw Error(t("comments.bodyTooLong", { max: n(gn) }));
1863
+ if (r.length > 1e3) throw Error(t("comments.bodyTooLong", { max: n(Qn) }));
1403
1864
  return r;
1404
1865
  }
1405
- function vn(e, t, n = {}) {
1406
- let r = Q(), i = () => M(), { isBoardHidden: a } = Yt(), o = O(), { t: s } = o, c = () => u(r, i()), d = P(() => L(e)), f = P(() => L(t)), p = () => !a(d.value), m = I([]), h = I(n.subscribe !== !1), g = I([]), _ = I(!1), v = I(n.subscribe !== !1), y = I(n.subscribe !== !1), b = null, x = 0, S = 0, C;
1866
+ function er(e, t, n = {}) {
1867
+ 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;
1407
1868
  function w(e) {
1408
1869
  return e.createdAt?.toMillis?.() ?? 0;
1409
1870
  }
1410
- let T = P(() => {
1871
+ let ee = N(() => {
1411
1872
  let e = /* @__PURE__ */ new Map();
1412
1873
  for (let t of [...m.value, ...g.value]) t.id && e.set(t.id, t);
1413
1874
  return [...e.values()].sort((e, t) => w(e) - w(t) || String(e.id).localeCompare(String(t.id)));
1414
1875
  });
1415
- R([f, d], async ([e]) => {
1876
+ L([f, d], async ([e]) => {
1416
1877
  let t = ++x;
1417
1878
  if (S++, _.value = !1, g.value = [], m.value = [], b = null, C?.(), C = void 0, v.value = n.subscribe !== !1, n.subscribe !== !1) {
1418
1879
  h.value = !0, y.value = !0;
1419
1880
  try {
1420
- let n = V(K(c(), Z("postId", "==", e), G("createdAt", "desc"), G(z(), "desc")));
1881
+ let n = B(G(c(), X("postId", "==", e), W("createdAt", "desc"), W(R(), "desc")));
1421
1882
  if (await Promise.all([n.then((e) => {
1422
1883
  if (t !== x) return;
1423
1884
  let n = e.docs.map((e) => ({
@@ -1425,16 +1886,16 @@ function vn(e, t, n = {}) {
1425
1886
  ...e.data()
1426
1887
  }));
1427
1888
  m.value = n.filter((e) => e.parentId === void 0);
1428
- }), D()]), t !== x) return;
1889
+ }), E()]), t !== x) return;
1429
1890
  } finally {
1430
1891
  t === x && (h.value = !1, y.value = !1);
1431
1892
  }
1432
1893
  }
1433
1894
  }, { immediate: !0 });
1434
- function E() {
1895
+ function T() {
1435
1896
  C?.();
1436
1897
  let e = ++S;
1437
- C = pn(r, i(), f.value, {
1898
+ C = Yn(r, i(), f.value, {
1438
1899
  cursor: b,
1439
1900
  hasMore: v.value
1440
1901
  }, (t) => {
@@ -1443,34 +1904,34 @@ function vn(e, t, n = {}) {
1443
1904
  console.error("[memi-board:comments] onSnapshot failed", e);
1444
1905
  });
1445
1906
  }
1446
- async function D() {
1907
+ async function E() {
1447
1908
  if (_.value || !v.value) return;
1448
1909
  let e = x;
1449
1910
  _.value = !0;
1450
1911
  try {
1451
1912
  let t = [
1452
- Z("postId", "==", f.value),
1453
- Z("parentId", "==", null),
1454
- G("createdAt", "asc"),
1455
- G(z(), "asc"),
1456
- ...b ? [Y(b)] : [],
1457
- U(11)
1458
- ], n = await V(K(c(), ...t));
1913
+ X("postId", "==", f.value),
1914
+ X("parentId", "==", null),
1915
+ W("createdAt", "asc"),
1916
+ W(R(), "asc"),
1917
+ ...b ? [J(b)] : [],
1918
+ H(11)
1919
+ ], n = await B(G(c(), ...t));
1459
1920
  if (e !== x) return;
1460
- let r = n.docs.slice(0, mn), i = r.map((e) => ({
1921
+ let r = n.docs.slice(0, Xn), i = r.map((e) => ({
1461
1922
  id: e.id,
1462
1923
  ...e.data()
1463
1924
  })), a = new Set(g.value.map((e) => e.id));
1464
- g.value.push(...i.filter((e) => !a.has(e.id))), b = r.at(-1) ?? b, v.value = n.size > mn, E();
1925
+ g.value.push(...i.filter((e) => !a.has(e.id))), b = r.at(-1) ?? b, v.value = n.size > Xn, T();
1465
1926
  } finally {
1466
1927
  e === x && (_.value = !1);
1467
1928
  }
1468
1929
  }
1469
- F(() => {
1930
+ P(() => {
1470
1931
  x++, S++, C?.();
1471
1932
  });
1472
- async function ee(e) {
1473
- let t = _n(e.body, o), n = ve(r), a = pe(c());
1933
+ async function te(e) {
1934
+ let t = $n(e.body, o), n = ye(r), a = me(c());
1474
1935
  n.set(a, {
1475
1936
  postId: f.value,
1476
1937
  boardId: d.value,
@@ -1488,14 +1949,14 @@ function vn(e, t, n = {}) {
1488
1949
  replyCount: 0,
1489
1950
  likeCount: 0,
1490
1951
  isReply: !1,
1491
- createdAt: q(),
1492
- updatedAt: q()
1493
- }), n.update(l(r, i(), f.value), { commentCount: H(1) }), await n.commit();
1952
+ createdAt: K(),
1953
+ updatedAt: K()
1954
+ }), n.update(l(r, i(), f.value), { commentCount: V(1) }), await n.commit();
1494
1955
  }
1495
- async function A(e) {
1496
- let t = _n(e.body, o);
1956
+ async function k(e) {
1957
+ let t = $n(e.body, o);
1497
1958
  if (!e.parent.id) throw Error(s("comments.replyTargetNotFound"));
1498
- let n = ve(r), a = pe(c()), u = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
1959
+ let n = ye(r), a = me(c()), u = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
1499
1960
  if (!u) throw Error(s("comments.threadNotFound"));
1500
1961
  let m = Math.min((e.parent.depth ?? 0) + 1, 2);
1501
1962
  n.set(a, {
@@ -1515,53 +1976,53 @@ function vn(e, t, n = {}) {
1515
1976
  replyCount: 0,
1516
1977
  likeCount: 0,
1517
1978
  isReply: !0,
1518
- createdAt: q(),
1519
- updatedAt: q()
1520
- }), n.update(k(r, i(), u), { replyCount: H(1) }), n.update(l(r, i(), f.value), { commentCount: H(1) }), await n.commit();
1979
+ createdAt: K(),
1980
+ updatedAt: K()
1981
+ }), n.update(O(r, i(), u), { replyCount: V(1) }), n.update(l(r, i(), f.value), { commentCount: V(1) }), await n.commit();
1521
1982
  }
1522
- async function j(e) {
1983
+ async function A(e) {
1523
1984
  if (!e.id) return;
1524
1985
  if (e.parentId == null && (e.replyCount ?? 0) > 0) throw Error(s("comments.hasRepliesCannotDelete"));
1525
- await At(r, i(), e.id);
1526
- let t = ve(r);
1527
- t.delete(k(r, i(), e.id)), e.parentId && e.rootId && t.update(k(r, i(), e.rootId), { replyCount: H(-1) }), t.update(l(r, i(), f.value), { commentCount: H(-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
+ await Wt(r, i(), e.id);
1987
+ let t = ye(r);
1988
+ 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);
1528
1989
  }
1529
- async function te(e, t) {
1530
- let n = _n(t, o);
1531
- await X(k(r, i(), e), {
1990
+ async function ne(e, t) {
1991
+ let n = $n(t, o);
1992
+ await Y(O(r, i(), e), {
1532
1993
  body: n,
1533
- updatedAt: q()
1994
+ updatedAt: K()
1534
1995
  });
1535
1996
  }
1536
- async function ne(e, t, n) {
1537
- await X(k(r, i(), e), {
1997
+ async function re(e, t, n) {
1998
+ await Y(O(r, i(), e), {
1538
1999
  isBlinded: t,
1539
- moderatedAt: q(),
2000
+ moderatedAt: K(),
1540
2001
  moderatedBy: n
1541
2002
  });
1542
2003
  }
1543
2004
  return {
1544
- comments: T,
1545
- commentsPending: P(() => y.value || h.value),
2005
+ comments: ee,
2006
+ commentsPending: N(() => y.value || h.value),
1546
2007
  hasMore: v,
1547
2008
  loadingMore: _,
1548
- loadMore: D,
1549
- addComment: ee,
1550
- addReply: A,
1551
- updateComment: te,
1552
- setCommentBlinded: ne,
1553
- deleteComment: j
2009
+ loadMore: E,
2010
+ addComment: te,
2011
+ addReply: k,
2012
+ updateComment: ne,
2013
+ setCommentBlinded: re,
2014
+ deleteComment: A
1554
2015
  };
1555
2016
  }
1556
- function yn(e, t, n) {
1557
- let r = Q(), i = () => M(), a = () => u(r, i()), o = t.trim(), s = I([]), c = I(!1), l = I(!1), d = I(!0), f = null, p, m = 0, h = 0;
2017
+ function tr(e, t, n) {
2018
+ 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;
1558
2019
  function g(e) {
1559
2020
  return e.sort((e, t) => (e.createdAt?.toMillis?.() ?? 0) - (t.createdAt?.toMillis?.() ?? 0) || String(e.id).localeCompare(String(t.id)));
1560
2021
  }
1561
2022
  function _() {
1562
2023
  p?.();
1563
2024
  let e = ++h;
1564
- p = pn(r, i(), o, {
2025
+ p = Yn(r, i(), o, {
1565
2026
  rootId: n,
1566
2027
  cursor: f,
1567
2028
  hasMore: d.value
@@ -1577,20 +2038,20 @@ function yn(e, t, n) {
1577
2038
  c.value = !0;
1578
2039
  try {
1579
2040
  let t = [
1580
- Z("postId", "==", o),
1581
- Z("rootId", "==", n),
1582
- Z("isReply", "==", !0),
1583
- G("createdAt", "asc"),
1584
- G(z(), "asc"),
1585
- ...f ? [Y(f)] : [],
1586
- U(6)
1587
- ], r = await V(K(a(), ...t));
2041
+ X("postId", "==", o),
2042
+ X("rootId", "==", n),
2043
+ X("isReply", "==", !0),
2044
+ W("createdAt", "asc"),
2045
+ W(R(), "asc"),
2046
+ ...f ? [J(f)] : [],
2047
+ H(6)
2048
+ ], r = await B(G(a(), ...t));
1588
2049
  if (e !== m) return;
1589
- let i = new Set(s.value.map((e) => e.id)), c = r.docs.slice(0, hn), u = c.map((e) => ({
2050
+ let i = new Set(s.value.map((e) => e.id)), c = r.docs.slice(0, Zn), u = c.map((e) => ({
1590
2051
  id: e.id,
1591
2052
  ...e.data()
1592
2053
  })).filter((e) => !i.has(e.id));
1593
- s.value = g([...s.value, ...u]), f = c.at(-1) ?? f, l.value = !0, d.value = r.size > hn, _();
2054
+ s.value = g([...s.value, ...u]), f = c.at(-1) ?? f, l.value = !0, d.value = r.size > Zn, _();
1594
2055
  } finally {
1595
2056
  e === m && (c.value = !1);
1596
2057
  }
@@ -1601,7 +2062,7 @@ function yn(e, t, n) {
1601
2062
  async function b() {
1602
2063
  y(), await v();
1603
2064
  }
1604
- return F(() => {
2065
+ return P(() => {
1605
2066
  m++, h++, p?.();
1606
2067
  }), {
1607
2068
  replies: s,
@@ -1614,22 +2075,22 @@ function yn(e, t, n) {
1614
2075
  }
1615
2076
  //#endregion
1616
2077
  //#region src/composables/useMemiBoardLikes.ts
1617
- function bn(e, t) {
1618
- let n = () => M(), r = Q(), i = be(), { t: a } = O(), o = I(!1), s = I(!1), c;
2078
+ function nr(e, t) {
2079
+ let n = () => j(), r = Z(), i = xe(), { t: a } = D(), o = F(!1), s = F(!1), c;
1619
2080
  function u(e) {
1620
2081
  return d(r, n(), t, e);
1621
2082
  }
1622
- R(i, (e) => {
2083
+ L(i, (e) => {
1623
2084
  if (c?.(), c = void 0, !e) {
1624
2085
  o.value = !1;
1625
2086
  return;
1626
2087
  }
1627
- c = W(u(e.uid), (e) => {
2088
+ c = U(u(e.uid), (e) => {
1628
2089
  o.value = e.exists();
1629
2090
  }, (e) => {
1630
2091
  console.error("[memi-board:likes] onSnapshot failed", e);
1631
2092
  });
1632
- }, { immediate: !0 }), F(() => c?.());
2093
+ }, { immediate: !0 }), P(() => c?.());
1633
2094
  async function f() {
1634
2095
  let c = i.value?.uid;
1635
2096
  if (!c) throw Error(a("likes.signInRequired"));
@@ -1637,12 +2098,12 @@ function bn(e, t) {
1637
2098
  s.value = !0;
1638
2099
  try {
1639
2100
  let i = u(c), a = l(r, n(), t);
1640
- return await _e(r, async (n) => (await n.get(i)).exists() ? (n.delete(i), n.update(a, { likeCount: H(-1) }), !1) : (n.set(i, {
2101
+ return await ve(r, async (n) => (await n.get(i)).exists() ? (n.delete(i), n.update(a, { likeCount: V(-1) }), !1) : (n.set(i, {
1641
2102
  uid: c,
1642
2103
  postId: t,
1643
2104
  boardId: e,
1644
- createdAt: q()
1645
- }), n.update(a, { likeCount: H(1) }), !0));
2105
+ createdAt: K()
2106
+ }), n.update(a, { likeCount: V(1) }), !0));
1646
2107
  } finally {
1647
2108
  s.value = !1;
1648
2109
  }
@@ -1653,11 +2114,11 @@ function bn(e, t) {
1653
2114
  toggleLike: f
1654
2115
  };
1655
2116
  }
1656
- var xn = Symbol.for("memi-board:comment-likes");
1657
- function Sn(e, t) {
1658
- let n = () => M(), r = Q(), i = be(), { t: o } = O(), s = P(() => L(e)), c = P(() => L(t)), l = I(/* @__PURE__ */ new Set()), u = I(null), f;
1659
- R([i, c], ([e, t]) => {
1660
- f?.(), f = void 0, l.value = /* @__PURE__ */ new Set(), e && t && (f = W(K(a(r, n()), Z("uid", "==", e.uid), Z("postId", "==", t), Z("target", "==", "comment")), (e) => {
2117
+ var rr = Symbol.for("memi-board:comment-likes");
2118
+ function ir(e, t) {
2119
+ 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;
2120
+ L([i, c], ([e, t]) => {
2121
+ 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) => {
1661
2122
  let t = /* @__PURE__ */ new Set();
1662
2123
  for (let n of e.docs) {
1663
2124
  let e = n.data().commentId;
@@ -1667,7 +2128,7 @@ function Sn(e, t) {
1667
2128
  }, (e) => {
1668
2129
  console.error("[memi-board:comment-likes] onSnapshot failed", e);
1669
2130
  }));
1670
- }, { immediate: !0 }), F(() => f?.());
2131
+ }, { immediate: !0 }), P(() => f?.());
1671
2132
  function p(e) {
1672
2133
  return !!e && l.value.has(e);
1673
2134
  }
@@ -1681,7 +2142,7 @@ function Sn(e, t) {
1681
2142
  if (u.value) return p(e);
1682
2143
  u.value = e;
1683
2144
  try {
1684
- let i = d(r, n(), e, t), a = k(r, n(), e), u = await _e(r, async (n) => {
2145
+ let i = d(r, n(), e, t), a = O(r, n(), e), u = await ve(r, async (n) => {
1685
2146
  let r = await n.get(i), l = await n.get(a);
1686
2147
  if (!l.exists()) throw Error(o("likes.commentNotFound"));
1687
2148
  let u = Number(l.data()?.likeCount ?? 0);
@@ -1691,7 +2152,7 @@ function Sn(e, t) {
1691
2152
  boardId: s.value,
1692
2153
  commentId: e,
1693
2154
  target: "comment",
1694
- createdAt: q()
2155
+ createdAt: K()
1695
2156
  }), n.update(a, { likeCount: u + 1 }), !0);
1696
2157
  }), f = new Set(l.value);
1697
2158
  return u ? f.add(e) : f.delete(e), l.value = f, u;
@@ -1707,7 +2168,7 @@ function Sn(e, t) {
1707
2168
  }
1708
2169
  //#endregion
1709
2170
  //#region src/composables/useMemiBoardReports.ts
1710
- var Cn = 200, wn = [
2171
+ var ar = 200, or = [
1711
2172
  {
1712
2173
  value: "spam",
1713
2174
  label: "스팸·광고"
@@ -1729,34 +2190,34 @@ var Cn = 200, wn = [
1729
2190
  label: "기타"
1730
2191
  }
1731
2192
  ];
1732
- function Tn(e, t) {
1733
- return wn.some((t) => t.value === e) ? w(t, `reports.reason.${e}`) : e || w(t, "reports.fallbackLabel");
2193
+ function sr(e, t) {
2194
+ return or.some((t) => t.value === e) ? w(t, `reports.reason.${e}`) : e || w(t, "reports.fallbackLabel");
1734
2195
  }
1735
- function En(e, n) {
1736
- let r = () => M(), i = Q(), a = be(), { t: o } = O(), s = I(!1), c = I(!1), l;
1737
- R(a, (e) => {
2196
+ function cr(e, n) {
2197
+ let r = () => j(), i = Z(), a = xe(), { t: o } = D(), s = F(!1), c = F(!1), l;
2198
+ L(a, (e) => {
1738
2199
  if (l?.(), l = void 0, !e) {
1739
2200
  s.value = !1;
1740
2201
  return;
1741
2202
  }
1742
- l = W(t(i, r(), n, e.uid), (e) => {
2203
+ l = U(t(i, r(), n, e.uid), (e) => {
1743
2204
  s.value = e.exists();
1744
2205
  }, (e) => {
1745
2206
  console.error("[memi-board:reports] onSnapshot failed", e);
1746
2207
  });
1747
- }, { immediate: !0 }), F(() => l?.());
2208
+ }, { immediate: !0 }), P(() => l?.());
1748
2209
  async function u(l) {
1749
2210
  let u = a.value?.uid;
1750
2211
  if (!u) throw Error(o("reports.signInRequired"));
1751
2212
  if (c.value) return;
1752
2213
  if (l.authorUid && l.authorUid === u) throw Error(o("reports.cannotReportOwn"));
1753
- let d = wn.some((e) => e.value === l.reason) ? l.reason : null;
2214
+ let d = or.some((e) => e.value === l.reason) ? l.reason : null;
1754
2215
  if (!d) throw Error(o("reports.reasonRequired"));
1755
2216
  let f = (l.detail || "").trim();
1756
2217
  if (f.length > 200) throw Error(o("reports.detailTooLong", { max: 200 }));
1757
2218
  c.value = !0;
1758
2219
  try {
1759
- await J(t(i, r(), n, u), {
2220
+ await q(t(i, r(), n, u), {
1760
2221
  uid: u,
1761
2222
  target: "post",
1762
2223
  postId: n,
@@ -1767,7 +2228,7 @@ function En(e, n) {
1767
2228
  postTitle: (l.postTitle || "").trim().slice(0, 200),
1768
2229
  authorUid: l.authorUid || "",
1769
2230
  authorName: l.authorName ?? null,
1770
- createdAt: q()
2231
+ createdAt: K()
1771
2232
  }), s.value = !0;
1772
2233
  } finally {
1773
2234
  c.value = !1;
@@ -1779,9 +2240,9 @@ function En(e, n) {
1779
2240
  submitReport: u
1780
2241
  };
1781
2242
  }
1782
- var Dn = 20;
1783
- function On(e = {}) {
1784
- let n = () => M(), r = Q(), { isAdmin: i, canManageContent: a, canManageBoard: o } = ht(), c = e.pageSize ?? Dn, l = P(() => L(e.boardId)?.trim() || ""), u = I([]), d = I(!0), f = I(!1), p = I(!1), m = I(""), h = null, g;
2243
+ var lr = 20;
2244
+ function ur(e = {}) {
2245
+ 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;
1785
2246
  function _(e) {
1786
2247
  return {
1787
2248
  id: e.id,
@@ -1794,19 +2255,19 @@ function On(e = {}) {
1794
2255
  return;
1795
2256
  }
1796
2257
  d.value = !0;
1797
- let e = l.value ? [Z("status", "==", "open"), Z("boardId", "==", l.value)] : [Z("status", "==", "open"), Z("target", "==", "post")];
1798
- g = W(K(s(r, n()), ...e, G("createdAt", "desc"), G(z(), "desc"), U(c)), (e) => {
2258
+ let e = l.value ? [X("status", "==", "open"), X("boardId", "==", l.value)] : [X("status", "==", "open"), X("target", "==", "post")];
2259
+ g = U(G(s(r, n()), ...e, W("createdAt", "desc"), W(R(), "desc"), H(c)), (e) => {
1799
2260
  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;
1800
2261
  }, (e) => {
1801
2262
  console.error("[memi-board:report-queue] onSnapshot failed", e), m.value = e.message || String(e), d.value = !1;
1802
2263
  });
1803
2264
  }
1804
- R([a, l], v, { immediate: !0 }), F(() => g?.());
2265
+ L([a, l], v, { immediate: !0 }), P(() => g?.());
1805
2266
  async function y() {
1806
2267
  if (!f.value && p.value && h && a.value) {
1807
2268
  f.value = !0, m.value = "";
1808
2269
  try {
1809
- let e = l.value ? [Z("status", "==", "open"), Z("boardId", "==", l.value)] : [Z("status", "==", "open"), Z("target", "==", "post")], t = await V(K(s(r, n()), ...e, G("createdAt", "desc"), G(z(), "desc"), Y(h), U(c + 1))), i = t.docs.slice(0, c), a = new Set(u.value.map((e) => e.id));
2270
+ 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));
1810
2271
  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;
1811
2272
  } catch (e) {
1812
2273
  m.value = e.message || String(e);
@@ -1816,10 +2277,10 @@ function On(e = {}) {
1816
2277
  }
1817
2278
  }
1818
2279
  async function b(e, i) {
1819
- e.id && o(e.boardId) && await X(t(r, n(), e.postId, e.uid), {
2280
+ e.id && o(e.boardId) && await Y(t(r, n(), e.postId, e.uid), {
1820
2281
  status: i,
1821
- reviewedAt: q(),
1822
- reviewedBy: be().value?.uid || ""
2282
+ reviewedAt: K(),
2283
+ reviewedBy: xe().value?.uid || ""
1823
2284
  });
1824
2285
  }
1825
2286
  return {
@@ -1835,8 +2296,8 @@ function On(e = {}) {
1835
2296
  }
1836
2297
  //#endregion
1837
2298
  //#region src/composables/useMemiBoardViews.ts
1838
- function kn() {
1839
- let e = () => M(), t = Q();
2299
+ function dr() {
2300
+ let e = () => j(), t = Z();
1840
2301
  function n(t, n) {
1841
2302
  let { postsCollection: r } = e();
1842
2303
  return `memi-board:viewed:${r}:${t}:${n}`;
@@ -1859,7 +2320,7 @@ function kn() {
1859
2320
  if (s && c && !r(s, c)) {
1860
2321
  i(s, c);
1861
2322
  try {
1862
- await X(l(t, e(), c), { viewCount: H(1) });
2323
+ await Y(l(t, e(), c), { viewCount: V(1) });
1863
2324
  } catch (e) {
1864
2325
  try {
1865
2326
  sessionStorage.removeItem(n(s, c));
@@ -1874,161 +2335,21 @@ function kn() {
1874
2335
  };
1875
2336
  }
1876
2337
  //#endregion
1877
- //#region src/utils/moderation-prompt.ts
1878
- 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 = {
1879
- ko: "Korean",
1880
- en: "English",
1881
- ja: "Japanese",
1882
- de: "German",
1883
- fr: "French",
1884
- es: "Spanish",
1885
- pt: "Portuguese",
1886
- zh: "Simplified Chinese",
1887
- ar: "Arabic",
1888
- id: "Indonesian"
1889
- };
1890
- function Mn(e) {
1891
- let t = jn[e] ?? "English", n = D(e).name;
1892
- return t === n ? t : `${t} (${n})`;
1893
- }
1894
- function Nn(e) {
1895
- let t = Mn(e);
1896
- return `You are a strict moderator reviewing content for a message board.
1897
- If the text below contains profanity, vulgar slang, sexual harassment, hate speech, threats, spam, or repetitive/flooding advertisements, you MUST reject it (flagged=true).
1898
-
1899
- The content may be written in ANY language, or a mix of languages — not only the board's language.
1900
- Judge every language equally strictly, including slang, abbreviations, romanizations, deliberate misspellings,
1901
- inserted spaces/symbols, and other obfuscation (e.g. in Korean, Japanese, Chinese, Arabic, Spanish, etc.).
1902
-
1903
- flagged=true examples:
1904
- - Profanity/vulgar slang: fuck, shit, bitch, asshole, f*ck, sh1t /
1905
- Korean: 씨발, 시발, ㅅㅂ, 병신, ㅂㅅ, 지랄, 좆, 개새끼, 존나 /
1906
- Japanese: クソ, 死ね, ちんこ / Chinese: 操你妈, 傻逼, 他妈的 /
1907
- Spanish: puta, mierda, pendejo / German: Scheiße, Hurensohn / French: putain, connard /
1908
- Portuguese: porra, caralho / Indonesian: anjing, bangsat, kontol / Arabic: كس, شرموطة, etc.
1909
- - Sexual insults, hate speech, threats
1910
- - Flooding advertisements/spam unrelated to the board's topic
1911
-
1912
- flagged=false:
1913
- - Normal posts/comments that fit the board's topic (strong opinions and legitimate criticism are allowed)
1914
-
1915
- Output JSON only:
1916
- {"flagged":boolean,"category":"none"|"abuse"|"spam"|"adult"|"violence"|"other","reason":string}
1917
- - If flagged=true, choose a category, and write reason as ONE concrete sentence in ${t} explaining **what the problem is**
1918
- (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}).
1919
- Do not quote the offensive words at length; clearly state the type (profanity/hate/spam, etc.).
1920
- - If flagged=false, category is "none" and reason is ""`;
1921
- }
1922
- function Pn(e = "ko") {
1923
- return e === "ko" ? An : Nn(e);
1924
- }
1925
- function Fn(e, t = "ko") {
1926
- let n = `"""${e.slice(0, 4e3)}"""`;
1927
- return t === "ko" ? `심사할 텍스트:\n${n}` : `Text to review:\n${n}`;
1928
- }
1929
- function In(e = "ko") {
1930
- return e === "ko" ? "이 이미지를 심사해 주세요." : "Please review this image.";
1931
- }
1932
- var Ln = {
1933
- abuse: "욕설·비속어·모욕",
1934
- spam: "스팸·광고",
1935
- adult: "선정적·성인 내용",
1936
- violence: "폭력·위협",
1937
- other: "부적절한 내용",
1938
- none: ""
1939
- };
1940
- function Rn(e, t = "ko") {
1941
- return !e || e === "none" ? "" : e in Ln ? w(t, `moderationPrompt.categories.${e}`) : e;
1942
- }
1943
- function zn(e) {
1944
- let t = e.locale ?? "ko";
1945
- if (e.via === "local" && e.localHit) {
1946
- let n = e.localHit.trim();
1947
- return n.length > 0 && n.length <= 12 ? w(t, "moderationPrompt.localHit", { hit: n }) : w(t, "moderationPrompt.localGeneric");
1948
- }
1949
- let n = (e.reason || "").trim();
1950
- if (n) return n;
1951
- let r = e.category ? Rn(e.category, t) : "";
1952
- return r ? w(t, "moderationPrompt.categoryBlocked", { category: r }) : w(t, "moderationPrompt.genericBlocked");
1953
- }
1954
- var Bn = [
1955
- "씨발",
1956
- "시발",
1957
- "시이발",
1958
- "씨팔",
1959
- "시팔",
1960
- "병신",
1961
- "븅신",
1962
- "지랄",
1963
- "좆",
1964
- "존나",
1965
- "개새끼",
1966
- "개새",
1967
- "새끼",
1968
- "꺼져",
1969
- "닥쳐",
1970
- "씹",
1971
- "니미",
1972
- "느금",
1973
- "애미",
1974
- "애비",
1975
- "fuck",
1976
- "shit",
1977
- "bitch",
1978
- "asshole"
1979
- ];
1980
- function Vn(e) {
1981
- return new RegExp(e.join("|"), "i");
1982
- }
1983
- var Hn = /* @__PURE__ */ new Set([
1984
- "none",
1985
- "abuse",
1986
- "spam",
1987
- "adult",
1988
- "violence",
1989
- "other"
1990
- ]);
1991
- function Un(e) {
1992
- let t = e.trim();
1993
- if (!t) return null;
1994
- let n;
1995
- try {
1996
- n = JSON.parse(t);
1997
- } catch {
1998
- let e = t.match(/\{[\s\S]*\}/);
1999
- if (!e) return null;
2000
- try {
2001
- n = JSON.parse(e[0]);
2002
- } catch {
2003
- return null;
2004
- }
2005
- }
2006
- if (typeof n != "object" || !n) return null;
2007
- let r = n;
2008
- if (typeof r.flagged != "boolean") return null;
2009
- let i = typeof r.category == "string" && Hn.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
2010
- return {
2011
- flagged: r.flagged,
2012
- category: i,
2013
- reason: a
2014
- };
2015
- }
2016
- //#endregion
2017
2338
  //#region src/composables/useMemiBoardModeration.ts
2018
- var Wn = {
2339
+ var fr = {
2019
2340
  flagged: !1,
2020
2341
  category: "none",
2021
2342
  reason: "",
2022
2343
  via: "empty"
2023
2344
  };
2024
- function Gn(e = {}) {
2025
- let t = O(), n = P(() => E(L(e.locale)) ?? t.locale.value), r = le().moderation ?? {}, i = Se(), a = (e) => w(n.value, e), { isWriteRestricted: o, restrictedMessage: s, recordContentModerationBlock: c } = ht(), l = Vn([...Bn, ...r.localBlocklist ?? []]);
2345
+ function pr(e = {}) {
2346
+ 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 ?? []]);
2026
2347
  function u(e) {
2027
2348
  let t = e.match(l);
2028
2349
  return t ? {
2029
2350
  flagged: !0,
2030
2351
  category: "abuse",
2031
- reason: zn({
2352
+ reason: hn({
2032
2353
  via: "local",
2033
2354
  localHit: t[0] || "",
2034
2355
  category: "abuse"
@@ -2037,21 +2358,21 @@ function Gn(e = {}) {
2037
2358
  } : null;
2038
2359
  }
2039
2360
  function d() {
2040
- let e = r.model ?? "gemini-3.5-flash-lite", t = r.useLimitedUseAppCheckTokens !== !1, a = Le(i, {
2041
- backend: new Fe(),
2361
+ let e = r.model ?? "gemini-3.5-flash-lite", t = r.useLimitedUseAppCheckTokens !== !1, a = Re(i, {
2362
+ backend: new Ie(),
2042
2363
  useLimitedUseAppCheckTokens: t
2043
2364
  });
2044
- return Re(a, {
2365
+ return ze(a, {
2045
2366
  model: e,
2046
- systemInstruction: Pn(n.value),
2367
+ systemInstruction: un(n.value),
2047
2368
  generationConfig: {
2048
2369
  temperature: 0,
2049
2370
  maxOutputTokens: 256,
2050
2371
  responseMimeType: "application/json",
2051
- responseSchema: Ie.object({ properties: {
2052
- flagged: Ie.boolean(),
2053
- category: Ie.string(),
2054
- reason: Ie.string()
2372
+ responseSchema: Le.object({ properties: {
2373
+ flagged: Le.boolean(),
2374
+ category: Le.string(),
2375
+ reason: Le.string()
2055
2376
  } })
2056
2377
  }
2057
2378
  });
@@ -2072,7 +2393,7 @@ function Gn(e = {}) {
2072
2393
  };
2073
2394
  }
2074
2395
  function p(e, t = "") {
2075
- let n = zn({
2396
+ let n = hn({
2076
2397
  reason: e.reason,
2077
2398
  category: e.category,
2078
2399
  via: e.via
@@ -2100,26 +2421,26 @@ function Gn(e = {}) {
2100
2421
  };
2101
2422
  let i = e.trim();
2102
2423
  if (!i) return {
2103
- ...Wn,
2424
+ ...fr,
2104
2425
  via: "empty"
2105
2426
  };
2106
2427
  if (t?.skipFilter) return {
2107
- ...Wn,
2428
+ ...fr,
2108
2429
  via: "admin-override"
2109
2430
  };
2110
2431
  let c = u(i);
2111
2432
  if (c) return m(c);
2112
2433
  if (r.enabled === !1) return {
2113
- ...Wn,
2434
+ ...fr,
2114
2435
  via: "disabled"
2115
2436
  };
2116
2437
  try {
2117
- let e = Un((await d().generateContent(Fn(i, n.value))).response.text());
2438
+ let e = yn((await d().generateContent(dn(i, n.value))).response.text());
2118
2439
  if (!e) return f();
2119
2440
  let t = {
2120
2441
  flagged: e.flagged,
2121
2442
  category: e.category,
2122
- reason: zn({
2443
+ reason: hn({
2123
2444
  reason: e.reason,
2124
2445
  category: e.category,
2125
2446
  via: "ai"
@@ -2139,22 +2460,22 @@ function Gn(e = {}) {
2139
2460
  via: "restricted"
2140
2461
  };
2141
2462
  if (r.enabled === !1 || !r.moderateImages) return {
2142
- ...Wn,
2463
+ ...fr,
2143
2464
  via: "disabled"
2144
2465
  };
2145
2466
  try {
2146
2467
  let t = await new Promise((t, n) => {
2147
2468
  let r = new FileReader();
2148
2469
  r.onload = () => t(r.result.split(",")[1] ?? ""), r.onerror = n, r.readAsDataURL(e);
2149
- }), r = Un((await d().generateContent([{ inlineData: {
2470
+ }), r = yn((await d().generateContent([{ inlineData: {
2150
2471
  mimeType: e.type || "image/jpeg",
2151
2472
  data: t
2152
- } }, In(n.value)])).response.text());
2473
+ } }, fn(n.value)])).response.text());
2153
2474
  if (!r) return f();
2154
2475
  let i = {
2155
2476
  flagged: r.flagged,
2156
2477
  category: r.category,
2157
- reason: zn({
2478
+ reason: hn({
2158
2479
  reason: r.reason,
2159
2480
  category: r.category,
2160
2481
  via: "ai"
@@ -2173,7 +2494,7 @@ function Gn(e = {}) {
2173
2494
  }
2174
2495
  //#endregion
2175
2496
  //#region src/composables/useMemiBoardUsers.ts
2176
- var Kn = [
2497
+ var mr = [
2177
2498
  "owner",
2178
2499
  "admin",
2179
2500
  "manager",
@@ -2184,13 +2505,13 @@ var Kn = [
2184
2505
  label: w("ko", `common.role.${e}`),
2185
2506
  description: w("ko", `boardUsers.roleDescription.${e}`)
2186
2507
  }));
2187
- function qn(t = {}) {
2188
- let n = () => M(), i = Q(), { t: a } = O(), o = P(() => L(t.enabled) ?? !0 ? e(i, n()) : null), s = ye(o, { ssrKey: r(n(), "users-management") });
2508
+ function hr(t = {}) {
2509
+ 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") });
2189
2510
  async function c(e, t) {
2190
- if (!Kn.some((e) => e.value === t)) throw Error(a("boardUsers.unsupportedRole"));
2191
- await X(f(i, n(), e), {
2511
+ if (!mr.some((e) => e.value === t)) throw Error(a("boardUsers.unsupportedRole"));
2512
+ await Y(f(i, n(), e), {
2192
2513
  role: t,
2193
- updatedAt: q()
2514
+ updatedAt: K()
2194
2515
  });
2195
2516
  }
2196
2517
  return {
@@ -2201,11 +2522,11 @@ function qn(t = {}) {
2201
2522
  }
2202
2523
  //#endregion
2203
2524
  //#region src/composables/useMemiBoardUserProfile.ts
2204
- function Jn(e) {
2205
- let t = Q(), n = () => M(), r = P(() => L(e)), i = P(() => {
2525
+ function gr(e) {
2526
+ let t = Z(), n = () => j(), r = N(() => I(e)), i = N(() => {
2206
2527
  let e = r.value;
2207
2528
  return e ? f(t, n(), e) : null;
2208
- }), { data: a, pending: o } = xe(i);
2529
+ }), { data: a, pending: o } = Se(i);
2209
2530
  return {
2210
2531
  profile: a,
2211
2532
  profilePending: o
@@ -2213,7 +2534,7 @@ function Jn(e) {
2213
2534
  }
2214
2535
  //#endregion
2215
2536
  //#region src/utils/section.ts
2216
- var Yn = [
2537
+ var _r = [
2217
2538
  3,
2218
2539
  4,
2219
2540
  5,
@@ -2223,7 +2544,7 @@ var Yn = [
2223
2544
  9,
2224
2545
  12
2225
2546
  ];
2226
- function Xn(e) {
2547
+ function vr(e) {
2227
2548
  return [
2228
2549
  {
2229
2550
  value: "list",
@@ -2239,7 +2560,7 @@ function Xn(e) {
2239
2560
  }
2240
2561
  ];
2241
2562
  }
2242
- function Zn(e) {
2563
+ function yr(e) {
2243
2564
  return [{
2244
2565
  value: "latest",
2245
2566
  label: w(e, "sectionUtil.sort.latest")
@@ -2248,18 +2569,18 @@ function Zn(e) {
2248
2569
  label: w(e, "sectionUtil.sort.likes")
2249
2570
  }];
2250
2571
  }
2251
- var Qn = Xn("ko"), $n = Zn("ko");
2252
- function er(e) {
2572
+ var br = vr("ko"), xr = yr("ko");
2573
+ function Sr(e) {
2253
2574
  return e === "list" ? 5 : 4;
2254
2575
  }
2255
- function tr(e, t) {
2576
+ function Cr(e, t) {
2256
2577
  let n = Math.floor(Number(t));
2257
- return Number.isFinite(n) ? Math.min(e === "list" ? 20 : 10, Math.max(1, n)) : er(e);
2578
+ return Number.isFinite(n) ? Math.min(e === "list" ? 20 : 10, Math.max(1, n)) : Sr(e);
2258
2579
  }
2259
- function nr(e) {
2580
+ function wr(e) {
2260
2581
  return e === 12 ? null : 12 - e;
2261
2582
  }
2262
- function rr(e) {
2583
+ function Tr(e) {
2263
2584
  return {
2264
2585
  3: "col-span-12 md:col-span-3",
2265
2586
  4: "col-span-12 md:col-span-4",
@@ -2271,36 +2592,36 @@ function rr(e) {
2271
2592
  12: "col-span-12"
2272
2593
  }[e];
2273
2594
  }
2274
- function ir(e) {
2595
+ function Er(e) {
2275
2596
  return e === "sm" ? "min-h-40" : e === "lg" ? "min-h-80" : "min-h-56";
2276
2597
  }
2277
- function ar(e) {
2598
+ function Dr(e) {
2278
2599
  let t = e.boardId || e.category || "free";
2279
2600
  return e.slug ? `/board/${t}/${e.slug}` : `/board/${t}`;
2280
2601
  }
2281
- function or(e) {
2602
+ function Or(e) {
2282
2603
  return e.kind === "comments" ? e.sort === "likes" ? "/boardComments?sort=likes" : "/boardComments" : e.kind === "list" && e.boardId ? `/board/${e.boardId}` : "/board";
2283
2604
  }
2284
- function sr(e) {
2605
+ function kr(e) {
2285
2606
  if (e.boardId && e.postId) return `/board/${e.boardId}/post/${e.postId}`;
2286
2607
  }
2287
- function cr(e) {
2608
+ function Ar(e) {
2288
2609
  return e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
2289
2610
  }
2290
- function lr(e, t) {
2291
- return e.title?.trim() || e.summary || w(t, "sectionUtil.untitledPost");
2611
+ function jr(e, t) {
2612
+ return it(e, t).trim() || at(e, t).trim() || w(t, "sectionUtil.untitledPost");
2292
2613
  }
2293
- function ur(e) {
2614
+ function Mr(e) {
2294
2615
  return e === "image" || e === "video" ? "media" : "list";
2295
2616
  }
2296
- function dr(e, t) {
2297
- return w(t, ur(e) === "media" ? "sectionUtil.view.media" : "sectionUtil.view.list");
2617
+ function Nr(e, t) {
2618
+ return w(t, Mr(e) === "media" ? "sectionUtil.view.media" : "sectionUtil.view.list");
2298
2619
  }
2299
- function fr(e, t) {
2620
+ function Pr(e, t) {
2300
2621
  return e ? t(e)?.listView ?? "default" : "default";
2301
2622
  }
2302
- function pr(e, t) {
2303
- let n = e.map((e) => fr(e.boardId, t));
2623
+ function Fr(e, t) {
2624
+ let n = e.map((e) => Pr(e.boardId, t));
2304
2625
  if (!n.length) return "default";
2305
2626
  if (n.every((e) => e === n[0])) return n[0];
2306
2627
  let r = n.filter((e) => e === "image").length, i = n.filter((e) => e === "video").length;
@@ -2308,27 +2629,27 @@ function pr(e, t) {
2308
2629
  }
2309
2630
  //#endregion
2310
2631
  //#region src/composables/useMemiBoardSections.ts
2311
- var mr = [];
2312
- function hr(e) {
2632
+ var Ir = [];
2633
+ function Lr(e) {
2313
2634
  let t = Number(e.cols);
2314
- if (Yn.includes(t)) return t;
2635
+ if (_r.includes(t)) return t;
2315
2636
  let n = e.width;
2316
2637
  return n === "xs" ? 3 : n === "sm" ? 4 : n === "lg" || Number(e.span) === 2 ? 12 : 6;
2317
2638
  }
2318
- function gr(e) {
2639
+ function Rr(e) {
2319
2640
  let t = e.height ?? e.size;
2320
2641
  return t === "sm" || t === "lg" ? t : "md";
2321
2642
  }
2322
- function _r(e) {
2643
+ function zr(e) {
2323
2644
  return e.kind === "post" || e.kind === "comments" ? e.kind : "list";
2324
2645
  }
2325
- function vr(e) {
2646
+ function Br(e) {
2326
2647
  return e.sort === "likes" ? "likes" : "latest";
2327
2648
  }
2328
- function yr(e) {
2649
+ function Vr(e) {
2329
2650
  return (typeof e.boardId == "string" ? e.boardId.trim() : "") || null;
2330
2651
  }
2331
- function br(e, t) {
2652
+ function Hr(e, t) {
2332
2653
  let n = Array.isArray(e.postIds) ? e.postIds : [], r = [];
2333
2654
  for (let e of n) {
2334
2655
  let n = String(e || "").trim();
@@ -2336,76 +2657,116 @@ function br(e, t) {
2336
2657
  }
2337
2658
  return r;
2338
2659
  }
2339
- function xr(e, t) {
2660
+ function Ur(e, t) {
2340
2661
  return w(t, "sections.defaultTitle", { n: e + 1 });
2341
2662
  }
2342
- function Sr(e, t, n) {
2343
- let r = _r(e), i = tr(r, e.count);
2663
+ function Wr(e, t, n) {
2664
+ let r = zr(e), i = Cr(r, e.count), a = Q(e.i18n), o = Qe(e.i18nSource);
2344
2665
  return {
2345
2666
  id: String(e.id || `box-${t + 1}`),
2346
- title: String(e.title || xr(t, n)),
2667
+ title: String(e.title || Ur(t, n)),
2347
2668
  showTitle: e.showTitle !== !1,
2348
2669
  kind: r,
2349
- boardId: yr(e),
2670
+ boardId: Vr(e),
2350
2671
  count: i,
2351
- postIds: br(e, i),
2352
- sort: vr(e),
2353
- cols: hr(e),
2354
- height: gr(e),
2355
- order: t
2672
+ postIds: Hr(e, i),
2673
+ sort: Br(e),
2674
+ cols: Lr(e),
2675
+ height: Rr(e),
2676
+ order: t,
2677
+ ...a ? { i18n: a } : {},
2678
+ ...o ? { i18nSource: o } : {}
2356
2679
  };
2357
2680
  }
2358
- function Cr() {
2359
- let { locale: e } = O(), t = Q(), n = pe(t, "settings", "homeLayout"), { data: r, pending: i } = xe(n), a = P(() => (Array.isArray(r.value?.blocks) ? r.value.blocks : mr).map((t, n) => Sr(t, n, e.value)));
2360
- async function o(t) {
2361
- await J(n, {
2681
+ function Gr() {
2682
+ 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)));
2683
+ async function s(n) {
2684
+ let r = e.value, i = n.filter((e) => {
2685
+ let t = e.title.trim();
2686
+ return t ? !(e.i18nSource?.title === t && tt(e.i18n, ["title"])) : !1;
2687
+ });
2688
+ if (!i.length) return n;
2689
+ let a = await t(i.map((e) => ({
2690
+ id: e.id,
2691
+ fields: { title: e.title.trim() },
2692
+ context: e.kind
2693
+ })), {
2694
+ source: r,
2695
+ purpose: "section heading on a website home page"
2696
+ });
2697
+ return n.map((e) => {
2698
+ let t = a[e.id];
2699
+ if (!t) return e;
2700
+ let n = e.title.trim(), i = et(r, { title: n }, t);
2701
+ return tt(i, ["title"]) ? {
2702
+ ...e,
2703
+ title: n,
2704
+ i18n: i,
2705
+ i18nSource: {
2706
+ locale: r,
2707
+ title: n
2708
+ }
2709
+ } : e;
2710
+ });
2711
+ }
2712
+ function c(t, n) {
2713
+ let r = t, i = zr(r), a = Cr(i, t.count), o = t.title.trim() || Ur(n, e.value);
2714
+ return {
2715
+ id: t.id,
2716
+ title: o,
2717
+ showTitle: t.showTitle !== !1,
2718
+ kind: i,
2719
+ boardId: Vr(r),
2720
+ count: a,
2721
+ postIds: Hr(r, a),
2722
+ sort: Br(r),
2723
+ cols: Lr(r),
2724
+ height: Rr(r),
2725
+ order: n,
2726
+ ...t.i18n ? { i18n: t.i18n } : {},
2727
+ ...t.i18nSource?.title === o ? { i18nSource: t.i18nSource } : {}
2728
+ };
2729
+ }
2730
+ async function l(e) {
2731
+ let t = e;
2732
+ try {
2733
+ t = await s(e);
2734
+ } catch (e) {
2735
+ console.warn("[memi-board] section translation skipped", e);
2736
+ }
2737
+ await q(r, {
2362
2738
  columns: 12,
2363
- blocks: t.map((t, n) => {
2364
- let r = t, i = _r(r), a = tr(i, t.count);
2365
- return {
2366
- id: t.id,
2367
- title: t.title.trim() || xr(n, e.value),
2368
- showTitle: t.showTitle !== !1,
2369
- kind: i,
2370
- boardId: yr(r),
2371
- count: a,
2372
- postIds: br(r, a),
2373
- sort: vr(r),
2374
- cols: hr(r),
2375
- height: gr(r),
2376
- order: n
2377
- };
2378
- }),
2379
- updatedAt: q()
2739
+ blocks: t.map((e, t) => c(e, t)),
2740
+ updatedAt: K()
2380
2741
  });
2381
2742
  }
2382
2743
  return {
2383
- settings: r,
2384
- pending: i,
2385
- sections: a,
2386
- blocks: a,
2387
- hasSavedLayout: P(() => Array.isArray(r.value?.blocks)),
2388
- saveSections: o,
2389
- saveBlocks: o
2744
+ settings: i,
2745
+ pending: a,
2746
+ sections: o,
2747
+ blocks: o,
2748
+ hasSavedLayout: N(() => Array.isArray(i.value?.blocks)),
2749
+ saveSections: l,
2750
+ saveBlocks: l
2390
2751
  };
2391
2752
  }
2392
2753
  //#endregion
2393
2754
  //#region src/composables/useMemiBoardMiniPosts.ts
2394
- function wr(e, t) {
2395
- let n = Q(), i = () => M(), a = L(e)?.trim() || "all", o = P(() => {
2396
- let r = Math.max(1, Math.floor(Number(L(t)) || 5)), a = L(e)?.trim() || "", o = [Z("isPublished", "==", !0), Z("listed", "==", !0)];
2397
- return a && o.unshift(Z("boardId", "==", a)), K(N(n, i()), ...o, G("createdAt", "desc"), U(r));
2398
- }), { data: s, pending: c, error: l } = ye(o, { ssrKey: r(i(), `mini-list/${a}`) });
2755
+ function Kr(e, t) {
2756
+ let n = Z(), i = () => j(), a = I(e)?.trim() || "all", o = N(() => {
2757
+ let r = Math.max(1, Math.floor(Number(I(t)) || 5)), a = I(e)?.trim() || "", o = [X("isPublished", "==", !0), X("listed", "==", !0)];
2758
+ return a && o.unshift(X("boardId", "==", a)), G(M(n, i()), ...o, W("createdAt", "desc"), H(r));
2759
+ }), { data: s, pending: c, error: l } = be(o, { ssrKey: r(i(), `mini-list/${a}`) });
2399
2760
  return {
2400
- posts: P(() => s.value ?? []),
2761
+ posts: N(() => s.value ?? []),
2401
2762
  pending: c,
2402
2763
  error: l
2403
2764
  };
2404
2765
  }
2405
- function Tr(e) {
2406
- let t = Q(), n = () => M(), i = P(() => [...new Set(L(e).map((e) => e.trim()).filter(Boolean))].slice(0, 10)), a = i.value.join(",") || "none", o = P(() => i.value.length ? K(N(t, n()), Z(z(), "in", i.value)) : null), { data: s, pending: c, error: l } = ye(o, { ssrKey: r(n(), `mini-picked/${a}`) });
2766
+ function qr(e) {
2767
+ 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}`) });
2407
2768
  return {
2408
- posts: P(() => {
2769
+ posts: N(() => {
2409
2770
  let e = new Map((s.value ?? []).map((e) => [e.id, e])), t = [];
2410
2771
  for (let n of i.value) {
2411
2772
  let r = e.get(n);
@@ -2419,7 +2780,21 @@ function Tr(e) {
2419
2780
  }
2420
2781
  //#endregion
2421
2782
  //#region src/data/versionHistory.ts
2422
- var Er = [
2783
+ var Jr = [
2784
+ {
2785
+ version: "0.46.2",
2786
+ date: "2026-10-23",
2787
+ highlights: ["게시판 표시 라벨 옆 버튼으로 이름과 설명을 10개 언어로 번역할 수 있어요", "저장할 때 자동으로 번역할지도 선택할 수 있어요"]
2788
+ },
2789
+ {
2790
+ version: "0.46.1",
2791
+ date: "2026-10-22",
2792
+ highlights: [
2793
+ "게시판 이름과 설명을 저장하면 10개 언어로 번역해 둬요",
2794
+ "글쓰기 AI에서 한 번에 10개 언어로 번역해 저장할 수 있어요",
2795
+ "보는 언어로 제목과 본문이 보이고, 원문을 고치면 번역 대신 원문이 보여요"
2796
+ ]
2797
+ },
2423
2798
  {
2424
2799
  version: "0.46.0",
2425
2800
  date: "2026-10-21",
@@ -2722,7 +3097,7 @@ var Er = [
2722
3097
  highlights: ["글쓰기·댓글·첨부·로그인 권한으로 게시판을 바로 시작할 수 있어요", "부적절한 글·댓글은 AI 검열로 막을 수 있어요"],
2723
3098
  note: "실제 보안은 호스트 Firebase 규칙 등록이 필요합니다."
2724
3099
  }
2725
- ], Dr = 10, Or = 864e5, kr = 200, Ar = {
3100
+ ], Yr = 10, Xr = 864e5, Zr = 200, Qr = {
2726
3101
  proofread: "맞춤법, 띄어쓰기, 문법 오류만 고쳐라. 의미와 말투는 바꾸지 마라.",
2727
3102
  polish: "뜻과 작성자의 말투를 유지하면서 더 자연스럽고 읽기 좋은 문장으로 다듬어라.",
2728
3103
  continue: "앞의 맥락과 말투에 맞는 내용을 과장 없이 한두 문단 이어서 작성하라.",
@@ -2731,7 +3106,7 @@ var Er = [
2731
3106
  "translate-ko": "자연스러운 한국어로 번역하라. 고유명사와 기술 용어는 정확히 유지하라.",
2732
3107
  title: "본문을 대표하는 간결한 한국어 제목 하나를 만들어라. 따옴표나 설명 없이 제목만 출력하라."
2733
3108
  };
2734
- function jr(e) {
3109
+ function $r(e) {
2735
3110
  return {
2736
3111
  proofread: "Fix only spelling, spacing, and grammar errors. Do not change the meaning or tone.",
2737
3112
  polish: "Refine the text into more natural, readable sentences while keeping the meaning and the author's tone.",
@@ -2742,34 +3117,34 @@ function jr(e) {
2742
3117
  title: `Create one concise title in ${e} that represents the body. Output only the title, without quotes or explanation.`
2743
3118
  };
2744
3119
  }
2745
- function Mr(e, t, n) {
2746
- let r = t === "ko", i = Mn(t);
3120
+ function ei(e, t, n) {
3121
+ let r = t === "ko", i = cn(t);
2747
3122
  if (e.action !== "custom") {
2748
- if (r) return Ar[e.action];
2749
- let t = jr(i)[e.action];
3123
+ if (r) return Qr[e.action];
3124
+ let t = $r(i)[e.action];
2750
3125
  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}.`;
2751
3126
  }
2752
- let a = e.customInstruction?.trim().slice(0, kr);
3127
+ let a = e.customInstruction?.trim().slice(0, Zr);
2753
3128
  if (!a) throw Error(n("writingAssistant.customRequired"));
2754
3129
  return r ? `뜻은 유지하면서 다음 스타일 지시를 반영해 다시 써라: ${a}` : `Rewrite the text applying the following style instruction while keeping the meaning: ${a}`;
2755
3130
  }
2756
- function Nr(e) {
3131
+ function ti(e) {
2757
3132
  return e.trim().replace(/^```(?:html|text)?\s*/i, "").replace(/\s*```$/, "").trim();
2758
3133
  }
2759
- function Pr(e) {
3134
+ function ni(e) {
2760
3135
  return !e || typeof e != "object" ? null : "toMillis" in e && typeof e.toMillis == "function" ? e.toMillis() : null;
2761
3136
  }
2762
- function Fr(e = {}) {
2763
- let t = O(), n = P(() => E(L(e.locale)) ?? t.locale.value), r = (e, t) => w(n.value, e, t), i = Se(), a = Q(), o = be(), s = le();
2764
- async function c() {
3137
+ function ri(e = {}) {
3138
+ 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();
3139
+ async function u() {
2765
3140
  let e = o.value?.uid;
2766
3141
  if (!e) throw Error(r("writingAssistant.loginRequired"));
2767
- await _e(a, async (t) => {
2768
- let n = f(a, M(), e), i = await t.get(n);
3142
+ await ve(a, async (t) => {
3143
+ let n = f(a, j(), e), i = await t.get(n);
2769
3144
  if (!i.exists()) throw Error(r("writingAssistant.userMissing"));
2770
- 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;
3145
+ 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;
2771
3146
  if (u && c >= 10) {
2772
- let e = s + Or, t = Math.max(1, Math.ceil((e - l) / 36e5));
3147
+ let e = s + Xr, t = Math.max(1, Math.ceil((e - l) / 36e5));
2773
3148
  throw Error(r("writingAssistant.dailyLimit", {
2774
3149
  limit: 10,
2775
3150
  hours: t
@@ -2777,20 +3152,20 @@ function Fr(e = {}) {
2777
3152
  }
2778
3153
  t.update(n, {
2779
3154
  aiWritingCount: u ? c + 1 : 1,
2780
- aiWritingWindowAt: u ? o.aiWritingWindowAt : q(),
2781
- updatedAt: q()
3155
+ aiWritingWindowAt: u ? o.aiWritingWindowAt : K(),
3156
+ updatedAt: K()
2782
3157
  });
2783
3158
  });
2784
3159
  }
2785
- async function l(e) {
2786
- let t = n.value, a = t === "ko", o = Mr(e, t, r);
3160
+ async function d(e) {
3161
+ let t = n.value, a = t === "ko", o = ei(e, t, r);
2787
3162
  if (!e.content.trim()) throw Error(r("writingAssistant.contentRequired"));
2788
- await c();
2789
- let l = s.moderation ?? {}, u = Le(i, {
2790
- backend: new Fe(),
2791
- useLimitedUseAppCheckTokens: l.useLimitedUseAppCheckTokens !== !1
2792
- }), d = Re(u, {
2793
- model: l.model ?? "gemini-3.5-flash-lite",
3163
+ await u();
3164
+ let c = s.moderation ?? {}, l = Re(i, {
3165
+ backend: new Ie(),
3166
+ useLimitedUseAppCheckTokens: c.useLimitedUseAppCheckTokens !== !1
3167
+ }), d = ze(l, {
3168
+ model: c.model ?? "gemini-3.5-flash-lite",
2794
3169
  systemInstruction: (a ? [
2795
3170
  "너는 게시판 글쓰기 편집 도우미다.",
2796
3171
  "사용자가 요청한 편집 결과만 출력하고 설명, 인사, 마크다운 코드 펜스를 붙이지 마라.",
@@ -2812,94 +3187,64 @@ function Fr(e = {}) {
2812
3187
  `Task: ${o}`,
2813
3188
  e.title ? `Current title: ${e.title}` : "",
2814
3189
  `Body:\n${f.slice(0, 12e3)}`
2815
- ]).filter(Boolean).join("\n\n"), m = Nr((await d.generateContent(p)).response.text());
3190
+ ]).filter(Boolean).join("\n\n"), m = ti((await d.generateContent(p)).response.text());
2816
3191
  if (!m) throw Error(r("writingAssistant.emptyResult"));
2817
3192
  return m;
2818
3193
  }
2819
- return { assist: l };
2820
- }
2821
- //#endregion
2822
- //#region src/composables/useMemiBoardAutoTranslate.ts
2823
- var Ir = 3e4;
2824
- function Lr(e, t) {
2825
- return new Promise((n, r) => {
2826
- let i = setTimeout(() => r(/* @__PURE__ */ Error(`translation timed out after ${t / 1e3}s`)), t);
2827
- e.then((e) => {
2828
- clearTimeout(i), n(e);
2829
- }, (e) => {
2830
- clearTimeout(i), r(e);
2831
- });
2832
- });
2833
- }
2834
- function Rr() {
2835
- let e = Se(), { moderation: t } = le();
2836
- function n(n, r) {
2837
- let i = Le(e, {
2838
- backend: new Fe(),
2839
- useLimitedUseAppCheckTokens: t.useLimitedUseAppCheckTokens !== !1
2840
- }), a = Ie.object({ properties: Object.fromEntries(r.map((e) => [e, Ie.string()])) });
2841
- return Re(i, {
2842
- model: t.model ?? "gemini-3.5-flash-lite",
2843
- systemInstruction: [
2844
- "You translate short user-interface strings for a website.",
2845
- "Return natural, concise UI wording a native speaker would expect in a menu or title — not literal word-for-word translations.",
2846
- "Keep proper nouns, brand names, product names and game titles as they are unless they have a well-known localized name.",
2847
- "Use the context only to understand meaning; never translate or output it."
2848
- ].join("\n"),
2849
- generationConfig: {
2850
- temperature: .2,
2851
- maxOutputTokens: 2048,
2852
- responseMimeType: "application/json",
2853
- responseSchema: Ie.object({ properties: Object.fromEntries(n.map((e) => [e, a])) })
3194
+ async function p(e) {
3195
+ let t = e.source ?? n.value, i = e.title?.trim() ?? "", a = e.summary?.trim() ?? "", o = e.content ?? "", s = {};
3196
+ i && (s.title = i), a && (s.summary = a);
3197
+ let d = !!Kt(o);
3198
+ if (!Object.keys(s).length && !d) throw Error(r("writingAssistant.translateEmpty"));
3199
+ if (d && o.length > 12e3) throw Error(r("writingAssistant.translateTooLong", { limit: Sn }));
3200
+ await u();
3201
+ let [f, p] = await Promise.all([Object.keys(s).length ? c([{
3202
+ id: "post",
3203
+ fields: s,
3204
+ context: "message board post title and short summary"
3205
+ }], {
3206
+ source: t,
3207
+ purpose: "message board post title and short summary"
3208
+ }) : Promise.resolve({}), d ? l(o, { source: t }) : Promise.resolve({})]), m = et(t, s, f.post);
3209
+ if (Object.keys(s).length && !tt(m, Object.keys(s))) throw Error(r("writingAssistant.translateIncomplete"));
3210
+ let h = {
3211
+ i18n: m,
3212
+ i18nSource: {
3213
+ locale: t,
3214
+ title: i,
3215
+ summary: a
2854
3216
  }
2855
- });
2856
- }
2857
- async function r(e, t, r, i) {
2858
- let a = Object.keys(e.fields), o = [
2859
- `Purpose: ${i}`,
2860
- `Source language: ${Mn(t)} (${t})`,
2861
- `Translate into: ${r.map((e) => `${e} = ${Mn(e)}`).join(", ")}`,
2862
- e.context ? `Context (do not translate): ${e.context}` : "",
2863
- "Text (JSON):",
2864
- JSON.stringify(e.fields)
2865
- ].filter(Boolean).join("\n"), s = await Lr(n(r, a).generateContent(o), Ir), c = JSON.parse(s.response.text()), l = {};
2866
- for (let e of r) {
2867
- let t = c?.[e];
2868
- if (!t) continue;
2869
- let n = {};
2870
- for (let e of a) {
2871
- let r = typeof t[e] == "string" ? t[e].trim() : "";
2872
- r && (n[e] = r);
3217
+ };
3218
+ if (d) {
3219
+ let e = {};
3220
+ for (let [t, n] of Object.entries(p)) {
3221
+ let r = T(t);
3222
+ r && typeof n == "string" && n.trim() && (e[r] = { content: n.trim() });
2873
3223
  }
2874
- Object.keys(n).length && (l[e] = n);
3224
+ let n = et(t, { content: o }, e);
3225
+ if (!tt(n, ["content"])) throw Error(r("writingAssistant.translateIncomplete"));
3226
+ h.contentI18n = n, h.contentI18nSource = {
3227
+ locale: t,
3228
+ hash: Xe(o)
3229
+ };
2875
3230
  }
2876
- return l;
3231
+ return h;
2877
3232
  }
2878
- async function i(e, t = {}) {
2879
- let n = t.source ?? "ko", i = (t.targets ?? j.map((e) => e.code)).filter((e) => e !== n), a = t.purpose ?? "website UI labels", o = e.map((e) => ({
2880
- ...e,
2881
- fields: Object.fromEntries(Object.entries(e.fields).map(([e, t]) => [e, t.trim()]).filter(([, e]) => e))
2882
- })).filter((e) => Object.keys(e.fields).length);
2883
- if (!o.length || !i.length) return {};
2884
- let s = await Promise.all(o.map((e) => r(e, n, i, a)));
2885
- return Object.fromEntries(o.map((e, t) => [e.id, s[t]]));
2886
- }
2887
- return { translateEntries: i };
2888
- }
2889
- function zr(e, t, n, r) {
2890
- let i = E(t);
2891
- return !i || i === "ko" ? r : e?.[i]?.[n] || e?.en?.[n] || r;
3233
+ return {
3234
+ assist: d,
3235
+ translatePost: p
3236
+ };
2892
3237
  }
2893
3238
  //#endregion
2894
3239
  //#region src/utils/renderMarkdown.ts
2895
- function Br(e) {
3240
+ function ii(e) {
2896
3241
  return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2897
3242
  }
2898
- function Vr(e) {
3243
+ function ai(e) {
2899
3244
  let t = e;
2900
3245
  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;
2901
3246
  }
2902
- function Hr(e) {
3247
+ function oi(e) {
2903
3248
  if (!e?.trim()) return "";
2904
3249
  let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = 0, i = !1, a = [], o = null, s = [];
2905
3250
  function c() {
@@ -2908,7 +3253,7 @@ function Hr(e) {
2908
3253
  return;
2909
3254
  }
2910
3255
  let e = o;
2911
- n.push(`<${e}>${s.map((e) => `<li>${Vr(e)}</li>`).join("")}</${e}>`), o = null, s = [];
3256
+ n.push(`<${e}>${s.map((e) => `<li>${ai(e)}</li>`).join("")}</${e}>`), o = null, s = [];
2912
3257
  }
2913
3258
  for (; r < t.length;) {
2914
3259
  let e = t[r] ?? "", l = e.match(/^```(\w*)\s*$/);
@@ -2917,7 +3262,7 @@ function Hr(e) {
2917
3262
  continue;
2918
3263
  }
2919
3264
  if (i) {
2920
- a.push(Br(e)), r++;
3265
+ a.push(ii(e)), r++;
2921
3266
  continue;
2922
3267
  }
2923
3268
  if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(e)) {
@@ -2928,24 +3273,24 @@ function Hr(e) {
2928
3273
  if (u) {
2929
3274
  c();
2930
3275
  let e = u[1].length;
2931
- n.push(`<h${e} class="font-semibold mt-4 mb-2">${Vr(Br(u[2]))}</h${e}>`), r++;
3276
+ n.push(`<h${e} class="font-semibold mt-4 mb-2">${ai(ii(u[2]))}</h${e}>`), r++;
2932
3277
  continue;
2933
3278
  }
2934
3279
  if (e.startsWith("> ")) {
2935
3280
  c();
2936
3281
  let e = [];
2937
- for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push(Br((t[r] ?? "").slice(2))), r++;
2938
- n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${Vr(e.join("<br />"))}</blockquote>`);
3282
+ for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push(ii((t[r] ?? "").slice(2))), r++;
3283
+ n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${ai(e.join("<br />"))}</blockquote>`);
2939
3284
  continue;
2940
3285
  }
2941
3286
  let d = e.match(/^[-*+]\s+(.+)$/);
2942
3287
  if (d) {
2943
- o && o !== "ul" && c(), o = "ul", s.push(Br(d[1])), r++;
3288
+ o && o !== "ul" && c(), o = "ul", s.push(ii(d[1])), r++;
2944
3289
  continue;
2945
3290
  }
2946
3291
  let f = e.match(/^\d+\.\s+(.+)$/);
2947
3292
  if (f) {
2948
- o && o !== "ol" && c(), o = "ol", s.push(Br(f[1])), r++;
3293
+ o && o !== "ol" && c(), o = "ol", s.push(ii(f[1])), r++;
2949
3294
  continue;
2950
3295
  }
2951
3296
  if (!e.trim()) {
@@ -2957,13 +3302,13 @@ function Hr(e) {
2957
3302
  for (; r < t.length;) {
2958
3303
  let e = t[r] ?? "";
2959
3304
  if (!e.trim() || /^(#{1,3}\s|```|[-*+]\s|\d+\.\s|> |(-{3,}|\*{3,})\s*$)/.test(e)) break;
2960
- p.push(Br(e)), r++;
3305
+ p.push(ii(e)), r++;
2961
3306
  }
2962
- n.push(`<p class="my-2 leading-relaxed">${Vr(p.join("<br />"))}</p>`);
3307
+ n.push(`<p class="my-2 leading-relaxed">${ai(p.join("<br />"))}</p>`);
2963
3308
  }
2964
3309
  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");
2965
3310
  }
2966
3311
  //#endregion
2967
- 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, ce as DEFAULT_LIKES_COLLECTION, T as DEFAULT_MEMI_BOARD_LOCALE, h as DEFAULT_POSTS_COLLECTION, n as DEFAULT_REPORTS_COLLECTION, se as DEFAULT_SETTINGS_COLLECTION, g as DEFAULT_USERS_COLLECTION, re as FALLBACK_MEMI_BOARD_LOCALE, j 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, ne as formatFullDate, ae 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, A as isMemiBoardLocaleLoaded, ut as isModerationWriteRestricted, d as likeDoc, p as likeDocId, a as likesCol, oe as loadMemiBoardLocale, zr as localizedText, xn as memiBoardCommentLikesKey, ie 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, N 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, te as slugify, St as storagePathFromDownloadUrl, Nt as titleFromBody, He as toAbsoluteUrl, Ve as toBoardOgImageUrl, w as translate, M as useBoardPathConfig, ht as useMemiBoardAuth, fn as useMemiBoardAuthorMemo, Rr as useMemiBoardAutoTranslate, ln as useMemiBoardCommentFeed, Sn as useMemiBoardCommentLikes, vn as useMemiBoardComments, le 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 };
3312
+ 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 };
2968
3313
 
2969
3314
  //# sourceMappingURL=index.js.map