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