memi-board 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,68 +1,74 @@
1
1
  import { n as e, t } from "./rolldown-runtime-DtPi1Y-2.js";
2
2
  import { computed as n, onScopeDispose as r, reactive as i, ref as a, toValue as o, watch as s } from "vue";
3
- import { collection as c, collectionGroup as l, deleteDoc as u, deleteField as d, doc as f, documentId as p, endBefore as m, getDoc as h, getDocs as g, increment as _, limit as v, limitToLast as y, onSnapshot as b, orderBy as x, query as S, runTransaction as C, serverTimestamp as w, setDoc as T, startAfter as E, updateDoc as D, where as O, writeBatch as k } from "firebase/firestore";
4
- import { useCollection as A, useCurrentUser as j, useDocument as M, useFirebaseApp as N, useFirebaseAuth as P, useFirestore as F } from "vuefire";
5
- import { GoogleAuthProvider as I, OAuthProvider as L, createUserWithEmailAndPassword as R, signInWithEmailAndPassword as z, signInWithPopup as B, signOut as ee, updateProfile as te } from "firebase/auth";
6
- import { deleteObject as V, getDownloadURL as H, getStorage as U, listAll as ne, ref as W, uploadBytes as re, uploadBytesResumable as ie } from "firebase/storage";
7
- import { GoogleAIBackend as ae, Schema as oe, getAI as se, getGenerativeModel as ce } from "firebase/ai";
3
+ import { collection as c, deleteDoc as l, deleteField as u, doc as d, documentId as f, endBefore as p, getDoc as m, getDocs as h, increment as g, limit as _, limitToLast as v, onSnapshot as y, orderBy as b, query as x, runTransaction as S, serverTimestamp as C, setDoc as w, startAfter as T, updateDoc as E, where as D, writeBatch as O } from "firebase/firestore";
4
+ import { useCollection as k, useCurrentUser as A, useDocument as j, useFirebaseApp as M, useFirebaseAuth as N, useFirestore as P } from "vuefire";
5
+ import { GoogleAuthProvider as F, OAuthProvider as I, createUserWithEmailAndPassword as L, signInWithEmailAndPassword as R, signInWithPopup as z, signOut as B, updateProfile as V } from "firebase/auth";
6
+ import { deleteObject as H, getDownloadURL as ee, getStorage as U, listAll as te, ref as W, uploadBytes as ne, uploadBytesResumable as re } from "firebase/storage";
7
+ import { GoogleAIBackend as ie, Schema as ae, getAI as oe, getGenerativeModel as se } from "firebase/ai";
8
8
  //#region src/utils/boardPaths.ts
