memi-board 0.32.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -2
- package/dist/composables/boardLookup.d.ts +8 -0
- package/dist/composables/useMemiBoardAuth.d.ts +4 -3
- package/dist/composables/useMemiBoardCommentFeed.d.ts +18 -0
- package/dist/composables/useMemiBoardComments.d.ts +2 -0
- package/dist/composables/useMemiBoardLikes.d.ts +9 -0
- package/dist/composables/useMemiBoardMiniPosts.d.ts +12 -0
- package/dist/composables/useMemiBoardReports.d.ts +114 -0
- package/dist/composables/useMemiBoardSections.d.ts +13 -0
- package/dist/composables/useMemiBoardSettings.d.ts +2 -0
- package/dist/config.d.ts +2 -0
- package/dist/{heic2any-BWRKffWN.js → heic2any-D2QKy06h.js} +2 -2
- package/dist/{heic2any-BWRKffWN.js.map → heic2any-D2QKy06h.js.map} +1 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.js +1161 -647
- package/dist/index.js.map +1 -1
- package/dist/runtime/components/CategorySettings.vue +3 -5
- package/dist/runtime/components/CommentFeed.vue +141 -0
- package/dist/runtime/components/CommentForm.vue +2 -2
- package/dist/runtime/components/CommentItem.vue +55 -5
- package/dist/runtime/components/CommentList.vue +10 -2
- package/dist/runtime/components/Detail.vue +8 -0
- package/dist/runtime/components/ListDefault.vue +2 -2
- package/dist/runtime/components/ListDense.vue +2 -2
- package/dist/runtime/components/ListVideo.vue +2 -2
- package/dist/runtime/components/Mini.vue +37 -0
- package/dist/runtime/components/MiniCard.vue +51 -0
- package/dist/runtime/components/MiniComments.vue +75 -0
- package/dist/runtime/components/MiniList.vue +36 -0
- package/dist/runtime/components/MiniPicker.vue +131 -0
- package/dist/runtime/components/MiniPost.vue +52 -0
- package/dist/runtime/components/MiniPosts.vue +115 -0
- package/dist/runtime/components/ReportButton.vue +117 -0
- package/dist/runtime/components/ReportList.vue +124 -0
- package/dist/runtime/components/Section.vue +62 -0
- package/dist/runtime/components/Settings.vue +3 -4
- package/dist/runtime/components/UserCommentList.vue +3 -0
- package/dist/{slugify-B5XHr4SZ.js → slugify-Cyq-Gg8X.js} +47 -36
- package/dist/slugify-Cyq-Gg8X.js.map +1 -0
- package/dist/storage.js +11 -11
- package/dist/types.d.ts +54 -3
- package/dist/utils/boardAccess.d.ts +7 -0
- package/dist/utils/boardPaths.d.ts +10 -3
- package/dist/utils/deletePostCascade.d.ts +7 -1
- package/dist/utils/section.d.ts +31 -0
- package/docs/firestore.indexes.json.example +38 -0
- package/docs/firestore.rules.example +78 -22
- package/package.json +1 -1
- package/dist/slugify-B5XHr4SZ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { computed as
|
|
3
|
-
import { deleteDoc as
|
|
4
|
-
import { useCollection as
|
|
5
|
-
import { GoogleAuthProvider as
|
|
6
|
-
import { deleteObject as
|
|
7
|
-
import { GoogleAIBackend as
|
|
1
|
+
import { A as e, C as t, D as n, E as r, M as i, N as a, O as o, S as s, T as c, _ as l, a as u, b as d, c as f, d as p, f as m, g as h, h as g, i as _, j as v, k as y, l as b, m as x, n as S, o as C, p as w, r as T, s as E, u as D, v as O, w as k, x as A, y as j } from "./slugify-Cyq-Gg8X.js";
|
|
2
|
+
import { computed as M, onScopeDispose as N, reactive as ee, ref as P, toValue as F, watch as I } from "vue";
|
|
3
|
+
import { deleteDoc as te, deleteField as ne, doc as re, documentId as L, endBefore as ie, getDoc as R, getDocs as z, increment as B, limit as V, limitToLast as ae, onSnapshot as H, orderBy as U, query as W, runTransaction as oe, 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 se, useCurrentUser as Z, useDocument as ce, useFirebaseApp as le, useFirebaseAuth as ue, useFirestore as Q } from "vuefire";
|
|
5
|
+
import { GoogleAuthProvider as de, OAuthProvider as fe, createUserWithEmailAndPassword as pe, signInWithEmailAndPassword as me, signInWithPopup as he, signOut as ge, updateProfile as _e } from "firebase/auth";
|
|
6
|
+
import { deleteObject as ve, getStorage as ye, listAll as be, ref as xe } from "firebase/storage";
|
|
7
|
+
import { GoogleAIBackend as Se, Schema as Ce, getAI as we, getGenerativeModel as Te } from "firebase/ai";
|
|
8
8
|
//#region src/utils/boardSeo.ts
|
|
9
|
-
function
|
|
9
|
+
function Ee(e) {
|
|
10
10
|
if (typeof e != "string") return null;
|
|
11
11
|
let t = e.trim();
|
|
12
12
|
return /^https?:\/\//i.test(t) ? t : null;
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
let t =
|
|
14
|
+
function De(e) {
|
|
15
|
+
let t = Ee(e);
|
|
16
16
|
if (!t) return null;
|
|
17
17
|
if (!/firebasestorage\.googleapis\.com/i.test(t)) return t;
|
|
18
18
|
try {
|
|
@@ -29,62 +29,62 @@ function we(e) {
|
|
|
29
29
|
return t;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
function
|
|
32
|
+
function Oe(e, t, n = "") {
|
|
33
33
|
let r = t.replace(/\/$/, ""), i = e.trim();
|
|
34
|
-
return i ? /^https?:\/\//i.test(i) ? i : `${r}${i.startsWith("/") ? i : `/${i}`}` : n ?
|
|
34
|
+
return i ? /^https?:\/\//i.test(i) ? i : `${r}${i.startsWith("/") ? i : `/${i}`}` : n ? Oe(n, t) : r;
|
|
35
35
|
}
|
|
36
|
-
function
|
|
36
|
+
function ke(e, t, n, r) {
|
|
37
37
|
let i = r?.trim().slice(0, 40), a = e.trim() || i || "사진", o = t?.trim(), s = n.trim() || "Board";
|
|
38
38
|
return o ? `${a} · ${o} | ${s}` : `${a} | ${s}`;
|
|
39
39
|
}
|
|
40
|
-
function
|
|
40
|
+
function Ae(e) {
|
|
41
41
|
let t = [];
|
|
42
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);
|
|
43
43
|
}
|
|
44
|
-
function
|
|
44
|
+
function je(e, t) {
|
|
45
45
|
let n = t.trim() || "Board", r = e?.trim();
|
|
46
46
|
return r && r !== "전체" ? `${r} 게시판 | ${n}` : `게시판 | ${n}`;
|
|
47
47
|
}
|
|
48
|
-
function
|
|
48
|
+
function Me(e) {
|
|
49
49
|
if (e.description?.trim()) return e.description.trim().slice(0, 200);
|
|
50
50
|
let t = e.categoryLabel?.trim(), n = t && t !== "전체" ? `${t} 게시판 — 소식을 확인하세요.` : "게시판 — 공지와 이야기를 확인하세요.", r = (e.recentTitles || []).filter(Boolean).slice(0, 3);
|
|
51
51
|
return r.length ? `${n} ${r.map((e) => `「${e}」`).join(" ")}`.slice(0, 200) : n.slice(0, 200);
|
|
52
52
|
}
|
|
53
|
-
function
|
|
53
|
+
function Ne(e) {
|
|
54
54
|
let t = (e || "/board").trim() || "/board";
|
|
55
55
|
return (t.startsWith("/") ? t : `/${t}`).replace(/\/$/, "") || "/board";
|
|
56
56
|
}
|
|
57
57
|
//#endregion
|
|
58
58
|
//#region src/composables/fetchPublicSeo.ts
|
|
59
|
-
function
|
|
59
|
+
function Pe() {
|
|
60
60
|
return {
|
|
61
|
-
db:
|
|
62
|
-
paths:
|
|
61
|
+
db: Q(),
|
|
62
|
+
paths: _()
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
-
async function
|
|
65
|
+
async function Fe(e, t) {
|
|
66
66
|
try {
|
|
67
|
-
let
|
|
68
|
-
if (!
|
|
69
|
-
label:
|
|
67
|
+
let n = await R(v(e.db, e.paths, t));
|
|
68
|
+
if (!n.exists()) return {
|
|
69
|
+
label: t,
|
|
70
70
|
description: "",
|
|
71
71
|
hidden: !1
|
|
72
72
|
};
|
|
73
|
-
let
|
|
73
|
+
let r = n.data() ?? {};
|
|
74
74
|
return {
|
|
75
|
-
label: typeof
|
|
76
|
-
description: typeof
|
|
77
|
-
hidden:
|
|
75
|
+
label: typeof r.label == "string" && r.label.trim() ? r.label.trim() : t,
|
|
76
|
+
description: typeof r.description == "string" ? r.description.trim() : "",
|
|
77
|
+
hidden: r.visibility === "hidden"
|
|
78
78
|
};
|
|
79
79
|
} catch {
|
|
80
80
|
return {
|
|
81
|
-
label:
|
|
81
|
+
label: t,
|
|
82
82
|
description: "",
|
|
83
83
|
hidden: !1
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
function
|
|
87
|
+
function Ie(e) {
|
|
88
88
|
if (e && typeof e == "object" && "toDate" in e) try {
|
|
89
89
|
let t = e.toDate();
|
|
90
90
|
return t instanceof Date ? t.toISOString() : null;
|
|
@@ -93,36 +93,36 @@ function Ne(e) {
|
|
|
93
93
|
}
|
|
94
94
|
return null;
|
|
95
95
|
}
|
|
96
|
-
async function
|
|
96
|
+
async function Le(e, t, n) {
|
|
97
97
|
let r = e.trim(), i = t.trim();
|
|
98
98
|
if (!r || !i) return null;
|
|
99
|
-
let
|
|
100
|
-
|
|
101
|
-
let
|
|
102
|
-
if (
|
|
99
|
+
let a = n ?? Pe();
|
|
100
|
+
a.paths = y(a.paths);
|
|
101
|
+
let o = await Fe(a, r);
|
|
102
|
+
if (o.hidden) return null;
|
|
103
103
|
try {
|
|
104
|
-
let e = (await z(W(
|
|
104
|
+
let e = (await z(W(c(a.db, a.paths), Y("boardId", "==", r), Y("slug", "==", i), Y("listed", "==", !0), Y("isPublished", "==", !0), V(1)))).docs[0];
|
|
105
105
|
if (!e) return null;
|
|
106
106
|
let t = e.data() ?? {}, n = typeof t.title == "string" ? t.title.trim() : "";
|
|
107
107
|
return n ? {
|
|
108
108
|
id: e.id,
|
|
109
109
|
slug: typeof t.slug == "string" ? t.slug : i,
|
|
110
110
|
category: r,
|
|
111
|
-
categoryLabel:
|
|
111
|
+
categoryLabel: o.label,
|
|
112
112
|
title: n,
|
|
113
113
|
summary: typeof t.summary == "string" ? t.summary.trim().slice(0, 200) : "",
|
|
114
|
-
previewImage:
|
|
114
|
+
previewImage: De(t.previewImage),
|
|
115
115
|
authorName: typeof t.authorName == "string" && t.authorName.trim() ? t.authorName.trim() : null,
|
|
116
|
-
createdAt:
|
|
116
|
+
createdAt: Ie(t.createdAt)
|
|
117
117
|
} : null;
|
|
118
118
|
} catch (e) {
|
|
119
119
|
return import.meta.dev && console.warn("[memi-board] fetchPublicPostForSeo failed", e), null;
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
async function
|
|
123
|
-
let n = e?.trim() || null, r = t ??
|
|
124
|
-
if (r.paths =
|
|
125
|
-
let e = await
|
|
122
|
+
async function Re(e, t) {
|
|
123
|
+
let n = e?.trim() || null, r = t ?? Pe();
|
|
124
|
+
if (r.paths = y(r.paths), n) {
|
|
125
|
+
let e = await Fe(r, n);
|
|
126
126
|
if (e.hidden) return {
|
|
127
127
|
kind: "category",
|
|
128
128
|
category: n,
|
|
@@ -131,7 +131,7 @@ async function Fe(e, t) {
|
|
|
131
131
|
ogImage: null,
|
|
132
132
|
recentTitles: []
|
|
133
133
|
};
|
|
134
|
-
let { recentTitles: t, ogImage: i } = await
|
|
134
|
+
let { recentTitles: t, ogImage: i } = await ze(r, n);
|
|
135
135
|
return {
|
|
136
136
|
kind: "category",
|
|
137
137
|
category: n,
|
|
@@ -150,13 +150,13 @@ async function Fe(e, t) {
|
|
|
150
150
|
recentTitles: []
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
|
-
async function
|
|
153
|
+
async function ze(e, t) {
|
|
154
154
|
let n = [], r = null;
|
|
155
155
|
try {
|
|
156
|
-
let i = await z(W(
|
|
156
|
+
let i = await z(W(c(e.db, e.paths), Y("boardId", "==", t), Y("listed", "==", !0), Y("isPublished", "==", !0), U("createdAt", "desc"), V(12)));
|
|
157
157
|
for (let e of i.docs) {
|
|
158
158
|
let t = e.data() ?? {}, i = typeof t.title == "string" ? t.title.trim() : "";
|
|
159
|
-
i && n.length < 5 && n.push(i), r ||=
|
|
159
|
+
i && n.length < 5 && n.push(i), r ||= De(t.previewImage);
|
|
160
160
|
}
|
|
161
161
|
} catch (e) {
|
|
162
162
|
import.meta.dev && console.warn("[memi-board] loadRecentListed failed", e);
|
|
@@ -167,9 +167,26 @@ async function Ie(e, t) {
|
|
|
167
167
|
};
|
|
168
168
|
}
|
|
169
169
|
//#endregion
|
|
170
|
+
//#region src/utils/boardAccess.ts
|
|
171
|
+
function Be(e, t) {
|
|
172
|
+
return !e || !t ? !1 : (t.allowedStaffUids ?? []).includes(e);
|
|
173
|
+
}
|
|
174
|
+
function Ve(e, t, n, r) {
|
|
175
|
+
return e ? t ? !0 : n ? Be(e, r) : !1 : !1;
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/composables/boardLookup.ts
|
|
179
|
+
var He;
|
|
180
|
+
function Ue(e) {
|
|
181
|
+
He = e;
|
|
182
|
+
}
|
|
183
|
+
function We(e) {
|
|
184
|
+
return He?.(e);
|
|
185
|
+
}
|
|
186
|
+
//#endregion
|
|
170
187
|
//#region src/utils/moderation-strike.ts
|
|
171
|
-
var
|
|
172
|
-
function
|
|
188
|
+
var Ge = 10, Ke = 864e5;
|
|
189
|
+
function qe(e, t, n = Date.now(), r = Ke) {
|
|
173
190
|
let i = Math.max(0, Math.floor(Number(e) || 0));
|
|
174
191
|
if (i <= 0) return 0;
|
|
175
192
|
let a = typeof t == "number" && Number.isFinite(t) ? t : null;
|
|
@@ -177,7 +194,7 @@ function ze(e, t, n = Date.now(), r = Re) {
|
|
|
177
194
|
let o = Math.floor((n - a) / r);
|
|
178
195
|
return Math.max(0, i - o);
|
|
179
196
|
}
|
|
180
|
-
function
|
|
197
|
+
function Je(e, t, n = Date.now(), r = 10, i = Ke) {
|
|
181
198
|
let a = Math.max(0, Math.floor(Number(e) || 0));
|
|
182
199
|
if (a < r) return null;
|
|
183
200
|
let o = typeof t == "number" && Number.isFinite(t) ? t : null;
|
|
@@ -185,17 +202,17 @@ function Be(e, t, n = Date.now(), r = 10, i = Re) {
|
|
|
185
202
|
let s = o + (a - (r - 1)) * i;
|
|
186
203
|
return s > n ? s : null;
|
|
187
204
|
}
|
|
188
|
-
function
|
|
189
|
-
return
|
|
205
|
+
function Ye(e, t, n = Date.now(), r = 10, i = Ke) {
|
|
206
|
+
return Je(e, t, n, r, i) != null;
|
|
190
207
|
}
|
|
191
|
-
function
|
|
208
|
+
function Xe(e, t = Date.now()) {
|
|
192
209
|
let n = Math.max(0, e - t), r = Math.ceil(n / 36e5);
|
|
193
210
|
if (r <= 0) return "곧 해제";
|
|
194
211
|
if (r < 24) return `약 ${r}시간 후 해제`;
|
|
195
212
|
let i = Math.floor(r / 24), a = r % 24;
|
|
196
213
|
return a === 0 ? `약 ${i}일 후 해제` : `약 ${i}일 ${a}시간 후 해제`;
|
|
197
214
|
}
|
|
198
|
-
function
|
|
215
|
+
function Ze(e) {
|
|
199
216
|
if (e == null) return null;
|
|
200
217
|
if (typeof e == "number" && Number.isFinite(e)) return e;
|
|
201
218
|
if (typeof e == "object" && e && "toMillis" in e && typeof e.toMillis == "function") return e.toMillis();
|
|
@@ -207,39 +224,39 @@ function Ue(e) {
|
|
|
207
224
|
}
|
|
208
225
|
//#endregion
|
|
209
226
|
//#region src/composables/useMemiBoardAuth.ts
|
|
210
|
-
var
|
|
211
|
-
function
|
|
227
|
+
var Qe = "__MEMI_BOARD_AUTH_STATE__";
|
|
228
|
+
function $e() {
|
|
212
229
|
let e = globalThis;
|
|
213
|
-
return e[
|
|
214
|
-
role:
|
|
215
|
-
rolePending:
|
|
216
|
-
rawBlockCount:
|
|
217
|
-
blockAtMs:
|
|
230
|
+
return e[Qe] || (e[Qe] = {
|
|
231
|
+
role: P("user"),
|
|
232
|
+
rolePending: P(!0),
|
|
233
|
+
rawBlockCount: P(0),
|
|
234
|
+
blockAtMs: P(null),
|
|
218
235
|
watchStarted: !1
|
|
219
|
-
}), e[
|
|
236
|
+
}), e[Qe];
|
|
220
237
|
}
|
|
221
|
-
function
|
|
222
|
-
let e =
|
|
223
|
-
function
|
|
238
|
+
function et() {
|
|
239
|
+
let e = u(), t = $e(), n = Q(), r = ue(), i = Z(), { role: a, rolePending: o, rawBlockCount: s, blockAtMs: c } = t;
|
|
240
|
+
function l() {
|
|
224
241
|
let t = e.moderation?.blockBanThreshold;
|
|
225
242
|
return t === 0 ? 0 : typeof t == "number" && t > 0 ? Math.floor(t) : 10;
|
|
226
243
|
}
|
|
227
244
|
function d() {
|
|
228
245
|
let t = e.moderation?.blockBanDecayMs;
|
|
229
|
-
return typeof t == "number" && t > 0 ? t :
|
|
246
|
+
return typeof t == "number" && t > 0 ? t : Ke;
|
|
230
247
|
}
|
|
231
248
|
function f(e) {
|
|
232
|
-
return
|
|
249
|
+
return x(n, _(), e);
|
|
233
250
|
}
|
|
234
251
|
function p(e) {
|
|
235
252
|
if (!e) {
|
|
236
|
-
|
|
253
|
+
s.value = 0, c.value = null;
|
|
237
254
|
return;
|
|
238
255
|
}
|
|
239
|
-
|
|
256
|
+
s.value = Math.max(0, Math.floor(Number(e.moderationBlockCount) || 0)), c.value = Ze(e.moderationBlockAt);
|
|
240
257
|
}
|
|
241
|
-
async function
|
|
242
|
-
let t = f(e.uid), r =
|
|
258
|
+
async function m(e) {
|
|
259
|
+
let t = f(e.uid), r = g(n, _(), e.uid), i = await R(t);
|
|
243
260
|
if (!i.exists()) return await Promise.all([K(t, {
|
|
244
261
|
role: "user",
|
|
245
262
|
displayName: e.displayName,
|
|
@@ -260,7 +277,7 @@ function Ke() {
|
|
|
260
277
|
"user"
|
|
261
278
|
].includes(String(a.role)) ? a.role : "user";
|
|
262
279
|
}
|
|
263
|
-
async function
|
|
280
|
+
async function h() {
|
|
264
281
|
let e = i.value?.uid;
|
|
265
282
|
if (!e) {
|
|
266
283
|
p(void 0);
|
|
@@ -269,34 +286,34 @@ function Ke() {
|
|
|
269
286
|
let t = await R(f(e));
|
|
270
287
|
p(t.exists() ? t.data() : void 0);
|
|
271
288
|
}
|
|
272
|
-
t.watchStarted || (t.watchStarted = !0,
|
|
289
|
+
t.watchStarted || (t.watchStarted = !0, I(i, async (e) => {
|
|
273
290
|
if (o.value = !0, !e) {
|
|
274
291
|
a.value = "user", p(void 0), o.value = !1;
|
|
275
292
|
return;
|
|
276
293
|
}
|
|
277
294
|
try {
|
|
278
|
-
a.value = await
|
|
295
|
+
a.value = await m(e);
|
|
279
296
|
} catch (e) {
|
|
280
297
|
console.error("[memi-board] ensureUserDoc failed", e), a.value = "user", p(void 0);
|
|
281
298
|
} finally {
|
|
282
299
|
o.value = !1;
|
|
283
300
|
}
|
|
284
301
|
}, { immediate: !0 }));
|
|
285
|
-
let v =
|
|
286
|
-
let e =
|
|
287
|
-
return e <= 0 ? !1 :
|
|
288
|
-
}), T =
|
|
289
|
-
let e =
|
|
302
|
+
let v = M(() => !!i.value), y = M(() => a.value === "admin"), b = M(() => a.value === "staff"), S = M(() => y.value || b.value), C = M(() => qe(s.value, c.value, Date.now(), d())), w = M(() => {
|
|
303
|
+
let e = l();
|
|
304
|
+
return e <= 0 ? !1 : Ye(s.value, c.value, Date.now(), e, d());
|
|
305
|
+
}), T = M(() => {
|
|
306
|
+
let e = l();
|
|
290
307
|
if (e <= 0) return null;
|
|
291
|
-
let t =
|
|
292
|
-
return t == null ? null :
|
|
293
|
-
}), E =
|
|
308
|
+
let t = Je(s.value, c.value, Date.now(), e, d());
|
|
309
|
+
return t == null ? null : Xe(t);
|
|
310
|
+
}), E = M(() => {
|
|
294
311
|
if (!w.value) return null;
|
|
295
312
|
let e = T.value;
|
|
296
313
|
return e ? `콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요. (${e})` : "콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요.";
|
|
297
314
|
});
|
|
298
315
|
async function D() {
|
|
299
|
-
let e = i.value?.uid, t =
|
|
316
|
+
let e = i.value?.uid, t = l();
|
|
300
317
|
if (!e || t <= 0) return {
|
|
301
318
|
effectiveCount: 0,
|
|
302
319
|
restricted: !1,
|
|
@@ -315,75 +332,79 @@ function Ke() {
|
|
|
315
332
|
updatedAt: G()
|
|
316
333
|
});
|
|
317
334
|
let a = (r.exists() ? Math.max(0, Math.floor(Number(r.data().moderationBlockCount) || 0)) : 0) + 1, o = Date.now();
|
|
318
|
-
|
|
319
|
-
let
|
|
335
|
+
s.value = a, c.value = o;
|
|
336
|
+
let u = qe(a, o, o, d()), p = Ye(a, o, o, t, d()), m = ` (경고 ${u}/${t})`;
|
|
320
337
|
if (p) {
|
|
321
|
-
let e =
|
|
322
|
-
m = n ? ` 경고 ${
|
|
338
|
+
let e = Je(a, o, o, t, d()), n = e == null ? null : Xe(e, o);
|
|
339
|
+
m = n ? ` 경고 ${u}회가 되어 글·댓글 작성이 제한됩니다 (${n}).` : ` 경고 ${u}회가 되어 글·댓글 작성이 제한됩니다.`;
|
|
323
340
|
}
|
|
324
341
|
return {
|
|
325
|
-
effectiveCount:
|
|
342
|
+
effectiveCount: u,
|
|
326
343
|
restricted: p,
|
|
327
344
|
messageSuffix: m
|
|
328
345
|
};
|
|
329
346
|
}
|
|
330
|
-
function
|
|
347
|
+
function O() {
|
|
331
348
|
if (!r) throw Error("[memi-board] Firebase Auth가 초기화되지 않았습니다.");
|
|
332
349
|
return r;
|
|
333
350
|
}
|
|
351
|
+
async function k() {
|
|
352
|
+
return he(O(), new de());
|
|
353
|
+
}
|
|
334
354
|
async function A() {
|
|
335
|
-
|
|
355
|
+
let e = new fe("apple.com");
|
|
356
|
+
return e.addScope("email"), e.addScope("name"), he(O(), e);
|
|
336
357
|
}
|
|
337
|
-
async function j() {
|
|
338
|
-
|
|
339
|
-
return e.addScope("email"), e.addScope("name"), fe(k(), e);
|
|
358
|
+
async function j(e, t) {
|
|
359
|
+
return me(O(), e, t);
|
|
340
360
|
}
|
|
341
|
-
async function
|
|
342
|
-
|
|
361
|
+
async function N(e, t, n) {
|
|
362
|
+
let r = await pe(O(), e, t);
|
|
363
|
+
return n && await _e(r.user, { displayName: n }), r;
|
|
343
364
|
}
|
|
344
|
-
async function
|
|
345
|
-
|
|
346
|
-
return n && await me(r.user, { displayName: n }), r;
|
|
365
|
+
async function ee() {
|
|
366
|
+
await ge(O());
|
|
347
367
|
}
|
|
348
|
-
|
|
349
|
-
|
|
368
|
+
function P(e) {
|
|
369
|
+
return Ve(i.value?.uid, y.value, b.value, e ? We(e) : void 0);
|
|
350
370
|
}
|
|
351
|
-
function
|
|
352
|
-
return v.value && (i.value?.uid === e.authorUid ||
|
|
371
|
+
function F(e) {
|
|
372
|
+
return v.value && (i.value?.uid === e.authorUid || P(e.boardId));
|
|
353
373
|
}
|
|
354
|
-
function
|
|
374
|
+
function te(e) {
|
|
355
375
|
return v.value && i.value?.uid === e.authorUid;
|
|
356
376
|
}
|
|
357
|
-
function
|
|
358
|
-
return v.value && (i.value?.uid === e.authorUid ||
|
|
377
|
+
function ne(e) {
|
|
378
|
+
return v.value && (i.value?.uid === e.authorUid || P(e.boardId));
|
|
359
379
|
}
|
|
360
380
|
return {
|
|
361
381
|
user: i,
|
|
362
382
|
isSignedIn: v,
|
|
363
383
|
isAdmin: y,
|
|
364
|
-
isStaff:
|
|
384
|
+
isStaff: b,
|
|
365
385
|
canManageContent: S,
|
|
386
|
+
canManageBoard: P,
|
|
366
387
|
rolePending: o,
|
|
367
388
|
moderationBlockCount: C,
|
|
368
389
|
isWriteRestricted: w,
|
|
369
390
|
restrictedUntilLabel: T,
|
|
370
391
|
restrictedMessage: E,
|
|
371
|
-
signInWithGoogle:
|
|
372
|
-
signInWithApple:
|
|
373
|
-
signInWithEmail:
|
|
374
|
-
signUpWithEmail:
|
|
375
|
-
signOut:
|
|
376
|
-
canEdit:
|
|
377
|
-
canDelete:
|
|
378
|
-
canEditComment:
|
|
379
|
-
canDeleteComment:
|
|
392
|
+
signInWithGoogle: k,
|
|
393
|
+
signInWithApple: A,
|
|
394
|
+
signInWithEmail: j,
|
|
395
|
+
signUpWithEmail: N,
|
|
396
|
+
signOut: ee,
|
|
397
|
+
canEdit: F,
|
|
398
|
+
canDelete: F,
|
|
399
|
+
canEditComment: te,
|
|
400
|
+
canDeleteComment: ne,
|
|
380
401
|
recordContentModerationBlock: D,
|
|
381
|
-
refreshBoardUser:
|
|
402
|
+
refreshBoardUser: h
|
|
382
403
|
};
|
|
383
404
|
}
|
|
384
405
|
//#endregion
|
|
385
406
|
//#region src/utils/extractEditorImageUrls.ts
|
|
386
|
-
function
|
|
407
|
+
function tt(e) {
|
|
387
408
|
if (!e) return [];
|
|
388
409
|
let t = /* @__PURE__ */ new Set();
|
|
389
410
|
for (let n of e.matchAll(/!\[[^\]]*]\((https?:\/\/[^)\s]+)\)/g)) n[1] && t.add(n[1].replaceAll("&", "&"));
|
|
@@ -392,7 +413,7 @@ function qe(e) {
|
|
|
392
413
|
}
|
|
393
414
|
//#endregion
|
|
394
415
|
//#region src/utils/storagePath.ts
|
|
395
|
-
function
|
|
416
|
+
function nt(e) {
|
|
396
417
|
try {
|
|
397
418
|
let t = new URL(e), n = t.hostname.toLowerCase();
|
|
398
419
|
if (!(n === "firebasestorage.googleapis.com" || n.endsWith(".firebasestorage.app"))) return null;
|
|
@@ -402,90 +423,104 @@ function Je(e) {
|
|
|
402
423
|
return null;
|
|
403
424
|
}
|
|
404
425
|
}
|
|
405
|
-
function
|
|
426
|
+
function rt(e) {
|
|
406
427
|
return e.match(/(?:^|\/)posts\/([^/]+)\//)?.[1] ?? null;
|
|
407
428
|
}
|
|
408
429
|
//#endregion
|
|
409
430
|
//#region src/utils/deletePostCascade.ts
|
|
410
|
-
async function
|
|
411
|
-
let t = await
|
|
412
|
-
await Promise.all([...t.items.map((e) =>
|
|
431
|
+
async function it(e) {
|
|
432
|
+
let t = await be(e);
|
|
433
|
+
await Promise.all([...t.items.map((e) => ve(e)), ...t.prefixes.map((e) => it(e))]);
|
|
413
434
|
}
|
|
414
|
-
async function
|
|
435
|
+
async function at(e, t) {
|
|
415
436
|
let n = [...new Set([...t].filter(Boolean))];
|
|
416
|
-
await Promise.all(n.map((t) =>
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
let
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
437
|
+
await Promise.all(n.map((t) => ve(xe(e, t)).catch(() => {})));
|
|
438
|
+
}
|
|
439
|
+
var ot = 450;
|
|
440
|
+
async function st(e, t) {
|
|
441
|
+
let n = await z(t);
|
|
442
|
+
for (let t = 0; t < n.docs.length; t += ot) {
|
|
443
|
+
let r = X(e);
|
|
444
|
+
n.docs.slice(t, t + ot).forEach((e) => r.delete(e.ref)), await r.commit();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async function ct(e, t, n) {
|
|
448
|
+
await st(e, W(A(e, t), Y("postId", "==", n)));
|
|
449
|
+
}
|
|
450
|
+
async function lt(e, t, n) {
|
|
451
|
+
await st(e, W(o(e, t), Y("postId", "==", n)));
|
|
452
|
+
}
|
|
453
|
+
async function ut(e, t, n) {
|
|
454
|
+
await st(e, W(A(e, t), Y("commentId", "==", n)));
|
|
455
|
+
}
|
|
456
|
+
async function dt(e, n, r, i) {
|
|
457
|
+
let a = t(e, r, i), o = s(e, r, i), [c, l] = await Promise.all([R(a), R(o)]);
|
|
458
|
+
if (!c.exists()) return;
|
|
459
|
+
let u = c.data(), d = l.exists() ? String(l.data().content ?? "") : "";
|
|
460
|
+
await st(e, W(O(e, r), Y("postId", "==", i))), await ct(e, r, i), await lt(e, r, i);
|
|
461
|
+
let f = /* @__PURE__ */ new Set(), p = /* @__PURE__ */ new Set();
|
|
462
|
+
for (let e of u.attachments ?? []) {
|
|
428
463
|
if (!e?.path) continue;
|
|
429
|
-
|
|
430
|
-
let t =
|
|
431
|
-
t && t !==
|
|
464
|
+
f.add(e.path);
|
|
465
|
+
let t = rt(e.path);
|
|
466
|
+
t && t !== i && p.add(t);
|
|
432
467
|
}
|
|
433
|
-
for (let e of
|
|
434
|
-
let t =
|
|
468
|
+
for (let e of tt(d)) {
|
|
469
|
+
let t = nt(e);
|
|
435
470
|
if (!t) continue;
|
|
436
|
-
|
|
471
|
+
f.add(t);
|
|
437
472
|
let n = t.replace(/\/images\/([^/]+)$/, "/images/thumbnails/$1").replace(/\.[^.]+$/, ".jpg");
|
|
438
|
-
n !== t && n.includes("/images/thumbnails/") &&
|
|
473
|
+
n !== t && n.includes("/images/thumbnails/") && f.add(n);
|
|
439
474
|
let r = t.match(/^(.*\/images\/)([^/]+)\.[^.]+$/);
|
|
440
|
-
r && !t.includes("/thumbnails/") &&
|
|
441
|
-
let
|
|
442
|
-
|
|
475
|
+
r && !t.includes("/thumbnails/") && f.add(`${r[1]}thumbnails/${r[2]}.jpg`);
|
|
476
|
+
let a = rt(t);
|
|
477
|
+
a && a !== i && p.add(a);
|
|
443
478
|
}
|
|
444
|
-
await
|
|
445
|
-
for (let
|
|
446
|
-
await
|
|
479
|
+
await te(o), await it(xe(n, k(r, i)));
|
|
480
|
+
for (let e of p) await it(xe(n, k(r, e)));
|
|
481
|
+
await at(n, f), await te(a);
|
|
447
482
|
}
|
|
448
483
|
//#endregion
|
|
449
484
|
//#region src/utils/postBody.ts
|
|
450
|
-
function
|
|
485
|
+
function ft(e) {
|
|
451
486
|
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(/ /gi, " ").replace(/ /g, " ").replace(/&[a-z]+;/gi, " ").replace(/&#\d+;/g, " ").replace(/[\u200B-\u200D\uFEFF\u00AD]/g, "").replace(/\s+/g, " ").trim();
|
|
452
487
|
}
|
|
453
|
-
function
|
|
454
|
-
let n =
|
|
488
|
+
function pt(e, t = 40) {
|
|
489
|
+
let n = ft(e);
|
|
455
490
|
if (!n) return "";
|
|
456
491
|
if (n.length <= t) return n;
|
|
457
492
|
let r = n.slice(0, t), i = r.lastIndexOf(" ");
|
|
458
493
|
return `${(i >= Math.floor(t * .6) ? r.slice(0, i) : r).trimEnd()}…`;
|
|
459
494
|
}
|
|
460
|
-
function
|
|
461
|
-
return
|
|
495
|
+
function mt(e) {
|
|
496
|
+
return ft(e).length > 0;
|
|
462
497
|
}
|
|
463
|
-
function
|
|
498
|
+
function ht(e, t) {
|
|
464
499
|
return /<img\b/i.test(String(e || "")) ? !0 : (t || []).some((e) => !!e?.type?.startsWith("image/"));
|
|
465
500
|
}
|
|
466
|
-
function
|
|
467
|
-
return !
|
|
501
|
+
function gt(e) {
|
|
502
|
+
return !mt(e);
|
|
468
503
|
}
|
|
469
504
|
//#endregion
|
|
470
505
|
//#region src/utils/postPreview.ts
|
|
471
|
-
function
|
|
506
|
+
function _t(e) {
|
|
472
507
|
return e.replace(/&/g, "&").replace(/"/g, "\"").replace(/'/g, "'");
|
|
473
508
|
}
|
|
474
|
-
function
|
|
509
|
+
function vt(e) {
|
|
475
510
|
if (e) try {
|
|
476
|
-
let t = new URL(
|
|
511
|
+
let t = new URL(_t(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
|
|
477
512
|
if (n === "youtu.be") return t.pathname.split("/").filter(Boolean)[0];
|
|
478
513
|
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;
|
|
479
514
|
} catch {
|
|
480
515
|
return;
|
|
481
516
|
}
|
|
482
517
|
}
|
|
483
|
-
function
|
|
484
|
-
let t =
|
|
518
|
+
function yt(e) {
|
|
519
|
+
let t = vt(e.videoUrl);
|
|
485
520
|
return t ? `https://i.ytimg.com/vi/${t}/hqdefault.jpg` : e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
|
|
486
521
|
}
|
|
487
|
-
function
|
|
488
|
-
let n = e.match(/<img\b[^>]*\bsrc=["']([^"']+)["']/i), r = e.match(/<iframe\b[^>]*\bsrc=["']([^"']+)["']/i), i = t.find((e) => e.type.startsWith("image/"))?.url, a = t.find((e) => e.type.startsWith("video/"))?.url, o = r ?
|
|
522
|
+
function bt(e, t = []) {
|
|
523
|
+
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 ? _t(r[1]) : a, s = o ? vt(o) : void 0, c = n ? _t(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
|
|
489
524
|
return {
|
|
490
525
|
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(/ /g, " ").replace(/&/g, "&").replace(/[^\S\n]+/g, " ").replace(/ *\n */g, "\n").replace(/\n{2,}/g, "\n").trim().slice(0, 160),
|
|
491
526
|
...c ? { previewImage: c } : {},
|
|
@@ -494,7 +529,7 @@ function st(e, t = []) {
|
|
|
494
529
|
}
|
|
495
530
|
//#endregion
|
|
496
531
|
//#region src/composables/useMemiBoardSettings.ts
|
|
497
|
-
var
|
|
532
|
+
var xt = [
|
|
498
533
|
{
|
|
499
534
|
id: "free",
|
|
500
535
|
label: "자유",
|
|
@@ -522,59 +557,64 @@ var ct = [
|
|
|
522
557
|
visibility: "public",
|
|
523
558
|
order: 2
|
|
524
559
|
}
|
|
525
|
-
],
|
|
526
|
-
function
|
|
560
|
+
], St = xt;
|
|
561
|
+
function Ct(e) {
|
|
527
562
|
return e === "hidden" ? "hidden" : "public";
|
|
528
563
|
}
|
|
529
|
-
function
|
|
564
|
+
function wt(e, t, n) {
|
|
530
565
|
return {
|
|
531
566
|
id: e,
|
|
532
567
|
label: typeof t.label == "string" && t.label.trim() ? t.label.trim() : e,
|
|
533
568
|
order: typeof t.order == "number" ? t.order : n,
|
|
534
569
|
description: (typeof t.description == "string" ? t.description : "") || "",
|
|
535
|
-
visibility:
|
|
570
|
+
visibility: Ct(t.visibility),
|
|
536
571
|
listView: t.listView ?? "default",
|
|
537
572
|
writeRole: t.writeRole ?? "user",
|
|
538
573
|
commentWriteRole: t.commentWriteRole ?? "user",
|
|
539
574
|
allowedStaffUids: Array.isArray(t.allowedStaffUids) ? t.allowedStaffUids : []
|
|
540
575
|
};
|
|
541
576
|
}
|
|
542
|
-
function
|
|
543
|
-
let
|
|
544
|
-
function
|
|
545
|
-
if (e) return
|
|
546
|
-
}
|
|
547
|
-
let g = h;
|
|
548
|
-
function _(e) {
|
|
549
|
-
if (e) return l.value.find((t) => t.id === e)?.description?.trim() || void 0;
|
|
577
|
+
function Tt() {
|
|
578
|
+
let t = () => _(), n = Q(), r = le(), { isSignedIn: i, isAdmin: a, isStaff: o, user: s } = et(), l = M(() => W(e(n, t()), U("order", "asc"))), { data: u, pending: d } = se(l, { ssrKey: w(t(), "boards") }), f = M(() => u.value.map((e, t) => wt(e.id, e, t))), p = f, m = M(() => f.value.filter((e) => e.visibility !== "hidden")), h = m;
|
|
579
|
+
function g(e) {
|
|
580
|
+
if (e) return f.value.find((t) => t.id === e)?.label ?? e;
|
|
550
581
|
}
|
|
551
|
-
let y =
|
|
582
|
+
let y = g;
|
|
552
583
|
function b(e) {
|
|
553
|
-
|
|
584
|
+
if (e) return f.value.find((t) => t.id === e)?.description?.trim() || void 0;
|
|
554
585
|
}
|
|
555
586
|
let x = b;
|
|
556
|
-
function
|
|
557
|
-
return
|
|
587
|
+
function C(e) {
|
|
588
|
+
return e ? f.value.find((t) => t.id === e)?.visibility ?? "public" : "public";
|
|
589
|
+
}
|
|
590
|
+
let T = C;
|
|
591
|
+
function E(e) {
|
|
592
|
+
return C(e) === "hidden";
|
|
593
|
+
}
|
|
594
|
+
let D = E;
|
|
595
|
+
function O(e) {
|
|
596
|
+
if (e) return f.value.find((t) => t.id === e);
|
|
558
597
|
}
|
|
559
|
-
|
|
560
|
-
function
|
|
561
|
-
|
|
598
|
+
Ue((e) => O(e));
|
|
599
|
+
function k(e) {
|
|
600
|
+
return Ve(s.value?.uid, a.value, o.value, e ? O(e) : void 0);
|
|
562
601
|
}
|
|
563
|
-
|
|
602
|
+
let A = M(() => f.value.filter((e) => k(e.id)));
|
|
603
|
+
async function j(e, r) {
|
|
564
604
|
let i;
|
|
565
605
|
for (;;) {
|
|
566
|
-
let a = await z(W(
|
|
606
|
+
let a = await z(W(c(n, t()), Y("boardId", "==", e), U("__name__"), ...i ? [q(i)] : [], V(400)));
|
|
567
607
|
if (a.empty) break;
|
|
568
|
-
let o = X(
|
|
569
|
-
for (let e of a.docs) o.update(e.ref, { listed:
|
|
608
|
+
let o = X(n);
|
|
609
|
+
for (let e of a.docs) o.update(e.ref, { listed: r });
|
|
570
610
|
if (await o.commit(), i = a.docs[a.docs.length - 1], a.docs.length < 400) break;
|
|
571
611
|
}
|
|
572
612
|
}
|
|
573
|
-
function
|
|
613
|
+
function N(e, t) {
|
|
574
614
|
return {
|
|
575
615
|
label: e.label.trim(),
|
|
576
616
|
description: (e.description ?? "").trim(),
|
|
577
|
-
visibility:
|
|
617
|
+
visibility: Ct(e.visibility),
|
|
578
618
|
listView: e.listView ?? "default",
|
|
579
619
|
writeRole: e.writeRole ?? "user",
|
|
580
620
|
commentWriteRole: e.commentWriteRole ?? "user",
|
|
@@ -583,120 +623,122 @@ function ft() {
|
|
|
583
623
|
updatedAt: G()
|
|
584
624
|
};
|
|
585
625
|
}
|
|
586
|
-
async function
|
|
587
|
-
if (!
|
|
588
|
-
let
|
|
589
|
-
if (!
|
|
590
|
-
let
|
|
591
|
-
...
|
|
592
|
-
label:
|
|
593
|
-
visibility:
|
|
594
|
-
},
|
|
595
|
-
await K(
|
|
596
|
-
}
|
|
597
|
-
let
|
|
598
|
-
async function
|
|
599
|
-
if (!
|
|
600
|
-
let
|
|
601
|
-
|
|
602
|
-
let
|
|
603
|
-
if (!
|
|
604
|
-
let
|
|
605
|
-
...
|
|
606
|
-
label:
|
|
607
|
-
visibility:
|
|
608
|
-
},
|
|
609
|
-
|
|
610
|
-
id:
|
|
611
|
-
listed:
|
|
626
|
+
async function ee(e, r = e.order ?? 0) {
|
|
627
|
+
if (!i.value) throw Error("로그인이 필요합니다.");
|
|
628
|
+
let a = e.id.trim(), o = e.label.trim();
|
|
629
|
+
if (!a || !o) throw Error("보드 ID와 이름을 입력해 주세요.");
|
|
630
|
+
let s = Ct(e.visibility), c = f.value.find((e) => e.id === a)?.visibility ?? "public", l = N({
|
|
631
|
+
...e,
|
|
632
|
+
label: o,
|
|
633
|
+
visibility: s
|
|
634
|
+
}, r);
|
|
635
|
+
await K(v(n, t(), a), l, { merge: !0 }), (c !== s || s === "public") && await j(a, s !== "hidden");
|
|
636
|
+
}
|
|
637
|
+
let P = ee;
|
|
638
|
+
async function F(e) {
|
|
639
|
+
if (!i.value) throw Error("로그인이 필요합니다.");
|
|
640
|
+
let r = new Map(f.value.map((e) => [e.id, e.visibility ?? "public"])), a = X(n), o = [];
|
|
641
|
+
e.forEach((e, i) => {
|
|
642
|
+
let s = e.id.trim(), c = e.label.trim();
|
|
643
|
+
if (!s || !c) throw Error("보드 ID와 이름을 입력해 주세요.");
|
|
644
|
+
let l = Ct(e.visibility), u = N({
|
|
645
|
+
...e,
|
|
646
|
+
label: c,
|
|
647
|
+
visibility: l
|
|
648
|
+
}, i);
|
|
649
|
+
a.set(v(n, t(), s), u, { merge: !0 }), (r.get(s) ?? "public") !== l && o.push({
|
|
650
|
+
id: s,
|
|
651
|
+
listed: l !== "hidden"
|
|
612
652
|
});
|
|
613
|
-
}), await
|
|
614
|
-
for (let e of
|
|
615
|
-
}
|
|
616
|
-
let
|
|
617
|
-
async function
|
|
618
|
-
if (!
|
|
619
|
-
let
|
|
620
|
-
if (!
|
|
653
|
+
}), await a.commit();
|
|
654
|
+
for (let e of o) await j(e.id, e.listed);
|
|
655
|
+
}
|
|
656
|
+
let I = F;
|
|
657
|
+
async function ne(e) {
|
|
658
|
+
if (!i.value) throw Error("로그인이 필요합니다.");
|
|
659
|
+
let a = e.trim();
|
|
660
|
+
if (!a) throw Error("삭제할 게시판 ID가 필요합니다.");
|
|
621
661
|
for (;;) {
|
|
622
|
-
let
|
|
623
|
-
if (
|
|
624
|
-
for (let
|
|
662
|
+
let e = await z(W(c(n, t()), Y("boardId", "==", a), V(25)));
|
|
663
|
+
if (e.empty) break;
|
|
664
|
+
for (let i of e.docs) await dt(n, ye(r), t(), i.id);
|
|
625
665
|
}
|
|
626
|
-
await
|
|
666
|
+
await te(v(n, t(), e));
|
|
627
667
|
}
|
|
628
|
-
let
|
|
629
|
-
async function
|
|
668
|
+
let re = ne;
|
|
669
|
+
async function L() {
|
|
630
670
|
return Promise.resolve();
|
|
631
671
|
}
|
|
632
|
-
async function
|
|
672
|
+
async function ie(e) {
|
|
633
673
|
let t = e.trim();
|
|
634
674
|
if (!t) throw Error("보드 이름을 입력해 주세요.");
|
|
635
|
-
if (!
|
|
636
|
-
let n =
|
|
675
|
+
if (!i.value) throw Error("로그인이 필요합니다.");
|
|
676
|
+
let n = f.value.find((e) => e.label === t);
|
|
637
677
|
if (n) return n.id;
|
|
638
|
-
let r =
|
|
639
|
-
for (; s.has(
|
|
640
|
-
return await
|
|
641
|
-
id:
|
|
678
|
+
let r = S(t) || `board-${Date.now()}`, a = r, o = 2, s = new Set(f.value.map((e) => e.id));
|
|
679
|
+
for (; s.has(a);) a = `${r}-${o++}`;
|
|
680
|
+
return await ee({
|
|
681
|
+
id: a,
|
|
642
682
|
label: t,
|
|
643
683
|
visibility: "public",
|
|
644
684
|
listView: "default",
|
|
645
685
|
writeRole: "user",
|
|
646
686
|
commentWriteRole: "user",
|
|
647
|
-
order:
|
|
648
|
-
}),
|
|
687
|
+
order: f.value.length
|
|
688
|
+
}), a;
|
|
649
689
|
}
|
|
650
690
|
return {
|
|
651
|
-
boards:
|
|
652
|
-
publicBoards:
|
|
653
|
-
boardLabel:
|
|
654
|
-
boardDescription:
|
|
655
|
-
boardVisibility:
|
|
656
|
-
isBoardHidden:
|
|
657
|
-
getBoard:
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
691
|
+
boards: f,
|
|
692
|
+
publicBoards: m,
|
|
693
|
+
boardLabel: g,
|
|
694
|
+
boardDescription: b,
|
|
695
|
+
boardVisibility: C,
|
|
696
|
+
isBoardHidden: E,
|
|
697
|
+
getBoard: O,
|
|
698
|
+
canManageBoard: k,
|
|
699
|
+
managedBoards: A,
|
|
700
|
+
categories: p,
|
|
701
|
+
publicCategories: h,
|
|
702
|
+
categoryLabel: y,
|
|
703
|
+
categoryDescription: x,
|
|
704
|
+
categoryVisibility: T,
|
|
705
|
+
isCategoryHidden: D,
|
|
706
|
+
settingsPending: d,
|
|
707
|
+
ensureSettings: L,
|
|
708
|
+
addBoard: ie,
|
|
709
|
+
addCategory: ie,
|
|
710
|
+
saveBoard: ee,
|
|
711
|
+
saveCategory: P,
|
|
712
|
+
saveBoards: F,
|
|
713
|
+
saveCategories: I,
|
|
714
|
+
deleteBoard: ne,
|
|
715
|
+
deleteCategory: re,
|
|
716
|
+
DEFAULT_BOARDS: xt,
|
|
717
|
+
DEFAULT_CATEGORIES: St
|
|
676
718
|
};
|
|
677
719
|
}
|
|
678
720
|
//#endregion
|
|
679
721
|
//#region src/composables/useMemiBoardPosts.ts
|
|
680
|
-
function
|
|
722
|
+
function Et(e) {
|
|
681
723
|
if (!e || typeof e != "object") return !1;
|
|
682
724
|
let t = "code" in e ? String(e.code) : "";
|
|
683
725
|
return t === "permission-denied" || t === "firestore/permission-denied";
|
|
684
726
|
}
|
|
685
|
-
function
|
|
686
|
-
let t =
|
|
727
|
+
function Dt(e) {
|
|
728
|
+
let t = F(e)?.trim();
|
|
687
729
|
if (!t) throw Error("[memi-board] boardId 가 필요합니다.");
|
|
688
730
|
return t;
|
|
689
731
|
}
|
|
690
|
-
function
|
|
691
|
-
let
|
|
692
|
-
function
|
|
693
|
-
return !
|
|
732
|
+
function Ot(e) {
|
|
733
|
+
let n = Q(), r = le(), { isBoardHidden: i, getBoard: a } = Tt(), o = () => _(), l = () => Dt(e), u = () => c(n, o()), d = (e) => t(n, o(), e), f = (e) => s(n, o(), e);
|
|
734
|
+
function p() {
|
|
735
|
+
return !i(l());
|
|
694
736
|
}
|
|
695
|
-
function
|
|
696
|
-
return a(
|
|
737
|
+
function m() {
|
|
738
|
+
return a(l())?.listView === "image";
|
|
697
739
|
}
|
|
698
|
-
function
|
|
699
|
-
let t =
|
|
740
|
+
function h(e) {
|
|
741
|
+
let t = m(), n = mt(e.content), r = ht(e.content, e.attachments);
|
|
700
742
|
if (t) {
|
|
701
743
|
if (!r) throw Error("사진을 올려 주세요.");
|
|
702
744
|
return;
|
|
@@ -704,13 +746,13 @@ function ht(e) {
|
|
|
704
746
|
if (!n && !r) throw Error("본문에 글자를 입력하거나 이미지를 첨부해 주세요.");
|
|
705
747
|
if (!e.title?.trim()) throw Error("제목을 입력해 주세요.");
|
|
706
748
|
}
|
|
707
|
-
function
|
|
708
|
-
return
|
|
749
|
+
function g() {
|
|
750
|
+
return re(u()).id;
|
|
709
751
|
}
|
|
710
|
-
async function
|
|
711
|
-
let t = e.pageSize ?? 20, n = [Y("boardId", "==",
|
|
752
|
+
async function v(e = {}) {
|
|
753
|
+
let t = e.pageSize ?? 20, n = [Y("boardId", "==", l()), Y("isPublished", "==", !0)];
|
|
712
754
|
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));
|
|
713
|
-
let r = await z(W(
|
|
755
|
+
let r = await z(W(u(), ...n)), i = r.docs.slice(0, t);
|
|
714
756
|
return {
|
|
715
757
|
posts: i.map((e) => ({
|
|
716
758
|
id: e.id,
|
|
@@ -720,49 +762,49 @@ function ht(e) {
|
|
|
720
762
|
hasMore: r.docs.length > t
|
|
721
763
|
};
|
|
722
764
|
}
|
|
723
|
-
async function
|
|
724
|
-
let [t, n] = await Promise.all([R(
|
|
765
|
+
async function y(e) {
|
|
766
|
+
let [t, n] = await Promise.all([R(d(e)), R(f(e))]);
|
|
725
767
|
return t.exists() ? {
|
|
726
768
|
id: t.id,
|
|
727
769
|
...t.data(),
|
|
728
770
|
content: n.exists() ? n.data().content ?? "" : ""
|
|
729
771
|
} : null;
|
|
730
772
|
}
|
|
731
|
-
async function
|
|
773
|
+
async function b(e) {
|
|
732
774
|
try {
|
|
733
|
-
let t = (await z(W(
|
|
775
|
+
let t = (await z(W(u(), Y("boardId", "==", l()), Y("slug", "==", e.trim()), V(1)))).docs[0];
|
|
734
776
|
if (!t) return { status: "not-found" };
|
|
735
777
|
try {
|
|
736
|
-
let e = await
|
|
778
|
+
let e = await y(t.id);
|
|
737
779
|
return e ? {
|
|
738
780
|
status: "ok",
|
|
739
781
|
post: e
|
|
740
782
|
} : { status: "not-found" };
|
|
741
783
|
} catch (e) {
|
|
742
|
-
return
|
|
784
|
+
return Et(e) ? { status: "permission-denied" } : {
|
|
743
785
|
status: "error",
|
|
744
786
|
message: e instanceof Error ? e.message : String(e)
|
|
745
787
|
};
|
|
746
788
|
}
|
|
747
789
|
} catch (e) {
|
|
748
|
-
return
|
|
790
|
+
return Et(e) ? { status: "permission-denied" } : {
|
|
749
791
|
status: "error",
|
|
750
792
|
message: e instanceof Error ? e.message : String(e)
|
|
751
793
|
};
|
|
752
794
|
}
|
|
753
795
|
}
|
|
754
|
-
async function
|
|
796
|
+
async function x(e) {
|
|
755
797
|
if (!e.createdAt) return {
|
|
756
798
|
previous: null,
|
|
757
799
|
next: null
|
|
758
800
|
};
|
|
759
801
|
try {
|
|
760
802
|
let t = [
|
|
761
|
-
Y("boardId", "==",
|
|
803
|
+
Y("boardId", "==", l()),
|
|
762
804
|
Y("isPublished", "==", !0),
|
|
763
805
|
...e.listed === !1 ? [] : [Y("listed", "==", !0)],
|
|
764
806
|
U("createdAt", "desc")
|
|
765
|
-
], [n, r] = await Promise.all([z(W(
|
|
807
|
+
], [n, r] = await Promise.all([z(W(u(), ...t, q(e.createdAt), V(1))), z(W(u(), ...t, ie(e.createdAt), ae(1)))]), i = (e) => {
|
|
766
808
|
let t = e.docs[0];
|
|
767
809
|
return t ? {
|
|
768
810
|
id: t.id,
|
|
@@ -780,20 +822,20 @@ function ht(e) {
|
|
|
780
822
|
};
|
|
781
823
|
}
|
|
782
824
|
}
|
|
783
|
-
async function
|
|
784
|
-
|
|
785
|
-
let t = e.postId ||
|
|
786
|
-
if (n) r = t, i =
|
|
825
|
+
async function C(e) {
|
|
826
|
+
h(e);
|
|
827
|
+
let t = e.postId || g(), n = m(), r, i;
|
|
828
|
+
if (n) r = t, i = pt(e.content);
|
|
787
829
|
else {
|
|
788
830
|
i = e.title.trim();
|
|
789
|
-
let t =
|
|
831
|
+
let t = S(i) || "post";
|
|
790
832
|
r = t;
|
|
791
833
|
let n = 1;
|
|
792
|
-
for (; !(await z(W(
|
|
834
|
+
for (; !(await z(W(u(), Y("boardId", "==", l()), Y("slug", "==", r), V(1)))).empty;) n++, r = `${t}-${n}`;
|
|
793
835
|
}
|
|
794
|
-
let a =
|
|
795
|
-
return await K(
|
|
796
|
-
boardId:
|
|
836
|
+
let a = bt(e.content, e.attachments), o = p();
|
|
837
|
+
return await K(d(t), {
|
|
838
|
+
boardId: l(),
|
|
797
839
|
slug: r,
|
|
798
840
|
title: i,
|
|
799
841
|
...a,
|
|
@@ -811,81 +853,81 @@ function ht(e) {
|
|
|
811
853
|
moderationModel: e.moderationModel ?? null,
|
|
812
854
|
createdAt: G(),
|
|
813
855
|
updatedAt: G()
|
|
814
|
-
}), await K(
|
|
856
|
+
}), await K(f(t), { content: e.content }), r;
|
|
815
857
|
}
|
|
816
|
-
async function
|
|
817
|
-
|
|
818
|
-
let n =
|
|
819
|
-
await Promise.all([J(
|
|
858
|
+
async function w(e, t) {
|
|
859
|
+
h(t);
|
|
860
|
+
let n = m() ? pt(t.content) : t.title.trim(), r = bt(t.content, t.attachments), i = p();
|
|
861
|
+
await Promise.all([J(d(e), {
|
|
820
862
|
title: n,
|
|
821
863
|
...r,
|
|
822
|
-
previewImage: r.previewImage ? r.previewImage :
|
|
823
|
-
videoUrl: r.videoUrl ? r.videoUrl :
|
|
864
|
+
previewImage: r.previewImage ? r.previewImage : ne(),
|
|
865
|
+
videoUrl: r.videoUrl ? r.videoUrl : ne(),
|
|
824
866
|
tags: t.tags ?? [],
|
|
825
867
|
listed: i,
|
|
826
868
|
attachments: t.attachments ?? [],
|
|
827
869
|
updatedAt: G()
|
|
828
|
-
}), K(
|
|
870
|
+
}), K(f(e), { content: t.content }, { merge: !0 })]);
|
|
829
871
|
}
|
|
830
|
-
async function
|
|
831
|
-
await J(
|
|
872
|
+
async function T(e) {
|
|
873
|
+
await J(d(e), {
|
|
832
874
|
isPublished: !0,
|
|
833
875
|
publishedAt: G(),
|
|
834
876
|
updatedAt: G()
|
|
835
877
|
});
|
|
836
878
|
}
|
|
837
|
-
async function
|
|
838
|
-
await
|
|
879
|
+
async function E(e) {
|
|
880
|
+
await dt(n, ye(r), o(), e);
|
|
839
881
|
}
|
|
840
882
|
return {
|
|
841
|
-
createPostId:
|
|
842
|
-
getPosts:
|
|
843
|
-
getPost:
|
|
844
|
-
getPostBySlug:
|
|
845
|
-
getAdjacentPosts:
|
|
846
|
-
createPost:
|
|
847
|
-
updatePost:
|
|
848
|
-
publishPost:
|
|
849
|
-
deletePost:
|
|
883
|
+
createPostId: g,
|
|
884
|
+
getPosts: v,
|
|
885
|
+
getPost: y,
|
|
886
|
+
getPostBySlug: b,
|
|
887
|
+
getAdjacentPosts: x,
|
|
888
|
+
createPost: C,
|
|
889
|
+
updatePost: w,
|
|
890
|
+
publishPost: T,
|
|
891
|
+
deletePost: E
|
|
850
892
|
};
|
|
851
893
|
}
|
|
852
|
-
var
|
|
853
|
-
function
|
|
854
|
-
let n =
|
|
855
|
-
function
|
|
894
|
+
var kt = 10;
|
|
895
|
+
function At(e, t = {}) {
|
|
896
|
+
let n = Q(), { isAdmin: r, user: i, canManageBoard: a } = et(), o = () => _(), s = t.pageSize ?? kt, l = M(() => i.value?.uid ?? ""), u = M(() => Dt(e)), d = () => c(n, o()), f = M(() => t.publicOnly === void 0 ? !(r.value || a(u.value)) : typeof t.publicOnly == "boolean" ? t.publicOnly : t.publicOnly.value), p = P([]), m = P([]), h = P([]), g = P(!0), v = P(!0), y = P(!1), b = P(""), x = null, S = null, C, w;
|
|
897
|
+
function T(e) {
|
|
856
898
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
857
899
|
}
|
|
858
|
-
let E =
|
|
900
|
+
let E = M(() => {
|
|
859
901
|
let e = /* @__PURE__ */ new Map();
|
|
860
902
|
for (let t of [
|
|
903
|
+
...m.value,
|
|
861
904
|
...p.value,
|
|
862
|
-
...f.value,
|
|
863
905
|
...h.value
|
|
864
906
|
]) t.id && e.set(t.id, t);
|
|
865
|
-
return [...e.values()].sort((e, t) =>
|
|
907
|
+
return [...e.values()].sort((e, t) => T(t) - T(e) || String(t.id).localeCompare(String(e.id)));
|
|
866
908
|
});
|
|
867
909
|
function D() {
|
|
868
|
-
let e = [Y("boardId", "==",
|
|
869
|
-
return
|
|
910
|
+
let e = [Y("boardId", "==", u.value), Y("isPublished", "==", !0)];
|
|
911
|
+
return f.value && e.push(Y("listed", "==", !0)), e;
|
|
870
912
|
}
|
|
871
|
-
function
|
|
872
|
-
|
|
873
|
-
|
|
913
|
+
function O() {
|
|
914
|
+
C?.(), C = H(W(d(), ...D(), U("createdAt", "desc"), U(L(), "desc"), V(s)), (e) => {
|
|
915
|
+
p.value = e.docs.map((e) => ({
|
|
874
916
|
id: e.id,
|
|
875
917
|
...e.data()
|
|
876
|
-
})),
|
|
918
|
+
})), x = e.docs.at(-1) ?? null, S || (v.value = e.docs.length >= s), g.value = !1;
|
|
877
919
|
}, (e) => {
|
|
878
|
-
console.error("[memi-board:postList] onSnapshot failed", e),
|
|
920
|
+
console.error("[memi-board:postList] onSnapshot failed", e), b.value = e.message || String(e), g.value = !1;
|
|
879
921
|
});
|
|
880
922
|
}
|
|
881
|
-
function
|
|
882
|
-
|
|
883
|
-
let e =
|
|
923
|
+
function k() {
|
|
924
|
+
w?.();
|
|
925
|
+
let e = l.value;
|
|
884
926
|
if (!e) {
|
|
885
927
|
h.value = [];
|
|
886
928
|
return;
|
|
887
929
|
}
|
|
888
|
-
|
|
930
|
+
w = H(W(d(), Y("boardId", "==", u.value), Y("authorUid", "==", e), Y("isPublished", "==", !1), U("createdAt", "desc"), V(20)), (e) => {
|
|
889
931
|
h.value = e.docs.map((e) => ({
|
|
890
932
|
id: e.id,
|
|
891
933
|
...e.data()
|
|
@@ -894,119 +936,183 @@ function _t(e, t = {}) {
|
|
|
894
936
|
console.error("[memi-board:postList] own draft onSnapshot failed", e);
|
|
895
937
|
});
|
|
896
938
|
}
|
|
897
|
-
async function
|
|
898
|
-
if (!(
|
|
899
|
-
|
|
939
|
+
async function A() {
|
|
940
|
+
if (!(y.value || !v.value || g.value)) {
|
|
941
|
+
y.value = !0, b.value = "";
|
|
900
942
|
try {
|
|
901
|
-
let e =
|
|
943
|
+
let e = S ?? x, t = [
|
|
902
944
|
...D(),
|
|
903
945
|
U("createdAt", "desc"),
|
|
904
946
|
U(L(), "desc"),
|
|
905
947
|
...e ? [q(e)] : [],
|
|
906
948
|
V(s + 1)
|
|
907
|
-
], n = await z(W(
|
|
949
|
+
], n = await z(W(d(), ...t)), r = n.docs.slice(0, s), i = r.map((e) => ({
|
|
908
950
|
id: e.id,
|
|
909
951
|
...e.data()
|
|
910
|
-
})), a = new Set([...
|
|
911
|
-
|
|
952
|
+
})), a = new Set([...m.value, ...p.value].map((e) => e.id));
|
|
953
|
+
m.value.push(...i.filter((e) => !a.has(e.id))), S = r.at(-1) ?? S, v.value = n.docs.length > s;
|
|
912
954
|
} catch (e) {
|
|
913
|
-
|
|
955
|
+
b.value = e.message || String(e), console.error("[memi-board:postList] loadMore failed", e);
|
|
914
956
|
} finally {
|
|
915
|
-
|
|
957
|
+
y.value = !1;
|
|
916
958
|
}
|
|
917
959
|
}
|
|
918
960
|
}
|
|
919
|
-
function
|
|
920
|
-
|
|
961
|
+
function j() {
|
|
962
|
+
C?.(), p.value = [], m.value = [], x = null, S = null, v.value = !0, b.value = "", g.value = !0, O(), k();
|
|
921
963
|
}
|
|
922
|
-
return
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
],
|
|
927
|
-
|
|
964
|
+
return I([
|
|
965
|
+
u,
|
|
966
|
+
f,
|
|
967
|
+
l
|
|
968
|
+
], j, { immediate: !0 }), N(() => {
|
|
969
|
+
C?.(), w?.();
|
|
928
970
|
}), {
|
|
929
971
|
posts: E,
|
|
930
972
|
postsPending: g,
|
|
931
|
-
hasMore:
|
|
932
|
-
loadingMore:
|
|
933
|
-
loadError:
|
|
934
|
-
loadMore:
|
|
973
|
+
hasMore: v,
|
|
974
|
+
loadingMore: y,
|
|
975
|
+
loadError: b,
|
|
976
|
+
loadMore: A
|
|
935
977
|
};
|
|
936
978
|
}
|
|
937
|
-
function
|
|
938
|
-
let
|
|
979
|
+
function jt(e, n) {
|
|
980
|
+
let r = Q(), i = () => _(), a = M(() => Dt(e)), o = M(() => typeof n == "string" ? n : n.value), c = M(() => t(r, i(), o.value)), l = M(() => s(r, i(), o.value)), { data: u, pending: d } = ce(c), { data: f, pending: p } = ce(l);
|
|
939
981
|
return {
|
|
940
|
-
post:
|
|
941
|
-
...
|
|
982
|
+
post: M(() => !u.value || u.value.boardId !== a.value ? null : {
|
|
983
|
+
...u.value,
|
|
942
984
|
id: o.value,
|
|
943
|
-
content:
|
|
985
|
+
content: f.value?.content ?? ""
|
|
944
986
|
}),
|
|
945
|
-
pending:
|
|
987
|
+
pending: M(() => d.value || p.value)
|
|
946
988
|
};
|
|
947
989
|
}
|
|
948
990
|
//#endregion
|
|
949
991
|
//#region src/composables/useMemiBoardUserPosts.ts
|
|
950
|
-
var
|
|
951
|
-
function
|
|
952
|
-
let n =
|
|
953
|
-
function
|
|
992
|
+
var Mt = 10;
|
|
993
|
+
function Nt(e, t = {}) {
|
|
994
|
+
let n = Q(), r = () => _(), i = () => c(n, r()), a = t.pageSize ?? Mt, o = M(() => F(e)?.trim() || ""), s = P([]), l = P([]), u = P(!0), d = P(!0), f = P(!1), p = P(""), m = null, h = null, g;
|
|
995
|
+
function v(e) {
|
|
954
996
|
return {
|
|
955
997
|
id: e.id,
|
|
956
998
|
...e.data()
|
|
957
999
|
};
|
|
958
1000
|
}
|
|
959
|
-
function
|
|
1001
|
+
function y(e) {
|
|
960
1002
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
961
1003
|
}
|
|
962
|
-
let
|
|
1004
|
+
let b = M(() => {
|
|
963
1005
|
let e = /* @__PURE__ */ new Map();
|
|
964
|
-
for (let t of [...
|
|
965
|
-
return [...e.values()].sort((e, t) =>
|
|
1006
|
+
for (let t of [...l.value, ...s.value]) t.id && e.set(t.id, t);
|
|
1007
|
+
return [...e.values()].sort((e, t) => y(t) - y(e) || String(t.id).localeCompare(String(e.id)));
|
|
966
1008
|
});
|
|
967
|
-
function
|
|
1009
|
+
function x() {
|
|
968
1010
|
return [
|
|
969
1011
|
Y("authorUid", "==", o.value),
|
|
970
1012
|
Y("isPublished", "==", !0),
|
|
971
1013
|
Y("listed", "==", !0)
|
|
972
1014
|
];
|
|
973
1015
|
}
|
|
974
|
-
function
|
|
1016
|
+
function S() {
|
|
975
1017
|
if (g?.(), !o.value) {
|
|
1018
|
+
u.value = !1;
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
g = H(W(i(), ...x(), U("createdAt", "desc"), U(L(), "desc"), V(a)), (e) => {
|
|
1022
|
+
s.value = e.docs.map(v), m = e.docs.at(-1) ?? null, h || (d.value = e.docs.length >= a), u.value = !1;
|
|
1023
|
+
}, (e) => {
|
|
1024
|
+
console.error("[memi-board:userPosts] onSnapshot failed", e), p.value = e.message || String(e), u.value = !1;
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
async function C() {
|
|
1028
|
+
if (!(f.value || !d.value || u.value || !o.value)) {
|
|
1029
|
+
f.value = !0, p.value = "";
|
|
1030
|
+
try {
|
|
1031
|
+
let e = h ?? m, t = [
|
|
1032
|
+
...x(),
|
|
1033
|
+
U("createdAt", "desc"),
|
|
1034
|
+
U(L(), "desc"),
|
|
1035
|
+
...e ? [q(e)] : [],
|
|
1036
|
+
V(a + 1)
|
|
1037
|
+
], n = await z(W(i(), ...t)), r = n.docs.slice(0, a), o = r.map(v), c = new Set([...l.value, ...s.value].map((e) => e.id));
|
|
1038
|
+
l.value.push(...o.filter((e) => !c.has(e.id))), h = r.at(-1) ?? h, d.value = n.docs.length > a;
|
|
1039
|
+
} catch (e) {
|
|
1040
|
+
p.value = e.message || String(e), console.error("[memi-board:userPosts] loadMore failed", e);
|
|
1041
|
+
} finally {
|
|
1042
|
+
f.value = !1;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
function w() {
|
|
1047
|
+
g?.(), s.value = [], l.value = [], m = null, h = null, d.value = !0, p.value = "", u.value = !0, S();
|
|
1048
|
+
}
|
|
1049
|
+
return I(o, w, { immediate: !0 }), N(() => g?.()), {
|
|
1050
|
+
posts: b,
|
|
1051
|
+
postsPending: u,
|
|
1052
|
+
hasMore: d,
|
|
1053
|
+
loadingMore: f,
|
|
1054
|
+
loadError: p,
|
|
1055
|
+
loadMore: C
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
//#endregion
|
|
1059
|
+
//#region src/composables/useMemiBoardUserComments.ts
|
|
1060
|
+
var Pt = 10;
|
|
1061
|
+
function Ft(e, t = {}) {
|
|
1062
|
+
let n = Q(), r = () => _(), i = () => O(n, r()), a = t.pageSize ?? Pt, o = M(() => F(e)?.trim() || ""), s = P([]), c = P([]), l = P(!0), u = P(!0), d = P(!1), f = P(""), p = null, m = null, h;
|
|
1063
|
+
function g(e) {
|
|
1064
|
+
return {
|
|
1065
|
+
id: e.id,
|
|
1066
|
+
...e.data()
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
function v(e) {
|
|
1070
|
+
return e.createdAt?.toMillis?.() ?? 0;
|
|
1071
|
+
}
|
|
1072
|
+
let y = M(() => {
|
|
1073
|
+
let e = /* @__PURE__ */ new Map();
|
|
1074
|
+
for (let t of [...c.value, ...s.value]) t.id && e.set(t.id, t);
|
|
1075
|
+
return [...e.values()].sort((e, t) => v(t) - v(e) || String(t.id).localeCompare(String(e.id)));
|
|
1076
|
+
});
|
|
1077
|
+
function b() {
|
|
1078
|
+
return [Y("authorUid", "==", o.value)];
|
|
1079
|
+
}
|
|
1080
|
+
function x() {
|
|
1081
|
+
if (h?.(), !o.value) {
|
|
976
1082
|
l.value = !1;
|
|
977
1083
|
return;
|
|
978
1084
|
}
|
|
979
|
-
|
|
980
|
-
s.value = e.docs.map(
|
|
1085
|
+
h = H(W(i(), ...b(), U("createdAt", "desc"), U(L(), "desc"), V(a)), (e) => {
|
|
1086
|
+
s.value = e.docs.map(g), p = e.docs.at(-1) ?? null, m || (u.value = e.docs.length >= a), l.value = !1;
|
|
981
1087
|
}, (e) => {
|
|
982
|
-
console.error("[memi-board:
|
|
1088
|
+
console.error("[memi-board:userComments] onSnapshot failed", e), f.value = e.message || String(e), l.value = !1;
|
|
983
1089
|
});
|
|
984
1090
|
}
|
|
985
1091
|
async function S() {
|
|
986
1092
|
if (!(d.value || !u.value || l.value || !o.value)) {
|
|
987
1093
|
d.value = !0, f.value = "";
|
|
988
1094
|
try {
|
|
989
|
-
let e =
|
|
1095
|
+
let e = m ?? p, t = [
|
|
990
1096
|
...b(),
|
|
991
1097
|
U("createdAt", "desc"),
|
|
992
1098
|
U(L(), "desc"),
|
|
993
1099
|
...e ? [q(e)] : [],
|
|
994
1100
|
V(a + 1)
|
|
995
|
-
], n = await z(W(i(), ...t)), r = n.docs.slice(0, a), o = r.map(
|
|
996
|
-
c.value.push(...o.filter((e) => !l.has(e.id))),
|
|
1101
|
+
], n = await z(W(i(), ...t)), r = n.docs.slice(0, a), o = r.map(g), l = new Set([...c.value, ...s.value].map((e) => e.id));
|
|
1102
|
+
c.value.push(...o.filter((e) => !l.has(e.id))), m = r.at(-1) ?? m, u.value = n.docs.length > a;
|
|
997
1103
|
} catch (e) {
|
|
998
|
-
f.value = e.message || String(e), console.error("[memi-board:
|
|
1104
|
+
f.value = e.message || String(e), console.error("[memi-board:userComments] loadMore failed", e);
|
|
999
1105
|
} finally {
|
|
1000
1106
|
d.value = !1;
|
|
1001
1107
|
}
|
|
1002
1108
|
}
|
|
1003
1109
|
}
|
|
1004
1110
|
function C() {
|
|
1005
|
-
|
|
1111
|
+
h?.(), s.value = [], c.value = [], p = null, m = null, u.value = !0, f.value = "", l.value = !0, x();
|
|
1006
1112
|
}
|
|
1007
|
-
return
|
|
1008
|
-
|
|
1009
|
-
|
|
1113
|
+
return I(o, C, { immediate: !0 }), N(() => h?.()), {
|
|
1114
|
+
comments: y,
|
|
1115
|
+
commentsPending: l,
|
|
1010
1116
|
hasMore: u,
|
|
1011
1117
|
loadingMore: d,
|
|
1012
1118
|
loadError: f,
|
|
@@ -1014,97 +1120,102 @@ function bt(e, t = {}) {
|
|
|
1014
1120
|
};
|
|
1015
1121
|
}
|
|
1016
1122
|
//#endregion
|
|
1017
|
-
//#region src/composables/
|
|
1018
|
-
var
|
|
1019
|
-
function
|
|
1020
|
-
let n =
|
|
1021
|
-
|
|
1123
|
+
//#region src/composables/useMemiBoardCommentFeed.ts
|
|
1124
|
+
var It = 10;
|
|
1125
|
+
function Lt(e = "latest", t = {}) {
|
|
1126
|
+
let n = Q(), r = () => _(), i = () => O(n, r()), a = M(() => {
|
|
1127
|
+
let e = Math.floor(Number(F(t.pageSize)));
|
|
1128
|
+
return Number.isFinite(e) && e > 0 ? e : It;
|
|
1129
|
+
}), o = M(() => F(e) === "likes" ? "likes" : "latest"), s = P([]), c = P([]), l = P(!0), u = P(!0), d = P(!1), f = P(""), p = null, m = null, h;
|
|
1130
|
+
function g(e) {
|
|
1022
1131
|
return {
|
|
1023
1132
|
id: e.id,
|
|
1024
1133
|
...e.data()
|
|
1025
1134
|
};
|
|
1026
1135
|
}
|
|
1027
|
-
function
|
|
1136
|
+
function v(e) {
|
|
1028
1137
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
1029
1138
|
}
|
|
1030
|
-
|
|
1139
|
+
function y(e, t) {
|
|
1140
|
+
if (o.value === "likes") {
|
|
1141
|
+
let n = (t.likeCount ?? 0) - (e.likeCount ?? 0);
|
|
1142
|
+
if (n) return n;
|
|
1143
|
+
}
|
|
1144
|
+
return v(t) - v(e) || String(t.id).localeCompare(String(e.id));
|
|
1145
|
+
}
|
|
1146
|
+
let b = M(() => {
|
|
1031
1147
|
let e = /* @__PURE__ */ new Map();
|
|
1032
|
-
for (let t of [...
|
|
1033
|
-
return [...e.values()].sort(
|
|
1148
|
+
for (let t of [...c.value, ...s.value]) t.id && e.set(t.id, t);
|
|
1149
|
+
return [...e.values()].sort(y);
|
|
1034
1150
|
});
|
|
1035
1151
|
function x() {
|
|
1036
|
-
return
|
|
1152
|
+
return o.value === "likes" ? [
|
|
1153
|
+
U("likeCount", "desc"),
|
|
1154
|
+
U("createdAt", "desc"),
|
|
1155
|
+
U(L(), "desc")
|
|
1156
|
+
] : [U("createdAt", "desc"), U(L(), "desc")];
|
|
1037
1157
|
}
|
|
1038
1158
|
function S() {
|
|
1039
|
-
|
|
1040
|
-
u.value = !1;
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
|
-
_ = H(W(i(), ...x(), U("createdAt", "desc"), U(L(), "desc"), V(a)), (e) => {
|
|
1044
|
-
c.value = e.docs.map(v), h = e.docs.at(-1) ?? null, g || (d.value = e.docs.length >= a), u.value = !1;
|
|
1159
|
+
h?.(), h = H(W(i(), ...x(), V(a.value)), (e) => {
|
|
1160
|
+
s.value = e.docs.map(g), p = e.docs.at(-1) ?? null, m || (u.value = e.docs.length >= a.value), l.value = !1;
|
|
1045
1161
|
}, (e) => {
|
|
1046
|
-
console.error("[memi-board:
|
|
1162
|
+
console.error("[memi-board:commentFeed] onSnapshot failed", e), f.value = e.message || String(e), l.value = !1;
|
|
1047
1163
|
});
|
|
1048
1164
|
}
|
|
1049
1165
|
async function C() {
|
|
1050
|
-
if (!(
|
|
1051
|
-
|
|
1166
|
+
if (!(d.value || !u.value || l.value)) {
|
|
1167
|
+
d.value = !0, f.value = "";
|
|
1052
1168
|
try {
|
|
1053
|
-
let e =
|
|
1054
|
-
|
|
1055
|
-
U("createdAt", "desc"),
|
|
1056
|
-
U(L(), "desc"),
|
|
1057
|
-
...e ? [q(e)] : [],
|
|
1058
|
-
V(a + 1)
|
|
1059
|
-
], 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));
|
|
1060
|
-
l.value.push(...o.filter((e) => !s.has(e.id))), g = r.at(-1) ?? g, d.value = n.docs.length > a;
|
|
1169
|
+
let e = m ?? p, t = await z(W(i(), ...x(), ...e ? [q(e)] : [], V(a.value + 1))), n = t.docs.slice(0, a.value), r = n.map(g), o = new Set([...c.value, ...s.value].map((e) => e.id));
|
|
1170
|
+
c.value.push(...r.filter((e) => !o.has(e.id))), m = n.at(-1) ?? m, u.value = t.docs.length > a.value;
|
|
1061
1171
|
} catch (e) {
|
|
1062
|
-
|
|
1172
|
+
f.value = e.message || String(e), console.error("[memi-board:commentFeed] loadMore failed", e);
|
|
1063
1173
|
} finally {
|
|
1064
|
-
|
|
1174
|
+
d.value = !1;
|
|
1065
1175
|
}
|
|
1066
1176
|
}
|
|
1067
1177
|
}
|
|
1068
1178
|
function w() {
|
|
1069
|
-
|
|
1179
|
+
h?.(), s.value = [], c.value = [], p = null, m = null, u.value = !0, f.value = "", l.value = !0, S();
|
|
1070
1180
|
}
|
|
1071
|
-
return
|
|
1181
|
+
return I([o, a], w, { immediate: !0 }), N(() => h?.()), {
|
|
1072
1182
|
comments: b,
|
|
1073
|
-
commentsPending:
|
|
1074
|
-
hasMore:
|
|
1075
|
-
loadingMore:
|
|
1076
|
-
loadError:
|
|
1077
|
-
loadMore: C
|
|
1183
|
+
commentsPending: l,
|
|
1184
|
+
hasMore: u,
|
|
1185
|
+
loadingMore: d,
|
|
1186
|
+
loadError: f,
|
|
1187
|
+
loadMore: C,
|
|
1188
|
+
sort: o
|
|
1078
1189
|
};
|
|
1079
1190
|
}
|
|
1080
1191
|
//#endregion
|
|
1081
1192
|
//#region src/composables/useMemiBoardAuthorMemo.ts
|
|
1082
|
-
var
|
|
1083
|
-
function
|
|
1084
|
-
let t =
|
|
1085
|
-
return t || (t =
|
|
1193
|
+
var Rt = 100, zt = /* @__PURE__ */ new Map();
|
|
1194
|
+
function $(e) {
|
|
1195
|
+
let t = zt.get(e);
|
|
1196
|
+
return t || (t = ee({
|
|
1086
1197
|
text: "",
|
|
1087
1198
|
sentiment: null,
|
|
1088
1199
|
loaded: !1,
|
|
1089
1200
|
pending: !1,
|
|
1090
1201
|
saving: !1,
|
|
1091
1202
|
error: ""
|
|
1092
|
-
}),
|
|
1203
|
+
}), zt.set(e, t)), t;
|
|
1093
1204
|
}
|
|
1094
|
-
function
|
|
1095
|
-
let t =
|
|
1205
|
+
function Bt(e) {
|
|
1206
|
+
let t = Q(), n = () => _(), { user: r } = et(), i = M(() => F(e)), a = M(() => `${r.value?.uid ?? ""}:${i.value}`), o = M(() => {
|
|
1096
1207
|
let e = r.value?.uid, a = i.value;
|
|
1097
|
-
return !e || !a ? null :
|
|
1098
|
-
}), s =
|
|
1099
|
-
get: () =>
|
|
1208
|
+
return !e || !a ? null : m(t, n(), e, a);
|
|
1209
|
+
}), s = M({
|
|
1210
|
+
get: () => $(a.value).text,
|
|
1100
1211
|
set: (e) => {
|
|
1101
|
-
|
|
1212
|
+
$(a.value).text = e;
|
|
1102
1213
|
}
|
|
1103
|
-
}), c =
|
|
1104
|
-
async function
|
|
1214
|
+
}), c = M(() => $(a.value).sentiment), l = M(() => $(a.value).loaded), u = M(() => $(a.value).pending), d = M(() => $(a.value).saving), f = M(() => $(a.value).error);
|
|
1215
|
+
async function p() {
|
|
1105
1216
|
let e = o.value;
|
|
1106
1217
|
if (!e) return;
|
|
1107
|
-
let t =
|
|
1218
|
+
let t = $(a.value);
|
|
1108
1219
|
t.pending = !0, t.error = "";
|
|
1109
1220
|
try {
|
|
1110
1221
|
let n = (await R(e)).data();
|
|
@@ -1115,12 +1226,12 @@ function Tt(e) {
|
|
|
1115
1226
|
t.pending = !1;
|
|
1116
1227
|
}
|
|
1117
1228
|
}
|
|
1118
|
-
async function
|
|
1229
|
+
async function h(e, t) {
|
|
1119
1230
|
let n = o.value;
|
|
1120
1231
|
if (!n) return;
|
|
1121
1232
|
let r = e.trim();
|
|
1122
1233
|
if (r.length > 100) throw Error(`메모는 ${100 .toLocaleString()}자까지 작성할 수 있습니다.`);
|
|
1123
|
-
let i =
|
|
1234
|
+
let i = $(a.value);
|
|
1124
1235
|
i.saving = !0, i.error = "";
|
|
1125
1236
|
try {
|
|
1126
1237
|
await K(n, {
|
|
@@ -1134,13 +1245,13 @@ function Tt(e) {
|
|
|
1134
1245
|
i.saving = !1;
|
|
1135
1246
|
}
|
|
1136
1247
|
}
|
|
1137
|
-
async function
|
|
1248
|
+
async function g() {
|
|
1138
1249
|
let e = o.value;
|
|
1139
1250
|
if (!e) return;
|
|
1140
|
-
let t =
|
|
1251
|
+
let t = $(a.value);
|
|
1141
1252
|
t.saving = !0, t.error = "";
|
|
1142
1253
|
try {
|
|
1143
|
-
await
|
|
1254
|
+
await te(e), t.text = "", t.sentiment = null, t.loaded = !0;
|
|
1144
1255
|
} catch (e) {
|
|
1145
1256
|
throw t.error = e.message || String(e), console.error("[memi-board:authorMemo] remove failed", e), e;
|
|
1146
1257
|
} finally {
|
|
@@ -1151,57 +1262,57 @@ function Tt(e) {
|
|
|
1151
1262
|
text: s,
|
|
1152
1263
|
sentiment: c,
|
|
1153
1264
|
loaded: l,
|
|
1154
|
-
pending:
|
|
1155
|
-
saving:
|
|
1156
|
-
error:
|
|
1157
|
-
load:
|
|
1158
|
-
save:
|
|
1159
|
-
remove:
|
|
1265
|
+
pending: u,
|
|
1266
|
+
saving: d,
|
|
1267
|
+
error: f,
|
|
1268
|
+
load: p,
|
|
1269
|
+
save: h,
|
|
1270
|
+
remove: g
|
|
1160
1271
|
};
|
|
1161
1272
|
}
|
|
1162
1273
|
//#endregion
|
|
1163
1274
|
//#region src/composables/useMemiBoardComments.ts
|
|
1164
|
-
var
|
|
1165
|
-
function
|
|
1275
|
+
var Vt = 10, Ht = 5, Ut = 1e3;
|
|
1276
|
+
function Wt(e) {
|
|
1166
1277
|
let t = e.trim();
|
|
1167
1278
|
if (!t) throw Error("댓글 내용을 입력해 주세요.");
|
|
1168
|
-
if (t.length > 1e3) throw Error(`댓글은 ${
|
|
1279
|
+
if (t.length > 1e3) throw Error(`댓글은 ${Ut.toLocaleString()}자까지 작성할 수 있습니다.`);
|
|
1169
1280
|
return t;
|
|
1170
1281
|
}
|
|
1171
|
-
function
|
|
1172
|
-
let
|
|
1173
|
-
function
|
|
1282
|
+
function Gt(e, n, r = {}) {
|
|
1283
|
+
let i = Q(), a = () => _(), o = () => O(i, a()), s = M(() => F(e)), c = M(() => F(n)), u = P([]), d = P([]), f = P(r.subscribe !== !1), p = P([]), m = P(!1), h = P(r.subscribe !== !1), g = P(r.subscribe !== !1), v = null, y, b;
|
|
1284
|
+
function x(e) {
|
|
1174
1285
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
1175
1286
|
}
|
|
1176
|
-
let
|
|
1287
|
+
let S = M(() => {
|
|
1177
1288
|
let e = /* @__PURE__ */ new Map();
|
|
1178
1289
|
for (let t of [
|
|
1179
1290
|
...u.value,
|
|
1180
1291
|
...d.value,
|
|
1181
|
-
...
|
|
1292
|
+
...p.value
|
|
1182
1293
|
]) t.id && e.set(t.id, t);
|
|
1183
|
-
return [...e.values()].sort((e, t) =>
|
|
1294
|
+
return [...e.values()].sort((e, t) => x(e) - x(t) || String(e.id).localeCompare(String(t.id)));
|
|
1184
1295
|
});
|
|
1185
|
-
|
|
1186
|
-
if (
|
|
1187
|
-
|
|
1296
|
+
I([c, s], async ([e]) => {
|
|
1297
|
+
if (p.value = [], u.value = [], d.value = [], v = null, clearTimeout(y), y = void 0, b?.(), b = void 0, h.value = r.subscribe !== !1, r.subscribe !== !1) {
|
|
1298
|
+
f.value = !0, g.value = !0;
|
|
1188
1299
|
try {
|
|
1189
|
-
let t = z(W(
|
|
1300
|
+
let t = z(W(o(), Y("postId", "==", e), U("createdAt", "desc"), U(L(), "desc")));
|
|
1190
1301
|
if (await Promise.all([t.then((t) => {
|
|
1191
|
-
if (
|
|
1302
|
+
if (c.value !== e) return;
|
|
1192
1303
|
let n = t.docs.map((e) => ({
|
|
1193
1304
|
id: e.id,
|
|
1194
1305
|
...e.data()
|
|
1195
1306
|
}));
|
|
1196
1307
|
d.value = n.filter((e) => e.parentId === void 0);
|
|
1197
|
-
}),
|
|
1308
|
+
}), w()]), c.value !== e) return;
|
|
1198
1309
|
} finally {
|
|
1199
|
-
|
|
1310
|
+
c.value === e && (f.value = !1, g.value = !1);
|
|
1200
1311
|
}
|
|
1201
1312
|
}
|
|
1202
1313
|
}, { immediate: !0 });
|
|
1203
|
-
function
|
|
1204
|
-
|
|
1314
|
+
function C() {
|
|
1315
|
+
b?.(), b = H(W(o(), Y("postId", "==", c.value), Y("parentId", "==", null), U("createdAt", "desc"), U(L(), "desc"), V(1)), (e) => {
|
|
1205
1316
|
let t = new Map(u.value.map((e) => [e.id, e]));
|
|
1206
1317
|
for (let n of e.docChanges()) n.type !== "removed" && t.set(n.doc.id, {
|
|
1207
1318
|
id: n.doc.id,
|
|
@@ -1212,172 +1323,175 @@ function At(e, t, n = {}) {
|
|
|
1212
1323
|
console.error("[memi-board:comments] onSnapshot failed", e);
|
|
1213
1324
|
});
|
|
1214
1325
|
}
|
|
1215
|
-
async function
|
|
1216
|
-
if (!(
|
|
1217
|
-
|
|
1326
|
+
async function w() {
|
|
1327
|
+
if (!(m.value || !h.value)) {
|
|
1328
|
+
m.value = !0;
|
|
1218
1329
|
try {
|
|
1219
1330
|
let e = [
|
|
1220
|
-
Y("postId", "==",
|
|
1331
|
+
Y("postId", "==", c.value),
|
|
1221
1332
|
Y("parentId", "==", null),
|
|
1222
1333
|
U("createdAt", "asc"),
|
|
1223
1334
|
U(L(), "asc"),
|
|
1224
|
-
...
|
|
1335
|
+
...v ? [q(v)] : [],
|
|
1225
1336
|
V(11)
|
|
1226
|
-
], t = await z(W(
|
|
1337
|
+
], t = await z(W(o(), ...e)), n = t.docs.slice(0, Vt), r = n.map((e) => ({
|
|
1227
1338
|
id: e.id,
|
|
1228
1339
|
...e.data()
|
|
1229
|
-
})), i = new Set(
|
|
1230
|
-
|
|
1231
|
-
|
|
1340
|
+
})), i = new Set(p.value.map((e) => e.id));
|
|
1341
|
+
p.value.push(...r.filter((e) => !i.has(e.id))), v = n.at(-1) ?? v, t.size <= Vt && (h.value = !1, y = setTimeout(() => {
|
|
1342
|
+
y = void 0, C();
|
|
1232
1343
|
}, 1e3));
|
|
1233
1344
|
} finally {
|
|
1234
|
-
|
|
1345
|
+
m.value = !1;
|
|
1235
1346
|
}
|
|
1236
1347
|
}
|
|
1237
1348
|
}
|
|
1238
|
-
|
|
1239
|
-
clearTimeout(
|
|
1349
|
+
N(() => {
|
|
1350
|
+
clearTimeout(y), b?.();
|
|
1240
1351
|
});
|
|
1241
|
-
async function
|
|
1242
|
-
let
|
|
1243
|
-
|
|
1244
|
-
postId:
|
|
1245
|
-
boardId:
|
|
1246
|
-
body:
|
|
1352
|
+
async function T(e) {
|
|
1353
|
+
let n = Wt(e.body), r = X(i), l = re(o());
|
|
1354
|
+
r.set(l, {
|
|
1355
|
+
postId: c.value,
|
|
1356
|
+
boardId: s.value,
|
|
1357
|
+
body: n,
|
|
1247
1358
|
authorUid: e.authorUid,
|
|
1248
1359
|
authorName: e.authorName,
|
|
1249
1360
|
authorPhoto: e.authorPhoto,
|
|
1250
1361
|
moderationStatus: "approved",
|
|
1251
1362
|
parentId: null,
|
|
1252
|
-
rootId:
|
|
1363
|
+
rootId: l.id,
|
|
1253
1364
|
depth: 0,
|
|
1254
1365
|
replyToUid: null,
|
|
1255
1366
|
replyToName: null,
|
|
1256
1367
|
replyCount: 0,
|
|
1368
|
+
likeCount: 0,
|
|
1257
1369
|
isReply: !1,
|
|
1258
1370
|
createdAt: G(),
|
|
1259
1371
|
updatedAt: G()
|
|
1260
|
-
}),
|
|
1372
|
+
}), r.update(t(i, a(), c.value), { commentCount: B(1) }), await r.commit();
|
|
1261
1373
|
}
|
|
1262
|
-
async function
|
|
1263
|
-
let
|
|
1374
|
+
async function E(e) {
|
|
1375
|
+
let n = Wt(e.body);
|
|
1264
1376
|
if (!e.parent.id) throw Error("답글 대상 댓글을 찾을 수 없습니다.");
|
|
1265
|
-
let
|
|
1266
|
-
if (!
|
|
1267
|
-
let
|
|
1268
|
-
|
|
1269
|
-
postId:
|
|
1270
|
-
boardId:
|
|
1271
|
-
body:
|
|
1377
|
+
let r = X(i), u = re(o()), d = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
|
|
1378
|
+
if (!d) throw Error("댓글 스레드를 찾을 수 없습니다.");
|
|
1379
|
+
let f = Math.min((e.parent.depth ?? 0) + 1, 2);
|
|
1380
|
+
r.set(u, {
|
|
1381
|
+
postId: c.value,
|
|
1382
|
+
boardId: s.value,
|
|
1383
|
+
body: n,
|
|
1272
1384
|
authorUid: e.authorUid,
|
|
1273
1385
|
authorName: e.authorName,
|
|
1274
1386
|
authorPhoto: e.authorPhoto,
|
|
1275
1387
|
moderationStatus: "approved",
|
|
1276
1388
|
parentId: e.parent.id,
|
|
1277
|
-
rootId:
|
|
1278
|
-
depth:
|
|
1389
|
+
rootId: d,
|
|
1390
|
+
depth: f,
|
|
1279
1391
|
replyToUid: e.parent.authorUid,
|
|
1280
1392
|
replyToName: e.parent.authorName,
|
|
1281
1393
|
replyCount: 0,
|
|
1394
|
+
likeCount: 0,
|
|
1282
1395
|
isReply: !0,
|
|
1283
1396
|
createdAt: G(),
|
|
1284
1397
|
updatedAt: G()
|
|
1285
|
-
}),
|
|
1398
|
+
}), r.update(l(i, a(), d), { replyCount: B(1) }), r.update(t(i, a(), c.value), { commentCount: B(1) }), await r.commit();
|
|
1286
1399
|
}
|
|
1287
|
-
async function
|
|
1400
|
+
async function D(e) {
|
|
1288
1401
|
if (!e.id) return;
|
|
1289
1402
|
if (e.parentId == null && (e.replyCount ?? 0) > 0) throw Error("답글이 있는 댓글은 현재 삭제할 수 없습니다.");
|
|
1290
|
-
|
|
1291
|
-
|
|
1403
|
+
await ut(i, a(), e.id);
|
|
1404
|
+
let n = X(i);
|
|
1405
|
+
n.delete(l(i, a(), e.id)), e.parentId && e.rootId && n.update(l(i, a(), e.rootId), { replyCount: B(-1) }), n.update(t(i, a(), c.value), { commentCount: B(-1) }), await n.commit(), p.value = p.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);
|
|
1292
1406
|
}
|
|
1293
|
-
async function
|
|
1294
|
-
let n =
|
|
1295
|
-
await J(
|
|
1407
|
+
async function k(e, t) {
|
|
1408
|
+
let n = Wt(t);
|
|
1409
|
+
await J(l(i, a(), e), {
|
|
1296
1410
|
body: n,
|
|
1297
1411
|
updatedAt: G()
|
|
1298
1412
|
});
|
|
1299
1413
|
}
|
|
1300
|
-
async function
|
|
1301
|
-
await J(
|
|
1414
|
+
async function A(e, t, n) {
|
|
1415
|
+
await J(l(i, a(), e), {
|
|
1302
1416
|
isBlinded: t,
|
|
1303
1417
|
moderatedAt: G(),
|
|
1304
1418
|
moderatedBy: n
|
|
1305
1419
|
});
|
|
1306
1420
|
}
|
|
1307
1421
|
return {
|
|
1308
|
-
comments:
|
|
1309
|
-
commentsPending:
|
|
1310
|
-
hasMore:
|
|
1311
|
-
loadingMore:
|
|
1312
|
-
loadMore:
|
|
1313
|
-
addComment:
|
|
1314
|
-
addReply:
|
|
1315
|
-
updateComment:
|
|
1316
|
-
setCommentBlinded:
|
|
1317
|
-
deleteComment:
|
|
1422
|
+
comments: S,
|
|
1423
|
+
commentsPending: M(() => g.value || f.value),
|
|
1424
|
+
hasMore: h,
|
|
1425
|
+
loadingMore: m,
|
|
1426
|
+
loadMore: w,
|
|
1427
|
+
addComment: T,
|
|
1428
|
+
addReply: E,
|
|
1429
|
+
updateComment: k,
|
|
1430
|
+
setCommentBlinded: A,
|
|
1431
|
+
deleteComment: D
|
|
1318
1432
|
};
|
|
1319
1433
|
}
|
|
1320
|
-
function
|
|
1321
|
-
let r =
|
|
1322
|
-
function
|
|
1434
|
+
function Kt(e, t, n) {
|
|
1435
|
+
let r = Q(), i = () => _(), a = () => O(r, i()), o = P([]), s = P(!1), c = P(!1), l = P(!0), u = null, d;
|
|
1436
|
+
function f(e) {
|
|
1323
1437
|
return e.sort((e, t) => (e.createdAt?.toMillis?.() ?? 0) - (t.createdAt?.toMillis?.() ?? 0) || String(e.id).localeCompare(String(t.id)));
|
|
1324
1438
|
}
|
|
1325
|
-
function
|
|
1326
|
-
|
|
1327
|
-
let t = new Map(
|
|
1439
|
+
function p() {
|
|
1440
|
+
d ||= H(W(a(), Y("rootId", "==", n), Y("isReply", "==", !0), U("createdAt", "desc"), U(L(), "desc"), V(1)), (e) => {
|
|
1441
|
+
let t = new Map(o.value.map((e) => [e.id, e]));
|
|
1328
1442
|
for (let n of e.docChanges()) n.type !== "removed" && t.set(n.doc.id, {
|
|
1329
1443
|
id: n.doc.id,
|
|
1330
1444
|
...n.doc.data()
|
|
1331
1445
|
});
|
|
1332
|
-
|
|
1446
|
+
o.value = f([...t.values()]);
|
|
1333
1447
|
}, (e) => {
|
|
1334
1448
|
console.error("[memi-board:replies] onSnapshot failed", e);
|
|
1335
1449
|
});
|
|
1336
1450
|
}
|
|
1337
|
-
async function
|
|
1338
|
-
if (!(
|
|
1339
|
-
|
|
1451
|
+
async function m() {
|
|
1452
|
+
if (!(s.value || !l.value)) {
|
|
1453
|
+
s.value = !0;
|
|
1340
1454
|
try {
|
|
1341
1455
|
let e = [
|
|
1342
1456
|
Y("rootId", "==", n),
|
|
1343
1457
|
Y("isReply", "==", !0),
|
|
1344
1458
|
U("createdAt", "asc"),
|
|
1345
1459
|
U(L(), "asc"),
|
|
1346
|
-
...
|
|
1460
|
+
...u ? [q(u)] : [],
|
|
1347
1461
|
V(6)
|
|
1348
|
-
], t = await z(W(a(), ...e)), r = new Set(
|
|
1462
|
+
], t = await z(W(a(), ...e)), r = new Set(o.value.map((e) => e.id)), i = t.docs.slice(0, Ht), s = i.map((e) => ({
|
|
1349
1463
|
id: e.id,
|
|
1350
1464
|
...e.data()
|
|
1351
1465
|
})).filter((e) => !r.has(e.id));
|
|
1352
|
-
|
|
1466
|
+
o.value = f([...o.value, ...s]), u = i.at(-1) ?? u, c.value = !0, t.size <= Ht && (l.value = !1, p());
|
|
1353
1467
|
} finally {
|
|
1354
|
-
|
|
1468
|
+
s.value = !1;
|
|
1355
1469
|
}
|
|
1356
1470
|
}
|
|
1357
1471
|
}
|
|
1358
|
-
function
|
|
1359
|
-
|
|
1360
|
-
}
|
|
1361
|
-
async function v() {
|
|
1362
|
-
_(), await g();
|
|
1472
|
+
function h() {
|
|
1473
|
+
d?.(), d = void 0, o.value = [], u = null, c.value = !1, l.value = !0;
|
|
1363
1474
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1475
|
+
async function g() {
|
|
1476
|
+
h(), await m();
|
|
1477
|
+
}
|
|
1478
|
+
return N(() => d?.()), {
|
|
1479
|
+
replies: o,
|
|
1480
|
+
loading: s,
|
|
1481
|
+
loaded: c,
|
|
1482
|
+
hasMore: l,
|
|
1483
|
+
loadMore: m,
|
|
1484
|
+
refresh: g
|
|
1371
1485
|
};
|
|
1372
1486
|
}
|
|
1373
1487
|
//#endregion
|
|
1374
1488
|
//#region src/composables/useMemiBoardLikes.ts
|
|
1375
|
-
function
|
|
1376
|
-
let
|
|
1489
|
+
function qt(e, n) {
|
|
1490
|
+
let r = () => _(), i = Q(), a = Z(), o = P(!1), s = P(!1), c;
|
|
1377
1491
|
function l(e) {
|
|
1378
|
-
return
|
|
1492
|
+
return j(i, r(), n, e);
|
|
1379
1493
|
}
|
|
1380
|
-
|
|
1494
|
+
I(a, (e) => {
|
|
1381
1495
|
if (c?.(), c = void 0, !e) {
|
|
1382
1496
|
o.value = !1;
|
|
1383
1497
|
return;
|
|
@@ -1387,20 +1501,20 @@ function Mt(e, t) {
|
|
|
1387
1501
|
}, (e) => {
|
|
1388
1502
|
console.error("[memi-board:likes] onSnapshot failed", e);
|
|
1389
1503
|
});
|
|
1390
|
-
}, { immediate: !0 }),
|
|
1504
|
+
}, { immediate: !0 }), N(() => c?.());
|
|
1391
1505
|
async function u() {
|
|
1392
1506
|
let c = a.value?.uid;
|
|
1393
1507
|
if (!c) throw Error("로그인이 필요합니다.");
|
|
1394
1508
|
if (s.value) return o.value;
|
|
1395
1509
|
s.value = !0;
|
|
1396
1510
|
try {
|
|
1397
|
-
let a = l(c), o = i
|
|
1398
|
-
return await
|
|
1511
|
+
let a = l(c), o = t(i, r(), n);
|
|
1512
|
+
return await oe(i, async (t) => (await t.get(a)).exists() ? (t.delete(a), t.update(o, { likeCount: B(-1) }), !1) : (t.set(a, {
|
|
1399
1513
|
uid: c,
|
|
1400
|
-
postId:
|
|
1514
|
+
postId: n,
|
|
1401
1515
|
boardId: e,
|
|
1402
1516
|
createdAt: G()
|
|
1403
|
-
}),
|
|
1517
|
+
}), t.update(o, { likeCount: B(1) }), !0));
|
|
1404
1518
|
} finally {
|
|
1405
1519
|
s.value = !1;
|
|
1406
1520
|
}
|
|
@@ -1411,36 +1525,216 @@ function Mt(e, t) {
|
|
|
1411
1525
|
toggleLike: u
|
|
1412
1526
|
};
|
|
1413
1527
|
}
|
|
1528
|
+
var Jt = Symbol.for("memi-board:comment-likes");
|
|
1529
|
+
function Yt(e, t) {
|
|
1530
|
+
let n = () => _(), r = Q(), i = Z(), a = M(() => F(e)), o = M(() => F(t)), s = P(/* @__PURE__ */ new Set()), c = P(null), u;
|
|
1531
|
+
I([i, o], ([e, t]) => {
|
|
1532
|
+
u?.(), u = void 0, s.value = /* @__PURE__ */ new Set(), !(!e || !t) && (u = H(W(A(r, n()), Y("uid", "==", e.uid), Y("postId", "==", t), Y("target", "==", "comment")), (e) => {
|
|
1533
|
+
let t = /* @__PURE__ */ new Set();
|
|
1534
|
+
for (let n of e.docs) {
|
|
1535
|
+
let e = n.data().commentId;
|
|
1536
|
+
typeof e == "string" && e && t.add(e);
|
|
1537
|
+
}
|
|
1538
|
+
s.value = t;
|
|
1539
|
+
}, (e) => {
|
|
1540
|
+
console.error("[memi-board:comment-likes] onSnapshot failed", e);
|
|
1541
|
+
}));
|
|
1542
|
+
}, { immediate: !0 }), N(() => u?.());
|
|
1543
|
+
function d(e) {
|
|
1544
|
+
return !!e && s.value.has(e);
|
|
1545
|
+
}
|
|
1546
|
+
function f(e) {
|
|
1547
|
+
return !!e && c.value === e;
|
|
1548
|
+
}
|
|
1549
|
+
async function p(e) {
|
|
1550
|
+
let t = i.value?.uid;
|
|
1551
|
+
if (!t) throw Error("로그인이 필요합니다.");
|
|
1552
|
+
if (!e) return !1;
|
|
1553
|
+
if (c.value) return d(e);
|
|
1554
|
+
c.value = e;
|
|
1555
|
+
try {
|
|
1556
|
+
let i = j(r, n(), e, t), c = l(r, n(), e), u = await oe(r, async (n) => {
|
|
1557
|
+
let r = await n.get(i), s = await n.get(c);
|
|
1558
|
+
if (!s.exists()) throw Error("댓글을 찾을 수 없습니다.");
|
|
1559
|
+
let l = Number(s.data()?.likeCount ?? 0);
|
|
1560
|
+
return r.exists() ? (n.delete(i), n.update(c, { likeCount: Math.max(0, l - 1) }), !1) : (n.set(i, {
|
|
1561
|
+
uid: t,
|
|
1562
|
+
postId: o.value,
|
|
1563
|
+
boardId: a.value,
|
|
1564
|
+
commentId: e,
|
|
1565
|
+
target: "comment",
|
|
1566
|
+
createdAt: G()
|
|
1567
|
+
}), n.update(c, { likeCount: l + 1 }), !0);
|
|
1568
|
+
}), d = new Set(s.value);
|
|
1569
|
+
return u ? d.add(e) : d.delete(e), s.value = d, u;
|
|
1570
|
+
} finally {
|
|
1571
|
+
c.value = null;
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
return {
|
|
1575
|
+
isLiked: d,
|
|
1576
|
+
isPending: f,
|
|
1577
|
+
toggleLike: p
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
//#endregion
|
|
1581
|
+
//#region src/composables/useMemiBoardReports.ts
|
|
1582
|
+
var Xt = 200, Zt = [
|
|
1583
|
+
{
|
|
1584
|
+
value: "spam",
|
|
1585
|
+
label: "스팸·광고"
|
|
1586
|
+
},
|
|
1587
|
+
{
|
|
1588
|
+
value: "abuse",
|
|
1589
|
+
label: "욕설·혐오·괴롭힘"
|
|
1590
|
+
},
|
|
1591
|
+
{
|
|
1592
|
+
value: "adult",
|
|
1593
|
+
label: "음란물"
|
|
1594
|
+
},
|
|
1595
|
+
{
|
|
1596
|
+
value: "illegal",
|
|
1597
|
+
label: "불법 정보"
|
|
1598
|
+
},
|
|
1599
|
+
{
|
|
1600
|
+
value: "other",
|
|
1601
|
+
label: "기타"
|
|
1602
|
+
}
|
|
1603
|
+
];
|
|
1604
|
+
function Qt(e) {
|
|
1605
|
+
return Zt.find((t) => t.value === e)?.label || e || "신고";
|
|
1606
|
+
}
|
|
1607
|
+
function $t(e, t) {
|
|
1608
|
+
let n = () => _(), i = Q(), a = Z(), o = P(!1), s = P(!1), c;
|
|
1609
|
+
I(a, (e) => {
|
|
1610
|
+
if (c?.(), c = void 0, !e) {
|
|
1611
|
+
o.value = !1;
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
c = H(r(i, n(), t, e.uid), (e) => {
|
|
1615
|
+
o.value = e.exists();
|
|
1616
|
+
}, (e) => {
|
|
1617
|
+
console.error("[memi-board:reports] onSnapshot failed", e);
|
|
1618
|
+
});
|
|
1619
|
+
}, { immediate: !0 }), N(() => c?.());
|
|
1620
|
+
async function l(c) {
|
|
1621
|
+
let l = a.value?.uid;
|
|
1622
|
+
if (!l) throw Error("로그인이 필요합니다.");
|
|
1623
|
+
if (s.value) return;
|
|
1624
|
+
if (c.authorUid && c.authorUid === l) throw Error("자신의 글은 신고할 수 없습니다.");
|
|
1625
|
+
let u = Zt.some((e) => e.value === c.reason) ? c.reason : null;
|
|
1626
|
+
if (!u) throw Error("신고 사유를 선택해 주세요.");
|
|
1627
|
+
let d = (c.detail || "").trim();
|
|
1628
|
+
if (d.length > 200) throw Error("자세한 내용은 200자까지입니다.");
|
|
1629
|
+
s.value = !0;
|
|
1630
|
+
try {
|
|
1631
|
+
await K(r(i, n(), t, l), {
|
|
1632
|
+
uid: l,
|
|
1633
|
+
target: "post",
|
|
1634
|
+
postId: t,
|
|
1635
|
+
boardId: e,
|
|
1636
|
+
reason: u,
|
|
1637
|
+
detail: d,
|
|
1638
|
+
status: "open",
|
|
1639
|
+
postTitle: (c.postTitle || "").trim().slice(0, 200),
|
|
1640
|
+
authorUid: c.authorUid || "",
|
|
1641
|
+
authorName: c.authorName ?? null,
|
|
1642
|
+
createdAt: G()
|
|
1643
|
+
}), o.value = !0;
|
|
1644
|
+
} finally {
|
|
1645
|
+
s.value = !1;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
return {
|
|
1649
|
+
hasReported: o,
|
|
1650
|
+
pending: s,
|
|
1651
|
+
submitReport: l
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
var en = 20;
|
|
1655
|
+
function tn(e = {}) {
|
|
1656
|
+
let t = () => _(), n = Q(), { canManageContent: i, canManageBoard: a } = et(), s = e.pageSize ?? en, c = M(() => F(e.boardId)?.trim() || ""), l = P([]), u = P(!0), d = P(!1), f = P(!1), p = P(""), m = null, h;
|
|
1657
|
+
function g(e) {
|
|
1658
|
+
return {
|
|
1659
|
+
id: e.id,
|
|
1660
|
+
...e.data()
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
function v() {
|
|
1664
|
+
if (h?.(), !i.value) {
|
|
1665
|
+
l.value = [], u.value = !1;
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
u.value = !0;
|
|
1669
|
+
let e = c.value ? [Y("status", "==", "open"), Y("boardId", "==", c.value)] : [Y("status", "==", "open"), Y("target", "==", "post")];
|
|
1670
|
+
h = H(W(o(n, t()), ...e, U("createdAt", "desc"), U(L(), "desc"), V(s)), (e) => {
|
|
1671
|
+
l.value = e.docs.map(g).filter((e) => a(e.boardId)), m = e.docs.at(-1) ?? null, f.value = e.docs.length >= s, u.value = !1;
|
|
1672
|
+
}, (e) => {
|
|
1673
|
+
console.error("[memi-board:report-queue] onSnapshot failed", e), p.value = e.message || String(e), u.value = !1;
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
I([i, c], v, { immediate: !0 }), N(() => h?.());
|
|
1677
|
+
async function y() {
|
|
1678
|
+
if (!(d.value || !f.value || !m || !i.value)) {
|
|
1679
|
+
d.value = !0, p.value = "";
|
|
1680
|
+
try {
|
|
1681
|
+
let e = c.value ? [Y("status", "==", "open"), Y("boardId", "==", c.value)] : [Y("status", "==", "open"), Y("target", "==", "post")], r = await z(W(o(n, t()), ...e, U("createdAt", "desc"), U(L(), "desc"), q(m), V(s + 1))), i = r.docs.slice(0, s), u = new Set(l.value.map((e) => e.id));
|
|
1682
|
+
l.value.push(...i.map(g).filter((e) => !u.has(e.id) && a(e.boardId))), m = i.at(-1) ?? m, f.value = r.docs.length > s;
|
|
1683
|
+
} catch (e) {
|
|
1684
|
+
p.value = e.message || String(e);
|
|
1685
|
+
} finally {
|
|
1686
|
+
d.value = !1;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
async function b(e, i) {
|
|
1691
|
+
!e.id || !a(e.boardId) || await J(r(n, t(), e.postId, e.uid), {
|
|
1692
|
+
status: i,
|
|
1693
|
+
reviewedAt: G(),
|
|
1694
|
+
reviewedBy: Z().value?.uid || ""
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
return {
|
|
1698
|
+
reports: l,
|
|
1699
|
+
pending: u,
|
|
1700
|
+
loadingMore: d,
|
|
1701
|
+
hasMore: f,
|
|
1702
|
+
loadError: p,
|
|
1703
|
+
loadMore: y,
|
|
1704
|
+
dismissReport: (e) => b(e, "dismissed"),
|
|
1705
|
+
actionReport: (e) => b(e, "actioned")
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1414
1708
|
//#endregion
|
|
1415
1709
|
//#region src/composables/useMemiBoardViews.ts
|
|
1416
|
-
function
|
|
1417
|
-
let e = () =>
|
|
1418
|
-
function
|
|
1710
|
+
function nn() {
|
|
1711
|
+
let e = () => _(), n = Q();
|
|
1712
|
+
function r(t, n) {
|
|
1419
1713
|
let { postsCollection: r } = e();
|
|
1420
1714
|
return `memi-board:viewed:${r}:${t}:${n}`;
|
|
1421
1715
|
}
|
|
1422
|
-
function
|
|
1716
|
+
function i(e, t) {
|
|
1423
1717
|
if (typeof sessionStorage > "u") return !1;
|
|
1424
1718
|
try {
|
|
1425
|
-
return sessionStorage.getItem(
|
|
1719
|
+
return sessionStorage.getItem(r(e, t)) === "1";
|
|
1426
1720
|
} catch {
|
|
1427
1721
|
return !1;
|
|
1428
1722
|
}
|
|
1429
1723
|
}
|
|
1430
1724
|
function a(e, t) {
|
|
1431
1725
|
if (!(typeof sessionStorage > "u")) try {
|
|
1432
|
-
sessionStorage.setItem(
|
|
1726
|
+
sessionStorage.setItem(r(e, t), "1");
|
|
1433
1727
|
} catch {}
|
|
1434
1728
|
}
|
|
1435
1729
|
async function o(o, s) {
|
|
1436
1730
|
let c = o?.trim(), l = s?.trim();
|
|
1437
|
-
if (!(!c || !l) && !
|
|
1731
|
+
if (!(!c || !l) && !i(c, l)) {
|
|
1438
1732
|
a(c, l);
|
|
1439
1733
|
try {
|
|
1440
|
-
await J(
|
|
1734
|
+
await J(t(n, e(), l), { viewCount: B(1) });
|
|
1441
1735
|
} catch (e) {
|
|
1442
1736
|
try {
|
|
1443
|
-
sessionStorage.removeItem(
|
|
1737
|
+
sessionStorage.removeItem(r(c, l));
|
|
1444
1738
|
} catch {}
|
|
1445
1739
|
import.meta.dev && console.warn("[memi-board] recordView failed", e);
|
|
1446
1740
|
}
|
|
@@ -1448,12 +1742,12 @@ function Nt() {
|
|
|
1448
1742
|
}
|
|
1449
1743
|
return {
|
|
1450
1744
|
recordView: o,
|
|
1451
|
-
hasRecordedView:
|
|
1745
|
+
hasRecordedView: i
|
|
1452
1746
|
};
|
|
1453
1747
|
}
|
|
1454
1748
|
//#endregion
|
|
1455
1749
|
//#region src/utils/moderation-prompt.ts
|
|
1456
|
-
var
|
|
1750
|
+
var rn = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\n아래 텍스트에 욕설·비속어·성희롱·혐오·위협·스팸·도배성 광고가 있으면 반드시 거부(flagged=true)합니다.\n\nflagged=true 예:\n- 욕설/비속어: 씨발, 시발, 병신, 지랄, 좆, 개새끼, 존나, fuck, shit, bitch 등\n- 성적 모욕, 혐오 발언, 협박\n- 게시판 주제와 무관한 도배성 광고/스팸\n\nflagged=false:\n- 게시판 주제에 맞는 정상적인 글/댓글 (강한 의견이나 정당한 비판은 허용)\n\nJSON만 출력:\n{\"flagged\":boolean,\"category\":\"none\"|\"abuse\"|\"spam\"|\"adult\"|\"violence\"|\"other\",\"reason\":string}\n- flagged=true면 category를 고르고, reason은 한국어로 **무엇이 문제인지** 구체적으로 1문장\n (예: \"욕설(비속어)이 포함되어 게시할 수 없습니다.\", \"광고·스팸성 문구가 있어 게시할 수 없습니다.\")\n 욕설 단어 전체를 그대로 길게 인용하지 말고, 유형(욕설/혐오/스팸 등)을 분명히 밝힌다.\n- flagged=false면 category는 \"none\", reason은 \"\"", an = {
|
|
1457
1751
|
abuse: "욕설·비속어·모욕",
|
|
1458
1752
|
spam: "스팸·광고",
|
|
1459
1753
|
adult: "선정적·성인 내용",
|
|
@@ -1461,17 +1755,17 @@ var Pt = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이
|
|
|
1461
1755
|
other: "부적절한 내용",
|
|
1462
1756
|
none: ""
|
|
1463
1757
|
};
|
|
1464
|
-
function
|
|
1758
|
+
function on(e) {
|
|
1465
1759
|
if (e.via === "local" && e.localHit) {
|
|
1466
1760
|
let t = e.localHit.trim();
|
|
1467
1761
|
return t.length > 0 && t.length <= 12 ? `「${t}」 같은 표현은 사용할 수 없습니다. 다른 말로 바꿔 주세요.` : "욕설·비속어가 포함되어 게시할 수 없습니다. 표현을 바꿔 주세요.";
|
|
1468
1762
|
}
|
|
1469
1763
|
let t = (e.reason || "").trim();
|
|
1470
1764
|
if (t) return t;
|
|
1471
|
-
let n = e.category && e.category !== "none" ?
|
|
1765
|
+
let n = e.category && e.category !== "none" ? an[e.category] || e.category : "";
|
|
1472
1766
|
return n ? `${n}이(가) 포함되어 게시할 수 없습니다. 내용을 수정해 주세요.` : "게시할 수 없는 내용이 포함되어 있습니다. 내용을 수정해 주세요.";
|
|
1473
1767
|
}
|
|
1474
|
-
var
|
|
1768
|
+
var sn = [
|
|
1475
1769
|
"씨발",
|
|
1476
1770
|
"시발",
|
|
1477
1771
|
"시이발",
|
|
@@ -1497,10 +1791,10 @@ var Lt = [
|
|
|
1497
1791
|
"bitch",
|
|
1498
1792
|
"asshole"
|
|
1499
1793
|
];
|
|
1500
|
-
function
|
|
1794
|
+
function cn(e) {
|
|
1501
1795
|
return new RegExp(e.join("|"), "i");
|
|
1502
1796
|
}
|
|
1503
|
-
var
|
|
1797
|
+
var ln = /* @__PURE__ */ new Set([
|
|
1504
1798
|
"none",
|
|
1505
1799
|
"abuse",
|
|
1506
1800
|
"spam",
|
|
@@ -1508,7 +1802,7 @@ var zt = /* @__PURE__ */ new Set([
|
|
|
1508
1802
|
"violence",
|
|
1509
1803
|
"other"
|
|
1510
1804
|
]);
|
|
1511
|
-
function
|
|
1805
|
+
function un(e) {
|
|
1512
1806
|
let t = e.trim();
|
|
1513
1807
|
if (!t) return null;
|
|
1514
1808
|
let n;
|
|
@@ -1526,7 +1820,7 @@ function Bt(e) {
|
|
|
1526
1820
|
if (typeof n != "object" || !n) return null;
|
|
1527
1821
|
let r = n;
|
|
1528
1822
|
if (typeof r.flagged != "boolean") return null;
|
|
1529
|
-
let i = typeof r.category == "string" &&
|
|
1823
|
+
let i = typeof r.category == "string" && ln.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
|
|
1530
1824
|
return {
|
|
1531
1825
|
flagged: r.flagged,
|
|
1532
1826
|
category: i,
|
|
@@ -1535,20 +1829,20 @@ function Bt(e) {
|
|
|
1535
1829
|
}
|
|
1536
1830
|
//#endregion
|
|
1537
1831
|
//#region src/composables/useMemiBoardModeration.ts
|
|
1538
|
-
var
|
|
1832
|
+
var dn = {
|
|
1539
1833
|
flagged: !1,
|
|
1540
1834
|
category: "none",
|
|
1541
1835
|
reason: "",
|
|
1542
1836
|
via: "empty"
|
|
1543
1837
|
};
|
|
1544
|
-
function
|
|
1545
|
-
let e =
|
|
1838
|
+
function fn() {
|
|
1839
|
+
let e = u().moderation ?? {}, t = le(), { isWriteRestricted: n, restrictedMessage: r, recordContentModerationBlock: i } = et(), a = cn([...sn, ...e.localBlocklist ?? []]);
|
|
1546
1840
|
function o(e) {
|
|
1547
1841
|
let t = e.match(a);
|
|
1548
1842
|
return t ? {
|
|
1549
1843
|
flagged: !0,
|
|
1550
1844
|
category: "abuse",
|
|
1551
|
-
reason:
|
|
1845
|
+
reason: on({
|
|
1552
1846
|
via: "local",
|
|
1553
1847
|
localHit: t[0] || "",
|
|
1554
1848
|
category: "abuse"
|
|
@@ -1556,27 +1850,27 @@ function Ht() {
|
|
|
1556
1850
|
via: "local"
|
|
1557
1851
|
} : null;
|
|
1558
1852
|
}
|
|
1559
|
-
function
|
|
1560
|
-
let n = e.model ?? "gemini-3.5-flash-lite", r = e.useLimitedUseAppCheckTokens !== !1, i =
|
|
1561
|
-
backend: new
|
|
1853
|
+
function s() {
|
|
1854
|
+
let n = e.model ?? "gemini-3.5-flash-lite", r = e.useLimitedUseAppCheckTokens !== !1, i = we(t, {
|
|
1855
|
+
backend: new Se(),
|
|
1562
1856
|
useLimitedUseAppCheckTokens: r
|
|
1563
1857
|
});
|
|
1564
|
-
return
|
|
1858
|
+
return Te(i, {
|
|
1565
1859
|
model: n,
|
|
1566
|
-
systemInstruction:
|
|
1860
|
+
systemInstruction: rn,
|
|
1567
1861
|
generationConfig: {
|
|
1568
1862
|
temperature: 0,
|
|
1569
1863
|
maxOutputTokens: 256,
|
|
1570
1864
|
responseMimeType: "application/json",
|
|
1571
|
-
responseSchema:
|
|
1572
|
-
flagged:
|
|
1573
|
-
category:
|
|
1574
|
-
reason:
|
|
1865
|
+
responseSchema: Ce.object({ properties: {
|
|
1866
|
+
flagged: Ce.boolean(),
|
|
1867
|
+
category: Ce.string(),
|
|
1868
|
+
reason: Ce.string()
|
|
1575
1869
|
} })
|
|
1576
1870
|
}
|
|
1577
1871
|
});
|
|
1578
1872
|
}
|
|
1579
|
-
function
|
|
1873
|
+
function c() {
|
|
1580
1874
|
return e.onError === "block" ? {
|
|
1581
1875
|
flagged: !0,
|
|
1582
1876
|
category: "other",
|
|
@@ -1591,8 +1885,8 @@ function Ht() {
|
|
|
1591
1885
|
via: "ai-error-allow"
|
|
1592
1886
|
};
|
|
1593
1887
|
}
|
|
1594
|
-
function
|
|
1595
|
-
let n =
|
|
1888
|
+
function l(e, t = "") {
|
|
1889
|
+
let n = on({
|
|
1596
1890
|
reason: e.reason,
|
|
1597
1891
|
category: e.category,
|
|
1598
1892
|
via: e.via
|
|
@@ -1606,9 +1900,9 @@ function Ht() {
|
|
|
1606
1900
|
if (!e.flagged || e.error || e.via !== "local" && e.via !== "ai") return e;
|
|
1607
1901
|
try {
|
|
1608
1902
|
let { messageSuffix: t } = await i();
|
|
1609
|
-
return
|
|
1903
|
+
return l(e, t);
|
|
1610
1904
|
} catch (t) {
|
|
1611
|
-
return console.warn("[memi-board] record moderation strike failed", t instanceof Error ? t.message : t),
|
|
1905
|
+
return console.warn("[memi-board] record moderation strike failed", t instanceof Error ? t.message : t), l(e);
|
|
1612
1906
|
}
|
|
1613
1907
|
}
|
|
1614
1908
|
async function f(t) {
|
|
@@ -1620,22 +1914,22 @@ function Ht() {
|
|
|
1620
1914
|
};
|
|
1621
1915
|
let i = t.trim();
|
|
1622
1916
|
if (!i) return {
|
|
1623
|
-
...
|
|
1917
|
+
...dn,
|
|
1624
1918
|
via: "empty"
|
|
1625
1919
|
};
|
|
1626
1920
|
let a = o(i);
|
|
1627
1921
|
if (a) return d(a);
|
|
1628
1922
|
if (e.enabled === !1) return {
|
|
1629
|
-
...
|
|
1923
|
+
...dn,
|
|
1630
1924
|
via: "disabled"
|
|
1631
1925
|
};
|
|
1632
1926
|
try {
|
|
1633
|
-
let e =
|
|
1634
|
-
if (!e) return
|
|
1927
|
+
let e = un((await s().generateContent(`심사할 텍스트:\n"""${i.slice(0, 4e3)}"""`)).response.text());
|
|
1928
|
+
if (!e) return c();
|
|
1635
1929
|
let t = {
|
|
1636
1930
|
flagged: e.flagged,
|
|
1637
1931
|
category: e.category,
|
|
1638
|
-
reason:
|
|
1932
|
+
reason: on({
|
|
1639
1933
|
reason: e.reason,
|
|
1640
1934
|
category: e.category,
|
|
1641
1935
|
via: "ai"
|
|
@@ -1644,7 +1938,7 @@ function Ht() {
|
|
|
1644
1938
|
};
|
|
1645
1939
|
return t.flagged ? d(t) : t;
|
|
1646
1940
|
} catch (e) {
|
|
1647
|
-
return console.warn("[memi-board] moderation AI failed", e instanceof Error ? e.message : e),
|
|
1941
|
+
return console.warn("[memi-board] moderation AI failed", e instanceof Error ? e.message : e), c();
|
|
1648
1942
|
}
|
|
1649
1943
|
}
|
|
1650
1944
|
async function p(t) {
|
|
@@ -1655,22 +1949,22 @@ function Ht() {
|
|
|
1655
1949
|
via: "restricted"
|
|
1656
1950
|
};
|
|
1657
1951
|
if (e.enabled === !1 || !e.moderateImages) return {
|
|
1658
|
-
...
|
|
1952
|
+
...dn,
|
|
1659
1953
|
via: "disabled"
|
|
1660
1954
|
};
|
|
1661
1955
|
try {
|
|
1662
1956
|
let e = await new Promise((e, n) => {
|
|
1663
1957
|
let r = new FileReader();
|
|
1664
1958
|
r.onload = () => e(r.result.split(",")[1] ?? ""), r.onerror = n, r.readAsDataURL(t);
|
|
1665
|
-
}), n =
|
|
1959
|
+
}), n = un((await s().generateContent([{ inlineData: {
|
|
1666
1960
|
mimeType: t.type || "image/jpeg",
|
|
1667
1961
|
data: e
|
|
1668
1962
|
} }, "이 이미지를 심사해 주세요."])).response.text());
|
|
1669
|
-
if (!n) return
|
|
1963
|
+
if (!n) return c();
|
|
1670
1964
|
let r = {
|
|
1671
1965
|
flagged: n.flagged,
|
|
1672
1966
|
category: n.category,
|
|
1673
|
-
reason:
|
|
1967
|
+
reason: on({
|
|
1674
1968
|
reason: n.reason,
|
|
1675
1969
|
category: n.category,
|
|
1676
1970
|
via: "ai"
|
|
@@ -1679,7 +1973,7 @@ function Ht() {
|
|
|
1679
1973
|
};
|
|
1680
1974
|
return r.flagged ? d(r) : r;
|
|
1681
1975
|
} catch (e) {
|
|
1682
|
-
return console.warn("[memi-board] moderation AI image failed", e instanceof Error ? e.message : e),
|
|
1976
|
+
return console.warn("[memi-board] moderation AI image failed", e instanceof Error ? e.message : e), c();
|
|
1683
1977
|
}
|
|
1684
1978
|
}
|
|
1685
1979
|
return {
|
|
@@ -1689,7 +1983,7 @@ function Ht() {
|
|
|
1689
1983
|
}
|
|
1690
1984
|
//#endregion
|
|
1691
1985
|
//#region src/composables/useMemiBoardUsers.ts
|
|
1692
|
-
var
|
|
1986
|
+
var pn = [
|
|
1693
1987
|
{
|
|
1694
1988
|
value: "admin",
|
|
1695
1989
|
label: "관리자",
|
|
@@ -1706,11 +2000,11 @@ var Ut = [
|
|
|
1706
2000
|
description: "글과 댓글을 작성하고 본인이 작성한 콘텐츠만 수정·삭제할 수 있습니다."
|
|
1707
2001
|
}
|
|
1708
2002
|
];
|
|
1709
|
-
function
|
|
1710
|
-
let t = () =>
|
|
2003
|
+
function mn(e = {}) {
|
|
2004
|
+
let t = () => _(), n = Q(), r = M(() => F(e.enabled) ?? !0 ? h(n, t()) : null), i = se(r, { ssrKey: w(t(), "users-management") });
|
|
1711
2005
|
async function a(e, r) {
|
|
1712
|
-
if (!
|
|
1713
|
-
await J(
|
|
2006
|
+
if (!pn.some((e) => e.value === r)) throw Error("지원하지 않는 역할입니다.");
|
|
2007
|
+
await J(x(n, t(), e), {
|
|
1714
2008
|
role: r,
|
|
1715
2009
|
updatedAt: G()
|
|
1716
2010
|
});
|
|
@@ -1723,19 +2017,239 @@ function Wt(e = {}) {
|
|
|
1723
2017
|
}
|
|
1724
2018
|
//#endregion
|
|
1725
2019
|
//#region src/composables/useMemiBoardUserProfile.ts
|
|
1726
|
-
function
|
|
1727
|
-
let t =
|
|
2020
|
+
function hn(e) {
|
|
2021
|
+
let t = Q(), n = () => _(), r = M(() => F(e)), i = M(() => {
|
|
1728
2022
|
let e = r.value;
|
|
1729
|
-
return e ?
|
|
1730
|
-
}), { data: a, pending: o } =
|
|
2023
|
+
return e ? x(t, n(), e) : null;
|
|
2024
|
+
}), { data: a, pending: o } = ce(i);
|
|
1731
2025
|
return {
|
|
1732
2026
|
profile: a,
|
|
1733
2027
|
profilePending: o
|
|
1734
2028
|
};
|
|
1735
2029
|
}
|
|
1736
2030
|
//#endregion
|
|
2031
|
+
//#region src/utils/section.ts
|
|
2032
|
+
var gn = [
|
|
2033
|
+
3,
|
|
2034
|
+
4,
|
|
2035
|
+
5,
|
|
2036
|
+
6,
|
|
2037
|
+
7,
|
|
2038
|
+
8,
|
|
2039
|
+
9,
|
|
2040
|
+
12
|
|
2041
|
+
], _n = [
|
|
2042
|
+
{
|
|
2043
|
+
value: "list",
|
|
2044
|
+
label: "게시목록"
|
|
2045
|
+
},
|
|
2046
|
+
{
|
|
2047
|
+
value: "post",
|
|
2048
|
+
label: "게시물"
|
|
2049
|
+
},
|
|
2050
|
+
{
|
|
2051
|
+
value: "comments",
|
|
2052
|
+
label: "댓글모음"
|
|
2053
|
+
}
|
|
2054
|
+
], vn = [{
|
|
2055
|
+
value: "latest",
|
|
2056
|
+
label: "최신순"
|
|
2057
|
+
}, {
|
|
2058
|
+
value: "likes",
|
|
2059
|
+
label: "좋아요순"
|
|
2060
|
+
}];
|
|
2061
|
+
function yn(e) {
|
|
2062
|
+
return e === "list" ? 5 : 4;
|
|
2063
|
+
}
|
|
2064
|
+
function bn(e, t) {
|
|
2065
|
+
let n = Math.floor(Number(t));
|
|
2066
|
+
return Number.isFinite(n) ? Math.min(e === "list" ? 20 : 10, Math.max(1, n)) : yn(e);
|
|
2067
|
+
}
|
|
2068
|
+
function xn(e) {
|
|
2069
|
+
return e === 12 ? null : 12 - e;
|
|
2070
|
+
}
|
|
2071
|
+
function Sn(e) {
|
|
2072
|
+
return {
|
|
2073
|
+
3: "col-span-12 md:col-span-3",
|
|
2074
|
+
4: "col-span-12 md:col-span-4",
|
|
2075
|
+
5: "col-span-12 md:col-span-5",
|
|
2076
|
+
6: "col-span-12 md:col-span-6",
|
|
2077
|
+
7: "col-span-12 md:col-span-7",
|
|
2078
|
+
8: "col-span-12 md:col-span-8",
|
|
2079
|
+
9: "col-span-12 md:col-span-9",
|
|
2080
|
+
12: "col-span-12"
|
|
2081
|
+
}[e];
|
|
2082
|
+
}
|
|
2083
|
+
function Cn(e) {
|
|
2084
|
+
return e === "sm" ? "min-h-40" : e === "lg" ? "min-h-80" : "min-h-56";
|
|
2085
|
+
}
|
|
2086
|
+
function wn(e) {
|
|
2087
|
+
let t = e.boardId || e.category || "free";
|
|
2088
|
+
return e.slug ? `/board/${t}/${e.slug}` : `/board/${t}`;
|
|
2089
|
+
}
|
|
2090
|
+
function Tn(e) {
|
|
2091
|
+
return e.kind === "comments" ? e.sort === "likes" ? "/boardComments?sort=likes" : "/boardComments" : e.kind === "list" && e.boardId ? `/board/${e.boardId}` : "/board";
|
|
2092
|
+
}
|
|
2093
|
+
function En(e) {
|
|
2094
|
+
if (!(!e.boardId || !e.postId)) return `/board/${e.boardId}/post/${e.postId}`;
|
|
2095
|
+
}
|
|
2096
|
+
function Dn(e) {
|
|
2097
|
+
return e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
|
|
2098
|
+
}
|
|
2099
|
+
function On(e) {
|
|
2100
|
+
return e.title?.trim() || e.summary || "게시물";
|
|
2101
|
+
}
|
|
2102
|
+
function kn(e) {
|
|
2103
|
+
return e === "image" || e === "video" ? "media" : "list";
|
|
2104
|
+
}
|
|
2105
|
+
function An(e) {
|
|
2106
|
+
return kn(e) === "media" ? "미디어" : "목록";
|
|
2107
|
+
}
|
|
2108
|
+
function jn(e, t) {
|
|
2109
|
+
return e ? t(e)?.listView ?? "default" : "default";
|
|
2110
|
+
}
|
|
2111
|
+
function Mn(e, t) {
|
|
2112
|
+
let n = e.map((e) => jn(e.boardId, t));
|
|
2113
|
+
if (!n.length) return "default";
|
|
2114
|
+
if (n.every((e) => e === n[0])) return n[0];
|
|
2115
|
+
let r = n.filter((e) => e === "image").length, i = n.filter((e) => e === "video").length;
|
|
2116
|
+
return r >= n.length / 2 ? "image" : i >= n.length / 2 ? "video" : "default";
|
|
2117
|
+
}
|
|
2118
|
+
//#endregion
|
|
2119
|
+
//#region src/composables/useMemiBoardSections.ts
|
|
2120
|
+
var Nn = [];
|
|
2121
|
+
function Pn(e) {
|
|
2122
|
+
let t = Number(e.cols);
|
|
2123
|
+
if (gn.includes(t)) return t;
|
|
2124
|
+
let n = e.width;
|
|
2125
|
+
return n === "xs" ? 3 : n === "sm" ? 4 : n === "lg" || Number(e.span) === 2 ? 12 : 6;
|
|
2126
|
+
}
|
|
2127
|
+
function Fn(e) {
|
|
2128
|
+
let t = e.height ?? e.size;
|
|
2129
|
+
return t === "sm" || t === "lg" ? t : "md";
|
|
2130
|
+
}
|
|
2131
|
+
function In(e) {
|
|
2132
|
+
return e.kind === "post" || e.kind === "comments" ? e.kind : "list";
|
|
2133
|
+
}
|
|
2134
|
+
function Ln(e) {
|
|
2135
|
+
return e.sort === "likes" ? "likes" : "latest";
|
|
2136
|
+
}
|
|
2137
|
+
function Rn(e) {
|
|
2138
|
+
return (typeof e.boardId == "string" ? e.boardId.trim() : "") || null;
|
|
2139
|
+
}
|
|
2140
|
+
function zn(e, t) {
|
|
2141
|
+
let n = Array.isArray(e.postIds) ? e.postIds : [], r = [];
|
|
2142
|
+
for (let e of n) {
|
|
2143
|
+
let n = String(e || "").trim();
|
|
2144
|
+
if (!(!n || r.includes(n)) && (r.push(n), r.length >= t)) break;
|
|
2145
|
+
}
|
|
2146
|
+
return r;
|
|
2147
|
+
}
|
|
2148
|
+
function Bn(e, t) {
|
|
2149
|
+
let n = In(e), r = bn(n, e.count);
|
|
2150
|
+
return {
|
|
2151
|
+
id: String(e.id || `box-${t + 1}`),
|
|
2152
|
+
title: String(e.title || `섹션 ${t + 1}`),
|
|
2153
|
+
showTitle: e.showTitle !== !1,
|
|
2154
|
+
kind: n,
|
|
2155
|
+
boardId: Rn(e),
|
|
2156
|
+
count: r,
|
|
2157
|
+
postIds: zn(e, r),
|
|
2158
|
+
sort: Ln(e),
|
|
2159
|
+
cols: Pn(e),
|
|
2160
|
+
height: Fn(e),
|
|
2161
|
+
order: t
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
function Vn() {
|
|
2165
|
+
let e = Q(), t = re(e, "settings", "homeLayout"), { data: n, pending: r } = ce(t), i = M(() => (Array.isArray(n.value?.blocks) ? n.value.blocks : Nn).map((e, t) => Bn(e, t)));
|
|
2166
|
+
async function a(e) {
|
|
2167
|
+
await K(t, {
|
|
2168
|
+
columns: 12,
|
|
2169
|
+
blocks: e.map((e, t) => {
|
|
2170
|
+
let n = e, r = In(n), i = bn(r, e.count);
|
|
2171
|
+
return {
|
|
2172
|
+
id: e.id,
|
|
2173
|
+
title: e.title.trim() || `섹션 ${t + 1}`,
|
|
2174
|
+
showTitle: e.showTitle !== !1,
|
|
2175
|
+
kind: r,
|
|
2176
|
+
boardId: Rn(n),
|
|
2177
|
+
count: i,
|
|
2178
|
+
postIds: zn(n, i),
|
|
2179
|
+
sort: Ln(n),
|
|
2180
|
+
cols: Pn(n),
|
|
2181
|
+
height: Fn(n),
|
|
2182
|
+
order: t
|
|
2183
|
+
};
|
|
2184
|
+
}),
|
|
2185
|
+
updatedAt: G()
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
return {
|
|
2189
|
+
settings: n,
|
|
2190
|
+
pending: r,
|
|
2191
|
+
sections: i,
|
|
2192
|
+
blocks: i,
|
|
2193
|
+
hasSavedLayout: M(() => Array.isArray(n.value?.blocks)),
|
|
2194
|
+
saveSections: a,
|
|
2195
|
+
saveBlocks: a
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
//#endregion
|
|
2199
|
+
//#region src/composables/useMemiBoardMiniPosts.ts
|
|
2200
|
+
function Hn(e, t) {
|
|
2201
|
+
let n = Q(), r = () => _(), i = M(() => {
|
|
2202
|
+
if (!import.meta.client) return null;
|
|
2203
|
+
let i = Math.max(1, Math.floor(Number(F(t)) || 5)), a = F(e)?.trim() || "", o = [Y("isPublished", "==", !0), Y("listed", "==", !0)];
|
|
2204
|
+
return a && o.unshift(Y("boardId", "==", a)), W(c(n, r()), ...o, U("createdAt", "desc"), V(i));
|
|
2205
|
+
}), { data: a, pending: o, error: s } = se(i);
|
|
2206
|
+
return {
|
|
2207
|
+
posts: M(() => a.value ?? []),
|
|
2208
|
+
pending: o,
|
|
2209
|
+
error: s
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
function Un(e) {
|
|
2213
|
+
let t = Q(), n = () => _(), r = M(() => [...new Set(F(e).map((e) => e.trim()).filter(Boolean))].slice(0, 10)), i = M(() => !import.meta.client || !r.value.length ? null : W(c(t, n()), Y(L(), "in", r.value))), { data: a, pending: o, error: s } = se(i);
|
|
2214
|
+
return {
|
|
2215
|
+
posts: M(() => {
|
|
2216
|
+
let e = new Map((a.value ?? []).map((e) => [e.id, e])), t = [];
|
|
2217
|
+
for (let n of r.value) {
|
|
2218
|
+
let r = e.get(n);
|
|
2219
|
+
r && r.isPublished !== !1 && t.push(r);
|
|
2220
|
+
}
|
|
2221
|
+
return t;
|
|
2222
|
+
}),
|
|
2223
|
+
pending: o,
|
|
2224
|
+
error: s
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
//#endregion
|
|
1737
2228
|
//#region src/data/versionHistory.ts
|
|
1738
|
-
var
|
|
2229
|
+
var Wn = [
|
|
2230
|
+
{
|
|
2231
|
+
version: "0.36.0",
|
|
2232
|
+
date: "2026-08-19",
|
|
2233
|
+
highlights: ["게시물을 신고할 수 있어요. 담당 스태프가 내용을 확인해요", "스태프는 지정된 게시판만 관리해요"],
|
|
2234
|
+
note: "호스트는 Firestore 신고 규칙·인덱스를 배포해야 해요."
|
|
2235
|
+
},
|
|
2236
|
+
{
|
|
2237
|
+
version: "0.35.0",
|
|
2238
|
+
date: "2026-08-19",
|
|
2239
|
+
highlights: ["메인에 게시목록·게시물·댓글모음 섹션을 올릴 수 있어요", "댓글모음은 최신순이나 좋아요순으로 네 개부터 보여요"]
|
|
2240
|
+
},
|
|
2241
|
+
{
|
|
2242
|
+
version: "0.34.0",
|
|
2243
|
+
date: "2026-08-19",
|
|
2244
|
+
highlights: ["댓글을 최신순이나 좋아요순으로 모아 볼 수 있어요"],
|
|
2245
|
+
note: "호스트는 좋아요순 목록용 Firestore 인덱스(likeCount + createdAt)를 배포해야 해요."
|
|
2246
|
+
},
|
|
2247
|
+
{
|
|
2248
|
+
version: "0.33.0",
|
|
2249
|
+
date: "2026-08-19",
|
|
2250
|
+
highlights: ["댓글과 답글에도 좋아요를 누를 수 있어요", "글을 지우면 댓글 좋아요도 함께 삭제돼요"],
|
|
2251
|
+
note: "호스트는 Firestore 규칙과 좋아요 인덱스를 병합·배포해야 댓글 좋아요가 동작해요."
|
|
2252
|
+
},
|
|
1739
2253
|
{
|
|
1740
2254
|
version: "0.32.0",
|
|
1741
2255
|
date: "2026-08-14",
|
|
@@ -1945,7 +2459,7 @@ var Kt = [
|
|
|
1945
2459
|
highlights: ["글쓰기·댓글·첨부·로그인 권한으로 게시판을 바로 시작할 수 있어요", "부적절한 글·댓글은 AI 검열로 막을 수 있어요"],
|
|
1946
2460
|
note: "실제 보안은 호스트 Firebase 규칙 등록이 필요합니다."
|
|
1947
2461
|
}
|
|
1948
|
-
],
|
|
2462
|
+
], Gn = /* @__PURE__ */ i(((e, t) => {
|
|
1949
2463
|
(function(n, r) {
|
|
1950
2464
|
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();
|
|
1951
2465
|
})(e, (function() {
|
|
@@ -2239,7 +2753,7 @@ var Kt = [
|
|
|
2239
2753
|
return w(1e3 * e);
|
|
2240
2754
|
}, w.en = b[y], w.Ls = b, w.p = {}, w;
|
|
2241
2755
|
}));
|
|
2242
|
-
})),
|
|
2756
|
+
})), Kn = /* @__PURE__ */ i(((e, t) => {
|
|
2243
2757
|
(function(n, r) {
|
|
2244
2758
|
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();
|
|
2245
2759
|
})(e, (function() {
|
|
@@ -2343,9 +2857,9 @@ var Kt = [
|
|
|
2343
2857
|
};
|
|
2344
2858
|
};
|
|
2345
2859
|
}));
|
|
2346
|
-
})),
|
|
2860
|
+
})), qn = /* @__PURE__ */ i(((e, t) => {
|
|
2347
2861
|
(function(n, r) {
|
|
2348
|
-
typeof e == "object" && t !== void 0 ? t.exports = r(
|
|
2862
|
+
typeof e == "object" && t !== void 0 ? t.exports = r(Gn()) : typeof define == "function" && define.amd ? define(["dayjs"], r) : (n = typeof globalThis < "u" ? globalThis : n || self).dayjs_locale_ko = r(n.dayjs);
|
|
2349
2863
|
})(e, (function(e) {
|
|
2350
2864
|
function t(e) {
|
|
2351
2865
|
return e && typeof e == "object" && "default" in e ? e : { default: e };
|
|
@@ -2393,24 +2907,24 @@ var Kt = [
|
|
|
2393
2907
|
};
|
|
2394
2908
|
return n.default.locale(r, null, !0), r;
|
|
2395
2909
|
}));
|
|
2396
|
-
})),
|
|
2397
|
-
|
|
2398
|
-
function
|
|
2910
|
+
})), Jn = /* @__PURE__ */ a(Gn(), 1), Yn = /* @__PURE__ */ a(Kn(), 1);
|
|
2911
|
+
qn(), Jn.default.extend(Yn.default), Jn.default.locale("ko");
|
|
2912
|
+
function Xn(e) {
|
|
2399
2913
|
return e?.toDate ? e.toDate().toLocaleDateString("ko-KR") : "";
|
|
2400
2914
|
}
|
|
2401
|
-
function
|
|
2402
|
-
return e?.toDate ? (0,
|
|
2915
|
+
function Zn(e, t = Date.now()) {
|
|
2916
|
+
return e?.toDate ? (0, Jn.default)(e.toDate()).from((0, Jn.default)(t)) : "방금 전";
|
|
2403
2917
|
}
|
|
2404
|
-
function
|
|
2405
|
-
return e?.toDate ? (0,
|
|
2918
|
+
function Qn(e) {
|
|
2919
|
+
return e?.toDate ? (0, Jn.default)(e.toDate()).format("YYYY년 M월 D일 HH:mm:ss") : "시간 확인 중";
|
|
2406
2920
|
}
|
|
2407
|
-
function
|
|
2408
|
-
let n =
|
|
2409
|
-
return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${
|
|
2921
|
+
function $n(e, t) {
|
|
2922
|
+
let n = Qn(e), r = e?.toMillis?.(), i = t?.toMillis?.();
|
|
2923
|
+
return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${Qn(t)}`];
|
|
2410
2924
|
}
|
|
2411
2925
|
//#endregion
|
|
2412
2926
|
//#region src/composables/useMemiBoardWritingAssistant.ts
|
|
2413
|
-
var
|
|
2927
|
+
var er = 10, tr = 864e5, nr = {
|
|
2414
2928
|
proofread: "맞춤법, 띄어쓰기, 문법 오류만 고쳐라. 의미와 말투는 바꾸지 마라.",
|
|
2415
2929
|
polish: "뜻과 작성자의 말투를 유지하면서 더 자연스럽고 읽기 좋은 문장으로 다듬어라.",
|
|
2416
2930
|
continue: "앞의 맥락과 말투에 맞는 내용을 과장 없이 한두 문단 이어서 작성하라.",
|
|
@@ -2419,23 +2933,23 @@ var nn = 10, rn = 864e5, an = {
|
|
|
2419
2933
|
"translate-ko": "자연스러운 한국어로 번역하라. 고유명사와 기술 용어는 정확히 유지하라.",
|
|
2420
2934
|
title: "본문을 대표하는 간결한 한국어 제목 하나를 만들어라. 따옴표나 설명 없이 제목만 출력하라."
|
|
2421
2935
|
};
|
|
2422
|
-
function
|
|
2936
|
+
function rr(e) {
|
|
2423
2937
|
return e.trim().replace(/^```(?:html|text)?\s*/i, "").replace(/\s*```$/, "").trim();
|
|
2424
2938
|
}
|
|
2425
|
-
function
|
|
2939
|
+
function ir(e) {
|
|
2426
2940
|
return !e || typeof e != "object" ? null : "toMillis" in e && typeof e.toMillis == "function" ? e.toMillis() : null;
|
|
2427
2941
|
}
|
|
2428
|
-
function
|
|
2429
|
-
let e =
|
|
2942
|
+
function ar() {
|
|
2943
|
+
let e = le(), t = Q(), n = Z(), r = u();
|
|
2430
2944
|
async function i() {
|
|
2431
2945
|
let e = n.value?.uid;
|
|
2432
2946
|
if (!e) throw Error("AI 글쓰기 도우미는 로그인 후 사용할 수 있습니다.");
|
|
2433
|
-
await
|
|
2434
|
-
let r =
|
|
2947
|
+
await oe(t, async (n) => {
|
|
2948
|
+
let r = x(t, _(), e), i = await n.get(r);
|
|
2435
2949
|
if (!i.exists()) throw Error("사용자 정보를 확인한 뒤 다시 시도해 주세요.");
|
|
2436
|
-
let a = i.data(), o =
|
|
2950
|
+
let a = i.data(), o = ir(a.aiWritingWindowAt), s = Math.max(0, Math.floor(Number(a.aiWritingCount) || 0)), c = Date.now(), l = o != null && c - o < tr;
|
|
2437
2951
|
if (l && s >= 10) {
|
|
2438
|
-
let e = o +
|
|
2952
|
+
let e = o + tr, t = Math.max(1, Math.ceil((e - c) / 36e5));
|
|
2439
2953
|
throw Error(`AI 글쓰기 도우미는 하루 10회까지 사용할 수 있습니다. 약 ${t}시간 후 다시 이용해 주세요.`);
|
|
2440
2954
|
}
|
|
2441
2955
|
n.update(r, {
|
|
@@ -2447,10 +2961,10 @@ function cn() {
|
|
|
2447
2961
|
}
|
|
2448
2962
|
async function a(t) {
|
|
2449
2963
|
await i();
|
|
2450
|
-
let n = r.moderation ?? {}, a =
|
|
2451
|
-
backend: new
|
|
2964
|
+
let n = r.moderation ?? {}, a = we(e, {
|
|
2965
|
+
backend: new Se(),
|
|
2452
2966
|
useLimitedUseAppCheckTokens: n.useLimitedUseAppCheckTokens !== !1
|
|
2453
|
-
}), o =
|
|
2967
|
+
}), o = Te(a, {
|
|
2454
2968
|
model: n.model ?? "gemini-3.5-flash-lite",
|
|
2455
2969
|
systemInstruction: [
|
|
2456
2970
|
"너는 게시판 글쓰기 편집 도우미다.",
|
|
@@ -2464,10 +2978,10 @@ function cn() {
|
|
|
2464
2978
|
}), s = t.content.trim();
|
|
2465
2979
|
if (!s) throw Error("AI로 다듬을 내용을 먼저 입력해 주세요.");
|
|
2466
2980
|
let c = [
|
|
2467
|
-
`작업: ${
|
|
2981
|
+
`작업: ${nr[t.action]}`,
|
|
2468
2982
|
t.title ? `현재 제목: ${t.title}` : "",
|
|
2469
2983
|
`본문:\n${s.slice(0, 12e3)}`
|
|
2470
|
-
].filter(Boolean).join("\n\n"), l =
|
|
2984
|
+
].filter(Boolean).join("\n\n"), l = rr((await o.generateContent(c)).response.text());
|
|
2471
2985
|
if (!l) throw Error("AI가 결과를 만들지 못했습니다. 다시 시도해 주세요.");
|
|
2472
2986
|
return l;
|
|
2473
2987
|
}
|
|
@@ -2475,14 +2989,14 @@ function cn() {
|
|
|
2475
2989
|
}
|
|
2476
2990
|
//#endregion
|
|
2477
2991
|
//#region src/utils/renderMarkdown.ts
|
|
2478
|
-
function
|
|
2992
|
+
function or(e) {
|
|
2479
2993
|
return e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2480
2994
|
}
|
|
2481
|
-
function
|
|
2995
|
+
function sr(e) {
|
|
2482
2996
|
let t = e;
|
|
2483
2997
|
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;
|
|
2484
2998
|
}
|
|
2485
|
-
function
|
|
2999
|
+
function cr(e) {
|
|
2486
3000
|
if (!e?.trim()) return "";
|
|
2487
3001
|
let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = 0, i = !1, a = [], o = null, s = [];
|
|
2488
3002
|
function c() {
|
|
@@ -2491,7 +3005,7 @@ function un(e) {
|
|
|
2491
3005
|
return;
|
|
2492
3006
|
}
|
|
2493
3007
|
let e = o;
|
|
2494
|
-
n.push(`<${e}>${s.map((e) => `<li>${
|
|
3008
|
+
n.push(`<${e}>${s.map((e) => `<li>${sr(e)}</li>`).join("")}</${e}>`), o = null, s = [];
|
|
2495
3009
|
}
|
|
2496
3010
|
for (; r < t.length;) {
|
|
2497
3011
|
let e = t[r] ?? "", l = e.match(/^```(\w*)\s*$/);
|
|
@@ -2500,7 +3014,7 @@ function un(e) {
|
|
|
2500
3014
|
continue;
|
|
2501
3015
|
}
|
|
2502
3016
|
if (i) {
|
|
2503
|
-
a.push(
|
|
3017
|
+
a.push(or(e)), r++;
|
|
2504
3018
|
continue;
|
|
2505
3019
|
}
|
|
2506
3020
|
if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(e)) {
|
|
@@ -2511,24 +3025,24 @@ function un(e) {
|
|
|
2511
3025
|
if (u) {
|
|
2512
3026
|
c();
|
|
2513
3027
|
let e = u[1].length;
|
|
2514
|
-
n.push(`<h${e} class="font-semibold mt-4 mb-2">${
|
|
3028
|
+
n.push(`<h${e} class="font-semibold mt-4 mb-2">${sr(or(u[2]))}</h${e}>`), r++;
|
|
2515
3029
|
continue;
|
|
2516
3030
|
}
|
|
2517
3031
|
if (e.startsWith("> ")) {
|
|
2518
3032
|
c();
|
|
2519
3033
|
let e = [];
|
|
2520
|
-
for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push(
|
|
2521
|
-
n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${
|
|
3034
|
+
for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push(or((t[r] ?? "").slice(2))), r++;
|
|
3035
|
+
n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${sr(e.join("<br />"))}</blockquote>`);
|
|
2522
3036
|
continue;
|
|
2523
3037
|
}
|
|
2524
3038
|
let d = e.match(/^[-*+]\s+(.+)$/);
|
|
2525
3039
|
if (d) {
|
|
2526
|
-
o && o !== "ul" && c(), o = "ul", s.push(
|
|
3040
|
+
o && o !== "ul" && c(), o = "ul", s.push(or(d[1])), r++;
|
|
2527
3041
|
continue;
|
|
2528
3042
|
}
|
|
2529
3043
|
let f = e.match(/^\d+\.\s+(.+)$/);
|
|
2530
3044
|
if (f) {
|
|
2531
|
-
o && o !== "ol" && c(), o = "ol", s.push(
|
|
3045
|
+
o && o !== "ol" && c(), o = "ol", s.push(or(f[1])), r++;
|
|
2532
3046
|
continue;
|
|
2533
3047
|
}
|
|
2534
3048
|
if (!e.trim()) {
|
|
@@ -2540,13 +3054,13 @@ function un(e) {
|
|
|
2540
3054
|
for (; r < t.length;) {
|
|
2541
3055
|
let e = t[r] ?? "";
|
|
2542
3056
|
if (!e.trim() || /^(#{1,3}\s|```|[-*+]\s|\d+\.\s|> |(-{3,}|\*{3,})\s*$)/.test(e)) break;
|
|
2543
|
-
p.push(
|
|
3057
|
+
p.push(or(e)), r++;
|
|
2544
3058
|
}
|
|
2545
|
-
n.push(`<p class="my-2 leading-relaxed">${
|
|
3059
|
+
n.push(`<p class="my-2 leading-relaxed">${sr(p.join("<br />"))}</p>`);
|
|
2546
3060
|
}
|
|
2547
3061
|
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");
|
|
2548
3062
|
}
|
|
2549
3063
|
//#endregion
|
|
2550
|
-
export {
|
|
3064
|
+
export { Rt as AUTHOR_MEMO_MAX_LENGTH, gn as BOARD_SECTION_COLS, _n as BOARD_SECTION_KINDS, vn as BOARD_SECTION_SORTS, pn as BOARD_USER_ROLES, Ut as COMMENT_BODY_MAX_LENGTH, Ke as DEFAULT_BLOCK_BAN_DECAY_MS, Ge as DEFAULT_BLOCK_BAN_THRESHOLD, xt as DEFAULT_BOARDS, St as DEFAULT_CATEGORIES, C as DEFAULT_COMMENTS_COLLECTION, E as DEFAULT_LIKES_COLLECTION, f as DEFAULT_POSTS_COLLECTION, b as DEFAULT_REPORTS_COLLECTION, D as DEFAULT_SETTINGS_COLLECTION, p as DEFAULT_USERS_COLLECTION, Xt as REPORT_DETAIL_MAX_LENGTH, Zt as REPORT_REASONS, er as WRITING_ASSISTANT_DAILY_LIMIT, Ee as asHttpUrl, Me as boardListOgDescription, je as boardListOgTitle, Ae as boardPostOgDescription, ke as boardPostOgTitle, w as boardSsrKey, x as boardUserDoc, h as boardUsersCol, bt as buildPostPreview, Ve as canManageBoardByRole, bn as clampSectionCount, l as commentDoc, O as commentsCol, T as configureMemiBoard, yn as defaultSectionCount, qe as effectiveModerationBlockCount, tt as extractEditorImageUrls, Re as fetchPublicListForSeo, Le as fetchPublicPostForSeo, Xn as formatDate, Qn as formatFullDate, Zn as formatRelativeDate, Xe as formatRestrictedUntilLabel, $n as formatTimestampDetails, ht as hasBodyImage, mt as hasBodyText, Be as isAssignedStaff, gt as isContentEmpty, Et as isFirestorePermissionDenied, Ye as isModerationWriteRestricted, j as likeDoc, d as likeDocId, A as likesCol, Jt as memiBoardCommentLikesKey, jn as miniBoardView, En as miniCommentLink, Tn as miniMoreLink, Dn as miniPostImage, wn as miniPostLink, On as miniPostTitle, Mn as miniPostsView, kn as miniViewGroup, An as miniViewLabel, Je as moderationWriteRestrictedUntilMs, Ne as normalizeBasePath, Bn as normalizeBoardSection, xn as pairSectionCols, ft as plainTextFromHtml, s as postBodyDoc, t as postDoc, rt as postNamespaceFromStoragePath, k as postStorageFolder, c as postsCol, cr as renderMarkdownToHtml, r as reportDoc, n as reportDocId, Qt as reportReasonLabel, o as reportsCol, y as resolveBoardPathConfig, Pe as resolvePublicSeoDb, Sn as sectionColClass, Cn as sectionMinHeight, e as settingsCol, v as settingsDoc, S as slugify, nt as storagePathFromDownloadUrl, pt as titleFromBody, Oe as toAbsoluteUrl, De as toBoardOgImageUrl, _ as useBoardPathConfig, et as useMemiBoardAuth, Bt as useMemiBoardAuthorMemo, Lt as useMemiBoardCommentFeed, Yt as useMemiBoardCommentLikes, Gt as useMemiBoardComments, u as useMemiBoardConfig, qt as useMemiBoardLikes, Hn as useMemiBoardMiniLatest, Un as useMemiBoardMiniPicked, fn as useMemiBoardModeration, jt as useMemiBoardPost, At as useMemiBoardPostList, Ot as useMemiBoardPosts, Kt as useMemiBoardReplies, tn as useMemiBoardReportQueue, $t as useMemiBoardReports, Vn as useMemiBoardSections, Tt as useMemiBoardSettings, Ft as useMemiBoardUserComments, Nt as useMemiBoardUserPosts, hn as useMemiBoardUserProfile, mn as useMemiBoardUsers, nn as useMemiBoardViews, ar as useMemiBoardWritingAssistant, Wn as versionHistory, yt as videoListCoverUrl, vt as youtubeId };
|
|
2551
3065
|
|
|
2552
3066
|
//# sourceMappingURL=index.js.map
|