memi-board 0.18.0 → 0.19.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,23 +1,23 @@
1
1
  import { computed as e, onScopeDispose as t, reactive as n, ref as r, toValue as i, watch as a } from "vue";
2
- import { useCollection as o, useCurrentUser as s, useDocument as c, useFirebaseApp as l, useFirebaseAuth as u, useFirestore as d } from "vuefire";
3
- import { collection as f, deleteDoc as p, deleteField as m, doc as h, documentId as g, endBefore as _, getDoc as v, getDocs as y, increment as b, limit as x, limitToLast as S, onSnapshot as C, orderBy as w, query as T, runTransaction as E, serverTimestamp as D, setDoc as O, startAfter as k, updateDoc as A, where as j, writeBatch as M } from "firebase/firestore";
2
+ import { collection as o, deleteDoc as s, deleteField as c, doc as l, documentId as u, endBefore as d, getDoc as f, getDocs as p, increment as m, limit as h, limitToLast as g, onSnapshot as _, orderBy as v, query as y, runTransaction as b, serverTimestamp as x, setDoc as S, startAfter as C, updateDoc as w, where as T, writeBatch as E } from "firebase/firestore";
3
+ import { useCollection as D, useCurrentUser as O, useDocument as k, useFirebaseApp as A, useFirebaseAuth as j, useFirestore as M } from "vuefire";
4
4
  import { GoogleAuthProvider as N, OAuthProvider as P, createUserWithEmailAndPassword as F, signInWithEmailAndPassword as I, signInWithPopup as L, signOut as R, updateProfile as ee } from "firebase/auth";
5
5
  import { deleteObject as z, getDownloadURL as B, getStorage as V, listAll as te, ref as H, uploadBytes as ne, uploadBytesResumable as re } from "firebase/storage";
6
6
  import { GoogleAIBackend as ie, Schema as U, getAI as ae, getGenerativeModel as oe } from "firebase/ai";
7
7
  //#region \0rolldown/runtime.js
8
- var se = Object.create, ce = Object.defineProperty, le = Object.getOwnPropertyDescriptor, ue = Object.getOwnPropertyNames, de = Object.getPrototypeOf, fe = Object.prototype.hasOwnProperty, W = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t.exports), pe = (e, t, n, r) => {
8
+ var se = Object.create, ce = Object.defineProperty, le = Object.getOwnPropertyDescriptor, ue = Object.getOwnPropertyNames, de = Object.getPrototypeOf, fe = Object.prototype.hasOwnProperty, pe = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t.exports), me = (e, t, n, r) => {
9
9
  if (t && typeof t == "object" || typeof t == "function") for (var i = ue(t), a = 0, o = i.length, s; a < o; a++) s = i[a], !fe.call(e, s) && s !== n && ce(e, s, {
10
10
  get: ((e) => t[e]).bind(null, s),
11
11
  enumerable: !(r = le(t, s)) || r.enumerable
12
12
  });
13
13
  return e;
14
- }, me = (e, t, n) => (n = e == null ? {} : se(de(e)), pe(t || !e || !e.__esModule || !fe.call(e, "default") ? ce(n, "default", {
14
+ }, he = (e, t, n) => (n = e == null ? {} : se(de(e)), me(t || !e || !e.__esModule || !fe.call(e, "default") ? ce(n, "default", {
15
15
  value: e,
16
16
  enumerable: !0
17
- }) : n, e)), he = "__MEMI_BOARD_CONFIG__";
18
- function ge() {
17
+ }) : n, e)), ge = "__MEMI_BOARD_CONFIG__";
18
+ function _e() {
19
19
  let e = globalThis;
20
- return e[he] || (e[he] = n({
20
+ return e[ge] || (e[ge] = n({
21
21
  collectionPrefix: "memiBoard",
22
22
  auth: { providers: ["google", "apple"] },
23
23
  moderation: {
@@ -28,20 +28,185 @@ function ge() {
28
28
  localBlocklist: [],
29
29
  blockBanThreshold: 3,
30
30
  blockBanDecayMs: 864e5
31
+ },
32
+ seo: {
33
+ enabled: !0,
34
+ siteName: "Board",
35
+ siteUrl: "",
36
+ defaultOgImage: "",
37
+ basePath: "/board"
31
38
  }
32
- })), e[he];
39
+ })), e[ge];
40
+ }
41
+ function ve(e) {
42
+ let t = _e();
43
+ e.collectionPrefix && (t.collectionPrefix = e.collectionPrefix), e.auth && Object.assign(t.auth, e.auth), e.moderation && Object.assign(t.moderation, e.moderation), e.seo && Object.assign(t.seo, e.seo);
44
+ }
45
+ function W() {
46
+ return _e();
47
+ }
48
+ //#endregion
49
+ //#region src/utils/boardSeo.ts
50
+ function ye(e) {
51
+ if (typeof e != "string") return null;
52
+ let t = e.trim();
53
+ return /^https?:\/\//i.test(t) ? t : null;
54
+ }
55
+ function be(e, t, n = "") {
56
+ let r = t.replace(/\/$/, ""), i = e.trim();
57
+ return i ? /^https?:\/\//i.test(i) ? i : `${r}${i.startsWith("/") ? i : `/${i}`}` : n ? be(n, t) : r;
58
+ }
59
+ function xe(e, t, n) {
60
+ let r = e.trim() || "게시글", i = t?.trim(), a = n.trim() || "Board";
61
+ return i ? `${r} · ${i} | ${a}` : `${r} | ${a}`;
62
+ }
63
+ function Se(e) {
64
+ let t = [];
65
+ 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);
66
+ }
67
+ function Ce(e, t) {
68
+ let n = t.trim() || "Board", r = e?.trim();
69
+ return r && r !== "전체" ? `${r} 게시판 | ${n}` : `게시판 | ${n}`;
70
+ }
71
+ function we(e) {
72
+ if (e.description?.trim()) return e.description.trim().slice(0, 200);
73
+ let t = e.categoryLabel?.trim(), n = t && t !== "전체" ? `${t} 게시판 — 소식을 확인하세요.` : "게시판 — 공지와 이야기를 확인하세요.", r = (e.recentTitles || []).filter(Boolean).slice(0, 3);
74
+ return r.length ? `${n} ${r.map((e) => `「${e}」`).join(" ")}`.slice(0, 200) : n.slice(0, 200);
75
+ }
76
+ function Te(e) {
77
+ let t = (e || "/board").trim() || "/board";
78
+ return (t.startsWith("/") ? t : `/${t}`).replace(/\/$/, "") || "/board";
79
+ }
80
+ //#endregion
81
+ //#region src/composables/fetchPublicSeo.ts
82
+ function Ee() {
83
+ return {
84
+ db: M(),
85
+ prefix: W().collectionPrefix
86
+ };
87
+ }
88
+ function De(e) {
89
+ return o(e.db, `${e.prefix}Posts`);
90
+ }
91
+ function Oe(e, t) {
92
+ return l(e.db, `${e.prefix}Settings`, "config", "categories", t);
93
+ }
94
+ async function ke(e, t) {
95
+ try {
96
+ let n = await f(Oe(e, t));
97
+ if (!n.exists()) return {
98
+ label: t,
99
+ description: "",
100
+ hidden: !1
101
+ };
102
+ let r = n.data() ?? {};
103
+ return {
104
+ label: typeof r.label == "string" && r.label.trim() ? r.label.trim() : t,
105
+ description: typeof r.description == "string" ? r.description.trim() : "",
106
+ hidden: r.visibility === "hidden"
107
+ };
108
+ } catch {
109
+ return {
110
+ label: t,
111
+ description: "",
112
+ hidden: !1
113
+ };
114
+ }
115
+ }
116
+ function Ae(e) {
117
+ if (e && typeof e == "object" && "toDate" in e) try {
118
+ let t = e.toDate();
119
+ return t instanceof Date ? t.toISOString() : null;
120
+ } catch {
121
+ return null;
122
+ }
123
+ return null;
124
+ }
125
+ async function je(e, t, n) {
126
+ let r = e.trim(), i = t.trim();
127
+ if (!r || !i) return null;
128
+ let a = n ?? Ee(), o = await ke(a, r);
129
+ if (o.hidden) return null;
130
+ try {
131
+ let e = (await p(y(De(a), T("category", "==", r), T("slug", "==", i), T("listed", "==", !0), h(1)))).docs[0];
132
+ if (!e) return null;
133
+ let t = e.data() ?? {}, n = typeof t.title == "string" ? t.title.trim() : "";
134
+ return n ? {
135
+ id: e.id,
136
+ slug: typeof t.slug == "string" ? t.slug : i,
137
+ category: r,
138
+ categoryLabel: o.label,
139
+ title: n,
140
+ summary: typeof t.summary == "string" ? t.summary.trim().slice(0, 200) : "",
141
+ previewImage: ye(t.previewImage),
142
+ authorName: typeof t.authorName == "string" && t.authorName.trim() ? t.authorName.trim() : null,
143
+ createdAt: Ae(t.createdAt)
144
+ } : null;
145
+ } catch (e) {
146
+ return import.meta.dev && console.warn("[memi-board] fetchPublicPostForSeo failed", e), null;
147
+ }
33
148
  }
