memi-board 0.32.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +17 -2
  2. package/dist/composables/boardLookup.d.ts +8 -0
  3. package/dist/composables/useMemiBoardAuth.d.ts +4 -3
  4. package/dist/composables/useMemiBoardCommentFeed.d.ts +18 -0
  5. package/dist/composables/useMemiBoardComments.d.ts +2 -0
  6. package/dist/composables/useMemiBoardLikes.d.ts +9 -0
  7. package/dist/composables/useMemiBoardMiniPosts.d.ts +12 -0
  8. package/dist/composables/useMemiBoardReports.d.ts +114 -0
  9. package/dist/composables/useMemiBoardSections.d.ts +13 -0
  10. package/dist/composables/useMemiBoardSettings.d.ts +2 -0
  11. package/dist/config.d.ts +2 -0
  12. package/dist/{heic2any-BWRKffWN.js → heic2any-D2QKy06h.js} +2 -2
  13. package/dist/{heic2any-BWRKffWN.js.map → heic2any-D2QKy06h.js.map} +1 -1
  14. package/dist/index.d.ts +12 -3
  15. package/dist/index.js +1161 -647
  16. package/dist/index.js.map +1 -1
  17. package/dist/runtime/components/CategorySettings.vue +3 -5
  18. package/dist/runtime/components/CommentFeed.vue +141 -0
  19. package/dist/runtime/components/CommentForm.vue +2 -2
  20. package/dist/runtime/components/CommentItem.vue +55 -5
  21. package/dist/runtime/components/CommentList.vue +10 -2
  22. package/dist/runtime/components/Detail.vue +8 -0
  23. package/dist/runtime/components/ListDefault.vue +2 -2
  24. package/dist/runtime/components/ListDense.vue +2 -2
  25. package/dist/runtime/components/ListVideo.vue +2 -2
  26. package/dist/runtime/components/Mini.vue +37 -0
  27. package/dist/runtime/components/MiniCard.vue +51 -0
  28. package/dist/runtime/components/MiniComments.vue +75 -0
  29. package/dist/runtime/components/MiniList.vue +36 -0
  30. package/dist/runtime/components/MiniPicker.vue +131 -0
  31. package/dist/runtime/components/MiniPost.vue +52 -0
  32. package/dist/runtime/components/MiniPosts.vue +115 -0
  33. package/dist/runtime/components/ReportButton.vue +117 -0
  34. package/dist/runtime/components/ReportList.vue +124 -0
  35. package/dist/runtime/components/Section.vue +62 -0
  36. package/dist/runtime/components/Settings.vue +3 -4
  37. package/dist/runtime/components/UserCommentList.vue +3 -0
  38. package/dist/{slugify-B5XHr4SZ.js → slugify-Cyq-Gg8X.js} +47 -36
  39. package/dist/slugify-Cyq-Gg8X.js.map +1 -0
  40. package/dist/storage.js +11 -11
  41. package/dist/types.d.ts +54 -3
  42. package/dist/utils/boardAccess.d.ts +7 -0
  43. package/dist/utils/boardPaths.d.ts +10 -3
  44. package/dist/utils/deletePostCascade.d.ts +7 -1
  45. package/dist/utils/section.d.ts +31 -0
  46. package/docs/firestore.indexes.json.example +38 -0
  47. package/docs/firestore.rules.example +78 -22
  48. package/package.json +1 -1
  49. package/dist/slugify-B5XHr4SZ.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,18 +1,18 @@
1
- import { C as e, D as t, E as n, O as r, S as i, T as a, _ as o, a as s, b as c, c as l, d as u, f as d, g as f, h as p, i as m, k as h, l as g, m as _, n as v, o as y, p as b, r as x, s as S, u as C, v as w, w as T, x as E, y as D } from "./slugify-B5XHr4SZ.js";
2
- import { computed as O, onScopeDispose as k, reactive as A, ref as j, toValue as M, watch as N } from "vue";
3
- import { deleteDoc as P, deleteField as F, doc as I, documentId as L, endBefore as ee, getDoc as R, getDocs as z, increment as B, limit as V, limitToLast as te, onSnapshot as H, orderBy as U, query as W, runTransaction as ne, serverTimestamp as G, setDoc as K, startAfter as q, updateDoc as J, where as Y, writeBatch as X } from "firebase/firestore";
4
- import { useCollection as re, useCurrentUser as ie, useDocument as ae, useFirebaseApp as oe, useFirebaseAuth as se, useFirestore as Z } from "vuefire";
5
- import { GoogleAuthProvider as ce, OAuthProvider as le, createUserWithEmailAndPassword as ue, signInWithEmailAndPassword as de, signInWithPopup as fe, signOut as pe, updateProfile as me } from "firebase/auth";
6
- import { deleteObject as he, getStorage as ge, listAll as _e, ref as ve } from "firebase/storage";
7
- import { GoogleAIBackend as ye, Schema as be, getAI as xe, getGenerativeModel as Se } from "firebase/ai";
1
+ import { A as e, C as t, D as n, E as r, M as i, N as a, O as o, S as s, T as c, _ as l, a as u, b as d, c as f, d as p, f as m, g as h, h as g, i as _, j as v, k as y, l as b, m as x, n as S, o as C, p as w, r as T, s as E, u as D, v as O, w as k, x as A, y as j } from "./slugify-Cyq-Gg8X.js";
2
+ import { computed as M, onScopeDispose as N, reactive as ee, ref as P, toValue as F, watch as I } from "vue";
3
+ import { deleteDoc as te, deleteField as ne, doc as re, documentId as L, endBefore as ie, getDoc as R, getDocs as z, increment as B, limit as V, limitToLast as ae, onSnapshot as H, orderBy as U, query as W, runTransaction as oe, serverTimestamp as G, setDoc as K, startAfter as q, updateDoc as J, where as Y, writeBatch as X } from "firebase/firestore";
4
+ import { useCollection as se, useCurrentUser as Z, useDocument as ce, useFirebaseApp as le, useFirebaseAuth as ue, useFirestore as Q } from "vuefire";
5
+ import { GoogleAuthProvider as de, OAuthProvider as fe, createUserWithEmailAndPassword as pe, signInWithEmailAndPassword as me, signInWithPopup as he, signOut as ge, updateProfile as _e } from "firebase/auth";
6
+ import { deleteObject as ve, getStorage as ye, listAll as be, ref as xe } from "firebase/storage";
7
+ import { GoogleAIBackend as Se, Schema as Ce, getAI as we, getGenerativeModel as Te } from "firebase/ai";
8
8
  //#region src/utils/boardSeo.ts
