memi-board 0.19.0 → 0.19.3

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
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
3
  import { useCollection as D, useCurrentUser as O, useDocument as k, useFirebaseApp as A, useFirebaseAuth as j, useFirestore as M } from "vuefire";
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
- 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
- import { GoogleAIBackend as ie, Schema as U, getAI as ae, getGenerativeModel as oe } from "firebase/ai";
4
+ import { GoogleAuthProvider as N, OAuthProvider as P, createUserWithEmailAndPassword as F, signInWithEmailAndPassword as I, signInWithPopup as L, signOut as R, updateProfile as z } from "firebase/auth";
5
+ import { deleteObject as B, getDownloadURL as V, getStorage as H, listAll as ee, ref as U, uploadBytes as te, uploadBytesResumable as ne } from "firebase/storage";
6
+ import { GoogleAIBackend as re, Schema as W, getAI as ie, getGenerativeModel as ae } 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, pe = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t.exports), me = (e, t, n, r) => {
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, {
8
+ var oe = Object.create, se = Object.defineProperty, ce = Object.getOwnPropertyDescriptor, le = Object.getOwnPropertyNames, ue = Object.getPrototypeOf, de = Object.prototype.hasOwnProperty, fe = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t.exports), pe = (e, t, n, r) => {
9
+ if (t && typeof t == "object" || typeof t == "function") for (var i = le(t), a = 0, o = i.length, s; a < o; a++) s = i[a], !de.call(e, s) && s !== n && se(e, s, {
10
10
  get: ((e) => t[e]).bind(null, s),
11
- enumerable: !(r = le(t, s)) || r.enumerable
11
+ enumerable: !(r = ce(t, s)) || r.enumerable
12
12
  });
13
13
  return e;
14
- }, he = (e, t, n) => (n = e == null ? {} : se(de(e)), me(t || !e || !e.__esModule || !fe.call(e, "default") ? ce(n, "default", {
14
+ }, me = (e, t, n) => (n = e == null ? {} : oe(ue(e)), pe(t || !e || !e.__esModule || !de.call(e, "default") ? se(n, "default", {
15
15
  value: e,
16
16
  enumerable: !0
17
- }) : n, e)), ge = "__MEMI_BOARD_CONFIG__";
18
- function _e() {
17
+ }) : n, e)), he = "__MEMI_BOARD_CONFIG__";
18
+ function ge() {
19
19
  let e = globalThis;
20
- return e[ge] || (e[ge] = n({
20
+ return e[he] || (e[he] = n({
21
21
  collectionPrefix: "memiBoard",
22
22
  auth: { providers: ["google", "apple"] },
23
23
  moderation: {
@@ -36,29 +36,47 @@ function _e() {
36
36
  defaultOgImage: "",
37
37
  basePath: "/board"
38
38
  }
39
- })), e[ge];
39
+ })), e[he];
40
40
  }
41
- function ve(e) {
42
- let t = _e();
41
+ function _e(e) {
42
+ let t = ge();
43
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
44
  }
45
- function W() {
46
- return _e();
45
+ function G() {
46
+ return ge();
47
47
  }
48
48
  //#endregion
49
49
  //#region src/utils/boardSeo.ts
50
- function ye(e) {
50
+ function ve(e) {
51
51
  if (typeof e != "string") return null;
52
52
  let t = e.trim();
53
53
  return /^https?:\/\//i.test(t) ? t : null;
54
54
  }
55
+ function ye(e) {
56
+ let t = ve(e);
57
+ if (!t) return null;
58
+ if (!/firebasestorage\.googleapis\.com/i.test(t)) return t;
59
+ try {
60
+ let e = new URL(t), n = e.pathname.split("/"), r = n[3], i = n.indexOf("o");
61
+ if (!r || i < 0 || !n[i + 1]) return t;
62
+ let a = decodeURIComponent(n.slice(i + 1).join("/")), o = a;
63
+ if (!a.includes("/images/thumbnails/")) {
64
+ let e = a.replace(/\/images\/([^/]+)$/, (e, t) => `/images/thumbnails/${String(t).replace(/\.[^.]+$/, "")}.jpg`);
65
+ if (e === a) return t;
66
+ o = e;
67
+ }
68
+ return `${e.origin}/v0/b/${r}/o/${encodeURIComponent(o)}?alt=media`;
69
+ } catch {
70
+ return t;
71
+ }
72
+ }
55
73
  function be(e, t, n = "") {
56
74
  let r = t.replace(/\/$/, ""), i = e.trim();
57
75
  return i ? /^https?:\/\//i.test(i) ? i : `${r}${i.startsWith("/") ? i : `/${i}`}` : n ? be(n, t) : r;
58
76
  }
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}`;
77
+ function xe(e, t, n, r) {
78
+ let i = r?.trim().slice(0, 40), a = e.trim() || i || "사진", o = t?.trim(), s = n.trim() || "Board";
79
+ return o ? `${a} · ${o} | ${s}` : `${a} | ${s}`;
62
80
  }
63
81
  function Se(e) {
64
82
  let t = [];
@@ -82,7 +100,7 @@ function Te(e) {
82
100
  function Ee() {
83
101
  return {
84
102
  db: M(),
85
- prefix: W().collectionPrefix
103
+ prefix: G().collectionPrefix
86
104
  };
87
105
  }
88
106
  function De(e) {
@@ -205,8 +223,8 @@ async function Ne(e, t) {
205
223
  }
206
224
  //#endregion
207
225
  //#region src/utils/moderation-strike.ts
208
- var Pe = 3, G = 864e5;
209
- function Fe(e, t, n = Date.now(), r = G) {
226
+ var Pe = 3, K = 864e5;
227
+ function Fe(e, t, n = Date.now(), r = K) {
210
228
  let i = Math.max(0, Math.floor(Number(e) || 0));
211
229
  if (i <= 0) return 0;
212
230
  let a = typeof t == "number" && Number.isFinite(t) ? t : null;
@@ -214,7 +232,7 @@ function Fe(e, t, n = Date.now(), r = G) {
214
232
  let o = Math.floor((n - a) / r);
215
233
  return Math.max(0, i - o);
216
234
  }
217
- function K(e, t, n = Date.now(), r = 3, i = G) {
235
+ function q(e, t, n = Date.now(), r = 3, i = K) {
218
236
  let a = Math.max(0, Math.floor(Number(e) || 0));
219
237
  if (a < r) return null;
220
238
  let o = typeof t == "number" && Number.isFinite(t) ? t : null;
@@ -222,8 +240,8 @@ function K(e, t, n = Date.now(), r = 3, i = G) {
222
240
  let s = o + (a - (r - 1)) * i;
223
241
  return s > n ? s : null;
224
242
  }
225
- function Ie(e, t, n = Date.now(), r = 3, i = G) {
226
- return K(e, t, n, r, i) != null;
243
+ function Ie(e, t, n = Date.now(), r = 3, i = K) {
244
+ return q(e, t, n, r, i) != null;
227
245
  }
228
246
  function Le(e, t = Date.now()) {
229
247
  let n = Math.max(0, e - t), r = Math.ceil(n / 36e5);
@@ -244,26 +262,26 @@ function Re(e) {
244
262
  }
245
263
  //#endregion
246
264
  //#region src/composables/useMemiBoardAuth.ts
247
- var q = "__MEMI_BOARD_AUTH_STATE__";
248
- function ze() {
265
+ var ze = "__MEMI_BOARD_AUTH_STATE__";
266
+ function Be() {
249
267
  let e = globalThis;
250
- return e[q] || (e[q] = {
268
+ return e[ze] || (e[ze] = {
251
269
  role: r("user"),
252
270
  rolePending: r(!0),
253
271
  rawBlockCount: r(0),
254
272
  blockAtMs: r(null),
255
273
  watchStarted: !1
256
- }), e[q];
274
+ }), e[ze];
257
275
  }
258
276
  function J() {
259
- let t = W(), n = ze(), r = M(), i = j(), o = O(), { role: s, rolePending: c, rawBlockCount: u, blockAtMs: d } = n;
277
+ let t = G(), n = Be(), r = M(), i = j(), o = O(), { role: s, rolePending: c, rawBlockCount: u, blockAtMs: d } = n;
260
278
  function p() {
261
279
  let e = t.moderation?.blockBanThreshold;
262
280
  return e === 0 ? 0 : typeof e == "number" && e > 0 ? Math.floor(e) : 3;
263
281
  }
264
282
  function h() {
265
283
  let e = t.moderation?.blockBanDecayMs;
266
- return typeof e == "number" && e > 0 ? e : G;
284
+ return typeof e == "number" && e > 0 ? e : K;
267
285
  }
268
286
  function g(e) {
269
287
  return l(r, `${t.collectionPrefix}Users`, e);
@@ -324,14 +342,14 @@ function J() {
324
342
  }), A = e(() => {
325
343
  let e = p();
326
344
  if (e <= 0) return null;
327
- let t = K(u.value, d.value, Date.now(), e, h());
345
+ let t = q(u.value, d.value, Date.now(), e, h());
328
346
  return t == null ? null : Le(t);
329
- }), z = e(() => {
347
+ }), B = e(() => {
330
348
  if (!k.value) return null;
331
349
  let e = A.value;
332
350
  return e ? `콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요. (${e})` : "콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요.";
333
351
  });
334
- async function B() {
352
+ async function V() {
335
353
  let e = o.value?.uid, t = p();
336
354
  if (!e || t <= 0) return {
337
355
  effectiveCount: 0,
@@ -354,7 +372,7 @@ function J() {
354
372
  u.value = i, d.value = a;
355
373
  let s = Fe(i, a, a, h()), c = Ie(i, a, a, t, h()), l = ` (경고 ${s}/${t})`;
356
374
  if (c) {
357
- let e = K(i, a, a, t, h()), n = e == null ? null : Le(e, a);
375
+ let e = q(i, a, a, t, h()), n = e == null ? null : Le(e, a);
358
376
  l = n ? ` 경고 ${s}회가 되어 글·댓글 작성이 제한됩니다 (${n}).` : ` 경고 ${s}회가 되어 글·댓글 작성이 제한됩니다.`;
359
377
  }
360
378
  return {
@@ -363,34 +381,34 @@ function J() {
363
381
  messageSuffix: l
364
382
  };
365
383
  }
366
- function V() {
384
+ function H() {
367
385
  if (!i) throw Error("[memi-board] Firebase Auth가 초기화되지 않았습니다.");
368
386
  return i;
369
387
  }
370
- async function te() {
371
- return L(V(), new N());
388
+ async function ee() {
389
+ return L(H(), new N());
372
390
  }
373
- async function H() {
391
+ async function U() {
374
392
  let e = new P("apple.com");
375
- return e.addScope("email"), e.addScope("name"), L(V(), e);
393
+ return e.addScope("email"), e.addScope("name"), L(H(), e);
376
394
  }
377
- async function ne(e, t) {
378
- return I(V(), e, t);
395
+ async function te(e, t) {
396
+ return I(H(), e, t);
379
397
  }
380
- async function re(e, t, n) {
381
- let r = await F(V(), e, t);
382
- return n && await ee(r.user, { displayName: n }), r;
398
+ async function ne(e, t, n) {
399
+ let r = await F(H(), e, t);
400
+ return n && await z(r.user, { displayName: n }), r;
383
401
  }
384
- async function ie() {
385
- await R(V());
402
+ async function re() {
403
+ await R(H());
386
404
  }
387
- function U(e) {
405
+ function W(e) {
388
406
  return b.value && (o.value?.uid === e.authorUid || E.value);
389
407
  }
390
- function ae(e) {
408
+ function ie(e) {
391
409
  return b.value && o.value?.uid === e.authorUid;
392
410
  }
393
- function oe(e) {
411
+ function ae(e) {
394
412
  return b.value && (o.value?.uid === e.authorUid || E.value);
395
413
  }
396
414
  return {
@@ -403,31 +421,31 @@ function J() {
403
421
  moderationBlockCount: D,
404
422
  isWriteRestricted: k,
405
423
  restrictedUntilLabel: A,
406
- restrictedMessage: z,
407
- signInWithGoogle: te,
408
- signInWithApple: H,
409
- signInWithEmail: ne,
410
- signUpWithEmail: re,
411
- signOut: ie,
412
- canEdit: U,
413
- canDelete: U,
414
- canEditComment: ae,
415
- canDeleteComment: oe,
416
- recordContentModerationBlock: B,
424
+ restrictedMessage: B,
425
+ signInWithGoogle: ee,
426
+ signInWithApple: U,
427
+ signInWithEmail: te,
428
+ signUpWithEmail: ne,
429
+ signOut: re,
430
+ canEdit: W,
431
+ canDelete: W,
432
+ canEditComment: ie,
433
+ canDeleteComment: ae,
434
+ recordContentModerationBlock: V,
417
435
  refreshBoardUser: y
418
436
  };
419
437
  }
420
438
  //#endregion
421
439
  //#region src/utils/slugify.ts
422
- function Be(e) {
440
+ function Ve(e) {
423
441
  return e.trim().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9가-힣ㄱ-ㅎㅏ-ㅣ-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
424
442
  }
425
- function Ve(e, t = 60) {
443
+ function He(e, t = 60) {
426
444
  return e.replace(/[^a-zA-Z0-9가-힣ㄱ-ㅎ._-]/g, "-").slice(0, t);
427
445
  }
428
446
  //#endregion
429
447
  //#region src/utils/extractEditorImageUrls.ts
430
- function He(e) {
448
+ function Ue(e) {
431
449
  if (!e) return [];
432
450
  let t = /* @__PURE__ */ new Set();
433
451
  for (let n of e.matchAll(/!\[[^\]]*]\((https?:\/\/[^)\s]+)\)/g)) n[1] && t.add(n[1].replaceAll("&amp;", "&"));
@@ -436,7 +454,7 @@ function He(e) {
436
454
  }
437
455
  //#endregion
438
456
  //#region src/utils/storagePath.ts
439
- function Ue(e) {
457
+ function We(e) {
440
458
  try {
441
459
  let t = e.match(/\/o\/([^?]+)/);
442
460
  return t?.[1] ? decodeURIComponent(t[1]) : null;
@@ -444,29 +462,50 @@ function Ue(e) {
444
462
  return null;
445
463
  }
446
464
  }
447
- function We(e) {
465
+ function Ge(e) {
448
466
  return e.match(/(?:^|\/)posts\/([^/]+)\//)?.[1] ?? null;
449
467
  }
450
468
  //#endregion
469
+ //#region src/utils/postBody.ts
470
+ function Ke(e) {
471
+ 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();
472
+ }
473
+ function qe(e, t = 40) {
474
+ let n = Ke(e);
475
+ if (!n) return "";
476
+ if (n.length <= t) return n;
477
+ let r = n.slice(0, t), i = r.lastIndexOf(" ");
478
+ return `${(i >= Math.floor(t * .6) ? r.slice(0, i) : r).trimEnd()}…`;
479
+ }
480
+ function Je(e) {
481
+ return Ke(e).length > 0;
482
+ }
483
+ function Ye(e, t) {
484
+ return /<img\b/i.test(String(e || "")) ? !0 : (t || []).some((e) => !!e?.type?.startsWith("image/"));
485
+ }
486
+ function Xe(e) {
487
+ return !Je(e);
488
+ }
489
+ //#endregion
451
490
  //#region src/utils/postPreview.ts
452
- function Ge(e) {
491
+ function Ze(e) {
453
492
  return e.replace(/&amp;/g, "&").replace(/&quot;/g, "\"").replace(/&#39;/g, "'");
454
493
  }
455
- function Ke(e) {
494
+ function Qe(e) {
456
495
  if (e) try {
457
- let t = new URL(Ge(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
496
+ let t = new URL(Ze(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
458
497
  if (n === "youtu.be") return t.pathname.split("/").filter(Boolean)[0];
459
498
  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;
460
499
  } catch {
461
500
  return;
462
501
  }
463
502
  }
464
- function qe(e) {
465
- let t = Ke(e.videoUrl);
503
+ function $e(e) {
504
+ let t = Qe(e.videoUrl);
466
505
  return t ? `https://i.ytimg.com/vi/${t}/hqdefault.jpg` : e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
467
506
  }
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);
507
+ function et(e, t = []) {
508
+ 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 ? Ze(r[1]) : a, s = o ? Qe(o) : void 0, c = n ? Ze(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
470
509
  return {
471
510
  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),
472
511
  ...c ? { previewImage: c } : {},
@@ -475,7 +514,7 @@ function Je(e, t = []) {
475
514
  }
476
515
  //#endregion
477
516
  //#region src/composables/useMemiBoardSettings.ts
478
- var Ye = [
517
+ var tt = [
479
518
  {
480
519
  id: "free",
481
520
  label: "자유",
@@ -504,16 +543,16 @@ var Ye = [
504
543
  order: 2
505
544
  }
506
545
  ];
507
- function Xe(e) {
546
+ function nt(e) {
508
547
  return e === "hidden" ? "hidden" : "public";
509
548
  }
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) => ({
549
+ function rt() {
550
+ let t = G(), 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) => ({
512
551
  ...e,
513
552
  id: e.id,
514
553
  order: typeof e.order == "number" ? e.order : t,
515
554
  description: e.description ?? e.desc ?? "",
516
- visibility: Xe(e.visibility),
555
+ visibility: nt(e.visibility),
517
556
  listView: e.listView ?? "default",
518
557
  writeRole: e.writeRole ?? "user",
519
558
  commentWriteRole: e.commentWriteRole ?? "user",
@@ -545,7 +584,7 @@ function Ze() {
545
584
  if (!r.value) throw Error("로그인이 필요합니다.");
546
585
  let n = e.id.trim(), o = e.label.trim();
547
586
  if (!n || !o) throw Error("카테고리 ID와 이름을 입력해 주세요.");
548
- let s = Xe(e.visibility), c = f.value.find((e) => e.id === n)?.visibility ?? "public";
587
+ let s = nt(e.visibility), c = f.value.find((e) => e.id === n)?.visibility ?? "public";
549
588
  await S(i.value, { updatedAt: x() }, { merge: !0 }), await S(l(a.value, n), {
550
589
  label: o,
551
590
  description: (e.description ?? "").trim(),
@@ -566,7 +605,7 @@ function Ze() {
566
605
  e.forEach((e, n) => {
567
606
  let r = e.id.trim(), i = e.label.trim();
568
607
  if (!r || !i) throw Error("카테고리 ID와 이름을 입력해 주세요.");
569
- let c = Xe(e.visibility);
608
+ let c = nt(e.visibility);
570
609
  o.set(l(a.value, r), {
571
610
  label: i,
572
611
  description: (e.description ?? "").trim(),
@@ -597,7 +636,7 @@ function Ze() {
597
636
  if (!r.value) throw Error("로그인이 필요합니다.");
598
637
  let n = f.value.find((e) => e.label === t);
599
638
  if (n) return n.id;
600
- let i = Be(t) || `cat-${Date.now()}`, a = i, o = 2, s = new Set(f.value.map((e) => e.id));
639
+ let i = Ve(t) || `cat-${Date.now()}`, a = i, o = 2, s = new Set(f.value.map((e) => e.id));
601
640
  for (; s.has(a);) a = `${i}-${o++}`;
602
641
  return await k({
603
642
  id: a,
@@ -622,39 +661,51 @@ function Ze() {
622
661
  saveCategory: k,
623
662
  saveCategories: A,
624
663
  deleteCategory: j,
625
- DEFAULT_CATEGORIES: Ye
664
+ DEFAULT_CATEGORIES: tt
626
665
  };
627
666
  }
628
667
  //#endregion
629
668
  //#region src/composables/useMemiBoardPosts.ts
630
- async function Qe(e) {
631
- let t = await te(e).catch(() => ({
669
+ async function it(e) {
670
+ let t = await ee(e).catch(() => ({
632
671
  items: [],
633
672
  prefixes: []
634
673
  }));
635
- await Promise.all([...t.items.map((e) => z(e)), ...t.prefixes.map((e) => Qe(e))]);
674
+ await Promise.all([...t.items.map((e) => B(e)), ...t.prefixes.map((e) => it(e))]);
636
675
  }
637
- async function $e(e, t) {
676
+ async function at(e, t) {
638
677
  let n = [...new Set([...t].filter(Boolean))];
639
- await Promise.all(n.map((t) => z(H(e, t)).catch(() => {})));
678
+ await Promise.all(n.map((t) => B(U(e, t)).catch(() => {})));
640
679
  }
641
- function et(e) {
680
+ function ot(e) {
642
681
  if (!e || typeof e != "object") return !1;
643
682
  let t = "code" in e ? String(e.code) : "";
644
683
  return t === "permission-denied" || t === "firestore/permission-denied";
645
684
  }
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) {
685
+ function st() {
686
+ let e = G(), t = M(), n = A(), { isCategoryHidden: r, categories: i } = rt(), a = () => e.collectionPrefix, u = () => o(t, `${a()}Posts`), m = (e) => l(t, `${a()}Posts`, e), _ = (e) => l(t, `${a()}Posts`, e, "body", "main"), b = (e) => o(t, `${a()}Posts`, e, "comments");
687
+ function D(e) {
649
688
  return !r(e);
650
689
  }
651
- function D() {
652
- return l(a()).id;
690
+ function O(e) {
691
+ return e ? i.value.find((t) => t.id === e)?.listView === "image" : !1;
653
692
  }
654
- async function O(e = {}) {
693
+ function k(e) {
694
+ let t = O(e.category);
695
+ if (!Je(e.content)) throw Error(t ? "내용을 입력해 주세요." : "본문에 글자를 입력해 주세요. 이미지나 첨부만으로는 게시할 수 없습니다.");
696
+ if (t) {
697
+ if (!Ye(e.content, e.attachments)) throw Error("사진을 올려 주세요.");
698
+ return;
699
+ }
700
+ if (!e.title?.trim()) throw Error("제목을 입력해 주세요.");
701
+ }
702
+ function j() {
703
+ return l(u()).id;
704
+ }
705
+ async function N(e = {}) {
655
706
  let t = e.pageSize ?? 20, n = [];
656
707
  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);
708
+ let r = await p(y(u(), ...n)), i = r.docs.slice(0, t);
658
709
  return {
659
710
  posts: i.map((e) => ({
660
711
  id: e.id,
@@ -664,38 +715,38 @@ function tt() {
664
715
  hasMore: r.docs.length > t
665
716
  };
666
717
  }
667
- async function k(e) {
668
- let [t, n] = await Promise.all([f(u(e)), f(m(e))]);
718
+ async function P(e) {
719
+ let [t, n] = await Promise.all([f(m(e)), f(_(e))]);
669
720
  return t.exists() ? {
670
721
  id: t.id,
671
722
  ...t.data(),
672
723
  content: n.exists() ? n.data().content ?? "" : ""
673
724
  } : null;
674
725
  }
675
- async function j(e, t) {
726
+ async function F(e, t) {
676
727
  try {
677
- let n = (await p(y(a(), T("category", "==", e), T("slug", "==", t), h(1)))).docs[0];
728
+ let n = (await p(y(u(), T("category", "==", e), T("slug", "==", t), h(1)))).docs[0];
678
729
  if (!n) return { status: "not-found" };
679
730
  try {
680
- let e = await k(n.id);
731
+ let e = await P(n.id);
681
732
  return e ? {
682
733
  status: "ok",
683
734
  post: e
684
735
  } : { status: "not-found" };
685
736
  } catch (e) {
686
- return et(e) ? { status: "permission-denied" } : {
737
+ return ot(e) ? { status: "permission-denied" } : {
687
738
  status: "error",
688
739
  message: e instanceof Error ? e.message : String(e)
689
740
  };
690
741
  }
691
742
  } catch (e) {
692
- return et(e) ? { status: "permission-denied" } : {
743
+ return ot(e) ? { status: "permission-denied" } : {
693
744
  status: "error",
694
745
  message: e instanceof Error ? e.message : String(e)
695
746
  };
696
747
  }
697
748
  }
698
- async function N(e) {
749
+ async function I(e) {
699
750
  if (!e.category || !e.createdAt) return {
700
751
  previous: null,
701
752
  next: null
@@ -705,7 +756,7 @@ function tt() {
705
756
  T("category", "==", e.category),
706
757
  ...e.listed === !1 ? [] : [T("listed", "==", !0)],
707
758
  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) => {
759
+ ], [n, r] = await Promise.all([p(y(u(), ...t, C(e.createdAt), h(1))), p(y(u(), ...t, d(e.createdAt), g(1)))]), i = (e) => {
709
760
  let t = e.docs[0];
710
761
  return t ? {
711
762
  id: t.id,
@@ -723,21 +774,29 @@ function tt() {
723
774
  };
724
775
  }
725
776
  }
726
- async function P(e) {
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), {
731
- slug: n,
732
- title: e.title,
733
- ...o,
777
+ async function L(e) {
778
+ k(e);
779
+ let t = e.postId || j(), n = O(e.category), r, i;
780
+ if (n) r = t, i = qe(e.content);
781
+ else {
782
+ i = e.title.trim();
783
+ let t = Ve(i) || "post";
784
+ r = t;
785
+ let n = 1;
786
+ for (; !(await p(y(u(), T("category", "==", e.category || ""), T("slug", "==", r), h(1)))).empty;) n++, r = `${t}-${n}`;
787
+ }
788
+ let a = et(e.content, e.attachments), o = D(e.category);
789
+ return await S(m(t), {
790
+ slug: r,
791
+ title: i,
792
+ ...a,
734
793
  tags: e.tags ?? [],
735
794
  ...e.category ? { category: e.category } : {},
736
795
  attachments: e.attachments ?? [],
737
796
  commentCount: 0,
738
797
  likeCount: 0,
739
798
  viewCount: 0,
740
- listed: s,
799
+ listed: o,
741
800
  authorUid: e.authorUid,
742
801
  authorName: e.authorName,
743
802
  authorPhoto: e.authorPhoto,
@@ -745,65 +804,66 @@ function tt() {
745
804
  moderationModel: e.moderationModel ?? null,
746
805
  createdAt: x(),
747
806
  updatedAt: x()
748
- }), await S(m(i), { content: e.content }), n;
807
+ }), await S(_(t), { content: e.content }), r;
749
808
  }
750
- async function F(e, t) {
751
- let n = Je(t.content, t.attachments), r = b(t.category);
752
- await Promise.all([w(u(e), {
753
- title: t.title,
754
- ...n,
755
- previewImage: n.previewImage ? n.previewImage : c(),
756
- videoUrl: n.videoUrl ? n.videoUrl : c(),
809
+ async function R(e, t) {
810
+ k(t);
811
+ let n = O(t.category) ? qe(t.content) : t.title.trim(), r = et(t.content, t.attachments), i = D(t.category);
812
+ await Promise.all([w(m(e), {
813
+ title: n,
814
+ ...r,
815
+ previewImage: r.previewImage ? r.previewImage : c(),
816
+ videoUrl: r.videoUrl ? r.videoUrl : c(),
757
817
  tags: t.tags ?? [],
758
818
  category: t.category ? t.category : c(),
759
- listed: r,
819
+ listed: i,
760
820
  attachments: t.attachments ?? [],
761
821
  updatedAt: x()
762
- }), S(m(e), { content: t.content }, { merge: !0 })]);
822
+ }), S(_(e), { content: t.content }, { merge: !0 })]);
763
823
  }
764
- async function I(e) {
765
- let [r, a] = await Promise.all([f(u(e)), f(m(e))]);
824
+ async function z(e) {
825
+ let [r, i] = await Promise.all([f(m(e)), f(_(e))]);
766
826
  if (!r.exists()) return;
767
- let o = r.data(), c = a.exists() ? String(a.data().content ?? "") : "", l = await p(_(e));
827
+ let o = r.data(), c = i.exists() ? String(i.data().content ?? "") : "", l = await p(b(e));
768
828
  for (let e = 0; e < l.docs.length; e += 450) {
769
829
  let n = E(t);
770
830
  l.docs.slice(e, e + 450).forEach((e) => n.delete(e.ref)), await n.commit();
771
831
  }
772
- let d = V(n), h = /* @__PURE__ */ new Set(), g = /* @__PURE__ */ new Set();
832
+ let u = H(n), d = /* @__PURE__ */ new Set(), h = /* @__PURE__ */ new Set();
773
833
  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);
834
+ d.add(t.path);
835
+ let n = Ge(t.path);
836
+ n && n !== e && h.add(n);
777
837
  }
778
- for (let t of He(c)) {
779
- let n = Ue(t);
838
+ for (let t of Ue(c)) {
839
+ let n = We(t);
780
840
  if (!n) continue;
781
- h.add(n);
841
+ d.add(n);
782
842
  let r = n.replace(/\/images\/([^/]+)$/, "/images/thumbnails/$1").replace(/\.[^.]+$/, ".jpg");
783
- r !== n && r.includes("/images/thumbnails/") && h.add(r);
843
+ r !== n && r.includes("/images/thumbnails/") && d.add(r);
784
844
  let i = n.match(/^(.*\/images\/)([^/]+)\.[^.]+$/);
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);
845
+ i && !n.includes("/thumbnails/") && d.add(`${i[1]}thumbnails/${i[2]}.jpg`);
846
+ let a = Ge(n);
847
+ a && a !== e && h.add(a);
788
848
  }
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));
849
+ await s(_(e)), await it(U(u, `${a()}/posts/${e}`));
850
+ for (let e of h) await it(U(u, `${a()}/posts/${e}`));
851
+ await at(u, d), await s(m(e));
792
852
  }
793
853
  return {
794
- createPostId: D,
795
- getPosts: O,
796
- getPost: k,
797
- getPostBySlug: j,
798
- getAdjacentPosts: N,
799
- createPost: P,
800
- updatePost: F,
801
- deletePost: I
854
+ createPostId: j,
855
+ getPosts: N,
856
+ getPost: P,
857
+ getPostBySlug: F,
858
+ getAdjacentPosts: I,
859
+ createPost: L,
860
+ updatePost: R,
861
+ deletePost: z
802
862
  };
803
863
  }
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;
864
+ var ct = 10;
865
+ function lt(n, i = {}) {
866
+ let s = G(), c = M(), { isAdmin: l, isStaff: d } = J(), f = () => s.collectionPrefix, m = () => o(c, `${f()}Posts`), g = i.pageSize ?? ct, 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;
807
867
  function P(e) {
808
868
  return e.createdAt?.toMillis?.() ?? 0;
809
869
  }
@@ -848,10 +908,10 @@ function rt(n, i = {}) {
848
908
  }
849
909
  }
850
910
  }
851
- function ee() {
911
+ function z() {
852
912
  N?.(), S.value = [], w.value = [], A = null, j = null, D.value = !0, k.value = "", E.value = !0, L();
853
913
  }
854
- return a([b, x], ee, { immediate: !0 }), t(() => N?.()), {
914
+ return a([b, x], z, { immediate: !0 }), t(() => N?.()), {
855
915
  posts: F,
856
916
  postsPending: E,
857
917
  hasMore: D,
@@ -860,8 +920,8 @@ function rt(n, i = {}) {
860
920
  loadMore: R
861
921
  };
862
922
  }
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);
923
+ function ut(t) {
924
+ let n = G(), 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);
865
925
  return {
866
926
  post: e(() => c.value ? {
867
927
  ...c.value,
@@ -873,15 +933,15 @@ function it(t) {
873
933
  }
874
934
  //#endregion
875
935
  //#region src/composables/useMemiBoardComments.ts
876
- var at = 10, ot = 5, st = 1e3;
877
- function ct(e) {
936
+ var dt = 10, ft = 5, pt = 1e3;
937
+ function mt(e) {
878
938
  let t = e.trim();
879
939
  if (!t) throw Error("댓글 내용을 입력해 주세요.");
880
- if (t.length > 1e3) throw Error(`댓글은 ${st.toLocaleString()}자까지 작성할 수 있습니다.`);
940
+ if (t.length > 1e3) throw Error(`댓글은 ${pt.toLocaleString()}자까지 작성할 수 있습니다.`);
881
941
  return t;
882
942
  }
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;
943
+ function ht(n, i = {}) {
944
+ let s = G(), 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;
885
945
  function F(e) {
886
946
  return e.createdAt?.toMillis?.() ?? 0;
887
947
  }
@@ -934,11 +994,11 @@ function lt(n, i = {}) {
934
994
  v(u(), "asc"),
935
995
  ...j ? [C(j)] : [],
936
996
  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) => ({
997
+ ], t = await p(y(o(c, `${d()}Posts`, f.value, "comments"), ...e)), n = t.docs.slice(0, dt), r = n.map((e) => ({
938
998
  id: e.id,
939
999
  ...e.data()
940
1000
  })), 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(() => {
1001
+ D.value.push(...r.filter((e) => !i.has(e.id))), j = n.at(-1) ?? j, t.size <= dt && (k.value = !1, N = setTimeout(() => {
942
1002
  N = void 0, L();
943
1003
  }, 1e3));
944
1004
  } finally {
@@ -949,8 +1009,8 @@ function lt(n, i = {}) {
949
1009
  t(() => {
950
1010
  clearTimeout(N), P?.();
951
1011
  });
952
- async function ee(e) {
953
- let t = ct(e.body), n = E(c), r = d(), i = l(o(c, `${r}Posts`, f.value, "comments"));
1012
+ async function z(e) {
1013
+ let t = mt(e.body), n = E(c), r = d(), i = l(o(c, `${r}Posts`, f.value, "comments"));
954
1014
  n.set(i, {
955
1015
  postId: f.value,
956
1016
  body: t,
@@ -969,8 +1029,8 @@ function lt(n, i = {}) {
969
1029
  updatedAt: x()
970
1030
  }), n.update(l(c, `${r}Posts`, f.value), { commentCount: m(1) }), await n.commit();
971
1031
  }
972
- async function z(e) {
973
- let t = ct(e.body);
1032
+ async function B(e) {
1033
+ let t = mt(e.body);
974
1034
  if (!e.parent.id) throw Error("답글 대상 댓글을 찾을 수 없습니다.");
975
1035
  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
1036
  if (!s) throw Error("댓글 스레드를 찾을 수 없습니다.");
@@ -993,20 +1053,20 @@ function lt(n, i = {}) {
993
1053
  updatedAt: x()
994
1054
  }), n.update(l(i, s), { replyCount: m(1) }), n.update(l(c, `${r}Posts`, f.value), { commentCount: m(1) }), await n.commit();
995
1055
  }
996
- async function B(e) {
1056
+ async function V(e) {
997
1057
  if (!e.id) return;
998
1058
  if (e.parentId == null && (e.replyCount ?? 0) > 0) throw Error("답글이 있는 댓글은 현재 삭제할 수 없습니다.");
999
1059
  let t = E(c), n = d();
1000
1060
  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);
1001
1061
  }
1002
- async function V(e, t) {
1003
- let n = ct(t);
1062
+ async function H(e, t) {
1063
+ let n = mt(t);
1004
1064
  await w(l(c, `${d()}Posts`, f.value, "comments", e), {
1005
1065
  body: n,
1006
1066
  updatedAt: x()
1007
1067
  });
1008
1068
  }
1009
- async function te(e, t, n) {
1069
+ async function ee(e, t, n) {
1010
1070
  await w(l(c, `${d()}Posts`, f.value, "comments", e), {
1011
1071
  isBlinded: t,
1012
1072
  moderatedAt: x(),
@@ -1019,15 +1079,15 @@ function lt(n, i = {}) {
1019
1079
  hasMore: k,
1020
1080
  loadingMore: O,
1021
1081
  loadMore: R,
1022
- addComment: ee,
1023
- addReply: z,
1024
- updateComment: V,
1025
- setCommentBlinded: te,
1026
- deleteComment: B
1082
+ addComment: z,
1083
+ addReply: B,
1084
+ updateComment: H,
1085
+ setCommentBlinded: ee,
1086
+ deleteComment: V
1027
1087
  };
1028
1088
  }
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;
1089
+ function gt(e, n) {
1090
+ let i = G(), a = M(), s = r([]), c = r(!1), l = r(!1), d = r(!0), f = null, m;
1031
1091
  function g(e) {
1032
1092
  return e.sort((e, t) => (e.createdAt?.toMillis?.() ?? 0) - (t.createdAt?.toMillis?.() ?? 0) || String(e.id).localeCompare(String(t.id)));
1033
1093
  }
@@ -1054,11 +1114,11 @@ function ut(e, n) {
1054
1114
  v(u(), "asc"),
1055
1115
  ...f ? [C(f)] : [],
1056
1116
  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) => ({
1117
+ ], 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, ft), _ = m.map((e) => ({
1058
1118
  id: e.id,
1059
1119
  ...e.data()
1060
1120
  })).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());
1121
+ s.value = g([...s.value, ..._]), f = m.at(-1) ?? f, l.value = !0, r.size <= ft && (d.value = !1, b());
1062
1122
  } finally {
1063
1123
  c.value = !1;
1064
1124
  }
@@ -1081,8 +1141,8 @@ function ut(e, n) {
1081
1141
  }
1082
1142
  //#endregion
1083
1143
  //#region src/composables/useMemiBoardLikes.ts
1084
- function dt(e) {
1085
- let n = W(), i = M(), o = O(), s = () => n.collectionPrefix, c = r(!1), u = r(!1), d;
1144
+ function _t(e) {
1145
+ let n = G(), i = M(), o = O(), s = () => n.collectionPrefix, c = r(!1), u = r(!1), d;
1086
1146
  function f(t) {
1087
1147
  return l(i, `${s()}Posts`, e, "likes", t);
1088
1148
  }
@@ -1120,8 +1180,8 @@ function dt(e) {
1120
1180
  }
1121
1181
  //#endregion
1122
1182
  //#region src/composables/useMemiBoardViews.ts
1123
- function ft() {
1124
- let e = W(), t = M(), n = e.collectionPrefix;
1183
+ function vt() {
1184
+ let e = G(), t = M(), n = e.collectionPrefix;
1125
1185
  function r(e) {
1126
1186
  return `memi-board:viewed:${n}:${e}`;
1127
1187
  }
@@ -1159,7 +1219,7 @@ function ft() {
1159
1219
  }
1160
1220
  //#endregion
1161
1221
  //#region src/utils/moderation-prompt.ts
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 = {
1222
+ var yt = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\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은 \"\"", bt = {
1163
1223
  abuse: "욕설·비속어·모욕",
1164
1224
  spam: "스팸·광고",
1165
1225
  adult: "선정적·성인 내용",
@@ -1174,10 +1234,10 @@ function Y(e) {
1174
1234
  }
1175
1235
  let t = (e.reason || "").trim();
1176
1236
  if (t) return t;
1177
- let n = e.category && e.category !== "none" ? mt[e.category] || e.category : "";
1237
+ let n = e.category && e.category !== "none" ? bt[e.category] || e.category : "";
1178
1238
  return n ? `${n}이(가) 포함되어 게시할 수 없습니다. 내용을 수정해 주세요.` : "게시할 수 없는 내용이 포함되어 있습니다. 내용을 수정해 주세요.";
1179
1239
  }
1180
- var ht = [
1240
+ var xt = [
1181
1241
  "씨발",
1182
1242
  "시발",
1183
1243
  "시이발",
@@ -1203,10 +1263,10 @@ var ht = [
1203
1263
  "bitch",
1204
1264
  "asshole"
1205
1265
  ];
1206
- function gt(e) {
1266
+ function St(e) {
1207
1267
  return new RegExp(e.join("|"), "i");
1208
1268
  }
1209
- var _t = /* @__PURE__ */ new Set([
1269
+ var Ct = /* @__PURE__ */ new Set([
1210
1270
  "none",
1211
1271
  "abuse",
1212
1272
  "spam",
@@ -1214,7 +1274,7 @@ var _t = /* @__PURE__ */ new Set([
1214
1274
  "violence",
1215
1275
  "other"
1216
1276
  ]);
1217
- function vt(e) {
1277
+ function wt(e) {
1218
1278
  let t = e.trim();
1219
1279
  if (!t) return null;
1220
1280
  let n;
@@ -1232,7 +1292,7 @@ function vt(e) {
1232
1292
  if (typeof n != "object" || !n) return null;
1233
1293
  let r = n;
1234
1294
  if (typeof r.flagged != "boolean") return null;
1235
- let i = typeof r.category == "string" && _t.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
1295
+ let i = typeof r.category == "string" && Ct.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
1236
1296
  return {
1237
1297
  flagged: r.flagged,
1238
1298
  category: i,
@@ -1241,14 +1301,14 @@ function vt(e) {
1241
1301
  }
1242
1302
  //#endregion
1243
1303
  //#region src/composables/useMemiBoardModeration.ts
1244
- var yt = {
1304
+ var Tt = {
1245
1305
  flagged: !1,
1246
1306
  category: "none",
1247
1307
  reason: "",
1248
1308
  via: "empty"
1249
1309
  };
1250
- function bt() {
1251
- let e = W().moderation ?? {}, t = A(), { isWriteRestricted: n, restrictedMessage: r, recordContentModerationBlock: i } = J(), a = gt([...ht, ...e.localBlocklist ?? []]);
1310
+ function Et() {
1311
+ let e = G().moderation ?? {}, t = A(), { isWriteRestricted: n, restrictedMessage: r, recordContentModerationBlock: i } = J(), a = St([...xt, ...e.localBlocklist ?? []]);
1252
1312
  function o(e) {
1253
1313
  let t = e.match(a);
1254
1314
  return t ? {
@@ -1263,21 +1323,21 @@ function bt() {
1263
1323
  } : null;
1264
1324
  }
1265
1325
  function s() {
1266
- let n = e.model ?? "gemini-3.5-flash-lite", r = e.useLimitedUseAppCheckTokens !== !1, i = ae(t, {
1267
- backend: new ie(),
1326
+ let n = e.model ?? "gemini-3.5-flash-lite", r = e.useLimitedUseAppCheckTokens !== !1, i = ie(t, {
1327
+ backend: new re(),
1268
1328
  useLimitedUseAppCheckTokens: r
1269
1329
  });
1270
- return oe(i, {
1330
+ return ae(i, {
1271
1331
  model: n,
1272
- systemInstruction: pt,
1332
+ systemInstruction: yt,
1273
1333
  generationConfig: {
1274
1334
  temperature: 0,
1275
1335
  maxOutputTokens: 256,
1276
1336
  responseMimeType: "application/json",
1277
- responseSchema: U.object({ properties: {
1278
- flagged: U.boolean(),
1279
- category: U.string(),
1280
- reason: U.string()
1337
+ responseSchema: W.object({ properties: {
1338
+ flagged: W.boolean(),
1339
+ category: W.string(),
1340
+ reason: W.string()
1281
1341
  } })
1282
1342
  }
1283
1343
  });
@@ -1326,17 +1386,17 @@ function bt() {
1326
1386
  };
1327
1387
  let i = t.trim();
1328
1388
  if (!i) return {
1329
- ...yt,
1389
+ ...Tt,
1330
1390
  via: "empty"
1331
1391
  };
1332
1392
  let a = o(i);
1333
1393
  if (a) return u(a);
1334
1394
  if (e.enabled === !1) return {
1335
- ...yt,
1395
+ ...Tt,
1336
1396
  via: "disabled"
1337
1397
  };
1338
1398
  try {
1339
- let e = vt((await s().generateContent(`심사할 텍스트:\n"""${i.slice(0, 4e3)}"""`)).response.text());
1399
+ let e = wt((await s().generateContent(`심사할 텍스트:\n"""${i.slice(0, 4e3)}"""`)).response.text());
1340
1400
  if (!e) return c();
1341
1401
  let t = {
1342
1402
  flagged: e.flagged,
@@ -1361,14 +1421,14 @@ function bt() {
1361
1421
  via: "restricted"
1362
1422
  };
1363
1423
  if (e.enabled === !1 || !e.moderateImages) return {
1364
- ...yt,
1424
+ ...Tt,
1365
1425
  via: "disabled"
1366
1426
  };
1367
1427
  try {
1368
1428
  let e = await new Promise((e, n) => {
1369
1429
  let r = new FileReader();
1370
1430
  r.onload = () => e(r.result.split(",")[1] ?? ""), r.onerror = n, r.readAsDataURL(t);
1371
- }), n = vt((await s().generateContent([{ inlineData: {
1431
+ }), n = wt((await s().generateContent([{ inlineData: {
1372
1432
  mimeType: t.type || "image/jpeg",
1373
1433
  data: e
1374
1434
  } }, "이 이미지를 심사해 주세요."])).response.text());
@@ -1415,9 +1475,9 @@ async function X(e, { maxWidth: t = 400, quality: n = .8 } = {}) {
1415
1475
  }
1416
1476
  //#endregion
1417
1477
  //#region src/composables/useMemiBoardStorage.ts
1418
- var xt = 5242880, St = 26214400;
1419
- async function Ct(e) {
1420
- if (e.size > 26214400) throw Error(`원본 이미지는 ${St / 1024 / 1024}MB 이하여야 합니다.`);
1478
+ var Dt = 5242880, Ot = 26214400;
1479
+ async function kt(e) {
1480
+ if (e.size > 26214400) throw Error(`원본 이미지는 ${Ot / 1024 / 1024}MB 이하여야 합니다.`);
1421
1481
  if (e.size <= 5242880) return {
1422
1482
  blob: e,
1423
1483
  compressed: !1
@@ -1439,10 +1499,10 @@ async function Ct(e) {
1439
1499
  throw e instanceof Error && e.message.includes("압축 후에도") ? e : Error("이 이미지 형식은 브라우저에서 최적화할 수 없습니다. JPG, PNG 또는 WebP로 변환해 주세요.");
1440
1500
  }
1441
1501
  }
1442
- function wt() {
1443
- let e = W(), t = A(), n = () => e.collectionPrefix;
1502
+ function At() {
1503
+ let e = G(), t = A(), n = () => e.collectionPrefix;
1444
1504
  function r(e, r, i) {
1445
- let a = V(t), o = `${n()}/posts/${r}/attachments/${Date.now()}-${Ve(e.name)}`, s = H(a, o), c = re(s, e, {
1505
+ let a = H(t), o = `${n()}/posts/${r}/attachments/${Date.now()}-${He(e.name)}`, s = U(a, o), c = ne(s, e, {
1446
1506
  contentType: e.type || "application/octet-stream",
1447
1507
  contentDisposition: `attachment; filename*=UTF-8''${encodeURIComponent(e.name)}`
1448
1508
  });
@@ -1451,7 +1511,7 @@ function wt() {
1451
1511
  c.on("state_changed", (e) => {
1452
1512
  i && e.totalBytes > 0 && i(e.bytesTransferred / e.totalBytes);
1453
1513
  }, n, async () => {
1454
- let n = await B(s);
1514
+ let n = await V(s);
1455
1515
  t({
1456
1516
  name: e.name,
1457
1517
  url: n,
@@ -1465,27 +1525,27 @@ function wt() {
1465
1525
  };
1466
1526
  }
1467
1527
  async function i(e) {
1468
- let n = V(t);
1469
- await z(H(n, e.path)).catch(() => {});
1528
+ let n = H(t);
1529
+ await B(U(n, e.path)).catch(() => {});
1470
1530
  }
1471
1531
  async function a(e, r) {
1472
1532
  if (!e.type.startsWith("image/")) throw Error("이미지 파일만 업로드할 수 있습니다.");
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);
1474
- await ne(d, i.blob, { contentType: i.blob.type || "image/jpeg" });
1475
- let f = await B(d), p = await X(i.blob, {
1533
+ let i = await kt(e), a = H(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 = He(e.name.replace(/\.[^.]+$/, "") || "image", 40), c = `${Date.now()}-${s || "image"}`, l = r || `new-${Date.now()}`, u = `${n()}/posts/${l}/images/${c}.${o}`, d = U(a, u);
1534
+ await te(d, i.blob, { contentType: i.blob.type || "image/jpeg" });
1535
+ let f = await V(d), p = await X(i.blob, {
1476
1536
  maxWidth: 400,
1477
1537
  quality: .8
1478
- }), m = `${n()}/posts/${l}/images/thumbnails/${c}.jpg`, h = H(a, m);
1479
- return await ne(h, p, { contentType: "image/jpeg" }), {
1538
+ }), m = `${n()}/posts/${l}/images/thumbnails/${c}.jpg`, h = U(a, m);
1539
+ return await te(h, p, { contentType: "image/jpeg" }), {
1480
1540
  originalUrl: f,
1481
1541
  originalPath: u,
1482
- thumbnailUrl: await B(h),
1542
+ thumbnailUrl: await V(h),
1483
1543
  thumbnailPath: m
1484
1544
  };
1485
1545
  }
1486
1546
  async function o(e) {
1487
- let n = V(t);
1488
- await Promise.all([z(H(n, e.originalPath)).catch(() => {}), z(H(n, e.thumbnailPath)).catch(() => {})]);
1547
+ let n = H(t);
1548
+ await Promise.all([B(U(n, e.originalPath)).catch(() => {}), B(U(n, e.thumbnailPath)).catch(() => {})]);
1489
1549
  }
1490
1550
  return {
1491
1551
  uploadAttachment: r,
@@ -1496,7 +1556,7 @@ function wt() {
1496
1556
  }
1497
1557
  //#endregion
1498
1558
  //#region src/composables/useMemiBoardUsers.ts
1499
- var Tt = [
1559
+ var jt = [
1500
1560
  {
1501
1561
  value: "admin",
1502
1562
  label: "관리자",
@@ -1513,10 +1573,10 @@ var Tt = [
1513
1573
  description: "글과 댓글을 작성하고 본인이 작성한 콘텐츠만 수정·삭제할 수 있습니다."
1514
1574
  }
1515
1575
  ];
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` });
1576
+ function Mt(t = {}) {
1577
+ let n = G(), r = M(), a = `${n.collectionPrefix}Users`, s = e(() => i(t.enabled) ?? !0 ? o(r, a) : null), c = D(s, { ssrKey: `${a}-management` });
1518
1578
  async function u(e, t) {
1519
- if (!Tt.some((e) => e.value === t)) throw Error("지원하지 않는 역할입니다.");
1579
+ if (!jt.some((e) => e.value === t)) throw Error("지원하지 않는 역할입니다.");
1520
1580
  await w(l(r, a, e), {
1521
1581
  role: t,
1522
1582
  updatedAt: x()
@@ -1530,7 +1590,22 @@ function Et(t = {}) {
1530
1590
  }
1531
1591
  //#endregion
1532
1592
  //#region src/data/versionHistory.ts
1533
- var Dt = [
1593
+ var Nt = [
1594
+ {
1595
+ version: "0.19.3",
1596
+ date: "2026-08-06",
1597
+ highlights: ["이미지 목록 게시판은 인스타처럼 제목 없이 사진+내용만 올리면 돼요", "사진을 여러 장 올리고 끌어 순서를 바꿀 수 있어요 (첫 장이 대표)"]
1598
+ },
1599
+ {
1600
+ version: "0.19.2",
1601
+ date: "2026-08-06",
1602
+ highlights: ["이미지만 넣고 글 없이 올리는 건 막았어요 — 본문에 글자를 넣어야 해요"]
1603
+ },
1604
+ {
1605
+ version: "0.19.1",
1606
+ date: "2026-08-06",
1607
+ highlights: ["공유 미리보기 이미지를 가벼운 썸네일로 쓰도록 바꿨어요 (카톡 등에 더 잘 맞아요)"]
1608
+ },
1534
1609
  {
1535
1610
  version: "0.19.0",
1536
1611
  date: "2026-08-06",
@@ -1633,7 +1708,7 @@ var Dt = [
1633
1708
  highlights: ["글쓰기·댓글·첨부·로그인 권한으로 게시판을 바로 시작할 수 있어요", "부적절한 글·댓글은 AI 검열로 막을 수 있어요"],
1634
1709
  note: "실제 보안은 호스트 Firebase 규칙 등록이 필요합니다."
1635
1710
  }
1636
- ], Ot = /* @__PURE__ */ pe(((e, t) => {
1711
+ ], Pt = /* @__PURE__ */ fe(((e, t) => {
1637
1712
  (function(n, r) {
1638
1713
  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();
1639
1714
  })(e, (function() {
@@ -1927,7 +2002,7 @@ var Dt = [
1927
2002
  return w(1e3 * e);
1928
2003
  }, w.en = b[y], w.Ls = b, w.p = {}, w;
1929
2004
  }));
1930
- })), kt = /* @__PURE__ */ pe(((e, t) => {
2005
+ })), Ft = /* @__PURE__ */ fe(((e, t) => {
1931
2006
  (function(n, r) {
1932
2007
  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();
1933
2008
  })(e, (function() {
@@ -2031,9 +2106,9 @@ var Dt = [
2031
2106
  };
2032
2107
  };
2033
2108
  }));
2034
- })), At = /* @__PURE__ */ pe(((e, t) => {
2109
+ })), It = /* @__PURE__ */ fe(((e, t) => {
2035
2110
  (function(n, r) {
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);
2111
+ 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);
2037
2112
  })(e, (function(e) {
2038
2113
  function t(e) {
2039
2114
  return e && typeof e == "object" && "default" in e ? e : { default: e };
@@ -2081,20 +2156,20 @@ var Dt = [
2081
2156
  };
2082
2157
  return n.default.locale(r, null, !0), r;
2083
2158
  }));
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) {
2159
+ })), Z = /* @__PURE__ */ me(Pt(), 1), Lt = /* @__PURE__ */ me(Ft(), 1);
2160
+ It(), Z.default.extend(Lt.default), Z.default.locale("ko");
2161
+ function Rt(e) {
2087
2162
  return e?.toDate ? e.toDate().toLocaleDateString("ko-KR") : "";
2088
2163
  }
2089
- function Nt(e, t = Date.now()) {
2164
+ function zt(e, t = Date.now()) {
2090
2165
  return e?.toDate ? (0, Z.default)(e.toDate()).from((0, Z.default)(t)) : "방금 전";
2091
2166
  }
2092
- function Pt(e) {
2167
+ function Bt(e) {
2093
2168
  return e?.toDate ? (0, Z.default)(e.toDate()).format("YYYY년 M월 D일 HH:mm:ss") : "시간 확인 중";
2094
2169
  }
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)}`];
2170
+ function Vt(e, t) {
2171
+ let n = Bt(e), r = e?.toMillis?.(), i = t?.toMillis?.();
2172
+ return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${Bt(t)}`];
2098
2173
  }
2099
2174
  //#endregion
2100
2175
  //#region src/utils/renderMarkdown.ts
@@ -2105,7 +2180,7 @@ function $(e) {
2105
2180
  let t = e;
2106
2181
  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;
2107
2182
  }
2108
- function It(e) {
2183
+ function Ht(e) {
2109
2184
  if (!e?.trim()) return "";
2110
2185
  let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = 0, i = !1, a = [], o = null, s = [];
2111
2186
  function c() {
@@ -2170,6 +2245,6 @@ function It(e) {
2170
2245
  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");
2171
2246
  }
2172
2247
  //#endregion
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 };
2248
+ export { jt as BOARD_USER_ROLES, pt as COMMENT_BODY_MAX_LENGTH, K as DEFAULT_BLOCK_BAN_DECAY_MS, Pe as DEFAULT_BLOCK_BAN_THRESHOLD, tt as DEFAULT_CATEGORIES, Dt as EDITOR_IMAGE_MAX_BYTES, Ot as EDITOR_IMAGE_SOURCE_MAX_BYTES, ve as asHttpUrl, we as boardListOgDescription, Ce as boardListOgTitle, Se as boardPostOgDescription, xe as boardPostOgTitle, et as buildPostPreview, X as compressImage, _e as configureMemiBoard, Fe as effectiveModerationBlockCount, Ue as extractEditorImageUrls, Me as fetchPublicListForSeo, je as fetchPublicPostForSeo, Rt as formatDate, Bt as formatFullDate, zt as formatRelativeDate, Le as formatRestrictedUntilLabel, Vt as formatTimestampDetails, Ye as hasBodyImage, Je as hasBodyText, Xe as isContentEmpty, ot as isFirestorePermissionDenied, Ie as isModerationWriteRestricted, q as moderationWriteRestrictedUntilMs, Te as normalizeBasePath, Ke as plainTextFromHtml, Ge as postNamespaceFromStoragePath, Ht as renderMarkdownToHtml, Ee as resolvePublicSeoDb, Ve as slugify, We as storagePathFromDownloadUrl, qe as titleFromBody, be as toAbsoluteUrl, ye as toBoardOgImageUrl, J as useMemiBoardAuth, ht as useMemiBoardComments, G as useMemiBoardConfig, _t as useMemiBoardLikes, Et as useMemiBoardModeration, ut as useMemiBoardPost, lt as useMemiBoardPostList, st as useMemiBoardPosts, gt as useMemiBoardReplies, rt as useMemiBoardSettings, At as useMemiBoardStorage, Mt as useMemiBoardUsers, vt as useMemiBoardViews, Nt as versionHistory, $e as videoListCoverUrl, Qe as youtubeId };
2174
2249
 
2175
2250
  //# sourceMappingURL=index.js.map