34
- function _e(e) {
35
- let t = ge();
36
- e.collectionPrefix && (t.collectionPrefix = e.collectionPrefix), e.auth && Object.assign(t.auth, e.auth), e.moderation && Object.assign(t.moderation, e.moderation);
149
+ async function Me(e, t) {
150
+ let n = e?.trim() || null, r = t ?? Ee();
151
+ if (n) {
152
+ let e = await ke(r, n);
153
+ if (e.hidden) return {
154
+ kind: "category",
155
+ category: n,
156
+ categoryLabel: e.label,
157
+ description: "",
158
+ ogImage: null,
159
+ recentTitles: []
160
+ };
161
+ let { recentTitles: t, ogImage: i } = await Ne(r, n);
162
+ return {
163
+ kind: "category",
164
+ category: n,
165
+ categoryLabel: e.label,
166
+ description: e.description || `${e.label} 게시판`,
167
+ ogImage: i,
168
+ recentTitles: t
169
+ };
170
+ }
171
+ let { recentTitles: i, ogImage: a } = await Ne(r, null);
172
+ return {
173
+ kind: "all",
174
+ category: null,
175
+ categoryLabel: "전체",
176
+ description: "게시판 — 공지와 커뮤니티 소식을 확인하세요.",
177
+ ogImage: a,
178
+ recentTitles: i
179
+ };
37
180
  }
38
- function G() {
39
- return ge();
181
+ async function Ne(e, t) {
182
+ let n = [], r = null;
183
+ try {
184
+ let i = t ? [
185
+ T("category", "==", t),
186
+ T("listed", "==", !0),
187
+ v("createdAt", "desc"),
188
+ h(12)
189
+ ] : [
190
+ T("listed", "==", !0),
191
+ v("createdAt", "desc"),
192
+ h(12)
193
+ ], a = await p(y(De(e), ...i));
194
+ for (let e of a.docs) {
195
+ let t = e.data() ?? {}, i = typeof t.title == "string" ? t.title.trim() : "";
196
+ i && n.length < 5 && n.push(i), r ||= ye(t.previewImage);
197
+ }
198
+ } catch (e) {
199
+ import.meta.dev && console.warn("[memi-board] loadRecentListed failed", e);
200
+ }
201
+ return {
202
+ recentTitles: n,
203
+ ogImage: r
204
+ };
40
205
  }
41
206
  //#endregion
42
207
  //#region src/utils/moderation-strike.ts
43
- var ve = 3, K = 864e5;
44
- function ye(e, t, n = Date.now(), r = K) {
208
+ var Pe = 3, G = 864e5;
209
+ function Fe(e, t, n = Date.now(), r = G) {
45
210
  let i = Math.max(0, Math.floor(Number(e) || 0));
46
211
  if (i <= 0) return 0;
47
212
  let a = typeof t == "number" && Number.isFinite(t) ? t : null;
@@ -49,7 +214,7 @@ function ye(e, t, n = Date.now(), r = K) {
49
214
  let o = Math.floor((n - a) / r);
50
215
  return Math.max(0, i - o);
51
216
  }
52
- function q(e, t, n = Date.now(), r = 3, i = K) {
217
+ function K(e, t, n = Date.now(), r = 3, i = G) {
53
218
  let a = Math.max(0, Math.floor(Number(e) || 0));
54
219
  if (a < r) return null;
55
220
  let o = typeof t == "number" && Number.isFinite(t) ? t : null;
@@ -57,17 +222,17 @@ function q(e, t, n = Date.now(), r = 3, i = K) {
57
222
  let s = o + (a - (r - 1)) * i;
58
223
  return s > n ? s : null;
59
224
  }
60
- function be(e, t, n = Date.now(), r = 3, i = K) {
61
- return q(e, t, n, r, i) != null;
225
+ function Ie(e, t, n = Date.now(), r = 3, i = G) {
226
+ return K(e, t, n, r, i) != null;
62
227
  }
63
- function xe(e, t = Date.now()) {
228
+ function Le(e, t = Date.now()) {
64
229
  let n = Math.max(0, e - t), r = Math.ceil(n / 36e5);
65
230
  if (r <= 0) return "곧 해제";
66
231
  if (r < 24) return `약 ${r}시간 후 해제`;
67
232
  let i = Math.floor(r / 24), a = r % 24;
68
233
  return a === 0 ? `약 ${i}일 후 해제` : `약 ${i}일 ${a}시간 후 해제`;
69
234
  }
70
- function Se(e) {
235
+ function Re(e) {
71
236
  if (e == null) return null;
72
237
  if (typeof e == "number" && Number.isFinite(e)) return e;
73
238
  if (typeof e == "object" && e && "toMillis" in e && typeof e.toMillis == "function") return e.toMillis();
@@ -79,117 +244,117 @@ function Se(e) {
79
244
  }
80
245
  //#endregion
81
246
  //#region src/composables/useMemiBoardAuth.ts
82
- var Ce = "__MEMI_BOARD_AUTH_STATE__";
83
- function we() {
247
+ var q = "__MEMI_BOARD_AUTH_STATE__";
248
+ function ze() {
84
249
  let e = globalThis;
85
- return e[Ce] || (e[Ce] = {
250
+ return e[q] || (e[q] = {
86
251
  role: r("user"),
87
252
  rolePending: r(!0),
88
253
  rawBlockCount: r(0),
89
254
  blockAtMs: r(null),
90
255
  watchStarted: !1
91
- }), e[Ce];
256
+ }), e[q];
92
257
  }
93
258
  function J() {
94
- let t = G(), n = we(), r = d(), i = u(), o = s(), { role: c, rolePending: l, rawBlockCount: f, blockAtMs: p } = n;
95
- function m() {
259
+ let t = W(), n = ze(), r = M(), i = j(), o = O(), { role: s, rolePending: c, rawBlockCount: u, blockAtMs: d } = n;
260
+ function p() {
96
261
  let e = t.moderation?.blockBanThreshold;
97
262
  return e === 0 ? 0 : typeof e == "number" && e > 0 ? Math.floor(e) : 3;
98
263
  }
99
- function g() {
264
+ function h() {
100
265
  let e = t.moderation?.blockBanDecayMs;
101
- return typeof e == "number" && e > 0 ? e : K;
266
+ return typeof e == "number" && e > 0 ? e : G;
102
267
  }
103
- function _(e) {
104
- return h(r, `${t.collectionPrefix}Users`, e);
268
+ function g(e) {
269
+ return l(r, `${t.collectionPrefix}Users`, e);
105
270
  }
106
- function y(e) {
271
+ function _(e) {
107
272
  if (!e) {
108
- f.value = 0, p.value = null;
273
+ u.value = 0, d.value = null;
109
274
  return;
110
275
  }
111
- f.value = Math.max(0, Math.floor(Number(e.moderationBlockCount) || 0)), p.value = Se(e.moderationBlockAt);
276
+ u.value = Math.max(0, Math.floor(Number(e.moderationBlockCount) || 0)), d.value = Re(e.moderationBlockAt);
112
277
  }
113
- async function x(e) {
114
- let t = _(e.uid), n = await v(t);
115
- if (!n.exists()) return await O(t, {
278
+ async function v(e) {
279
+ let t = g(e.uid), n = await f(t);
280
+ if (!n.exists()) return await S(t, {
116
281
  role: "user",
117
282
  displayName: e.displayName,
118
283
  email: e.email,
119
284
  photoURL: e.photoURL,
120
- updatedAt: D()
121
- }), y({ moderationBlockCount: 0 }), "user";
285
+ updatedAt: x()
286
+ }), _({ moderationBlockCount: 0 }), "user";
122
287
  let r = n.data();
123
- return y(r), await A(t, {
288
+ return _(r), await w(t, {
124
289
  displayName: e.displayName,
125
290
  email: e.email,
126
291
  photoURL: e.photoURL,
127
- updatedAt: D()
292
+ updatedAt: x()
128
293
  }), [
129
294
  "admin",
130
295
  "staff",
131
296
  "user"
132
297
  ].includes(String(r.role)) ? r.role : "user";
133
298
  }
134
- async function S() {
299
+ async function y() {
135
300
  let e = o.value?.uid;
136
301
  if (!e) {
137
- y(void 0);
302
+ _(void 0);
138
303
  return;
139
304
  }
140
- let t = await v(_(e));
141
- y(t.exists() ? t.data() : void 0);
305
+ let t = await f(g(e));
306
+ _(t.exists() ? t.data() : void 0);
142
307
  }
143
308
  n.watchStarted || (n.watchStarted = !0, a(o, async (e) => {
144
- if (l.value = !0, !e) {
145
- c.value = "user", y(void 0), l.value = !1;
309
+ if (c.value = !0, !e) {
310
+ s.value = "user", _(void 0), c.value = !1;
146
311
  return;
147
312
  }
148
313
  try {
149
- c.value = await x(e);
314
+ s.value = await v(e);
150
315
  } catch {
151
- c.value = "user";
316
+ s.value = "user";
152
317
  } finally {
153
- l.value = !1;
318
+ c.value = !1;
154
319
  }
155
320
  }, { immediate: !0 }));
156
- let C = e(() => !!o.value), w = e(() => c.value === "admin"), T = e(() => c.value === "staff"), E = e(() => w.value || T.value), k = e(() => ye(f.value, p.value, Date.now(), g())), j = e(() => {
157
- let e = m();
158
- return e <= 0 ? !1 : be(f.value, p.value, Date.now(), e, g());
159
- }), M = e(() => {
160
- let e = m();
321
+ let b = e(() => !!o.value), C = e(() => s.value === "admin"), T = e(() => s.value === "staff"), E = e(() => C.value || T.value), D = e(() => Fe(u.value, d.value, Date.now(), h())), k = e(() => {
322
+ let e = p();
323
+ return e <= 0 ? !1 : Ie(u.value, d.value, Date.now(), e, h());
324
+ }), A = e(() => {
325
+ let e = p();
161
326
  if (e <= 0) return null;
162
- let t = q(f.value, p.value, Date.now(), e, g());
163
- return t == null ? null : xe(t);
327
+ let t = K(u.value, d.value, Date.now(), e, h());
328
+ return t == null ? null : Le(t);
164
329
  }), z = e(() => {
165
- if (!j.value) return null;
166
- let e = M.value;
330
+ if (!k.value) return null;
331
+ let e = A.value;
167
332
  return e ? `콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요. (${e})` : "콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요.";
168
333
  });
169
334
  async function B() {
170
- let e = o.value?.uid, t = m();
335
+ let e = o.value?.uid, t = p();
171
336
  if (!e || t <= 0) return {
172
337
  effectiveCount: 0,
173
338
  restricted: !1,
174
339
  messageSuffix: ""
175
340
  };
176
- let n = _(e), r = await v(n);
177
- r.exists() ? await A(n, {
178
- moderationBlockCount: b(1),
179
- moderationBlockAt: D(),
180
- updatedAt: D()
181
- }) : await O(n, {
341
+ let n = g(e), r = await f(n);
342
+ r.exists() ? await w(n, {
343
+ moderationBlockCount: m(1),
344
+ moderationBlockAt: x(),
345
+ updatedAt: x()
346
+ }) : await S(n, {
182
347
  role: "user",
183
348
  displayName: o.value?.displayName ?? null,
184
349
  moderationBlockCount: 1,
185
- moderationBlockAt: D(),
186
- updatedAt: D()
350
+ moderationBlockAt: x(),
351
+ updatedAt: x()
187
352
  });
188
353
  let i = (r.exists() ? Math.max(0, Math.floor(Number(r.data().moderationBlockCount) || 0)) : 0) + 1, a = Date.now();
189
- f.value = i, p.value = a;
190
- let s = ye(i, a, a, g()), c = be(i, a, a, t, g()), l = ` (경고 ${s}/${t})`;
354
+ u.value = i, d.value = a;
355
+ let s = Fe(i, a, a, h()), c = Ie(i, a, a, t, h()), l = ` (경고 ${s}/${t})`;
191
356
  if (c) {
192
- let e = q(i, a, a, t, g()), n = e == null ? null : xe(e, a);
357
+ let e = K(i, a, a, t, h()), n = e == null ? null : Le(e, a);
193
358
  l = n ? ` 경고 ${s}회가 되어 글·댓글 작성이 제한됩니다 (${n}).` : ` 경고 ${s}회가 되어 글·댓글 작성이 제한됩니다.`;
194
359
  }
195
360
  return {
@@ -220,24 +385,24 @@ function J() {
220
385
  await R(V());
221
386
  }
222
387
  function U(e) {
223
- return C.value && (o.value?.uid === e.authorUid || E.value);
388
+ return b.value && (o.value?.uid === e.authorUid || E.value);
224
389
  }
225
390
  function ae(e) {
226
- return C.value && o.value?.uid === e.authorUid;
391
+ return b.value && o.value?.uid === e.authorUid;
227
392
  }
228
393
  function oe(e) {
229
- return C.value && (o.value?.uid === e.authorUid || E.value);
394
+ return b.value && (o.value?.uid === e.authorUid || E.value);
230
395
  }
231
396
  return {
232
397
  user: o,
233
- isSignedIn: C,
234
- isAdmin: w,
398
+ isSignedIn: b,
399
+ isAdmin: C,
235
400
  isStaff: T,
236
401
  canManageContent: E,
237
- rolePending: l,
238
- moderationBlockCount: k,
239
- isWriteRestricted: j,
240
- restrictedUntilLabel: M,
402
+ rolePending: c,
403
+ moderationBlockCount: D,
404
+ isWriteRestricted: k,
405
+ restrictedUntilLabel: A,
241
406
  restrictedMessage: z,
242
407
  signInWithGoogle: te,
243
408
  signInWithApple: H,
@@ -249,20 +414,20 @@ function J() {
249
414
  canEditComment: ae,
250
415
  canDeleteComment: oe,
251
416
  recordContentModerationBlock: B,
252
- refreshBoardUser: S
417
+ refreshBoardUser: y
253
418
  };
254
419
  }
255
420
  //#endregion
256
421
  //#region src/utils/slugify.ts
257
- function Te(e) {
422
+ function Be(e) {
258
423
  return e.trim().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9가-힣ㄱ-ㅎㅏ-ㅣ-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
259
424
  }
260
- function Ee(e, t = 60) {
425
+ function Ve(e, t = 60) {
261
426
  return e.replace(/[^a-zA-Z0-9가-힣ㄱ-ㅎ._-]/g, "-").slice(0, t);
262
427
  }
263
428
  //#endregion
264
429
  //#region src/utils/extractEditorImageUrls.ts
265
- function De(e) {
430
+ function He(e) {
266
431
  if (!e) return [];
267
432
  let t = /* @__PURE__ */ new Set();
268
433
  for (let n of e.matchAll(/!\[[^\]]*]\((https?:\/\/[^)\s]+)\)/g)) n[1] && t.add(n[1].replaceAll("&amp;", "&"));
@@ -271,7 +436,7 @@ function De(e) {
271
436
  }
272
437
  //#endregion
273
438
  //#region src/utils/storagePath.ts
274
- function Oe(e) {
439
+ function Ue(e) {
275
440
  try {
276
441
  let t = e.match(/\/o\/([^?]+)/);
277
442
  return t?.[1] ? decodeURIComponent(t[1]) : null;
@@ -279,29 +444,29 @@ function Oe(e) {
279
444
  return null;
280
445
  }
281
446
  }
282
- function ke(e) {
447
+ function We(e) {
283
448
  return e.match(/(?:^|\/)posts\/([^/]+)\//)?.[1] ?? null;
284
449
  }
285
450
  //#endregion
286
451
  //#region src/utils/postPreview.ts
287
- function Ae(e) {
452
+ function Ge(e) {
288
453
  return e.replace(/&amp;/g, "&").replace(/&quot;/g, "\"").replace(/&#39;/g, "'");
289
454
  }
290
- function je(e) {
455
+ function Ke(e) {
291
456
  if (e) try {
292
- let t = new URL(Ae(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
457
+ let t = new URL(Ge(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
293
458
  if (n === "youtu.be") return t.pathname.split("/").filter(Boolean)[0];
294
459
  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;
295
460
  } catch {
296
461
  return;
297
462
  }
298
463
  }
299
- function Me(e) {
300
- let t = je(e.videoUrl);
464
+ function qe(e) {
465
+ let t = Ke(e.videoUrl);
301
466
  return t ? `https://i.ytimg.com/vi/${t}/hqdefault.jpg` : e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
302
467
  }
303
- function Ne(e, t = []) {
304
- 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 ? Ae(r[1]) : a, s = o ? je(o) : void 0, c = n ? Ae(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
468
+ function Je(e, t = []) {
469
+ 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 ? Ge(r[1]) : a, s = o ? Ke(o) : void 0, c = n ? Ge(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
305
470
  return {
306
471
  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),
307
472
  ...c ? { previewImage: c } : {},
@@ -310,7 +475,7 @@ function Ne(e, t = []) {
310
475
  }
311
476
  //#endregion
312
477
  //#region src/composables/useMemiBoardSettings.ts
313
- var Pe = [
478
+ var Ye = [
314
479
  {
315
480
  id: "free",
316
481
  label: "자유",
@@ -339,49 +504,49 @@ var Pe = [
339
504
  order: 2
340
505
  }
341
506
  ];
342
- function Fe(e) {
507
+ function Xe(e) {
343
508
  return e === "hidden" ? "hidden" : "public";
344
509
  }
345
- function Ie() {
346
- let t = G(), n = d(), { isSignedIn: r } = J(), i = e(() => h(n, `${t.collectionPrefix}Settings`, "config")), a = e(() => f(n, `${t.collectionPrefix}Settings`, "config", "categories")), s = e(() => T(a.value, w("order", "asc"))), { data: c, pending: l } = o(s, { ssrKey: `${t.collectionPrefix}Settings/config/categories` }), u = e(() => c.value.map((e, t) => ({
510
+ function Ze() {
511
+ let t = W(), n = M(), { isSignedIn: r } = J(), i = e(() => l(n, `${t.collectionPrefix}Settings`, "config")), a = e(() => o(n, `${t.collectionPrefix}Settings`, "config", "categories")), c = e(() => y(a.value, v("order", "asc"))), { data: u, pending: d } = D(c, { ssrKey: `${t.collectionPrefix}Settings/config/categories` }), f = e(() => u.value.map((e, t) => ({
347
512
  ...e,
348
513
  id: e.id,
349
514
  order: typeof e.order == "number" ? e.order : t,
350
515
  description: e.description ?? e.desc ?? "",
351
- visibility: Fe(e.visibility),
516
+ visibility: Xe(e.visibility),
352
517
  listView: e.listView ?? "default",
353
518
  writeRole: e.writeRole ?? "user",
354
519
  commentWriteRole: e.commentWriteRole ?? "user",
355
520
  allowedStaffUids: e.allowedStaffUids ?? []
356
- }))), m = e(() => u.value.filter((e) => e.visibility !== "hidden"));
521
+ }))), m = e(() => f.value.filter((e) => e.visibility !== "hidden"));
357
522
  function g(e) {
358
- if (e) return u.value.find((t) => t.id === e)?.label ?? e;
523
+ if (e) return f.value.find((t) => t.id === e)?.label ?? e;
359
524
  }
360
525
  function _(e) {
361
- if (e) return u.value.find((t) => t.id === e)?.description?.trim() || void 0;
362
- }
363
- function v(e) {
364
- return e ? u.value.find((t) => t.id === e)?.visibility ?? "public" : "public";
526
+ if (e) return f.value.find((t) => t.id === e)?.description?.trim() || void 0;
365
527
  }
366
528
  function b(e) {
367
- return v(e) === "hidden";
529
+ return e ? f.value.find((t) => t.id === e)?.visibility ?? "public" : "public";
530
+ }
531
+ function w(e) {
532
+ return b(e) === "hidden";
368
533
  }
369
- async function S(e, r) {
370
- let i = f(n, `${t.collectionPrefix}Posts`), a;
534
+ async function O(e, r) {
535
+ let i = o(n, `${t.collectionPrefix}Posts`), a;
371
536
  for (;;) {
372
- let t = await y(T(i, j("category", "==", e), w("__name__"), ...a ? [k(a)] : [], x(400)));
537
+ let t = await p(y(i, T("category", "==", e), v("__name__"), ...a ? [C(a)] : [], h(400)));
373
538
  if (t.empty) break;
374
- let o = M(n);
539
+ let o = E(n);
375
540
  for (let e of t.docs) o.update(e.ref, { listed: r });
376
541
  if (await o.commit(), a = t.docs[t.docs.length - 1], t.docs.length < 400) break;
377
542
  }
378
543
  }
379
- async function C(e, t = e.order ?? 0) {
544
+ async function k(e, t = e.order ?? 0) {
380
545
  if (!r.value) throw Error("로그인이 필요합니다.");
381
546
  let n = e.id.trim(), o = e.label.trim();
382
547
  if (!n || !o) throw Error("카테고리 ID와 이름을 입력해 주세요.");
383
- let s = Fe(e.visibility), c = u.value.find((e) => e.id === n)?.visibility ?? "public";
384
- await O(i.value, { updatedAt: D() }, { merge: !0 }), await O(h(a.value, n), {
548
+ let s = Xe(e.visibility), c = f.value.find((e) => e.id === n)?.visibility ?? "public";
549
+ await S(i.value, { updatedAt: x() }, { merge: !0 }), await S(l(a.value, n), {
385
550
  label: o,
386
551
  description: (e.description ?? "").trim(),
387
552
  visibility: s,
@@ -390,19 +555,19 @@ function Ie() {
390
555
  commentWriteRole: e.commentWriteRole ?? "user",
391
556
  allowedStaffUids: e.allowedStaffUids ?? [],
392
557
  order: t,
393
- updatedAt: D()
394
- }, { merge: !0 }), (c !== s || s === "public") && await S(n, s !== "hidden");
558
+ updatedAt: x()
559
+ }, { merge: !0 }), (c !== s || s === "public") && await O(n, s !== "hidden");
395
560
  }
396
- async function E(e) {
561
+ async function A(e) {
397
562
  if (!r.value) throw Error("로그인이 필요합니다.");
398
- let t = new Map(u.value.map((e) => [e.id, e.visibility ?? "public"])), o = M(n);
399
- o.set(i.value, { updatedAt: D() }, { merge: !0 });
563
+ let t = new Map(f.value.map((e) => [e.id, e.visibility ?? "public"])), o = E(n);
564
+ o.set(i.value, { updatedAt: x() }, { merge: !0 });
400
565
  let s = [];
401
566
  e.forEach((e, n) => {
402
567
  let r = e.id.trim(), i = e.label.trim();
403
568
  if (!r || !i) throw Error("카테고리 ID와 이름을 입력해 주세요.");
404
- let c = Fe(e.visibility);
405
- o.set(h(a.value, r), {
569
+ let c = Xe(e.visibility);
570
+ o.set(l(a.value, r), {
406
571
  label: i,
407
572
  description: (e.description ?? "").trim(),
408
573
  visibility: c,
@@ -411,17 +576,17 @@ function Ie() {
411
576
  commentWriteRole: e.commentWriteRole ?? "user",
412
577
  allowedStaffUids: e.allowedStaffUids ?? [],
413
578
  order: n,
414
- updatedAt: D()
579
+ updatedAt: x()
415
580
  }, { merge: !0 }), (t.get(r) ?? "public") !== c && s.push({
416
581
  id: r,
417
582
  listed: c !== "hidden"
418
583
  });
419
584
  }), await o.commit();
420
- for (let e of s) await S(e.id, e.listed);
585
+ for (let e of s) await O(e.id, e.listed);
421
586
  }
422
- async function A(e) {
587
+ async function j(e) {
423
588
  if (!r.value) throw Error("로그인이 필요합니다.");
424
- await p(h(a.value, e));
589
+ await s(l(a.value, e));
425
590
  }
426
591
  async function N() {
427
592
  return Promise.resolve();
@@ -430,66 +595,66 @@ function Ie() {
430
595
  let t = e.trim();
431
596
  if (!t) throw Error("카테고리 이름을 입력해 주세요.");
432
597
  if (!r.value) throw Error("로그인이 필요합니다.");
433
- let n = u.value.find((e) => e.label === t);
598
+ let n = f.value.find((e) => e.label === t);
434
599
  if (n) return n.id;
435
- let i = Te(t) || `cat-${Date.now()}`, a = i, o = 2, s = new Set(u.value.map((e) => e.id));
600
+ let i = Be(t) || `cat-${Date.now()}`, a = i, o = 2, s = new Set(f.value.map((e) => e.id));
436
601
  for (; s.has(a);) a = `${i}-${o++}`;
437
- return await C({
602
+ return await k({
438
603
  id: a,
439
604
  label: t,
440
605
  visibility: "public",
441
606
  listView: "default",
442
607
  writeRole: "user",
443
608
  commentWriteRole: "user",
444
- order: u.value.length
609
+ order: f.value.length
445
610
  }), a;
446
611
  }
447
612
  return {
448
- categories: u,
613
+ categories: f,
449
614
  publicCategories: m,
450
615
  categoryLabel: g,
451
616
  categoryDescription: _,
452
- categoryVisibility: v,
453
- isCategoryHidden: b,
454
- settingsPending: l,
617
+ categoryVisibility: b,
618
+ isCategoryHidden: w,
619
+ settingsPending: d,
455
620
  ensureSettings: N,
456
621
  addCategory: P,
457
- saveCategory: C,
458
- saveCategories: E,
459
- deleteCategory: A,
460
- DEFAULT_CATEGORIES: Pe
622
+ saveCategory: k,
623
+ saveCategories: A,
624
+ deleteCategory: j,
625
+ DEFAULT_CATEGORIES: Ye
461
626
  };
462
627
  }
463
628
  //#endregion
464
629
  //#region src/composables/useMemiBoardPosts.ts
465
- async function Le(e) {
630
+ async function Qe(e) {
466
631
  let t = await te(e).catch(() => ({
467
632
  items: [],
468
633
  prefixes: []
469
634
  }));
470
- await Promise.all([...t.items.map((e) => z(e)), ...t.prefixes.map((e) => Le(e))]);
635
+ await Promise.all([...t.items.map((e) => z(e)), ...t.prefixes.map((e) => Qe(e))]);
471
636
  }
472
- async function Re(e, t) {
637
+ async function $e(e, t) {
473
638
  let n = [...new Set([...t].filter(Boolean))];
474
639
  await Promise.all(n.map((t) => z(H(e, t)).catch(() => {})));
475
640
  }
476
- function ze(e) {
641
+ function et(e) {
477
642
  if (!e || typeof e != "object") return !1;
478
643
  let t = "code" in e ? String(e.code) : "";
479
644
  return t === "permission-denied" || t === "firestore/permission-denied";
480
645
  }
481
- function Be() {
482
- let e = G(), t = d(), n = l(), { isCategoryHidden: r } = Ie(), i = () => e.collectionPrefix, a = () => f(t, `${i()}Posts`), o = (e) => h(t, `${i()}Posts`, e), s = (e) => h(t, `${i()}Posts`, e, "body", "main"), c = (e) => f(t, `${i()}Posts`, e, "comments");
483
- function u(e) {
646
+ function tt() {
647
+ let e = W(), t = M(), n = A(), { isCategoryHidden: r } = Ze(), i = () => e.collectionPrefix, a = () => o(t, `${i()}Posts`), u = (e) => l(t, `${i()}Posts`, e), m = (e) => l(t, `${i()}Posts`, e, "body", "main"), _ = (e) => o(t, `${i()}Posts`, e, "comments");
648
+ function b(e) {
484
649
  return !r(e);
485
650
  }
486
- function g() {
487
- return h(a()).id;
651
+ function D() {
652
+ return l(a()).id;
488
653
  }
489
- async function b(e = {}) {
654
+ async function O(e = {}) {
490
655
  let t = e.pageSize ?? 20, n = [];
491
- e.category && n.push(j("category", "==", e.category)), e.publicOnly !== !1 && n.push(j("listed", "==", !0)), n.push(w("createdAt", "desc")), e.cursor && n.push(k(e.cursor)), n.push(x(t + 1));
492
- let r = await y(T(a(), ...n)), i = r.docs.slice(0, t);
656
+ e.category && n.push(T("category", "==", e.category)), e.publicOnly !== !1 && n.push(T("listed", "==", !0)), n.push(v("createdAt", "desc")), e.cursor && n.push(C(e.cursor)), n.push(h(t + 1));
657
+ let r = await p(y(a(), ...n)), i = r.docs.slice(0, t);
493
658
  return {
494
659
  posts: i.map((e) => ({
495
660
  id: e.id,
@@ -499,32 +664,32 @@ function Be() {
499
664
  hasMore: r.docs.length > t
500
665
  };
501
666
  }
502
- async function C(e) {
503
- let [t, n] = await Promise.all([v(o(e)), v(s(e))]);
667
+ async function k(e) {
668
+ let [t, n] = await Promise.all([f(u(e)), f(m(e))]);
504
669
  return t.exists() ? {
505
670
  id: t.id,
506
671
  ...t.data(),
507
672
  content: n.exists() ? n.data().content ?? "" : ""
508
673
  } : null;
509
674
  }
510
- async function E(e, t) {
675
+ async function j(e, t) {
511
676
  try {
512
- let n = (await y(T(a(), j("category", "==", e), j("slug", "==", t), x(1)))).docs[0];
677
+ let n = (await p(y(a(), T("category", "==", e), T("slug", "==", t), h(1)))).docs[0];
513
678
  if (!n) return { status: "not-found" };
514
679
  try {
515
- let e = await C(n.id);
680
+ let e = await k(n.id);
516
681
  return e ? {
517
682
  status: "ok",
518
683
  post: e
519
684
  } : { status: "not-found" };
520
685
  } catch (e) {
521
- return ze(e) ? { status: "permission-denied" } : {
686
+ return et(e) ? { status: "permission-denied" } : {
522
687
  status: "error",
523
688
  message: e instanceof Error ? e.message : String(e)
524
689
  };
525
690
  }
526
691
  } catch (e) {
527
- return ze(e) ? { status: "permission-denied" } : {
692
+ return et(e) ? { status: "permission-denied" } : {
528
693
  status: "error",
529
694
  message: e instanceof Error ? e.message : String(e)
530
695
  };
@@ -537,10 +702,10 @@ function Be() {
537
702
  };
538
703
  try {
539
704
  let t = [
540
- j("category", "==", e.category),
541
- ...e.listed === !1 ? [] : [j("listed", "==", !0)],
542
- w("createdAt", "desc")
543
- ], [n, r] = await Promise.all([y(T(a(), ...t, k(e.createdAt), x(1))), y(T(a(), ...t, _(e.createdAt), S(1)))]), i = (e) => {
705
+ T("category", "==", e.category),
706
+ ...e.listed === !1 ? [] : [T("listed", "==", !0)],
707
+ v("createdAt", "desc")
708
+ ], [n, r] = await Promise.all([p(y(a(), ...t, C(e.createdAt), h(1))), p(y(a(), ...t, d(e.createdAt), g(1)))]), i = (e) => {
544
709
  let t = e.docs[0];
545
710
  return t ? {
546
711
  id: t.id,
@@ -559,225 +724,225 @@ function Be() {
559
724
  }
560
725
  }
561
726
  async function P(e) {
562
- let t = Te(e.title) || "post", n = t, r = 1;
563
- for (; !(await y(T(a(), j("category", "==", e.category || ""), j("slug", "==", n), x(1)))).empty;) r++, n = `${t}-${r}`;
564
- let i = e.postId || g(), c = Ne(e.content, e.attachments), l = u(e.category);
565
- return await O(o(i), {
727
+ let t = Be(e.title) || "post", n = t, r = 1;
728
+ for (; !(await p(y(a(), T("category", "==", e.category || ""), T("slug", "==", n), h(1)))).empty;) r++, n = `${t}-${r}`;
729
+ let i = e.postId || D(), o = Je(e.content, e.attachments), s = b(e.category);
730
+ return await S(u(i), {
566
731
  slug: n,
567
732
  title: e.title,
568
- ...c,
733
+ ...o,
569
734
  tags: e.tags ?? [],
570
735
  ...e.category ? { category: e.category } : {},
571
736
  attachments: e.attachments ?? [],
572
737
  commentCount: 0,
573
738
  likeCount: 0,
574
739
  viewCount: 0,
575
- listed: l,
740
+ listed: s,
576
741
  authorUid: e.authorUid,
577
742
  authorName: e.authorName,
578
743
  authorPhoto: e.authorPhoto,
579
744
  moderationStatus: "approved",
580
745
  moderationModel: e.moderationModel ?? null,
581
- createdAt: D(),
582
- updatedAt: D()
583
- }), await O(s(i), { content: e.content }), n;
746
+ createdAt: x(),
747
+ updatedAt: x()
748
+ }), await S(m(i), { content: e.content }), n;
584
749
  }
585
750
  async function F(e, t) {
586
- let n = Ne(t.content, t.attachments), r = u(t.category);
587
- await Promise.all([A(o(e), {
751
+ let n = Je(t.content, t.attachments), r = b(t.category);
752
+ await Promise.all([w(u(e), {
588
753
  title: t.title,
589
754
  ...n,
590
- previewImage: n.previewImage ? n.previewImage : m(),
591
- videoUrl: n.videoUrl ? n.videoUrl : m(),
755
+ previewImage: n.previewImage ? n.previewImage : c(),
756
+ videoUrl: n.videoUrl ? n.videoUrl : c(),
592
757
  tags: t.tags ?? [],
593
- category: t.category ? t.category : m(),
758
+ category: t.category ? t.category : c(),
594
759
  listed: r,
595
760
  attachments: t.attachments ?? [],
596
- updatedAt: D()
597
- }), O(s(e), { content: t.content }, { merge: !0 })]);
761
+ updatedAt: x()
762
+ }), S(m(e), { content: t.content }, { merge: !0 })]);
598
763
  }
599
764
  async function I(e) {
600
- let [r, a] = await Promise.all([v(o(e)), v(s(e))]);
765
+ let [r, a] = await Promise.all([f(u(e)), f(m(e))]);
601
766
  if (!r.exists()) return;
602
- let l = r.data(), u = a.exists() ? String(a.data().content ?? "") : "", d = await y(c(e));
603
- for (let e = 0; e < d.docs.length; e += 450) {
604
- let n = M(t);
605
- d.docs.slice(e, e + 450).forEach((e) => n.delete(e.ref)), await n.commit();
767
+ let o = r.data(), c = a.exists() ? String(a.data().content ?? "") : "", l = await p(_(e));
768
+ for (let e = 0; e < l.docs.length; e += 450) {
769
+ let n = E(t);
770
+ l.docs.slice(e, e + 450).forEach((e) => n.delete(e.ref)), await n.commit();
606
771
  }
607
- let f = V(n), m = /* @__PURE__ */ new Set(), h = /* @__PURE__ */ new Set();
608
- for (let t of l.attachments ?? []) if (t?.path) {
609
- m.add(t.path);
610
- let n = ke(t.path);
611
- n && n !== e && h.add(n);
772
+ let d = V(n), h = /* @__PURE__ */ new Set(), g = /* @__PURE__ */ new Set();
773
+ for (let t of o.attachments ?? []) if (t?.path) {
774
+ h.add(t.path);
775
+ let n = We(t.path);
776
+ n && n !== e && g.add(n);
612
777
  }
613
- for (let t of De(u)) {
614
- let n = Oe(t);
778
+ for (let t of He(c)) {
779
+ let n = Ue(t);
615
780
  if (!n) continue;
616
- m.add(n);
781
+ h.add(n);
617
782
  let r = n.replace(/\/images\/([^/]+)$/, "/images/thumbnails/$1").replace(/\.[^.]+$/, ".jpg");
618
- r !== n && r.includes("/images/thumbnails/") && m.add(r);
783
+ r !== n && r.includes("/images/thumbnails/") && h.add(r);
619
784
  let i = n.match(/^(.*\/images\/)([^/]+)\.[^.]+$/);
620
- i && !n.includes("/thumbnails/") && m.add(`${i[1]}thumbnails/${i[2]}.jpg`);
621
- let a = ke(n);
622
- a && a !== e && h.add(a);
785
+ i && !n.includes("/thumbnails/") && h.add(`${i[1]}thumbnails/${i[2]}.jpg`);
786
+ let a = We(n);
787
+ a && a !== e && g.add(a);
623
788
  }
624
- await p(s(e)), await Le(H(f, `${i()}/posts/${e}`));
625
- for (let e of h) await Le(H(f, `${i()}/posts/${e}`));
626
- await Re(f, m), await p(o(e));
789
+ await s(m(e)), await Qe(H(d, `${i()}/posts/${e}`));
790
+ for (let e of g) await Qe(H(d, `${i()}/posts/${e}`));
791
+ await $e(d, h), await s(u(e));
627
792
  }
628
793
  return {
629
- createPostId: g,
630
- getPosts: b,
631
- getPost: C,
632
- getPostBySlug: E,
794
+ createPostId: D,
795
+ getPosts: O,
796
+ getPost: k,
797
+ getPostBySlug: j,
633
798
  getAdjacentPosts: N,
634
799
  createPost: P,
635
800
  updatePost: F,
636
801
  deletePost: I
637
802
  };
638
803
  }
639
- var Ve = 10;
640
- function He(n, i = {}) {
641
- let o = G(), s = d(), { isAdmin: c, isStaff: l } = J(), u = () => o.collectionPrefix, p = () => f(s, `${u()}Posts`), m = i.pageSize ?? Ve, h = e(() => typeof n == "string" || n === void 0 ? n : n.value), _ = e(() => i.publicOnly === void 0 ? !(c.value || l.value) : typeof i.publicOnly == "boolean" ? i.publicOnly : i.publicOnly.value), v = r([]), b = r([]), S = r(!0), E = r(!0), D = r(!1), O = r(""), A = null, M = null, N;
804
+ var nt = 10;
805
+ function rt(n, i = {}) {
806
+ let s = W(), c = M(), { isAdmin: l, isStaff: d } = J(), f = () => s.collectionPrefix, m = () => o(c, `${f()}Posts`), g = i.pageSize ?? nt, b = e(() => typeof n == "string" || n === void 0 ? n : n.value), x = e(() => i.publicOnly === void 0 ? !(l.value || d.value) : typeof i.publicOnly == "boolean" ? i.publicOnly : i.publicOnly.value), S = r([]), w = r([]), E = r(!0), D = r(!0), O = r(!1), k = r(""), A = null, j = null, N;
642
807
  function P(e) {
643
808
  return e.createdAt?.toMillis?.() ?? 0;
644
809
  }
645
810
  let F = e(() => {
646
811
  let e = /* @__PURE__ */ new Map();
647
- for (let t of [...b.value, ...v.value]) t.id && e.set(t.id, t);
812
+ for (let t of [...w.value, ...S.value]) t.id && e.set(t.id, t);
648
813
  return [...e.values()].sort((e, t) => P(t) - P(e) || String(t.id).localeCompare(String(e.id)));
649
814
  });
650
815
  function I() {
651
- let e = [], t = h.value;
652
- return t && e.push(j("category", "==", t)), _.value && e.push(j("listed", "==", !0)), e;
816
+ let e = [], t = b.value;
817
+ return t && e.push(T("category", "==", t)), x.value && e.push(T("listed", "==", !0)), e;
653
818
  }
654
819
  function L() {
655
- N?.(), N = C(T(p(), ...I(), w("createdAt", "desc"), w(g(), "desc"), x(m)), (e) => {
656
- v.value = e.docs.map((e) => ({
820
+ N?.(), N = _(y(m(), ...I(), v("createdAt", "desc"), v(u(), "desc"), h(g)), (e) => {
821
+ S.value = e.docs.map((e) => ({
657
822
  id: e.id,
658
823
  ...e.data()
659
- })), A = e.docs.at(-1) ?? null, M || (E.value = e.docs.length >= m), S.value = !1;
824
+ })), A = e.docs.at(-1) ?? null, j || (D.value = e.docs.length >= g), E.value = !1;
660
825
  }, (e) => {
661
- console.error("[memi-board:postList] onSnapshot failed", e), S.value = !1;
826
+ console.error("[memi-board:postList] onSnapshot failed", e), E.value = !1;
662
827
  });
663
828
  }
664
829
  async function R() {
665
- if (!(D.value || !E.value || S.value)) {
666
- D.value = !0, O.value = "";
830
+ if (!(O.value || !D.value || E.value)) {
831
+ O.value = !0, k.value = "";
667
832
  try {
668
- let e = M ?? A, t = [
833
+ let e = j ?? A, t = [
669
834
  ...I(),
670
- w("createdAt", "desc"),
671
- w(g(), "desc"),
672
- ...e ? [k(e)] : [],
673
- x(m + 1)
674
- ], n = await y(T(p(), ...t)), r = n.docs.slice(0, m), i = r.map((e) => ({
835
+ v("createdAt", "desc"),
836
+ v(u(), "desc"),
837
+ ...e ? [C(e)] : [],
838
+ h(g + 1)
839
+ ], n = await p(y(m(), ...t)), r = n.docs.slice(0, g), i = r.map((e) => ({
675
840
  id: e.id,
676
841
  ...e.data()
677
- })), a = new Set([...b.value, ...v.value].map((e) => e.id));
678
- b.value.push(...i.filter((e) => !a.has(e.id))), M = r.at(-1) ?? M, E.value = n.docs.length > m;
842
+ })), a = new Set([...w.value, ...S.value].map((e) => e.id));
843
+ w.value.push(...i.filter((e) => !a.has(e.id))), j = r.at(-1) ?? j, D.value = n.docs.length > g;
679
844
  } catch (e) {
680
- O.value = e.message || String(e), console.error("[memi-board:postList] loadMore failed", e);
845
+ k.value = e.message || String(e), console.error("[memi-board:postList] loadMore failed", e);
681
846
  } finally {
682
- D.value = !1;
847
+ O.value = !1;
683
848
  }
684
849
  }
685
850
  }
686
851
  function ee() {
687
- N?.(), v.value = [], b.value = [], A = null, M = null, E.value = !0, O.value = "", S.value = !0, L();
852
+ N?.(), S.value = [], w.value = [], A = null, j = null, D.value = !0, k.value = "", E.value = !0, L();
688
853
  }
689
- return a([h, _], ee, { immediate: !0 }), t(() => N?.()), {
854
+ return a([b, x], ee, { immediate: !0 }), t(() => N?.()), {
690
855
  posts: F,
691
- postsPending: S,
692
- hasMore: E,
693
- loadingMore: D,
694
- loadError: O,
856
+ postsPending: E,
857
+ hasMore: D,
858
+ loadingMore: O,
859
+ loadError: k,
695
860
  loadMore: R
696
861
  };
697
862
  }
698
- function Ue(t) {
699
- let n = G(), r = d(), i = () => n.collectionPrefix, a = e(() => typeof t == "string" ? t : t.value), o = e(() => h(r, `${i()}Posts`, a.value)), s = e(() => h(r, `${i()}Posts`, a.value, "body", "main")), { data: l, pending: u } = c(o), { data: f, pending: p } = c(s);
863
+ function it(t) {
864
+ let n = W(), r = M(), i = () => n.collectionPrefix, a = e(() => typeof t == "string" ? t : t.value), o = e(() => l(r, `${i()}Posts`, a.value)), s = e(() => l(r, `${i()}Posts`, a.value, "body", "main")), { data: c, pending: u } = k(o), { data: d, pending: f } = k(s);
700
865
  return {
701
- post: e(() => l.value ? {
702
- ...l.value,
866
+ post: e(() => c.value ? {
867
+ ...c.value,
703
868
  id: a.value,
704
- content: f.value?.content ?? ""
869
+ content: d.value?.content ?? ""
705
870
  } : null),
706
- pending: e(() => u.value || p.value)
871
+ pending: e(() => u.value || f.value)
707
872
  };
708
873
  }
709
874
  //#endregion
710
875
  //#region src/composables/useMemiBoardComments.ts
711
- var We = 10, Ge = 5, Ke = 1e3;
712
- function qe(e) {
876
+ var at = 10, ot = 5, st = 1e3;
877
+ function ct(e) {
713
878
  let t = e.trim();
714
879
  if (!t) throw Error("댓글 내용을 입력해 주세요.");
715
- if (t.length > 1e3) throw Error(`댓글은 ${Ke.toLocaleString()}자까지 작성할 수 있습니다.`);
880
+ if (t.length > 1e3) throw Error(`댓글은 ${st.toLocaleString()}자까지 작성할 수 있습니다.`);
716
881
  return t;
717
882
  }
718
- function Je(n, i = {}) {
719
- let o = G(), s = d(), c = () => o.collectionPrefix, l = e(() => typeof n == "string" ? n : n.value), u = r([]), p = r([]), m = r(i.subscribe !== !1), _ = r([]), v = r(!1), S = r(i.subscribe !== !1), E = r(i.subscribe !== !1), O = null, N, P;
883
+ function lt(n, i = {}) {
884
+ let s = W(), c = M(), d = () => s.collectionPrefix, f = e(() => typeof n == "string" ? n : n.value), g = r([]), b = r([]), S = r(i.subscribe !== !1), D = r([]), O = r(!1), k = r(i.subscribe !== !1), A = r(i.subscribe !== !1), j = null, N, P;
720
885
  function F(e) {
721
886
  return e.createdAt?.toMillis?.() ?? 0;
722
887
  }
723
888
  let I = e(() => {
724
889
  let e = /* @__PURE__ */ new Map();
725
890
  for (let t of [
726
- ...u.value,
727
- ...p.value,
728
- ..._.value
891
+ ...g.value,
892
+ ...b.value,
893
+ ...D.value
729
894
  ]) t.id && e.set(t.id, t);
730
895
  return [...e.values()].sort((e, t) => F(e) - F(t) || String(e.id).localeCompare(String(t.id)));
731
896
  });
732
- a(l, async (e) => {
733
- if (_.value = [], u.value = [], p.value = [], O = null, clearTimeout(N), N = void 0, P?.(), P = void 0, S.value = i.subscribe !== !1, i.subscribe !== !1) {
734
- m.value = !0, E.value = !0;
897
+ a(f, async (e) => {
898
+ if (D.value = [], g.value = [], b.value = [], j = null, clearTimeout(N), N = void 0, P?.(), P = void 0, k.value = i.subscribe !== !1, i.subscribe !== !1) {
899
+ S.value = !0, A.value = !0;
735
900
  try {
736
- let t = y(T(f(s, `${c()}Posts`, e, "comments"), w("createdAt", "desc"), w(g(), "desc")));
901
+ let t = p(y(o(c, `${d()}Posts`, e, "comments"), v("createdAt", "desc"), v(u(), "desc")));
737
902
  if (await Promise.all([t.then((t) => {
738
- if (l.value !== e) return;
903
+ if (f.value !== e) return;
739
904
  let n = t.docs.map((e) => ({
740
905
  id: e.id,
741
906
  ...e.data()
742
907
  }));
743
- p.value = n.filter((e) => e.parentId === void 0);
744
- }), R()]), l.value !== e) return;
908
+ b.value = n.filter((e) => e.parentId === void 0);
909
+ }), R()]), f.value !== e) return;
745
910
  } finally {
746
- l.value === e && (m.value = !1, E.value = !1);
911
+ f.value === e && (S.value = !1, A.value = !1);
747
912
  }
748
913
  }
749
914
  }, { immediate: !0 });
750
915
  function L() {
751
- P?.(), P = C(T(f(s, `${c()}Posts`, l.value, "comments"), j("parentId", "==", null), w("createdAt", "desc"), w(g(), "desc"), x(1)), (e) => {
752
- let t = new Map(u.value.map((e) => [e.id, e]));
916
+ P?.(), P = _(y(o(c, `${d()}Posts`, f.value, "comments"), T("parentId", "==", null), v("createdAt", "desc"), v(u(), "desc"), h(1)), (e) => {
917
+ let t = new Map(g.value.map((e) => [e.id, e]));
753
918
  for (let n of e.docChanges()) n.type !== "removed" && t.set(n.doc.id, {
754
919
  id: n.doc.id,
755
920
  ...n.doc.data()
756
921
  });
757
- u.value = [...t.values()];
922
+ g.value = [...t.values()];
758
923
  }, (e) => {
759
924
  console.error("[memi-board:comments] onSnapshot failed", e);
760
925
  });
761
926
  }
762
927
  async function R() {
763
- if (!(v.value || !S.value)) {
764
- v.value = !0;
928
+ if (!(O.value || !k.value)) {
929
+ O.value = !0;
765
930
  try {
766
931
  let e = [
767
- j("parentId", "==", null),
768
- w("createdAt", "asc"),
769
- w(g(), "asc"),
770
- ...O ? [k(O)] : [],
771
- x(11)
772
- ], t = await y(T(f(s, `${c()}Posts`, l.value, "comments"), ...e)), n = t.docs.slice(0, We), r = n.map((e) => ({
932
+ T("parentId", "==", null),
933
+ v("createdAt", "asc"),
934
+ v(u(), "asc"),
935
+ ...j ? [C(j)] : [],
936
+ h(11)
937
+ ], t = await p(y(o(c, `${d()}Posts`, f.value, "comments"), ...e)), n = t.docs.slice(0, at), r = n.map((e) => ({
773
938
  id: e.id,
774
939
  ...e.data()
775
- })), i = new Set(_.value.map((e) => e.id));
776
- _.value.push(...r.filter((e) => !i.has(e.id))), O = n.at(-1) ?? O, t.size <= We && (S.value = !1, N = setTimeout(() => {
940
+ })), i = new Set(D.value.map((e) => e.id));
941
+ D.value.push(...r.filter((e) => !i.has(e.id))), j = n.at(-1) ?? j, t.size <= at && (k.value = !1, N = setTimeout(() => {
777
942
  N = void 0, L();
778
943
  }, 1e3));
779
944
  } finally {
780
- v.value = !1;
945
+ O.value = !1;
781
946
  }
782
947
  }
783
948
  }
@@ -785,9 +950,9 @@ function Je(n, i = {}) {
785
950
  clearTimeout(N), P?.();
786
951
  });
787
952
  async function ee(e) {
788
- let t = qe(e.body), n = M(s), r = c(), i = h(f(s, `${r}Posts`, l.value, "comments"));
953
+ let t = ct(e.body), n = E(c), r = d(), i = l(o(c, `${r}Posts`, f.value, "comments"));
789
954
  n.set(i, {
790
- postId: l.value,
955
+ postId: f.value,
791
956
  body: t,
792
957
  authorUid: e.authorUid,
793
958
  authorName: e.authorName,
@@ -800,59 +965,59 @@ function Je(n, i = {}) {
800
965
  replyToName: null,
801
966
  replyCount: 0,
802
967
  isReply: !1,
803
- createdAt: D(),
804
- updatedAt: D()
805
- }), n.update(h(s, `${r}Posts`, l.value), { commentCount: b(1) }), await n.commit();
968
+ createdAt: x(),
969
+ updatedAt: x()
970
+ }), n.update(l(c, `${r}Posts`, f.value), { commentCount: m(1) }), await n.commit();
806
971
  }
807
972
  async function z(e) {
808
- let t = qe(e.body);
973
+ let t = ct(e.body);
809
974
  if (!e.parent.id) throw Error("답글 대상 댓글을 찾을 수 없습니다.");
810
- let n = M(s), r = c(), i = f(s, `${r}Posts`, l.value, "comments"), a = h(i), o = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
811
- if (!o) throw Error("댓글 스레드를 찾을 수 없습니다.");
975
+ let n = E(c), r = d(), i = o(c, `${r}Posts`, f.value, "comments"), a = l(i), s = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
976
+ if (!s) throw Error("댓글 스레드를 찾을 수 없습니다.");
812
977
  let u = Math.min((e.parent.depth ?? 0) + 1, 2);
813
978
  n.set(a, {
814
- postId: l.value,
979
+ postId: f.value,
815
980
  body: t,
816
981
  authorUid: e.authorUid,
817
982
  authorName: e.authorName,
818
983
  authorPhoto: e.authorPhoto,
819
984
  moderationStatus: "approved",
820
985
  parentId: e.parent.id,
821
- rootId: o,
986
+ rootId: s,
822
987
  depth: u,
823
988
  replyToUid: e.parent.authorUid,
824
989
  replyToName: e.parent.authorName,
825
990
  replyCount: 0,
826
991
  isReply: !0,
827
- createdAt: D(),
828
- updatedAt: D()
829
- }), n.update(h(i, o), { replyCount: b(1) }), n.update(h(s, `${r}Posts`, l.value), { commentCount: b(1) }), await n.commit();
992
+ createdAt: x(),
993
+ updatedAt: x()
994
+ }), n.update(l(i, s), { replyCount: m(1) }), n.update(l(c, `${r}Posts`, f.value), { commentCount: m(1) }), await n.commit();
830
995
  }
831
996
  async function B(e) {
832
997
  if (!e.id) return;
833
998
  if (e.parentId == null && (e.replyCount ?? 0) > 0) throw Error("답글이 있는 댓글은 현재 삭제할 수 없습니다.");
834
- let t = M(s), n = c();
835
- t.delete(h(s, `${n}Posts`, l.value, "comments", e.id)), e.parentId && e.rootId && t.update(h(s, `${n}Posts`, l.value, "comments", e.rootId), { replyCount: b(-1) }), t.update(h(s, `${n}Posts`, l.value), { commentCount: b(-1) }), await t.commit(), _.value = _.value.filter((t) => t.id !== e.id), p.value = p.value.filter((t) => t.id !== e.id), u.value = u.value.filter((t) => t.id !== e.id);
999
+ let t = E(c), n = d();
1000
+ t.delete(l(c, `${n}Posts`, f.value, "comments", e.id)), e.parentId && e.rootId && t.update(l(c, `${n}Posts`, f.value, "comments", e.rootId), { replyCount: m(-1) }), t.update(l(c, `${n}Posts`, f.value), { commentCount: m(-1) }), await t.commit(), D.value = D.value.filter((t) => t.id !== e.id), b.value = b.value.filter((t) => t.id !== e.id), g.value = g.value.filter((t) => t.id !== e.id);
836
1001
  }
837
1002
  async function V(e, t) {
838
- let n = qe(t);
839
- await A(h(s, `${c()}Posts`, l.value, "comments", e), {
1003
+ let n = ct(t);
1004
+ await w(l(c, `${d()}Posts`, f.value, "comments", e), {
840
1005
  body: n,
841
- updatedAt: D()
1006
+ updatedAt: x()
842
1007
  });
843
1008
  }
844
1009
  async function te(e, t, n) {
845
- await A(h(s, `${c()}Posts`, l.value, "comments", e), {
1010
+ await w(l(c, `${d()}Posts`, f.value, "comments", e), {
846
1011
  isBlinded: t,
847
- moderatedAt: D(),
1012
+ moderatedAt: x(),
848
1013
  moderatedBy: n
849
1014
  });
850
1015
  }
851
1016
  return {
852
1017
  comments: I,
853
- commentsPending: e(() => E.value || m.value),
854
- hasMore: S,
855
- loadingMore: v,
1018
+ commentsPending: e(() => A.value || S.value),
1019
+ hasMore: k,
1020
+ loadingMore: O,
856
1021
  loadMore: R,
857
1022
  addComment: ee,
858
1023
  addReply: z,
@@ -861,102 +1026,102 @@ function Je(n, i = {}) {
861
1026
  deleteComment: B
862
1027
  };
863
1028
  }
864
- function Ye(e, n) {
865
- let i = G(), a = d(), o = r([]), s = r(!1), c = r(!1), l = r(!0), u = null, p;
866
- function m(e) {
1029
+ function ut(e, n) {
1030
+ let i = W(), a = M(), s = r([]), c = r(!1), l = r(!1), d = r(!0), f = null, m;
1031
+ function g(e) {
867
1032
  return e.sort((e, t) => (e.createdAt?.toMillis?.() ?? 0) - (t.createdAt?.toMillis?.() ?? 0) || String(e.id).localeCompare(String(t.id)));
868
1033
  }
869
- function h() {
870
- p ||= C(T(f(a, `${i.collectionPrefix}Posts`, e, "comments"), j("rootId", "==", n), j("isReply", "==", !0), w("createdAt", "desc"), w(g(), "desc"), x(1)), (e) => {
871
- let t = new Map(o.value.map((e) => [e.id, e]));
1034
+ function b() {
1035
+ m ||= _(y(o(a, `${i.collectionPrefix}Posts`, e, "comments"), T("rootId", "==", n), T("isReply", "==", !0), v("createdAt", "desc"), v(u(), "desc"), h(1)), (e) => {
1036
+ let t = new Map(s.value.map((e) => [e.id, e]));
872
1037
  for (let n of e.docChanges()) n.type !== "removed" && t.set(n.doc.id, {
873
1038
  id: n.doc.id,
874
1039
  ...n.doc.data()
875
1040
  });
876
- o.value = m([...t.values()]);
1041
+ s.value = g([...t.values()]);
877
1042
  }, (e) => {
878
1043
  console.error("[memi-board:replies] onSnapshot failed", e);
879
1044
  });
880
1045
  }
881
- async function _() {
882
- if (!(s.value || !l.value)) {
883
- s.value = !0;
1046
+ async function x() {
1047
+ if (!(c.value || !d.value)) {
1048
+ c.value = !0;
884
1049
  try {
885
1050
  let t = [
886
- j("rootId", "==", n),
887
- j("isReply", "==", !0),
888
- w("createdAt", "asc"),
889
- w(g(), "asc"),
890
- ...u ? [k(u)] : [],
891
- x(6)
892
- ], r = await y(T(f(a, `${i.collectionPrefix}Posts`, e, "comments"), ...t)), s = new Set(o.value.map((e) => e.id)), d = r.docs.slice(0, Ge), p = d.map((e) => ({
1051
+ T("rootId", "==", n),
1052
+ T("isReply", "==", !0),
1053
+ v("createdAt", "asc"),
1054
+ v(u(), "asc"),
1055
+ ...f ? [C(f)] : [],
1056
+ h(6)
1057
+ ], r = await p(y(o(a, `${i.collectionPrefix}Posts`, e, "comments"), ...t)), c = new Set(s.value.map((e) => e.id)), m = r.docs.slice(0, ot), _ = m.map((e) => ({
893
1058
  id: e.id,
894
1059
  ...e.data()
895
- })).filter((e) => !s.has(e.id));
896
- o.value = m([...o.value, ...p]), u = d.at(-1) ?? u, c.value = !0, r.size <= Ge && (l.value = !1, h());
1060
+ })).filter((e) => !c.has(e.id));
1061
+ s.value = g([...s.value, ..._]), f = m.at(-1) ?? f, l.value = !0, r.size <= ot && (d.value = !1, b());
897
1062
  } finally {
898
- s.value = !1;
1063
+ c.value = !1;
899
1064
  }
900
1065
  }
901
1066
  }
902
- function v() {
903
- p?.(), p = void 0, o.value = [], u = null, c.value = !1, l.value = !0;
1067
+ function S() {
1068
+ m?.(), m = void 0, s.value = [], f = null, l.value = !1, d.value = !0;
904
1069
  }
905
- async function b() {
906
- v(), await _();
1070
+ async function w() {
1071
+ S(), await x();
907
1072
  }
908
- return t(() => p?.()), {
909
- replies: o,
910
- loading: s,
911
- loaded: c,
912
- hasMore: l,
913
- loadMore: _,
914
- refresh: b
1073
+ return t(() => m?.()), {
1074
+ replies: s,
1075
+ loading: c,
1076
+ loaded: l,
1077
+ hasMore: d,
1078
+ loadMore: x,
1079
+ refresh: w
915
1080
  };
916
1081
  }
917
1082
  //#endregion
918
1083
  //#region src/composables/useMemiBoardLikes.ts
919
- function Xe(e) {
920
- let n = G(), i = d(), o = s(), c = () => n.collectionPrefix, l = r(!1), u = r(!1), f;
921
- function p(t) {
922
- return h(i, `${c()}Posts`, e, "likes", t);
1084
+ function dt(e) {
1085
+ let n = W(), i = M(), o = O(), s = () => n.collectionPrefix, c = r(!1), u = r(!1), d;
1086
+ function f(t) {
1087
+ return l(i, `${s()}Posts`, e, "likes", t);
923
1088
  }
924
1089
  a(o, (e) => {
925
- if (f?.(), f = void 0, !e) {
926
- l.value = !1;
1090
+ if (d?.(), d = void 0, !e) {
1091
+ c.value = !1;
927
1092
  return;
928
1093
  }
929
- f = C(p(e.uid), (e) => {
930
- l.value = e.exists();
1094
+ d = _(f(e.uid), (e) => {
1095
+ c.value = e.exists();
931
1096
  }, (e) => {
932
1097
  console.error("[memi-board:likes] onSnapshot failed", e);
933
1098
  });
934
- }, { immediate: !0 }), t(() => f?.());
935
- async function m() {
1099
+ }, { immediate: !0 }), t(() => d?.());
1100
+ async function p() {
936
1101
  let t = o.value?.uid;
937
1102
  if (!t) throw Error("로그인이 필요합니다.");
938
- if (u.value) return l.value;
1103
+ if (u.value) return c.value;
939
1104
  u.value = !0;
940
1105
  try {
941
- let n = p(t), r = h(i, `${c()}Posts`, e);
942
- return await E(i, async (e) => (await e.get(n)).exists() ? (e.delete(n), e.update(r, { likeCount: b(-1) }), !1) : (e.set(n, {
1106
+ let n = f(t), r = l(i, `${s()}Posts`, e);
1107
+ return await b(i, async (e) => (await e.get(n)).exists() ? (e.delete(n), e.update(r, { likeCount: m(-1) }), !1) : (e.set(n, {
943
1108
  uid: t,
944
- createdAt: D()
945
- }), e.update(r, { likeCount: b(1) }), !0));
1109
+ createdAt: x()
1110
+ }), e.update(r, { likeCount: m(1) }), !0));
946
1111
  } finally {
947
1112
  u.value = !1;
948
1113
  }
949
1114
  }
950
1115
  return {
951
- isLiked: l,
1116
+ isLiked: c,
952
1117
  likePending: u,
953
- toggleLike: m
1118
+ toggleLike: p
954
1119
  };
955
1120
  }
956
1121
  //#endregion
957
1122
  //#region src/composables/useMemiBoardViews.ts
958
- function Ze() {
959
- let e = G(), t = d(), n = e.collectionPrefix;
1123
+ function ft() {
1124
+ let e = W(), t = M(), n = e.collectionPrefix;
960
1125
  function r(e) {
961
1126
  return `memi-board:viewed:${n}:${e}`;
962
1127
  }
@@ -978,7 +1143,7 @@ function Ze() {
978
1143
  if (o && !i(o)) {
979
1144
  a(o);
980
1145
  try {
981
- await A(h(t, `${n}Posts`, o), { viewCount: b(1) });
1146
+ await w(l(t, `${n}Posts`, o), { viewCount: m(1) });
982
1147
  } catch (e) {
983
1148
  try {
984
1149
  sessionStorage.removeItem(r(o));
@@ -994,7 +1159,7 @@ function Ze() {
994
1159
  }
995
1160
  //#endregion
996
1161
  //#region src/utils/moderation-prompt.ts
997
- var Qe = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\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은 \"\"", $e = {
1162
+ var pt = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\n아래 텍스트에 욕설·비속어·성희롱·혐오·위협·스팸·도배성 광고가 있으면 반드시 거부(flagged=true)합니다.\n\nflagged=true 예:\n- 욕설/비속어: 씨발, 시발, 병신, 지랄, 좆, 개새끼, 존나, fuck, shit, bitch 등\n- 성적 모욕, 혐오 발언, 협박\n- 게시판 주제와 무관한 도배성 광고/스팸\n\nflagged=false:\n- 게시판 주제에 맞는 정상적인 글/댓글 (강한 의견이나 정당한 비판은 허용)\n\nJSON만 출력:\n{\"flagged\":boolean,\"category\":\"none\"|\"abuse\"|\"spam\"|\"adult\"|\"violence\"|\"other\",\"reason\":string}\n- flagged=true면 category를 고르고, reason은 한국어로 **무엇이 문제인지** 구체적으로 1문장\n (예: \"욕설(비속어)이 포함되어 게시할 수 없습니다.\", \"광고·스팸성 문구가 있어 게시할 수 없습니다.\")\n 욕설 단어 전체를 그대로 길게 인용하지 말고, 유형(욕설/혐오/스팸 등)을 분명히 밝힌다.\n- flagged=false면 category는 \"none\", reason은 \"\"", mt = {
998
1163
  abuse: "욕설·비속어·모욕",
999
1164
  spam: "스팸·광고",
1000
1165
  adult: "선정적·성인 내용",
@@ -1009,10 +1174,10 @@ function Y(e) {
1009
1174
  }
1010
1175
  let t = (e.reason || "").trim();
1011
1176
  if (t) return t;
1012
- let n = e.category && e.category !== "none" ? $e[e.category] || e.category : "";
1177
+ let n = e.category && e.category !== "none" ? mt[e.category] || e.category : "";
1013
1178
  return n ? `${n}이(가) 포함되어 게시할 수 없습니다. 내용을 수정해 주세요.` : "게시할 수 없는 내용이 포함되어 있습니다. 내용을 수정해 주세요.";
1014
1179
  }
1015
- var et = [
1180
+ var ht = [
1016
1181
  "씨발",
1017
1182
  "시발",
1018
1183
  "시이발",
@@ -1038,10 +1203,10 @@ var et = [
1038
1203
  "bitch",
1039
1204
  "asshole"
1040
1205
  ];
1041
- function tt(e) {
1206
+ function gt(e) {
1042
1207
  return new RegExp(e.join("|"), "i");
1043
1208
  }
1044
- var nt = /* @__PURE__ */ new Set([
1209
+ var _t = /* @__PURE__ */ new Set([
1045
1210
  "none",
1046
1211
  "abuse",
1047
1212
  "spam",
@@ -1049,7 +1214,7 @@ var nt = /* @__PURE__ */ new Set([
1049
1214
  "violence",
1050
1215
  "other"
1051
1216
  ]);
1052
- function rt(e) {
1217
+ function vt(e) {
1053
1218
  let t = e.trim();
1054
1219
  if (!t) return null;
1055
1220
  let n;
@@ -1067,7 +1232,7 @@ function rt(e) {
1067
1232
  if (typeof n != "object" || !n) return null;
1068
1233
  let r = n;
1069
1234
  if (typeof r.flagged != "boolean") return null;
1070
- let i = typeof r.category == "string" && nt.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
1235
+ let i = typeof r.category == "string" && _t.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
1071
1236
  return {
1072
1237
  flagged: r.flagged,
1073
1238
  category: i,
@@ -1076,14 +1241,14 @@ function rt(e) {
1076
1241
  }
1077
1242
  //#endregion
1078
1243
  //#region src/composables/useMemiBoardModeration.ts
1079
- var it = {
1244
+ var yt = {
1080
1245
  flagged: !1,
1081
1246
  category: "none",
1082
1247
  reason: "",
1083
1248
  via: "empty"
1084
1249
  };
1085
- function at() {
1086
- let e = G().moderation ?? {}, t = l(), { isWriteRestricted: n, restrictedMessage: r, recordContentModerationBlock: i } = J(), a = tt([...et, ...e.localBlocklist ?? []]);
1250
+ function bt() {
1251
+ let e = W().moderation ?? {}, t = A(), { isWriteRestricted: n, restrictedMessage: r, recordContentModerationBlock: i } = J(), a = gt([...ht, ...e.localBlocklist ?? []]);
1087
1252
  function o(e) {
1088
1253
  let t = e.match(a);
1089
1254
  return t ? {
@@ -1104,7 +1269,7 @@ function at() {
1104
1269
  });
1105
1270
  return oe(i, {
1106
1271
  model: n,
1107
- systemInstruction: Qe,
1272
+ systemInstruction: pt,
1108
1273
  generationConfig: {
1109
1274
  temperature: 0,
1110
1275
  maxOutputTokens: 256,
@@ -1132,7 +1297,7 @@ function at() {
1132
1297
  via: "ai-error-allow"
1133
1298
  };
1134
1299
  }
1135
- function u(e, t = "") {
1300
+ function l(e, t = "") {
1136
1301
  let n = Y({
1137
1302
  reason: e.reason,
1138
1303
  category: e.category,
@@ -1143,16 +1308,16 @@ function at() {
1143
1308
  reason: r
1144
1309
  };
1145
1310
  }
1146
- async function d(e) {
1311
+ async function u(e) {
1147
1312
  if (!e.flagged || e.error || e.via !== "local" && e.via !== "ai") return e;
1148
1313
  try {
1149
1314
  let { messageSuffix: t } = await i();
1150
- return u(e, t);
1315
+ return l(e, t);
1151
1316
  } catch (t) {
1152
- return console.warn("[memi-board] record moderation strike failed", t instanceof Error ? t.message : t), u(e);
1317
+ return console.warn("[memi-board] record moderation strike failed", t instanceof Error ? t.message : t), l(e);
1153
1318
  }
1154
1319
  }
1155
- async function f(t) {
1320
+ async function d(t) {
1156
1321
  if (n.value) return {
1157
1322
  flagged: !0,
1158
1323
  category: "other",
@@ -1161,17 +1326,17 @@ function at() {
1161
1326
  };
1162
1327
  let i = t.trim();
1163
1328
  if (!i) return {
1164
- ...it,
1329
+ ...yt,
1165
1330
  via: "empty"
1166
1331
  };
1167
1332
  let a = o(i);
1168
- if (a) return d(a);
1333
+ if (a) return u(a);
1169
1334
  if (e.enabled === !1) return {
1170
- ...it,
1335
+ ...yt,
1171
1336
  via: "disabled"
1172
1337
  };
1173
1338
  try {
1174
- let e = rt((await s().generateContent(`심사할 텍스트:\n"""${i.slice(0, 4e3)}"""`)).response.text());
1339
+ let e = vt((await s().generateContent(`심사할 텍스트:\n"""${i.slice(0, 4e3)}"""`)).response.text());
1175
1340
  if (!e) return c();
1176
1341
  let t = {
1177
1342
  flagged: e.flagged,
@@ -1183,12 +1348,12 @@ function at() {
1183
1348
  }),
1184
1349
  via: "ai"
1185
1350
  };
1186
- return t.flagged ? d(t) : t;
1351
+ return t.flagged ? u(t) : t;
1187
1352
  } catch (e) {
1188
1353
  return console.warn("[memi-board] moderation AI failed", e instanceof Error ? e.message : e), c();
1189
1354
  }
1190
1355
  }
1191
- async function p(t) {
1356
+ async function f(t) {
1192
1357
  if (n.value) return {
1193
1358
  flagged: !0,
1194
1359
  category: "other",
@@ -1196,14 +1361,14 @@ function at() {
1196
1361
  via: "restricted"
1197
1362
  };
1198
1363
  if (e.enabled === !1 || !e.moderateImages) return {
1199
- ...it,
1364
+ ...yt,
1200
1365
  via: "disabled"
1201
1366
  };
1202
1367
  try {
1203
1368
  let e = await new Promise((e, n) => {
1204
1369
  let r = new FileReader();
1205
1370
  r.onload = () => e(r.result.split(",")[1] ?? ""), r.onerror = n, r.readAsDataURL(t);
1206
- }), n = rt((await s().generateContent([{ inlineData: {
1371
+ }), n = vt((await s().generateContent([{ inlineData: {
1207
1372
  mimeType: t.type || "image/jpeg",
1208
1373
  data: e
1209
1374
  } }, "이 이미지를 심사해 주세요."])).response.text());
@@ -1218,14 +1383,14 @@ function at() {
1218
1383
  }),
1219
1384
  via: "ai"
1220
1385
  };
1221
- return r.flagged ? d(r) : r;
1386
+ return r.flagged ? u(r) : r;
1222
1387
  } catch (e) {
1223
1388
  return console.warn("[memi-board] moderation AI image failed", e instanceof Error ? e.message : e), c();
1224
1389
  }
1225
1390
  }
1226
1391
  return {
1227
- checkText: f,
1228
- checkImage: p
1392
+ checkText: d,
1393
+ checkImage: f
1229
1394
  };
1230
1395
  }
1231
1396
  //#endregion
@@ -1250,9 +1415,9 @@ async function X(e, { maxWidth: t = 400, quality: n = .8 } = {}) {
1250
1415
  }
1251
1416
  //#endregion
1252
1417
  //#region src/composables/useMemiBoardStorage.ts
1253
- var ot = 5242880, st = 26214400;
1254
- async function ct(e) {
1255
- if (e.size > 26214400) throw Error(`원본 이미지는 ${st / 1024 / 1024}MB 이하여야 합니다.`);
1418
+ var xt = 5242880, St = 26214400;
1419
+ async function Ct(e) {
1420
+ if (e.size > 26214400) throw Error(`원본 이미지는 ${St / 1024 / 1024}MB 이하여야 합니다.`);
1256
1421
  if (e.size <= 5242880) return {
1257
1422
  blob: e,
1258
1423
  compressed: !1
@@ -1274,10 +1439,10 @@ async function ct(e) {
1274
1439
  throw e instanceof Error && e.message.includes("압축 후에도") ? e : Error("이 이미지 형식은 브라우저에서 최적화할 수 없습니다. JPG, PNG 또는 WebP로 변환해 주세요.");
1275
1440
  }
1276
1441
  }
1277
- function lt() {
1278
- let e = G(), t = l(), n = () => e.collectionPrefix;
1442
+ function wt() {
1443
+ let e = W(), t = A(), n = () => e.collectionPrefix;
1279
1444
  function r(e, r, i) {
1280
- let a = V(t), o = `${n()}/posts/${r}/attachments/${Date.now()}-${Ee(e.name)}`, s = H(a, o), c = re(s, e, {
1445
+ let a = V(t), o = `${n()}/posts/${r}/attachments/${Date.now()}-${Ve(e.name)}`, s = H(a, o), c = re(s, e, {
1281
1446
  contentType: e.type || "application/octet-stream",
1282
1447
  contentDisposition: `attachment; filename*=UTF-8''${encodeURIComponent(e.name)}`
1283
1448
  });
@@ -1305,7 +1470,7 @@ function lt() {
1305
1470
  }
1306
1471
  async function a(e, r) {
1307
1472
  if (!e.type.startsWith("image/")) throw Error("이미지 파일만 업로드할 수 있습니다.");
1308
- let i = await ct(e), a = V(t), o = (i.compressed ? "jpg" : e.name.split(".").pop() || e.type.split("/")[1] || "png").replace(/[^a-zA-Z0-9]/g, "").slice(0, 8) || "png", s = Ee(e.name.replace(/\.[^.]+$/, "") || "image", 40), c = `${Date.now()}-${s || "image"}`, l = r || `new-${Date.now()}`, u = `${n()}/posts/${l}/images/${c}.${o}`, d = H(a, u);
1473
+ let i = await Ct(e), a = V(t), o = (i.compressed ? "jpg" : e.name.split(".").pop() || e.type.split("/")[1] || "png").replace(/[^a-zA-Z0-9]/g, "").slice(0, 8) || "png", s = Ve(e.name.replace(/\.[^.]+$/, "") || "image", 40), c = `${Date.now()}-${s || "image"}`, l = r || `new-${Date.now()}`, u = `${n()}/posts/${l}/images/${c}.${o}`, d = H(a, u);
1309
1474
  await ne(d, i.blob, { contentType: i.blob.type || "image/jpeg" });
1310
1475
  let f = await B(d), p = await X(i.blob, {
1311
1476
  maxWidth: 400,
@@ -1331,7 +1496,7 @@ function lt() {
1331
1496
  }
1332
1497
  //#endregion
1333
1498
  //#region src/composables/useMemiBoardUsers.ts
1334
- var ut = [
1499
+ var Tt = [
1335
1500
  {
1336
1501
  value: "admin",
1337
1502
  label: "관리자",
@@ -1348,24 +1513,29 @@ var ut = [
1348
1513
  description: "글과 댓글을 작성하고 본인이 작성한 콘텐츠만 수정·삭제할 수 있습니다."
1349
1514
  }
1350
1515
  ];
1351
- function dt(t = {}) {
1352
- let n = G(), r = d(), a = `${n.collectionPrefix}Users`, s = e(() => i(t.enabled) ?? !0 ? f(r, a) : null), c = o(s, { ssrKey: `${a}-management` });
1353
- async function l(e, t) {
1354
- if (!ut.some((e) => e.value === t)) throw Error("지원하지 않는 역할입니다.");
1355
- await A(h(r, a, e), {
1516
+ function Et(t = {}) {
1517
+ let n = W(), r = M(), a = `${n.collectionPrefix}Users`, s = e(() => i(t.enabled) ?? !0 ? o(r, a) : null), c = D(s, { ssrKey: `${a}-management` });
1518
+ async function u(e, t) {
1519
+ if (!Tt.some((e) => e.value === t)) throw Error("지원하지 않는 역할입니다.");
1520
+ await w(l(r, a, e), {
1356
1521
  role: t,
1357
- updatedAt: D()
1522
+ updatedAt: x()
1358
1523
  });
1359
1524
  }
1360
1525
  return {
1361
1526
  users: c,
1362
1527
  usersPending: c.pending,
1363
- updateUserRole: l
1528
+ updateUserRole: u
1364
1529
  };
1365
1530
  }
1366
1531
  //#endregion
1367
1532
  //#region src/data/versionHistory.ts
1368
- var ft = [
1533
+ var Dt = [
1534
+ {
1535
+ version: "0.19.0",
1536
+ date: "2026-08-06",
1537
+ highlights: ["게시판 목록·글 공유 미리보기(제목·설명·대표 이미지)를 모듈만으로 붙일 수 있어요", "호스트가 따로 SEO API를 만들 필요 없이, 공개 글 기준으로 미리보기가 채워져요"]
1538
+ },
1369
1539
  {
1370
1540
  version: "0.18.0",
1371
1541
  date: "2026-08-06",
@@ -1463,7 +1633,7 @@ var ft = [
1463
1633
  highlights: ["글쓰기·댓글·첨부·로그인 권한으로 게시판을 바로 시작할 수 있어요", "부적절한 글·댓글은 AI 검열로 막을 수 있어요"],
1464
1634
  note: "실제 보안은 호스트 Firebase 규칙 등록이 필요합니다."
1465
1635
  }
1466
- ], pt = /* @__PURE__ */ W(((e, t) => {
1636
+ ], Ot = /* @__PURE__ */ pe(((e, t) => {
1467
1637
  (function(n, r) {
1468
1638
  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();
1469
1639
  })(e, (function() {
@@ -1757,7 +1927,7 @@ var ft = [
1757
1927
  return w(1e3 * e);
1758
1928
  }, w.en = b[y], w.Ls = b, w.p = {}, w;
1759
1929
  }));
1760
- })), mt = /* @__PURE__ */ W(((e, t) => {
1930
+ })), kt = /* @__PURE__ */ pe(((e, t) => {
1761
1931
  (function(n, r) {
1762
1932
  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();
1763
1933
  })(e, (function() {
@@ -1861,9 +2031,9 @@ var ft = [
1861
2031
  };
1862
2032
  };
1863
2033
  }));
1864
- })), ht = /* @__PURE__ */ W(((e, t) => {
2034
+ })), At = /* @__PURE__ */ pe(((e, t) => {
1865
2035
  (function(n, r) {
1866
- typeof e == "object" && t !== void 0 ? t.exports = r(pt()) : typeof define == "function" && define.amd ? define(["dayjs"], r) : (n = typeof globalThis < "u" ? globalThis : n || self).dayjs_locale_ko = r(n.dayjs);
2036
+ typeof e == "object" && t !== void 0 ? t.exports = r(Ot()) : typeof define == "function" && define.amd ? define(["dayjs"], r) : (n = typeof globalThis < "u" ? globalThis : n || self).dayjs_locale_ko = r(n.dayjs);
1867
2037
  })(e, (function(e) {
1868
2038
  function t(e) {
1869
2039
  return e && typeof e == "object" && "default" in e ? e : { default: e };
@@ -1911,20 +2081,20 @@ var ft = [
1911
2081
  };
1912
2082
  return n.default.locale(r, null, !0), r;
1913
2083
  }));
1914
- })), Z = /* @__PURE__ */ me(pt(), 1), gt = /* @__PURE__ */ me(mt(), 1);
1915
- ht(), Z.default.extend(gt.default), Z.default.locale("ko");
1916
- function _t(e) {
2084
+ })), Z = /* @__PURE__ */ he(Ot(), 1), jt = /* @__PURE__ */ he(kt(), 1);
2085
+ At(), Z.default.extend(jt.default), Z.default.locale("ko");
2086
+ function Mt(e) {
1917
2087
  return e?.toDate ? e.toDate().toLocaleDateString("ko-KR") : "";
1918
2088
  }
1919
- function vt(e, t = Date.now()) {
2089
+ function Nt(e, t = Date.now()) {
1920
2090
  return e?.toDate ? (0, Z.default)(e.toDate()).from((0, Z.default)(t)) : "방금 전";
1921
2091
  }
1922
- function yt(e) {
2092
+ function Pt(e) {
1923
2093
  return e?.toDate ? (0, Z.default)(e.toDate()).format("YYYY년 M월 D일 HH:mm:ss") : "시간 확인 중";
1924
2094
  }
1925
- function bt(e, t) {
1926
- let n = yt(e), r = e?.toMillis?.(), i = t?.toMillis?.();
1927
- return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${yt(t)}`];
2095
+ function Ft(e, t) {
2096
+ let n = Pt(e), r = e?.toMillis?.(), i = t?.toMillis?.();
2097
+ return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${Pt(t)}`];
1928
2098
  }
1929
2099
  //#endregion
1930
2100
  //#region src/utils/renderMarkdown.ts
@@ -1935,7 +2105,7 @@ function $(e) {
1935
2105
  let t = e;
1936
2106
  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;
1937
2107
  }
1938
- function xt(e) {
2108
+ function It(e) {
1939
2109
  if (!e?.trim()) return "";
1940
2110
  let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = 0, i = !1, a = [], o = null, s = [];
1941
2111
  function c() {
@@ -2000,6 +2170,6 @@ function xt(e) {
2000
2170
  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");
2001
2171
  }
2002
2172
  //#endregion
2003
- export { ut as BOARD_USER_ROLES, Ke as COMMENT_BODY_MAX_LENGTH, K as DEFAULT_BLOCK_BAN_DECAY_MS, ve as DEFAULT_BLOCK_BAN_THRESHOLD, Pe as DEFAULT_CATEGORIES, ot as EDITOR_IMAGE_MAX_BYTES, st as EDITOR_IMAGE_SOURCE_MAX_BYTES, Ne as buildPostPreview, X as compressImage, _e as configureMemiBoard, ye as effectiveModerationBlockCount, De as extractEditorImageUrls, _t as formatDate, yt as formatFullDate, vt as formatRelativeDate, xe as formatRestrictedUntilLabel, bt as formatTimestampDetails, ze as isFirestorePermissionDenied, be as isModerationWriteRestricted, q as moderationWriteRestrictedUntilMs, ke as postNamespaceFromStoragePath, xt as renderMarkdownToHtml, Te as slugify, Oe as storagePathFromDownloadUrl, J as useMemiBoardAuth, Je as useMemiBoardComments, G as useMemiBoardConfig, Xe as useMemiBoardLikes, at as useMemiBoardModeration, Ue as useMemiBoardPost, He as useMemiBoardPostList, Be as useMemiBoardPosts, Ye as useMemiBoardReplies, Ie as useMemiBoardSettings, lt as useMemiBoardStorage, dt as useMemiBoardUsers, Ze as useMemiBoardViews, ft as versionHistory, Me as videoListCoverUrl, je as youtubeId };
2173
+ export { Tt as BOARD_USER_ROLES, st as COMMENT_BODY_MAX_LENGTH, G as DEFAULT_BLOCK_BAN_DECAY_MS, Pe as DEFAULT_BLOCK_BAN_THRESHOLD, Ye as DEFAULT_CATEGORIES, xt as EDITOR_IMAGE_MAX_BYTES, St as EDITOR_IMAGE_SOURCE_MAX_BYTES, ye as asHttpUrl, we as boardListOgDescription, Ce as boardListOgTitle, Se as boardPostOgDescription, xe as boardPostOgTitle, Je as buildPostPreview, X as compressImage, ve as configureMemiBoard, Fe as effectiveModerationBlockCount, He as extractEditorImageUrls, Me as fetchPublicListForSeo, je as fetchPublicPostForSeo, Mt as formatDate, Pt as formatFullDate, Nt as formatRelativeDate, Le as formatRestrictedUntilLabel, Ft as formatTimestampDetails, et as isFirestorePermissionDenied, Ie as isModerationWriteRestricted, K as moderationWriteRestrictedUntilMs, Te as normalizeBasePath, We as postNamespaceFromStoragePath, It as renderMarkdownToHtml, Ee as resolvePublicSeoDb, Be as slugify, Ue as storagePathFromDownloadUrl, be as toAbsoluteUrl, J as useMemiBoardAuth, lt as useMemiBoardComments, W as useMemiBoardConfig, dt as useMemiBoardLikes, bt as useMemiBoardModeration, it as useMemiBoardPost, rt as useMemiBoardPostList, tt as useMemiBoardPosts, ut as useMemiBoardReplies, Ze as useMemiBoardSettings, wt as useMemiBoardStorage, Et as useMemiBoardUsers, ft as useMemiBoardViews, Dt as versionHistory, qe as videoListCoverUrl, Ke as youtubeId };
2004
2174
 
2005
2175
  //# sourceMappingURL=index.js.map