memi-board 0.23.0 → 0.28.0

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