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