9
- function Ce(e) {
9
+ function Ee(e) {
10
10
  if (typeof e != "string") return null;
11
11
  let t = e.trim();
12
12
  return /^https?:\/\//i.test(t) ? t : null;
13
13
  }
14
- function we(e) {
15
- let t = Ce(e);
14
+ function De(e) {
15
+ let t = Ee(e);
16
16
  if (!t) return null;
17
17
  if (!/firebasestorage\.googleapis\.com/i.test(t)) return t;
18
18
  try {
@@ -29,62 +29,62 @@ function we(e) {
29
29
  return t;
30
30
  }
31
31
  }
32
- function Te(e, t, n = "") {
32
+ function Oe(e, t, n = "") {
33
33
  let r = t.replace(/\/$/, ""), i = e.trim();
34
- return i ? /^https?:\/\//i.test(i) ? i : `${r}${i.startsWith("/") ? i : `/${i}`}` : n ? Te(n, t) : r;
34
+ return i ? /^https?:\/\//i.test(i) ? i : `${r}${i.startsWith("/") ? i : `/${i}`}` : n ? Oe(n, t) : r;
35
35
  }
36
- function Ee(e, t, n, r) {
36
+ function ke(e, t, n, r) {
37
37
  let i = r?.trim().slice(0, 40), a = e.trim() || i || "사진", o = t?.trim(), s = n.trim() || "Board";
38
38
  return o ? `${a} · ${o} | ${s}` : `${a} | ${s}`;
39
39
  }
40
- function De(e) {
40
+ function Ae(e) {
41
41
  let t = [];
42
42
  return e.summary?.trim() ? t.push(e.summary.trim().slice(0, 140)) : t.push("게시판 글"), e.categoryLabel?.trim() && t.push(e.categoryLabel.trim()), e.authorName?.trim() && t.push(e.authorName.trim()), t.join(" · ").slice(0, 200);
43
43
  }
44
- function Oe(e, t) {
44
+ function je(e, t) {
45
45
  let n = t.trim() || "Board", r = e?.trim();
46
46
  return r && r !== "전체" ? `${r} 게시판 | ${n}` : `게시판 | ${n}`;
47
47
  }
48
- function ke(e) {
48
+ function Me(e) {
49
49
  if (e.description?.trim()) return e.description.trim().slice(0, 200);
50
50
  let t = e.categoryLabel?.trim(), n = t && t !== "전체" ? `${t} 게시판 — 소식을 확인하세요.` : "게시판 — 공지와 이야기를 확인하세요.", r = (e.recentTitles || []).filter(Boolean).slice(0, 3);
51
51
  return r.length ? `${n} ${r.map((e) => `「${e}」`).join(" ")}`.slice(0, 200) : n.slice(0, 200);
52
52
  }
53
- function Ae(e) {
53
+ function Ne(e) {
54
54
  let t = (e || "/board").trim() || "/board";
55
55
  return (t.startsWith("/") ? t : `/${t}`).replace(/\/$/, "") || "/board";
56
56
  }
57
57
  //#endregion
58
58
  //#region src/composables/fetchPublicSeo.ts
59
- function je() {
59
+ function Pe() {
60
60
  return {
61
- db: Z(),
62
- paths: m()
61
+ db: Q(),
62
+ paths: _()
63
63
  };
64
64
  }
65
- async function Me(e, n) {
65
+ async function Fe(e, t) {
66
66
  try {
67
- let r = await R(t(e.db, e.paths, n));
68
- if (!r.exists()) return {
69
- label: n,
67
+ let n = await R(v(e.db, e.paths, t));
68
+ if (!n.exists()) return {
69
+ label: t,
70
70
  description: "",
71
71
  hidden: !1
72
72
  };
73
- let i = r.data() ?? {};
73
+ let r = n.data() ?? {};
74
74
  return {
75
- label: typeof i.label == "string" && i.label.trim() ? i.label.trim() : n,
76
- description: typeof i.description == "string" ? i.description.trim() : "",
77
- hidden: i.visibility === "hidden"
75
+ label: typeof r.label == "string" && r.label.trim() ? r.label.trim() : t,
76
+ description: typeof r.description == "string" ? r.description.trim() : "",
77
+ hidden: r.visibility === "hidden"
78
78
  };
79
79
  } catch {
80
80
  return {
81
- label: n,
81
+ label: t,
82
82
  description: "",
83
83
  hidden: !1
84
84
  };
85
85
  }
86
86
  }
87
- function Ne(e) {
87
+ function Ie(e) {
88
88
  if (e && typeof e == "object" && "toDate" in e) try {
89
89
  let t = e.toDate();
90
90
  return t instanceof Date ? t.toISOString() : null;
@@ -93,36 +93,36 @@ function Ne(e) {
93
93
  }
94
94
  return null;
95
95
  }
96
- async function Pe(e, t, n) {
96
+ async function Le(e, t, n) {
97
97
  let r = e.trim(), i = t.trim();
98
98
  if (!r || !i) return null;
99
- let o = n ?? je();
100
- o.paths = a(o.paths);
101
- let s = await Me(o, r);
102
- if (s.hidden) return null;
99
+ let a = n ?? Pe();
100
+ a.paths = y(a.paths);
101
+ let o = await Fe(a, r);
102
+ if (o.hidden) return null;
103
103
  try {
104
- let e = (await z(W(T(o.db, o.paths), Y("boardId", "==", r), Y("slug", "==", i), Y("listed", "==", !0), Y("isPublished", "==", !0), V(1)))).docs[0];
104
+ let e = (await z(W(c(a.db, a.paths), Y("boardId", "==", r), Y("slug", "==", i), Y("listed", "==", !0), Y("isPublished", "==", !0), V(1)))).docs[0];
105
105
  if (!e) return null;
106
106
  let t = e.data() ?? {}, n = typeof t.title == "string" ? t.title.trim() : "";
107
107
  return n ? {
108
108
  id: e.id,
109
109
  slug: typeof t.slug == "string" ? t.slug : i,
110
110
  category: r,
111
- categoryLabel: s.label,
111
+ categoryLabel: o.label,
112
112
  title: n,
113
113
  summary: typeof t.summary == "string" ? t.summary.trim().slice(0, 200) : "",
114
- previewImage: we(t.previewImage),
114
+ previewImage: De(t.previewImage),
115
115
  authorName: typeof t.authorName == "string" && t.authorName.trim() ? t.authorName.trim() : null,
116
- createdAt: Ne(t.createdAt)
116
+ createdAt: Ie(t.createdAt)
117
117
  } : null;
118
118
  } catch (e) {
119
119
  return import.meta.dev && console.warn("[memi-board] fetchPublicPostForSeo failed", e), null;
120
120
  }
121
121
  }
122
- async function Fe(e, t) {
123
- let n = e?.trim() || null, r = t ?? je();
124
- if (r.paths = a(r.paths), n) {
125
- let e = await Me(r, n);
122
+ async function Re(e, t) {
123
+ let n = e?.trim() || null, r = t ?? Pe();
124
+ if (r.paths = y(r.paths), n) {
125
+ let e = await Fe(r, n);
126
126
  if (e.hidden) return {
127
127
  kind: "category",
128
128
  category: n,
@@ -131,7 +131,7 @@ async function Fe(e, t) {
131
131
  ogImage: null,
132
132
  recentTitles: []
133
133
  };
134
- let { recentTitles: t, ogImage: i } = await Ie(r, n);
134
+ let { recentTitles: t, ogImage: i } = await ze(r, n);
135
135
  return {
136
136
  kind: "category",
137
137
  category: n,
@@ -150,13 +150,13 @@ async function Fe(e, t) {
150
150
  recentTitles: []
151
151
  };
152
152
  }
153
- async function Ie(e, t) {
153
+ async function ze(e, t) {
154
154
  let n = [], r = null;
155
155
  try {
156
- let i = await z(W(T(e.db, e.paths), Y("boardId", "==", t), Y("listed", "==", !0), Y("isPublished", "==", !0), U("createdAt", "desc"), V(12)));
156
+ let i = await z(W(c(e.db, e.paths), Y("boardId", "==", t), Y("listed", "==", !0), Y("isPublished", "==", !0), U("createdAt", "desc"), V(12)));
157
157
  for (let e of i.docs) {
158
158
  let t = e.data() ?? {}, i = typeof t.title == "string" ? t.title.trim() : "";
159
- i && n.length < 5 && n.push(i), r ||= we(t.previewImage);
159
+ i && n.length < 5 && n.push(i), r ||= De(t.previewImage);
160
160
  }
161
161
  } catch (e) {
162
162
  import.meta.dev && console.warn("[memi-board] loadRecentListed failed", e);
@@ -167,9 +167,26 @@ async function Ie(e, t) {
167
167
  };
168
168
  }
169
169
  //#endregion
170
+ //#region src/utils/boardAccess.ts
171
+ function Be(e, t) {
172
+ return !e || !t ? !1 : (t.allowedStaffUids ?? []).includes(e);
173
+ }
174
+ function Ve(e, t, n, r) {
175
+ return e ? t ? !0 : n ? Be(e, r) : !1 : !1;
176
+ }
177
+ //#endregion
178
+ //#region src/composables/boardLookup.ts
179
+ var He;
180
+ function Ue(e) {
181
+ He = e;
182
+ }
183
+ function We(e) {
184
+ return He?.(e);
185
+ }
186
+ //#endregion
170
187
  //#region src/utils/moderation-strike.ts
171
- var Le = 10, Re = 864e5;
172
- function ze(e, t, n = Date.now(), r = Re) {
188
+ var Ge = 10, Ke = 864e5;
189
+ function qe(e, t, n = Date.now(), r = Ke) {
173
190
  let i = Math.max(0, Math.floor(Number(e) || 0));
174
191
  if (i <= 0) return 0;
175
192
  let a = typeof t == "number" && Number.isFinite(t) ? t : null;
@@ -177,7 +194,7 @@ function ze(e, t, n = Date.now(), r = Re) {
177
194
  let o = Math.floor((n - a) / r);
178
195
  return Math.max(0, i - o);
179
196
  }
180
- function Be(e, t, n = Date.now(), r = 10, i = Re) {
197
+ function Je(e, t, n = Date.now(), r = 10, i = Ke) {
181
198
  let a = Math.max(0, Math.floor(Number(e) || 0));
182
199
  if (a < r) return null;
183
200
  let o = typeof t == "number" && Number.isFinite(t) ? t : null;
@@ -185,17 +202,17 @@ function Be(e, t, n = Date.now(), r = 10, i = Re) {
185
202
  let s = o + (a - (r - 1)) * i;
186
203
  return s > n ? s : null;
187
204
  }
188
- function Ve(e, t, n = Date.now(), r = 10, i = Re) {
189
- return Be(e, t, n, r, i) != null;
205
+ function Ye(e, t, n = Date.now(), r = 10, i = Ke) {
206
+ return Je(e, t, n, r, i) != null;
190
207
  }
191
- function He(e, t = Date.now()) {
208
+ function Xe(e, t = Date.now()) {
192
209
  let n = Math.max(0, e - t), r = Math.ceil(n / 36e5);
193
210
  if (r <= 0) return "곧 해제";
194
211
  if (r < 24) return `약 ${r}시간 후 해제`;
195
212
  let i = Math.floor(r / 24), a = r % 24;
196
213
  return a === 0 ? `약 ${i}일 후 해제` : `약 ${i}일 ${a}시간 후 해제`;
197
214
  }
198
- function Ue(e) {
215
+ function Ze(e) {
199
216
  if (e == null) return null;
200
217
  if (typeof e == "number" && Number.isFinite(e)) return e;
201
218
  if (typeof e == "object" && e && "toMillis" in e && typeof e.toMillis == "function") return e.toMillis();
@@ -207,39 +224,39 @@ function Ue(e) {
207
224
  }
208
225
  //#endregion
209
226
  //#region src/composables/useMemiBoardAuth.ts
210
- var We = "__MEMI_BOARD_AUTH_STATE__";
211
- function Ge() {
227
+ var Qe = "__MEMI_BOARD_AUTH_STATE__";
228
+ function $e() {
212
229
  let e = globalThis;
213
- return e[We] || (e[We] = {
214
- role: j("user"),
215
- rolePending: j(!0),
216
- rawBlockCount: j(0),
217
- blockAtMs: j(null),
230
+ return e[Qe] || (e[Qe] = {
231
+ role: P("user"),
232
+ rolePending: P(!0),
233
+ rawBlockCount: P(0),
234
+ blockAtMs: P(null),
218
235
  watchStarted: !1
219
- }), e[We];
236
+ }), e[Qe];
220
237
  }
221
- function Ke() {
222
- let e = s(), t = Ge(), n = Z(), r = se(), i = ie(), { role: a, rolePending: o, rawBlockCount: c, blockAtMs: l } = t;
223
- function u() {
238
+ function et() {
239
+ let e = u(), t = $e(), n = Q(), r = ue(), i = Z(), { role: a, rolePending: o, rawBlockCount: s, blockAtMs: c } = t;
240
+ function l() {
224
241
  let t = e.moderation?.blockBanThreshold;
225
242
  return t === 0 ? 0 : typeof t == "number" && t > 0 ? Math.floor(t) : 10;
226
243
  }
227
244
  function d() {
228
245
  let t = e.moderation?.blockBanDecayMs;
229
- return typeof t == "number" && t > 0 ? t : Re;
246
+ return typeof t == "number" && t > 0 ? t : Ke;
230
247
  }
231
248
  function f(e) {
232
- return b(n, m(), e);
249
+ return x(n, _(), e);
233
250
  }
234
251
  function p(e) {
235
252
  if (!e) {
236
- c.value = 0, l.value = null;
253
+ s.value = 0, c.value = null;
237
254
  return;
238
255
  }
239
- c.value = Math.max(0, Math.floor(Number(e.moderationBlockCount) || 0)), l.value = Ue(e.moderationBlockAt);
256
+ s.value = Math.max(0, Math.floor(Number(e.moderationBlockCount) || 0)), c.value = Ze(e.moderationBlockAt);
240
257
  }
241
- async function h(e) {
242
- let t = f(e.uid), r = _(n, m(), e.uid), i = await R(t);
258
+ async function m(e) {
259
+ let t = f(e.uid), r = g(n, _(), e.uid), i = await R(t);
243
260
  if (!i.exists()) return await Promise.all([K(t, {
244
261
  role: "user",
245
262
  displayName: e.displayName,
@@ -260,7 +277,7 @@ function Ke() {
260
277
  "user"
261
278
  ].includes(String(a.role)) ? a.role : "user";
262
279
  }
263
- async function g() {
280
+ async function h() {
264
281
  let e = i.value?.uid;
265
282
  if (!e) {
266
283
  p(void 0);
@@ -269,34 +286,34 @@ function Ke() {
269
286
  let t = await R(f(e));
270
287
  p(t.exists() ? t.data() : void 0);
271
288
  }
272
- t.watchStarted || (t.watchStarted = !0, N(i, async (e) => {
289
+ t.watchStarted || (t.watchStarted = !0, I(i, async (e) => {
273
290
  if (o.value = !0, !e) {
274
291
  a.value = "user", p(void 0), o.value = !1;
275
292
  return;
276
293
  }
277
294
  try {
278
- a.value = await h(e);
295
+ a.value = await m(e);
279
296
  } catch (e) {
280
297
  console.error("[memi-board] ensureUserDoc failed", e), a.value = "user", p(void 0);
281
298
  } finally {
282
299
  o.value = !1;
283
300
  }
284
301
  }, { immediate: !0 }));
285
- let v = O(() => !!i.value), y = O(() => a.value === "admin"), x = O(() => a.value === "staff"), S = O(() => y.value || x.value), C = O(() => ze(c.value, l.value, Date.now(), d())), w = O(() => {
286
- let e = u();
287
- return e <= 0 ? !1 : Ve(c.value, l.value, Date.now(), e, d());
288
- }), T = O(() => {
289
- let e = u();
302
+ let v = M(() => !!i.value), y = M(() => a.value === "admin"), b = M(() => a.value === "staff"), S = M(() => y.value || b.value), C = M(() => qe(s.value, c.value, Date.now(), d())), w = M(() => {
303
+ let e = l();
304
+ return e <= 0 ? !1 : Ye(s.value, c.value, Date.now(), e, d());
305
+ }), T = M(() => {
306
+ let e = l();
290
307
  if (e <= 0) return null;
291
- let t = Be(c.value, l.value, Date.now(), e, d());
292
- return t == null ? null : He(t);
293
- }), E = O(() => {
308
+ let t = Je(s.value, c.value, Date.now(), e, d());
309
+ return t == null ? null : Xe(t);
310
+ }), E = M(() => {
294
311
  if (!w.value) return null;
295
312
  let e = T.value;
296
313
  return e ? `콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요. (${e})` : "콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요.";
297
314
  });
298
315
  async function D() {
299
- let e = i.value?.uid, t = u();
316
+ let e = i.value?.uid, t = l();
300
317
  if (!e || t <= 0) return {
301
318
  effectiveCount: 0,
302
319
  restricted: !1,
@@ -315,75 +332,79 @@ function Ke() {
315
332
  updatedAt: G()
316
333
  });
317
334
  let a = (r.exists() ? Math.max(0, Math.floor(Number(r.data().moderationBlockCount) || 0)) : 0) + 1, o = Date.now();
318
- c.value = a, l.value = o;
319
- let s = ze(a, o, o, d()), p = Ve(a, o, o, t, d()), m = ` (경고 ${s}/${t})`;
335
+ s.value = a, c.value = o;
336
+ let u = qe(a, o, o, d()), p = Ye(a, o, o, t, d()), m = ` (경고 ${u}/${t})`;
320
337
  if (p) {
321
- let e = Be(a, o, o, t, d()), n = e == null ? null : He(e, o);
322
- m = n ? ` 경고 ${s}회가 되어 글·댓글 작성이 제한됩니다 (${n}).` : ` 경고 ${s}회가 되어 글·댓글 작성이 제한됩니다.`;
338
+ let e = Je(a, o, o, t, d()), n = e == null ? null : Xe(e, o);
339
+ m = n ? ` 경고 ${u}회가 되어 글·댓글 작성이 제한됩니다 (${n}).` : ` 경고 ${u}회가 되어 글·댓글 작성이 제한됩니다.`;
323
340
  }
324
341
  return {
325
- effectiveCount: s,
342
+ effectiveCount: u,
326
343
  restricted: p,
327
344
  messageSuffix: m
328
345
  };
329
346
  }
330
- function k() {
347
+ function O() {
331
348
  if (!r) throw Error("[memi-board] Firebase Auth가 초기화되지 않았습니다.");
332
349
  return r;
333
350
  }
351
+ async function k() {
352
+ return he(O(), new de());
353
+ }
334
354
  async function A() {
335
- return fe(k(), new ce());
355
+ let e = new fe("apple.com");
356
+ return e.addScope("email"), e.addScope("name"), he(O(), e);
336
357
  }
337
- async function j() {
338
- let e = new le("apple.com");
339
- return e.addScope("email"), e.addScope("name"), fe(k(), e);
358
+ async function j(e, t) {
359
+ return me(O(), e, t);
340
360
  }
341
- async function M(e, t) {
342
- return de(k(), e, t);
361
+ async function N(e, t, n) {
362
+ let r = await pe(O(), e, t);
363
+ return n && await _e(r.user, { displayName: n }), r;
343
364
  }
344
- async function P(e, t, n) {
345
- let r = await ue(k(), e, t);
346
- return n && await me(r.user, { displayName: n }), r;
365
+ async function ee() {
366
+ await ge(O());
347
367
  }
348
- async function F() {
349
- await pe(k());
368
+ function P(e) {
369
+ return Ve(i.value?.uid, y.value, b.value, e ? We(e) : void 0);
350
370
  }
351
- function I(e) {
352
- return v.value && (i.value?.uid === e.authorUid || S.value);
371
+ function F(e) {
372
+ return v.value && (i.value?.uid === e.authorUid || P(e.boardId));
353
373
  }
354
- function L(e) {
374
+ function te(e) {
355
375
  return v.value && i.value?.uid === e.authorUid;
356
376
  }
357
- function ee(e) {
358
- return v.value && (i.value?.uid === e.authorUid || S.value);
377
+ function ne(e) {
378
+ return v.value && (i.value?.uid === e.authorUid || P(e.boardId));
359
379
  }
360
380
  return {
361
381
  user: i,
362
382
  isSignedIn: v,
363
383
  isAdmin: y,
364
- isStaff: x,
384
+ isStaff: b,
365
385
  canManageContent: S,
386
+ canManageBoard: P,
366
387
  rolePending: o,
367
388
  moderationBlockCount: C,
368
389
  isWriteRestricted: w,
369
390
  restrictedUntilLabel: T,
370
391
  restrictedMessage: E,
371
- signInWithGoogle: A,
372
- signInWithApple: j,
373
- signInWithEmail: M,
374
- signUpWithEmail: P,
375
- signOut: F,
376
- canEdit: I,
377
- canDelete: I,
378
- canEditComment: L,
379
- canDeleteComment: ee,
392
+ signInWithGoogle: k,
393
+ signInWithApple: A,
394
+ signInWithEmail: j,
395
+ signUpWithEmail: N,
396
+ signOut: ee,
397
+ canEdit: F,
398
+ canDelete: F,
399
+ canEditComment: te,
400
+ canDeleteComment: ne,
380
401
  recordContentModerationBlock: D,
381
- refreshBoardUser: g
402
+ refreshBoardUser: h
382
403
  };
383
404
  }
384
405
  //#endregion
385
406
  //#region src/utils/extractEditorImageUrls.ts
386
- function qe(e) {
407
+ function tt(e) {
387
408
  if (!e) return [];
388
409
  let t = /* @__PURE__ */ new Set();
389
410
  for (let n of e.matchAll(/!\[[^\]]*]\((https?:\/\/[^)\s]+)\)/g)) n[1] && t.add(n[1].replaceAll("&amp;", "&"));
@@ -392,7 +413,7 @@ function qe(e) {
392
413
  }
393
414
  //#endregion
394
415
  //#region src/utils/storagePath.ts
395
- function Je(e) {
416
+ function nt(e) {
396
417
  try {
397
418
  let t = new URL(e), n = t.hostname.toLowerCase();
398
419
  if (!(n === "firebasestorage.googleapis.com" || n.endsWith(".firebasestorage.app"))) return null;
@@ -402,90 +423,104 @@ function Je(e) {
402
423
  return null;
403
424
  }
404
425
  }
405
- function Ye(e) {
426
+ function rt(e) {
406
427
  return e.match(/(?:^|\/)posts\/([^/]+)\//)?.[1] ?? null;
407
428
  }
408
429
  //#endregion
409
430
  //#region src/utils/deletePostCascade.ts
410
- async function Xe(e) {
411
- let t = await _e(e);
412
- await Promise.all([...t.items.map((e) => he(e)), ...t.prefixes.map((e) => Xe(e))]);
431
+ async function it(e) {
432
+ let t = await be(e);
433
+ await Promise.all([...t.items.map((e) => ve(e)), ...t.prefixes.map((e) => it(e))]);
413
434
  }
414
- async function Ze(e, t) {
435
+ async function at(e, t) {
415
436
  let n = [...new Set([...t].filter(Boolean))];
416
- await Promise.all(n.map((t) => he(ve(e, t)).catch(() => {})));
417
- }
418
- async function Qe(t, n, r, a) {
419
- let s = i(t, r, a), c = E(t, r, a), [l, u] = await Promise.all([R(s), R(c)]);
420
- if (!l.exists()) return;
421
- let d = l.data(), f = u.exists() ? String(u.data().content ?? "") : "", p = await z(W(o(t, r), Y("postId", "==", a)));
422
- for (let e = 0; e < p.docs.length; e += 450) {
423
- let n = X(t);
424
- p.docs.slice(e, e + 450).forEach((e) => n.delete(e.ref)), await n.commit();
425
- }
426
- let m = /* @__PURE__ */ new Set(), h = /* @__PURE__ */ new Set();
427
- for (let e of d.attachments ?? []) {
437
+ await Promise.all(n.map((t) => ve(xe(e, t)).catch(() => {})));
438
+ }
439
+ var ot = 450;
440
+ async function st(e, t) {
441
+ let n = await z(t);
442
+ for (let t = 0; t < n.docs.length; t += ot) {
443
+ let r = X(e);
444
+ n.docs.slice(t, t + ot).forEach((e) => r.delete(e.ref)), await r.commit();
445
+ }
446
+ }
447
+ async function ct(e, t, n) {
448
+ await st(e, W(A(e, t), Y("postId", "==", n)));
449
+ }
450
+ async function lt(e, t, n) {
451
+ await st(e, W(o(e, t), Y("postId", "==", n)));
452
+ }
453
+ async function ut(e, t, n) {
454
+ await st(e, W(A(e, t), Y("commentId", "==", n)));
455
+ }
456
+ async function dt(e, n, r, i) {
457
+ let a = t(e, r, i), o = s(e, r, i), [c, l] = await Promise.all([R(a), R(o)]);
458
+ if (!c.exists()) return;
459
+ let u = c.data(), d = l.exists() ? String(l.data().content ?? "") : "";
460
+ await st(e, W(O(e, r), Y("postId", "==", i))), await ct(e, r, i), await lt(e, r, i);
461
+ let f = /* @__PURE__ */ new Set(), p = /* @__PURE__ */ new Set();
462
+ for (let e of u.attachments ?? []) {
428
463
  if (!e?.path) continue;
429
- m.add(e.path);
430
- let t = Ye(e.path);
431
- t && t !== a && h.add(t);
464
+ f.add(e.path);
465
+ let t = rt(e.path);
466
+ t && t !== i && p.add(t);
432
467
  }
433
- for (let e of qe(f)) {
434
- let t = Je(e);
468
+ for (let e of tt(d)) {
469
+ let t = nt(e);
435
470
  if (!t) continue;
436
- m.add(t);
471
+ f.add(t);
437
472
  let n = t.replace(/\/images\/([^/]+)$/, "/images/thumbnails/$1").replace(/\.[^.]+$/, ".jpg");
438
- n !== t && n.includes("/images/thumbnails/") && m.add(n);
473
+ n !== t && n.includes("/images/thumbnails/") && f.add(n);
439
474
  let r = t.match(/^(.*\/images\/)([^/]+)\.[^.]+$/);
440
- r && !t.includes("/thumbnails/") && m.add(`${r[1]}thumbnails/${r[2]}.jpg`);
441
- let i = Ye(t);
442
- i && i !== a && h.add(i);
475
+ r && !t.includes("/thumbnails/") && f.add(`${r[1]}thumbnails/${r[2]}.jpg`);
476
+ let a = rt(t);
477
+ a && a !== i && p.add(a);
443
478
  }
444
- await P(c), await Xe(ve(n, e(r, a)));
445
- for (let t of h) await Xe(ve(n, e(r, t)));
446
- await Ze(n, m), await P(s);
479
+ await te(o), await it(xe(n, k(r, i)));
480
+ for (let e of p) await it(xe(n, k(r, e)));
481
+ await at(n, f), await te(a);
447
482
  }
448
483
  //#endregion
449
484
  //#region src/utils/postBody.ts
450
- function $e(e) {
485
+ function ft(e) {
451
486
  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();
452
487
  }
453
- function et(e, t = 40) {
454
- let n = $e(e);
488
+ function pt(e, t = 40) {
489
+ let n = ft(e);
455
490
  if (!n) return "";
456
491
  if (n.length <= t) return n;
457
492
  let r = n.slice(0, t), i = r.lastIndexOf(" ");
458
493
  return `${(i >= Math.floor(t * .6) ? r.slice(0, i) : r).trimEnd()}…`;
459
494
  }
460
- function tt(e) {
461
- return $e(e).length > 0;
495
+ function mt(e) {
496
+ return ft(e).length > 0;
462
497
  }
463
- function nt(e, t) {
498
+ function ht(e, t) {
464
499
  return /<img\b/i.test(String(e || "")) ? !0 : (t || []).some((e) => !!e?.type?.startsWith("image/"));
465
500
  }
466
- function rt(e) {
467
- return !tt(e);
501
+ function gt(e) {
502
+ return !mt(e);
468
503
  }
469
504
  //#endregion
470
505
  //#region src/utils/postPreview.ts
471
- function it(e) {
506
+ function _t(e) {
472
507
  return e.replace(/&amp;/g, "&").replace(/&quot;/g, "\"").replace(/&#39;/g, "'");
473
508
  }
474
- function at(e) {
509
+ function vt(e) {
475
510
  if (e) try {
476
- let t = new URL(it(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
511
+ let t = new URL(_t(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
477
512
  if (n === "youtu.be") return t.pathname.split("/").filter(Boolean)[0];
478
513
  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;
479
514
  } catch {
480
515
  return;
481
516
  }
482
517
  }
483
- function ot(e) {
484
- let t = at(e.videoUrl);
518
+ function yt(e) {
519
+ let t = vt(e.videoUrl);
485
520
  return t ? `https://i.ytimg.com/vi/${t}/hqdefault.jpg` : e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
486
521
  }
487
- function st(e, t = []) {
488
- 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 ? it(r[1]) : a, s = o ? at(o) : void 0, c = n ? it(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
522
+ function bt(e, t = []) {
523
+ 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 ? _t(r[1]) : a, s = o ? vt(o) : void 0, c = n ? _t(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
489
524
  return {
490
525
  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),
491
526
  ...c ? { previewImage: c } : {},
@@ -494,7 +529,7 @@ function st(e, t = []) {
494
529
  }
495
530
  //#endregion
496
531
  //#region src/composables/useMemiBoardSettings.ts
497
- var ct = [
532
+ var xt = [
498
533
  {
499
534
  id: "free",
500
535
  label: "자유",
@@ -522,59 +557,64 @@ var ct = [
522
557
  visibility: "public",
523
558
  order: 2
524
559
  }
525
- ], lt = ct;
526
- function ut(e) {
560
+ ], St = xt;
561
+ function Ct(e) {
527
562
  return e === "hidden" ? "hidden" : "public";
528
563
  }
529
- function dt(e, t, n) {
564
+ function wt(e, t, n) {
530
565
  return {
531
566
  id: e,
532
567
  label: typeof t.label == "string" && t.label.trim() ? t.label.trim() : e,
533
568
  order: typeof t.order == "number" ? t.order : n,
534
569
  description: (typeof t.description == "string" ? t.description : "") || "",
535
- visibility: ut(t.visibility),
570
+ visibility: Ct(t.visibility),
536
571
  listView: t.listView ?? "default",
537
572
  writeRole: t.writeRole ?? "user",
538
573
  commentWriteRole: t.commentWriteRole ?? "user",
539
574
  allowedStaffUids: Array.isArray(t.allowedStaffUids) ? t.allowedStaffUids : []
540
575
  };
541
576
  }
542
- function ft() {
543
- let e = () => m(), r = Z(), i = oe(), { isSignedIn: a } = Ke(), o = O(() => W(n(r, e()), U("order", "asc"))), { data: s, pending: c } = re(o, { ssrKey: d(e(), "boards") }), l = O(() => s.value.map((e, t) => dt(e.id, e, t))), u = l, f = O(() => l.value.filter((e) => e.visibility !== "hidden")), p = f;
544
- function h(e) {
545
- if (e) return l.value.find((t) => t.id === e)?.label ?? e;
546
- }
547
- let g = h;
548
- function _(e) {
549
- if (e) return l.value.find((t) => t.id === e)?.description?.trim() || void 0;
577
+ function Tt() {
578
+ let t = () => _(), n = Q(), r = le(), { isSignedIn: i, isAdmin: a, isStaff: o, user: s } = et(), l = M(() => W(e(n, t()), U("order", "asc"))), { data: u, pending: d } = se(l, { ssrKey: w(t(), "boards") }), f = M(() => u.value.map((e, t) => wt(e.id, e, t))), p = f, m = M(() => f.value.filter((e) => e.visibility !== "hidden")), h = m;
579
+ function g(e) {
580
+ if (e) return f.value.find((t) => t.id === e)?.label ?? e;
550
581
  }
551
- let y = _;
582
+ let y = g;
552
583
  function b(e) {
553
- return e ? l.value.find((t) => t.id === e)?.visibility ?? "public" : "public";
584
+ if (e) return f.value.find((t) => t.id === e)?.description?.trim() || void 0;
554
585
  }
555
586
  let x = b;
556
- function S(e) {
557
- return b(e) === "hidden";
587
+ function C(e) {
588
+ return e ? f.value.find((t) => t.id === e)?.visibility ?? "public" : "public";
589
+ }
590
+ let T = C;
591
+ function E(e) {
592
+ return C(e) === "hidden";
593
+ }
594
+ let D = E;
595
+ function O(e) {
596
+ if (e) return f.value.find((t) => t.id === e);
558
597
  }
559
- let C = S;
560
- function w(e) {
561
- if (e) return l.value.find((t) => t.id === e);
598
+ Ue((e) => O(e));
599
+ function k(e) {
600
+ return Ve(s.value?.uid, a.value, o.value, e ? O(e) : void 0);
562
601
  }
563
- async function E(t, n) {
602
+ let A = M(() => f.value.filter((e) => k(e.id)));
603
+ async function j(e, r) {
564
604
  let i;
565
605
  for (;;) {
566
- let a = await z(W(T(r, e()), Y("boardId", "==", t), U("__name__"), ...i ? [q(i)] : [], V(400)));
606
+ let a = await z(W(c(n, t()), Y("boardId", "==", e), U("__name__"), ...i ? [q(i)] : [], V(400)));
567
607
  if (a.empty) break;
568
- let o = X(r);
569
- for (let e of a.docs) o.update(e.ref, { listed: n });
608
+ let o = X(n);
609
+ for (let e of a.docs) o.update(e.ref, { listed: r });
570
610
  if (await o.commit(), i = a.docs[a.docs.length - 1], a.docs.length < 400) break;
571
611
  }
572
612
  }
573
- function D(e, t) {
613
+ function N(e, t) {
574
614
  return {
575
615
  label: e.label.trim(),
576
616
  description: (e.description ?? "").trim(),
577
- visibility: ut(e.visibility),
617
+ visibility: Ct(e.visibility),
578
618
  listView: e.listView ?? "default",
579
619
  writeRole: e.writeRole ?? "user",
580
620
  commentWriteRole: e.commentWriteRole ?? "user",
@@ -583,120 +623,122 @@ function ft() {
583
623
  updatedAt: G()
584
624
  };
585
625
  }
586
- async function k(n, i = n.order ?? 0) {
587
- if (!a.value) throw Error("로그인이 필요합니다.");
588
- let o = n.id.trim(), s = n.label.trim();
589
- if (!o || !s) throw Error("보드 ID와 이름을 입력해 주세요.");
590
- let c = ut(n.visibility), u = l.value.find((e) => e.id === o)?.visibility ?? "public", d = D({
591
- ...n,
592
- label: s,
593
- visibility: c
594
- }, i);
595
- await K(t(r, e(), o), d, { merge: !0 }), (u !== c || c === "public") && await E(o, c !== "hidden");
596
- }
597
- let A = k;
598
- async function j(n) {
599
- if (!a.value) throw Error("로그인이 필요합니다.");
600
- let i = new Map(l.value.map((e) => [e.id, e.visibility ?? "public"])), o = X(r), s = [];
601
- n.forEach((n, a) => {
602
- let c = n.id.trim(), l = n.label.trim();
603
- if (!c || !l) throw Error("보드 ID와 이름을 입력해 주세요.");
604
- let u = ut(n.visibility), d = D({
605
- ...n,
606
- label: l,
607
- visibility: u
608
- }, a);
609
- o.set(t(r, e(), c), d, { merge: !0 }), (i.get(c) ?? "public") !== u && s.push({
610
- id: c,
611
- listed: u !== "hidden"
626
+ async function ee(e, r = e.order ?? 0) {
627
+ if (!i.value) throw Error("로그인이 필요합니다.");
628
+ let a = e.id.trim(), o = e.label.trim();
629
+ if (!a || !o) throw Error("보드 ID와 이름을 입력해 주세요.");
630
+ let s = Ct(e.visibility), c = f.value.find((e) => e.id === a)?.visibility ?? "public", l = N({
631
+ ...e,
632
+ label: o,
633
+ visibility: s
634
+ }, r);
635
+ await K(v(n, t(), a), l, { merge: !0 }), (c !== s || s === "public") && await j(a, s !== "hidden");
636
+ }
637
+ let P = ee;
638
+ async function F(e) {
639
+ if (!i.value) throw Error("로그인이 필요합니다.");
640
+ let r = new Map(f.value.map((e) => [e.id, e.visibility ?? "public"])), a = X(n), o = [];
641
+ e.forEach((e, i) => {
642
+ let s = e.id.trim(), c = e.label.trim();
643
+ if (!s || !c) throw Error("보드 ID와 이름을 입력해 주세요.");
644
+ let l = Ct(e.visibility), u = N({
645
+ ...e,
646
+ label: c,
647
+ visibility: l
648
+ }, i);
649
+ a.set(v(n, t(), s), u, { merge: !0 }), (r.get(s) ?? "public") !== l && o.push({
650
+ id: s,
651
+ listed: l !== "hidden"
612
652
  });
613
- }), await o.commit();
614
- for (let e of s) await E(e.id, e.listed);
615
- }
616
- let M = j;
617
- async function N(n) {
618
- if (!a.value) throw Error("로그인이 필요합니다.");
619
- let o = n.trim();
620
- if (!o) throw Error("삭제할 게시판 ID가 필요합니다.");
653
+ }), await a.commit();
654
+ for (let e of o) await j(e.id, e.listed);
655
+ }
656
+ let I = F;
657
+ async function ne(e) {
658
+ if (!i.value) throw Error("로그인이 필요합니다.");
659
+ let a = e.trim();
660
+ if (!a) throw Error("삭제할 게시판 ID가 필요합니다.");
621
661
  for (;;) {
622
- let t = await z(W(T(r, e()), Y("boardId", "==", o), V(25)));
623
- if (t.empty) break;
624
- for (let n of t.docs) await Qe(r, ge(i), e(), n.id);
662
+ let e = await z(W(c(n, t()), Y("boardId", "==", a), V(25)));
663
+ if (e.empty) break;
664
+ for (let i of e.docs) await dt(n, ye(r), t(), i.id);
625
665
  }
626
- await P(t(r, e(), n));
666
+ await te(v(n, t(), e));
627
667
  }
628
- let F = N;
629
- async function I() {
668
+ let re = ne;
669
+ async function L() {
630
670
  return Promise.resolve();
631
671
  }
632
- async function L(e) {
672
+ async function ie(e) {
633
673
  let t = e.trim();
634
674
  if (!t) throw Error("보드 이름을 입력해 주세요.");
635
- if (!a.value) throw Error("로그인이 필요합니다.");
636
- let n = l.value.find((e) => e.label === t);
675
+ if (!i.value) throw Error("로그인이 필요합니다.");
676
+ let n = f.value.find((e) => e.label === t);
637
677
  if (n) return n.id;
638
- let r = v(t) || `board-${Date.now()}`, i = r, o = 2, s = new Set(l.value.map((e) => e.id));
639
- for (; s.has(i);) i = `${r}-${o++}`;
640
- return await k({
641
- id: i,
678
+ let r = S(t) || `board-${Date.now()}`, a = r, o = 2, s = new Set(f.value.map((e) => e.id));
679
+ for (; s.has(a);) a = `${r}-${o++}`;
680
+ return await ee({
681
+ id: a,
642
682
  label: t,
643
683
  visibility: "public",
644
684
  listView: "default",
645
685
  writeRole: "user",
646
686
  commentWriteRole: "user",
647
- order: l.value.length
648
- }), i;
687
+ order: f.value.length
688
+ }), a;
649
689
  }
650
690
  return {
651
- boards: l,
652
- publicBoards: f,
653
- boardLabel: h,
654
- boardDescription: _,
655
- boardVisibility: b,
656
- isBoardHidden: S,
657
- getBoard: w,
658
- categories: u,
659
- publicCategories: p,
660
- categoryLabel: g,
661
- categoryDescription: y,
662
- categoryVisibility: x,
663
- isCategoryHidden: C,
664
- settingsPending: c,
665
- ensureSettings: I,
666
- addBoard: L,
667
- addCategory: L,
668
- saveBoard: k,
669
- saveCategory: A,
670
- saveBoards: j,
671
- saveCategories: M,
672
- deleteBoard: N,
673
- deleteCategory: F,
674
- DEFAULT_BOARDS: ct,
675
- DEFAULT_CATEGORIES: lt
691
+ boards: f,
692
+ publicBoards: m,
693
+ boardLabel: g,
694
+ boardDescription: b,
695
+ boardVisibility: C,
696
+ isBoardHidden: E,
697
+ getBoard: O,
698
+ canManageBoard: k,
699
+ managedBoards: A,
700
+ categories: p,
701
+ publicCategories: h,
702
+ categoryLabel: y,
703
+ categoryDescription: x,
704
+ categoryVisibility: T,
705
+ isCategoryHidden: D,
706
+ settingsPending: d,
707
+ ensureSettings: L,
708
+ addBoard: ie,
709
+ addCategory: ie,
710
+ saveBoard: ee,
711
+ saveCategory: P,
712
+ saveBoards: F,
713
+ saveCategories: I,
714
+ deleteBoard: ne,
715
+ deleteCategory: re,
716
+ DEFAULT_BOARDS: xt,
717
+ DEFAULT_CATEGORIES: St
676
718
  };
677
719
  }
678
720
  //#endregion
679
721
  //#region src/composables/useMemiBoardPosts.ts
680
- function pt(e) {
722
+ function Et(e) {
681
723
  if (!e || typeof e != "object") return !1;
682
724
  let t = "code" in e ? String(e.code) : "";
683
725
  return t === "permission-denied" || t === "firestore/permission-denied";
684
726
  }
685
- function mt(e) {
686
- let t = M(e)?.trim();
727
+ function Dt(e) {
728
+ let t = F(e)?.trim();
687
729
  if (!t) throw Error("[memi-board] boardId 가 필요합니다.");
688
730
  return t;
689
731
  }
690
- function ht(e) {
691
- let t = Z(), n = oe(), { isBoardHidden: r, getBoard: a } = ft(), o = () => m(), s = () => mt(e), c = () => T(t, o()), l = (e) => i(t, o(), e), u = (e) => E(t, o(), e);
692
- function d() {
693
- return !r(s());
732
+ function Ot(e) {
733
+ let n = Q(), r = le(), { isBoardHidden: i, getBoard: a } = Tt(), o = () => _(), l = () => Dt(e), u = () => c(n, o()), d = (e) => t(n, o(), e), f = (e) => s(n, o(), e);
734
+ function p() {
735
+ return !i(l());
694
736
  }
695
- function f() {
696
- return a(s())?.listView === "image";
737
+ function m() {
738
+ return a(l())?.listView === "image";
697
739
  }
698
- function p(e) {
699
- let t = f(), n = tt(e.content), r = nt(e.content, e.attachments);
740
+ function h(e) {
741
+ let t = m(), n = mt(e.content), r = ht(e.content, e.attachments);
700
742
  if (t) {
701
743
  if (!r) throw Error("사진을 올려 주세요.");
702
744
  return;
@@ -704,13 +746,13 @@ function ht(e) {
704
746
  if (!n && !r) throw Error("본문에 글자를 입력하거나 이미지를 첨부해 주세요.");
705
747
  if (!e.title?.trim()) throw Error("제목을 입력해 주세요.");
706
748
  }
707
- function h() {
708
- return I(c()).id;
749
+ function g() {
750
+ return re(u()).id;
709
751
  }
710
- async function g(e = {}) {
711
- let t = e.pageSize ?? 20, n = [Y("boardId", "==", s()), Y("isPublished", "==", !0)];
752
+ async function v(e = {}) {
753
+ let t = e.pageSize ?? 20, n = [Y("boardId", "==", l()), Y("isPublished", "==", !0)];
712
754
  e.publicOnly !== !1 && n.push(Y("listed", "==", !0)), n.push(U("createdAt", "desc")), e.cursor && n.push(q(e.cursor)), n.push(V(t + 1));
713
- let r = await z(W(c(), ...n)), i = r.docs.slice(0, t);
755
+ let r = await z(W(u(), ...n)), i = r.docs.slice(0, t);
714
756
  return {
715
757
  posts: i.map((e) => ({
716
758
  id: e.id,
@@ -720,49 +762,49 @@ function ht(e) {
720
762
  hasMore: r.docs.length > t
721
763
  };
722
764
  }
723
- async function _(e) {
724
- let [t, n] = await Promise.all([R(l(e)), R(u(e))]);
765
+ async function y(e) {
766
+ let [t, n] = await Promise.all([R(d(e)), R(f(e))]);
725
767
  return t.exists() ? {
726
768
  id: t.id,
727
769
  ...t.data(),
728
770
  content: n.exists() ? n.data().content ?? "" : ""
729
771
  } : null;
730
772
  }
731
- async function y(e) {
773
+ async function b(e) {
732
774
  try {
733
- let t = (await z(W(c(), Y("boardId", "==", s()), Y("slug", "==", e.trim()), V(1)))).docs[0];
775
+ let t = (await z(W(u(), Y("boardId", "==", l()), Y("slug", "==", e.trim()), V(1)))).docs[0];
734
776
  if (!t) return { status: "not-found" };
735
777
  try {
736
- let e = await _(t.id);
778
+ let e = await y(t.id);
737
779
  return e ? {
738
780
  status: "ok",
739
781
  post: e
740
782
  } : { status: "not-found" };
741
783
  } catch (e) {
742
- return pt(e) ? { status: "permission-denied" } : {
784
+ return Et(e) ? { status: "permission-denied" } : {
743
785
  status: "error",
744
786
  message: e instanceof Error ? e.message : String(e)
745
787
  };
746
788
  }
747
789
  } catch (e) {
748
- return pt(e) ? { status: "permission-denied" } : {
790
+ return Et(e) ? { status: "permission-denied" } : {
749
791
  status: "error",
750
792
  message: e instanceof Error ? e.message : String(e)
751
793
  };
752
794
  }
753
795
  }
754
- async function b(e) {
796
+ async function x(e) {
755
797
  if (!e.createdAt) return {
756
798
  previous: null,
757
799
  next: null
758
800
  };
759
801
  try {
760
802
  let t = [
761
- Y("boardId", "==", s()),
803
+ Y("boardId", "==", l()),
762
804
  Y("isPublished", "==", !0),
763
805
  ...e.listed === !1 ? [] : [Y("listed", "==", !0)],
764
806
  U("createdAt", "desc")
765
- ], [n, r] = await Promise.all([z(W(c(), ...t, q(e.createdAt), V(1))), z(W(c(), ...t, ee(e.createdAt), te(1)))]), i = (e) => {
807
+ ], [n, r] = await Promise.all([z(W(u(), ...t, q(e.createdAt), V(1))), z(W(u(), ...t, ie(e.createdAt), ae(1)))]), i = (e) => {
766
808
  let t = e.docs[0];
767
809
  return t ? {
768
810
  id: t.id,
@@ -780,20 +822,20 @@ function ht(e) {
780
822
  };
781
823
  }
782
824
  }
783
- async function x(e) {
784
- p(e);
785
- let t = e.postId || h(), n = f(), r, i;
786
- if (n) r = t, i = et(e.content);
825
+ async function C(e) {
826
+ h(e);
827
+ let t = e.postId || g(), n = m(), r, i;
828
+ if (n) r = t, i = pt(e.content);
787
829
  else {
788
830
  i = e.title.trim();
789
- let t = v(i) || "post";
831
+ let t = S(i) || "post";
790
832
  r = t;
791
833
  let n = 1;
792
- for (; !(await z(W(c(), Y("boardId", "==", s()), Y("slug", "==", r), V(1)))).empty;) n++, r = `${t}-${n}`;
834
+ for (; !(await z(W(u(), Y("boardId", "==", l()), Y("slug", "==", r), V(1)))).empty;) n++, r = `${t}-${n}`;
793
835
  }
794
- let a = st(e.content, e.attachments), o = d();
795
- return await K(l(t), {
796
- boardId: s(),
836
+ let a = bt(e.content, e.attachments), o = p();
837
+ return await K(d(t), {
838
+ boardId: l(),
797
839
  slug: r,
798
840
  title: i,
799
841
  ...a,
@@ -811,81 +853,81 @@ function ht(e) {
811
853
  moderationModel: e.moderationModel ?? null,
812
854
  createdAt: G(),
813
855
  updatedAt: G()
814
- }), await K(u(t), { content: e.content }), r;
856
+ }), await K(f(t), { content: e.content }), r;
815
857
  }
816
- async function S(e, t) {
817
- p(t);
818
- let n = f() ? et(t.content) : t.title.trim(), r = st(t.content, t.attachments), i = d();
819
- await Promise.all([J(l(e), {
858
+ async function w(e, t) {
859
+ h(t);
860
+ let n = m() ? pt(t.content) : t.title.trim(), r = bt(t.content, t.attachments), i = p();
861
+ await Promise.all([J(d(e), {
820
862
  title: n,
821
863
  ...r,
822
- previewImage: r.previewImage ? r.previewImage : F(),
823
- videoUrl: r.videoUrl ? r.videoUrl : F(),
864
+ previewImage: r.previewImage ? r.previewImage : ne(),
865
+ videoUrl: r.videoUrl ? r.videoUrl : ne(),
824
866
  tags: t.tags ?? [],
825
867
  listed: i,
826
868
  attachments: t.attachments ?? [],
827
869
  updatedAt: G()
828
- }), K(u(e), { content: t.content }, { merge: !0 })]);
870
+ }), K(f(e), { content: t.content }, { merge: !0 })]);
829
871
  }
830
- async function C(e) {
831
- await J(l(e), {
872
+ async function T(e) {
873
+ await J(d(e), {
832
874
  isPublished: !0,
833
875
  publishedAt: G(),
834
876
  updatedAt: G()
835
877
  });
836
878
  }
837
- async function w(e) {
838
- await Qe(t, ge(n), o(), e);
879
+ async function E(e) {
880
+ await dt(n, ye(r), o(), e);
839
881
  }
840
882
  return {
841
- createPostId: h,
842
- getPosts: g,
843
- getPost: _,
844
- getPostBySlug: y,
845
- getAdjacentPosts: b,
846
- createPost: x,
847
- updatePost: S,
848
- publishPost: C,
849
- deletePost: w
883
+ createPostId: g,
884
+ getPosts: v,
885
+ getPost: y,
886
+ getPostBySlug: b,
887
+ getAdjacentPosts: x,
888
+ createPost: C,
889
+ updatePost: w,
890
+ publishPost: T,
891
+ deletePost: E
850
892
  };
851
893
  }
852
- var gt = 10;
853
- function _t(e, t = {}) {
854
- let n = Z(), { isAdmin: r, isStaff: i, user: a } = Ke(), o = () => m(), s = t.pageSize ?? gt, c = O(() => a.value?.uid ?? ""), l = O(() => mt(e)), u = () => T(n, o()), d = O(() => t.publicOnly === void 0 ? !(r.value || i.value) : typeof t.publicOnly == "boolean" ? t.publicOnly : t.publicOnly.value), f = j([]), p = j([]), h = j([]), g = j(!0), _ = j(!0), v = j(!1), y = j(""), b = null, x = null, S, C;
855
- function w(e) {
894
+ var kt = 10;
895
+ function At(e, t = {}) {
896
+ let n = Q(), { isAdmin: r, user: i, canManageBoard: a } = et(), o = () => _(), s = t.pageSize ?? kt, l = M(() => i.value?.uid ?? ""), u = M(() => Dt(e)), d = () => c(n, o()), f = M(() => t.publicOnly === void 0 ? !(r.value || a(u.value)) : typeof t.publicOnly == "boolean" ? t.publicOnly : t.publicOnly.value), p = P([]), m = P([]), h = P([]), g = P(!0), v = P(!0), y = P(!1), b = P(""), x = null, S = null, C, w;
897
+ function T(e) {
856
898
  return e.createdAt?.toMillis?.() ?? 0;
857
899
  }
858
- let E = O(() => {
900
+ let E = M(() => {
859
901
  let e = /* @__PURE__ */ new Map();
860
902
  for (let t of [
903
+ ...m.value,
861
904
  ...p.value,
862
- ...f.value,
863
905
  ...h.value
864
906
  ]) t.id && e.set(t.id, t);
865
- return [...e.values()].sort((e, t) => w(t) - w(e) || String(t.id).localeCompare(String(e.id)));
907
+ return [...e.values()].sort((e, t) => T(t) - T(e) || String(t.id).localeCompare(String(e.id)));
866
908
  });
867
909
  function D() {
868
- let e = [Y("boardId", "==", l.value), Y("isPublished", "==", !0)];
869
- return d.value && e.push(Y("listed", "==", !0)), e;
910
+ let e = [Y("boardId", "==", u.value), Y("isPublished", "==", !0)];
911
+ return f.value && e.push(Y("listed", "==", !0)), e;
870
912
  }
871
- function A() {
872
- S?.(), S = H(W(u(), ...D(), U("createdAt", "desc"), U(L(), "desc"), V(s)), (e) => {
873
- f.value = e.docs.map((e) => ({
913
+ function O() {
914
+ C?.(), C = H(W(d(), ...D(), U("createdAt", "desc"), U(L(), "desc"), V(s)), (e) => {
915
+ p.value = e.docs.map((e) => ({
874
916
  id: e.id,
875
917
  ...e.data()
876
- })), b = e.docs.at(-1) ?? null, x || (_.value = e.docs.length >= s), g.value = !1;
918
+ })), x = e.docs.at(-1) ?? null, S || (v.value = e.docs.length >= s), g.value = !1;
877
919
  }, (e) => {
878
- console.error("[memi-board:postList] onSnapshot failed", e), y.value = e.message || String(e), g.value = !1;
920
+ console.error("[memi-board:postList] onSnapshot failed", e), b.value = e.message || String(e), g.value = !1;
879
921
  });
880
922
  }
881
- function M() {
882
- C?.();
883
- let e = c.value;
923
+ function k() {
924
+ w?.();
925
+ let e = l.value;
884
926
  if (!e) {
885
927
  h.value = [];
886
928
  return;
887
929
  }
888
- C = H(W(u(), Y("boardId", "==", l.value), Y("authorUid", "==", e), Y("isPublished", "==", !1), U("createdAt", "desc"), V(20)), (e) => {
930
+ w = H(W(d(), Y("boardId", "==", u.value), Y("authorUid", "==", e), Y("isPublished", "==", !1), U("createdAt", "desc"), V(20)), (e) => {
889
931
  h.value = e.docs.map((e) => ({
890
932
  id: e.id,
891
933
  ...e.data()
@@ -894,119 +936,183 @@ function _t(e, t = {}) {
894
936
  console.error("[memi-board:postList] own draft onSnapshot failed", e);
895
937
  });
896
938
  }
897
- async function P() {
898
- if (!(v.value || !_.value || g.value)) {
899
- v.value = !0, y.value = "";
939
+ async function A() {
940
+ if (!(y.value || !v.value || g.value)) {
941
+ y.value = !0, b.value = "";
900
942
  try {
901
- let e = x ?? b, t = [
943
+ let e = S ?? x, t = [
902
944
  ...D(),
903
945
  U("createdAt", "desc"),
904
946
  U(L(), "desc"),
905
947
  ...e ? [q(e)] : [],
906
948
  V(s + 1)
907
- ], n = await z(W(u(), ...t)), r = n.docs.slice(0, s), i = r.map((e) => ({
949
+ ], n = await z(W(d(), ...t)), r = n.docs.slice(0, s), i = r.map((e) => ({
908
950
  id: e.id,
909
951
  ...e.data()
910
- })), a = new Set([...p.value, ...f.value].map((e) => e.id));
911
- p.value.push(...i.filter((e) => !a.has(e.id))), x = r.at(-1) ?? x, _.value = n.docs.length > s;
952
+ })), a = new Set([...m.value, ...p.value].map((e) => e.id));
953
+ m.value.push(...i.filter((e) => !a.has(e.id))), S = r.at(-1) ?? S, v.value = n.docs.length > s;
912
954
  } catch (e) {
913
- y.value = e.message || String(e), console.error("[memi-board:postList] loadMore failed", e);
955
+ b.value = e.message || String(e), console.error("[memi-board:postList] loadMore failed", e);
914
956
  } finally {
915
- v.value = !1;
957
+ y.value = !1;
916
958
  }
917
959
  }
918
960
  }
919
- function F() {
920
- S?.(), f.value = [], p.value = [], b = null, x = null, _.value = !0, y.value = "", g.value = !0, A(), M();
961
+ function j() {
962
+ C?.(), p.value = [], m.value = [], x = null, S = null, v.value = !0, b.value = "", g.value = !0, O(), k();
921
963
  }
922
- return N([
923
- l,
924
- d,
925
- c
926
- ], F, { immediate: !0 }), k(() => {
927
- S?.(), C?.();
964
+ return I([
965
+ u,
966
+ f,
967
+ l
968
+ ], j, { immediate: !0 }), N(() => {
969
+ C?.(), w?.();
928
970
  }), {
929
971
  posts: E,
930
972
  postsPending: g,
931
- hasMore: _,
932
- loadingMore: v,
933
- loadError: y,
934
- loadMore: P
973
+ hasMore: v,
974
+ loadingMore: y,
975
+ loadError: b,
976
+ loadMore: A
935
977
  };
936
978
  }
937
- function vt(e, t) {
938
- let n = Z(), r = () => m(), a = O(() => mt(e)), o = O(() => typeof t == "string" ? t : t.value), s = O(() => i(n, r(), o.value)), c = O(() => E(n, r(), o.value)), { data: l, pending: u } = ae(s), { data: d, pending: f } = ae(c);
979
+ function jt(e, n) {
980
+ let r = Q(), i = () => _(), a = M(() => Dt(e)), o = M(() => typeof n == "string" ? n : n.value), c = M(() => t(r, i(), o.value)), l = M(() => s(r, i(), o.value)), { data: u, pending: d } = ce(c), { data: f, pending: p } = ce(l);
939
981
  return {
940
- post: O(() => !l.value || l.value.boardId !== a.value ? null : {
941
- ...l.value,
982
+ post: M(() => !u.value || u.value.boardId !== a.value ? null : {
983
+ ...u.value,
942
984
  id: o.value,
943
- content: d.value?.content ?? ""
985
+ content: f.value?.content ?? ""
944
986
  }),
945
- pending: O(() => u.value || f.value)
987
+ pending: M(() => d.value || p.value)
946
988
  };
947
989
  }
948
990
  //#endregion
949
991
  //#region src/composables/useMemiBoardUserPosts.ts
950
- var yt = 10;
951
- function bt(e, t = {}) {
952
- let n = Z(), r = () => m(), i = () => T(n, r()), a = t.pageSize ?? yt, o = O(() => M(e)?.trim() || ""), s = j([]), c = j([]), l = j(!0), u = j(!0), d = j(!1), f = j(""), p = null, h = null, g;
953
- function _(e) {
992
+ var Mt = 10;
993
+ function Nt(e, t = {}) {
994
+ let n = Q(), r = () => _(), i = () => c(n, r()), a = t.pageSize ?? Mt, o = M(() => F(e)?.trim() || ""), s = P([]), l = P([]), u = P(!0), d = P(!0), f = P(!1), p = P(""), m = null, h = null, g;
995
+ function v(e) {
954
996
  return {
955
997
  id: e.id,
956
998
  ...e.data()
957
999
  };
958
1000
  }
959
- function v(e) {
1001
+ function y(e) {
960
1002
  return e.createdAt?.toMillis?.() ?? 0;
961
1003
  }
962
- let y = O(() => {
1004
+ let b = M(() => {
963
1005
  let e = /* @__PURE__ */ new Map();
964
- for (let t of [...c.value, ...s.value]) t.id && e.set(t.id, t);
965
- return [...e.values()].sort((e, t) => v(t) - v(e) || String(t.id).localeCompare(String(e.id)));
1006
+ for (let t of [...l.value, ...s.value]) t.id && e.set(t.id, t);
1007
+ return [...e.values()].sort((e, t) => y(t) - y(e) || String(t.id).localeCompare(String(e.id)));
966
1008
  });
967
- function b() {
1009
+ function x() {
968
1010
  return [
969
1011
  Y("authorUid", "==", o.value),
970
1012
  Y("isPublished", "==", !0),
971
1013
  Y("listed", "==", !0)
972
1014
  ];
973
1015
  }
974
- function x() {
1016
+ function S() {
975
1017
  if (g?.(), !o.value) {
1018
+ u.value = !1;
1019
+ return;
1020
+ }
1021
+ g = H(W(i(), ...x(), U("createdAt", "desc"), U(L(), "desc"), V(a)), (e) => {
1022
+ s.value = e.docs.map(v), m = e.docs.at(-1) ?? null, h || (d.value = e.docs.length >= a), u.value = !1;
1023
+ }, (e) => {
1024
+ console.error("[memi-board:userPosts] onSnapshot failed", e), p.value = e.message || String(e), u.value = !1;
1025
+ });
1026
+ }
1027
+ async function C() {
1028
+ if (!(f.value || !d.value || u.value || !o.value)) {
1029
+ f.value = !0, p.value = "";
1030
+ try {
1031
+ let e = h ?? m, t = [
1032
+ ...x(),
1033
+ U("createdAt", "desc"),
1034
+ U(L(), "desc"),
1035
+ ...e ? [q(e)] : [],
1036
+ V(a + 1)
1037
+ ], n = await z(W(i(), ...t)), r = n.docs.slice(0, a), o = r.map(v), c = new Set([...l.value, ...s.value].map((e) => e.id));
1038
+ l.value.push(...o.filter((e) => !c.has(e.id))), h = r.at(-1) ?? h, d.value = n.docs.length > a;
1039
+ } catch (e) {
1040
+ p.value = e.message || String(e), console.error("[memi-board:userPosts] loadMore failed", e);
1041
+ } finally {
1042
+ f.value = !1;
1043
+ }
1044
+ }
1045
+ }
1046
+ function w() {
1047
+ g?.(), s.value = [], l.value = [], m = null, h = null, d.value = !0, p.value = "", u.value = !0, S();
1048
+ }
1049
+ return I(o, w, { immediate: !0 }), N(() => g?.()), {
1050
+ posts: b,
1051
+ postsPending: u,
1052
+ hasMore: d,
1053
+ loadingMore: f,
1054
+ loadError: p,
1055
+ loadMore: C
1056
+ };
1057
+ }
1058
+ //#endregion
1059
+ //#region src/composables/useMemiBoardUserComments.ts
1060
+ var Pt = 10;
1061
+ function Ft(e, t = {}) {
1062
+ let n = Q(), r = () => _(), i = () => O(n, r()), a = t.pageSize ?? Pt, o = M(() => F(e)?.trim() || ""), s = P([]), c = P([]), l = P(!0), u = P(!0), d = P(!1), f = P(""), p = null, m = null, h;
1063
+ function g(e) {
1064
+ return {
1065
+ id: e.id,
1066
+ ...e.data()
1067
+ };
1068
+ }
1069
+ function v(e) {
1070
+ return e.createdAt?.toMillis?.() ?? 0;
1071
+ }
1072
+ let y = M(() => {
1073
+ let e = /* @__PURE__ */ new Map();
1074
+ for (let t of [...c.value, ...s.value]) t.id && e.set(t.id, t);
1075
+ return [...e.values()].sort((e, t) => v(t) - v(e) || String(t.id).localeCompare(String(e.id)));
1076
+ });
1077
+ function b() {
1078
+ return [Y("authorUid", "==", o.value)];
1079
+ }
1080
+ function x() {
1081
+ if (h?.(), !o.value) {
976
1082
  l.value = !1;
977
1083
  return;
978
1084
  }
979
- g = H(W(i(), ...b(), U("createdAt", "desc"), U(L(), "desc"), V(a)), (e) => {
980
- s.value = e.docs.map(_), p = e.docs.at(-1) ?? null, h || (u.value = e.docs.length >= a), l.value = !1;
1085
+ h = H(W(i(), ...b(), U("createdAt", "desc"), U(L(), "desc"), V(a)), (e) => {
1086
+ s.value = e.docs.map(g), p = e.docs.at(-1) ?? null, m || (u.value = e.docs.length >= a), l.value = !1;
981
1087
  }, (e) => {
982
- console.error("[memi-board:userPosts] onSnapshot failed", e), f.value = e.message || String(e), l.value = !1;
1088
+ console.error("[memi-board:userComments] onSnapshot failed", e), f.value = e.message || String(e), l.value = !1;
983
1089
  });
984
1090
  }
985
1091
  async function S() {
986
1092
  if (!(d.value || !u.value || l.value || !o.value)) {
987
1093
  d.value = !0, f.value = "";
988
1094
  try {
989
- let e = h ?? p, t = [
1095
+ let e = m ?? p, t = [
990
1096
  ...b(),
991
1097
  U("createdAt", "desc"),
992
1098
  U(L(), "desc"),
993
1099
  ...e ? [q(e)] : [],
994
1100
  V(a + 1)
995
- ], n = await z(W(i(), ...t)), r = n.docs.slice(0, a), o = r.map(_), l = new Set([...c.value, ...s.value].map((e) => e.id));
996
- c.value.push(...o.filter((e) => !l.has(e.id))), h = r.at(-1) ?? h, u.value = n.docs.length > a;
1101
+ ], n = await z(W(i(), ...t)), r = n.docs.slice(0, a), o = r.map(g), l = new Set([...c.value, ...s.value].map((e) => e.id));
1102
+ c.value.push(...o.filter((e) => !l.has(e.id))), m = r.at(-1) ?? m, u.value = n.docs.length > a;
997
1103
  } catch (e) {
998
- f.value = e.message || String(e), console.error("[memi-board:userPosts] loadMore failed", e);
1104
+ f.value = e.message || String(e), console.error("[memi-board:userComments] loadMore failed", e);
999
1105
  } finally {
1000
1106
  d.value = !1;
1001
1107
  }
1002
1108
  }
1003
1109
  }
1004
1110
  function C() {
1005
- g?.(), s.value = [], c.value = [], p = null, h = null, u.value = !0, f.value = "", l.value = !0, x();
1111
+ h?.(), s.value = [], c.value = [], p = null, m = null, u.value = !0, f.value = "", l.value = !0, x();
1006
1112
  }
1007
- return N(o, C, { immediate: !0 }), k(() => g?.()), {
1008
- posts: y,
1009
- postsPending: l,
1113
+ return I(o, C, { immediate: !0 }), N(() => h?.()), {
1114
+ comments: y,
1115
+ commentsPending: l,
1010
1116
  hasMore: u,
1011
1117
  loadingMore: d,
1012
1118
  loadError: f,
@@ -1014,97 +1120,102 @@ function bt(e, t = {}) {
1014
1120
  };
1015
1121
  }
1016
1122
  //#endregion
1017
- //#region src/composables/useMemiBoardUserComments.ts
1018
- var xt = 10;
1019
- function St(e, t = {}) {
1020
- let n = Z(), r = () => m(), i = () => o(n, r()), a = t.pageSize ?? xt, s = O(() => M(e)?.trim() || ""), c = j([]), l = j([]), u = j(!0), d = j(!0), f = j(!1), p = j(""), h = null, g = null, _;
1021
- function v(e) {
1123
+ //#region src/composables/useMemiBoardCommentFeed.ts
1124
+ var It = 10;
1125
+ function Lt(e = "latest", t = {}) {
1126
+ let n = Q(), r = () => _(), i = () => O(n, r()), a = M(() => {
1127
+ let e = Math.floor(Number(F(t.pageSize)));
1128
+ return Number.isFinite(e) && e > 0 ? e : It;
1129
+ }), o = M(() => F(e) === "likes" ? "likes" : "latest"), s = P([]), c = P([]), l = P(!0), u = P(!0), d = P(!1), f = P(""), p = null, m = null, h;
1130
+ function g(e) {
1022
1131
  return {
1023
1132
  id: e.id,
1024
1133
  ...e.data()
1025
1134
  };
1026
1135
  }
1027
- function y(e) {
1136
+ function v(e) {
1028
1137
  return e.createdAt?.toMillis?.() ?? 0;
1029
1138
  }
1030
- let b = O(() => {
1139
+ function y(e, t) {
1140
+ if (o.value === "likes") {
1141
+ let n = (t.likeCount ?? 0) - (e.likeCount ?? 0);
1142
+ if (n) return n;
1143
+ }
1144
+ return v(t) - v(e) || String(t.id).localeCompare(String(e.id));
1145
+ }
1146
+ let b = M(() => {
1031
1147
  let e = /* @__PURE__ */ new Map();
1032
- for (let t of [...l.value, ...c.value]) t.id && e.set(t.id, t);
1033
- return [...e.values()].sort((e, t) => y(t) - y(e) || String(t.id).localeCompare(String(e.id)));
1148
+ for (let t of [...c.value, ...s.value]) t.id && e.set(t.id, t);
1149
+ return [...e.values()].sort(y);
1034
1150
  });
1035
1151
  function x() {
1036
- return [Y("authorUid", "==", s.value)];
1152
+ return o.value === "likes" ? [
1153
+ U("likeCount", "desc"),
1154
+ U("createdAt", "desc"),
1155
+ U(L(), "desc")
1156
+ ] : [U("createdAt", "desc"), U(L(), "desc")];
1037
1157
  }
1038
1158
  function S() {
1039
- if (_?.(), !s.value) {
1040
- u.value = !1;
1041
- return;
1042
- }
1043
- _ = H(W(i(), ...x(), U("createdAt", "desc"), U(L(), "desc"), V(a)), (e) => {
1044
- c.value = e.docs.map(v), h = e.docs.at(-1) ?? null, g || (d.value = e.docs.length >= a), u.value = !1;
1159
+ h?.(), h = H(W(i(), ...x(), V(a.value)), (e) => {
1160
+ s.value = e.docs.map(g), p = e.docs.at(-1) ?? null, m || (u.value = e.docs.length >= a.value), l.value = !1;
1045
1161
  }, (e) => {
1046
- console.error("[memi-board:userComments] onSnapshot failed", e), p.value = e.message || String(e), u.value = !1;
1162
+ console.error("[memi-board:commentFeed] onSnapshot failed", e), f.value = e.message || String(e), l.value = !1;
1047
1163
  });
1048
1164
  }
1049
1165
  async function C() {
1050
- if (!(f.value || !d.value || u.value || !s.value)) {
1051
- f.value = !0, p.value = "";
1166
+ if (!(d.value || !u.value || l.value)) {
1167
+ d.value = !0, f.value = "";
1052
1168
  try {
1053
- let e = g ?? h, t = [
1054
- ...x(),
1055
- U("createdAt", "desc"),
1056
- U(L(), "desc"),
1057
- ...e ? [q(e)] : [],
1058
- V(a + 1)
1059
- ], n = await z(W(i(), ...t)), r = n.docs.slice(0, a), o = r.map(v), s = new Set([...l.value, ...c.value].map((e) => e.id));
1060
- l.value.push(...o.filter((e) => !s.has(e.id))), g = r.at(-1) ?? g, d.value = n.docs.length > a;
1169
+ let e = m ?? p, t = await z(W(i(), ...x(), ...e ? [q(e)] : [], V(a.value + 1))), n = t.docs.slice(0, a.value), r = n.map(g), o = new Set([...c.value, ...s.value].map((e) => e.id));
1170
+ c.value.push(...r.filter((e) => !o.has(e.id))), m = n.at(-1) ?? m, u.value = t.docs.length > a.value;
1061
1171
  } catch (e) {
1062
- p.value = e.message || String(e), console.error("[memi-board:userComments] loadMore failed", e);
1172
+ f.value = e.message || String(e), console.error("[memi-board:commentFeed] loadMore failed", e);
1063
1173
  } finally {
1064
- f.value = !1;
1174
+ d.value = !1;
1065
1175
  }
1066
1176
  }
1067
1177
  }
1068
1178
  function w() {
1069
- _?.(), c.value = [], l.value = [], h = null, g = null, d.value = !0, p.value = "", u.value = !0, S();
1179
+ h?.(), s.value = [], c.value = [], p = null, m = null, u.value = !0, f.value = "", l.value = !0, S();
1070
1180
  }
1071
- return N(s, w, { immediate: !0 }), k(() => _?.()), {
1181
+ return I([o, a], w, { immediate: !0 }), N(() => h?.()), {
1072
1182
  comments: b,
1073
- commentsPending: u,
1074
- hasMore: d,
1075
- loadingMore: f,
1076
- loadError: p,
1077
- loadMore: C
1183
+ commentsPending: l,
1184
+ hasMore: u,
1185
+ loadingMore: d,
1186
+ loadError: f,
1187
+ loadMore: C,
1188
+ sort: o
1078
1189
  };
1079
1190
  }
1080
1191
  //#endregion
1081
1192
  //#region src/composables/useMemiBoardAuthorMemo.ts
1082
- var Ct = 100, wt = /* @__PURE__ */ new Map();
1083
- function Q(e) {
1084
- let t = wt.get(e);
1085
- return t || (t = A({
1193
+ var Rt = 100, zt = /* @__PURE__ */ new Map();
1194
+ function $(e) {
1195
+ let t = zt.get(e);
1196
+ return t || (t = ee({
1086
1197
  text: "",
1087
1198
  sentiment: null,
1088
1199
  loaded: !1,
1089
1200
  pending: !1,
1090
1201
  saving: !1,
1091
1202
  error: ""
1092
- }), wt.set(e, t)), t;
1203
+ }), zt.set(e, t)), t;
1093
1204
  }
1094
- function Tt(e) {
1095
- let t = Z(), n = () => m(), { user: r } = Ke(), i = O(() => M(e)), a = O(() => `${r.value?.uid ?? ""}:${i.value}`), o = O(() => {
1205
+ function Bt(e) {
1206
+ let t = Q(), n = () => _(), { user: r } = et(), i = M(() => F(e)), a = M(() => `${r.value?.uid ?? ""}:${i.value}`), o = M(() => {
1096
1207
  let e = r.value?.uid, a = i.value;
1097
- return !e || !a ? null : u(t, n(), e, a);
1098
- }), s = O({
1099
- get: () => Q(a.value).text,
1208
+ return !e || !a ? null : m(t, n(), e, a);
1209
+ }), s = M({
1210
+ get: () => $(a.value).text,
1100
1211
  set: (e) => {
1101
- Q(a.value).text = e;
1212
+ $(a.value).text = e;
1102
1213
  }
1103
- }), c = O(() => Q(a.value).sentiment), l = O(() => Q(a.value).loaded), d = O(() => Q(a.value).pending), f = O(() => Q(a.value).saving), p = O(() => Q(a.value).error);
1104
- async function h() {
1214
+ }), c = M(() => $(a.value).sentiment), l = M(() => $(a.value).loaded), u = M(() => $(a.value).pending), d = M(() => $(a.value).saving), f = M(() => $(a.value).error);
1215
+ async function p() {
1105
1216
  let e = o.value;
1106
1217
  if (!e) return;
1107
- let t = Q(a.value);
1218
+ let t = $(a.value);
1108
1219
  t.pending = !0, t.error = "";
1109
1220
  try {
1110
1221
  let n = (await R(e)).data();
@@ -1115,12 +1226,12 @@ function Tt(e) {
1115
1226
  t.pending = !1;
1116
1227
  }
1117
1228
  }
1118
- async function g(e, t) {
1229
+ async function h(e, t) {
1119
1230
  let n = o.value;
1120
1231
  if (!n) return;
1121
1232
  let r = e.trim();
1122
1233
  if (r.length > 100) throw Error(`메모는 ${100 .toLocaleString()}자까지 작성할 수 있습니다.`);
1123
- let i = Q(a.value);
1234
+ let i = $(a.value);
1124
1235
  i.saving = !0, i.error = "";
1125
1236
  try {
1126
1237
  await K(n, {
@@ -1134,13 +1245,13 @@ function Tt(e) {
1134
1245
  i.saving = !1;
1135
1246
  }
1136
1247
  }
1137
- async function _() {
1248
+ async function g() {
1138
1249
  let e = o.value;
1139
1250
  if (!e) return;
1140
- let t = Q(a.value);
1251
+ let t = $(a.value);
1141
1252
  t.saving = !0, t.error = "";
1142
1253
  try {
1143
- await P(e), t.text = "", t.sentiment = null, t.loaded = !0;
1254
+ await te(e), t.text = "", t.sentiment = null, t.loaded = !0;
1144
1255
  } catch (e) {
1145
1256
  throw t.error = e.message || String(e), console.error("[memi-board:authorMemo] remove failed", e), e;
1146
1257
  } finally {
@@ -1151,57 +1262,57 @@ function Tt(e) {
1151
1262
  text: s,
1152
1263
  sentiment: c,
1153
1264
  loaded: l,
1154
- pending: d,
1155
- saving: f,
1156
- error: p,
1157
- load: h,
1158
- save: g,
1159
- remove: _
1265
+ pending: u,
1266
+ saving: d,
1267
+ error: f,
1268
+ load: p,
1269
+ save: h,
1270
+ remove: g
1160
1271
  };
1161
1272
  }
1162
1273
  //#endregion
1163
1274
  //#region src/composables/useMemiBoardComments.ts
1164
- var Et = 10, Dt = 5, Ot = 1e3;
1165
- function kt(e) {
1275
+ var Vt = 10, Ht = 5, Ut = 1e3;
1276
+ function Wt(e) {
1166
1277
  let t = e.trim();
1167
1278
  if (!t) throw Error("댓글 내용을 입력해 주세요.");
1168
- if (t.length > 1e3) throw Error(`댓글은 ${Ot.toLocaleString()}자까지 작성할 수 있습니다.`);
1279
+ if (t.length > 1e3) throw Error(`댓글은 ${Ut.toLocaleString()}자까지 작성할 수 있습니다.`);
1169
1280
  return t;
1170
1281
  }
1171
- function At(e, t, n = {}) {
1172
- let r = Z(), a = () => m(), s = () => o(r, a()), c = O(() => M(e)), l = O(() => M(t)), u = j([]), d = j([]), p = j(n.subscribe !== !1), h = j([]), g = j(!1), _ = j(n.subscribe !== !1), v = j(n.subscribe !== !1), y = null, b, x;
1173
- function S(e) {
1282
+ function Gt(e, n, r = {}) {
1283
+ let i = Q(), a = () => _(), o = () => O(i, a()), s = M(() => F(e)), c = M(() => F(n)), u = P([]), d = P([]), f = P(r.subscribe !== !1), p = P([]), m = P(!1), h = P(r.subscribe !== !1), g = P(r.subscribe !== !1), v = null, y, b;
1284
+ function x(e) {
1174
1285
  return e.createdAt?.toMillis?.() ?? 0;
1175
1286
  }
1176
- let C = O(() => {
1287
+ let S = M(() => {
1177
1288
  let e = /* @__PURE__ */ new Map();
1178
1289
  for (let t of [
1179
1290
  ...u.value,
1180
1291
  ...d.value,
1181
- ...h.value
1292
+ ...p.value
1182
1293
  ]) t.id && e.set(t.id, t);
1183
- return [...e.values()].sort((e, t) => S(e) - S(t) || String(e.id).localeCompare(String(t.id)));
1294
+ return [...e.values()].sort((e, t) => x(e) - x(t) || String(e.id).localeCompare(String(t.id)));
1184
1295
  });
1185
- N([l, c], async ([e]) => {
1186
- if (h.value = [], u.value = [], d.value = [], y = null, clearTimeout(b), b = void 0, x?.(), x = void 0, _.value = n.subscribe !== !1, n.subscribe !== !1) {
1187
- p.value = !0, v.value = !0;
1296
+ I([c, s], async ([e]) => {
1297
+ if (p.value = [], u.value = [], d.value = [], v = null, clearTimeout(y), y = void 0, b?.(), b = void 0, h.value = r.subscribe !== !1, r.subscribe !== !1) {
1298
+ f.value = !0, g.value = !0;
1188
1299
  try {
1189
- let t = z(W(s(), Y("postId", "==", e), U("createdAt", "desc"), U(L(), "desc")));
1300
+ let t = z(W(o(), Y("postId", "==", e), U("createdAt", "desc"), U(L(), "desc")));
1190
1301
  if (await Promise.all([t.then((t) => {
1191
- if (l.value !== e) return;
1302
+ if (c.value !== e) return;
1192
1303
  let n = t.docs.map((e) => ({
1193
1304
  id: e.id,
1194
1305
  ...e.data()
1195
1306
  }));
1196
1307
  d.value = n.filter((e) => e.parentId === void 0);
1197
- }), T()]), l.value !== e) return;
1308
+ }), w()]), c.value !== e) return;
1198
1309
  } finally {
1199
- l.value === e && (p.value = !1, v.value = !1);
1310
+ c.value === e && (f.value = !1, g.value = !1);
1200
1311
  }
1201
1312
  }
1202
1313
  }, { immediate: !0 });
1203
- function w() {
1204
- x?.(), x = H(W(s(), Y("postId", "==", l.value), Y("parentId", "==", null), U("createdAt", "desc"), U(L(), "desc"), V(1)), (e) => {
1314
+ function C() {
1315
+ b?.(), b = H(W(o(), Y("postId", "==", c.value), Y("parentId", "==", null), U("createdAt", "desc"), U(L(), "desc"), V(1)), (e) => {
1205
1316
  let t = new Map(u.value.map((e) => [e.id, e]));
1206
1317
  for (let n of e.docChanges()) n.type !== "removed" && t.set(n.doc.id, {
1207
1318
  id: n.doc.id,
@@ -1212,172 +1323,175 @@ function At(e, t, n = {}) {
1212
1323
  console.error("[memi-board:comments] onSnapshot failed", e);
1213
1324
  });
1214
1325
  }
1215
- async function T() {
1216
- if (!(g.value || !_.value)) {
1217
- g.value = !0;
1326
+ async function w() {
1327
+ if (!(m.value || !h.value)) {
1328
+ m.value = !0;
1218
1329
  try {
1219
1330
  let e = [
1220
- Y("postId", "==", l.value),
1331
+ Y("postId", "==", c.value),
1221
1332
  Y("parentId", "==", null),
1222
1333
  U("createdAt", "asc"),
1223
1334
  U(L(), "asc"),
1224
- ...y ? [q(y)] : [],
1335
+ ...v ? [q(v)] : [],
1225
1336
  V(11)
1226
- ], t = await z(W(s(), ...e)), n = t.docs.slice(0, Et), r = n.map((e) => ({
1337
+ ], t = await z(W(o(), ...e)), n = t.docs.slice(0, Vt), r = n.map((e) => ({
1227
1338
  id: e.id,
1228
1339
  ...e.data()
1229
- })), i = new Set(h.value.map((e) => e.id));
1230
- h.value.push(...r.filter((e) => !i.has(e.id))), y = n.at(-1) ?? y, t.size <= Et && (_.value = !1, b = setTimeout(() => {
1231
- b = void 0, w();
1340
+ })), i = new Set(p.value.map((e) => e.id));
1341
+ p.value.push(...r.filter((e) => !i.has(e.id))), v = n.at(-1) ?? v, t.size <= Vt && (h.value = !1, y = setTimeout(() => {
1342
+ y = void 0, C();
1232
1343
  }, 1e3));
1233
1344
  } finally {
1234
- g.value = !1;
1345
+ m.value = !1;
1235
1346
  }
1236
1347
  }
1237
1348
  }
1238
- k(() => {
1239
- clearTimeout(b), x?.();
1349
+ N(() => {
1350
+ clearTimeout(y), b?.();
1240
1351
  });
1241
- async function E(e) {
1242
- let t = kt(e.body), n = X(r), o = I(s());
1243
- n.set(o, {
1244
- postId: l.value,
1245
- boardId: c.value,
1246
- body: t,
1352
+ async function T(e) {
1353
+ let n = Wt(e.body), r = X(i), l = re(o());
1354
+ r.set(l, {
1355
+ postId: c.value,
1356
+ boardId: s.value,
1357
+ body: n,
1247
1358
  authorUid: e.authorUid,
1248
1359
  authorName: e.authorName,
1249
1360
  authorPhoto: e.authorPhoto,
1250
1361
  moderationStatus: "approved",
1251
1362
  parentId: null,
1252
- rootId: o.id,
1363
+ rootId: l.id,
1253
1364
  depth: 0,
1254
1365
  replyToUid: null,
1255
1366
  replyToName: null,
1256
1367
  replyCount: 0,
1368
+ likeCount: 0,
1257
1369
  isReply: !1,
1258
1370
  createdAt: G(),
1259
1371
  updatedAt: G()
1260
- }), n.update(i(r, a(), l.value), { commentCount: B(1) }), await n.commit();
1372
+ }), r.update(t(i, a(), c.value), { commentCount: B(1) }), await r.commit();
1261
1373
  }
1262
- async function D(e) {
1263
- let t = kt(e.body);
1374
+ async function E(e) {
1375
+ let n = Wt(e.body);
1264
1376
  if (!e.parent.id) throw Error("답글 대상 댓글을 찾을 수 없습니다.");
1265
- let n = X(r), o = I(s()), u = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
1266
- if (!u) throw Error("댓글 스레드를 찾을 수 없습니다.");
1267
- let d = Math.min((e.parent.depth ?? 0) + 1, 2);
1268
- n.set(o, {
1269
- postId: l.value,
1270
- boardId: c.value,
1271
- body: t,
1377
+ let r = X(i), u = re(o()), d = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
1378
+ if (!d) throw Error("댓글 스레드를 찾을 수 없습니다.");
1379
+ let f = Math.min((e.parent.depth ?? 0) + 1, 2);
1380
+ r.set(u, {
1381
+ postId: c.value,
1382
+ boardId: s.value,
1383
+ body: n,
1272
1384
  authorUid: e.authorUid,
1273
1385
  authorName: e.authorName,
1274
1386
  authorPhoto: e.authorPhoto,
1275
1387
  moderationStatus: "approved",
1276
1388
  parentId: e.parent.id,
1277
- rootId: u,
1278
- depth: d,
1389
+ rootId: d,
1390
+ depth: f,
1279
1391
  replyToUid: e.parent.authorUid,
1280
1392
  replyToName: e.parent.authorName,
1281
1393
  replyCount: 0,
1394
+ likeCount: 0,
1282
1395
  isReply: !0,
1283
1396
  createdAt: G(),
1284
1397
  updatedAt: G()
1285
- }), n.update(f(r, a(), u), { replyCount: B(1) }), n.update(i(r, a(), l.value), { commentCount: B(1) }), await n.commit();
1398
+ }), r.update(l(i, a(), d), { replyCount: B(1) }), r.update(t(i, a(), c.value), { commentCount: B(1) }), await r.commit();
1286
1399
  }
1287
- async function A(e) {
1400
+ async function D(e) {
1288
1401
  if (!e.id) return;
1289
1402
  if (e.parentId == null && (e.replyCount ?? 0) > 0) throw Error("답글이 있는 댓글은 현재 삭제할 수 없습니다.");
1290
- let t = X(r);
1291
- t.delete(f(r, a(), e.id)), e.parentId && e.rootId && t.update(f(r, a(), e.rootId), { replyCount: B(-1) }), t.update(i(r, a(), l.value), { commentCount: B(-1) }), await t.commit(), h.value = h.value.filter((t) => t.id !== e.id), d.value = d.value.filter((t) => t.id !== e.id), u.value = u.value.filter((t) => t.id !== e.id);
1403
+ await ut(i, a(), e.id);
1404
+ let n = X(i);
1405
+ n.delete(l(i, a(), e.id)), e.parentId && e.rootId && n.update(l(i, a(), e.rootId), { replyCount: B(-1) }), n.update(t(i, a(), c.value), { commentCount: B(-1) }), await n.commit(), p.value = p.value.filter((t) => t.id !== e.id), d.value = d.value.filter((t) => t.id !== e.id), u.value = u.value.filter((t) => t.id !== e.id);
1292
1406
  }
1293
- async function P(e, t) {
1294
- let n = kt(t);
1295
- await J(f(r, a(), e), {
1407
+ async function k(e, t) {
1408
+ let n = Wt(t);
1409
+ await J(l(i, a(), e), {
1296
1410
  body: n,
1297
1411
  updatedAt: G()
1298
1412
  });
1299
1413
  }
1300
- async function F(e, t, n) {
1301
- await J(f(r, a(), e), {
1414
+ async function A(e, t, n) {
1415
+ await J(l(i, a(), e), {
1302
1416
  isBlinded: t,
1303
1417
  moderatedAt: G(),
1304
1418
  moderatedBy: n
1305
1419
  });
1306
1420
  }
1307
1421
  return {
1308
- comments: C,
1309
- commentsPending: O(() => v.value || p.value),
1310
- hasMore: _,
1311
- loadingMore: g,
1312
- loadMore: T,
1313
- addComment: E,
1314
- addReply: D,
1315
- updateComment: P,
1316
- setCommentBlinded: F,
1317
- deleteComment: A
1422
+ comments: S,
1423
+ commentsPending: M(() => g.value || f.value),
1424
+ hasMore: h,
1425
+ loadingMore: m,
1426
+ loadMore: w,
1427
+ addComment: T,
1428
+ addReply: E,
1429
+ updateComment: k,
1430
+ setCommentBlinded: A,
1431
+ deleteComment: D
1318
1432
  };
1319
1433
  }
1320
- function jt(e, t, n) {
1321
- let r = Z(), i = () => m(), a = () => o(r, i()), s = j([]), c = j(!1), l = j(!1), u = j(!0), d = null, f;
1322
- function p(e) {
1434
+ function Kt(e, t, n) {
1435
+ let r = Q(), i = () => _(), a = () => O(r, i()), o = P([]), s = P(!1), c = P(!1), l = P(!0), u = null, d;
1436
+ function f(e) {
1323
1437
  return e.sort((e, t) => (e.createdAt?.toMillis?.() ?? 0) - (t.createdAt?.toMillis?.() ?? 0) || String(e.id).localeCompare(String(t.id)));
1324
1438
  }
1325
- function h() {
1326
- f ||= H(W(a(), Y("rootId", "==", n), Y("isReply", "==", !0), U("createdAt", "desc"), U(L(), "desc"), V(1)), (e) => {
1327
- let t = new Map(s.value.map((e) => [e.id, e]));
1439
+ function p() {
1440
+ d ||= H(W(a(), Y("rootId", "==", n), Y("isReply", "==", !0), U("createdAt", "desc"), U(L(), "desc"), V(1)), (e) => {
1441
+ let t = new Map(o.value.map((e) => [e.id, e]));
1328
1442
  for (let n of e.docChanges()) n.type !== "removed" && t.set(n.doc.id, {
1329
1443
  id: n.doc.id,
1330
1444
  ...n.doc.data()
1331
1445
  });
1332
- s.value = p([...t.values()]);
1446
+ o.value = f([...t.values()]);
1333
1447
  }, (e) => {
1334
1448
  console.error("[memi-board:replies] onSnapshot failed", e);
1335
1449
  });
1336
1450
  }
1337
- async function g() {
1338
- if (!(c.value || !u.value)) {
1339
- c.value = !0;
1451
+ async function m() {
1452
+ if (!(s.value || !l.value)) {
1453
+ s.value = !0;
1340
1454
  try {
1341
1455
  let e = [
1342
1456
  Y("rootId", "==", n),
1343
1457
  Y("isReply", "==", !0),
1344
1458
  U("createdAt", "asc"),
1345
1459
  U(L(), "asc"),
1346
- ...d ? [q(d)] : [],
1460
+ ...u ? [q(u)] : [],
1347
1461
  V(6)
1348
- ], t = await z(W(a(), ...e)), r = new Set(s.value.map((e) => e.id)), i = t.docs.slice(0, Dt), o = i.map((e) => ({
1462
+ ], t = await z(W(a(), ...e)), r = new Set(o.value.map((e) => e.id)), i = t.docs.slice(0, Ht), s = i.map((e) => ({
1349
1463
  id: e.id,
1350
1464
  ...e.data()
1351
1465
  })).filter((e) => !r.has(e.id));
1352
- s.value = p([...s.value, ...o]), d = i.at(-1) ?? d, l.value = !0, t.size <= Dt && (u.value = !1, h());
1466
+ o.value = f([...o.value, ...s]), u = i.at(-1) ?? u, c.value = !0, t.size <= Ht && (l.value = !1, p());
1353
1467
  } finally {
1354
- c.value = !1;
1468
+ s.value = !1;
1355
1469
  }
1356
1470
  }
1357
1471
  }
1358
- function _() {
1359
- f?.(), f = void 0, s.value = [], d = null, l.value = !1, u.value = !0;
1360
- }
1361
- async function v() {
1362
- _(), await g();
1472
+ function h() {
1473
+ d?.(), d = void 0, o.value = [], u = null, c.value = !1, l.value = !0;
1363
1474
  }
1364
- return k(() => f?.()), {
1365
- replies: s,
1366
- loading: c,
1367
- loaded: l,
1368
- hasMore: u,
1369
- loadMore: g,
1370
- refresh: v
1475
+ async function g() {
1476
+ h(), await m();
1477
+ }
1478
+ return N(() => d?.()), {
1479
+ replies: o,
1480
+ loading: s,
1481
+ loaded: c,
1482
+ hasMore: l,
1483
+ loadMore: m,
1484
+ refresh: g
1371
1485
  };
1372
1486
  }
1373
1487
  //#endregion
1374
1488
  //#region src/composables/useMemiBoardLikes.ts
1375
- function Mt(e, t) {
1376
- let n = () => m(), r = Z(), a = ie(), o = j(!1), s = j(!1), c;
1489
+ function qt(e, n) {
1490
+ let r = () => _(), i = Q(), a = Z(), o = P(!1), s = P(!1), c;
1377
1491
  function l(e) {
1378
- return w(r, n(), t, e);
1492
+ return j(i, r(), n, e);
1379
1493
  }
1380
- N(a, (e) => {
1494
+ I(a, (e) => {
1381
1495
  if (c?.(), c = void 0, !e) {
1382
1496
  o.value = !1;
1383
1497
  return;
@@ -1387,20 +1501,20 @@ function Mt(e, t) {
1387
1501
  }, (e) => {
1388
1502
  console.error("[memi-board:likes] onSnapshot failed", e);
1389
1503
  });
1390
- }, { immediate: !0 }), k(() => c?.());
1504
+ }, { immediate: !0 }), N(() => c?.());
1391
1505
  async function u() {
1392
1506
  let c = a.value?.uid;
1393
1507
  if (!c) throw Error("로그인이 필요합니다.");
1394
1508
  if (s.value) return o.value;
1395
1509
  s.value = !0;
1396
1510
  try {
1397
- let a = l(c), o = i(r, n(), t);
1398
- return await ne(r, async (n) => (await n.get(a)).exists() ? (n.delete(a), n.update(o, { likeCount: B(-1) }), !1) : (n.set(a, {
1511
+ let a = l(c), o = t(i, r(), n);
1512
+ return await oe(i, async (t) => (await t.get(a)).exists() ? (t.delete(a), t.update(o, { likeCount: B(-1) }), !1) : (t.set(a, {
1399
1513
  uid: c,
1400
- postId: t,
1514
+ postId: n,
1401
1515
  boardId: e,
1402
1516
  createdAt: G()
1403
- }), n.update(o, { likeCount: B(1) }), !0));
1517
+ }), t.update(o, { likeCount: B(1) }), !0));
1404
1518
  } finally {
1405
1519
  s.value = !1;
1406
1520
  }
@@ -1411,36 +1525,216 @@ function Mt(e, t) {
1411
1525
  toggleLike: u
1412
1526
  };
1413
1527
  }
1528
+ var Jt = Symbol.for("memi-board:comment-likes");
1529
+ function Yt(e, t) {
1530
+ let n = () => _(), r = Q(), i = Z(), a = M(() => F(e)), o = M(() => F(t)), s = P(/* @__PURE__ */ new Set()), c = P(null), u;
1531
+ I([i, o], ([e, t]) => {
1532
+ u?.(), u = void 0, s.value = /* @__PURE__ */ new Set(), !(!e || !t) && (u = H(W(A(r, n()), Y("uid", "==", e.uid), Y("postId", "==", t), Y("target", "==", "comment")), (e) => {
1533
+ let t = /* @__PURE__ */ new Set();
1534
+ for (let n of e.docs) {
1535
+ let e = n.data().commentId;
1536
+ typeof e == "string" && e && t.add(e);
1537
+ }
1538
+ s.value = t;
1539
+ }, (e) => {
1540
+ console.error("[memi-board:comment-likes] onSnapshot failed", e);
1541
+ }));
1542
+ }, { immediate: !0 }), N(() => u?.());
1543
+ function d(e) {
1544
+ return !!e && s.value.has(e);
1545
+ }
1546
+ function f(e) {
1547
+ return !!e && c.value === e;
1548
+ }
1549
+ async function p(e) {
1550
+ let t = i.value?.uid;
1551
+ if (!t) throw Error("로그인이 필요합니다.");
1552
+ if (!e) return !1;
1553
+ if (c.value) return d(e);
1554
+ c.value = e;
1555
+ try {
1556
+ let i = j(r, n(), e, t), c = l(r, n(), e), u = await oe(r, async (n) => {
1557
+ let r = await n.get(i), s = await n.get(c);
1558
+ if (!s.exists()) throw Error("댓글을 찾을 수 없습니다.");
1559
+ let l = Number(s.data()?.likeCount ?? 0);
1560
+ return r.exists() ? (n.delete(i), n.update(c, { likeCount: Math.max(0, l - 1) }), !1) : (n.set(i, {
1561
+ uid: t,
1562
+ postId: o.value,
1563
+ boardId: a.value,
1564
+ commentId: e,
1565
+ target: "comment",
1566
+ createdAt: G()
1567
+ }), n.update(c, { likeCount: l + 1 }), !0);
1568
+ }), d = new Set(s.value);
1569
+ return u ? d.add(e) : d.delete(e), s.value = d, u;
1570
+ } finally {
1571
+ c.value = null;
1572
+ }
1573
+ }
1574
+ return {
1575
+ isLiked: d,
1576
+ isPending: f,
1577
+ toggleLike: p
1578
+ };
1579
+ }
1580
+ //#endregion
1581
+ //#region src/composables/useMemiBoardReports.ts
1582
+ var Xt = 200, Zt = [
1583
+ {
1584
+ value: "spam",
1585
+ label: "스팸·광고"
1586
+ },
1587
+ {
1588
+ value: "abuse",
1589
+ label: "욕설·혐오·괴롭힘"
1590
+ },
1591
+ {
1592
+ value: "adult",
1593
+ label: "음란물"
1594
+ },
1595
+ {
1596
+ value: "illegal",
1597
+ label: "불법 정보"
1598
+ },
1599
+ {
1600
+ value: "other",
1601
+ label: "기타"
1602
+ }
1603
+ ];
1604
+ function Qt(e) {
1605
+ return Zt.find((t) => t.value === e)?.label || e || "신고";
1606
+ }
1607
+ function $t(e, t) {
1608
+ let n = () => _(), i = Q(), a = Z(), o = P(!1), s = P(!1), c;
1609
+ I(a, (e) => {
1610
+ if (c?.(), c = void 0, !e) {
1611
+ o.value = !1;
1612
+ return;
1613
+ }
1614
+ c = H(r(i, n(), t, e.uid), (e) => {
1615
+ o.value = e.exists();
1616
+ }, (e) => {
1617
+ console.error("[memi-board:reports] onSnapshot failed", e);
1618
+ });
1619
+ }, { immediate: !0 }), N(() => c?.());
1620
+ async function l(c) {
1621
+ let l = a.value?.uid;
1622
+ if (!l) throw Error("로그인이 필요합니다.");
1623
+ if (s.value) return;
1624
+ if (c.authorUid && c.authorUid === l) throw Error("자신의 글은 신고할 수 없습니다.");
1625
+ let u = Zt.some((e) => e.value === c.reason) ? c.reason : null;
1626
+ if (!u) throw Error("신고 사유를 선택해 주세요.");
1627
+ let d = (c.detail || "").trim();
1628
+ if (d.length > 200) throw Error("자세한 내용은 200자까지입니다.");
1629
+ s.value = !0;
1630
+ try {
1631
+ await K(r(i, n(), t, l), {
1632
+ uid: l,
1633
+ target: "post",
1634
+ postId: t,
1635
+ boardId: e,
1636
+ reason: u,
1637
+ detail: d,
1638
+ status: "open",
1639
+ postTitle: (c.postTitle || "").trim().slice(0, 200),
1640
+ authorUid: c.authorUid || "",
1641
+ authorName: c.authorName ?? null,
1642
+ createdAt: G()
1643
+ }), o.value = !0;
1644
+ } finally {
1645
+ s.value = !1;
1646
+ }
1647
+ }
1648
+ return {
1649
+ hasReported: o,
1650
+ pending: s,
1651
+ submitReport: l
1652
+ };
1653
+ }
1654
+ var en = 20;
1655
+ function tn(e = {}) {
1656
+ let t = () => _(), n = Q(), { canManageContent: i, canManageBoard: a } = et(), s = e.pageSize ?? en, c = M(() => F(e.boardId)?.trim() || ""), l = P([]), u = P(!0), d = P(!1), f = P(!1), p = P(""), m = null, h;
1657
+ function g(e) {
1658
+ return {
1659
+ id: e.id,
1660
+ ...e.data()
1661
+ };
1662
+ }
1663
+ function v() {
1664
+ if (h?.(), !i.value) {
1665
+ l.value = [], u.value = !1;
1666
+ return;
1667
+ }
1668
+ u.value = !0;
1669
+ let e = c.value ? [Y("status", "==", "open"), Y("boardId", "==", c.value)] : [Y("status", "==", "open"), Y("target", "==", "post")];
1670
+ h = H(W(o(n, t()), ...e, U("createdAt", "desc"), U(L(), "desc"), V(s)), (e) => {
1671
+ l.value = e.docs.map(g).filter((e) => a(e.boardId)), m = e.docs.at(-1) ?? null, f.value = e.docs.length >= s, u.value = !1;
1672
+ }, (e) => {
1673
+ console.error("[memi-board:report-queue] onSnapshot failed", e), p.value = e.message || String(e), u.value = !1;
1674
+ });
1675
+ }
1676
+ I([i, c], v, { immediate: !0 }), N(() => h?.());
1677
+ async function y() {
1678
+ if (!(d.value || !f.value || !m || !i.value)) {
1679
+ d.value = !0, p.value = "";
1680
+ try {
1681
+ let e = c.value ? [Y("status", "==", "open"), Y("boardId", "==", c.value)] : [Y("status", "==", "open"), Y("target", "==", "post")], r = await z(W(o(n, t()), ...e, U("createdAt", "desc"), U(L(), "desc"), q(m), V(s + 1))), i = r.docs.slice(0, s), u = new Set(l.value.map((e) => e.id));
1682
+ l.value.push(...i.map(g).filter((e) => !u.has(e.id) && a(e.boardId))), m = i.at(-1) ?? m, f.value = r.docs.length > s;
1683
+ } catch (e) {
1684
+ p.value = e.message || String(e);
1685
+ } finally {
1686
+ d.value = !1;
1687
+ }
1688
+ }
1689
+ }
1690
+ async function b(e, i) {
1691
+ !e.id || !a(e.boardId) || await J(r(n, t(), e.postId, e.uid), {
1692
+ status: i,
1693
+ reviewedAt: G(),
1694
+ reviewedBy: Z().value?.uid || ""
1695
+ });
1696
+ }
1697
+ return {
1698
+ reports: l,
1699
+ pending: u,
1700
+ loadingMore: d,
1701
+ hasMore: f,
1702
+ loadError: p,
1703
+ loadMore: y,
1704
+ dismissReport: (e) => b(e, "dismissed"),
1705
+ actionReport: (e) => b(e, "actioned")
1706
+ };
1707
+ }
1414
1708
  //#endregion
1415
1709
  //#region src/composables/useMemiBoardViews.ts
1416
- function Nt() {
1417
- let e = () => m(), t = Z();
1418
- function n(t, n) {
1710
+ function nn() {
1711
+ let e = () => _(), n = Q();
1712
+ function r(t, n) {
1419
1713
  let { postsCollection: r } = e();
1420
1714
  return `memi-board:viewed:${r}:${t}:${n}`;
1421
1715
  }
1422
- function r(e, t) {
1716
+ function i(e, t) {
1423
1717
  if (typeof sessionStorage > "u") return !1;
1424
1718
  try {
1425
- return sessionStorage.getItem(n(e, t)) === "1";
1719
+ return sessionStorage.getItem(r(e, t)) === "1";
1426
1720
  } catch {
1427
1721
  return !1;
1428
1722
  }
1429
1723
  }
1430
1724
  function a(e, t) {
1431
1725
  if (!(typeof sessionStorage > "u")) try {
1432
- sessionStorage.setItem(n(e, t), "1");
1726
+ sessionStorage.setItem(r(e, t), "1");
1433
1727
  } catch {}
1434
1728
  }
1435
1729
  async function o(o, s) {
1436
1730
  let c = o?.trim(), l = s?.trim();
1437
- if (!(!c || !l) && !r(c, l)) {
1731
+ if (!(!c || !l) && !i(c, l)) {
1438
1732
  a(c, l);
1439
1733
  try {
1440
- await J(i(t, e(), l), { viewCount: B(1) });
1734
+ await J(t(n, e(), l), { viewCount: B(1) });
1441
1735
  } catch (e) {
1442
1736
  try {
1443
- sessionStorage.removeItem(n(c, l));
1737
+ sessionStorage.removeItem(r(c, l));
1444
1738
  } catch {}
1445
1739
  import.meta.dev && console.warn("[memi-board] recordView failed", e);
1446
1740
  }
@@ -1448,12 +1742,12 @@ function Nt() {
1448
1742
  }
1449
1743
  return {
1450
1744
  recordView: o,
1451
- hasRecordedView: r
1745
+ hasRecordedView: i
1452
1746
  };
1453
1747
  }
1454
1748
  //#endregion
1455
1749
  //#region src/utils/moderation-prompt.ts
1456
- var Pt = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\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은 \"\"", Ft = {
1750
+ var rn = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\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은 \"\"", an = {
1457
1751
  abuse: "욕설·비속어·모욕",
1458
1752
  spam: "스팸·광고",
1459
1753
  adult: "선정적·성인 내용",
@@ -1461,17 +1755,17 @@ var Pt = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이
1461
1755
  other: "부적절한 내용",
1462
1756
  none: ""
1463
1757
  };
1464
- function It(e) {
1758
+ function on(e) {
1465
1759
  if (e.via === "local" && e.localHit) {
1466
1760
  let t = e.localHit.trim();
1467
1761
  return t.length > 0 && t.length <= 12 ? `「${t}」 같은 표현은 사용할 수 없습니다. 다른 말로 바꿔 주세요.` : "욕설·비속어가 포함되어 게시할 수 없습니다. 표현을 바꿔 주세요.";
1468
1762
  }
1469
1763
  let t = (e.reason || "").trim();
1470
1764
  if (t) return t;
1471
- let n = e.category && e.category !== "none" ? Ft[e.category] || e.category : "";
1765
+ let n = e.category && e.category !== "none" ? an[e.category] || e.category : "";
1472
1766
  return n ? `${n}이(가) 포함되어 게시할 수 없습니다. 내용을 수정해 주세요.` : "게시할 수 없는 내용이 포함되어 있습니다. 내용을 수정해 주세요.";
1473
1767
  }
1474
- var Lt = [
1768
+ var sn = [
1475
1769
  "씨발",
1476
1770
  "시발",
1477
1771
  "시이발",
@@ -1497,10 +1791,10 @@ var Lt = [
1497
1791
  "bitch",
1498
1792
  "asshole"
1499
1793
  ];
1500
- function Rt(e) {
1794
+ function cn(e) {
1501
1795
  return new RegExp(e.join("|"), "i");
1502
1796
  }
1503
- var zt = /* @__PURE__ */ new Set([
1797
+ var ln = /* @__PURE__ */ new Set([
1504
1798
  "none",
1505
1799
  "abuse",
1506
1800
  "spam",
@@ -1508,7 +1802,7 @@ var zt = /* @__PURE__ */ new Set([
1508
1802
  "violence",
1509
1803
  "other"
1510
1804
  ]);
1511
- function Bt(e) {
1805
+ function un(e) {
1512
1806
  let t = e.trim();
1513
1807
  if (!t) return null;
1514
1808
  let n;
@@ -1526,7 +1820,7 @@ function Bt(e) {
1526
1820
  if (typeof n != "object" || !n) return null;
1527
1821
  let r = n;
1528
1822
  if (typeof r.flagged != "boolean") return null;
1529
- let i = typeof r.category == "string" && zt.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
1823
+ let i = typeof r.category == "string" && ln.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
1530
1824
  return {
1531
1825
  flagged: r.flagged,
1532
1826
  category: i,
@@ -1535,20 +1829,20 @@ function Bt(e) {
1535
1829
  }
1536
1830
  //#endregion
1537
1831
  //#region src/composables/useMemiBoardModeration.ts
1538
- var Vt = {
1832
+ var dn = {
1539
1833
  flagged: !1,
1540
1834
  category: "none",
1541
1835
  reason: "",
1542
1836
  via: "empty"
1543
1837
  };
1544
- function Ht() {
1545
- let e = s().moderation ?? {}, t = oe(), { isWriteRestricted: n, restrictedMessage: r, recordContentModerationBlock: i } = Ke(), a = Rt([...Lt, ...e.localBlocklist ?? []]);
1838
+ function fn() {
1839
+ let e = u().moderation ?? {}, t = le(), { isWriteRestricted: n, restrictedMessage: r, recordContentModerationBlock: i } = et(), a = cn([...sn, ...e.localBlocklist ?? []]);
1546
1840
  function o(e) {
1547
1841
  let t = e.match(a);
1548
1842
  return t ? {
1549
1843
  flagged: !0,
1550
1844
  category: "abuse",
1551
- reason: It({
1845
+ reason: on({
1552
1846
  via: "local",
1553
1847
  localHit: t[0] || "",
1554
1848
  category: "abuse"
@@ -1556,27 +1850,27 @@ function Ht() {
1556
1850
  via: "local"
1557
1851
  } : null;
1558
1852
  }
1559
- function c() {
1560
- let n = e.model ?? "gemini-3.5-flash-lite", r = e.useLimitedUseAppCheckTokens !== !1, i = xe(t, {
1561
- backend: new ye(),
1853
+ function s() {
1854
+ let n = e.model ?? "gemini-3.5-flash-lite", r = e.useLimitedUseAppCheckTokens !== !1, i = we(t, {
1855
+ backend: new Se(),
1562
1856
  useLimitedUseAppCheckTokens: r
1563
1857
  });
1564
- return Se(i, {
1858
+ return Te(i, {
1565
1859
  model: n,
1566
- systemInstruction: Pt,
1860
+ systemInstruction: rn,
1567
1861
  generationConfig: {
1568
1862
  temperature: 0,
1569
1863
  maxOutputTokens: 256,
1570
1864
  responseMimeType: "application/json",
1571
- responseSchema: be.object({ properties: {
1572
- flagged: be.boolean(),
1573
- category: be.string(),
1574
- reason: be.string()
1865
+ responseSchema: Ce.object({ properties: {
1866
+ flagged: Ce.boolean(),
1867
+ category: Ce.string(),
1868
+ reason: Ce.string()
1575
1869
  } })
1576
1870
  }
1577
1871
  });
1578
1872
  }
1579
- function l() {
1873
+ function c() {
1580
1874
  return e.onError === "block" ? {
1581
1875
  flagged: !0,
1582
1876
  category: "other",
@@ -1591,8 +1885,8 @@ function Ht() {
1591
1885
  via: "ai-error-allow"
1592
1886
  };
1593
1887
  }
1594
- function u(e, t = "") {
1595
- let n = It({
1888
+ function l(e, t = "") {
1889
+ let n = on({
1596
1890
  reason: e.reason,
1597
1891
  category: e.category,
1598
1892
  via: e.via
@@ -1606,9 +1900,9 @@ function Ht() {
1606
1900
  if (!e.flagged || e.error || e.via !== "local" && e.via !== "ai") return e;
1607
1901
  try {
1608
1902
  let { messageSuffix: t } = await i();
1609
- return u(e, t);
1903
+ return l(e, t);
1610
1904
  } catch (t) {
1611
- return console.warn("[memi-board] record moderation strike failed", t instanceof Error ? t.message : t), u(e);
1905
+ return console.warn("[memi-board] record moderation strike failed", t instanceof Error ? t.message : t), l(e);
1612
1906
  }
1613
1907
  }
1614
1908
  async function f(t) {
@@ -1620,22 +1914,22 @@ function Ht() {
1620
1914
  };
1621
1915
  let i = t.trim();
1622
1916
  if (!i) return {
1623
- ...Vt,
1917
+ ...dn,
1624
1918
  via: "empty"
1625
1919
  };
1626
1920
  let a = o(i);
1627
1921
  if (a) return d(a);
1628
1922
  if (e.enabled === !1) return {
1629
- ...Vt,
1923
+ ...dn,
1630
1924
  via: "disabled"
1631
1925
  };
1632
1926
  try {
1633
- let e = Bt((await c().generateContent(`심사할 텍스트:\n"""${i.slice(0, 4e3)}"""`)).response.text());
1634
- if (!e) return l();
1927
+ let e = un((await s().generateContent(`심사할 텍스트:\n"""${i.slice(0, 4e3)}"""`)).response.text());
1928
+ if (!e) return c();
1635
1929
  let t = {
1636
1930
  flagged: e.flagged,
1637
1931
  category: e.category,
1638
- reason: It({
1932
+ reason: on({
1639
1933
  reason: e.reason,
1640
1934
  category: e.category,
1641
1935
  via: "ai"
@@ -1644,7 +1938,7 @@ function Ht() {
1644
1938
  };
1645
1939
  return t.flagged ? d(t) : t;
1646
1940
  } catch (e) {
1647
- return console.warn("[memi-board] moderation AI failed", e instanceof Error ? e.message : e), l();
1941
+ return console.warn("[memi-board] moderation AI failed", e instanceof Error ? e.message : e), c();
1648
1942
  }
1649
1943
  }
1650
1944
  async function p(t) {
@@ -1655,22 +1949,22 @@ function Ht() {
1655
1949
  via: "restricted"
1656
1950
  };
1657
1951
  if (e.enabled === !1 || !e.moderateImages) return {
1658
- ...Vt,
1952
+ ...dn,
1659
1953
  via: "disabled"
1660
1954
  };
1661
1955
  try {
1662
1956
  let e = await new Promise((e, n) => {
1663
1957
  let r = new FileReader();
1664
1958
  r.onload = () => e(r.result.split(",")[1] ?? ""), r.onerror = n, r.readAsDataURL(t);
1665
- }), n = Bt((await c().generateContent([{ inlineData: {
1959
+ }), n = un((await s().generateContent([{ inlineData: {
1666
1960
  mimeType: t.type || "image/jpeg",
1667
1961
  data: e
1668
1962
  } }, "이 이미지를 심사해 주세요."])).response.text());
1669
- if (!n) return l();
1963
+ if (!n) return c();
1670
1964
  let r = {
1671
1965
  flagged: n.flagged,
1672
1966
  category: n.category,
1673
- reason: It({
1967
+ reason: on({
1674
1968
  reason: n.reason,
1675
1969
  category: n.category,
1676
1970
  via: "ai"
@@ -1679,7 +1973,7 @@ function Ht() {
1679
1973
  };
1680
1974
  return r.flagged ? d(r) : r;
1681
1975
  } catch (e) {
1682
- return console.warn("[memi-board] moderation AI image failed", e instanceof Error ? e.message : e), l();
1976
+ return console.warn("[memi-board] moderation AI image failed", e instanceof Error ? e.message : e), c();
1683
1977
  }
1684
1978
  }
1685
1979
  return {
@@ -1689,7 +1983,7 @@ function Ht() {
1689
1983
  }
1690
1984
  //#endregion
1691
1985
  //#region src/composables/useMemiBoardUsers.ts
1692
- var Ut = [
1986
+ var pn = [
1693
1987
  {
1694
1988
  value: "admin",
1695
1989
  label: "관리자",
@@ -1706,11 +2000,11 @@ var Ut = [
1706
2000
  description: "글과 댓글을 작성하고 본인이 작성한 콘텐츠만 수정·삭제할 수 있습니다."
1707
2001
  }
1708
2002
  ];
1709
- function Wt(e = {}) {
1710
- let t = () => m(), n = Z(), r = O(() => M(e.enabled) ?? !0 ? p(n, t()) : null), i = re(r, { ssrKey: d(t(), "users-management") });
2003
+ function mn(e = {}) {
2004
+ let t = () => _(), n = Q(), r = M(() => F(e.enabled) ?? !0 ? h(n, t()) : null), i = se(r, { ssrKey: w(t(), "users-management") });
1711
2005
  async function a(e, r) {
1712
- if (!Ut.some((e) => e.value === r)) throw Error("지원하지 않는 역할입니다.");
1713
- await J(b(n, t(), e), {
2006
+ if (!pn.some((e) => e.value === r)) throw Error("지원하지 않는 역할입니다.");
2007
+ await J(x(n, t(), e), {
1714
2008
  role: r,
1715
2009
  updatedAt: G()
1716
2010
  });
@@ -1723,19 +2017,239 @@ function Wt(e = {}) {
1723
2017
  }
1724
2018
  //#endregion
1725
2019
  //#region src/composables/useMemiBoardUserProfile.ts
1726
- function Gt(e) {
1727
- let t = Z(), n = () => m(), r = O(() => M(e)), i = O(() => {
2020
+ function hn(e) {
2021
+ let t = Q(), n = () => _(), r = M(() => F(e)), i = M(() => {
1728
2022
  let e = r.value;
1729
- return e ? b(t, n(), e) : null;
1730
- }), { data: a, pending: o } = ae(i);
2023
+ return e ? x(t, n(), e) : null;
2024
+ }), { data: a, pending: o } = ce(i);
1731
2025
  return {
1732
2026
  profile: a,
1733
2027
  profilePending: o
1734
2028
  };
1735
2029
  }
1736
2030
  //#endregion
2031
+ //#region src/utils/section.ts
2032
+ var gn = [
2033
+ 3,
2034
+ 4,
2035
+ 5,
2036
+ 6,
2037
+ 7,
2038
+ 8,
2039
+ 9,
2040
+ 12
2041
+ ], _n = [
2042
+ {
2043
+ value: "list",
2044
+ label: "게시목록"
2045
+ },
2046
+ {
2047
+ value: "post",
2048
+ label: "게시물"
2049
+ },
2050
+ {
2051
+ value: "comments",
2052
+ label: "댓글모음"
2053
+ }
2054
+ ], vn = [{
2055
+ value: "latest",
2056
+ label: "최신순"
2057
+ }, {
2058
+ value: "likes",
2059
+ label: "좋아요순"
2060
+ }];
2061
+ function yn(e) {
2062
+ return e === "list" ? 5 : 4;
2063
+ }
2064
+ function bn(e, t) {
2065
+ let n = Math.floor(Number(t));
2066
+ return Number.isFinite(n) ? Math.min(e === "list" ? 20 : 10, Math.max(1, n)) : yn(e);
2067
+ }
2068
+ function xn(e) {
2069
+ return e === 12 ? null : 12 - e;
2070
+ }
2071
+ function Sn(e) {
2072
+ return {
2073
+ 3: "col-span-12 md:col-span-3",
2074
+ 4: "col-span-12 md:col-span-4",
2075
+ 5: "col-span-12 md:col-span-5",
2076
+ 6: "col-span-12 md:col-span-6",
2077
+ 7: "col-span-12 md:col-span-7",
2078
+ 8: "col-span-12 md:col-span-8",
2079
+ 9: "col-span-12 md:col-span-9",
2080
+ 12: "col-span-12"
2081
+ }[e];
2082
+ }
2083
+ function Cn(e) {
2084
+ return e === "sm" ? "min-h-40" : e === "lg" ? "min-h-80" : "min-h-56";
2085
+ }
2086
+ function wn(e) {
2087
+ let t = e.boardId || e.category || "free";
2088
+ return e.slug ? `/board/${t}/${e.slug}` : `/board/${t}`;
2089
+ }
2090
+ function Tn(e) {
2091
+ return e.kind === "comments" ? e.sort === "likes" ? "/boardComments?sort=likes" : "/boardComments" : e.kind === "list" && e.boardId ? `/board/${e.boardId}` : "/board";
2092
+ }
2093
+ function En(e) {
2094
+ if (!(!e.boardId || !e.postId)) return `/board/${e.boardId}/post/${e.postId}`;
2095
+ }
2096
+ function Dn(e) {
2097
+ return e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
2098
+ }
2099
+ function On(e) {
2100
+ return e.title?.trim() || e.summary || "게시물";
2101
+ }
2102
+ function kn(e) {
2103
+ return e === "image" || e === "video" ? "media" : "list";
2104
+ }
2105
+ function An(e) {
2106
+ return kn(e) === "media" ? "미디어" : "목록";
2107
+ }
2108
+ function jn(e, t) {
2109
+ return e ? t(e)?.listView ?? "default" : "default";
2110
+ }
2111
+ function Mn(e, t) {
2112
+ let n = e.map((e) => jn(e.boardId, t));
2113
+ if (!n.length) return "default";
2114
+ if (n.every((e) => e === n[0])) return n[0];
2115
+ let r = n.filter((e) => e === "image").length, i = n.filter((e) => e === "video").length;
2116
+ return r >= n.length / 2 ? "image" : i >= n.length / 2 ? "video" : "default";
2117
+ }
2118
+ //#endregion
2119
+ //#region src/composables/useMemiBoardSections.ts
2120
+ var Nn = [];
2121
+ function Pn(e) {
2122
+ let t = Number(e.cols);
2123
+ if (gn.includes(t)) return t;
2124
+ let n = e.width;
2125
+ return n === "xs" ? 3 : n === "sm" ? 4 : n === "lg" || Number(e.span) === 2 ? 12 : 6;
2126
+ }
2127
+ function Fn(e) {
2128
+ let t = e.height ?? e.size;
2129
+ return t === "sm" || t === "lg" ? t : "md";
2130
+ }
2131
+ function In(e) {
2132
+ return e.kind === "post" || e.kind === "comments" ? e.kind : "list";
2133
+ }
2134
+ function Ln(e) {
2135
+ return e.sort === "likes" ? "likes" : "latest";
2136
+ }
2137
+ function Rn(e) {
2138
+ return (typeof e.boardId == "string" ? e.boardId.trim() : "") || null;
2139
+ }
2140
+ function zn(e, t) {
2141
+ let n = Array.isArray(e.postIds) ? e.postIds : [], r = [];
2142
+ for (let e of n) {
2143
+ let n = String(e || "").trim();
2144
+ if (!(!n || r.includes(n)) && (r.push(n), r.length >= t)) break;
2145
+ }
2146
+ return r;
2147
+ }
2148
+ function Bn(e, t) {
2149
+ let n = In(e), r = bn(n, e.count);
2150
+ return {
2151
+ id: String(e.id || `box-${t + 1}`),
2152
+ title: String(e.title || `섹션 ${t + 1}`),
2153
+ showTitle: e.showTitle !== !1,
2154
+ kind: n,
2155
+ boardId: Rn(e),
2156
+ count: r,
2157
+ postIds: zn(e, r),
2158
+ sort: Ln(e),
2159
+ cols: Pn(e),
2160
+ height: Fn(e),
2161
+ order: t
2162
+ };
2163
+ }
2164
+ function Vn() {
2165
+ let e = Q(), t = re(e, "settings", "homeLayout"), { data: n, pending: r } = ce(t), i = M(() => (Array.isArray(n.value?.blocks) ? n.value.blocks : Nn).map((e, t) => Bn(e, t)));
2166
+ async function a(e) {
2167
+ await K(t, {
2168
+ columns: 12,
2169
+ blocks: e.map((e, t) => {
2170
+ let n = e, r = In(n), i = bn(r, e.count);
2171
+ return {
2172
+ id: e.id,
2173
+ title: e.title.trim() || `섹션 ${t + 1}`,
2174
+ showTitle: e.showTitle !== !1,
2175
+ kind: r,
2176
+ boardId: Rn(n),
2177
+ count: i,
2178
+ postIds: zn(n, i),
2179
+ sort: Ln(n),
2180
+ cols: Pn(n),
2181
+ height: Fn(n),
2182
+ order: t
2183
+ };
2184
+ }),
2185
+ updatedAt: G()
2186
+ });
2187
+ }
2188
+ return {
2189
+ settings: n,
2190
+ pending: r,
2191
+ sections: i,
2192
+ blocks: i,
2193
+ hasSavedLayout: M(() => Array.isArray(n.value?.blocks)),
2194
+ saveSections: a,
2195
+ saveBlocks: a
2196
+ };
2197
+ }
2198
+ //#endregion
2199
+ //#region src/composables/useMemiBoardMiniPosts.ts
2200
+ function Hn(e, t) {
2201
+ let n = Q(), r = () => _(), i = M(() => {
2202
+ if (!import.meta.client) return null;
2203
+ let i = Math.max(1, Math.floor(Number(F(t)) || 5)), a = F(e)?.trim() || "", o = [Y("isPublished", "==", !0), Y("listed", "==", !0)];
2204
+ return a && o.unshift(Y("boardId", "==", a)), W(c(n, r()), ...o, U("createdAt", "desc"), V(i));
2205
+ }), { data: a, pending: o, error: s } = se(i);
2206
+ return {
2207
+ posts: M(() => a.value ?? []),
2208
+ pending: o,
2209
+ error: s
2210
+ };
2211
+ }
2212
+ function Un(e) {
2213
+ let t = Q(), n = () => _(), r = M(() => [...new Set(F(e).map((e) => e.trim()).filter(Boolean))].slice(0, 10)), i = M(() => !import.meta.client || !r.value.length ? null : W(c(t, n()), Y(L(), "in", r.value))), { data: a, pending: o, error: s } = se(i);
2214
+ return {
2215
+ posts: M(() => {
2216
+ let e = new Map((a.value ?? []).map((e) => [e.id, e])), t = [];
2217
+ for (let n of r.value) {
2218
+ let r = e.get(n);
2219
+ r && r.isPublished !== !1 && t.push(r);
2220
+ }
2221
+ return t;
2222
+ }),
2223
+ pending: o,
2224
+ error: s
2225
+ };
2226
+ }
2227
+ //#endregion
1737
2228
  //#region src/data/versionHistory.ts
1738
- var Kt = [
2229
+ var Wn = [
2230
+ {
2231
+ version: "0.36.0",
2232
+ date: "2026-08-19",
2233
+ highlights: ["게시물을 신고할 수 있어요. 담당 스태프가 내용을 확인해요", "스태프는 지정된 게시판만 관리해요"],
2234
+ note: "호스트는 Firestore 신고 규칙·인덱스를 배포해야 해요."
2235
+ },
2236
+ {
2237
+ version: "0.35.0",
2238
+ date: "2026-08-19",
2239
+ highlights: ["메인에 게시목록·게시물·댓글모음 섹션을 올릴 수 있어요", "댓글모음은 최신순이나 좋아요순으로 네 개부터 보여요"]
2240
+ },
2241
+ {
2242
+ version: "0.34.0",
2243
+ date: "2026-08-19",
2244
+ highlights: ["댓글을 최신순이나 좋아요순으로 모아 볼 수 있어요"],
2245
+ note: "호스트는 좋아요순 목록용 Firestore 인덱스(likeCount + createdAt)를 배포해야 해요."
2246
+ },
2247
+ {
2248
+ version: "0.33.0",
2249
+ date: "2026-08-19",
2250
+ highlights: ["댓글과 답글에도 좋아요를 누를 수 있어요", "글을 지우면 댓글 좋아요도 함께 삭제돼요"],
2251
+ note: "호스트는 Firestore 규칙과 좋아요 인덱스를 병합·배포해야 댓글 좋아요가 동작해요."
2252
+ },
1739
2253
  {
1740
2254
  version: "0.32.0",
1741
2255
  date: "2026-08-14",
@@ -1945,7 +2459,7 @@ var Kt = [
1945
2459
  highlights: ["글쓰기·댓글·첨부·로그인 권한으로 게시판을 바로 시작할 수 있어요", "부적절한 글·댓글은 AI 검열로 막을 수 있어요"],
1946
2460
  note: "실제 보안은 호스트 Firebase 규칙 등록이 필요합니다."
1947
2461
  }
1948
- ], qt = /* @__PURE__ */ r(((e, t) => {
2462
+ ], Gn = /* @__PURE__ */ i(((e, t) => {
1949
2463
  (function(n, r) {
1950
2464
  typeof e == "object" && t !== void 0 ? t.exports = r() : typeof define == "function" && define.amd ? define(r) : (n = typeof globalThis < "u" ? globalThis : n || self).dayjs = r();
1951
2465
  })(e, (function() {
@@ -2239,7 +2753,7 @@ var Kt = [
2239
2753
  return w(1e3 * e);
2240
2754
  }, w.en = b[y], w.Ls = b, w.p = {}, w;
2241
2755
  }));
2242
- })), Jt = /* @__PURE__ */ r(((e, t) => {
2756
+ })), Kn = /* @__PURE__ */ i(((e, t) => {
2243
2757
  (function(n, r) {
2244
2758
  typeof e == "object" && t !== void 0 ? t.exports = r() : typeof define == "function" && define.amd ? define(r) : (n = typeof globalThis < "u" ? globalThis : n || self).dayjs_plugin_relativeTime = r();
2245
2759
  })(e, (function() {
@@ -2343,9 +2857,9 @@ var Kt = [
2343
2857
  };
2344
2858
  };
2345
2859
  }));
2346
- })), Yt = /* @__PURE__ */ r(((e, t) => {
2860
+ })), qn = /* @__PURE__ */ i(((e, t) => {
2347
2861
  (function(n, r) {
2348
- typeof e == "object" && t !== void 0 ? t.exports = r(qt()) : typeof define == "function" && define.amd ? define(["dayjs"], r) : (n = typeof globalThis < "u" ? globalThis : n || self).dayjs_locale_ko = r(n.dayjs);
2862
+ typeof e == "object" && t !== void 0 ? t.exports = r(Gn()) : typeof define == "function" && define.amd ? define(["dayjs"], r) : (n = typeof globalThis < "u" ? globalThis : n || self).dayjs_locale_ko = r(n.dayjs);
2349
2863
  })(e, (function(e) {
2350
2864
  function t(e) {
2351
2865
  return e && typeof e == "object" && "default" in e ? e : { default: e };
@@ -2393,24 +2907,24 @@ var Kt = [
2393
2907
  };
2394
2908
  return n.default.locale(r, null, !0), r;
2395
2909
  }));
2396
- })), Xt = /* @__PURE__ */ h(qt(), 1), Zt = /* @__PURE__ */ h(Jt(), 1);
2397
- Yt(), Xt.default.extend(Zt.default), Xt.default.locale("ko");
2398
- function Qt(e) {
2910
+ })), Jn = /* @__PURE__ */ a(Gn(), 1), Yn = /* @__PURE__ */ a(Kn(), 1);
2911
+ qn(), Jn.default.extend(Yn.default), Jn.default.locale("ko");
2912
+ function Xn(e) {
2399
2913
  return e?.toDate ? e.toDate().toLocaleDateString("ko-KR") : "";
2400
2914
  }
2401
- function $t(e, t = Date.now()) {
2402
- return e?.toDate ? (0, Xt.default)(e.toDate()).from((0, Xt.default)(t)) : "방금 전";
2915
+ function Zn(e, t = Date.now()) {
2916
+ return e?.toDate ? (0, Jn.default)(e.toDate()).from((0, Jn.default)(t)) : "방금 전";
2403
2917
  }
2404
- function en(e) {
2405
- return e?.toDate ? (0, Xt.default)(e.toDate()).format("YYYY년 M월 D일 HH:mm:ss") : "시간 확인 중";
2918
+ function Qn(e) {
2919
+ return e?.toDate ? (0, Jn.default)(e.toDate()).format("YYYY년 M월 D일 HH:mm:ss") : "시간 확인 중";
2406
2920
  }
2407
- function tn(e, t) {
2408
- let n = en(e), r = e?.toMillis?.(), i = t?.toMillis?.();
2409
- return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${en(t)}`];
2921
+ function $n(e, t) {
2922
+ let n = Qn(e), r = e?.toMillis?.(), i = t?.toMillis?.();
2923
+ return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${Qn(t)}`];
2410
2924
  }
2411
2925
  //#endregion
2412
2926
  //#region src/composables/useMemiBoardWritingAssistant.ts
2413
- var nn = 10, rn = 864e5, an = {
2927
+ var er = 10, tr = 864e5, nr = {
2414
2928
  proofread: "맞춤법, 띄어쓰기, 문법 오류만 고쳐라. 의미와 말투는 바꾸지 마라.",
2415
2929
  polish: "뜻과 작성자의 말투를 유지하면서 더 자연스럽고 읽기 좋은 문장으로 다듬어라.",
2416
2930
  continue: "앞의 맥락과 말투에 맞는 내용을 과장 없이 한두 문단 이어서 작성하라.",
@@ -2419,23 +2933,23 @@ var nn = 10, rn = 864e5, an = {
2419
2933
  "translate-ko": "자연스러운 한국어로 번역하라. 고유명사와 기술 용어는 정확히 유지하라.",
2420
2934
  title: "본문을 대표하는 간결한 한국어 제목 하나를 만들어라. 따옴표나 설명 없이 제목만 출력하라."
2421
2935
  };
2422
- function on(e) {
2936
+ function rr(e) {
2423
2937
  return e.trim().replace(/^```(?:html|text)?\s*/i, "").replace(/\s*```$/, "").trim();
2424
2938
  }
2425
- function sn(e) {
2939
+ function ir(e) {
2426
2940
  return !e || typeof e != "object" ? null : "toMillis" in e && typeof e.toMillis == "function" ? e.toMillis() : null;
2427
2941
  }
2428
- function cn() {
2429
- let e = oe(), t = Z(), n = ie(), r = s();
2942
+ function ar() {
2943
+ let e = le(), t = Q(), n = Z(), r = u();
2430
2944
  async function i() {
2431
2945
  let e = n.value?.uid;
2432
2946
  if (!e) throw Error("AI 글쓰기 도우미는 로그인 후 사용할 수 있습니다.");
2433
- await ne(t, async (n) => {
2434
- let r = b(t, m(), e), i = await n.get(r);
2947
+ await oe(t, async (n) => {
2948
+ let r = x(t, _(), e), i = await n.get(r);
2435
2949
  if (!i.exists()) throw Error("사용자 정보를 확인한 뒤 다시 시도해 주세요.");
2436
- let a = i.data(), o = sn(a.aiWritingWindowAt), s = Math.max(0, Math.floor(Number(a.aiWritingCount) || 0)), c = Date.now(), l = o != null && c - o < rn;
2950
+ let a = i.data(), o = ir(a.aiWritingWindowAt), s = Math.max(0, Math.floor(Number(a.aiWritingCount) || 0)), c = Date.now(), l = o != null && c - o < tr;
2437
2951
  if (l && s >= 10) {
2438
- let e = o + rn, t = Math.max(1, Math.ceil((e - c) / 36e5));
2952
+ let e = o + tr, t = Math.max(1, Math.ceil((e - c) / 36e5));
2439
2953
  throw Error(`AI 글쓰기 도우미는 하루 10회까지 사용할 수 있습니다. 약 ${t}시간 후 다시 이용해 주세요.`);
2440
2954
  }
2441
2955
  n.update(r, {
@@ -2447,10 +2961,10 @@ function cn() {
2447
2961
  }
2448
2962
  async function a(t) {
2449
2963
  await i();
2450
- let n = r.moderation ?? {}, a = xe(e, {
2451
- backend: new ye(),
2964
+ let n = r.moderation ?? {}, a = we(e, {
2965
+ backend: new Se(),
2452
2966
  useLimitedUseAppCheckTokens: n.useLimitedUseAppCheckTokens !== !1
2453
- }), o = Se(a, {
2967
+ }), o = Te(a, {
2454
2968
  model: n.model ?? "gemini-3.5-flash-lite",
2455
2969
  systemInstruction: [
2456
2970
  "너는 게시판 글쓰기 편집 도우미다.",
@@ -2464,10 +2978,10 @@ function cn() {
2464
2978
  }), s = t.content.trim();
2465
2979
  if (!s) throw Error("AI로 다듬을 내용을 먼저 입력해 주세요.");
2466
2980
  let c = [
2467
- `작업: ${an[t.action]}`,
2981
+ `작업: ${nr[t.action]}`,
2468
2982
  t.title ? `현재 제목: ${t.title}` : "",
2469
2983
  `본문:\n${s.slice(0, 12e3)}`
2470
- ].filter(Boolean).join("\n\n"), l = on((await o.generateContent(c)).response.text());
2984
+ ].filter(Boolean).join("\n\n"), l = rr((await o.generateContent(c)).response.text());
2471
2985
  if (!l) throw Error("AI가 결과를 만들지 못했습니다. 다시 시도해 주세요.");
2472
2986
  return l;
2473
2987
  }
@@ -2475,14 +2989,14 @@ function cn() {
2475
2989
  }
2476
2990
  //#endregion
2477
2991
  //#region src/utils/renderMarkdown.ts
2478
- function $(e) {
2992
+ function or(e) {
2479
2993
  return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2480
2994
  }
2481
- function ln(e) {
2995
+ function sr(e) {
2482
2996
  let t = e;
2483
2997
  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;
2484
2998
  }
2485
- function un(e) {
2999
+ function cr(e) {
2486
3000
  if (!e?.trim()) return "";
2487
3001
  let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = 0, i = !1, a = [], o = null, s = [];
2488
3002
  function c() {
@@ -2491,7 +3005,7 @@ function un(e) {
2491
3005
  return;
2492
3006
  }
2493
3007
  let e = o;
2494
- n.push(`<${e}>${s.map((e) => `<li>${ln(e)}</li>`).join("")}</${e}>`), o = null, s = [];
3008
+ n.push(`<${e}>${s.map((e) => `<li>${sr(e)}</li>`).join("")}</${e}>`), o = null, s = [];
2495
3009
  }
2496
3010
  for (; r < t.length;) {
2497
3011
  let e = t[r] ?? "", l = e.match(/^```(\w*)\s*$/);
@@ -2500,7 +3014,7 @@ function un(e) {
2500
3014
  continue;
2501
3015
  }
2502
3016
  if (i) {
2503
- a.push($(e)), r++;
3017
+ a.push(or(e)), r++;
2504
3018
  continue;
2505
3019
  }
2506
3020
  if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(e)) {
@@ -2511,24 +3025,24 @@ function un(e) {
2511
3025
  if (u) {
2512
3026
  c();
2513
3027
  let e = u[1].length;
2514
- n.push(`<h${e} class="font-semibold mt-4 mb-2">${ln($(u[2]))}</h${e}>`), r++;
3028
+ n.push(`<h${e} class="font-semibold mt-4 mb-2">${sr(or(u[2]))}</h${e}>`), r++;
2515
3029
  continue;
2516
3030
  }
2517
3031
  if (e.startsWith("> ")) {
2518
3032
  c();
2519
3033
  let e = [];
2520
- for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push($((t[r] ?? "").slice(2))), r++;
2521
- n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${ln(e.join("<br />"))}</blockquote>`);
3034
+ for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push(or((t[r] ?? "").slice(2))), r++;
3035
+ n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${sr(e.join("<br />"))}</blockquote>`);
2522
3036
  continue;
2523
3037
  }
2524
3038
  let d = e.match(/^[-*+]\s+(.+)$/);
2525
3039
  if (d) {
2526
- o && o !== "ul" && c(), o = "ul", s.push($(d[1])), r++;
3040
+ o && o !== "ul" && c(), o = "ul", s.push(or(d[1])), r++;
2527
3041
  continue;
2528
3042
  }
2529
3043
  let f = e.match(/^\d+\.\s+(.+)$/);
2530
3044
  if (f) {
2531
- o && o !== "ol" && c(), o = "ol", s.push($(f[1])), r++;
3045
+ o && o !== "ol" && c(), o = "ol", s.push(or(f[1])), r++;
2532
3046
  continue;
2533
3047
  }
2534
3048
  if (!e.trim()) {
@@ -2540,13 +3054,13 @@ function un(e) {
2540
3054
  for (; r < t.length;) {
2541
3055
  let e = t[r] ?? "";
2542
3056
  if (!e.trim() || /^(#{1,3}\s|```|[-*+]\s|\d+\.\s|> |(-{3,}|\*{3,})\s*$)/.test(e)) break;
2543
- p.push($(e)), r++;
3057
+ p.push(or(e)), r++;
2544
3058
  }
2545
- n.push(`<p class="my-2 leading-relaxed">${ln(p.join("<br />"))}</p>`);
3059
+ n.push(`<p class="my-2 leading-relaxed">${sr(p.join("<br />"))}</p>`);
2546
3060
  }
2547
3061
  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");
2548
3062
  }
2549
3063
  //#endregion
2550
- export { Ct as AUTHOR_MEMO_MAX_LENGTH, Ut as BOARD_USER_ROLES, Ot as COMMENT_BODY_MAX_LENGTH, Re as DEFAULT_BLOCK_BAN_DECAY_MS, Le as DEFAULT_BLOCK_BAN_THRESHOLD, ct as DEFAULT_BOARDS, lt as DEFAULT_CATEGORIES, y as DEFAULT_COMMENTS_COLLECTION, S as DEFAULT_LIKES_COLLECTION, l as DEFAULT_POSTS_COLLECTION, g as DEFAULT_SETTINGS_COLLECTION, C as DEFAULT_USERS_COLLECTION, nn as WRITING_ASSISTANT_DAILY_LIMIT, Ce as asHttpUrl, ke as boardListOgDescription, Oe as boardListOgTitle, De as boardPostOgDescription, Ee as boardPostOgTitle, d as boardSsrKey, b as boardUserDoc, p as boardUsersCol, st as buildPostPreview, f as commentDoc, o as commentsCol, x as configureMemiBoard, ze as effectiveModerationBlockCount, qe as extractEditorImageUrls, Fe as fetchPublicListForSeo, Pe as fetchPublicPostForSeo, Qt as formatDate, en as formatFullDate, $t as formatRelativeDate, He as formatRestrictedUntilLabel, tn as formatTimestampDetails, nt as hasBodyImage, tt as hasBodyText, rt as isContentEmpty, pt as isFirestorePermissionDenied, Ve as isModerationWriteRestricted, w as likeDoc, D as likeDocId, c as likesCol, Be as moderationWriteRestrictedUntilMs, Ae as normalizeBasePath, $e as plainTextFromHtml, E as postBodyDoc, i as postDoc, Ye as postNamespaceFromStoragePath, e as postStorageFolder, T as postsCol, un as renderMarkdownToHtml, a as resolveBoardPathConfig, je as resolvePublicSeoDb, n as settingsCol, t as settingsDoc, v as slugify, Je as storagePathFromDownloadUrl, et as titleFromBody, Te as toAbsoluteUrl, we as toBoardOgImageUrl, m as useBoardPathConfig, Ke as useMemiBoardAuth, Tt as useMemiBoardAuthorMemo, At as useMemiBoardComments, s as useMemiBoardConfig, Mt as useMemiBoardLikes, Ht as useMemiBoardModeration, vt as useMemiBoardPost, _t as useMemiBoardPostList, ht as useMemiBoardPosts, jt as useMemiBoardReplies, ft as useMemiBoardSettings, St as useMemiBoardUserComments, bt as useMemiBoardUserPosts, Gt as useMemiBoardUserProfile, Wt as useMemiBoardUsers, Nt as useMemiBoardViews, cn as useMemiBoardWritingAssistant, Kt as versionHistory, ot as videoListCoverUrl, at as youtubeId };
3064
+ export { Rt as AUTHOR_MEMO_MAX_LENGTH, gn as BOARD_SECTION_COLS, _n as BOARD_SECTION_KINDS, vn as BOARD_SECTION_SORTS, pn as BOARD_USER_ROLES, Ut as COMMENT_BODY_MAX_LENGTH, Ke as DEFAULT_BLOCK_BAN_DECAY_MS, Ge as DEFAULT_BLOCK_BAN_THRESHOLD, xt as DEFAULT_BOARDS, St as DEFAULT_CATEGORIES, C as DEFAULT_COMMENTS_COLLECTION, E as DEFAULT_LIKES_COLLECTION, f as DEFAULT_POSTS_COLLECTION, b as DEFAULT_REPORTS_COLLECTION, D as DEFAULT_SETTINGS_COLLECTION, p as DEFAULT_USERS_COLLECTION, Xt as REPORT_DETAIL_MAX_LENGTH, Zt as REPORT_REASONS, er as WRITING_ASSISTANT_DAILY_LIMIT, Ee as asHttpUrl, Me as boardListOgDescription, je as boardListOgTitle, Ae as boardPostOgDescription, ke as boardPostOgTitle, w as boardSsrKey, x as boardUserDoc, h as boardUsersCol, bt as buildPostPreview, Ve as canManageBoardByRole, bn as clampSectionCount, l as commentDoc, O as commentsCol, T as configureMemiBoard, yn as defaultSectionCount, qe as effectiveModerationBlockCount, tt as extractEditorImageUrls, Re as fetchPublicListForSeo, Le as fetchPublicPostForSeo, Xn as formatDate, Qn as formatFullDate, Zn as formatRelativeDate, Xe as formatRestrictedUntilLabel, $n as formatTimestampDetails, ht as hasBodyImage, mt as hasBodyText, Be as isAssignedStaff, gt as isContentEmpty, Et as isFirestorePermissionDenied, Ye as isModerationWriteRestricted, j as likeDoc, d as likeDocId, A as likesCol, Jt as memiBoardCommentLikesKey, jn as miniBoardView, En as miniCommentLink, Tn as miniMoreLink, Dn as miniPostImage, wn as miniPostLink, On as miniPostTitle, Mn as miniPostsView, kn as miniViewGroup, An as miniViewLabel, Je as moderationWriteRestrictedUntilMs, Ne as normalizeBasePath, Bn as normalizeBoardSection, xn as pairSectionCols, ft as plainTextFromHtml, s as postBodyDoc, t as postDoc, rt as postNamespaceFromStoragePath, k as postStorageFolder, c as postsCol, cr as renderMarkdownToHtml, r as reportDoc, n as reportDocId, Qt as reportReasonLabel, o as reportsCol, y as resolveBoardPathConfig, Pe as resolvePublicSeoDb, Sn as sectionColClass, Cn as sectionMinHeight, e as settingsCol, v as settingsDoc, S as slugify, nt as storagePathFromDownloadUrl, pt as titleFromBody, Oe as toAbsoluteUrl, De as toBoardOgImageUrl, _ as useBoardPathConfig, et as useMemiBoardAuth, Bt as useMemiBoardAuthorMemo, Lt as useMemiBoardCommentFeed, Yt as useMemiBoardCommentLikes, Gt as useMemiBoardComments, u as useMemiBoardConfig, qt as useMemiBoardLikes, Hn as useMemiBoardMiniLatest, Un as useMemiBoardMiniPicked, fn as useMemiBoardModeration, jt as useMemiBoardPost, At as useMemiBoardPostList, Ot as useMemiBoardPosts, Kt as useMemiBoardReplies, tn as useMemiBoardReportQueue, $t as useMemiBoardReports, Vn as useMemiBoardSections, Tt as useMemiBoardSettings, Ft as useMemiBoardUserComments, Nt as useMemiBoardUserPosts, hn as useMemiBoardUserProfile, mn as useMemiBoardUsers, nn as useMemiBoardViews, ar as useMemiBoardWritingAssistant, Wn as versionHistory, yt as videoListCoverUrl, vt as youtubeId };
2551
3065
 
2552
3066
  //# sourceMappingURL=index.js.map