memi-board 0.4.0 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/composables/useMemiBoardModeration.d.ts +2 -5
- package/dist/composables/useMemiBoardPosts.d.ts +2 -0
- package/dist/composables/useMemiBoardSettings.d.ts +10 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +271 -159
- package/dist/index.js.map +1 -1
- package/dist/module.mjs +28 -15
- package/dist/module.mjs.map +1 -1
- package/dist/runtime/components/CommentList.vue +1 -0
- package/dist/runtime/components/Editor.vue +233 -26
- package/dist/runtime/components/List.vue +56 -9
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,43 +1,48 @@
|
|
|
1
1
|
import { computed as e, reactive as t, ref as n, watch as r } from "vue";
|
|
2
2
|
import { useCollection as i, useCurrentUser as a, useDocument as o, useFirebaseApp as s, useFirebaseAuth as c, useFirestore as l } from "vuefire";
|
|
3
|
-
import { collection as u, deleteDoc as d,
|
|
4
|
-
import { GoogleAuthProvider as
|
|
5
|
-
import { deleteObject as
|
|
6
|
-
import { GoogleAIBackend as I, Schema as L, getAI as R, getGenerativeModel as
|
|
3
|
+
import { collection as u, deleteDoc as d, deleteField as f, doc as p, getDoc as m, getDocs as h, increment as g, limit as _, orderBy as v, query as y, serverTimestamp as b, setDoc as x, startAfter as S, updateDoc as C, where as w, writeBatch as T } from "firebase/firestore";
|
|
4
|
+
import { GoogleAuthProvider as E, OAuthProvider as D, createUserWithEmailAndPassword as O, signInWithEmailAndPassword as k, signInWithPopup as A, signOut as j, updateProfile as ee } from "firebase/auth";
|
|
5
|
+
import { deleteObject as M, getDownloadURL as N, getStorage as P, listAll as te, ref as F, uploadBytesResumable as ne } from "firebase/storage";
|
|
6
|
+
import { GoogleAIBackend as I, Schema as L, getAI as R, getGenerativeModel as re } from "firebase/ai";
|
|
7
7
|
//#region src/config.ts
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
8
|
+
var z = "__MEMI_BOARD_CONFIG__";
|
|
9
|
+
function B() {
|
|
10
|
+
let e = globalThis;
|
|
11
|
+
return e[z] || (e[z] = t({
|
|
12
|
+
collectionPrefix: "memiBoard",
|
|
13
|
+
auth: { providers: ["google", "apple"] },
|
|
14
|
+
moderation: {
|
|
15
|
+
enabled: !0,
|
|
16
|
+
model: "gemini-2.5-flash",
|
|
17
|
+
onError: "allow",
|
|
18
|
+
moderateImages: !1,
|
|
19
|
+
localBlocklist: []
|
|
20
|
+
}
|
|
21
|
+
})), e[z];
|
|
21
22
|
}
|
|
22
|
-
function
|
|
23
|
-
|
|
23
|
+
function ie(e) {
|
|
24
|
+
let t = B();
|
|
25
|
+
e.collectionPrefix && (t.collectionPrefix = e.collectionPrefix), e.auth && Object.assign(t.auth, e.auth), e.moderation && Object.assign(t.moderation, e.moderation);
|
|
26
|
+
}
|
|
27
|
+
function V() {
|
|
28
|
+
return B();
|
|
24
29
|
}
|
|
25
30
|
//#endregion
|
|
26
31
|
//#region src/composables/useMemiBoardAuth.ts
|
|
27
|
-
function
|
|
28
|
-
let t =
|
|
29
|
-
function
|
|
30
|
-
return
|
|
32
|
+
function H() {
|
|
33
|
+
let t = V(), i = l(), o = c(), s = a(), u = n("user"), d = n(!0);
|
|
34
|
+
function f(e) {
|
|
35
|
+
return p(i, `${t.collectionPrefix}Users`, e);
|
|
31
36
|
}
|
|
32
37
|
async function h(e) {
|
|
33
|
-
let t =
|
|
34
|
-
return n.exists() ? (await
|
|
38
|
+
let t = f(e.uid), n = await m(t);
|
|
39
|
+
return n.exists() ? (await C(t, {
|
|
35
40
|
displayName: e.displayName,
|
|
36
|
-
updatedAt:
|
|
37
|
-
}), n.data().role ?? "user") : (await
|
|
41
|
+
updatedAt: b()
|
|
42
|
+
}), n.data().role ?? "user") : (await x(t, {
|
|
38
43
|
role: "user",
|
|
39
44
|
displayName: e.displayName,
|
|
40
|
-
updatedAt:
|
|
45
|
+
updatedAt: b()
|
|
41
46
|
}), "user");
|
|
42
47
|
}
|
|
43
48
|
r(s, async (e) => {
|
|
@@ -58,22 +63,22 @@ function U() {
|
|
|
58
63
|
if (!o) throw Error("[memi-board] Firebase Auth가 초기화되지 않았습니다.");
|
|
59
64
|
return o;
|
|
60
65
|
}
|
|
61
|
-
async function
|
|
62
|
-
return
|
|
66
|
+
async function y() {
|
|
67
|
+
return A(v(), new E());
|
|
63
68
|
}
|
|
64
|
-
async function
|
|
65
|
-
let e = new
|
|
66
|
-
return e.addScope("email"), e.addScope("name"),
|
|
69
|
+
async function S() {
|
|
70
|
+
let e = new D("apple.com");
|
|
71
|
+
return e.addScope("email"), e.addScope("name"), A(v(), e);
|
|
67
72
|
}
|
|
68
|
-
async function
|
|
69
|
-
return
|
|
73
|
+
async function w(e, t) {
|
|
74
|
+
return k(v(), e, t);
|
|
70
75
|
}
|
|
71
|
-
async function
|
|
72
|
-
let r = await
|
|
76
|
+
async function T(e, t, n) {
|
|
77
|
+
let r = await O(v(), e, t);
|
|
73
78
|
return n && await ee(r.user, { displayName: n }), r;
|
|
74
79
|
}
|
|
75
80
|
async function M() {
|
|
76
|
-
await
|
|
81
|
+
await j(v());
|
|
77
82
|
}
|
|
78
83
|
function N(e) {
|
|
79
84
|
return g.value && (s.value?.uid === e.authorUid || _.value);
|
|
@@ -86,10 +91,10 @@ function U() {
|
|
|
86
91
|
isSignedIn: g,
|
|
87
92
|
isAdmin: _,
|
|
88
93
|
rolePending: d,
|
|
89
|
-
signInWithGoogle:
|
|
90
|
-
signInWithApple:
|
|
91
|
-
signInWithEmail:
|
|
92
|
-
signUpWithEmail:
|
|
94
|
+
signInWithGoogle: y,
|
|
95
|
+
signInWithApple: S,
|
|
96
|
+
signInWithEmail: w,
|
|
97
|
+
signUpWithEmail: T,
|
|
93
98
|
signOut: M,
|
|
94
99
|
canEdit: N,
|
|
95
100
|
canDelete: N,
|
|
@@ -98,29 +103,27 @@ function U() {
|
|
|
98
103
|
}
|
|
99
104
|
//#endregion
|
|
100
105
|
//#region src/utils/slugify.ts
|
|
101
|
-
function
|
|
106
|
+
function U(e) {
|
|
102
107
|
return e.trim().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9가-힣ㄱ-ㅎㅏ-ㅣ-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
103
108
|
}
|
|
104
|
-
function
|
|
109
|
+
function W(e, t = 60) {
|
|
105
110
|
return e.replace(/[^a-zA-Z0-9가-힣ㄱ-ㅎ._-]/g, "-").slice(0, t);
|
|
106
111
|
}
|
|
107
112
|
//#endregion
|
|
108
113
|
//#region src/composables/useMemiBoardPosts.ts
|
|
109
|
-
async function
|
|
110
|
-
let t = await
|
|
114
|
+
async function G(e) {
|
|
115
|
+
let t = await te(e).catch(() => ({
|
|
111
116
|
items: [],
|
|
112
117
|
prefixes: []
|
|
113
118
|
}));
|
|
114
|
-
await Promise.all([...t.items.map((e) =>
|
|
119
|
+
await Promise.all([...t.items.map((e) => M(e)), ...t.prefixes.map((e) => G(e))]);
|
|
115
120
|
}
|
|
116
|
-
function
|
|
117
|
-
let e =
|
|
118
|
-
async function
|
|
119
|
-
let t = e.pageSize ?? 20, n =
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
g(t + 1)
|
|
123
|
-
] : [_("createdAt", "desc"), g(t + 1)], r = await m(v(i(), ...n)), a = r.docs.slice(0, t);
|
|
121
|
+
function K() {
|
|
122
|
+
let e = V(), t = l(), n = s(), r = () => e.collectionPrefix, i = () => u(t, `${r()}Posts`), a = (e) => p(t, `${r()}Posts`, e), o = (e) => p(t, `${r()}Posts`, e, "body", "main"), c = (e) => u(t, `${r()}Posts`, e, "comments");
|
|
123
|
+
async function g(e = {}) {
|
|
124
|
+
let t = e.pageSize ?? 20, n = [];
|
|
125
|
+
e.category && n.push(w("category", "==", e.category)), n.push(v("createdAt", "desc")), e.cursor && n.push(S(e.cursor)), n.push(_(t + 1));
|
|
126
|
+
let r = await h(y(i(), ...n)), a = r.docs.slice(0, t);
|
|
124
127
|
return {
|
|
125
128
|
posts: a.map((e) => ({
|
|
126
129
|
id: e.id,
|
|
@@ -130,18 +133,18 @@ function q() {
|
|
|
130
133
|
hasMore: r.docs.length > t
|
|
131
134
|
};
|
|
132
135
|
}
|
|
133
|
-
async function
|
|
134
|
-
let [t, n] = await Promise.all([
|
|
136
|
+
async function E(e) {
|
|
137
|
+
let [t, n] = await Promise.all([m(a(e)), m(o(e))]);
|
|
135
138
|
return t.exists() ? {
|
|
136
139
|
id: t.id,
|
|
137
140
|
...t.data(),
|
|
138
141
|
content: n.exists() ? n.data().content ?? "" : ""
|
|
139
142
|
} : null;
|
|
140
143
|
}
|
|
141
|
-
async function
|
|
142
|
-
let t =
|
|
143
|
-
for (; (await
|
|
144
|
-
return await Promise.all([
|
|
144
|
+
async function D(e) {
|
|
145
|
+
let t = U(e.title) || "post", n = t, r = 1;
|
|
146
|
+
for (; (await m(a(n))).exists();) r++, n = `${t}-${r}`;
|
|
147
|
+
return await Promise.all([x(a(n), {
|
|
145
148
|
title: e.title,
|
|
146
149
|
tags: e.tags ?? [],
|
|
147
150
|
...e.category ? { category: e.category } : {},
|
|
@@ -152,65 +155,65 @@ function q() {
|
|
|
152
155
|
authorPhoto: e.authorPhoto,
|
|
153
156
|
moderationStatus: "approved",
|
|
154
157
|
moderationModel: e.moderationModel ?? null,
|
|
155
|
-
createdAt:
|
|
156
|
-
updatedAt:
|
|
157
|
-
}),
|
|
158
|
+
createdAt: b(),
|
|
159
|
+
updatedAt: b()
|
|
160
|
+
}), x(o(n), { content: e.content })]), n;
|
|
158
161
|
}
|
|
159
|
-
async function
|
|
160
|
-
await Promise.all([
|
|
162
|
+
async function O(e, t) {
|
|
163
|
+
await Promise.all([C(a(e), {
|
|
161
164
|
title: t.title,
|
|
162
165
|
tags: t.tags ?? [],
|
|
163
|
-
|
|
166
|
+
category: t.category ? t.category : f(),
|
|
164
167
|
attachments: t.attachments ?? [],
|
|
165
|
-
updatedAt:
|
|
166
|
-
}),
|
|
168
|
+
updatedAt: b()
|
|
169
|
+
}), x(o(e), { content: t.content }, { merge: !0 })]);
|
|
167
170
|
}
|
|
168
|
-
async function
|
|
169
|
-
let i = await
|
|
171
|
+
async function k(e) {
|
|
172
|
+
let i = await h(c(e));
|
|
170
173
|
for (let e = 0; e < i.docs.length; e += 450) {
|
|
171
|
-
let n =
|
|
174
|
+
let n = T(t);
|
|
172
175
|
i.docs.slice(e, e + 450).forEach((e) => n.delete(e.ref)), await n.commit();
|
|
173
176
|
}
|
|
174
|
-
let s =
|
|
175
|
-
await d(o(e)), await
|
|
177
|
+
let s = P(n);
|
|
178
|
+
await d(o(e)), await G(F(s, `${r()}/posts/${e}`)), await d(a(e));
|
|
176
179
|
}
|
|
177
180
|
return {
|
|
178
|
-
getPosts:
|
|
179
|
-
getPost:
|
|
180
|
-
createPost:
|
|
181
|
-
updatePost:
|
|
182
|
-
deletePost:
|
|
181
|
+
getPosts: g,
|
|
182
|
+
getPost: E,
|
|
183
|
+
createPost: D,
|
|
184
|
+
updatePost: O,
|
|
185
|
+
deletePost: k
|
|
183
186
|
};
|
|
184
187
|
}
|
|
185
188
|
//#endregion
|
|
186
189
|
//#region src/composables/useMemiBoardComments.ts
|
|
187
|
-
function
|
|
188
|
-
let n =
|
|
190
|
+
function q(t) {
|
|
191
|
+
let n = V(), r = l(), a = () => n.collectionPrefix, o = e(() => typeof t == "string" ? t : t.value), s = e(() => y(u(r, `${a()}Posts`, o.value, "comments"), v("createdAt", "asc"))), c = i(s);
|
|
189
192
|
async function d(e) {
|
|
190
|
-
let t =
|
|
191
|
-
t.set(
|
|
193
|
+
let t = T(r), n = a(), i = p(u(r, `${n}Posts`, o.value, "comments"));
|
|
194
|
+
t.set(i, {
|
|
192
195
|
postId: o.value,
|
|
193
196
|
body: e.body,
|
|
194
197
|
authorUid: e.authorUid,
|
|
195
198
|
authorName: e.authorName,
|
|
196
199
|
authorPhoto: e.authorPhoto,
|
|
197
200
|
moderationStatus: "approved",
|
|
198
|
-
createdAt:
|
|
199
|
-
}), t.update(
|
|
201
|
+
createdAt: b()
|
|
202
|
+
}), t.update(p(r, `${n}Posts`, o.value), { commentCount: g(1) }), await t.commit();
|
|
200
203
|
}
|
|
201
|
-
async function
|
|
202
|
-
let t =
|
|
203
|
-
t.delete(
|
|
204
|
+
async function f(e) {
|
|
205
|
+
let t = T(r), n = a();
|
|
206
|
+
t.delete(p(r, `${n}Posts`, o.value, "comments", e)), t.update(p(r, `${n}Posts`, o.value), { commentCount: g(-1) }), await t.commit();
|
|
204
207
|
}
|
|
205
208
|
return {
|
|
206
209
|
comments: c,
|
|
207
210
|
addComment: d,
|
|
208
|
-
deleteComment:
|
|
211
|
+
deleteComment: f
|
|
209
212
|
};
|
|
210
213
|
}
|
|
211
214
|
//#endregion
|
|
212
215
|
//#region src/utils/moderation-prompt.ts
|
|
213
|
-
var
|
|
216
|
+
var J = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\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- flagged=false면 category는 \"none\", reason은 \"\"", ae = [
|
|
214
217
|
"씨발",
|
|
215
218
|
"시발",
|
|
216
219
|
"씨팔",
|
|
@@ -234,10 +237,10 @@ var Y = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터
|
|
|
234
237
|
"bitch",
|
|
235
238
|
"asshole"
|
|
236
239
|
];
|
|
237
|
-
function
|
|
240
|
+
function oe(e) {
|
|
238
241
|
return new RegExp(e.join("|"), "i");
|
|
239
242
|
}
|
|
240
|
-
var
|
|
243
|
+
var se = /* @__PURE__ */ new Set([
|
|
241
244
|
"none",
|
|
242
245
|
"abuse",
|
|
243
246
|
"spam",
|
|
@@ -245,7 +248,7 @@ var ne = /* @__PURE__ */ new Set([
|
|
|
245
248
|
"violence",
|
|
246
249
|
"other"
|
|
247
250
|
]);
|
|
248
|
-
function
|
|
251
|
+
function Y(e) {
|
|
249
252
|
let t = e.trim();
|
|
250
253
|
if (!t) return null;
|
|
251
254
|
let n;
|
|
@@ -263,7 +266,7 @@ function Z(e) {
|
|
|
263
266
|
if (typeof n != "object" || !n) return null;
|
|
264
267
|
let r = n;
|
|
265
268
|
if (typeof r.flagged != "boolean") return null;
|
|
266
|
-
let i = typeof r.category == "string" &&
|
|
269
|
+
let i = typeof r.category == "string" && se.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
|
|
267
270
|
return {
|
|
268
271
|
flagged: r.flagged,
|
|
269
272
|
category: i,
|
|
@@ -272,85 +275,159 @@ function Z(e) {
|
|
|
272
275
|
}
|
|
273
276
|
//#endregion
|
|
274
277
|
//#region src/composables/useMemiBoardModeration.ts
|
|
275
|
-
var
|
|
278
|
+
var X = {
|
|
276
279
|
flagged: !1,
|
|
277
280
|
category: "none",
|
|
278
|
-
reason: ""
|
|
281
|
+
reason: "",
|
|
282
|
+
via: "empty"
|
|
279
283
|
};
|
|
280
|
-
function
|
|
281
|
-
|
|
284
|
+
function Z(e, t) {
|
|
285
|
+
console.info(`[memi-board:moderation] ${e}`, t);
|
|
286
|
+
}
|
|
287
|
+
function Q() {
|
|
288
|
+
let e = V().moderation ?? {}, t = s(), n = oe([...ae, ...e.localBlocklist ?? []]);
|
|
282
289
|
function r(e) {
|
|
283
|
-
return
|
|
290
|
+
return e.match(n) ? {
|
|
284
291
|
flagged: !0,
|
|
285
292
|
category: "abuse",
|
|
286
|
-
reason: "욕설·비속어가 포함되어 게시할 수 없습니다. 표현을 바꿔 주세요."
|
|
293
|
+
reason: "욕설·비속어가 포함되어 게시할 수 없습니다. 표현을 바꿔 주세요.",
|
|
294
|
+
via: "local"
|
|
287
295
|
} : null;
|
|
288
296
|
}
|
|
289
297
|
function i() {
|
|
290
|
-
let n = R(t, {
|
|
298
|
+
let n = e.model ?? "gemini-2.5-flash", r = R(t, {
|
|
291
299
|
backend: new I(),
|
|
292
300
|
useLimitedUseAppCheckTokens: !0
|
|
293
301
|
});
|
|
294
|
-
return
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
302
|
+
return {
|
|
303
|
+
modelName: n,
|
|
304
|
+
model: re(r, {
|
|
305
|
+
model: n,
|
|
306
|
+
systemInstruction: J,
|
|
307
|
+
generationConfig: {
|
|
308
|
+
temperature: 0,
|
|
309
|
+
maxOutputTokens: 256,
|
|
310
|
+
responseMimeType: "application/json",
|
|
311
|
+
responseSchema: L.object({ properties: {
|
|
312
|
+
flagged: L.boolean(),
|
|
313
|
+
category: L.string(),
|
|
314
|
+
reason: L.string()
|
|
315
|
+
} })
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
};
|
|
308
319
|
}
|
|
309
320
|
function a() {
|
|
310
321
|
return e.onError === "block" ? {
|
|
311
322
|
flagged: !0,
|
|
312
323
|
category: "other",
|
|
313
324
|
reason: "검열 서비스에 일시적으로 연결할 수 없어 제출이 보류되었습니다. 잠시 후 다시 시도해 주세요.",
|
|
314
|
-
error: !0
|
|
325
|
+
error: !0,
|
|
326
|
+
via: "ai-error-block"
|
|
315
327
|
} : {
|
|
316
|
-
|
|
317
|
-
|
|
328
|
+
flagged: !1,
|
|
329
|
+
category: "none",
|
|
330
|
+
reason: "",
|
|
331
|
+
error: !0,
|
|
332
|
+
via: "ai-error-allow"
|
|
318
333
|
};
|
|
319
334
|
}
|
|
320
335
|
async function o(t) {
|
|
321
|
-
let
|
|
322
|
-
if (
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
336
|
+
let o = t.trim();
|
|
337
|
+
if (Z("start", {
|
|
338
|
+
preview: o.slice(0, 80),
|
|
339
|
+
length: o.length,
|
|
340
|
+
enabled: e.enabled !== !1,
|
|
341
|
+
model: e.model ?? "gemini-2.5-flash",
|
|
342
|
+
onError: e.onError ?? "allow"
|
|
343
|
+
}), !o) {
|
|
344
|
+
let e = {
|
|
345
|
+
...X,
|
|
346
|
+
via: "empty"
|
|
347
|
+
};
|
|
348
|
+
return Z("result", e), e;
|
|
349
|
+
}
|
|
350
|
+
let s = r(o);
|
|
351
|
+
if (s) {
|
|
352
|
+
let e = o.match(n)?.[0];
|
|
353
|
+
return Z("local-block", {
|
|
354
|
+
hit: e,
|
|
355
|
+
...s
|
|
356
|
+
}), Z("result", s), s;
|
|
357
|
+
}
|
|
358
|
+
if (Z("local-pass", { message: "로컬 금칙어 미매칭 → AI Logic 호출" }), e.enabled === !1) {
|
|
359
|
+
let e = {
|
|
360
|
+
...X,
|
|
361
|
+
via: "disabled"
|
|
362
|
+
};
|
|
363
|
+
return Z("result", e), e;
|
|
364
|
+
}
|
|
326
365
|
try {
|
|
327
|
-
let e
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
366
|
+
let { model: e, modelName: t } = i();
|
|
367
|
+
Z("ai-call", {
|
|
368
|
+
modelName: t,
|
|
369
|
+
useLimitedUseAppCheckTokens: !0
|
|
370
|
+
});
|
|
371
|
+
let n = performance.now(), r = await e.generateContent(`심사할 텍스트:\n"""${o.slice(0, 4e3)}"""`), s = Math.round(performance.now() - n), c = r.response.text();
|
|
372
|
+
Z("ai-raw", {
|
|
373
|
+
ms: s,
|
|
374
|
+
raw: c.slice(0, 500)
|
|
375
|
+
});
|
|
376
|
+
let l = Y(c);
|
|
377
|
+
if (!l) {
|
|
378
|
+
Z("ai-parse-fail", { raw: c.slice(0, 300) });
|
|
379
|
+
let e = a();
|
|
380
|
+
return Z("result", e), e;
|
|
381
|
+
}
|
|
382
|
+
let u = {
|
|
383
|
+
flagged: l.flagged,
|
|
384
|
+
category: l.category,
|
|
385
|
+
reason: l.reason,
|
|
386
|
+
via: "ai"
|
|
387
|
+
};
|
|
388
|
+
return Z("result", {
|
|
389
|
+
...u,
|
|
390
|
+
ms: s
|
|
391
|
+
}), u;
|
|
333
392
|
} catch (e) {
|
|
334
|
-
|
|
393
|
+
Z("ai-error", {
|
|
394
|
+
message: e instanceof Error ? e.message : String(e),
|
|
395
|
+
name: e instanceof Error ? e.name : void 0,
|
|
396
|
+
code: e?.code
|
|
397
|
+
});
|
|
398
|
+
let t = a();
|
|
399
|
+
return Z("result", t), t;
|
|
335
400
|
}
|
|
336
401
|
}
|
|
337
402
|
async function c(t) {
|
|
338
|
-
if (e.enabled === !1 || !e.moderateImages) return
|
|
403
|
+
if (e.enabled === !1 || !e.moderateImages) return {
|
|
404
|
+
...X,
|
|
405
|
+
via: "disabled"
|
|
406
|
+
};
|
|
339
407
|
try {
|
|
340
408
|
let e = await new Promise((e, n) => {
|
|
341
409
|
let r = new FileReader();
|
|
342
410
|
r.onload = () => e(r.result.split(",")[1] ?? ""), r.onerror = n, r.readAsDataURL(t);
|
|
343
|
-
}), n =
|
|
411
|
+
}), { model: n, modelName: r } = i();
|
|
412
|
+
Z("ai-image-call", {
|
|
413
|
+
modelName: r,
|
|
414
|
+
type: t.type,
|
|
415
|
+
size: t.size
|
|
416
|
+
});
|
|
417
|
+
let o = (await n.generateContent([{ inlineData: {
|
|
344
418
|
mimeType: t.type || "image/jpeg",
|
|
345
419
|
data: e
|
|
346
|
-
} }, "이 이미지를 심사해 주세요."])).response.text());
|
|
347
|
-
return
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
420
|
+
} }, "이 이미지를 심사해 주세요."])).response.text(), s = Y(o);
|
|
421
|
+
if (!s) return Z("ai-image-parse-fail", { raw: o.slice(0, 300) }), a();
|
|
422
|
+
let c = {
|
|
423
|
+
flagged: s.flagged,
|
|
424
|
+
category: s.category,
|
|
425
|
+
reason: s.reason,
|
|
426
|
+
via: "ai"
|
|
427
|
+
};
|
|
428
|
+
return Z("ai-image-result", c), c;
|
|
352
429
|
} catch (e) {
|
|
353
|
-
return
|
|
430
|
+
return Z("ai-image-error", { message: e instanceof Error ? e.message : String(e) }), a();
|
|
354
431
|
}
|
|
355
432
|
}
|
|
356
433
|
return {
|
|
@@ -360,10 +437,10 @@ function re() {
|
|
|
360
437
|
}
|
|
361
438
|
//#endregion
|
|
362
439
|
//#region src/composables/useMemiBoardStorage.ts
|
|
363
|
-
function
|
|
364
|
-
let e =
|
|
440
|
+
function ce() {
|
|
441
|
+
let e = V(), t = s(), n = () => e.collectionPrefix;
|
|
365
442
|
function r(e, r, i) {
|
|
366
|
-
let a =
|
|
443
|
+
let a = P(t), o = `${n()}/posts/${r}/attachments/${Date.now()}-${W(e.name)}`, s = F(a, o), c = ne(s, e, {
|
|
367
444
|
contentType: e.type || "application/octet-stream",
|
|
368
445
|
contentDisposition: `attachment; filename*=UTF-8''${encodeURIComponent(e.name)}`
|
|
369
446
|
});
|
|
@@ -372,7 +449,7 @@ function ie() {
|
|
|
372
449
|
c.on("state_changed", (e) => {
|
|
373
450
|
i && e.totalBytes > 0 && i(e.bytesTransferred / e.totalBytes);
|
|
374
451
|
}, n, async () => {
|
|
375
|
-
let n = await
|
|
452
|
+
let n = await N(s);
|
|
376
453
|
t({
|
|
377
454
|
name: e.name,
|
|
378
455
|
url: n,
|
|
@@ -386,8 +463,8 @@ function ie() {
|
|
|
386
463
|
};
|
|
387
464
|
}
|
|
388
465
|
async function i(e) {
|
|
389
|
-
let n =
|
|
390
|
-
await
|
|
466
|
+
let n = P(t);
|
|
467
|
+
await M(F(n, e.path)).catch(() => {});
|
|
391
468
|
}
|
|
392
469
|
return {
|
|
393
470
|
uploadAttachment: r,
|
|
@@ -410,30 +487,65 @@ var $ = [
|
|
|
410
487
|
label: "질문"
|
|
411
488
|
}
|
|
412
489
|
];
|
|
413
|
-
function
|
|
414
|
-
let t =
|
|
490
|
+
function le() {
|
|
491
|
+
let t = V(), n = l(), { isSignedIn: i, isAdmin: a } = H(), s = e(() => p(n, `${t.collectionPrefix}Settings`, "config")), { data: c, pending: u } = o(s), d = e(() => {
|
|
415
492
|
let e = c.value?.categories;
|
|
416
493
|
return e && e.length > 0 ? e : $;
|
|
417
494
|
});
|
|
418
|
-
function
|
|
495
|
+
function f(e) {
|
|
419
496
|
if (e) return d.value.find((t) => t.id === e)?.label ?? e;
|
|
420
497
|
}
|
|
421
|
-
|
|
422
|
-
if (!
|
|
423
|
-
let
|
|
424
|
-
!
|
|
498
|
+
async function h() {
|
|
499
|
+
if (!i.value) return;
|
|
500
|
+
let e = s.value, t = await m(e).catch(() => null);
|
|
501
|
+
!t || t.exists() || await x(e, {
|
|
425
502
|
categories: $,
|
|
426
|
-
updatedAt:
|
|
427
|
-
})
|
|
503
|
+
updatedAt: b()
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
async function g(e) {
|
|
507
|
+
let t = e.trim();
|
|
508
|
+
if (!t) throw Error("카테고리 이름을 입력해 주세요.");
|
|
509
|
+
if (!i.value) throw Error("로그인이 필요합니다.");
|
|
510
|
+
await h();
|
|
511
|
+
let n = d.value.find((e) => e.label === t || e.id === U(t));
|
|
512
|
+
if (n) return n.id;
|
|
513
|
+
let r = U(t) || `cat-${Date.now()}`, a = new Set(d.value.map((e) => e.id));
|
|
514
|
+
if (a.has(r)) {
|
|
515
|
+
let e = 2;
|
|
516
|
+
for (; a.has(`${r}-${e}`);) e++;
|
|
517
|
+
r = `${r}-${e}`;
|
|
518
|
+
}
|
|
519
|
+
let o = [...d.value, {
|
|
520
|
+
id: r,
|
|
521
|
+
label: t
|
|
522
|
+
}], c = s.value;
|
|
523
|
+
return (await m(c)).exists() ? await C(c, {
|
|
524
|
+
categories: o,
|
|
525
|
+
updatedAt: b()
|
|
526
|
+
}) : await x(c, {
|
|
527
|
+
categories: o,
|
|
528
|
+
updatedAt: b()
|
|
529
|
+
}), r;
|
|
530
|
+
}
|
|
531
|
+
return r([
|
|
532
|
+
a,
|
|
533
|
+
i,
|
|
534
|
+
u
|
|
535
|
+
], async ([e, t, n]) => {
|
|
536
|
+
n || c.value || !e && !t || await h().catch(() => {});
|
|
428
537
|
}, { immediate: !0 }), {
|
|
429
538
|
categories: d,
|
|
430
|
-
categoryLabel:
|
|
431
|
-
settingsPending: u
|
|
539
|
+
categoryLabel: f,
|
|
540
|
+
settingsPending: u,
|
|
541
|
+
ensureSettings: h,
|
|
542
|
+
addCategory: g,
|
|
543
|
+
DEFAULT_CATEGORIES: $
|
|
432
544
|
};
|
|
433
545
|
}
|
|
434
546
|
//#endregion
|
|
435
547
|
//#region src/data/versionHistory.ts
|
|
436
|
-
var
|
|
548
|
+
var ue = [
|
|
437
549
|
{
|
|
438
550
|
version: "0.2.2",
|
|
439
551
|
date: "2026-07-31",
|
|
@@ -460,10 +572,10 @@ var oe = [
|
|
|
460
572
|
];
|
|
461
573
|
//#endregion
|
|
462
574
|
//#region src/utils/formatDate.ts
|
|
463
|
-
function
|
|
575
|
+
function de(e) {
|
|
464
576
|
return e?.toDate ? e.toDate().toLocaleDateString("ko-KR") : "";
|
|
465
577
|
}
|
|
466
578
|
//#endregion
|
|
467
|
-
export {
|
|
579
|
+
export { $ as DEFAULT_CATEGORIES, ie as configureMemiBoard, de as formatDate, H as useMemiBoardAuth, q as useMemiBoardComments, V as useMemiBoardConfig, Q as useMemiBoardModeration, K as useMemiBoardPosts, le as useMemiBoardSettings, ce as useMemiBoardStorage, ue as versionHistory };
|
|
468
580
|
|
|
469
581
|
//# sourceMappingURL=index.js.map
|