9
- var le = "memiBoards", ue = "memiBoardUsers";
10
- function de(e) {
9
+ var ce = "memiBoardPosts", le = "memiBoardComments", ue = "memiBoardLikes", de = "memiBoardSettings", fe = "memiBoardUsers";
10
+ function pe(e) {
11
11
  return {
12
- boardsCollection: e.boardsCollection?.trim() || "memiBoards",
12
+ postsCollection: e.postsCollection?.trim() || "memiBoardPosts",
13
+ commentsCollection: e.commentsCollection?.trim() || "memiBoardComments",
14
+ likesCollection: e.likesCollection?.trim() || "memiBoardLikes",
15
+ settingsCollection: e.settingsCollection?.trim() || "memiBoardSettings",
13
16
  usersCollection: e.usersCollection?.trim() || "memiBoardUsers"
14
17
  };
15
18
  }
16
- function fe(e, t) {
17
- return c(e, t.boardsCollection);
18
- }
19
- function pe(e, t, n) {
20
- return f(e, t.boardsCollection, n);
19
+ function me(e, t) {
20
+ return c(e, t.settingsCollection);
21
21
  }
22
22
  function G(e, t, n) {
23
- return f(e, t.boardsCollection, n, "settings", "config");
23
+ return d(e, t.settingsCollection, n);
24
24
  }
25
- function K(e, t, n) {
26
- return c(e, t.boardsCollection, n, "posts");
25
+ function K(e, t) {
26
+ return c(e, t.postsCollection);
27
27
  }
28
- function q(e, t, n, r) {
29
- return f(e, t.boardsCollection, n, "posts", r);
28
+ function q(e, t, n) {
29
+ return d(e, t.postsCollection, n);
30
30
  }
31
- function me(e, t, n, r) {
32
- return f(e, t.boardsCollection, n, "posts", r, "body", "main");
31
+ function he(e, t, n) {
32
+ return d(e, t.postsCollection, n, "body", "main");
33
33
  }
34
- function J(e, t, n, r) {
35
- return c(e, t.boardsCollection, n, "posts", r, "comments");
34
+ function ge(e, t) {
35
+ return c(e, t.commentsCollection);
36
36
  }
37
- function Y(e, t, n, r, i) {
38
- return f(e, t.boardsCollection, n, "posts", r, "comments", i);
37
+ function J(e, t, n) {
38
+ return d(e, t.commentsCollection, n);
39
39
  }
40
- function he(e, t, n, r, i) {
41
- return f(e, t.boardsCollection, n, "posts", r, "likes", i);
40
+ function _e(e, t) {
41
+ return c(e, t.likesCollection);
42
42
  }
43
- function ge(e, t) {
44
- return c(e, t.usersCollection);
43
+ function ve(e, t) {
44
+ return `${e}_${t}`;
45
45
  }
46
- function _e(e, t, n) {
47
- return f(e, t.usersCollection, n);
46
+ function ye(e, t, n, r) {
47
+ return d(e, t.likesCollection, ve(n, r));
48
48
  }
49
- function ve(e, t) {
50
- return `${e.boardsCollection}/${t}`;
49
+ function be(e, t) {
50
+ return c(e, t.usersCollection);
51
51
  }
52
- function X(e, t, n) {
53
- return `${ve(e, t)}/posts/${n}`;
52
+ function xe(e, t, n) {
53
+ return d(e, t.usersCollection, n);
54
54
  }
55
- function ye(e, t) {
56
- return `${e.boardsCollection}/${e.usersCollection}/${t}`;
55
+ function Y(e, t) {
56
+ return `${e.postsCollection}/${t}`;
57
+ }
58
+ function Se(e, t) {
59
+ return `${e.postsCollection}/${e.usersCollection}/${t}`;
57
60
  }
58
61
  //#endregion
59
62
  //#region src/config.ts
60
- var be = "__MEMI_BOARD_CONFIG__";
61
- function xe() {
63
+ var Ce = "__MEMI_BOARD_CONFIG__";
64
+ function we() {
62
65
  let e = globalThis;
63
- return e[be] || (e[be] = i({
64
- boardsCollection: le,
65
- usersCollection: ue,
66
+ return e[Ce] || (e[Ce] = i({
67
+ postsCollection: ce,
68
+ commentsCollection: le,
69
+ likesCollection: ue,
70
+ settingsCollection: de,
71
+ usersCollection: fe,
66
72
  auth: { providers: ["google", "apple"] },
67
73
  moderation: {
68
74
  enabled: !0,
@@ -80,27 +86,27 @@ function xe() {
80
86
  defaultOgImage: "",
81
87
  basePath: "/board"
82
88
  }
83
- })), e[be];
89
+ })), e[Ce];
84
90
  }
85
- function Se(e) {
86
- let t = xe();
87
- e.boardsCollection != null && e.boardsCollection !== "" && (t.boardsCollection = e.boardsCollection.trim()), e.usersCollection != null && e.usersCollection !== "" && (t.usersCollection = e.usersCollection.trim()), e.auth && Object.assign(t.auth, e.auth), e.moderation && Object.assign(t.moderation, e.moderation), e.seo && Object.assign(t.seo, e.seo);
91
+ function Te(e) {
92
+ let t = we();
93
+ e.postsCollection != null && e.postsCollection !== "" && (t.postsCollection = e.postsCollection.trim()), e.commentsCollection != null && e.commentsCollection !== "" && (t.commentsCollection = e.commentsCollection.trim()), e.likesCollection != null && e.likesCollection !== "" && (t.likesCollection = e.likesCollection.trim()), e.settingsCollection != null && e.settingsCollection !== "" && (t.settingsCollection = e.settingsCollection.trim()), e.usersCollection != null && e.usersCollection !== "" && (t.usersCollection = e.usersCollection.trim()), e.auth && Object.assign(t.auth, e.auth), e.moderation && Object.assign(t.moderation, e.moderation), e.seo && Object.assign(t.seo, e.seo);
88
94
  }
89
- function Ce() {
90
- return xe();
95
+ function Ee() {
96
+ return we();
91
97
  }
92
- function Z() {
93
- return de(Ce());
98
+ function X() {
99
+ return pe(Ee());
94
100
  }
95
101
  //#endregion
96
102
  //#region src/utils/boardSeo.ts
97
- function we(e) {
103
+ function De(e) {
98
104
  if (typeof e != "string") return null;
99
105
  let t = e.trim();
100
106
  return /^https?:\/\//i.test(t) ? t : null;
101
107
  }
102
- function Te(e) {
103
- let t = we(e);
108
+ function Oe(e) {
109
+ let t = De(e);
104
110
  if (!t) return null;
105
111
  if (!/firebasestorage\.googleapis\.com/i.test(t)) return t;
106
112
  try {
@@ -117,43 +123,43 @@ function Te(e) {
117
123
  return t;
118
124
  }
119
125
  }
120
- function Ee(e, t, n = "") {
126
+ function ke(e, t, n = "") {
121
127
  let r = t.replace(/\/$/, ""), i = e.trim();
122
- return i ? /^https?:\/\//i.test(i) ? i : `${r}${i.startsWith("/") ? i : `/${i}`}` : n ? Ee(n, t) : r;
128
+ return i ? /^https?:\/\//i.test(i) ? i : `${r}${i.startsWith("/") ? i : `/${i}`}` : n ? ke(n, t) : r;
123
129
  }
124
- function De(e, t, n, r) {
130
+ function Ae(e, t, n, r) {
125
131
  let i = r?.trim().slice(0, 40), a = e.trim() || i || "사진", o = t?.trim(), s = n.trim() || "Board";
126
132
  return o ? `${a} · ${o} | ${s}` : `${a} | ${s}`;
127
133
  }
128
- function Oe(e) {
134
+ function je(e) {
129
135
  let t = [];
130
136
  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);
131
137
  }
132
- function ke(e, t) {
138
+ function Me(e, t) {
133
139
  let n = t.trim() || "Board", r = e?.trim();
134
140
  return r && r !== "전체" ? `${r} 게시판 | ${n}` : `게시판 | ${n}`;
135
141
  }
136
- function Ae(e) {
142
+ function Ne(e) {
137
143
  if (e.description?.trim()) return e.description.trim().slice(0, 200);
138
144
  let t = e.categoryLabel?.trim(), n = t && t !== "전체" ? `${t} 게시판 — 소식을 확인하세요.` : "게시판 — 공지와 이야기를 확인하세요.", r = (e.recentTitles || []).filter(Boolean).slice(0, 3);
139
145
  return r.length ? `${n} ${r.map((e) => `「${e}」`).join(" ")}`.slice(0, 200) : n.slice(0, 200);
140
146
  }
141
- function je(e) {
147
+ function Pe(e) {
142
148
  let t = (e || "/board").trim() || "/board";
143
149
  return (t.startsWith("/") ? t : `/${t}`).replace(/\/$/, "") || "/board";
144
150
  }
145
151
  //#endregion
146
152
  //#region src/composables/fetchPublicSeo.ts
147
- function Me() {
153
+ function Fe() {
148
154
  return {
149
- db: F(),
150
- paths: Z()
155
+ db: P(),
156
+ paths: X()
151
157
  };
152
158
  }
153
- async function Ne(e, t) {
159
+ async function Ie(e, t) {
154
160
  try {
155
- let n = await h(G(e.db, e.paths, t));
156
- if (!n.exists() && !(await h(G(e.db, e.paths, t))).exists()) return {
161
+ let n = await m(G(e.db, e.paths, t));
162
+ if (!n.exists()) return {
157
163
  label: t,
158
164
  description: "",
159
165
  hidden: !1
@@ -172,7 +178,7 @@ async function Ne(e, t) {
172
178
  };
173
179
  }
174
180
  }
175
- function Pe(e) {
181
+ function Le(e) {
176
182
  if (e && typeof e == "object" && "toDate" in e) try {
177
183
  let t = e.toDate();
178
184
  return t instanceof Date ? t.toISOString() : null;
@@ -181,15 +187,15 @@ function Pe(e) {
181
187
  }
182
188
  return null;
183
189
  }
184
- async function Fe(e, t, n) {
190
+ async function Re(e, t, n) {
185
191
  let r = e.trim(), i = t.trim();
186
192
  if (!r || !i) return null;
187
- let a = n ?? Me();
188
- a.paths = de(a.paths);
189
- let o = await Ne(a, r);
193
+ let a = n ?? Fe();
194
+ a.paths = pe(a.paths);
195
+ let o = await Ie(a, r);
190
196
  if (o.hidden) return null;
191
197
  try {
192
- let e = (await g(S(K(a.db, a.paths, r), O("slug", "==", i), O("listed", "==", !0), O("isPublished", "==", !0), v(1)))).docs[0];
198
+ let e = (await h(x(K(a.db, a.paths), D("boardId", "==", r), D("slug", "==", i), D("listed", "==", !0), D("isPublished", "==", !0), _(1)))).docs[0];
193
199
  if (!e) return null;
194
200
  let t = e.data() ?? {}, n = typeof t.title == "string" ? t.title.trim() : "";
195
201
  return n ? {
@@ -199,18 +205,18 @@ async function Fe(e, t, n) {
199
205
  categoryLabel: o.label,
200
206
  title: n,
201
207
  summary: typeof t.summary == "string" ? t.summary.trim().slice(0, 200) : "",
202
- previewImage: Te(t.previewImage),
208
+ previewImage: Oe(t.previewImage),
203
209
  authorName: typeof t.authorName == "string" && t.authorName.trim() ? t.authorName.trim() : null,
204
- createdAt: Pe(t.createdAt)
210
+ createdAt: Le(t.createdAt)
205
211
  } : null;
206
212
  } catch (e) {
207
213
  return import.meta.dev && console.warn("[memi-board] fetchPublicPostForSeo failed", e), null;
208
214
  }
209
215
  }
210
- async function Ie(e, t) {
211
- let n = e?.trim() || null, r = t ?? Me();
212
- if (r.paths = de(r.paths), n) {
213
- let e = await Ne(r, n);
216
+ async function ze(e, t) {
217
+ let n = e?.trim() || null, r = t ?? Fe();
218
+ if (r.paths = pe(r.paths), n) {
219
+ let e = await Ie(r, n);
214
220
  if (e.hidden) return {
215
221
  kind: "category",
216
222
  category: n,
@@ -219,7 +225,7 @@ async function Ie(e, t) {
219
225
  ogImage: null,
220
226
  recentTitles: []
221
227
  };
222
- let { recentTitles: t, ogImage: i } = await Le(r, n);
228
+ let { recentTitles: t, ogImage: i } = await Be(r, n);
223
229
  return {
224
230
  kind: "category",
225
231
  category: n,
@@ -238,13 +244,13 @@ async function Ie(e, t) {
238
244
  recentTitles: []
239
245
  };
240
246
  }
241
- async function Le(e, t) {
247
+ async function Be(e, t) {
242
248
  let n = [], r = null;
243
249
  try {
244
- let i = await g(S(K(e.db, e.paths, t), O("listed", "==", !0), O("isPublished", "==", !0), x("createdAt", "desc"), v(12)));
250
+ let i = await h(x(K(e.db, e.paths), D("boardId", "==", t), D("listed", "==", !0), D("isPublished", "==", !0), b("createdAt", "desc"), _(12)));
245
251
  for (let e of i.docs) {
246
252
  let t = e.data() ?? {}, i = typeof t.title == "string" ? t.title.trim() : "";
247
- i && n.length < 5 && n.push(i), r ||= Te(t.previewImage);
253
+ i && n.length < 5 && n.push(i), r ||= Oe(t.previewImage);
248
254
  }
249
255
  } catch (e) {
250
256
  import.meta.dev && console.warn("[memi-board] loadRecentListed failed", e);
@@ -256,8 +262,8 @@ async function Le(e, t) {
256
262
  }
257
263
  //#endregion
258
264
  //#region src/utils/moderation-strike.ts
259
- var Re = 3, Q = 864e5;
260
- function ze(e, t, n = Date.now(), r = Q) {
265
+ var Ve = 3, Z = 864e5;
266
+ function He(e, t, n = Date.now(), r = Z) {
261
267
  let i = Math.max(0, Math.floor(Number(e) || 0));
262
268
  if (i <= 0) return 0;
263
269
  let a = typeof t == "number" && Number.isFinite(t) ? t : null;
@@ -265,7 +271,7 @@ function ze(e, t, n = Date.now(), r = Q) {
265
271
  let o = Math.floor((n - a) / r);
266
272
  return Math.max(0, i - o);
267
273
  }
268
- function Be(e, t, n = Date.now(), r = 3, i = Q) {
274
+ function Ue(e, t, n = Date.now(), r = 3, i = Z) {
269
275
  let a = Math.max(0, Math.floor(Number(e) || 0));
270
276
  if (a < r) return null;
271
277
  let o = typeof t == "number" && Number.isFinite(t) ? t : null;
@@ -273,17 +279,17 @@ function Be(e, t, n = Date.now(), r = 3, i = Q) {
273
279
  let s = o + (a - (r - 1)) * i;
274
280
  return s > n ? s : null;
275
281
  }
276
- function Ve(e, t, n = Date.now(), r = 3, i = Q) {
277
- return Be(e, t, n, r, i) != null;
282
+ function We(e, t, n = Date.now(), r = 3, i = Z) {
283
+ return Ue(e, t, n, r, i) != null;
278
284
  }
279
- function He(e, t = Date.now()) {
285
+ function Ge(e, t = Date.now()) {
280
286
  let n = Math.max(0, e - t), r = Math.ceil(n / 36e5);
281
287
  if (r <= 0) return "곧 해제";
282
288
  if (r < 24) return `약 ${r}시간 후 해제`;
283
289
  let i = Math.floor(r / 24), a = r % 24;
284
290
  return a === 0 ? `약 ${i}일 후 해제` : `약 ${i}일 ${a}시간 후 해제`;
285
291
  }
286
- function Ue(e) {
292
+ function Ke(e) {
287
293
  if (e == null) return null;
288
294
  if (typeof e == "number" && Number.isFinite(e)) return e;
289
295
  if (typeof e == "object" && e && "toMillis" in e && typeof e.toMillis == "function") return e.toMillis();
@@ -295,52 +301,52 @@ function Ue(e) {
295
301
  }
296
302
  //#endregion
297
303
  //#region src/composables/useMemiBoardAuth.ts
298
- var We = "__MEMI_BOARD_AUTH_STATE__";
299
- function Ge() {
304
+ var qe = "__MEMI_BOARD_AUTH_STATE__";
305
+ function Je() {
300
306
  let e = globalThis;
301
- return e[We] || (e[We] = {
307
+ return e[qe] || (e[qe] = {
302
308
  role: a("user"),
303
309
  rolePending: a(!0),
304
310
  rawBlockCount: a(0),
305
311
  blockAtMs: a(null),
306
312
  watchStarted: !1
307
- }), e[We];
313
+ }), e[qe];
308
314
  }
309
- function Ke() {
310
- let e = Ce(), t = Ge(), r = F(), i = P(), a = j(), { role: o, rolePending: c, rawBlockCount: l, blockAtMs: u } = t;
315
+ function Ye() {
316
+ let e = Ee(), t = Je(), r = P(), i = N(), a = A(), { role: o, rolePending: c, rawBlockCount: l, blockAtMs: u } = t;
311
317
  function d() {
312
318
  let t = e.moderation?.blockBanThreshold;
313
319
  return t === 0 ? 0 : typeof t == "number" && t > 0 ? Math.floor(t) : 3;
314
320
  }
315
321
  function f() {
316
322
  let t = e.moderation?.blockBanDecayMs;
317
- return typeof t == "number" && t > 0 ? t : Q;
323
+ return typeof t == "number" && t > 0 ? t : Z;
318
324
  }
319
325
  function p(e) {
320
- return _e(r, Z(), e);
326
+ return xe(r, X(), e);
321
327
  }
322
- function m(e) {
328
+ function h(e) {
323
329
  if (!e) {
324
330
  l.value = 0, u.value = null;
325
331
  return;
326
332
  }
327
- l.value = Math.max(0, Math.floor(Number(e.moderationBlockCount) || 0)), u.value = Ue(e.moderationBlockAt);
333
+ l.value = Math.max(0, Math.floor(Number(e.moderationBlockCount) || 0)), u.value = Ke(e.moderationBlockAt);
328
334
  }
329
- async function g(e) {
330
- let t = p(e.uid), n = await h(t);
331
- if (!n.exists()) return await T(t, {
335
+ async function _(e) {
336
+ let t = p(e.uid), n = await m(t);
337
+ if (!n.exists()) return await w(t, {
332
338
  role: "user",
333
339
  displayName: e.displayName,
334
340
  email: e.email,
335
341
  photoURL: e.photoURL,
336
- updatedAt: w()
337
- }), m({ moderationBlockCount: 0 }), "user";
342
+ updatedAt: C()
343
+ }), h({ moderationBlockCount: 0 }), "user";
338
344
  let r = n.data();
339
- return m(r), await D(t, {
345
+ return h(r), await E(t, {
340
346
  displayName: e.displayName,
341
347
  email: e.email,
342
348
  photoURL: e.photoURL,
343
- updatedAt: w()
349
+ updatedAt: C()
344
350
  }), [
345
351
  "admin",
346
352
  "staff",
@@ -350,92 +356,92 @@ function Ke() {
350
356
  async function v() {
351
357
  let e = a.value?.uid;
352
358
  if (!e) {
353
- m(void 0);
359
+ h(void 0);
354
360
  return;
355
361
  }
356
- let t = await h(p(e));
357
- m(t.exists() ? t.data() : void 0);
362
+ let t = await m(p(e));
363
+ h(t.exists() ? t.data() : void 0);
358
364
  }
359
365
  t.watchStarted || (t.watchStarted = !0, s(a, async (e) => {
360
366
  if (c.value = !0, !e) {
361
- o.value = "user", m(void 0), c.value = !1;
367
+ o.value = "user", h(void 0), c.value = !1;
362
368
  return;
363
369
  }
364
370
  try {
365
- o.value = await g(e);
371
+ o.value = await _(e);
366
372
  } catch (e) {
367
- console.error("[memi-board] ensureUserDoc failed", e), o.value = "user", m(void 0);
373
+ console.error("[memi-board] ensureUserDoc failed", e), o.value = "user", h(void 0);
368
374
  } finally {
369
375
  c.value = !1;
370
376
  }
371
377
  }, { immediate: !0 }));
372
- let y = n(() => !!a.value), b = n(() => o.value === "admin"), x = n(() => o.value === "staff"), S = n(() => b.value || x.value), C = n(() => ze(l.value, u.value, Date.now(), f())), E = n(() => {
378
+ let y = n(() => !!a.value), b = n(() => o.value === "admin"), x = n(() => o.value === "staff"), S = n(() => b.value || x.value), T = n(() => He(l.value, u.value, Date.now(), f())), D = n(() => {
373
379
  let e = d();
374
- return e <= 0 ? !1 : Ve(l.value, u.value, Date.now(), e, f());
380
+ return e <= 0 ? !1 : We(l.value, u.value, Date.now(), e, f());
375
381
  }), O = n(() => {
376
382
  let e = d();
377
383
  if (e <= 0) return null;
378
- let t = Be(l.value, u.value, Date.now(), e, f());
379
- return t == null ? null : He(t);
384
+ let t = Ue(l.value, u.value, Date.now(), e, f());
385
+ return t == null ? null : Ge(t);
380
386
  }), k = n(() => {
381
- if (!E.value) return null;
387
+ if (!D.value) return null;
382
388
  let e = O.value;
383
389
  return e ? `콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요. (${e})` : "콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요.";
384
390
  });
385
- async function A() {
391
+ async function j() {
386
392
  let e = a.value?.uid, t = d();
387
393
  if (!e || t <= 0) return {
388
394
  effectiveCount: 0,
389
395
  restricted: !1,
390
396
  messageSuffix: ""
391
397
  };
392
- let n = p(e), r = await h(n);
393
- r.exists() ? await D(n, {
394
- moderationBlockCount: _(1),
395
- moderationBlockAt: w(),
396
- updatedAt: w()
397
- }) : await T(n, {
398
+ let n = p(e), r = await m(n);
399
+ r.exists() ? await E(n, {
400
+ moderationBlockCount: g(1),
401
+ moderationBlockAt: C(),
402
+ updatedAt: C()
403
+ }) : await w(n, {
398
404
  role: "user",
399
405
  displayName: a.value?.displayName ?? null,
400
406
  moderationBlockCount: 1,
401
- moderationBlockAt: w(),
402
- updatedAt: w()
407
+ moderationBlockAt: C(),
408
+ updatedAt: C()
403
409
  });
404
410
  let i = (r.exists() ? Math.max(0, Math.floor(Number(r.data().moderationBlockCount) || 0)) : 0) + 1, o = Date.now();
405
411
  l.value = i, u.value = o;
406
- let s = ze(i, o, o, f()), c = Ve(i, o, o, t, f()), m = ` (경고 ${s}/${t})`;
412
+ let s = He(i, o, o, f()), c = We(i, o, o, t, f()), h = ` (경고 ${s}/${t})`;
407
413
  if (c) {
408
- let e = Be(i, o, o, t, f()), n = e == null ? null : He(e, o);
409
- m = n ? ` 경고 ${s}회가 되어 글·댓글 작성이 제한됩니다 (${n}).` : ` 경고 ${s}회가 되어 글·댓글 작성이 제한됩니다.`;
414
+ let e = Ue(i, o, o, t, f()), n = e == null ? null : Ge(e, o);
415
+ h = n ? ` 경고 ${s}회가 되어 글·댓글 작성이 제한됩니다 (${n}).` : ` 경고 ${s}회가 되어 글·댓글 작성이 제한됩니다.`;
410
416
  }
411
417
  return {
412
418
  effectiveCount: s,
413
419
  restricted: c,
414
- messageSuffix: m
420
+ messageSuffix: h
415
421
  };
416
422
  }
417
423
  function M() {
418
424
  if (!i) throw Error("[memi-board] Firebase Auth가 초기화되지 않았습니다.");
419
425
  return i;
420
426
  }
421
- async function N() {
422
- return B(M(), new I());
427
+ async function H() {
428
+ return z(M(), new F());
423
429
  }
424
- async function V() {
425
- let e = new L("apple.com");
426
- return e.addScope("email"), e.addScope("name"), B(M(), e);
430
+ async function ee() {
431
+ let e = new I("apple.com");
432
+ return e.addScope("email"), e.addScope("name"), z(M(), e);
427
433
  }
428
- async function H(e, t) {
429
- return z(M(), e, t);
434
+ async function U(e, t) {
435
+ return R(M(), e, t);
430
436
  }
431
- async function U(e, t, n) {
432
- let r = await R(M(), e, t);
433
- return n && await te(r.user, { displayName: n }), r;
437
+ async function te(e, t, n) {
438
+ let r = await L(M(), e, t);
439
+ return n && await V(r.user, { displayName: n }), r;
434
440
  }
435
- async function ne() {
436
- await ee(M());
441
+ async function W() {
442
+ await B(M());
437
443
  }
438
- function W(e) {
444
+ function ne(e) {
439
445
  return y.value && (a.value?.uid === e.authorUid || S.value);
440
446
  }
441
447
  function re(e) {
@@ -451,34 +457,34 @@ function Ke() {
451
457
  isStaff: x,
452
458
  canManageContent: S,
453
459
  rolePending: c,
454
- moderationBlockCount: C,
455
- isWriteRestricted: E,
460
+ moderationBlockCount: T,
461
+ isWriteRestricted: D,
456
462
  restrictedUntilLabel: O,
457
463
  restrictedMessage: k,
458
- signInWithGoogle: N,
459
- signInWithApple: V,
460
- signInWithEmail: H,
461
- signUpWithEmail: U,
462
- signOut: ne,
463
- canEdit: W,
464
- canDelete: W,
464
+ signInWithGoogle: H,
465
+ signInWithApple: ee,
466
+ signInWithEmail: U,
467
+ signUpWithEmail: te,
468
+ signOut: W,
469
+ canEdit: ne,
470
+ canDelete: ne,
465
471
  canEditComment: re,
466
472
  canDeleteComment: ie,
467
- recordContentModerationBlock: A,
473
+ recordContentModerationBlock: j,
468
474
  refreshBoardUser: v
469
475
  };
470
476
  }
471
477
  //#endregion
472
478
  //#region src/utils/slugify.ts
473
- function qe(e) {
479
+ function Xe(e) {
474
480
  return e.trim().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9가-힣ㄱ-ㅎㅏ-ㅣ-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
475
481
  }
476
- function Je(e, t = 60) {
482
+ function Ze(e, t = 60) {
477
483
  return e.replace(/[^a-zA-Z0-9가-힣ㄱ-ㅎ._-]/g, "-").slice(0, t);
478
484
  }
479
485
  //#endregion
480
486
  //#region src/utils/extractEditorImageUrls.ts
481
- function Ye(e) {
487
+ function Qe(e) {
482
488
  if (!e) return [];
483
489
  let t = /* @__PURE__ */ new Set();
484
490
  for (let n of e.matchAll(/!\[[^\]]*]\((https?:\/\/[^)\s]+)\)/g)) n[1] && t.add(n[1].replaceAll("&amp;", "&"));
@@ -487,7 +493,7 @@ function Ye(e) {
487
493
  }
488
494
  //#endregion
489
495
  //#region src/utils/storagePath.ts
490
- function Xe(e) {
496
+ function $e(e) {
491
497
  try {
492
498
  let t = e.match(/\/o\/([^?]+)/);
493
499
  return t?.[1] ? decodeURIComponent(t[1]) : null;
@@ -495,50 +501,50 @@ function Xe(e) {
495
501
  return null;
496
502
  }
497
503
  }
498
- function Ze(e) {
504
+ function et(e) {
499
505
  return e.match(/(?:^|\/)posts\/([^/]+)\//)?.[1] ?? null;
500
506
  }
501
507
  //#endregion
502
508
  //#region src/utils/postBody.ts
503
- function Qe(e) {
509
+ function tt(e) {
504
510
  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();
505
511
  }
506
- function $e(e, t = 40) {
507
- let n = Qe(e);
512
+ function nt(e, t = 40) {
513
+ let n = tt(e);
508
514
  if (!n) return "";
509
515
  if (n.length <= t) return n;
510
516
  let r = n.slice(0, t), i = r.lastIndexOf(" ");
511
517
  return `${(i >= Math.floor(t * .6) ? r.slice(0, i) : r).trimEnd()}…`;
512
518
  }
513
- function et(e) {
514
- return Qe(e).length > 0;
519
+ function rt(e) {
520
+ return tt(e).length > 0;
515
521
  }
516
- function tt(e, t) {
522
+ function it(e, t) {
517
523
  return /<img\b/i.test(String(e || "")) ? !0 : (t || []).some((e) => !!e?.type?.startsWith("image/"));
518
524
  }
519
- function nt(e) {
520
- return !et(e);
525
+ function at(e) {
526
+ return !rt(e);
521
527
  }
522
528
  //#endregion
523
529
  //#region src/utils/postPreview.ts
524
- function rt(e) {
530
+ function ot(e) {
525
531
  return e.replace(/&amp;/g, "&").replace(/&quot;/g, "\"").replace(/&#39;/g, "'");
526
532
  }
527
- function it(e) {
533
+ function st(e) {
528
534
  if (e) try {
529
- let t = new URL(rt(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
535
+ let t = new URL(ot(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
530
536
  if (n === "youtu.be") return t.pathname.split("/").filter(Boolean)[0];
531
537
  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;
532
538
  } catch {
533
539
  return;
534
540
  }
535
541
  }
536
- function at(e) {
537
- let t = it(e.videoUrl);
542
+ function ct(e) {
543
+ let t = st(e.videoUrl);
538
544
  return t ? `https://i.ytimg.com/vi/${t}/hqdefault.jpg` : e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
539
545
  }
540
- function ot(e, t = []) {
541
- 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 ? rt(r[1]) : a, s = o ? it(o) : void 0, c = n ? rt(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
546
+ function lt(e, t = []) {
547
+ 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 ? ot(r[1]) : a, s = o ? st(o) : void 0, c = n ? ot(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
542
548
  return {
543
549
  summary: e.replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/\s+/g, " ").trim().slice(0, 160),
544
550
  ...c ? { previewImage: c } : {},
@@ -547,7 +553,7 @@ function ot(e, t = []) {
547
553
  }
548
554
  //#endregion
549
555
  //#region src/composables/useMemiBoardSettings.ts
550
- var st = [
556
+ var ut = [
551
557
  {
552
558
  id: "free",
553
559
  label: "자유",
@@ -575,25 +581,25 @@ var st = [
575
581
  visibility: "public",
576
582
  order: 2
577
583
  }
578
- ], ct = st;
579
- function lt(e) {
584
+ ], dt = ut;
585
+ function ft(e) {
580
586
  return e === "hidden" ? "hidden" : "public";
581
587
  }
582
- function ut(e, t, n) {
588
+ function pt(e, t, n) {
583
589
  return {
584
590
  id: e,
585
591
  label: typeof t.label == "string" && t.label.trim() ? t.label.trim() : e,
586
592
  order: typeof t.order == "number" ? t.order : n,
587
593
  description: (typeof t.description == "string" ? t.description : "") || "",
588
- visibility: lt(t.visibility),
594
+ visibility: ft(t.visibility),
589
595
  listView: t.listView ?? "default",
590
596
  writeRole: t.writeRole ?? "user",
591
597
  commentWriteRole: t.commentWriteRole ?? "user",
592
598
  allowedStaffUids: Array.isArray(t.allowedStaffUids) ? t.allowedStaffUids : []
593
599
  };
594
600
  }
595
- function dt() {
596
- let e = () => Z(), t = F(), { isSignedIn: r } = Ke(), i = n(() => S(fe(t, e()), x("order", "asc"))), { data: a, pending: o } = A(i, { ssrKey: ye(e(), "boards") }), s = n(() => a.value.map((e, t) => ut(e.id, e, t))), c = s, l = n(() => s.value.filter((e) => e.visibility !== "hidden")), d = l;
601
+ function mt() {
602
+ let e = () => X(), t = P(), { isSignedIn: r } = Ye(), i = n(() => x(me(t, e()), b("order", "asc"))), { data: a, pending: o } = k(i, { ssrKey: Se(e(), "boards") }), s = n(() => a.value.map((e, t) => pt(e.id, e, t))), c = s, u = n(() => s.value.filter((e) => e.visibility !== "hidden")), d = u;
597
603
  function f(e) {
598
604
  if (e) return s.value.find((t) => t.id === e)?.label ?? e;
599
605
  }
@@ -601,90 +607,89 @@ function dt() {
601
607
  function m(e) {
602
608
  if (e) return s.value.find((t) => t.id === e)?.description?.trim() || void 0;
603
609
  }
604
- let h = m;
605
- function _(e) {
610
+ let g = m;
611
+ function v(e) {
606
612
  return e ? s.value.find((t) => t.id === e)?.visibility ?? "public" : "public";
607
613
  }
608
- let y = _;
609
- function b(e) {
610
- return _(e) === "hidden";
614
+ let y = v;
615
+ function S(e) {
616
+ return v(e) === "hidden";
611
617
  }
612
- let C = b;
613
- function D(e) {
618
+ let E = S;
619
+ function A(e) {
614
620
  if (e) return s.value.find((t) => t.id === e);
615
621
  }
616
- async function O(n, r) {
617
- let i = K(t, e(), n), a;
622
+ async function j(n, r) {
623
+ let i;
618
624
  for (;;) {
619
- let e = await g(S(i, x("__name__"), ...a ? [E(a)] : [], v(400)));
620
- if (e.empty) break;
621
- let n = k(t);
622
- for (let t of e.docs) n.update(t.ref, { listed: r });
623
- if (await n.commit(), a = e.docs[e.docs.length - 1], e.docs.length < 400) break;
625
+ let a = await h(x(K(t, e()), D("boardId", "==", n), b("__name__"), ...i ? [T(i)] : [], _(400)));
626
+ if (a.empty) break;
627
+ let o = O(t);
628
+ for (let e of a.docs) o.update(e.ref, { listed: r });
629
+ if (await o.commit(), i = a.docs[a.docs.length - 1], a.docs.length < 400) break;
624
630
  }
625
631
  }
626
- function j(e, t) {
632
+ function M(e, t) {
627
633
  return {
628
634
  label: e.label.trim(),
629
635
  description: (e.description ?? "").trim(),
630
- visibility: lt(e.visibility),
636
+ visibility: ft(e.visibility),
631
637
  listView: e.listView ?? "default",
632
638
  writeRole: e.writeRole ?? "user",
633
639
  commentWriteRole: e.commentWriteRole ?? "user",
634
640
  allowedStaffUids: e.allowedStaffUids ?? [],
635
641
  order: t,
636
- updatedAt: w()
642
+ updatedAt: C()
637
643
  };
638
644
  }
639
- async function M(n, i = n.order ?? 0) {
645
+ async function N(n, i = n.order ?? 0) {
640
646
  if (!r.value) throw Error("로그인이 필요합니다.");
641
647
  let a = n.id.trim(), o = n.label.trim();
642
648
  if (!a || !o) throw Error("보드 ID와 이름을 입력해 주세요.");
643
- let c = lt(n.visibility), l = s.value.find((e) => e.id === a)?.visibility ?? "public", u = j({
649
+ let c = ft(n.visibility), l = s.value.find((e) => e.id === a)?.visibility ?? "public", u = M({
644
650
  ...n,
645
651
  label: o,
646
652
  visibility: c
647
- }, i), d = e();
648
- await T(pe(t, d, a), u, { merge: !0 }), await T(G(t, d, a), u, { merge: !0 }), (l !== c || c === "public") && await O(a, c !== "hidden");
653
+ }, i);
654
+ await w(G(t, e(), a), u, { merge: !0 }), (l !== c || c === "public") && await j(a, c !== "hidden");
649
655
  }
650
- let N = M;
651
- async function P(n) {
656
+ let F = N;
657
+ async function I(n) {
652
658
  if (!r.value) throw Error("로그인이 필요합니다.");
653
- let i = new Map(s.value.map((e) => [e.id, e.visibility ?? "public"])), a = k(t), o = e(), c = [];
654
- n.forEach((e, n) => {
655
- let r = e.id.trim(), s = e.label.trim();
656
- if (!r || !s) throw Error("보드 ID와 이름을 입력해 주세요.");
657
- let l = lt(e.visibility), u = j({
658
- ...e,
659
- label: s,
659
+ let i = new Map(s.value.map((e) => [e.id, e.visibility ?? "public"])), a = O(t), o = [];
660
+ n.forEach((n, r) => {
661
+ let s = n.id.trim(), c = n.label.trim();
662
+ if (!s || !c) throw Error("보드 ID와 이름을 입력해 주세요.");
663
+ let l = ft(n.visibility), u = M({
664
+ ...n,
665
+ label: c,
660
666
  visibility: l
661
- }, n);
662
- a.set(pe(t, o, r), u, { merge: !0 }), a.set(G(t, o, r), u, { merge: !0 }), (i.get(r) ?? "public") !== l && c.push({
663
- id: r,
667
+ }, r);
668
+ a.set(G(t, e(), s), u, { merge: !0 }), (i.get(s) ?? "public") !== l && o.push({
669
+ id: s,
664
670
  listed: l !== "hidden"
665
671
  });
666
672
  }), await a.commit();
667
- for (let e of c) await O(e.id, e.listed);
673
+ for (let e of o) await j(e.id, e.listed);
668
674
  }
669
- let I = P;
670
- async function L(n) {
675
+ let L = I;
676
+ async function R(n) {
671
677
  if (!r.value) throw Error("로그인이 필요합니다.");
672
- let i = e();
673
- await u(G(t, i, n)).catch(() => {}), await u(pe(t, i, n));
678
+ await l(G(t, e(), n));
674
679
  }
675
- let R = L;
676
- async function z() {
680
+ let z = R;
681
+ async function B() {
677
682
  return Promise.resolve();
678
683
  }
679
- async function B(e) {
684
+ async function V(e) {
680
685
  let t = e.trim();
681
686
  if (!t) throw Error("보드 이름을 입력해 주세요.");
682
687
  if (!r.value) throw Error("로그인이 필요합니다.");
683
688
  let n = s.value.find((e) => e.label === t);
684
689
  if (n) return n.id;
685
- let i = qe(t) || `board-${Date.now()}`, a = i, o = 2, c = new Set(s.value.map((e) => e.id));
690
+ let i = Xe(t) || `board-${Date.now()}`, a = i, o = 2, c = new Set(s.value.map((e) => e.id));
686
691
  for (; c.has(a);) a = `${i}-${o++}`;
687
- return await M({
692
+ return await N({
688
693
  id: a,
689
694
  label: t,
690
695
  visibility: "public",
@@ -696,79 +701,79 @@ function dt() {
696
701
  }
697
702
  return {
698
703
  boards: s,
699
- publicBoards: l,
704
+ publicBoards: u,
700
705
  boardLabel: f,
701
706
  boardDescription: m,
702
- boardVisibility: _,
703
- isBoardHidden: b,
704
- getBoard: D,
707
+ boardVisibility: v,
708
+ isBoardHidden: S,
709
+ getBoard: A,
705
710
  categories: c,
706
711
  publicCategories: d,
707
712
  categoryLabel: p,
708
- categoryDescription: h,
713
+ categoryDescription: g,
709
714
  categoryVisibility: y,
710
- isCategoryHidden: C,
715
+ isCategoryHidden: E,
711
716
  settingsPending: o,
712
- ensureSettings: z,
713
- addBoard: B,
714
- addCategory: B,
715
- saveBoard: M,
716
- saveCategory: N,
717
- saveBoards: P,
718
- saveCategories: I,
719
- deleteBoard: L,
720
- deleteCategory: R,
721
- DEFAULT_BOARDS: st,
722
- DEFAULT_CATEGORIES: ct
717
+ ensureSettings: B,
718
+ addBoard: V,
719
+ addCategory: V,
720
+ saveBoard: N,
721
+ saveCategory: F,
722
+ saveBoards: I,
723
+ saveCategories: L,
724
+ deleteBoard: R,
725
+ deleteCategory: z,
726
+ DEFAULT_BOARDS: ut,
727
+ DEFAULT_CATEGORIES: dt
723
728
  };
724
729
  }
725
730
  //#endregion
726
731
  //#region src/composables/useMemiBoardPosts.ts
727
- async function ft(e) {
728
- let t = await ne(e).catch(() => ({
732
+ async function ht(e) {
733
+ let t = await te(e).catch(() => ({
729
734
  items: [],
730
735
  prefixes: []
731
736
  }));
732
- await Promise.all([...t.items.map((e) => V(e)), ...t.prefixes.map((e) => ft(e))]);
737
+ await Promise.all([...t.items.map((e) => H(e)), ...t.prefixes.map((e) => ht(e))]);
733
738
  }
734
- async function pt(e, t) {
739
+ async function gt(e, t) {
735
740
  let n = [...new Set([...t].filter(Boolean))];
736
- await Promise.all(n.map((t) => V(W(e, t)).catch(() => {})));
741
+ await Promise.all(n.map((t) => H(W(e, t)).catch(() => {})));
737
742
  }
738
- function mt(e) {
743
+ function _t(e) {
739
744
  if (!e || typeof e != "object") return !1;
740
745
  let t = "code" in e ? String(e.code) : "";
741
746
  return t === "permission-denied" || t === "firestore/permission-denied";
742
747
  }
743
- function ht(e) {
748
+ function vt(e) {
744
749
  let t = o(e)?.trim();
745
750
  if (!t) throw Error("[memi-board] boardId 가 필요합니다.");
746
751
  return t;
747
752
  }
748
- function gt(e) {
749
- let t = F(), n = N(), { isBoardHidden: r, getBoard: i } = dt(), a = () => Z(), o = () => ht(e), s = () => K(t, a(), o()), c = (e) => q(t, a(), o(), e), l = (e) => me(t, a(), o(), e), p = (e) => J(t, a(), o(), e);
750
- function _() {
753
+ function yt(e) {
754
+ let t = P(), n = M(), { isBoardHidden: r, getBoard: i } = mt(), a = () => X(), o = () => vt(e), s = () => K(t, a()), c = (e) => q(t, a(), e), f = (e) => he(t, a(), e), g = () => ge(t, a());
755
+ function y() {
751
756
  return !r(o());
752
757
  }
753
- function b() {
758
+ function S() {
754
759
  return i(o())?.listView === "image";
755
760
  }
756
- function C(e) {
757
- let t = b();
758
- if (!et(e.content)) throw Error(t ? "내용을 입력해 주세요." : "본문에 글자를 입력해 주세요. 이미지나 첨부만으로는 게시할 수 없습니다.");
761
+ function k(e) {
762
+ let t = S();
763
+ if (!rt(e.content)) throw Error(t ? "내용을 입력해 주세요." : "본문에 글자를 입력해 주세요. 이미지나 첨부만으로는 게시할 수 없습니다.");
759
764
  if (t) {
760
- if (!tt(e.content, e.attachments)) throw Error("사진을 올려 주세요.");
765
+ if (!it(e.content, e.attachments)) throw Error("사진을 올려 주세요.");
761
766
  return;
762
767
  }
763
768
  if (!e.title?.trim()) throw Error("제목을 입력해 주세요.");
764
769
  }
765
770
  function A() {
766
- return f(s()).id;
771
+ return d(s()).id;
767
772
  }
768
773
  async function j(e = {}) {
769
- let t = e.pageSize ?? 20, n = [O("isPublished", "==", !0)];
770
- e.publicOnly !== !1 && n.push(O("listed", "==", !0)), n.push(x("createdAt", "desc")), e.cursor && n.push(E(e.cursor)), n.push(v(t + 1));
771
- let r = await g(S(s(), ...n)), i = r.docs.slice(0, t);
774
+ let t = e.pageSize ?? 20, n = [D("boardId", "==", o()), D("isPublished", "==", !0)];
775
+ e.publicOnly !== !1 && n.push(D("listed", "==", !0)), n.push(b("createdAt", "desc")), e.cursor && n.push(T(e.cursor)), n.push(_(t + 1));
776
+ let r = await h(x(s(), ...n)), i = r.docs.slice(0, t);
772
777
  return {
773
778
  posts: i.map((e) => ({
774
779
  id: e.id,
@@ -778,32 +783,32 @@ function gt(e) {
778
783
  hasMore: r.docs.length > t
779
784
  };
780
785
  }
781
- async function M(e) {
782
- let [t, n] = await Promise.all([h(c(e)), h(l(e))]);
786
+ async function N(e) {
787
+ let [t, n] = await Promise.all([m(c(e)), m(f(e))]);
783
788
  return t.exists() ? {
784
789
  id: t.id,
785
790
  ...t.data(),
786
791
  content: n.exists() ? n.data().content ?? "" : ""
787
792
  } : null;
788
793
  }
789
- async function P(e) {
794
+ async function F(e) {
790
795
  try {
791
- let t = (await g(S(s(), O("slug", "==", e.trim()), v(1)))).docs[0];
796
+ let t = (await h(x(s(), D("boardId", "==", o()), D("slug", "==", e.trim()), _(1)))).docs[0];
792
797
  if (!t) return { status: "not-found" };
793
798
  try {
794
- let e = await M(t.id);
799
+ let e = await N(t.id);
795
800
  return e ? {
796
801
  status: "ok",
797
802
  post: e
798
803
  } : { status: "not-found" };
799
804
  } catch (e) {
800
- return mt(e) ? { status: "permission-denied" } : {
805
+ return _t(e) ? { status: "permission-denied" } : {
801
806
  status: "error",
802
807
  message: e instanceof Error ? e.message : String(e)
803
808
  };
804
809
  }
805
810
  } catch (e) {
806
- return mt(e) ? { status: "permission-denied" } : {
811
+ return _t(e) ? { status: "permission-denied" } : {
807
812
  status: "error",
808
813
  message: e instanceof Error ? e.message : String(e)
809
814
  };
@@ -816,10 +821,11 @@ function gt(e) {
816
821
  };
817
822
  try {
818
823
  let t = [
819
- O("isPublished", "==", !0),
820
- ...e.listed === !1 ? [] : [O("listed", "==", !0)],
821
- x("createdAt", "desc")
822
- ], [n, r] = await Promise.all([g(S(s(), ...t, E(e.createdAt), v(1))), g(S(s(), ...t, m(e.createdAt), y(1)))]), i = (e) => {
824
+ D("boardId", "==", o()),
825
+ D("isPublished", "==", !0),
826
+ ...e.listed === !1 ? [] : [D("listed", "==", !0)],
827
+ b("createdAt", "desc")
828
+ ], [n, r] = await Promise.all([h(x(s(), ...t, T(e.createdAt), _(1))), h(x(s(), ...t, p(e.createdAt), v(1)))]), i = (e) => {
823
829
  let t = e.docs[0];
824
830
  return t ? {
825
831
  id: t.id,
@@ -838,18 +844,19 @@ function gt(e) {
838
844
  }
839
845
  }
840
846
  async function L(e) {
841
- C(e);
842
- let t = e.postId || A(), n = b(), r, i;
843
- if (n) r = t, i = $e(e.content);
847
+ k(e);
848
+ let t = e.postId || A(), n = S(), r, i;
849
+ if (n) r = t, i = nt(e.content);
844
850
  else {
845
851
  i = e.title.trim();
846
- let t = qe(i) || "post";
852
+ let t = Xe(i) || "post";
847
853
  r = t;
848
854
  let n = 1;
849
- for (; !(await g(S(s(), O("slug", "==", r), v(1)))).empty;) n++, r = `${t}-${n}`;
855
+ for (; !(await h(x(s(), D("boardId", "==", o()), D("slug", "==", r), _(1)))).empty;) n++, r = `${t}-${n}`;
850
856
  }
851
- let a = ot(e.content, e.attachments), o = _();
852
- return await T(c(t), {
857
+ let a = lt(e.content, e.attachments), l = y();
858
+ return await w(c(t), {
859
+ boardId: o(),
853
860
  slug: r,
854
861
  title: i,
855
862
  ...a,
@@ -858,74 +865,72 @@ function gt(e) {
858
865
  commentCount: 0,
859
866
  likeCount: 0,
860
867
  viewCount: 0,
861
- listed: o,
868
+ listed: l,
862
869
  isPublished: !1,
863
870
  authorUid: e.authorUid,
864
871
  authorName: e.authorName,
865
872
  authorPhoto: e.authorPhoto,
866
873
  moderationStatus: "approved",
867
874
  moderationModel: e.moderationModel ?? null,
868
- createdAt: w(),
869
- updatedAt: w()
870
- }), await T(l(t), { content: e.content }), r;
875
+ createdAt: C(),
876
+ updatedAt: C()
877
+ }), await w(f(t), { content: e.content }), r;
871
878
  }
872
879
  async function R(e, t) {
873
- C(t);
874
- let n = b() ? $e(t.content) : t.title.trim(), r = ot(t.content, t.attachments), i = _();
875
- await Promise.all([D(c(e), {
880
+ k(t);
881
+ let n = S() ? nt(t.content) : t.title.trim(), r = lt(t.content, t.attachments), i = y();
882
+ await Promise.all([E(c(e), {
876
883
  title: n,
877
884
  ...r,
878
- previewImage: r.previewImage ? r.previewImage : d(),
879
- videoUrl: r.videoUrl ? r.videoUrl : d(),
885
+ previewImage: r.previewImage ? r.previewImage : u(),
886
+ videoUrl: r.videoUrl ? r.videoUrl : u(),
880
887
  tags: t.tags ?? [],
881
888
  listed: i,
882
889
  attachments: t.attachments ?? [],
883
- updatedAt: w()
884
- }), T(l(e), { content: t.content }, { merge: !0 })]);
890
+ updatedAt: C()
891
+ }), w(f(e), { content: t.content }, { merge: !0 })]);
885
892
  }
886
893
  async function z(e) {
887
- await D(c(e), {
894
+ await E(c(e), {
888
895
  isPublished: !0,
889
- publishedAt: w(),
890
- updatedAt: w()
896
+ publishedAt: C(),
897
+ updatedAt: C()
891
898
  });
892
899
  }
893
900
  async function B(e) {
894
- let [r, i] = await Promise.all([h(c(e)), h(l(e))]);
901
+ let [r, i] = await Promise.all([m(c(e)), m(f(e))]);
895
902
  if (!r.exists()) return;
896
- let s = r.data(), d = i.exists() ? String(i.data().content ?? "") : "", f = await g(p(e));
897
- for (let e = 0; e < f.docs.length; e += 450) {
898
- let n = k(t);
899
- f.docs.slice(e, e + 450).forEach((e) => n.delete(e.ref)), await n.commit();
903
+ let o = r.data(), s = i.exists() ? String(i.data().content ?? "") : "", u = await h(x(g(), D("postId", "==", e)));
904
+ for (let e = 0; e < u.docs.length; e += 450) {
905
+ let n = O(t);
906
+ u.docs.slice(e, e + 450).forEach((e) => n.delete(e.ref)), await n.commit();
900
907
  }
901
- let m = U(n), _ = /* @__PURE__ */ new Set(), v = /* @__PURE__ */ new Set();
902
- for (let t of s.attachments ?? []) if (t?.path) {
903
- _.add(t.path);
904
- let n = Ze(t.path);
905
- n && n !== e && v.add(n);
908
+ let d = U(n), p = /* @__PURE__ */ new Set(), _ = /* @__PURE__ */ new Set();
909
+ for (let t of o.attachments ?? []) if (t?.path) {
910
+ p.add(t.path);
911
+ let n = et(t.path);
912
+ n && n !== e && _.add(n);
906
913
  }
907
- for (let t of Ye(d)) {
908
- let n = Xe(t);
914
+ for (let t of Qe(s)) {
915
+ let n = $e(t);
909
916
  if (!n) continue;
910
- _.add(n);
917
+ p.add(n);
911
918
  let r = n.replace(/\/images\/([^/]+)$/, "/images/thumbnails/$1").replace(/\.[^.]+$/, ".jpg");
912
- r !== n && r.includes("/images/thumbnails/") && _.add(r);
919
+ r !== n && r.includes("/images/thumbnails/") && p.add(r);
913
920
  let i = n.match(/^(.*\/images\/)([^/]+)\.[^.]+$/);
914
- i && !n.includes("/thumbnails/") && _.add(`${i[1]}thumbnails/${i[2]}.jpg`);
915
- let a = Ze(n);
916
- a && a !== e && v.add(a);
921
+ i && !n.includes("/thumbnails/") && p.add(`${i[1]}thumbnails/${i[2]}.jpg`);
922
+ let a = et(n);
923
+ a && a !== e && _.add(a);
917
924
  }
918
- await u(l(e));
919
- let y = o();
920
- await ft(W(m, X(a(), y, e)));
921
- for (let e of v) await ft(W(m, X(a(), y, e)));
922
- await pt(m, _), await u(c(e));
925
+ await l(f(e)), await ht(W(d, Y(a(), e)));
926
+ for (let e of _) await ht(W(d, Y(a(), e)));
927
+ await gt(d, p), await l(c(e));
923
928
  }
924
929
  return {
925
930
  createPostId: A,
926
931
  getPosts: j,
927
- getPost: M,
928
- getPostBySlug: P,
932
+ getPost: N,
933
+ getPostBySlug: F,
929
934
  getAdjacentPosts: I,
930
935
  createPost: L,
931
936
  updatePost: R,
@@ -933,9 +938,9 @@ function gt(e) {
933
938
  deletePost: B
934
939
  };
935
940
  }
936
- var _t = 10;
937
- function vt(e, t = {}) {
938
- let i = F(), { isAdmin: o, isStaff: c, user: l } = Ke(), u = () => Z(), d = t.pageSize ?? _t, f = n(() => l.value?.uid ?? ""), m = n(() => ht(e)), h = () => K(i, u(), m.value), _ = n(() => t.publicOnly === void 0 ? !(o.value || c.value) : typeof t.publicOnly == "boolean" ? t.publicOnly : t.publicOnly.value), y = a([]), C = a([]), w = a([]), T = a(!0), D = a(!0), k = a(!1), A = a(""), j = null, M = null, N, P;
941
+ var bt = 10;
942
+ function xt(e, t = {}) {
943
+ let i = P(), { isAdmin: o, isStaff: c, user: l } = Ye(), u = () => X(), d = t.pageSize ?? bt, p = n(() => l.value?.uid ?? ""), m = n(() => vt(e)), g = () => K(i, u()), v = n(() => t.publicOnly === void 0 ? !(o.value || c.value) : typeof t.publicOnly == "boolean" ? t.publicOnly : t.publicOnly.value), S = a([]), C = a([]), w = a([]), E = a(!0), O = a(!0), k = a(!1), A = a(""), j = null, M = null, N, F;
939
944
  function I(e) {
940
945
  return e.createdAt?.toMillis?.() ?? 0;
941
946
  }
@@ -943,33 +948,33 @@ function vt(e, t = {}) {
943
948
  let e = /* @__PURE__ */ new Map();
944
949
  for (let t of [
945
950
  ...C.value,
946
- ...y.value,
951
+ ...S.value,
947
952
  ...w.value
948
953
  ]) t.id && e.set(t.id, t);
949
954
  return [...e.values()].sort((e, t) => I(t) - I(e) || String(t.id).localeCompare(String(e.id)));
950
955
  });
951
956
  function R() {
952
- let e = [O("isPublished", "==", !0)];
953
- return _.value && e.push(O("listed", "==", !0)), e;
957
+ let e = [D("boardId", "==", m.value), D("isPublished", "==", !0)];
958
+ return v.value && e.push(D("listed", "==", !0)), e;
954
959
  }
955
960
  function z() {
956
- N?.(), N = b(S(h(), ...R(), x("createdAt", "desc"), x(p(), "desc"), v(d)), (e) => {
957
- y.value = e.docs.map((e) => ({
961
+ N?.(), N = y(x(g(), ...R(), b("createdAt", "desc"), b(f(), "desc"), _(d)), (e) => {
962
+ S.value = e.docs.map((e) => ({
958
963
  id: e.id,
959
964
  ...e.data()
960
- })), j = e.docs.at(-1) ?? null, M || (D.value = e.docs.length >= d), T.value = !1;
965
+ })), j = e.docs.at(-1) ?? null, M || (O.value = e.docs.length >= d), E.value = !1;
961
966
  }, (e) => {
962
- console.error("[memi-board:postList] onSnapshot failed", e), A.value = e.message || String(e), T.value = !1;
967
+ console.error("[memi-board:postList] onSnapshot failed", e), A.value = e.message || String(e), E.value = !1;
963
968
  });
964
969
  }
965
970
  function B() {
966
- P?.();
967
- let e = f.value;
971
+ F?.();
972
+ let e = p.value;
968
973
  if (!e) {
969
974
  w.value = [];
970
975
  return;
971
976
  }
972
- P = b(S(h(), O("authorUid", "==", e), O("isPublished", "==", !1), x("createdAt", "desc"), v(20)), (e) => {
977
+ F = y(x(g(), D("boardId", "==", m.value), D("authorUid", "==", e), D("isPublished", "==", !1), b("createdAt", "desc"), _(20)), (e) => {
973
978
  w.value = e.docs.map((e) => ({
974
979
  id: e.id,
975
980
  ...e.data()
@@ -978,21 +983,21 @@ function vt(e, t = {}) {
978
983
  console.error("[memi-board:postList] own draft onSnapshot failed", e);
979
984
  });
980
985
  }
981
- async function ee() {
982
- if (!(k.value || !D.value || T.value)) {
986
+ async function V() {
987
+ if (!(k.value || !O.value || E.value)) {
983
988
  k.value = !0, A.value = "";
984
989
  try {
985
990
  let e = M ?? j, t = [
986
991
  ...R(),
987
- x("createdAt", "desc"),
988
- x(p(), "desc"),
989
- ...e ? [E(e)] : [],
990
- v(d + 1)
991
- ], n = await g(S(h(), ...t)), r = n.docs.slice(0, d), i = r.map((e) => ({
992
+ b("createdAt", "desc"),
993
+ b(f(), "desc"),
994
+ ...e ? [T(e)] : [],
995
+ _(d + 1)
996
+ ], n = await h(x(g(), ...t)), r = n.docs.slice(0, d), i = r.map((e) => ({
992
997
  id: e.id,
993
998
  ...e.data()
994
- })), a = new Set([...C.value, ...y.value].map((e) => e.id));
995
- C.value.push(...i.filter((e) => !a.has(e.id))), M = r.at(-1) ?? M, D.value = n.docs.length > d;
999
+ })), a = new Set([...C.value, ...S.value].map((e) => e.id));
1000
+ C.value.push(...i.filter((e) => !a.has(e.id))), M = r.at(-1) ?? M, O.value = n.docs.length > d;
996
1001
  } catch (e) {
997
1002
  A.value = e.message || String(e), console.error("[memi-board:postList] loadMore failed", e);
998
1003
  } finally {
@@ -1000,187 +1005,187 @@ function vt(e, t = {}) {
1000
1005
  }
1001
1006
  }
1002
1007
  }
1003
- function te() {
1004
- N?.(), y.value = [], C.value = [], j = null, M = null, D.value = !0, A.value = "", T.value = !0, z(), B();
1008
+ function H() {
1009
+ N?.(), S.value = [], C.value = [], j = null, M = null, O.value = !0, A.value = "", E.value = !0, z(), B();
1005
1010
  }
1006
1011
  return s([
1007
1012
  m,
1008
- _,
1009
- f
1010
- ], te, { immediate: !0 }), r(() => {
1011
- N?.(), P?.();
1013
+ v,
1014
+ p
1015
+ ], H, { immediate: !0 }), r(() => {
1016
+ N?.(), F?.();
1012
1017
  }), {
1013
1018
  posts: L,
1014
- postsPending: T,
1015
- hasMore: D,
1019
+ postsPending: E,
1020
+ hasMore: O,
1016
1021
  loadingMore: k,
1017
1022
  loadError: A,
1018
- loadMore: ee
1023
+ loadMore: V
1019
1024
  };
1020
1025
  }
1021
- function yt(e, t) {
1022
- let r = F(), i = () => Z(), a = n(() => ht(e)), o = n(() => typeof t == "string" ? t : t.value), s = n(() => q(r, i(), a.value, o.value)), c = n(() => me(r, i(), a.value, o.value)), { data: l, pending: u } = M(s), { data: d, pending: f } = M(c);
1026
+ function St(e, t) {
1027
+ let r = P(), i = () => X(), a = n(() => vt(e)), o = n(() => typeof t == "string" ? t : t.value), s = n(() => q(r, i(), o.value)), c = n(() => he(r, i(), o.value)), { data: l, pending: u } = j(s), { data: d, pending: f } = j(c);
1023
1028
  return {
1024
- post: n(() => l.value ? {
1029
+ post: n(() => !l.value || l.value.boardId !== a.value ? null : {
1025
1030
  ...l.value,
1026
1031
  id: o.value,
1027
1032
  content: d.value?.content ?? ""
1028
- } : null),
1033
+ }),
1029
1034
  pending: n(() => u.value || f.value)
1030
1035
  };
1031
1036
  }
1032
1037
  //#endregion
1033
1038
  //#region src/composables/useMemiBoardUserPosts.ts
1034
- var bt = 10;
1035
- function xt(e, t = {}) {
1036
- let i = F(), c = t.pageSize ?? bt, u = n(() => o(e)?.trim() || ""), d = a([]), f = a([]), m = a(!0), h = a(!0), _ = a(!1), y = a(""), C = null, w = null, T;
1037
- function D(e) {
1038
- let t = e.ref.parent.parent?.id ?? "";
1039
+ var Ct = 10;
1040
+ function wt(e, t = {}) {
1041
+ let i = P(), c = () => X(), l = () => K(i, c()), u = t.pageSize ?? Ct, d = n(() => o(e)?.trim() || ""), p = a([]), m = a([]), g = a(!0), v = a(!0), S = a(!1), C = a(""), w = null, E = null, O;
1042
+ function k(e) {
1039
1043
  return {
1040
1044
  id: e.id,
1041
- boardId: t,
1042
1045
  ...e.data()
1043
1046
  };
1044
1047
  }
1045
- function k(e) {
1048
+ function A(e) {
1046
1049
  return e.createdAt?.toMillis?.() ?? 0;
1047
1050
  }
1048
- let A = n(() => {
1051
+ let j = n(() => {
1049
1052
  let e = /* @__PURE__ */ new Map();
1050
- for (let t of [...f.value, ...d.value]) t.id && e.set(t.id, t);
1051
- return [...e.values()].sort((e, t) => k(t) - k(e) || String(t.id).localeCompare(String(e.id)));
1053
+ for (let t of [...m.value, ...p.value]) t.id && e.set(t.id, t);
1054
+ return [...e.values()].sort((e, t) => A(t) - A(e) || String(t.id).localeCompare(String(e.id)));
1052
1055
  });
1053
- function j() {
1056
+ function M() {
1054
1057
  return [
1055
- O("authorUid", "==", u.value),
1056
- O("isPublished", "==", !0),
1057
- O("listed", "==", !0)
1058
+ D("authorUid", "==", d.value),
1059
+ D("isPublished", "==", !0),
1060
+ D("listed", "==", !0)
1058
1061
  ];
1059
1062
  }
1060
- function M() {
1061
- if (T?.(), !u.value) {
1062
- m.value = !1;
1063
+ function N() {
1064
+ if (O?.(), !d.value) {
1065
+ g.value = !1;
1063
1066
  return;
1064
1067
  }
1065
- T = b(S(l(i, "posts"), ...j(), x("createdAt", "desc"), x(p(), "desc"), v(c)), (e) => {
1066
- d.value = e.docs.map(D), C = e.docs.at(-1) ?? null, w || (h.value = e.docs.length >= c), m.value = !1;
1068
+ O = y(x(l(), ...M(), b("createdAt", "desc"), b(f(), "desc"), _(u)), (e) => {
1069
+ p.value = e.docs.map(k), w = e.docs.at(-1) ?? null, E || (v.value = e.docs.length >= u), g.value = !1;
1067
1070
  }, (e) => {
1068
- console.error("[memi-board:userPosts] onSnapshot failed", e), y.value = e.message || String(e), m.value = !1;
1071
+ console.error("[memi-board:userPosts] onSnapshot failed", e), C.value = e.message || String(e), g.value = !1;
1069
1072
  });
1070
1073
  }
1071
- async function N() {
1072
- if (!(_.value || !h.value || m.value || !u.value)) {
1073
- _.value = !0, y.value = "";
1074
+ async function F() {
1075
+ if (!(S.value || !v.value || g.value || !d.value)) {
1076
+ S.value = !0, C.value = "";
1074
1077
  try {
1075
- let e = w ?? C, t = [
1076
- ...j(),
1077
- x("createdAt", "desc"),
1078
- x(p(), "desc"),
1079
- ...e ? [E(e)] : [],
1080
- v(c + 1)
1081
- ], n = await g(S(l(i, "posts"), ...t)), r = n.docs.slice(0, c), a = r.map(D), o = new Set([...f.value, ...d.value].map((e) => e.id));
1082
- f.value.push(...a.filter((e) => !o.has(e.id))), w = r.at(-1) ?? w, h.value = n.docs.length > c;
1078
+ let e = E ?? w, t = [
1079
+ ...M(),
1080
+ b("createdAt", "desc"),
1081
+ b(f(), "desc"),
1082
+ ...e ? [T(e)] : [],
1083
+ _(u + 1)
1084
+ ], n = await h(x(l(), ...t)), r = n.docs.slice(0, u), i = r.map(k), a = new Set([...m.value, ...p.value].map((e) => e.id));
1085
+ m.value.push(...i.filter((e) => !a.has(e.id))), E = r.at(-1) ?? E, v.value = n.docs.length > u;
1083
1086
  } catch (e) {
1084
- y.value = e.message || String(e), console.error("[memi-board:userPosts] loadMore failed", e);
1087
+ C.value = e.message || String(e), console.error("[memi-board:userPosts] loadMore failed", e);
1085
1088
  } finally {
1086
- _.value = !1;
1089
+ S.value = !1;
1087
1090
  }
1088
1091
  }
1089
1092
  }
1090
- function P() {
1091
- T?.(), d.value = [], f.value = [], C = null, w = null, h.value = !0, y.value = "", m.value = !0, M();
1093
+ function I() {
1094
+ O?.(), p.value = [], m.value = [], w = null, E = null, v.value = !0, C.value = "", g.value = !0, N();
1092
1095
  }
1093
- return s(u, P, { immediate: !0 }), r(() => T?.()), {
1094
- posts: A,
1095
- postsPending: m,
1096
- hasMore: h,
1097
- loadingMore: _,
1098
- loadError: y,
1099
- loadMore: N
1096
+ return s(d, I, { immediate: !0 }), r(() => O?.()), {
1097
+ posts: j,
1098
+ postsPending: g,
1099
+ hasMore: v,
1100
+ loadingMore: S,
1101
+ loadError: C,
1102
+ loadMore: F
1100
1103
  };
1101
1104
  }
1102
1105
  //#endregion
1103
1106
  //#region src/composables/useMemiBoardComments.ts
1104
- var St = 10, Ct = 5, wt = 1e3;
1105
- function Tt(e) {
1107
+ var Tt = 10, Et = 5, Dt = 1e3;
1108
+ function Ot(e) {
1106
1109
  let t = e.trim();
1107
1110
  if (!t) throw Error("댓글 내용을 입력해 주세요.");
1108
- if (t.length > 1e3) throw Error(`댓글은 ${wt.toLocaleString()}자까지 작성할 수 있습니다.`);
1111
+ if (t.length > 1e3) throw Error(`댓글은 ${Dt.toLocaleString()}자까지 작성할 수 있습니다.`);
1109
1112
  return t;
1110
1113
  }
1111
- function Et(e, t, i = {}) {
1112
- let c = F(), l = () => Z(), u = n(() => o(e)), d = n(() => o(t)), m = a([]), h = a([]), y = a(i.subscribe !== !1), C = a([]), T = a(!1), A = a(i.subscribe !== !1), j = a(i.subscribe !== !1), M = null, N, P;
1113
- function I(e) {
1114
+ function kt(e, t, i = {}) {
1115
+ let c = P(), l = () => X(), u = () => ge(c, l()), p = n(() => o(e)), m = n(() => o(t)), v = a([]), S = a([]), w = a(i.subscribe !== !1), k = a([]), A = a(!1), j = a(i.subscribe !== !1), M = a(i.subscribe !== !1), N = null, F, I;
1116
+ function L(e) {
1114
1117
  return e.createdAt?.toMillis?.() ?? 0;
1115
1118
  }
1116
- let L = n(() => {
1119
+ let R = n(() => {
1117
1120
  let e = /* @__PURE__ */ new Map();
1118
1121
  for (let t of [
1119
- ...m.value,
1120
- ...h.value,
1121
- ...C.value
1122
+ ...v.value,
1123
+ ...S.value,
1124
+ ...k.value
1122
1125
  ]) t.id && e.set(t.id, t);
1123
- return [...e.values()].sort((e, t) => I(e) - I(t) || String(e.id).localeCompare(String(t.id)));
1126
+ return [...e.values()].sort((e, t) => L(e) - L(t) || String(e.id).localeCompare(String(t.id)));
1124
1127
  });
1125
- s([d, u], async ([e]) => {
1126
- if (C.value = [], m.value = [], h.value = [], M = null, clearTimeout(N), N = void 0, P?.(), P = void 0, A.value = i.subscribe !== !1, i.subscribe !== !1) {
1127
- y.value = !0, j.value = !0;
1128
+ s([m, p], async ([e]) => {
1129
+ if (k.value = [], v.value = [], S.value = [], N = null, clearTimeout(F), F = void 0, I?.(), I = void 0, j.value = i.subscribe !== !1, i.subscribe !== !1) {
1130
+ w.value = !0, M.value = !0;
1128
1131
  try {
1129
- let t = g(S(J(c, l(), u.value, e), x("createdAt", "desc"), x(p(), "desc")));
1132
+ let t = h(x(u(), D("postId", "==", e), b("createdAt", "desc"), b(f(), "desc")));
1130
1133
  if (await Promise.all([t.then((t) => {
1131
- if (d.value !== e) return;
1134
+ if (m.value !== e) return;
1132
1135
  let n = t.docs.map((e) => ({
1133
1136
  id: e.id,
1134
1137
  ...e.data()
1135
1138
  }));
1136
- h.value = n.filter((e) => e.parentId === void 0);
1137
- }), z()]), d.value !== e) return;
1139
+ S.value = n.filter((e) => e.parentId === void 0);
1140
+ }), B()]), m.value !== e) return;
1138
1141
  } finally {
1139
- d.value === e && (y.value = !1, j.value = !1);
1142
+ m.value === e && (w.value = !1, M.value = !1);
1140
1143
  }
1141
1144
  }
1142
1145
  }, { immediate: !0 });
1143
- function R() {
1144
- P?.(), P = b(S(J(c, l(), u.value, d.value), O("parentId", "==", null), x("createdAt", "desc"), x(p(), "desc"), v(1)), (e) => {
1145
- let t = new Map(m.value.map((e) => [e.id, e]));
1146
+ function z() {
1147
+ I?.(), I = y(x(u(), D("postId", "==", m.value), D("parentId", "==", null), b("createdAt", "desc"), b(f(), "desc"), _(1)), (e) => {
1148
+ let t = new Map(v.value.map((e) => [e.id, e]));
1146
1149
  for (let n of e.docChanges()) n.type !== "removed" && t.set(n.doc.id, {
1147
1150
  id: n.doc.id,
1148
1151
  ...n.doc.data()
1149
1152
  });
1150
- m.value = [...t.values()];
1153
+ v.value = [...t.values()];
1151
1154
  }, (e) => {
1152
1155
  console.error("[memi-board:comments] onSnapshot failed", e);
1153
1156
  });
1154
1157
  }
1155
- async function z() {
1156
- if (!(T.value || !A.value)) {
1157
- T.value = !0;
1158
+ async function B() {
1159
+ if (!(A.value || !j.value)) {
1160
+ A.value = !0;
1158
1161
  try {
1159
1162
  let e = [
1160
- O("parentId", "==", null),
1161
- x("createdAt", "asc"),
1162
- x(p(), "asc"),
1163
- ...M ? [E(M)] : [],
1164
- v(11)
1165
- ], t = await g(S(J(c, l(), u.value, d.value), ...e)), n = t.docs.slice(0, St), r = n.map((e) => ({
1163
+ D("postId", "==", m.value),
1164
+ D("parentId", "==", null),
1165
+ b("createdAt", "asc"),
1166
+ b(f(), "asc"),
1167
+ ...N ? [T(N)] : [],
1168
+ _(11)
1169
+ ], t = await h(x(u(), ...e)), n = t.docs.slice(0, Tt), r = n.map((e) => ({
1166
1170
  id: e.id,
1167
1171
  ...e.data()
1168
- })), i = new Set(C.value.map((e) => e.id));
1169
- C.value.push(...r.filter((e) => !i.has(e.id))), M = n.at(-1) ?? M, t.size <= St && (A.value = !1, N = setTimeout(() => {
1170
- N = void 0, R();
1172
+ })), i = new Set(k.value.map((e) => e.id));
1173
+ k.value.push(...r.filter((e) => !i.has(e.id))), N = n.at(-1) ?? N, t.size <= Tt && (j.value = !1, F = setTimeout(() => {
1174
+ F = void 0, z();
1171
1175
  }, 1e3));
1172
1176
  } finally {
1173
- T.value = !1;
1177
+ A.value = !1;
1174
1178
  }
1175
1179
  }
1176
1180
  }
1177
1181
  r(() => {
1178
- clearTimeout(N), P?.();
1182
+ clearTimeout(F), I?.();
1179
1183
  });
1180
- async function B(e) {
1181
- let t = Tt(e.body), n = k(c), r = f(J(c, l(), u.value, d.value));
1184
+ async function V(e) {
1185
+ let t = Ot(e.body), n = O(c), r = d(u());
1182
1186
  n.set(r, {
1183
- postId: d.value,
1187
+ postId: m.value,
1188
+ boardId: p.value,
1184
1189
  body: t,
1185
1190
  authorUid: e.authorUid,
1186
1191
  authorName: e.authorName,
@@ -1193,133 +1198,134 @@ function Et(e, t, i = {}) {
1193
1198
  replyToName: null,
1194
1199
  replyCount: 0,
1195
1200
  isReply: !1,
1196
- createdAt: w(),
1197
- updatedAt: w()
1198
- }), n.update(q(c, l(), u.value, d.value), { commentCount: _(1) }), await n.commit();
1201
+ createdAt: C(),
1202
+ updatedAt: C()
1203
+ }), n.update(q(c, l(), m.value), { commentCount: g(1) }), await n.commit();
1199
1204
  }
1200
- async function ee(e) {
1201
- let t = Tt(e.body);
1205
+ async function H(e) {
1206
+ let t = Ot(e.body);
1202
1207
  if (!e.parent.id) throw Error("답글 대상 댓글을 찾을 수 없습니다.");
1203
- let n = k(c), r = J(c, l(), u.value, d.value), i = f(r), a = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
1204
- if (!a) throw Error("댓글 스레드를 찾을 수 없습니다.");
1205
- let o = Math.min((e.parent.depth ?? 0) + 1, 2);
1206
- n.set(i, {
1207
- postId: d.value,
1208
+ let n = O(c), r = d(u()), i = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
1209
+ if (!i) throw Error("댓글 스레드를 찾을 수 없습니다.");
1210
+ let a = Math.min((e.parent.depth ?? 0) + 1, 2);
1211
+ n.set(r, {
1212
+ postId: m.value,
1213
+ boardId: p.value,
1208
1214
  body: t,
1209
1215
  authorUid: e.authorUid,
1210
1216
  authorName: e.authorName,
1211
1217
  authorPhoto: e.authorPhoto,
1212
1218
  moderationStatus: "approved",
1213
1219
  parentId: e.parent.id,
1214
- rootId: a,
1215
- depth: o,
1220
+ rootId: i,
1221
+ depth: a,
1216
1222
  replyToUid: e.parent.authorUid,
1217
1223
  replyToName: e.parent.authorName,
1218
1224
  replyCount: 0,
1219
1225
  isReply: !0,
1220
- createdAt: w(),
1221
- updatedAt: w()
1222
- }), n.update(f(r, a), { replyCount: _(1) }), n.update(q(c, l(), u.value, d.value), { commentCount: _(1) }), await n.commit();
1226
+ createdAt: C(),
1227
+ updatedAt: C()
1228
+ }), n.update(J(c, l(), i), { replyCount: g(1) }), n.update(q(c, l(), m.value), { commentCount: g(1) }), await n.commit();
1223
1229
  }
1224
- async function te(e) {
1230
+ async function ee(e) {
1225
1231
  if (!e.id) return;
1226
1232
  if (e.parentId == null && (e.replyCount ?? 0) > 0) throw Error("답글이 있는 댓글은 현재 삭제할 수 없습니다.");
1227
- let t = k(c);
1228
- t.delete(Y(c, l(), u.value, d.value, e.id)), e.parentId && e.rootId && t.update(Y(c, l(), u.value, d.value, e.rootId), { replyCount: _(-1) }), t.update(q(c, l(), u.value, d.value), { commentCount: _(-1) }), await t.commit(), C.value = C.value.filter((t) => t.id !== e.id), h.value = h.value.filter((t) => t.id !== e.id), m.value = m.value.filter((t) => t.id !== e.id);
1233
+ let t = O(c);
1234
+ t.delete(J(c, l(), e.id)), e.parentId && e.rootId && t.update(J(c, l(), e.rootId), { replyCount: g(-1) }), t.update(q(c, l(), m.value), { commentCount: g(-1) }), await t.commit(), k.value = k.value.filter((t) => t.id !== e.id), S.value = S.value.filter((t) => t.id !== e.id), v.value = v.value.filter((t) => t.id !== e.id);
1229
1235
  }
1230
- async function V(e, t) {
1231
- let n = Tt(t);
1232
- await D(Y(c, l(), u.value, d.value, e), {
1236
+ async function U(e, t) {
1237
+ let n = Ot(t);
1238
+ await E(J(c, l(), e), {
1233
1239
  body: n,
1234
- updatedAt: w()
1240
+ updatedAt: C()
1235
1241
  });
1236
1242
  }
1237
- async function H(e, t, n) {
1238
- await D(Y(c, l(), u.value, d.value, e), {
1243
+ async function te(e, t, n) {
1244
+ await E(J(c, l(), e), {
1239
1245
  isBlinded: t,
1240
- moderatedAt: w(),
1246
+ moderatedAt: C(),
1241
1247
  moderatedBy: n
1242
1248
  });
1243
1249
  }
1244
1250
  return {
1245
- comments: L,
1246
- commentsPending: n(() => j.value || y.value),
1247
- hasMore: A,
1248
- loadingMore: T,
1249
- loadMore: z,
1250
- addComment: B,
1251
- addReply: ee,
1252
- updateComment: V,
1253
- setCommentBlinded: H,
1254
- deleteComment: te
1251
+ comments: R,
1252
+ commentsPending: n(() => M.value || w.value),
1253
+ hasMore: j,
1254
+ loadingMore: A,
1255
+ loadMore: B,
1256
+ addComment: V,
1257
+ addReply: H,
1258
+ updateComment: U,
1259
+ setCommentBlinded: te,
1260
+ deleteComment: ee
1255
1261
  };
1256
1262
  }
1257
- function Dt(e, t, n) {
1258
- let i = F(), o = () => Z(), s = a([]), c = a(!1), l = a(!1), u = a(!0), d = null, f;
1259
- function m(e) {
1263
+ function At(e, t, n) {
1264
+ let i = P(), o = () => X(), s = () => ge(i, o()), c = a([]), l = a(!1), u = a(!1), d = a(!0), p = null, m;
1265
+ function g(e) {
1260
1266
  return e.sort((e, t) => (e.createdAt?.toMillis?.() ?? 0) - (t.createdAt?.toMillis?.() ?? 0) || String(e.id).localeCompare(String(t.id)));
1261
1267
  }
1262
- function h() {
1263
- f ||= b(S(J(i, o(), e, t), O("rootId", "==", n), O("isReply", "==", !0), x("createdAt", "desc"), x(p(), "desc"), v(1)), (e) => {
1264
- let t = new Map(s.value.map((e) => [e.id, e]));
1268
+ function v() {
1269
+ m ||= y(x(s(), D("rootId", "==", n), D("isReply", "==", !0), b("createdAt", "desc"), b(f(), "desc"), _(1)), (e) => {
1270
+ let t = new Map(c.value.map((e) => [e.id, e]));
1265
1271
  for (let n of e.docChanges()) n.type !== "removed" && t.set(n.doc.id, {
1266
1272
  id: n.doc.id,
1267
1273
  ...n.doc.data()
1268
1274
  });
1269
- s.value = m([...t.values()]);
1275
+ c.value = g([...t.values()]);
1270
1276
  }, (e) => {
1271
1277
  console.error("[memi-board:replies] onSnapshot failed", e);
1272
1278
  });
1273
1279
  }
1274
- async function _() {
1275
- if (!(c.value || !u.value)) {
1276
- c.value = !0;
1280
+ async function S() {
1281
+ if (!(l.value || !d.value)) {
1282
+ l.value = !0;
1277
1283
  try {
1278
- let r = [
1279
- O("rootId", "==", n),
1280
- O("isReply", "==", !0),
1281
- x("createdAt", "asc"),
1282
- x(p(), "asc"),
1283
- ...d ? [E(d)] : [],
1284
- v(6)
1285
- ], a = await g(S(J(i, o(), e, t), ...r)), c = new Set(s.value.map((e) => e.id)), f = a.docs.slice(0, Ct), _ = f.map((e) => ({
1284
+ let e = [
1285
+ D("rootId", "==", n),
1286
+ D("isReply", "==", !0),
1287
+ b("createdAt", "asc"),
1288
+ b(f(), "asc"),
1289
+ ...p ? [T(p)] : [],
1290
+ _(6)
1291
+ ], t = await h(x(s(), ...e)), r = new Set(c.value.map((e) => e.id)), i = t.docs.slice(0, Et), a = i.map((e) => ({
1286
1292
  id: e.id,
1287
1293
  ...e.data()
1288
- })).filter((e) => !c.has(e.id));
1289
- s.value = m([...s.value, ..._]), d = f.at(-1) ?? d, l.value = !0, a.size <= Ct && (u.value = !1, h());
1294
+ })).filter((e) => !r.has(e.id));
1295
+ c.value = g([...c.value, ...a]), p = i.at(-1) ?? p, u.value = !0, t.size <= Et && (d.value = !1, v());
1290
1296
  } finally {
1291
- c.value = !1;
1297
+ l.value = !1;
1292
1298
  }
1293
1299
  }
1294
1300
  }
1295
- function y() {
1296
- f?.(), f = void 0, s.value = [], d = null, l.value = !1, u.value = !0;
1297
- }
1298
- async function C() {
1299
- y(), await _();
1300
- }
1301
- return r(() => f?.()), {
1302
- replies: s,
1303
- loading: c,
1304
- loaded: l,
1305
- hasMore: u,
1306
- loadMore: _,
1307
- refresh: C
1301
+ function C() {
1302
+ m?.(), m = void 0, c.value = [], p = null, u.value = !1, d.value = !0;
1303
+ }
1304
+ async function w() {
1305
+ C(), await S();
1306
+ }
1307
+ return r(() => m?.()), {
1308
+ replies: c,
1309
+ loading: l,
1310
+ loaded: u,
1311
+ hasMore: d,
1312
+ loadMore: S,
1313
+ refresh: w
1308
1314
  };
1309
1315
  }
1310
1316
  //#endregion
1311
1317
  //#region src/composables/useMemiBoardLikes.ts
1312
- function Ot(e, t) {
1313
- let n = () => Z(), i = F(), o = j(), c = a(!1), l = a(!1), u;
1314
- function d(r) {
1315
- return he(i, n(), e, t, r);
1318
+ function jt(e, t) {
1319
+ let n = () => X(), i = P(), o = A(), c = a(!1), l = a(!1), u;
1320
+ function d(e) {
1321
+ return ye(i, n(), t, e);
1316
1322
  }
1317
1323
  s(o, (e) => {
1318
1324
  if (u?.(), u = void 0, !e) {
1319
1325
  c.value = !1;
1320
1326
  return;
1321
1327
  }
1322
- u = b(d(e.uid), (e) => {
1328
+ u = y(d(e.uid), (e) => {
1323
1329
  c.value = e.exists();
1324
1330
  }, (e) => {
1325
1331
  console.error("[memi-board:likes] onSnapshot failed", e);
@@ -1331,11 +1337,13 @@ function Ot(e, t) {
1331
1337
  if (l.value) return c.value;
1332
1338
  l.value = !0;
1333
1339
  try {
1334
- let a = d(r), o = q(i, n(), e, t);
1335
- return await C(i, async (e) => (await e.get(a)).exists() ? (e.delete(a), e.update(o, { likeCount: _(-1) }), !1) : (e.set(a, {
1340
+ let a = d(r), o = q(i, n(), t);
1341
+ return await S(i, async (n) => (await n.get(a)).exists() ? (n.delete(a), n.update(o, { likeCount: g(-1) }), !1) : (n.set(a, {
1336
1342
  uid: r,
1337
- createdAt: w()
1338
- }), e.update(o, { likeCount: _(1) }), !0));
1343
+ postId: t,
1344
+ boardId: e,
1345
+ createdAt: C()
1346
+ }), n.update(o, { likeCount: g(1) }), !0));
1339
1347
  } finally {
1340
1348
  l.value = !1;
1341
1349
  }
@@ -1348,10 +1356,10 @@ function Ot(e, t) {
1348
1356
  }
1349
1357
  //#endregion
1350
1358
  //#region src/composables/useMemiBoardViews.ts
1351
- function kt() {
1352
- let e = () => Z(), t = F();
1359
+ function Mt() {
1360
+ let e = () => X(), t = P();
1353
1361
  function n(t, n) {
1354
- let { boardsCollection: r } = e();
1362
+ let { postsCollection: r } = e();
1355
1363
  return `memi-board:viewed:${r}:${t}:${n}`;
1356
1364
  }
1357
1365
  function r(e, t) {
@@ -1372,7 +1380,7 @@ function kt() {
1372
1380
  if (!(!s || !c) && !r(s, c)) {
1373
1381
  i(s, c);
1374
1382
  try {
1375
- await D(q(t, e(), s, c), { viewCount: _(1) });
1383
+ await E(q(t, e(), c), { viewCount: g(1) });
1376
1384
  } catch (e) {
1377
1385
  try {
1378
1386
  sessionStorage.removeItem(n(s, c));
@@ -1388,7 +1396,7 @@ function kt() {
1388
1396
  }
1389
1397
  //#endregion
1390
1398
  //#region src/utils/moderation-prompt.ts
1391
- var At = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\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은 \"\"", jt = {
1399
+ var Nt = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\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은 \"\"", Pt = {
1392
1400
  abuse: "욕설·비속어·모욕",
1393
1401
  spam: "스팸·광고",
1394
1402
  adult: "선정적·성인 내용",
@@ -1396,17 +1404,17 @@ var At = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이
1396
1404
  other: "부적절한 내용",
1397
1405
  none: ""
1398
1406
  };
1399
- function Mt(e) {
1407
+ function Ft(e) {
1400
1408
  if (e.via === "local" && e.localHit) {
1401
1409
  let t = e.localHit.trim();
1402
1410
  return t.length > 0 && t.length <= 12 ? `「${t}」 같은 표현은 사용할 수 없습니다. 다른 말로 바꿔 주세요.` : "욕설·비속어가 포함되어 게시할 수 없습니다. 표현을 바꿔 주세요.";
1403
1411
  }
1404
1412
  let t = (e.reason || "").trim();
1405
1413
  if (t) return t;
1406
- let n = e.category && e.category !== "none" ? jt[e.category] || e.category : "";
1414
+ let n = e.category && e.category !== "none" ? Pt[e.category] || e.category : "";
1407
1415
  return n ? `${n}이(가) 포함되어 게시할 수 없습니다. 내용을 수정해 주세요.` : "게시할 수 없는 내용이 포함되어 있습니다. 내용을 수정해 주세요.";
1408
1416
  }
1409
- var Nt = [
1417
+ var It = [
1410
1418
  "씨발",
1411
1419
  "시발",
1412
1420
  "시이발",
@@ -1432,10 +1440,10 @@ var Nt = [
1432
1440
  "bitch",
1433
1441
  "asshole"
1434
1442
  ];
1435
- function Pt(e) {
1443
+ function Lt(e) {
1436
1444
  return new RegExp(e.join("|"), "i");
1437
1445
  }
1438
- var Ft = /* @__PURE__ */ new Set([
1446
+ var Rt = /* @__PURE__ */ new Set([
1439
1447
  "none",
1440
1448
  "abuse",
1441
1449
  "spam",
@@ -1443,7 +1451,7 @@ var Ft = /* @__PURE__ */ new Set([
1443
1451
  "violence",
1444
1452
  "other"
1445
1453
  ]);
1446
- function It(e) {
1454
+ function zt(e) {
1447
1455
  let t = e.trim();
1448
1456
  if (!t) return null;
1449
1457
  let n;
@@ -1461,7 +1469,7 @@ function It(e) {
1461
1469
  if (typeof n != "object" || !n) return null;
1462
1470
  let r = n;
1463
1471
  if (typeof r.flagged != "boolean") return null;
1464
- let i = typeof r.category == "string" && Ft.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
1472
+ let i = typeof r.category == "string" && Rt.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
1465
1473
  return {
1466
1474
  flagged: r.flagged,
1467
1475
  category: i,
@@ -1470,20 +1478,20 @@ function It(e) {
1470
1478
  }
1471
1479
  //#endregion
1472
1480
  //#region src/composables/useMemiBoardModeration.ts
1473
- var Lt = {
1481
+ var Bt = {
1474
1482
  flagged: !1,
1475
1483
  category: "none",
1476
1484
  reason: "",
1477
1485
  via: "empty"
1478
1486
  };
1479
- function Rt() {
1480
- let e = Ce().moderation ?? {}, t = N(), { isWriteRestricted: n, restrictedMessage: r, recordContentModerationBlock: i } = Ke(), a = Pt([...Nt, ...e.localBlocklist ?? []]);
1487
+ function Vt() {
1488
+ let e = Ee().moderation ?? {}, t = M(), { isWriteRestricted: n, restrictedMessage: r, recordContentModerationBlock: i } = Ye(), a = Lt([...It, ...e.localBlocklist ?? []]);
1481
1489
  function o(e) {
1482
1490
  let t = e.match(a);
1483
1491
  return t ? {
1484
1492
  flagged: !0,
1485
1493
  category: "abuse",
1486
- reason: Mt({
1494
+ reason: Ft({
1487
1495
  via: "local",
1488
1496
  localHit: t[0] || "",
1489
1497
  category: "abuse"
@@ -1492,21 +1500,21 @@ function Rt() {
1492
1500
  } : null;
1493
1501
  }
1494
1502
  function s() {
1495
- let n = e.model ?? "gemini-3.5-flash-lite", r = e.useLimitedUseAppCheckTokens !== !1, i = se(t, {
1496
- backend: new ae(),
1503
+ let n = e.model ?? "gemini-3.5-flash-lite", r = e.useLimitedUseAppCheckTokens !== !1, i = oe(t, {
1504
+ backend: new ie(),
1497
1505
  useLimitedUseAppCheckTokens: r
1498
1506
  });
1499
- return ce(i, {
1507
+ return se(i, {
1500
1508
  model: n,
1501
- systemInstruction: At,
1509
+ systemInstruction: Nt,
1502
1510
  generationConfig: {
1503
1511
  temperature: 0,
1504
1512
  maxOutputTokens: 256,
1505
1513
  responseMimeType: "application/json",
1506
- responseSchema: oe.object({ properties: {
1507
- flagged: oe.boolean(),
1508
- category: oe.string(),
1509
- reason: oe.string()
1514
+ responseSchema: ae.object({ properties: {
1515
+ flagged: ae.boolean(),
1516
+ category: ae.string(),
1517
+ reason: ae.string()
1510
1518
  } })
1511
1519
  }
1512
1520
  });
@@ -1527,7 +1535,7 @@ function Rt() {
1527
1535
  };
1528
1536
  }
1529
1537
  function l(e, t = "") {
1530
- let n = Mt({
1538
+ let n = Ft({
1531
1539
  reason: e.reason,
1532
1540
  category: e.category,
1533
1541
  via: e.via
@@ -1555,22 +1563,22 @@ function Rt() {
1555
1563
  };
1556
1564
  let i = t.trim();
1557
1565
  if (!i) return {
1558
- ...Lt,
1566
+ ...Bt,
1559
1567
  via: "empty"
1560
1568
  };
1561
1569
  let a = o(i);
1562
1570
  if (a) return u(a);
1563
1571
  if (e.enabled === !1) return {
1564
- ...Lt,
1572
+ ...Bt,
1565
1573
  via: "disabled"
1566
1574
  };
1567
1575
  try {
1568
- let e = It((await s().generateContent(`심사할 텍스트:\n"""${i.slice(0, 4e3)}"""`)).response.text());
1576
+ let e = zt((await s().generateContent(`심사할 텍스트:\n"""${i.slice(0, 4e3)}"""`)).response.text());
1569
1577
  if (!e) return c();
1570
1578
  let t = {
1571
1579
  flagged: e.flagged,
1572
1580
  category: e.category,
1573
- reason: Mt({
1581
+ reason: Ft({
1574
1582
  reason: e.reason,
1575
1583
  category: e.category,
1576
1584
  via: "ai"
@@ -1590,14 +1598,14 @@ function Rt() {
1590
1598
  via: "restricted"
1591
1599
  };
1592
1600
  if (e.enabled === !1 || !e.moderateImages) return {
1593
- ...Lt,
1601
+ ...Bt,
1594
1602
  via: "disabled"
1595
1603
  };
1596
1604
  try {
1597
1605
  let e = await new Promise((e, n) => {
1598
1606
  let r = new FileReader();
1599
1607
  r.onload = () => e(r.result.split(",")[1] ?? ""), r.onerror = n, r.readAsDataURL(t);
1600
- }), n = It((await s().generateContent([{ inlineData: {
1608
+ }), n = zt((await s().generateContent([{ inlineData: {
1601
1609
  mimeType: t.type || "image/jpeg",
1602
1610
  data: e
1603
1611
  } }, "이 이미지를 심사해 주세요."])).response.text());
@@ -1605,7 +1613,7 @@ function Rt() {
1605
1613
  let r = {
1606
1614
  flagged: n.flagged,
1607
1615
  category: n.category,
1608
- reason: Mt({
1616
+ reason: Ft({
1609
1617
  reason: n.reason,
1610
1618
  category: n.category,
1611
1619
  via: "ai"
@@ -1624,7 +1632,7 @@ function Rt() {
1624
1632
  }
1625
1633
  //#endregion
1626
1634
  //#region src/utils/compressImage.ts
1627
- async function zt(e, { maxWidth: t = 400, quality: n = .8 } = {}) {
1635
+ async function Q(e, { maxWidth: t = 400, quality: n = .8 } = {}) {
1628
1636
  return new Promise((r, i) => {
1629
1637
  let a = new Image(), o = URL.createObjectURL(e);
1630
1638
  a.onload = () => {
@@ -1644,12 +1652,12 @@ async function zt(e, { maxWidth: t = 400, quality: n = .8 } = {}) {
1644
1652
  }
1645
1653
  //#endregion
1646
1654
  //#region src/composables/useMemiBoardStorage.ts
1647
- var Bt = 5242880, Vt = 26214400;
1648
- function Ht(e) {
1655
+ var Ht = 5242880, Ut = 26214400;
1656
+ function Wt(e) {
1649
1657
  let t = (e.type || "").toLowerCase(), n = e.name.toLowerCase();
1650
1658
  return t === "image/heic" || t === "image/heif" || n.endsWith(".heic") || n.endsWith(".heif");
1651
1659
  }
1652
- async function Ut(t, n = .85) {
1660
+ async function Gt(t, n = .85) {
1653
1661
  let { default: r } = await import("./heic2any-BXb_U1zK.js").then((t) => /* @__PURE__ */ e(t.default, 1)), i = await r({
1654
1662
  blob: t,
1655
1663
  toType: "image/jpeg",
@@ -1658,30 +1666,30 @@ async function Ut(t, n = .85) {
1658
1666
  if (!(a instanceof Blob)) throw Error("HEIC 변환 결과가 비어 있습니다.");
1659
1667
  return a;
1660
1668
  }
1661
- async function Wt(e) {
1669
+ async function Kt(e) {
1662
1670
  if (e.size <= 5242880) return e;
1663
- let t = await zt(e, {
1671
+ let t = await Q(e, {
1664
1672
  maxWidth: 2560,
1665
1673
  quality: .85
1666
1674
  });
1667
- if (t.size > 5242880 && (t = await zt(e, {
1675
+ if (t.size > 5242880 && (t = await Q(e, {
1668
1676
  maxWidth: 2048,
1669
1677
  quality: .75
1670
1678
  })), t.size > 5242880) throw Error("압축 후에도 이미지가 5MB를 초과합니다. 더 작은 이미지를 선택해 주세요.");
1671
1679
  return t;
1672
1680
  }
1673
- async function Gt(e) {
1674
- if (e.size > 26214400) throw Error(`원본 이미지는 ${Vt / 1024 / 1024}MB 이하여야 합니다.`);
1675
- if (Ht(e)) try {
1681
+ async function qt(e) {
1682
+ if (e.size > 26214400) throw Error(`원본 이미지는 ${Ut / 1024 / 1024}MB 이하여야 합니다.`);
1683
+ if (Wt(e)) try {
1676
1684
  return {
1677
- blob: await Wt(await Ut(e, .85)),
1685
+ blob: await Kt(await Gt(e, .85)),
1678
1686
  compressed: !0
1679
1687
  };
1680
1688
  } catch (t) {
1681
1689
  if (t instanceof Error && t.message.includes("압축 후에도")) throw t;
1682
1690
  try {
1683
1691
  return {
1684
- blob: await Wt(await zt(e, {
1692
+ blob: await Kt(await Q(e, {
1685
1693
  maxWidth: 2560,
1686
1694
  quality: .85
1687
1695
  })),
@@ -1697,60 +1705,60 @@ async function Gt(e) {
1697
1705
  };
1698
1706
  try {
1699
1707
  return {
1700
- blob: await Wt(e),
1708
+ blob: await Kt(e),
1701
1709
  compressed: !0
1702
1710
  };
1703
1711
  } catch (e) {
1704
1712
  throw e instanceof Error && e.message.includes("압축 후에도") ? e : Error("이 이미지 형식은 브라우저에서 최적화할 수 없습니다. JPG, PNG 또는 WebP로 변환해 주세요.");
1705
1713
  }
1706
1714
  }
1707
- function Kt() {
1708
- let e = () => Z(), t = N();
1709
- function n(n, r, i, a) {
1710
- let o = U(t), s = `${X(e(), r, i)}/attachments/${Date.now()}-${Je(n.name)}`, c = W(o, s), l = ie(c, n, {
1715
+ function Jt() {
1716
+ let e = () => X(), t = M();
1717
+ function n(n, r, i) {
1718
+ let a = U(t), o = `${Y(e(), r)}/attachments/${Date.now()}-${Ze(n.name)}`, s = W(a, o), c = re(s, n, {
1711
1719
  contentType: n.type || "application/octet-stream",
1712
1720
  contentDisposition: `attachment; filename*=UTF-8''${encodeURIComponent(n.name)}`
1713
1721
  });
1714
1722
  return {
1715
1723
  promise: new Promise((e, t) => {
1716
- l.on("state_changed", (e) => {
1717
- a && e.totalBytes > 0 && a(e.bytesTransferred / e.totalBytes);
1724
+ c.on("state_changed", (e) => {
1725
+ i && e.totalBytes > 0 && i(e.bytesTransferred / e.totalBytes);
1718
1726
  }, t, async () => {
1719
- let t = await H(c);
1727
+ let t = await ee(s);
1720
1728
  e({
1721
1729
  name: n.name,
1722
1730
  url: t,
1723
- path: s,
1731
+ path: o,
1724
1732
  size: n.size,
1725
1733
  type: n.type
1726
1734
  });
1727
1735
  });
1728
1736
  }),
1729
- cancel: () => l.cancel()
1737
+ cancel: () => c.cancel()
1730
1738
  };
1731
1739
  }
1732
1740
  async function r(e) {
1733
1741
  let n = U(t);
1734
- await V(W(n, e.path)).catch(() => {});
1742
+ await H(W(n, e.path)).catch(() => {});
1735
1743
  }
1736
- async function i(n, r, i) {
1744
+ async function i(n, r) {
1737
1745
  if (!n.type.startsWith("image/")) throw Error("이미지 파일만 업로드할 수 있습니다.");
1738
- let a = await Gt(n), o = U(t), s = (a.compressed ? "jpg" : n.name.split(".").pop() || n.type.split("/")[1] || "png").replace(/[^a-zA-Z0-9]/g, "").slice(0, 8) || "png", c = Je(n.name.replace(/\.[^.]+$/, "") || "image", 40), l = `${Date.now()}-${c || "image"}`, u = i || `new-${Date.now()}`, d = X(e(), r, u), f = `${d}/images/${l}.${s}`, p = W(o, f);
1739
- await re(p, a.blob, { contentType: a.blob.type || "image/jpeg" });
1740
- let m = await H(p), h = await zt(a.blob, {
1746
+ let i = await qt(n), a = U(t), o = (i.compressed ? "jpg" : n.name.split(".").pop() || n.type.split("/")[1] || "png").replace(/[^a-zA-Z0-9]/g, "").slice(0, 8) || "png", s = Ze(n.name.replace(/\.[^.]+$/, "") || "image", 40), c = `${Date.now()}-${s || "image"}`, l = r || `new-${Date.now()}`, u = Y(e(), l), d = `${u}/images/${c}.${o}`, f = W(a, d);
1747
+ await ne(f, i.blob, { contentType: i.blob.type || "image/jpeg" });
1748
+ let p = await ee(f), m = await Q(i.blob, {
1741
1749
  maxWidth: 400,
1742
1750
  quality: .8
1743
- }), g = `${d}/images/thumbnails/${l}.jpg`, _ = W(o, g);
1744
- return await re(_, h, { contentType: "image/jpeg" }), {
1745
- originalUrl: m,
1746
- originalPath: f,
1747
- thumbnailUrl: await H(_),
1748
- thumbnailPath: g
1751
+ }), h = `${u}/images/thumbnails/${c}.jpg`, g = W(a, h);
1752
+ return await ne(g, m, { contentType: "image/jpeg" }), {
1753
+ originalUrl: p,
1754
+ originalPath: d,
1755
+ thumbnailUrl: await ee(g),
1756
+ thumbnailPath: h
1749
1757
  };
1750
1758
  }
1751
1759
  async function a(e) {
1752
1760
  let n = U(t);
1753
- await Promise.all([V(W(n, e.originalPath)).catch(() => {}), V(W(n, e.thumbnailPath)).catch(() => {})]);
1761
+ await Promise.all([H(W(n, e.originalPath)).catch(() => {}), H(W(n, e.thumbnailPath)).catch(() => {})]);
1754
1762
  }
1755
1763
  return {
1756
1764
  uploadAttachment: n,
@@ -1761,7 +1769,7 @@ function Kt() {
1761
1769
  }
1762
1770
  //#endregion
1763
1771
  //#region src/composables/useMemiBoardUsers.ts
1764
- var qt = [
1772
+ var Yt = [
1765
1773
  {
1766
1774
  value: "admin",
1767
1775
  label: "관리자",
@@ -1778,13 +1786,13 @@ var qt = [
1778
1786
  description: "글과 댓글을 작성하고 본인이 작성한 콘텐츠만 수정·삭제할 수 있습니다."
1779
1787
  }
1780
1788
  ];
1781
- function Jt(e = {}) {
1782
- let t = () => Z(), r = F(), i = n(() => o(e.enabled) ?? !0 ? ge(r, t()) : null), a = A(i, { ssrKey: ye(t(), "users-management") });
1789
+ function Xt(e = {}) {
1790
+ let t = () => X(), r = P(), i = n(() => o(e.enabled) ?? !0 ? be(r, t()) : null), a = k(i, { ssrKey: Se(t(), "users-management") });
1783
1791
  async function s(e, n) {
1784
- if (!qt.some((e) => e.value === n)) throw Error("지원하지 않는 역할입니다.");
1785
- await D(_e(r, t(), e), {
1792
+ if (!Yt.some((e) => e.value === n)) throw Error("지원하지 않는 역할입니다.");
1793
+ await E(xe(r, t(), e), {
1786
1794
  role: n,
1787
- updatedAt: w()
1795
+ updatedAt: C()
1788
1796
  });
1789
1797
  }
1790
1798
  return {
@@ -1795,7 +1803,18 @@ function Jt(e = {}) {
1795
1803
  }
1796
1804
  //#endregion
1797
1805
  //#region src/data/versionHistory.ts
1798
- var Yt = [
1806
+ var Zt = [
1807
+ {
1808
+ version: "0.23.0",
1809
+ date: "2026-08-11",
1810
+ highlights: [
1811
+ "글·댓글·좋아요를 게시판 아래 중첩시키지 않고 memiBoardPosts/memiBoardComments/memiBoardLikes로 따로 모아요 — 보드 ID는 각 글의 필드로 들어가요",
1812
+ "작성자별 글 모음이 더 가벼워졌어요 — 여러 게시판을 뒤지는 대신 한 번에 찾아요",
1813
+ "\"내가 좋아요한 글\" 같은 기능을 나중에 붙이기 쉬워졌어요",
1814
+ "날짜에 마우스를 올려야 보이던 작성 시각을 이제 눌러서 볼 수 있어요 — 폰에서도 똑같이 동작해요"
1815
+ ],
1816
+ note: "기존 게시판 데이터와 호환되지 않아요 — 새로 설치하는 것처럼 다시 시작해야 해요."
1817
+ },
1799
1818
  {
1800
1819
  version: "0.21.1",
1801
1820
  date: "2026-08-07",
@@ -1928,7 +1947,7 @@ var Yt = [
1928
1947
  highlights: ["글쓰기·댓글·첨부·로그인 권한으로 게시판을 바로 시작할 수 있어요", "부적절한 글·댓글은 AI 검열로 막을 수 있어요"],
1929
1948
  note: "실제 보안은 호스트 Firebase 규칙 등록이 필요합니다."
1930
1949
  }
1931
- ], Xt = /* @__PURE__ */ t(((e, t) => {
1950
+ ], Qt = /* @__PURE__ */ t(((e, t) => {
1932
1951
  (function(n, r) {
1933
1952
  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();
1934
1953
  })(e, (function() {
@@ -2222,7 +2241,7 @@ var Yt = [
2222
2241
  return w(1e3 * e);
2223
2242
  }, w.en = b[y], w.Ls = b, w.p = {}, w;
2224
2243
  }));
2225
- })), Zt = /* @__PURE__ */ t(((e, t) => {
2244
+ })), $t = /* @__PURE__ */ t(((e, t) => {
2226
2245
  (function(n, r) {
2227
2246
  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();
2228
2247
  })(e, (function() {
@@ -2326,9 +2345,9 @@ var Yt = [
2326
2345
  };
2327
2346
  };
2328
2347
  }));
2329
- })), Qt = /* @__PURE__ */ t(((e, t) => {
2348
+ })), en = /* @__PURE__ */ t(((e, t) => {
2330
2349
  (function(n, r) {
2331
- typeof e == "object" && t !== void 0 ? t.exports = r(Xt()) : typeof define == "function" && define.amd ? define(["dayjs"], r) : (n = typeof globalThis < "u" ? globalThis : n || self).dayjs_locale_ko = r(n.dayjs);
2350
+ 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);
2332
2351
  })(e, (function(e) {
2333
2352
  function t(e) {
2334
2353
  return e && typeof e == "object" && "default" in e ? e : { default: e };
@@ -2376,31 +2395,31 @@ var Yt = [
2376
2395
  };
2377
2396
  return n.default.locale(r, null, !0), r;
2378
2397
  }));
2379
- })), $t = /* @__PURE__ */ e(Xt(), 1), en = /* @__PURE__ */ e(Zt(), 1);
2380
- Qt(), $t.default.extend(en.default), $t.default.locale("ko");
2381
- function tn(e) {
2398
+ })), tn = /* @__PURE__ */ e(Qt(), 1), nn = /* @__PURE__ */ e($t(), 1);
2399
+ en(), tn.default.extend(nn.default), tn.default.locale("ko");
2400
+ function rn(e) {
2382
2401
  return e?.toDate ? e.toDate().toLocaleDateString("ko-KR") : "";
2383
2402
  }
2384
- function nn(e, t = Date.now()) {
2385
- return e?.toDate ? (0, $t.default)(e.toDate()).from((0, $t.default)(t)) : "방금 전";
2403
+ function an(e, t = Date.now()) {
2404
+ return e?.toDate ? (0, tn.default)(e.toDate()).from((0, tn.default)(t)) : "방금 전";
2386
2405
  }
2387
- function rn(e) {
2388
- return e?.toDate ? (0, $t.default)(e.toDate()).format("YYYY년 M월 D일 HH:mm:ss") : "시간 확인 중";
2406
+ function on(e) {
2407
+ return e?.toDate ? (0, tn.default)(e.toDate()).format("YYYY년 M월 D일 HH:mm:ss") : "시간 확인 중";
2389
2408
  }
2390
- function an(e, t) {
2391
- let n = rn(e), r = e?.toMillis?.(), i = t?.toMillis?.();
2392
- return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${rn(t)}`];
2409
+ function sn(e, t) {
2410
+ let n = on(e), r = e?.toMillis?.(), i = t?.toMillis?.();
2411
+ return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${on(t)}`];
2393
2412
  }
2394
2413
  //#endregion
2395
2414
  //#region src/utils/renderMarkdown.ts
2396
2415
  function $(e) {
2397
2416
  return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2398
2417
  }
2399
- function on(e) {
2418
+ function cn(e) {
2400
2419
  let t = e;
2401
2420
  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;
2402
2421
  }
2403
- function sn(e) {
2422
+ function ln(e) {
2404
2423
  if (!e?.trim()) return "";
2405
2424
  let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = 0, i = !1, a = [], o = null, s = [];
2406
2425
  function c() {
@@ -2409,7 +2428,7 @@ function sn(e) {
2409
2428
  return;
2410
2429
  }
2411
2430
  let e = o;
2412
- n.push(`<${e}>${s.map((e) => `<li>${on(e)}</li>`).join("")}</${e}>`), o = null, s = [];
2431
+ n.push(`<${e}>${s.map((e) => `<li>${cn(e)}</li>`).join("")}</${e}>`), o = null, s = [];
2413
2432
  }
2414
2433
  for (; r < t.length;) {
2415
2434
  let e = t[r] ?? "", l = e.match(/^```(\w*)\s*$/);
@@ -2429,14 +2448,14 @@ function sn(e) {
2429
2448
  if (u) {
2430
2449
  c();
2431
2450
  let e = u[1].length;
2432
- n.push(`<h${e} class="font-semibold mt-4 mb-2">${on($(u[2]))}</h${e}>`), r++;
2451
+ n.push(`<h${e} class="font-semibold mt-4 mb-2">${cn($(u[2]))}</h${e}>`), r++;
2433
2452
  continue;
2434
2453
  }
2435
2454
  if (e.startsWith("> ")) {
2436
2455
  c();
2437
2456
  let e = [];
2438
2457
  for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push($((t[r] ?? "").slice(2))), r++;
2439
- n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${on(e.join("<br />"))}</blockquote>`);
2458
+ n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${cn(e.join("<br />"))}</blockquote>`);
2440
2459
  continue;
2441
2460
  }
2442
2461
  let d = e.match(/^[-*+]\s+(.+)$/);
@@ -2460,11 +2479,11 @@ function sn(e) {
2460
2479
  if (!e.trim() || /^(#{1,3}\s|```|[-*+]\s|\d+\.\s|> |(-{3,}|\*{3,})\s*$)/.test(e)) break;
2461
2480
  p.push($(e)), r++;
2462
2481
  }
2463
- n.push(`<p class="my-2 leading-relaxed">${on(p.join("<br />"))}</p>`);
2482
+ n.push(`<p class="my-2 leading-relaxed">${cn(p.join("<br />"))}</p>`);
2464
2483
  }
2465
2484
  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");
2466
2485
  }
2467
2486
  //#endregion
2468
- export { qt as BOARD_USER_ROLES, wt as COMMENT_BODY_MAX_LENGTH, Q as DEFAULT_BLOCK_BAN_DECAY_MS, Re as DEFAULT_BLOCK_BAN_THRESHOLD, st as DEFAULT_BOARDS, le as DEFAULT_BOARDS_COLLECTION, ct as DEFAULT_CATEGORIES, ue as DEFAULT_USERS_COLLECTION, Bt as EDITOR_IMAGE_MAX_BYTES, Vt as EDITOR_IMAGE_SOURCE_MAX_BYTES, we as asHttpUrl, pe as boardDoc, Ae as boardListOgDescription, ke as boardListOgTitle, me as boardPostBodyDoc, Y as boardPostCommentDoc, J as boardPostCommentsCol, q as boardPostDoc, he as boardPostLikeDoc, Oe as boardPostOgDescription, De as boardPostOgTitle, X as boardPostStorageFolder, K as boardPostsCol, G as boardSettingsDoc, ye as boardSsrKey, ve as boardStorageRoot, _e as boardUserDoc, ge as boardUsersCol, fe as boardsCol, ot as buildPostPreview, zt as compressImage, Se as configureMemiBoard, ze as effectiveModerationBlockCount, Ye as extractEditorImageUrls, Ie as fetchPublicListForSeo, Fe as fetchPublicPostForSeo, tn as formatDate, rn as formatFullDate, nn as formatRelativeDate, He as formatRestrictedUntilLabel, an as formatTimestampDetails, tt as hasBodyImage, et as hasBodyText, nt as isContentEmpty, mt as isFirestorePermissionDenied, Ve as isModerationWriteRestricted, Be as moderationWriteRestrictedUntilMs, je as normalizeBasePath, Qe as plainTextFromHtml, Ze as postNamespaceFromStoragePath, sn as renderMarkdownToHtml, de as resolveBoardPathConfig, Me as resolvePublicSeoDb, qe as slugify, Xe as storagePathFromDownloadUrl, $e as titleFromBody, Ee as toAbsoluteUrl, Te as toBoardOgImageUrl, Z as useBoardPathConfig, Ke as useMemiBoardAuth, Et as useMemiBoardComments, Ce as useMemiBoardConfig, Ot as useMemiBoardLikes, Rt as useMemiBoardModeration, yt as useMemiBoardPost, vt as useMemiBoardPostList, gt as useMemiBoardPosts, Dt as useMemiBoardReplies, dt as useMemiBoardSettings, Kt as useMemiBoardStorage, xt as useMemiBoardUserPosts, Jt as useMemiBoardUsers, kt as useMemiBoardViews, Yt as versionHistory, at as videoListCoverUrl, it as youtubeId };
2487
+ export { Yt as BOARD_USER_ROLES, Dt as COMMENT_BODY_MAX_LENGTH, Z as DEFAULT_BLOCK_BAN_DECAY_MS, Ve as DEFAULT_BLOCK_BAN_THRESHOLD, ut as DEFAULT_BOARDS, dt as DEFAULT_CATEGORIES, le as DEFAULT_COMMENTS_COLLECTION, ue as DEFAULT_LIKES_COLLECTION, ce as DEFAULT_POSTS_COLLECTION, de as DEFAULT_SETTINGS_COLLECTION, fe as DEFAULT_USERS_COLLECTION, Ht as EDITOR_IMAGE_MAX_BYTES, Ut as EDITOR_IMAGE_SOURCE_MAX_BYTES, De as asHttpUrl, Ne as boardListOgDescription, Me as boardListOgTitle, je as boardPostOgDescription, Ae as boardPostOgTitle, Se as boardSsrKey, xe as boardUserDoc, be as boardUsersCol, lt as buildPostPreview, J as commentDoc, ge as commentsCol, Q as compressImage, Te as configureMemiBoard, He as effectiveModerationBlockCount, Qe as extractEditorImageUrls, ze as fetchPublicListForSeo, Re as fetchPublicPostForSeo, rn as formatDate, on as formatFullDate, an as formatRelativeDate, Ge as formatRestrictedUntilLabel, sn as formatTimestampDetails, it as hasBodyImage, rt as hasBodyText, at as isContentEmpty, _t as isFirestorePermissionDenied, We as isModerationWriteRestricted, ye as likeDoc, ve as likeDocId, _e as likesCol, Ue as moderationWriteRestrictedUntilMs, Pe as normalizeBasePath, tt as plainTextFromHtml, he as postBodyDoc, q as postDoc, et as postNamespaceFromStoragePath, Y as postStorageFolder, K as postsCol, ln as renderMarkdownToHtml, pe as resolveBoardPathConfig, Fe as resolvePublicSeoDb, me as settingsCol, G as settingsDoc, Xe as slugify, $e as storagePathFromDownloadUrl, nt as titleFromBody, ke as toAbsoluteUrl, Oe as toBoardOgImageUrl, X as useBoardPathConfig, Ye as useMemiBoardAuth, kt as useMemiBoardComments, Ee as useMemiBoardConfig, jt as useMemiBoardLikes, Vt as useMemiBoardModeration, St as useMemiBoardPost, xt as useMemiBoardPostList, yt as useMemiBoardPosts, At as useMemiBoardReplies, mt as useMemiBoardSettings, Jt as useMemiBoardStorage, wt as useMemiBoardUserPosts, Xt as useMemiBoardUsers, Mt as useMemiBoardViews, Zt as versionHistory, ct as videoListCoverUrl, st as youtubeId };
2469
2488
 
2470
2489
  //# sourceMappingURL=index.js.map