memi-board 0.30.2 → 0.32.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,4 +1,4 @@
1
- import { C as e, D as t, E as n, O as r, S as i, T as a, _ as o, a as s, b as c, c as l, d as u, f as d, g as f, h as p, i as m, k as h, l as g, m as _, n as v, o as y, p as b, r as x, s as S, u as C, v as w, w as T, x as E, y as D } from "./slugify-CDMIDDCN.js";
1
+ import { C as e, D as t, E as n, O as r, S as i, T as a, _ as o, a as s, b as c, c as l, d as u, f as d, g as f, h as p, i as m, k as h, l as g, m as _, n as v, o as y, p as b, r as x, s as S, u as C, v as w, w as T, x as E, y as D } from "./slugify-B5XHr4SZ.js";
2
2
  import { computed as O, onScopeDispose as k, reactive as A, ref as j, toValue as M, watch as N } from "vue";
3
3
  import { deleteDoc as P, deleteField as F, doc as I, documentId as L, endBefore as ee, getDoc as R, getDocs as z, increment as B, limit as V, limitToLast as te, onSnapshot as H, orderBy as U, query as W, runTransaction as ne, serverTimestamp as G, setDoc as K, startAfter as q, updateDoc as J, where as Y, writeBatch as X } from "firebase/firestore";
4
4
  import { useCollection as re, useCurrentUser as ie, useDocument as ae, useFirebaseApp as oe, useFirebaseAuth as se, useFirestore as Z } from "vuefire";
@@ -168,7 +168,7 @@ async function Ie(e, t) {
168
168
  }
169
169
  //#endregion
170
170
  //#region src/utils/moderation-strike.ts
