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