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