171
- var Le = 3, Re = 864e5;
171
+ var Le = 10, Re = 864e5;
172
172
  function ze(e, t, n = Date.now(), r = Re) {
173
173
  let i = Math.max(0, Math.floor(Number(e) || 0));
174
174
  if (i <= 0) return 0;
@@ -177,7 +177,7 @@ function ze(e, t, n = Date.now(), r = Re) {
177
177
  let o = Math.floor((n - a) / r);
178
178
  return Math.max(0, i - o);
179
179
  }
180
- function Be(e, t, n = Date.now(), r = 3, i = Re) {
180
+ function Be(e, t, n = Date.now(), r = 10, i = Re) {
181
181
  let a = Math.max(0, Math.floor(Number(e) || 0));
182
182
  if (a < r) return null;
183
183
  let o = typeof t == "number" && Number.isFinite(t) ? t : null;
@@ -185,7 +185,7 @@ function Be(e, t, n = Date.now(), r = 3, i = Re) {
185
185
  let s = o + (a - (r - 1)) * i;
186
186
  return s > n ? s : null;
187
187
  }
188
- function Ve(e, t, n = Date.now(), r = 3, i = Re) {
188
+ function Ve(e, t, n = Date.now(), r = 10, i = Re) {
189
189
  return Be(e, t, n, r, i) != null;
190
190
  }
191
191
  function He(e, t = Date.now()) {
@@ -222,7 +222,7 @@ function Ke() {
222
222
  let e = s(), t = Ge(), n = Z(), r = se(), i = ie(), { role: a, rolePending: o, rawBlockCount: c, blockAtMs: l } = t;
223
223
  function u() {
224
224
  let t = e.moderation?.blockBanThreshold;
225
- return t === 0 ? 0 : typeof t == "number" && t > 0 ? Math.floor(t) : 3;
225
+ return t === 0 ? 0 : typeof t == "number" && t > 0 ? Math.floor(t) : 10;
226
226
  }
227
227
  function d() {
228
228
  let t = e.moderation?.blockBanDecayMs;
@@ -394,8 +394,10 @@ function qe(e) {
394
394
  //#region src/utils/storagePath.ts
395
395
  function Je(e) {
396
396
  try {
397
- let t = e.match(/\/o\/([^?]+)/);
398
- return t?.[1] ? decodeURIComponent(t[1]) : null;
397
+ let t = new URL(e), n = t.hostname.toLowerCase();
398
+ if (!(n === "firebasestorage.googleapis.com" || n.endsWith(".firebasestorage.app"))) return null;
399
+ let r = t.pathname.match(/\/o\/([^?]+)/);
400
+ return r?.[1] ? decodeURIComponent(r[1]) : null;
399
401
  } catch {
400
402
  return null;
401
403
  }
@@ -404,46 +406,86 @@ function Ye(e) {
404
406
  return e.match(/(?:^|\/)posts\/([^/]+)\//)?.[1] ?? null;
405
407
  }
406
408
  //#endregion
409
+ //#region src/utils/deletePostCascade.ts
410
+ async function Xe(e) {
411
+ let t = await _e(e);
412
+ await Promise.all([...t.items.map((e) => he(e)), ...t.prefixes.map((e) => Xe(e))]);
413
+ }
414
+ async function Ze(e, t) {
415
+ let n = [...new Set([...t].filter(Boolean))];
416
+ await Promise.all(n.map((t) => he(ve(e, t)).catch(() => {})));
417
+ }
418
+ async function Qe(t, n, r, a) {
419
+ let s = i(t, r, a), c = E(t, r, a), [l, u] = await Promise.all([R(s), R(c)]);
420
+ if (!l.exists()) return;
421
+ let d = l.data(), f = u.exists() ? String(u.data().content ?? "") : "", p = await z(W(o(t, r), Y("postId", "==", a)));
422
+ for (let e = 0; e < p.docs.length; e += 450) {
423
+ let n = X(t);
424
+ p.docs.slice(e, e + 450).forEach((e) => n.delete(e.ref)), await n.commit();
425
+ }
426
+ let m = /* @__PURE__ */ new Set(), h = /* @__PURE__ */ new Set();
427
+ for (let e of d.attachments ?? []) {
428
+ if (!e?.path) continue;
429
+ m.add(e.path);
430
+ let t = Ye(e.path);
431
+ t && t !== a && h.add(t);
432
+ }
433
+ for (let e of qe(f)) {
434
+ let t = Je(e);
435
+ if (!t) continue;
436
+ m.add(t);
437
+ let n = t.replace(/\/images\/([^/]+)$/, "/images/thumbnails/$1").replace(/\.[^.]+$/, ".jpg");
438
+ n !== t && n.includes("/images/thumbnails/") && m.add(n);
439
+ let r = t.match(/^(.*\/images\/)([^/]+)\.[^.]+$/);
440
+ r && !t.includes("/thumbnails/") && m.add(`${r[1]}thumbnails/${r[2]}.jpg`);
441
+ let i = Ye(t);
442
+ i && i !== a && h.add(i);
443
+ }
444
+ await P(c), await Xe(ve(n, e(r, a)));
445
+ for (let t of h) await Xe(ve(n, e(r, t)));
446
+ await Ze(n, m), await P(s);
447
+ }
448
+ //#endregion
407
449
  //#region src/utils/postBody.ts
408
- function Xe(e) {
450
+ function $e(e) {
409
451
  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();
410
452
  }
411
- function Ze(e, t = 40) {
412
- let n = Xe(e);
453
+ function et(e, t = 40) {
454
+ let n = $e(e);
413
455
  if (!n) return "";
414
456
  if (n.length <= t) return n;
415
457
  let r = n.slice(0, t), i = r.lastIndexOf(" ");
416
458
  return `${(i >= Math.floor(t * .6) ? r.slice(0, i) : r).trimEnd()}…`;
417
459
  }
418
- function Qe(e) {
419
- return Xe(e).length > 0;
460
+ function tt(e) {
461
+ return $e(e).length > 0;
420
462
  }
421
- function $e(e, t) {
463
+ function nt(e, t) {
422
464
  return /<img\b/i.test(String(e || "")) ? !0 : (t || []).some((e) => !!e?.type?.startsWith("image/"));
423
465
  }
424
- function et(e) {
425
- return !Qe(e);
466
+ function rt(e) {
467
+ return !tt(e);
426
468
  }
427
469
  //#endregion
428
470
  //#region src/utils/postPreview.ts
429
- function tt(e) {
471
+ function it(e) {
430
472
  return e.replace(/&amp;/g, "&").replace(/&quot;/g, "\"").replace(/&#39;/g, "'");
431
473
  }
432
- function nt(e) {
474
+ function at(e) {
433
475
  if (e) try {
434
- let t = new URL(tt(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
476
+ let t = new URL(it(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
435
477
  if (n === "youtu.be") return t.pathname.split("/").filter(Boolean)[0];
436
478
  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;
437
479
  } catch {
438
480
  return;
439
481
  }
440
482
  }
441
- function rt(e) {
442
- let t = nt(e.videoUrl);
483
+ function ot(e) {
484
+ let t = at(e.videoUrl);
443
485
  return t ? `https://i.ytimg.com/vi/${t}/hqdefault.jpg` : e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
444
486
  }
445
- function it(e, t = []) {
446
- 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 ? tt(r[1]) : a, s = o ? nt(o) : void 0, c = n ? tt(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
487
+ function st(e, t = []) {
488
+ let n = e.match(/<img\b[^>]*\bsrc=["']([^"']+)["']/i), r = e.match(/<iframe\b[^>]*\bsrc=["']([^"']+)["']/i), i = t.find((e) => e.type.startsWith("image/"))?.url, a = t.find((e) => e.type.startsWith("video/"))?.url, o = r ? it(r[1]) : a, s = o ? at(o) : void 0, c = n ? it(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
447
489
  return {
448
490
  summary: e.replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div|li|h[1-6]|blockquote)>/gi, "\n").replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/[^\S\n]+/g, " ").replace(/ *\n */g, "\n").replace(/\n{2,}/g, "\n").trim().slice(0, 160),
449
491
  ...c ? { previewImage: c } : {},
@@ -452,7 +494,7 @@ function it(e, t = []) {
452
494
  }
453
495
  //#endregion
454
496
  //#region src/composables/useMemiBoardSettings.ts
455
- var at = [
497
+ var ct = [
456
498
  {
457
499
  id: "free",
458
500
  label: "자유",
@@ -480,45 +522,45 @@ var at = [
480
522
  visibility: "public",
481
523
  order: 2
482
524
  }
483
- ], ot = at;
484
- function st(e) {
525
+ ], lt = ct;
526
+ function ut(e) {
485
527
  return e === "hidden" ? "hidden" : "public";
486
528
  }
487
- function ct(e, t, n) {
529
+ function dt(e, t, n) {
488
530
  return {
489
531
  id: e,
490
532
  label: typeof t.label == "string" && t.label.trim() ? t.label.trim() : e,
491
533
  order: typeof t.order == "number" ? t.order : n,
492
534
  description: (typeof t.description == "string" ? t.description : "") || "",
493
- visibility: st(t.visibility),
535
+ visibility: ut(t.visibility),
494
536
  listView: t.listView ?? "default",
495
537
  writeRole: t.writeRole ?? "user",
496
538
  commentWriteRole: t.commentWriteRole ?? "user",
497
539
  allowedStaffUids: Array.isArray(t.allowedStaffUids) ? t.allowedStaffUids : []
498
540
  };
499
541
  }
500
- function lt() {
501
- let e = () => m(), r = Z(), { isSignedIn: i } = Ke(), a = O(() => W(n(r, e()), U("order", "asc"))), { data: o, pending: s } = re(a, { ssrKey: d(e(), "boards") }), c = O(() => o.value.map((e, t) => ct(e.id, e, t))), l = c, u = O(() => c.value.filter((e) => e.visibility !== "hidden")), f = u;
502
- function p(e) {
503
- if (e) return c.value.find((t) => t.id === e)?.label ?? e;
542
+ function ft() {
543
+ let e = () => m(), r = Z(), i = oe(), { isSignedIn: a } = Ke(), o = O(() => W(n(r, e()), U("order", "asc"))), { data: s, pending: c } = re(o, { ssrKey: d(e(), "boards") }), l = O(() => s.value.map((e, t) => dt(e.id, e, t))), u = l, f = O(() => l.value.filter((e) => e.visibility !== "hidden")), p = f;
544
+ function h(e) {
545
+ if (e) return l.value.find((t) => t.id === e)?.label ?? e;
504
546
  }
505
- let h = p;
506
- function g(e) {
507
- if (e) return c.value.find((t) => t.id === e)?.description?.trim() || void 0;
547
+ let g = h;
548
+ function _(e) {
549
+ if (e) return l.value.find((t) => t.id === e)?.description?.trim() || void 0;
508
550
  }
509
- let _ = g;
510
- function y(e) {
511
- return e ? c.value.find((t) => t.id === e)?.visibility ?? "public" : "public";
551
+ let y = _;
552
+ function b(e) {
553
+ return e ? l.value.find((t) => t.id === e)?.visibility ?? "public" : "public";
512
554
  }
513
- let b = y;
514
- function x(e) {
515
- return y(e) === "hidden";
555
+ let x = b;
556
+ function S(e) {
557
+ return b(e) === "hidden";
516
558
  }
517
- let S = x;
518
- function C(e) {
519
- if (e) return c.value.find((t) => t.id === e);
559
+ let C = S;
560
+ function w(e) {
561
+ if (e) return l.value.find((t) => t.id === e);
520
562
  }
521
- async function w(t, n) {
563
+ async function E(t, n) {
522
564
  let i;
523
565
  for (;;) {
524
566
  let a = await z(W(T(r, e()), Y("boardId", "==", t), U("__name__"), ...i ? [q(i)] : [], V(400)));
@@ -528,11 +570,11 @@ function lt() {
528
570
  if (await o.commit(), i = a.docs[a.docs.length - 1], a.docs.length < 400) break;
529
571
  }
530
572
  }
531
- function E(e, t) {
573
+ function D(e, t) {
532
574
  return {
533
575
  label: e.label.trim(),
534
576
  description: (e.description ?? "").trim(),
535
- visibility: st(e.visibility),
577
+ visibility: ut(e.visibility),
536
578
  listView: e.listView ?? "default",
537
579
  writeRole: e.writeRole ?? "user",
538
580
  commentWriteRole: e.commentWriteRole ?? "user",
@@ -541,124 +583,120 @@ function lt() {
541
583
  updatedAt: G()
542
584
  };
543
585
  }
544
- async function D(n, a = n.order ?? 0) {
545
- if (!i.value) throw Error("로그인이 필요합니다.");
586
+ async function k(n, i = n.order ?? 0) {
587
+ if (!a.value) throw Error("로그인이 필요합니다.");
546
588
  let o = n.id.trim(), s = n.label.trim();
547
589
  if (!o || !s) throw Error("보드 ID와 이름을 입력해 주세요.");
548
- let l = st(n.visibility), u = c.value.find((e) => e.id === o)?.visibility ?? "public", d = E({
590
+ let c = ut(n.visibility), u = l.value.find((e) => e.id === o)?.visibility ?? "public", d = D({
549
591
  ...n,
550
592
  label: s,
551
- visibility: l
552
- }, a);
553
- await K(t(r, e(), o), d, { merge: !0 }), (u !== l || l === "public") && await w(o, l !== "hidden");
554
- }
555
- let k = D;
556
- async function A(n) {
557
- if (!i.value) throw Error("로그인이 필요합니다.");
558
- let a = new Map(c.value.map((e) => [e.id, e.visibility ?? "public"])), o = X(r), s = [];
559
- n.forEach((n, i) => {
593
+ visibility: c
594
+ }, i);
595
+ await K(t(r, e(), o), d, { merge: !0 }), (u !== c || c === "public") && await E(o, c !== "hidden");
596
+ }
597
+ let A = k;
598
+ async function j(n) {
599
+ if (!a.value) throw Error("로그인이 필요합니다.");
600
+ let i = new Map(l.value.map((e) => [e.id, e.visibility ?? "public"])), o = X(r), s = [];
601
+ n.forEach((n, a) => {
560
602
  let c = n.id.trim(), l = n.label.trim();
561
603
  if (!c || !l) throw Error("보드 ID와 이름을 입력해 주세요.");
562
- let u = st(n.visibility), d = E({
604
+ let u = ut(n.visibility), d = D({
563
605
  ...n,
564
606
  label: l,
565
607
  visibility: u
566
- }, i);
567
- o.set(t(r, e(), c), d, { merge: !0 }), (a.get(c) ?? "public") !== u && s.push({
608
+ }, a);
609
+ o.set(t(r, e(), c), d, { merge: !0 }), (i.get(c) ?? "public") !== u && s.push({
568
610
  id: c,
569
611
  listed: u !== "hidden"
570
612
  });
571
613
  }), await o.commit();
572
- for (let e of s) await w(e.id, e.listed);
614
+ for (let e of s) await E(e.id, e.listed);
573
615
  }
574
- let j = A;
575
- async function M(n) {
576
- if (!i.value) throw Error("로그인이 필요합니다.");
616
+ let M = j;
617
+ async function N(n) {
618
+ if (!a.value) throw Error("로그인이 필요합니다.");
619
+ let o = n.trim();
620
+ if (!o) throw Error("삭제할 게시판 ID가 필요합니다.");
621
+ for (;;) {
622
+ let t = await z(W(T(r, e()), Y("boardId", "==", o), V(25)));
623
+ if (t.empty) break;
624
+ for (let n of t.docs) await Qe(r, ge(i), e(), n.id);
625
+ }
577
626
  await P(t(r, e(), n));
578
627
  }
579
- let N = M;
580
- async function F() {
628
+ let F = N;
629
+ async function I() {
581
630
  return Promise.resolve();
582
631
  }
583
- async function I(e) {
632
+ async function L(e) {
584
633
  let t = e.trim();
585
634
  if (!t) throw Error("보드 이름을 입력해 주세요.");
586
- if (!i.value) throw Error("로그인이 필요합니다.");
587
- let n = c.value.find((e) => e.label === t);
635
+ if (!a.value) throw Error("로그인이 필요합니다.");
636
+ let n = l.value.find((e) => e.label === t);
588
637
  if (n) return n.id;
589
- let r = v(t) || `board-${Date.now()}`, a = r, o = 2, s = new Set(c.value.map((e) => e.id));
590
- for (; s.has(a);) a = `${r}-${o++}`;
591
- return await D({
592
- id: a,
638
+ let r = v(t) || `board-${Date.now()}`, i = r, o = 2, s = new Set(l.value.map((e) => e.id));
639
+ for (; s.has(i);) i = `${r}-${o++}`;
640
+ return await k({
641
+ id: i,
593
642
  label: t,
594
643
  visibility: "public",
595
644
  listView: "default",
596
645
  writeRole: "user",
597
646
  commentWriteRole: "user",
598
- order: c.value.length
599
- }), a;
647
+ order: l.value.length
648
+ }), i;
600
649
  }
601
650
  return {
602
- boards: c,
603
- publicBoards: u,
604
- boardLabel: p,
605
- boardDescription: g,
606
- boardVisibility: y,
607
- isBoardHidden: x,
608
- getBoard: C,
609
- categories: l,
610
- publicCategories: f,
611
- categoryLabel: h,
612
- categoryDescription: _,
613
- categoryVisibility: b,
614
- isCategoryHidden: S,
615
- settingsPending: s,
616
- ensureSettings: F,
617
- addBoard: I,
618
- addCategory: I,
619
- saveBoard: D,
620
- saveCategory: k,
621
- saveBoards: A,
622
- saveCategories: j,
623
- deleteBoard: M,
624
- deleteCategory: N,
625
- DEFAULT_BOARDS: at,
626
- DEFAULT_CATEGORIES: ot
651
+ boards: l,
652
+ publicBoards: f,
653
+ boardLabel: h,
654
+ boardDescription: _,
655
+ boardVisibility: b,
656
+ isBoardHidden: S,
657
+ getBoard: w,
658
+ categories: u,
659
+ publicCategories: p,
660
+ categoryLabel: g,
661
+ categoryDescription: y,
662
+ categoryVisibility: x,
663
+ isCategoryHidden: C,
664
+ settingsPending: c,
665
+ ensureSettings: I,
666
+ addBoard: L,
667
+ addCategory: L,
668
+ saveBoard: k,
669
+ saveCategory: A,
670
+ saveBoards: j,
671
+ saveCategories: M,
672
+ deleteBoard: N,
673
+ deleteCategory: F,
674
+ DEFAULT_BOARDS: ct,
675
+ DEFAULT_CATEGORIES: lt
627
676
  };
628
677
  }
629
678
  //#endregion
630
679
  //#region src/composables/useMemiBoardPosts.ts
631
- async function ut(e) {
632
- let t = await _e(e).catch(() => ({
633
- items: [],
634
- prefixes: []
635
- }));
636
- await Promise.all([...t.items.map((e) => he(e)), ...t.prefixes.map((e) => ut(e))]);
637
- }
638
- async function dt(e, t) {
639
- let n = [...new Set([...t].filter(Boolean))];
640
- await Promise.all(n.map((t) => he(ve(e, t)).catch(() => {})));
641
- }
642
- function ft(e) {
680
+ function pt(e) {
643
681
  if (!e || typeof e != "object") return !1;
644
682
  let t = "code" in e ? String(e.code) : "";
645
683
  return t === "permission-denied" || t === "firestore/permission-denied";
646
684
  }
647
- function pt(e) {
685
+ function mt(e) {
648
686
  let t = M(e)?.trim();
649
687
  if (!t) throw Error("[memi-board] boardId 가 필요합니다.");
650
688
  return t;
651
689
  }
652
- function mt(t) {
653
- let n = Z(), r = oe(), { isBoardHidden: a, getBoard: s } = lt(), c = () => m(), l = () => pt(t), u = () => T(n, c()), d = (e) => i(n, c(), e), f = (e) => E(n, c(), e), p = () => o(n, c());
654
- function h() {
655
- return !a(l());
690
+ function ht(e) {
691
+ let t = Z(), n = oe(), { isBoardHidden: r, getBoard: a } = ft(), o = () => m(), s = () => mt(e), c = () => T(t, o()), l = (e) => i(t, o(), e), u = (e) => E(t, o(), e);
692
+ function d() {
693
+ return !r(s());
656
694
  }
657
- function g() {
658
- return s(l())?.listView === "image";
695
+ function f() {
696
+ return a(s())?.listView === "image";
659
697
  }
660
- function _(e) {
661
- let t = g(), n = Qe(e.content), r = $e(e.content, e.attachments);
698
+ function p(e) {
699
+ let t = f(), n = tt(e.content), r = nt(e.content, e.attachments);
662
700
  if (t) {
663
701
  if (!r) throw Error("사진을 올려 주세요.");
664
702
  return;
@@ -666,13 +704,13 @@ function mt(t) {
666
704
  if (!n && !r) throw Error("본문에 글자를 입력하거나 이미지를 첨부해 주세요.");
667
705
  if (!e.title?.trim()) throw Error("제목을 입력해 주세요.");
668
706
  }
669
- function y() {
670
- return I(u()).id;
707
+ function h() {
708
+ return I(c()).id;
671
709
  }
672
- async function b(e = {}) {
673
- let t = e.pageSize ?? 20, n = [Y("boardId", "==", l()), Y("isPublished", "==", !0)];
710
+ async function g(e = {}) {
711
+ let t = e.pageSize ?? 20, n = [Y("boardId", "==", s()), Y("isPublished", "==", !0)];
674
712
  e.publicOnly !== !1 && n.push(Y("listed", "==", !0)), n.push(U("createdAt", "desc")), e.cursor && n.push(q(e.cursor)), n.push(V(t + 1));
675
- let r = await z(W(u(), ...n)), i = r.docs.slice(0, t);
713
+ let r = await z(W(c(), ...n)), i = r.docs.slice(0, t);
676
714
  return {
677
715
  posts: i.map((e) => ({
678
716
  id: e.id,
@@ -682,49 +720,49 @@ function mt(t) {
682
720
  hasMore: r.docs.length > t
683
721
  };
684
722
  }
685
- async function x(e) {
686
- let [t, n] = await Promise.all([R(d(e)), R(f(e))]);
723
+ async function _(e) {
724
+ let [t, n] = await Promise.all([R(l(e)), R(u(e))]);
687
725
  return t.exists() ? {
688
726
  id: t.id,
689
727
  ...t.data(),
690
728
  content: n.exists() ? n.data().content ?? "" : ""
691
729
  } : null;
692
730
  }
693
- async function S(e) {
731
+ async function y(e) {
694
732
  try {
695
- let t = (await z(W(u(), Y("boardId", "==", l()), Y("slug", "==", e.trim()), V(1)))).docs[0];
733
+ let t = (await z(W(c(), Y("boardId", "==", s()), Y("slug", "==", e.trim()), V(1)))).docs[0];
696
734
  if (!t) return { status: "not-found" };
697
735
  try {
698
- let e = await x(t.id);
736
+ let e = await _(t.id);
699
737
  return e ? {
700
738
  status: "ok",
701
739
  post: e
702
740
  } : { status: "not-found" };
703
741
  } catch (e) {
704
- return ft(e) ? { status: "permission-denied" } : {
742
+ return pt(e) ? { status: "permission-denied" } : {
705
743
  status: "error",
706
744
  message: e instanceof Error ? e.message : String(e)
707
745
  };
708
746
  }
709
747
  } catch (e) {
710
- return ft(e) ? { status: "permission-denied" } : {
748
+ return pt(e) ? { status: "permission-denied" } : {
711
749
  status: "error",
712
750
  message: e instanceof Error ? e.message : String(e)
713
751
  };
714
752
  }
715
753
  }
716
- async function C(e) {
754
+ async function b(e) {
717
755
  if (!e.createdAt) return {
718
756
  previous: null,
719
757
  next: null
720
758
  };
721
759
  try {
722
760
  let t = [
723
- Y("boardId", "==", l()),
761
+ Y("boardId", "==", s()),
724
762
  Y("isPublished", "==", !0),
725
763
  ...e.listed === !1 ? [] : [Y("listed", "==", !0)],
726
764
  U("createdAt", "desc")
727
- ], [n, r] = await Promise.all([z(W(u(), ...t, q(e.createdAt), V(1))), z(W(u(), ...t, ee(e.createdAt), te(1)))]), i = (e) => {
765
+ ], [n, r] = await Promise.all([z(W(c(), ...t, q(e.createdAt), V(1))), z(W(c(), ...t, ee(e.createdAt), te(1)))]), i = (e) => {
728
766
  let t = e.docs[0];
729
767
  return t ? {
730
768
  id: t.id,
@@ -742,20 +780,20 @@ function mt(t) {
742
780
  };
743
781
  }
744
782
  }
745
- async function w(e) {
746
- _(e);
747
- let t = e.postId || y(), n = g(), r, i;
748
- if (n) r = t, i = Ze(e.content);
783
+ async function x(e) {
784
+ p(e);
785
+ let t = e.postId || h(), n = f(), r, i;
786
+ if (n) r = t, i = et(e.content);
749
787
  else {
750
788
  i = e.title.trim();
751
789
  let t = v(i) || "post";
752
790
  r = t;
753
791
  let n = 1;
754
- for (; !(await z(W(u(), Y("boardId", "==", l()), Y("slug", "==", r), V(1)))).empty;) n++, r = `${t}-${n}`;
792
+ for (; !(await z(W(c(), Y("boardId", "==", s()), Y("slug", "==", r), V(1)))).empty;) n++, r = `${t}-${n}`;
755
793
  }
756
- let a = it(e.content, e.attachments), o = h();
757
- return await K(d(t), {
758
- boardId: l(),
794
+ let a = st(e.content, e.attachments), o = d();
795
+ return await K(l(t), {
796
+ boardId: s(),
759
797
  slug: r,
760
798
  title: i,
761
799
  ...a,
@@ -773,12 +811,12 @@ function mt(t) {
773
811
  moderationModel: e.moderationModel ?? null,
774
812
  createdAt: G(),
775
813
  updatedAt: G()
776
- }), await K(f(t), { content: e.content }), r;
814
+ }), await K(u(t), { content: e.content }), r;
777
815
  }
778
- async function D(e, t) {
779
- _(t);
780
- let n = g() ? Ze(t.content) : t.title.trim(), r = it(t.content, t.attachments), i = h();
781
- await Promise.all([J(d(e), {
816
+ async function S(e, t) {
817
+ p(t);
818
+ let n = f() ? et(t.content) : t.title.trim(), r = st(t.content, t.attachments), i = d();
819
+ await Promise.all([J(l(e), {
782
820
  title: n,
783
821
  ...r,
784
822
  previewImage: r.previewImage ? r.previewImage : F(),
@@ -787,59 +825,33 @@ function mt(t) {
787
825
  listed: i,
788
826
  attachments: t.attachments ?? [],
789
827
  updatedAt: G()
790
- }), K(f(e), { content: t.content }, { merge: !0 })]);
828
+ }), K(u(e), { content: t.content }, { merge: !0 })]);
791
829
  }
792
- async function O(e) {
793
- await J(d(e), {
830
+ async function C(e) {
831
+ await J(l(e), {
794
832
  isPublished: !0,
795
833
  publishedAt: G(),
796
834
  updatedAt: G()
797
835
  });
798
836
  }
799
- async function k(t) {
800
- let [i, a] = await Promise.all([R(d(t)), R(f(t))]);
801
- if (!i.exists()) return;
802
- let o = i.data(), s = a.exists() ? String(a.data().content ?? "") : "", l = await z(W(p(), Y("postId", "==", t)));
803
- for (let e = 0; e < l.docs.length; e += 450) {
804
- let t = X(n);
805
- l.docs.slice(e, e + 450).forEach((e) => t.delete(e.ref)), await t.commit();
806
- }
807
- let u = ge(r), m = /* @__PURE__ */ new Set(), h = /* @__PURE__ */ new Set();
808
- for (let e of o.attachments ?? []) if (e?.path) {
809
- m.add(e.path);
810
- let n = Ye(e.path);
811
- n && n !== t && h.add(n);
812
- }
813
- for (let e of qe(s)) {
814
- let n = Je(e);
815
- if (!n) continue;
816
- m.add(n);
817
- let r = n.replace(/\/images\/([^/]+)$/, "/images/thumbnails/$1").replace(/\.[^.]+$/, ".jpg");
818
- r !== n && r.includes("/images/thumbnails/") && m.add(r);
819
- let i = n.match(/^(.*\/images\/)([^/]+)\.[^.]+$/);
820
- i && !n.includes("/thumbnails/") && m.add(`${i[1]}thumbnails/${i[2]}.jpg`);
821
- let a = Ye(n);
822
- a && a !== t && h.add(a);
823
- }
824
- await P(f(t)), await ut(ve(u, e(c(), t)));
825
- for (let t of h) await ut(ve(u, e(c(), t)));
826
- await dt(u, m), await P(d(t));
837
+ async function w(e) {
838
+ await Qe(t, ge(n), o(), e);
827
839
  }
828
840
  return {
829
- createPostId: y,
830
- getPosts: b,
831
- getPost: x,
832
- getPostBySlug: S,
833
- getAdjacentPosts: C,
834
- createPost: w,
835
- updatePost: D,
836
- publishPost: O,
837
- deletePost: k
841
+ createPostId: h,
842
+ getPosts: g,
843
+ getPost: _,
844
+ getPostBySlug: y,
845
+ getAdjacentPosts: b,
846
+ createPost: x,
847
+ updatePost: S,
848
+ publishPost: C,
849
+ deletePost: w
838
850
  };
839
851
  }
840
- var ht = 10;
841
- function gt(e, t = {}) {
842
- let n = Z(), { isAdmin: r, isStaff: i, user: a } = Ke(), o = () => m(), s = t.pageSize ?? ht, c = O(() => a.value?.uid ?? ""), l = O(() => pt(e)), u = () => T(n, o()), d = O(() => t.publicOnly === void 0 ? !(r.value || i.value) : typeof t.publicOnly == "boolean" ? t.publicOnly : t.publicOnly.value), f = j([]), p = j([]), h = j([]), g = j(!0), _ = j(!0), v = j(!1), y = j(""), b = null, x = null, S, C;
852
+ var gt = 10;
853
+ function _t(e, t = {}) {
854
+ let n = Z(), { isAdmin: r, isStaff: i, user: a } = Ke(), o = () => m(), s = t.pageSize ?? gt, c = O(() => a.value?.uid ?? ""), l = O(() => mt(e)), u = () => T(n, o()), d = O(() => t.publicOnly === void 0 ? !(r.value || i.value) : typeof t.publicOnly == "boolean" ? t.publicOnly : t.publicOnly.value), f = j([]), p = j([]), h = j([]), g = j(!0), _ = j(!0), v = j(!1), y = j(""), b = null, x = null, S, C;
843
855
  function w(e) {
844
856
  return e.createdAt?.toMillis?.() ?? 0;
845
857
  }
@@ -922,8 +934,8 @@ function gt(e, t = {}) {
922
934
  loadMore: P
923
935
  };
924
936
  }
925
- function _t(e, t) {
926
- let n = Z(), r = () => m(), a = O(() => pt(e)), o = O(() => typeof t == "string" ? t : t.value), s = O(() => i(n, r(), o.value)), c = O(() => E(n, r(), o.value)), { data: l, pending: u } = ae(s), { data: d, pending: f } = ae(c);
937
+ function vt(e, t) {
938
+ let n = Z(), r = () => m(), a = O(() => mt(e)), o = O(() => typeof t == "string" ? t : t.value), s = O(() => i(n, r(), o.value)), c = O(() => E(n, r(), o.value)), { data: l, pending: u } = ae(s), { data: d, pending: f } = ae(c);
927
939
  return {
928
940
  post: O(() => !l.value || l.value.boardId !== a.value ? null : {
929
941
  ...l.value,
@@ -935,9 +947,9 @@ function _t(e, t) {
935
947
  }
936
948
  //#endregion
937
949
  //#region src/composables/useMemiBoardUserPosts.ts
938
- var vt = 10;
939
- function yt(e, t = {}) {
940
- let n = Z(), r = () => m(), i = () => T(n, r()), a = t.pageSize ?? vt, o = O(() => M(e)?.trim() || ""), s = j([]), c = j([]), l = j(!0), u = j(!0), d = j(!1), f = j(""), p = null, h = null, g;
950
+ var yt = 10;
951
+ function bt(e, t = {}) {
952
+ let n = Z(), r = () => m(), i = () => T(n, r()), a = t.pageSize ?? yt, o = O(() => M(e)?.trim() || ""), s = j([]), c = j([]), l = j(!0), u = j(!0), d = j(!1), f = j(""), p = null, h = null, g;
941
953
  function _(e) {
942
954
  return {
943
955
  id: e.id,
@@ -1003,9 +1015,9 @@ function yt(e, t = {}) {
1003
1015
  }
1004
1016
  //#endregion
1005
1017
  //#region src/composables/useMemiBoardUserComments.ts
1006
- var bt = 10;
1007
- function xt(e, t = {}) {
1008
- let n = Z(), r = () => m(), i = () => o(n, r()), a = t.pageSize ?? bt, s = O(() => M(e)?.trim() || ""), c = j([]), l = j([]), u = j(!0), d = j(!0), f = j(!1), p = j(""), h = null, g = null, _;
1018
+ var xt = 10;
1019
+ function St(e, t = {}) {
1020
+ let n = Z(), r = () => m(), i = () => o(n, r()), a = t.pageSize ?? xt, s = O(() => M(e)?.trim() || ""), c = j([]), l = j([]), u = j(!0), d = j(!0), f = j(!1), p = j(""), h = null, g = null, _;
1009
1021
  function v(e) {
1010
1022
  return {
1011
1023
  id: e.id,
@@ -1067,9 +1079,9 @@ function xt(e, t = {}) {
1067
1079
  }
1068
1080
  //#endregion
1069
1081
  //#region src/composables/useMemiBoardAuthorMemo.ts
1070
- var St = 100, Ct = /* @__PURE__ */ new Map();
1082
+ var Ct = 100, wt = /* @__PURE__ */ new Map();
1071
1083
  function Q(e) {
1072
- let t = Ct.get(e);
1084
+ let t = wt.get(e);
1073
1085
  return t || (t = A({
1074
1086
  text: "",
1075
1087
  sentiment: null,
@@ -1077,9 +1089,9 @@ function Q(e) {
1077
1089
  pending: !1,
1078
1090
  saving: !1,
1079
1091
  error: ""
1080
- }), Ct.set(e, t)), t;
1092
+ }), wt.set(e, t)), t;
1081
1093
  }
1082
- function wt(e) {
1094
+ function Tt(e) {
1083
1095
  let t = Z(), n = () => m(), { user: r } = Ke(), i = O(() => M(e)), a = O(() => `${r.value?.uid ?? ""}:${i.value}`), o = O(() => {
1084
1096
  let e = r.value?.uid, a = i.value;
1085
1097
  return !e || !a ? null : u(t, n(), e, a);
@@ -1149,14 +1161,14 @@ function wt(e) {
1149
1161
  }
1150
1162
  //#endregion
1151
1163
  //#region src/composables/useMemiBoardComments.ts
1152
- var Tt = 10, Et = 5, Dt = 1e3;
1153
- function Ot(e) {
1164
+ var Et = 10, Dt = 5, Ot = 1e3;
1165
+ function kt(e) {
1154
1166
  let t = e.trim();
1155
1167
  if (!t) throw Error("댓글 내용을 입력해 주세요.");
1156
- if (t.length > 1e3) throw Error(`댓글은 ${Dt.toLocaleString()}자까지 작성할 수 있습니다.`);
1168
+ if (t.length > 1e3) throw Error(`댓글은 ${Ot.toLocaleString()}자까지 작성할 수 있습니다.`);
1157
1169
  return t;
1158
1170
  }
1159
- function kt(e, t, n = {}) {
1171
+ function At(e, t, n = {}) {
1160
1172
  let r = Z(), a = () => m(), s = () => o(r, a()), c = O(() => M(e)), l = O(() => M(t)), u = j([]), d = j([]), p = j(n.subscribe !== !1), h = j([]), g = j(!1), _ = j(n.subscribe !== !1), v = j(n.subscribe !== !1), y = null, b, x;
1161
1173
  function S(e) {
1162
1174
  return e.createdAt?.toMillis?.() ?? 0;
@@ -1211,11 +1223,11 @@ function kt(e, t, n = {}) {
1211
1223
  U(L(), "asc"),
1212
1224
  ...y ? [q(y)] : [],
1213
1225
  V(11)
1214
- ], t = await z(W(s(), ...e)), n = t.docs.slice(0, Tt), r = n.map((e) => ({
1226
+ ], t = await z(W(s(), ...e)), n = t.docs.slice(0, Et), r = n.map((e) => ({
1215
1227
  id: e.id,
1216
1228
  ...e.data()
1217
1229
  })), i = new Set(h.value.map((e) => e.id));
1218
- h.value.push(...r.filter((e) => !i.has(e.id))), y = n.at(-1) ?? y, t.size <= Tt && (_.value = !1, b = setTimeout(() => {
1230
+ h.value.push(...r.filter((e) => !i.has(e.id))), y = n.at(-1) ?? y, t.size <= Et && (_.value = !1, b = setTimeout(() => {
1219
1231
  b = void 0, w();
1220
1232
  }, 1e3));
1221
1233
  } finally {
@@ -1227,7 +1239,7 @@ function kt(e, t, n = {}) {
1227
1239
  clearTimeout(b), x?.();
1228
1240
  });
1229
1241
  async function E(e) {
1230
- let t = Ot(e.body), n = X(r), o = I(s());
1242
+ let t = kt(e.body), n = X(r), o = I(s());
1231
1243
  n.set(o, {
1232
1244
  postId: l.value,
1233
1245
  boardId: c.value,
@@ -1248,7 +1260,7 @@ function kt(e, t, n = {}) {
1248
1260
  }), n.update(i(r, a(), l.value), { commentCount: B(1) }), await n.commit();
1249
1261
  }
1250
1262
  async function D(e) {
1251
- let t = Ot(e.body);
1263
+ let t = kt(e.body);
1252
1264
  if (!e.parent.id) throw Error("답글 대상 댓글을 찾을 수 없습니다.");
1253
1265
  let n = X(r), o = I(s()), u = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
1254
1266
  if (!u) throw Error("댓글 스레드를 찾을 수 없습니다.");
@@ -1279,7 +1291,7 @@ function kt(e, t, n = {}) {
1279
1291
  t.delete(f(r, a(), e.id)), e.parentId && e.rootId && t.update(f(r, a(), e.rootId), { replyCount: B(-1) }), t.update(i(r, a(), l.value), { commentCount: B(-1) }), await t.commit(), h.value = h.value.filter((t) => t.id !== e.id), d.value = d.value.filter((t) => t.id !== e.id), u.value = u.value.filter((t) => t.id !== e.id);
1280
1292
  }
1281
1293
  async function P(e, t) {
1282
- let n = Ot(t);
1294
+ let n = kt(t);
1283
1295
  await J(f(r, a(), e), {
1284
1296
  body: n,
1285
1297
  updatedAt: G()
@@ -1305,7 +1317,7 @@ function kt(e, t, n = {}) {
1305
1317
  deleteComment: A
1306
1318
  };
1307
1319
  }
1308
- function At(e, t, n) {
1320
+ function jt(e, t, n) {
1309
1321
  let r = Z(), i = () => m(), a = () => o(r, i()), s = j([]), c = j(!1), l = j(!1), u = j(!0), d = null, f;
1310
1322
  function p(e) {
1311
1323
  return e.sort((e, t) => (e.createdAt?.toMillis?.() ?? 0) - (t.createdAt?.toMillis?.() ?? 0) || String(e.id).localeCompare(String(t.id)));
@@ -1333,11 +1345,11 @@ function At(e, t, n) {
1333
1345
  U(L(), "asc"),
1334
1346
  ...d ? [q(d)] : [],
1335
1347
  V(6)
1336
- ], t = await z(W(a(), ...e)), r = new Set(s.value.map((e) => e.id)), i = t.docs.slice(0, Et), o = i.map((e) => ({
1348
+ ], t = await z(W(a(), ...e)), r = new Set(s.value.map((e) => e.id)), i = t.docs.slice(0, Dt), o = i.map((e) => ({
1337
1349
  id: e.id,
1338
1350
  ...e.data()
1339
1351
  })).filter((e) => !r.has(e.id));
1340
- s.value = p([...s.value, ...o]), d = i.at(-1) ?? d, l.value = !0, t.size <= Et && (u.value = !1, h());
1352
+ s.value = p([...s.value, ...o]), d = i.at(-1) ?? d, l.value = !0, t.size <= Dt && (u.value = !1, h());
1341
1353
  } finally {
1342
1354
  c.value = !1;
1343
1355
  }
@@ -1360,7 +1372,7 @@ function At(e, t, n) {
1360
1372
  }
1361
1373
  //#endregion
1362
1374
  //#region src/composables/useMemiBoardLikes.ts
1363
- function jt(e, t) {
1375
+ function Mt(e, t) {
1364
1376
  let n = () => m(), r = Z(), a = ie(), o = j(!1), s = j(!1), c;
1365
1377
  function l(e) {
1366
1378
  return w(r, n(), t, e);
@@ -1401,7 +1413,7 @@ function jt(e, t) {
1401
1413
  }
1402
1414
  //#endregion
1403
1415
  //#region src/composables/useMemiBoardViews.ts
1404
- function Mt() {
1416
+ function Nt() {
1405
1417
  let e = () => m(), t = Z();
1406
1418
  function n(t, n) {
1407
1419
  let { postsCollection: r } = e();
@@ -1441,7 +1453,7 @@ function Mt() {
1441
1453
  }
1442
1454
  //#endregion
1443
1455
  //#region src/utils/moderation-prompt.ts
1444
- var Nt = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\n아래 텍스트에 욕설·비속어·성희롱·혐오·위협·스팸·도배성 광고가 있으면 반드시 거부(flagged=true)합니다.\n\nflagged=true 예:\n- 욕설/비속어: 씨발, 시발, 병신, 지랄, 좆, 개새끼, 존나, fuck, shit, bitch 등\n- 성적 모욕, 혐오 발언, 협박\n- 게시판 주제와 무관한 도배성 광고/스팸\n\nflagged=false:\n- 게시판 주제에 맞는 정상적인 글/댓글 (강한 의견이나 정당한 비판은 허용)\n\nJSON만 출력:\n{\"flagged\":boolean,\"category\":\"none\"|\"abuse\"|\"spam\"|\"adult\"|\"violence\"|\"other\",\"reason\":string}\n- flagged=true면 category를 고르고, reason은 한국어로 **무엇이 문제인지** 구체적으로 1문장\n (예: \"욕설(비속어)이 포함되어 게시할 수 없습니다.\", \"광고·스팸성 문구가 있어 게시할 수 없습니다.\")\n 욕설 단어 전체를 그대로 길게 인용하지 말고, 유형(욕설/혐오/스팸 등)을 분명히 밝힌다.\n- flagged=false면 category는 \"none\", reason은 \"\"", Pt = {
1456
+ var Pt = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\n아래 텍스트에 욕설·비속어·성희롱·혐오·위협·스팸·도배성 광고가 있으면 반드시 거부(flagged=true)합니다.\n\nflagged=true 예:\n- 욕설/비속어: 씨발, 시발, 병신, 지랄, 좆, 개새끼, 존나, fuck, shit, bitch 등\n- 성적 모욕, 혐오 발언, 협박\n- 게시판 주제와 무관한 도배성 광고/스팸\n\nflagged=false:\n- 게시판 주제에 맞는 정상적인 글/댓글 (강한 의견이나 정당한 비판은 허용)\n\nJSON만 출력:\n{\"flagged\":boolean,\"category\":\"none\"|\"abuse\"|\"spam\"|\"adult\"|\"violence\"|\"other\",\"reason\":string}\n- flagged=true면 category를 고르고, reason은 한국어로 **무엇이 문제인지** 구체적으로 1문장\n (예: \"욕설(비속어)이 포함되어 게시할 수 없습니다.\", \"광고·스팸성 문구가 있어 게시할 수 없습니다.\")\n 욕설 단어 전체를 그대로 길게 인용하지 말고, 유형(욕설/혐오/스팸 등)을 분명히 밝힌다.\n- flagged=false면 category는 \"none\", reason은 \"\"", Ft = {
1445
1457
  abuse: "욕설·비속어·모욕",
1446
1458
  spam: "스팸·광고",
1447
1459
  adult: "선정적·성인 내용",
@@ -1449,17 +1461,17 @@ var Nt = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이
1449
1461
  other: "부적절한 내용",
1450
1462
  none: ""
1451
1463
  };
1452
- function Ft(e) {
1464
+ function It(e) {
1453
1465
  if (e.via === "local" && e.localHit) {
1454
1466
  let t = e.localHit.trim();
1455
1467
  return t.length > 0 && t.length <= 12 ? `「${t}」 같은 표현은 사용할 수 없습니다. 다른 말로 바꿔 주세요.` : "욕설·비속어가 포함되어 게시할 수 없습니다. 표현을 바꿔 주세요.";
1456
1468
  }
1457
1469
  let t = (e.reason || "").trim();
1458
1470
  if (t) return t;
1459
- let n = e.category && e.category !== "none" ? Pt[e.category] || e.category : "";
1471
+ let n = e.category && e.category !== "none" ? Ft[e.category] || e.category : "";
1460
1472
  return n ? `${n}이(가) 포함되어 게시할 수 없습니다. 내용을 수정해 주세요.` : "게시할 수 없는 내용이 포함되어 있습니다. 내용을 수정해 주세요.";
1461
1473
  }
1462
- var It = [
1474
+ var Lt = [
1463
1475
  "씨발",
1464
1476
  "시발",
1465
1477
  "시이발",
@@ -1485,10 +1497,10 @@ var It = [
1485
1497
  "bitch",
1486
1498
  "asshole"
1487
1499
  ];
1488
- function Lt(e) {
1500
+ function Rt(e) {
1489
1501
  return new RegExp(e.join("|"), "i");
1490
1502
  }
1491
- var Rt = /* @__PURE__ */ new Set([
1503
+ var zt = /* @__PURE__ */ new Set([
1492
1504
  "none",
1493
1505
  "abuse",
1494
1506
  "spam",
@@ -1496,7 +1508,7 @@ var Rt = /* @__PURE__ */ new Set([
1496
1508
  "violence",
1497
1509
  "other"
1498
1510
  ]);
1499
- function zt(e) {
1511
+ function Bt(e) {
1500
1512
  let t = e.trim();
1501
1513
  if (!t) return null;
1502
1514
  let n;
@@ -1514,7 +1526,7 @@ function zt(e) {
1514
1526
  if (typeof n != "object" || !n) return null;
1515
1527
  let r = n;
1516
1528
  if (typeof r.flagged != "boolean") return null;
1517
- let i = typeof r.category == "string" && Rt.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
1529
+ let i = typeof r.category == "string" && zt.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
1518
1530
  return {
1519
1531
  flagged: r.flagged,
1520
1532
  category: i,
@@ -1523,20 +1535,20 @@ function zt(e) {
1523
1535
  }
1524
1536
  //#endregion
1525
1537
  //#region src/composables/useMemiBoardModeration.ts
1526
- var Bt = {
1538
+ var Vt = {
1527
1539
  flagged: !1,
1528
1540
  category: "none",
1529
1541
  reason: "",
1530
1542
  via: "empty"
1531
1543
  };
1532
- function Vt() {
1533
- let e = s().moderation ?? {}, t = oe(), { isWriteRestricted: n, restrictedMessage: r, recordContentModerationBlock: i } = Ke(), a = Lt([...It, ...e.localBlocklist ?? []]);
1544
+ function Ht() {
1545
+ let e = s().moderation ?? {}, t = oe(), { isWriteRestricted: n, restrictedMessage: r, recordContentModerationBlock: i } = Ke(), a = Rt([...Lt, ...e.localBlocklist ?? []]);
1534
1546
  function o(e) {
1535
1547
  let t = e.match(a);
1536
1548
  return t ? {
1537
1549
  flagged: !0,
1538
1550
  category: "abuse",
1539
- reason: Ft({
1551
+ reason: It({
1540
1552
  via: "local",
1541
1553
  localHit: t[0] || "",
1542
1554
  category: "abuse"
@@ -1551,7 +1563,7 @@ function Vt() {
1551
1563
  });
1552
1564
  return Se(i, {
1553
1565
  model: n,
1554
- systemInstruction: Nt,
1566
+ systemInstruction: Pt,
1555
1567
  generationConfig: {
1556
1568
  temperature: 0,
1557
1569
  maxOutputTokens: 256,
@@ -1580,7 +1592,7 @@ function Vt() {
1580
1592
  };
1581
1593
  }
1582
1594
  function u(e, t = "") {
1583
- let n = Ft({
1595
+ let n = It({
1584
1596
  reason: e.reason,
1585
1597
  category: e.category,
1586
1598
  via: e.via
@@ -1608,22 +1620,22 @@ function Vt() {
1608
1620
  };
1609
1621
  let i = t.trim();
1610
1622
  if (!i) return {
1611
- ...Bt,
1623
+ ...Vt,
1612
1624
  via: "empty"
1613
1625
  };
1614
1626
  let a = o(i);
1615
1627
  if (a) return d(a);
1616
1628
  if (e.enabled === !1) return {
1617
- ...Bt,
1629
+ ...Vt,
1618
1630
  via: "disabled"
1619
1631
  };
1620
1632
  try {
1621
- let e = zt((await c().generateContent(`심사할 텍스트:\n"""${i.slice(0, 4e3)}"""`)).response.text());
1633
+ let e = Bt((await c().generateContent(`심사할 텍스트:\n"""${i.slice(0, 4e3)}"""`)).response.text());
1622
1634
  if (!e) return l();
1623
1635
  let t = {
1624
1636
  flagged: e.flagged,
1625
1637
  category: e.category,
1626
- reason: Ft({
1638
+ reason: It({
1627
1639
  reason: e.reason,
1628
1640
  category: e.category,
1629
1641
  via: "ai"
@@ -1643,14 +1655,14 @@ function Vt() {
1643
1655
  via: "restricted"
1644
1656
  };
1645
1657
  if (e.enabled === !1 || !e.moderateImages) return {
1646
- ...Bt,
1658
+ ...Vt,
1647
1659
  via: "disabled"
1648
1660
  };
1649
1661
  try {
1650
1662
  let e = await new Promise((e, n) => {
1651
1663
  let r = new FileReader();
1652
1664
  r.onload = () => e(r.result.split(",")[1] ?? ""), r.onerror = n, r.readAsDataURL(t);
1653
- }), n = zt((await c().generateContent([{ inlineData: {
1665
+ }), n = Bt((await c().generateContent([{ inlineData: {
1654
1666
  mimeType: t.type || "image/jpeg",
1655
1667
  data: e
1656
1668
  } }, "이 이미지를 심사해 주세요."])).response.text());
@@ -1658,7 +1670,7 @@ function Vt() {
1658
1670
  let r = {
1659
1671
  flagged: n.flagged,
1660
1672
  category: n.category,
1661
- reason: Ft({
1673
+ reason: It({
1662
1674
  reason: n.reason,
1663
1675
  category: n.category,
1664
1676
  via: "ai"
@@ -1677,7 +1689,7 @@ function Vt() {
1677
1689
  }
1678
1690
  //#endregion
1679
1691
  //#region src/composables/useMemiBoardUsers.ts
1680
- var Ht = [
1692
+ var Ut = [
1681
1693
  {
1682
1694
  value: "admin",
1683
1695
  label: "관리자",
@@ -1694,10 +1706,10 @@ var Ht = [
1694
1706
  description: "글과 댓글을 작성하고 본인이 작성한 콘텐츠만 수정·삭제할 수 있습니다."
1695
1707
  }
1696
1708
  ];
1697
- function Ut(e = {}) {
1709
+ function Wt(e = {}) {
1698
1710
  let t = () => m(), n = Z(), r = O(() => M(e.enabled) ?? !0 ? p(n, t()) : null), i = re(r, { ssrKey: d(t(), "users-management") });
1699
1711
  async function a(e, r) {
1700
- if (!Ht.some((e) => e.value === r)) throw Error("지원하지 않는 역할입니다.");
1712
+ if (!Ut.some((e) => e.value === r)) throw Error("지원하지 않는 역할입니다.");
1701
1713
  await J(b(n, t(), e), {
1702
1714
  role: r,
1703
1715
  updatedAt: G()
@@ -1711,7 +1723,7 @@ function Ut(e = {}) {
1711
1723
  }
1712
1724
  //#endregion
1713
1725
  //#region src/composables/useMemiBoardUserProfile.ts
1714
- function Wt(e) {
1726
+ function Gt(e) {
1715
1727
  let t = Z(), n = () => m(), r = O(() => M(e)), i = O(() => {
1716
1728
  let e = r.value;
1717
1729
  return e ? b(t, n(), e) : null;
@@ -1723,7 +1735,18 @@ function Wt(e) {
1723
1735
  }
1724
1736
  //#endregion
1725
1737
  //#region src/data/versionHistory.ts
1726
- var Gt = [
1738
+ var Kt = [
1739
+ {
1740
+ version: "0.32.0",
1741
+ date: "2026-08-14",
1742
+ highlights: ["글쓰기에서 사진 파일을 올리거나 외부 이미지 주소를 넣을 수 있어요", "게시판을 지우면 그 안의 글·댓글·이미지도 함께 삭제돼요"]
1743
+ },
1744
+ {
1745
+ version: "0.31.0",
1746
+ date: "2026-08-14",
1747
+ highlights: ["글쓰기 화면에 AI 도우미가 생겼어요. 맞춤법, 다듬기, 이어서 쓰기, 요약, 번역, 제목을 도와줘요", "AI 도우미는 하루 10번까지 사용할 수 있어요"],
1748
+ note: "호스트는 Firestore 규칙에 AI 글쓰기 횟수 필드를 병합·배포해야 제한이 지켜져요."
1749
+ },
1727
1750
  {
1728
1751
  version: "0.30.2",
1729
1752
  date: "2026-08-14",
@@ -1922,7 +1945,7 @@ var Gt = [
1922
1945
  highlights: ["글쓰기·댓글·첨부·로그인 권한으로 게시판을 바로 시작할 수 있어요", "부적절한 글·댓글은 AI 검열로 막을 수 있어요"],
1923
1946
  note: "실제 보안은 호스트 Firebase 규칙 등록이 필요합니다."
1924
1947
  }
1925
- ], Kt = /* @__PURE__ */ r(((e, t) => {
1948
+ ], qt = /* @__PURE__ */ r(((e, t) => {
1926
1949
  (function(n, r) {
1927
1950
  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();
1928
1951
  })(e, (function() {
@@ -2216,7 +2239,7 @@ var Gt = [
2216
2239
  return w(1e3 * e);
2217
2240
  }, w.en = b[y], w.Ls = b, w.p = {}, w;
2218
2241
  }));
2219
- })), qt = /* @__PURE__ */ r(((e, t) => {
2242
+ })), Jt = /* @__PURE__ */ r(((e, t) => {
2220
2243
  (function(n, r) {
2221
2244
  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();
2222
2245
  })(e, (function() {
@@ -2320,9 +2343,9 @@ var Gt = [
2320
2343
  };
2321
2344
  };
2322
2345
  }));
2323
- })), Jt = /* @__PURE__ */ r(((e, t) => {
2346
+ })), Yt = /* @__PURE__ */ r(((e, t) => {
2324
2347
  (function(n, r) {
2325
- typeof e == "object" && t !== void 0 ? t.exports = r(Kt()) : typeof define == "function" && define.amd ? define(["dayjs"], r) : (n = typeof globalThis < "u" ? globalThis : n || self).dayjs_locale_ko = r(n.dayjs);
2348
+ typeof e == "object" && t !== void 0 ? t.exports = r(qt()) : typeof define == "function" && define.amd ? define(["dayjs"], r) : (n = typeof globalThis < "u" ? globalThis : n || self).dayjs_locale_ko = r(n.dayjs);
2326
2349
  })(e, (function(e) {
2327
2350
  function t(e) {
2328
2351
  return e && typeof e == "object" && "default" in e ? e : { default: e };
@@ -2370,31 +2393,96 @@ var Gt = [
2370
2393
  };
2371
2394
  return n.default.locale(r, null, !0), r;
2372
2395
  }));
2373
- })), Yt = /* @__PURE__ */ h(Kt(), 1), Xt = /* @__PURE__ */ h(qt(), 1);
2374
- Jt(), Yt.default.extend(Xt.default), Yt.default.locale("ko");
2375
- function Zt(e) {
2396
+ })), Xt = /* @__PURE__ */ h(qt(), 1), Zt = /* @__PURE__ */ h(Jt(), 1);
2397
+ Yt(), Xt.default.extend(Zt.default), Xt.default.locale("ko");
2398
+ function Qt(e) {
2376
2399
  return e?.toDate ? e.toDate().toLocaleDateString("ko-KR") : "";
2377
2400
  }
2378
- function Qt(e, t = Date.now()) {
2379
- return e?.toDate ? (0, Yt.default)(e.toDate()).from((0, Yt.default)(t)) : "방금 전";
2401
+ function $t(e, t = Date.now()) {
2402
+ return e?.toDate ? (0, Xt.default)(e.toDate()).from((0, Xt.default)(t)) : "방금 전";
2403
+ }
2404
+ function en(e) {
2405
+ return e?.toDate ? (0, Xt.default)(e.toDate()).format("YYYY년 M월 D일 HH:mm:ss") : "시간 확인 중";
2380
2406
  }
2381
- function $t(e) {
2382
- return e?.toDate ? (0, Yt.default)(e.toDate()).format("YYYY년 M월 D일 HH:mm:ss") : "시간 확인 중";
2407
+ function tn(e, t) {
2408
+ let n = en(e), r = e?.toMillis?.(), i = t?.toMillis?.();
2409
+ return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${en(t)}`];
2383
2410
  }
2384
- function en(e, t) {
2385
- let n = $t(e), r = e?.toMillis?.(), i = t?.toMillis?.();
2386
- return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${$t(t)}`];
2411
+ //#endregion
2412
+ //#region src/composables/useMemiBoardWritingAssistant.ts
2413
+ var nn = 10, rn = 864e5, an = {
2414
+ proofread: "맞춤법, 띄어쓰기, 문법 오류만 고쳐라. 의미와 말투는 바꾸지 마라.",
2415
+ polish: "뜻과 작성자의 말투를 유지하면서 더 자연스럽고 읽기 좋은 문장으로 다듬어라.",
2416
+ continue: "앞의 맥락과 말투에 맞는 내용을 과장 없이 한두 문단 이어서 작성하라.",
2417
+ summarize: "핵심 정보는 보존하면서 원문의 약 절반 길이로 간결하게 줄여라.",
2418
+ "translate-en": "자연스러운 영어로 번역하라. 고유명사와 기술 용어는 정확히 유지하라.",
2419
+ "translate-ko": "자연스러운 한국어로 번역하라. 고유명사와 기술 용어는 정확히 유지하라.",
2420
+ title: "본문을 대표하는 간결한 한국어 제목 하나를 만들어라. 따옴표나 설명 없이 제목만 출력하라."
2421
+ };
2422
+ function on(e) {
2423
+ return e.trim().replace(/^```(?:html|text)?\s*/i, "").replace(/\s*```$/, "").trim();
2424
+ }
2425
+ function sn(e) {
2426
+ return !e || typeof e != "object" ? null : "toMillis" in e && typeof e.toMillis == "function" ? e.toMillis() : null;
2427
+ }
2428
+ function cn() {
2429
+ let e = oe(), t = Z(), n = ie(), r = s();
2430
+ async function i() {
2431
+ let e = n.value?.uid;
2432
+ if (!e) throw Error("AI 글쓰기 도우미는 로그인 후 사용할 수 있습니다.");
2433
+ await ne(t, async (n) => {
2434
+ let r = b(t, m(), e), i = await n.get(r);
2435
+ if (!i.exists()) throw Error("사용자 정보를 확인한 뒤 다시 시도해 주세요.");
2436
+ let a = i.data(), o = sn(a.aiWritingWindowAt), s = Math.max(0, Math.floor(Number(a.aiWritingCount) || 0)), c = Date.now(), l = o != null && c - o < rn;
2437
+ if (l && s >= 10) {
2438
+ let e = o + rn, t = Math.max(1, Math.ceil((e - c) / 36e5));
2439
+ throw Error(`AI 글쓰기 도우미는 하루 10회까지 사용할 수 있습니다. 약 ${t}시간 후 다시 이용해 주세요.`);
2440
+ }
2441
+ n.update(r, {
2442
+ aiWritingCount: l ? s + 1 : 1,
2443
+ aiWritingWindowAt: l ? a.aiWritingWindowAt : G(),
2444
+ updatedAt: G()
2445
+ });
2446
+ });
2447
+ }
2448
+ async function a(t) {
2449
+ await i();
2450
+ let n = r.moderation ?? {}, a = xe(e, {
2451
+ backend: new ye(),
2452
+ useLimitedUseAppCheckTokens: n.useLimitedUseAppCheckTokens !== !1
2453
+ }), o = Se(a, {
2454
+ model: n.model ?? "gemini-3.5-flash-lite",
2455
+ systemInstruction: [
2456
+ "너는 게시판 글쓰기 편집 도우미다.",
2457
+ "사용자가 요청한 편집 결과만 출력하고 설명, 인사, 마크다운 코드 펜스를 붙이지 마라.",
2458
+ t.action === "title" || t.selection ? "출력은 일반 텍스트로 작성하라." : "입력의 HTML 구조와 링크, 이미지 태그를 보존하고 결과도 HTML 조각으로 출력하라."
2459
+ ].join("\n"),
2460
+ generationConfig: {
2461
+ temperature: t.action === "continue" || t.action === "title" ? .6 : .2,
2462
+ maxOutputTokens: 2048
2463
+ }
2464
+ }), s = t.content.trim();
2465
+ if (!s) throw Error("AI로 다듬을 내용을 먼저 입력해 주세요.");
2466
+ let c = [
2467
+ `작업: ${an[t.action]}`,
2468
+ t.title ? `현재 제목: ${t.title}` : "",
2469
+ `본문:\n${s.slice(0, 12e3)}`
2470
+ ].filter(Boolean).join("\n\n"), l = on((await o.generateContent(c)).response.text());
2471
+ if (!l) throw Error("AI가 결과를 만들지 못했습니다. 다시 시도해 주세요.");
2472
+ return l;
2473
+ }
2474
+ return { assist: a };
2387
2475
  }
2388
2476
  //#endregion
2389
2477
  //#region src/utils/renderMarkdown.ts
2390
2478
  function $(e) {
2391
2479
  return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2392
2480
  }
2393
- function tn(e) {
2481
+ function ln(e) {
2394
2482
  let t = e;
2395
2483
  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;
2396
2484
  }
2397
- function nn(e) {
2485
+ function un(e) {
2398
2486
  if (!e?.trim()) return "";
2399
2487
  let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = 0, i = !1, a = [], o = null, s = [];
2400
2488
  function c() {
@@ -2403,7 +2491,7 @@ function nn(e) {
2403
2491
  return;
2404
2492
  }
2405
2493
  let e = o;
2406
- n.push(`<${e}>${s.map((e) => `<li>${tn(e)}</li>`).join("")}</${e}>`), o = null, s = [];
2494
+ n.push(`<${e}>${s.map((e) => `<li>${ln(e)}</li>`).join("")}</${e}>`), o = null, s = [];
2407
2495
  }
2408
2496
  for (; r < t.length;) {
2409
2497
  let e = t[r] ?? "", l = e.match(/^```(\w*)\s*$/);
@@ -2423,14 +2511,14 @@ function nn(e) {
2423
2511
  if (u) {
2424
2512
  c();
2425
2513
  let e = u[1].length;
2426
- n.push(`<h${e} class="font-semibold mt-4 mb-2">${tn($(u[2]))}</h${e}>`), r++;
2514
+ n.push(`<h${e} class="font-semibold mt-4 mb-2">${ln($(u[2]))}</h${e}>`), r++;
2427
2515
  continue;
2428
2516
  }
2429
2517
  if (e.startsWith("> ")) {
2430
2518
  c();
2431
2519
  let e = [];
2432
2520
  for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push($((t[r] ?? "").slice(2))), r++;
2433
- n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${tn(e.join("<br />"))}</blockquote>`);
2521
+ n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${ln(e.join("<br />"))}</blockquote>`);
2434
2522
  continue;
2435
2523
  }
2436
2524
  let d = e.match(/^[-*+]\s+(.+)$/);
@@ -2454,11 +2542,11 @@ function nn(e) {
2454
2542
  if (!e.trim() || /^(#{1,3}\s|```|[-*+]\s|\d+\.\s|> |(-{3,}|\*{3,})\s*$)/.test(e)) break;
2455
2543
  p.push($(e)), r++;
2456
2544
  }
2457
- n.push(`<p class="my-2 leading-relaxed">${tn(p.join("<br />"))}</p>`);
2545
+ n.push(`<p class="my-2 leading-relaxed">${ln(p.join("<br />"))}</p>`);
2458
2546
  }
2459
2547
  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");
2460
2548
  }
2461
2549
  //#endregion
2462
- export { St as AUTHOR_MEMO_MAX_LENGTH, Ht as BOARD_USER_ROLES, Dt as COMMENT_BODY_MAX_LENGTH, Re as DEFAULT_BLOCK_BAN_DECAY_MS, Le as DEFAULT_BLOCK_BAN_THRESHOLD, at as DEFAULT_BOARDS, ot as DEFAULT_CATEGORIES, y as DEFAULT_COMMENTS_COLLECTION, S as DEFAULT_LIKES_COLLECTION, l as DEFAULT_POSTS_COLLECTION, g as DEFAULT_SETTINGS_COLLECTION, C as DEFAULT_USERS_COLLECTION, Ce as asHttpUrl, ke as boardListOgDescription, Oe as boardListOgTitle, De as boardPostOgDescription, Ee as boardPostOgTitle, d as boardSsrKey, b as boardUserDoc, p as boardUsersCol, it as buildPostPreview, f as commentDoc, o as commentsCol, x as configureMemiBoard, ze as effectiveModerationBlockCount, qe as extractEditorImageUrls, Fe as fetchPublicListForSeo, Pe as fetchPublicPostForSeo, Zt as formatDate, $t as formatFullDate, Qt as formatRelativeDate, He as formatRestrictedUntilLabel, en as formatTimestampDetails, $e as hasBodyImage, Qe as hasBodyText, et as isContentEmpty, ft as isFirestorePermissionDenied, Ve as isModerationWriteRestricted, w as likeDoc, D as likeDocId, c as likesCol, Be as moderationWriteRestrictedUntilMs, Ae as normalizeBasePath, Xe as plainTextFromHtml, E as postBodyDoc, i as postDoc, Ye as postNamespaceFromStoragePath, e as postStorageFolder, T as postsCol, nn as renderMarkdownToHtml, a as resolveBoardPathConfig, je as resolvePublicSeoDb, n as settingsCol, t as settingsDoc, v as slugify, Je as storagePathFromDownloadUrl, Ze as titleFromBody, Te as toAbsoluteUrl, we as toBoardOgImageUrl, m as useBoardPathConfig, Ke as useMemiBoardAuth, wt as useMemiBoardAuthorMemo, kt as useMemiBoardComments, s as useMemiBoardConfig, jt as useMemiBoardLikes, Vt as useMemiBoardModeration, _t as useMemiBoardPost, gt as useMemiBoardPostList, mt as useMemiBoardPosts, At as useMemiBoardReplies, lt as useMemiBoardSettings, xt as useMemiBoardUserComments, yt as useMemiBoardUserPosts, Wt as useMemiBoardUserProfile, Ut as useMemiBoardUsers, Mt as useMemiBoardViews, Gt as versionHistory, rt as videoListCoverUrl, nt as youtubeId };
2550
+ export { Ct as AUTHOR_MEMO_MAX_LENGTH, Ut as BOARD_USER_ROLES, Ot as COMMENT_BODY_MAX_LENGTH, Re as DEFAULT_BLOCK_BAN_DECAY_MS, Le as DEFAULT_BLOCK_BAN_THRESHOLD, ct as DEFAULT_BOARDS, lt as DEFAULT_CATEGORIES, y as DEFAULT_COMMENTS_COLLECTION, S as DEFAULT_LIKES_COLLECTION, l as DEFAULT_POSTS_COLLECTION, g as DEFAULT_SETTINGS_COLLECTION, C as DEFAULT_USERS_COLLECTION, nn as WRITING_ASSISTANT_DAILY_LIMIT, Ce as asHttpUrl, ke as boardListOgDescription, Oe as boardListOgTitle, De as boardPostOgDescription, Ee as boardPostOgTitle, d as boardSsrKey, b as boardUserDoc, p as boardUsersCol, st as buildPostPreview, f as commentDoc, o as commentsCol, x as configureMemiBoard, ze as effectiveModerationBlockCount, qe as extractEditorImageUrls, Fe as fetchPublicListForSeo, Pe as fetchPublicPostForSeo, Qt as formatDate, en as formatFullDate, $t as formatRelativeDate, He as formatRestrictedUntilLabel, tn as formatTimestampDetails, nt as hasBodyImage, tt as hasBodyText, rt as isContentEmpty, pt as isFirestorePermissionDenied, Ve as isModerationWriteRestricted, w as likeDoc, D as likeDocId, c as likesCol, Be as moderationWriteRestrictedUntilMs, Ae as normalizeBasePath, $e as plainTextFromHtml, E as postBodyDoc, i as postDoc, Ye as postNamespaceFromStoragePath, e as postStorageFolder, T as postsCol, un as renderMarkdownToHtml, a as resolveBoardPathConfig, je as resolvePublicSeoDb, n as settingsCol, t as settingsDoc, v as slugify, Je as storagePathFromDownloadUrl, et as titleFromBody, Te as toAbsoluteUrl, we as toBoardOgImageUrl, m as useBoardPathConfig, Ke as useMemiBoardAuth, Tt as useMemiBoardAuthorMemo, At as useMemiBoardComments, s as useMemiBoardConfig, Mt as useMemiBoardLikes, Ht as useMemiBoardModeration, vt as useMemiBoardPost, _t as useMemiBoardPostList, ht as useMemiBoardPosts, jt as useMemiBoardReplies, ft as useMemiBoardSettings, St as useMemiBoardUserComments, bt as useMemiBoardUserPosts, Gt as useMemiBoardUserProfile, Wt as useMemiBoardUsers, Nt as useMemiBoardViews, cn as useMemiBoardWritingAssistant, Kt as versionHistory, ot as videoListCoverUrl, at as youtubeId };
2463
2551
 
2464
2552
  //# sourceMappingURL=index.js.map