memi-board 0.46.0 → 0.46.1
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 +1 -0
- package/dist/{ar-BCQdRw_S.js → ar-2vagHrlh.js} +17 -1
- package/dist/ar-2vagHrlh.js.map +1 -0
- package/dist/composables/fetchPublicSeo.d.ts +1 -1
- package/dist/composables/useMemiBoardAutoTranslate.d.ts +6 -6
- package/dist/composables/useMemiBoardPosts.d.ts +6 -1
- package/dist/composables/useMemiBoardSettings.d.ts +5 -4
- package/dist/composables/useMemiBoardWritingAssistant.d.ts +8 -0
- package/dist/{de-ddWQNaqs.js → de-B6gqgrqZ.js} +17 -1
- package/dist/de-B6gqgrqZ.js.map +1 -0
- package/dist/{en-CaPfPNDy.js → en-BaP_ikG-.js} +14 -1
- package/dist/en-BaP_ikG-.js.map +1 -0
- package/dist/{es-7JHjGVlM.js → es-CGxYBYNH.js} +17 -1
- package/dist/es-CGxYBYNH.js.map +1 -0
- package/dist/{fr-C3NCMNoy.js → fr-DeP9Swrv.js} +17 -1
- package/dist/fr-DeP9Swrv.js.map +1 -0
- package/dist/{id-C8hCAMPJ.js → id-BIUlVuq8.js} +17 -1
- package/dist/id-BIUlVuq8.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1259 -921
- package/dist/index.js.map +1 -1
- package/dist/{ja-oTWri4hY.js → ja-0FOftjGv.js} +17 -1
- package/dist/{ja-oTWri4hY.js.map → ja-0FOftjGv.js.map} +1 -1
- package/dist/{pt-B9EH4scA.js → pt-D8IArHb-.js} +17 -1
- package/dist/pt-D8IArHb-.js.map +1 -0
- package/dist/runtime/components/CategorySettings.vue +27 -10
- package/dist/runtime/components/Detail.vue +9 -7
- package/dist/runtime/components/Editor.vue +79 -8
- package/dist/runtime/components/List.vue +2 -2
- package/dist/runtime/components/ListDefault.vue +16 -6
- package/dist/runtime/components/ListDense.vue +8 -4
- package/dist/runtime/components/ListImage.vue +13 -6
- package/dist/runtime/components/ListVideo.vue +14 -5
- package/dist/runtime/components/Section.vue +10 -3
- package/dist/runtime/components/Settings.vue +27 -10
- package/dist/runtime/composables/useMemiBoardSeo.ts +3 -3
- package/dist/{slugify-DJ82ifCd.js → slugify-BR-FLuDb.js} +23 -10
- package/dist/slugify-BR-FLuDb.js.map +1 -0
- package/dist/storage.js +1 -1
- package/dist/types.d.ts +43 -0
- package/dist/utils/localizedFields.d.ts +33 -0
- package/dist/{zh-D5rDYV6E.js → zh-DlXIP7ju.js} +17 -1
- package/dist/zh-DlXIP7ju.js.map +1 -0
- package/package.json +1 -1
- package/dist/ar-BCQdRw_S.js.map +0 -1
- package/dist/de-ddWQNaqs.js.map +0 -1
- package/dist/en-CaPfPNDy.js.map +0 -1
- package/dist/es-7JHjGVlM.js.map +0 -1
- package/dist/fr-C3NCMNoy.js.map +0 -1
- package/dist/id-C8hCAMPJ.js.map +0 -1
- package/dist/pt-B9EH4scA.js.map +0 -1
- package/dist/slugify-DJ82ifCd.js.map +0 -1
- package/dist/zh-D5rDYV6E.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { A as e, B as t, C as n, D as r, E as i, F as a, G as o, H as s, I as c, L as l, M as u, N as d, O as f, P as p, R as m, S as h, T as g, U as _, V as v, W as y, _ as b, a as x, b as S, c as C, d as w, f as
|
|
2
|
-
import { computed as
|
|
3
|
-
import { deleteDoc as
|
|
4
|
-
import { useCollection as
|
|
5
|
-
import { GoogleAuthProvider as
|
|
6
|
-
import { deleteObject as
|
|
7
|
-
import { GoogleAIBackend as
|
|
1
|
+
import { A as e, B as t, C as n, D as r, E as i, F as a, G as o, H as s, I as c, L as l, M as u, N as d, O as f, P as p, R as m, S as h, T as g, U as _, V as v, W as y, _ as b, a as x, b as S, c as C, d as w, f as ee, g as T, h as E, i as D, j as O, k as te, l as k, m as A, n as ne, o as re, p as ie, r as ae, s as oe, u as se, v as j, w as ce, x as le, y as ue, z as M } from "./slugify-BR-FLuDb.js";
|
|
2
|
+
import { computed as N, onScopeDispose as P, reactive as de, ref as F, toValue as I, watch as L } from "vue";
|
|
3
|
+
import { deleteDoc as fe, deleteField as pe, doc as me, documentId as R, endAt as he, endBefore as ge, getDoc as z, getDocs as B, increment as V, limit as H, limitToLast as _e, onSnapshot as U, orderBy as W, query as G, runTransaction as ve, serverTimestamp as K, setDoc as q, startAfter as J, updateDoc as Y, where as X, writeBatch as ye } from "firebase/firestore";
|
|
4
|
+
import { useCollection as be, useCurrentUser as xe, useDocument as Se, useFirebaseApp as Ce, useFirebaseAuth as we, useFirestore as Z } from "vuefire";
|
|
5
|
+
import { GoogleAuthProvider as Te, OAuthProvider as Ee, createUserWithEmailAndPassword as De, signInWithEmailAndPassword as Oe, signInWithPopup as ke, signOut as Ae, updateProfile as je } from "firebase/auth";
|
|
6
|
+
import { deleteObject as Me, getStorage as Ne, listAll as Pe, ref as Fe } from "firebase/storage";
|
|
7
|
+
import { GoogleAIBackend as Ie, Schema as Le, getAI as Re, getGenerativeModel as ze } from "firebase/ai";
|
|
8
8
|
//#region src/utils/boardSeo.ts
|
|
9
|
-
function
|
|
9
|
+
function Be(e, t) {
|
|
10
10
|
return !e || e === "전체" || e === w(t, "seo.allCategory");
|
|
11
11
|
}
|
|
12
|
-
function
|
|
12
|
+
function Ve(e) {
|
|
13
13
|
if (typeof e != "string") return null;
|
|
14
14
|
let t = e.trim();
|
|
15
15
|
return /^https?:\/\//i.test(t) ? t : null;
|
|
16
16
|
}
|
|
17
|
-
function
|
|
18
|
-
let t =
|
|
17
|
+
function He(e) {
|
|
18
|
+
let t = Ve(e);
|
|
19
19
|
if (!t) return null;
|
|
20
20
|
if (!/firebasestorage\.googleapis\.com/i.test(t)) return t;
|
|
21
21
|
try {
|
|
@@ -32,55 +32,147 @@ function Ve(e) {
|
|
|
32
32
|
return t;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
function
|
|
35
|
+
function Ue(e, t, n = "") {
|
|
36
36
|
let r = t.replace(/\/$/, ""), i = e.trim();
|
|
37
|
-
return i ? /^https?:\/\//i.test(i) ? i : `${r}${i.startsWith("/") ? i : `/${i}`}` : n ?
|
|
37
|
+
return i ? /^https?:\/\//i.test(i) ? i : `${r}${i.startsWith("/") ? i : `/${i}`}` : n ? Ue(n, t) : r;
|
|
38
38
|
}
|
|
39
|
-
function
|
|
39
|
+
function We(e, t, n, r, i) {
|
|
40
40
|
let a = r?.trim().slice(0, 40), o = e.trim() || a || w(i, "seo.photo"), s = t?.trim(), c = n.trim() || "Board";
|
|
41
41
|
return s ? `${o} · ${s} | ${c}` : `${o} | ${c}`;
|
|
42
42
|
}
|
|
43
|
-
function
|
|
43
|
+
function Ge(e, t) {
|
|
44
44
|
let n = [];
|
|
45
45
|
return e.summary?.trim() ? n.push(e.summary.trim().slice(0, 140)) : n.push(w(t, "seo.postDescriptionFallback")), e.categoryLabel?.trim() && n.push(e.categoryLabel.trim()), e.authorName?.trim() && n.push(e.authorName.trim()), n.join(" · ").slice(0, 200);
|
|
46
46
|
}
|
|
47
|
-
function
|
|
47
|
+
function Ke(e, t, n) {
|
|
48
48
|
let r = t.trim() || "Board", i = e?.trim();
|
|
49
|
-
return
|
|
49
|
+
return Be(i, n) ? w(n, "seo.listTitleAll", { site: r }) : w(n, "seo.listTitle", {
|
|
50
50
|
name: i,
|
|
51
51
|
site: r
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
-
function
|
|
54
|
+
function qe(e, t) {
|
|
55
55
|
if (e.description?.trim()) return e.description.trim().slice(0, 200);
|
|
56
|
-
let n = e.categoryLabel?.trim(), r =
|
|
56
|
+
let n = e.categoryLabel?.trim(), r = Be(n, t) ? w(t, "seo.listDescriptionAll") : w(t, "seo.listDescription", { name: n }), i = (e.recentTitles || []).filter(Boolean).slice(0, 3);
|
|
57
57
|
return i.length ? `${r} ${i.map((e) => `「${e}」`).join(" ")}`.slice(0, 200) : r.slice(0, 200);
|
|
58
58
|
}
|
|
59
|
-
function
|
|
59
|
+
function Je(e) {
|
|
60
60
|
let t = (e || "/board").trim() || "/board";
|
|
61
61
|
return (t.startsWith("/") ? t : `/${t}`).replace(/\/$/, "") || "/board";
|
|
62
62
|
}
|
|
63
63
|
//#endregion
|
|
64
|
+
//#region src/utils/localizedFields.ts
|
|
65
|
+
var Ye = [
|
|
66
|
+
"label",
|
|
67
|
+
"description",
|
|
68
|
+
"title",
|
|
69
|
+
"summary",
|
|
70
|
+
"contentHash"
|
|
71
|
+
];
|
|
72
|
+
function Xe(e) {
|
|
73
|
+
let t = 5381;
|
|
74
|
+
for (let n = 0; n < e.length; n++) t = (t << 5) + t ^ e.charCodeAt(n);
|
|
75
|
+
return (t >>> 0).toString(36);
|
|
76
|
+
}
|
|
77
|
+
function Ze(e, t) {
|
|
78
|
+
let n = e?.[t];
|
|
79
|
+
return typeof n == "string" ? n.trim() : "";
|
|
80
|
+
}
|
|
81
|
+
function Q(e) {
|
|
82
|
+
if (!e || typeof e != "object") return;
|
|
83
|
+
let t = {};
|
|
84
|
+
for (let [n, r] of Object.entries(e)) {
|
|
85
|
+
let e = T(n);
|
|
86
|
+
if (!e || !r || typeof r != "object") continue;
|
|
87
|
+
let i = {};
|
|
88
|
+
for (let [e, t] of Object.entries(r)) typeof t == "string" && t.trim() && (i[e] = t.trim());
|
|
89
|
+
Object.keys(i).length && (t[e] = i);
|
|
90
|
+
}
|
|
91
|
+
return Object.keys(t).length ? t : void 0;
|
|
92
|
+
}
|
|
93
|
+
function Qe(e) {
|
|
94
|
+
if (!e || typeof e != "object") return;
|
|
95
|
+
let t = e, n = {}, r = T(t.locale);
|
|
96
|
+
r && (n.locale = r);
|
|
97
|
+
for (let e of Ye) typeof t[e] == "string" && (n[e] = t[e]);
|
|
98
|
+
return Object.keys(n).length ? n : void 0;
|
|
99
|
+
}
|
|
100
|
+
function $e(e) {
|
|
101
|
+
if (!e || typeof e != "object") return;
|
|
102
|
+
let t = e, n = {}, r = T(t.locale);
|
|
103
|
+
return r && (n.locale = r), typeof t.hash == "string" && t.hash && (n.hash = t.hash), n.locale || n.hash ? n : void 0;
|
|
104
|
+
}
|
|
105
|
+
function et(e, t, n) {
|
|
106
|
+
let r = {};
|
|
107
|
+
if (n) for (let e of A) {
|
|
108
|
+
let t = n[e.code];
|
|
109
|
+
if (!t) continue;
|
|
110
|
+
let i = {};
|
|
111
|
+
for (let [e, n] of Object.entries(t)) typeof n == "string" && n.trim() && (i[e] = n.trim());
|
|
112
|
+
Object.keys(i).length && (r[e.code] = i);
|
|
113
|
+
}
|
|
114
|
+
let i = {};
|
|
115
|
+
for (let [e, n] of Object.entries(t)) {
|
|
116
|
+
let t = n.trim();
|
|
117
|
+
t && (i[e] = t);
|
|
118
|
+
}
|
|
119
|
+
return Object.keys(i).length && (r[e] = {
|
|
120
|
+
...r[e],
|
|
121
|
+
...i
|
|
122
|
+
}), r;
|
|
123
|
+
}
|
|
124
|
+
function tt(e, t) {
|
|
125
|
+
return !e || !t.length ? !1 : A.every((n) => t.every((t) => !!Ze(e[n.code], t)));
|
|
126
|
+
}
|
|
127
|
+
function nt(e, t, n, r, i) {
|
|
128
|
+
let a = T(t), o = T(i);
|
|
129
|
+
if (!a || o && a === o) return r;
|
|
130
|
+
let s = Ze(e?.[a], n);
|
|
131
|
+
if (s) return s;
|
|
132
|
+
if (a !== "en" && o !== "en") {
|
|
133
|
+
let t = Ze(e?.en, n);
|
|
134
|
+
if (t) return t;
|
|
135
|
+
}
|
|
136
|
+
return r;
|
|
137
|
+
}
|
|
138
|
+
function rt(e, t, n, r, i) {
|
|
139
|
+
return r ? !t || t[n] !== r ? r : nt(e, i, n, r, t.locale) : "";
|
|
140
|
+
}
|
|
141
|
+
function it(e, t) {
|
|
142
|
+
return rt(e.i18n, e.i18nSource, "title", e.title ?? "", t);
|
|
143
|
+
}
|
|
144
|
+
function at(e, t) {
|
|
145
|
+
return rt(e.i18n, e.i18nSource, "summary", e.summary ?? "", t);
|
|
146
|
+
}
|
|
147
|
+
function ot(e, t) {
|
|
148
|
+
let n = e.content || "", r = e.contentI18nSource?.hash;
|
|
149
|
+
return !n || !r || r !== Xe(n) ? n : nt(e.contentI18n, t, "content", n, e.contentI18nSource?.locale) || n;
|
|
150
|
+
}
|
|
151
|
+
function st(e) {
|
|
152
|
+
let t = e.label.trim(), n = (e.description ?? "").trim();
|
|
153
|
+
return !t || !e.i18nSource || e.i18nSource.label !== t || (e.i18nSource.description ?? "") !== n ? !1 : tt(e.i18n, n ? ["label", "description"] : ["label"]);
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
64
156
|
//#region src/composables/fetchPublicSeo.ts
|
|
65
|
-
function
|
|
157
|
+
function ct() {
|
|
66
158
|
return {
|
|
67
|
-
db:
|
|
68
|
-
paths:
|
|
159
|
+
db: Z(),
|
|
160
|
+
paths: j()
|
|
69
161
|
};
|
|
70
162
|
}
|
|
71
|
-
async function
|
|
163
|
+
async function lt(e, t, n) {
|
|
72
164
|
try {
|
|
73
|
-
let
|
|
74
|
-
if (!
|
|
165
|
+
let r = await z(o(e.db, e.paths, t));
|
|
166
|
+
if (!r.exists()) return {
|
|
75
167
|
label: t,
|
|
76
168
|
description: "",
|
|
77
169
|
hidden: !1
|
|
78
170
|
};
|
|
79
|
-
let
|
|
171
|
+
let i = r.data() ?? {}, a = typeof i.label == "string" && i.label.trim() ? i.label.trim() : t, s = typeof i.description == "string" ? i.description.trim() : "", c = Q(i.i18n), l = Qe(i.i18nSource);
|
|
80
172
|
return {
|
|
81
|
-
label:
|
|
82
|
-
description:
|
|
83
|
-
hidden:
|
|
173
|
+
label: rt(c, l, "label", a, n) || a,
|
|
174
|
+
description: rt(c, l, "description", s, n),
|
|
175
|
+
hidden: i.visibility === "hidden"
|
|
84
176
|
};
|
|
85
177
|
} catch {
|
|
86
178
|
return {
|
|
@@ -90,7 +182,7 @@ async function Ye(e, t) {
|
|
|
90
182
|
};
|
|
91
183
|
}
|
|
92
184
|
}
|
|
93
|
-
function
|
|
185
|
+
function ut(e) {
|
|
94
186
|
if (e && typeof e == "object" && "toDate" in e) try {
|
|
95
187
|
let t = e.toDate();
|
|
96
188
|
return t instanceof Date ? t.toISOString() : null;
|
|
@@ -99,37 +191,45 @@ function Xe(e) {
|
|
|
99
191
|
}
|
|
100
192
|
return null;
|
|
101
193
|
}
|
|
102
|
-
async function
|
|
103
|
-
let
|
|
104
|
-
if (!
|
|
105
|
-
let
|
|
106
|
-
|
|
107
|
-
let
|
|
108
|
-
if (
|
|
194
|
+
async function dt(e, t, n, r) {
|
|
195
|
+
let i = e.trim(), a = t.trim();
|
|
196
|
+
if (!i || !a) return null;
|
|
197
|
+
let o = n ?? ct();
|
|
198
|
+
o.paths = _(o.paths), r && await se(r);
|
|
199
|
+
let s = await lt(o, i, r);
|
|
200
|
+
if (s.hidden) return null;
|
|
109
201
|
try {
|
|
110
|
-
let e = (await
|
|
202
|
+
let e = (await B(G(M(o.db, o.paths), X("boardId", "==", i), X("slug", "==", a), X("listed", "==", !0), X("isPublished", "==", !0), H(1)))).docs[0];
|
|
111
203
|
if (!e) return null;
|
|
112
|
-
let t = e.data() ?? {}, n = typeof t.title == "string" ? t.title.
|
|
113
|
-
return n ? {
|
|
114
|
-
id: e.id,
|
|
115
|
-
slug: typeof t.slug == "string" ? t.slug : i,
|
|
116
|
-
category: r,
|
|
117
|
-
categoryLabel: o.label,
|
|
204
|
+
let t = e.data() ?? {}, n = typeof t.title == "string" ? t.title : "", c = typeof t.summary == "string" ? t.summary : "", l = Q(t.i18n), u = Qe(t.i18nSource), d = it({
|
|
118
205
|
title: n,
|
|
119
|
-
|
|
120
|
-
|
|
206
|
+
i18n: l,
|
|
207
|
+
i18nSource: u
|
|
208
|
+
}, r).trim();
|
|
209
|
+
return d ? {
|
|
210
|
+
id: e.id,
|
|
211
|
+
slug: typeof t.slug == "string" ? t.slug : a,
|
|
212
|
+
category: i,
|
|
213
|
+
categoryLabel: s.label,
|
|
214
|
+
title: d,
|
|
215
|
+
summary: at({
|
|
216
|
+
summary: c,
|
|
217
|
+
i18n: l,
|
|
218
|
+
i18nSource: u
|
|
219
|
+
}, r).trim().slice(0, 200),
|
|
220
|
+
previewImage: He(t.previewImage),
|
|
121
221
|
authorName: typeof t.authorName == "string" && t.authorName.trim() ? t.authorName.trim() : null,
|
|
122
|
-
createdAt:
|
|
222
|
+
createdAt: ut(t.createdAt)
|
|
123
223
|
} : null;
|
|
124
224
|
} catch (e) {
|
|
125
225
|
return import.meta.dev && console.warn("[memi-board] fetchPublicPostForSeo failed", e), null;
|
|
126
226
|
}
|
|
127
227
|
}
|
|
128
|
-
async function
|
|
129
|
-
n && await
|
|
130
|
-
let r = e?.trim() || null, i = t ??
|
|
228
|
+
async function ft(e, t, n) {
|
|
229
|
+
n && await se(n);
|
|
230
|
+
let r = e?.trim() || null, i = t ?? ct();
|
|
131
231
|
if (i.paths = _(i.paths), r) {
|
|
132
|
-
let e = await
|
|
232
|
+
let e = await lt(i, r, n);
|
|
133
233
|
if (e.hidden) return {
|
|
134
234
|
kind: "category",
|
|
135
235
|
category: r,
|
|
@@ -138,7 +238,7 @@ async function Qe(e, t, n) {
|
|
|
138
238
|
ogImage: null,
|
|
139
239
|
recentTitles: []
|
|
140
240
|
};
|
|
141
|
-
let { recentTitles: t, ogImage: a } = await
|
|
241
|
+
let { recentTitles: t, ogImage: a } = await pt(i, r, n);
|
|
142
242
|
return {
|
|
143
243
|
kind: "category",
|
|
144
244
|
category: r,
|
|
@@ -157,49 +257,53 @@ async function Qe(e, t, n) {
|
|
|
157
257
|
recentTitles: []
|
|
158
258
|
};
|
|
159
259
|
}
|
|
160
|
-
async function
|
|
161
|
-
let
|
|
260
|
+
async function pt(e, t, n) {
|
|
261
|
+
let r = [], i = null;
|
|
162
262
|
try {
|
|
163
|
-
let
|
|
164
|
-
for (let e of
|
|
165
|
-
let t = e.data() ?? {},
|
|
166
|
-
|
|
263
|
+
let a = await B(G(M(e.db, e.paths), X("boardId", "==", t), X("listed", "==", !0), X("isPublished", "==", !0), W("createdAt", "desc"), H(12)));
|
|
264
|
+
for (let e of a.docs) {
|
|
265
|
+
let t = e.data() ?? {}, a = it({
|
|
266
|
+
title: typeof t.title == "string" ? t.title : "",
|
|
267
|
+
i18n: Q(t.i18n),
|
|
268
|
+
i18nSource: Qe(t.i18nSource)
|
|
269
|
+
}, n).trim();
|
|
270
|
+
a && r.length < 5 && r.push(a), i ||= He(t.previewImage);
|
|
167
271
|
}
|
|
168
272
|
} catch (e) {
|
|
169
273
|
import.meta.dev && console.warn("[memi-board] loadRecentListed failed", e);
|
|
170
274
|
}
|
|
171
275
|
return {
|
|
172
|
-
recentTitles:
|
|
173
|
-
ogImage:
|
|
276
|
+
recentTitles: r,
|
|
277
|
+
ogImage: i
|
|
174
278
|
};
|
|
175
279
|
}
|
|
176
280
|
//#endregion
|
|
177
281
|
//#region src/utils/boardAccess.ts
|
|
178
|
-
function
|
|
282
|
+
function mt(e, t) {
|
|
179
283
|
return !e || !t ? !1 : [...t.allowedStaffUids ?? [], ...t.allowedManagerUids ?? []].includes(e);
|
|
180
284
|
}
|
|
181
|
-
function
|
|
182
|
-
return e ? t ? !0 : n ?
|
|
285
|
+
function ht(e, t, n, r) {
|
|
286
|
+
return e ? t ? !0 : n ? mt(e, r) : !1 : !1;
|
|
183
287
|
}
|
|
184
|
-
function
|
|
288
|
+
function gt(e, t, n, r) {
|
|
185
289
|
if (!e) return !1;
|
|
186
|
-
if (r?.visibility === "hidden") return
|
|
290
|
+
if (r?.visibility === "hidden") return ht(e, t, n, r);
|
|
187
291
|
let i = r?.commentWriteRole ?? "user";
|
|
188
|
-
return i === "admin" ? t : i !== "staff" ||
|
|
292
|
+
return i === "admin" ? t : i !== "staff" || ht(e, t, n, r);
|
|
189
293
|
}
|
|
190
294
|
//#endregion
|
|
191
295
|
//#region src/composables/boardLookup.ts
|
|
192
|
-
var
|
|
193
|
-
function
|
|
194
|
-
|
|
296
|
+
var _t;
|
|
297
|
+
function vt(e) {
|
|
298
|
+
_t = e;
|
|
195
299
|
}
|
|
196
|
-
function
|
|
197
|
-
return
|
|
300
|
+
function yt(e) {
|
|
301
|
+
return _t?.(e);
|
|
198
302
|
}
|
|
199
303
|
//#endregion
|
|
200
304
|
//#region src/utils/moderation-strike.ts
|
|
201
|
-
var
|
|
202
|
-
function
|
|
305
|
+
var bt = 10, xt = 864e5;
|
|
306
|
+
function St(e, t, n = Date.now(), r = xt) {
|
|
203
307
|
let i = Math.max(0, Math.floor(Number(e) || 0));
|
|
204
308
|
if (i <= 0) return 0;
|
|
205
309
|
let a = typeof t == "number" && Number.isFinite(t) ? t : null;
|
|
@@ -207,7 +311,7 @@ function ct(e, t, n = Date.now(), r = st) {
|
|
|
207
311
|
let o = Math.floor((n - a) / r);
|
|
208
312
|
return Math.max(0, i - o);
|
|
209
313
|
}
|
|
210
|
-
function
|
|
314
|
+
function Ct(e, t, n = Date.now(), r = 10, i = xt) {
|
|
211
315
|
let a = Math.max(0, Math.floor(Number(e) || 0));
|
|
212
316
|
if (a < r) return null;
|
|
213
317
|
let o = typeof t == "number" && Number.isFinite(t) ? t : null;
|
|
@@ -215,10 +319,10 @@ function lt(e, t, n = Date.now(), r = 10, i = st) {
|
|
|
215
319
|
let s = o + (a - (r - 1)) * i;
|
|
216
320
|
return s > n ? s : null;
|
|
217
321
|
}
|
|
218
|
-
function
|
|
219
|
-
return
|
|
322
|
+
function wt(e, t, n = Date.now(), r = 10, i = xt) {
|
|
323
|
+
return Ct(e, t, n, r, i) != null;
|
|
220
324
|
}
|
|
221
|
-
function
|
|
325
|
+
function Tt(e, t = Date.now(), n) {
|
|
222
326
|
let r = Math.max(0, e - t), i = Math.ceil(r / 36e5);
|
|
223
327
|
if (i <= 0) return w(n, "moderationStrike.liftSoon");
|
|
224
328
|
if (i < 24) return w(n, "moderationStrike.liftInHours", { hours: i });
|
|
@@ -228,7 +332,7 @@ function dt(e, t = Date.now(), n) {
|
|
|
228
332
|
hours: o
|
|
229
333
|
});
|
|
230
334
|
}
|
|
231
|
-
function
|
|
335
|
+
function Et(e) {
|
|
232
336
|
if (e == null) return null;
|
|
233
337
|
if (typeof e == "number" && Number.isFinite(e)) return e;
|
|
234
338
|
if (typeof e == "object" && e && "toMillis" in e && typeof e.toMillis == "function") return e.toMillis();
|
|
@@ -240,29 +344,29 @@ function ft(e) {
|
|
|
240
344
|
}
|
|
241
345
|
//#endregion
|
|
242
346
|
//#region src/composables/useMemiBoardAuth.ts
|
|
243
|
-
var
|
|
244
|
-
function
|
|
347
|
+
var Dt = "__MEMI_BOARD_AUTH_STATE__";
|
|
348
|
+
function Ot() {
|
|
245
349
|
let e = globalThis;
|
|
246
|
-
return e[
|
|
247
|
-
role:
|
|
248
|
-
rolePending:
|
|
249
|
-
rawBlockCount:
|
|
250
|
-
blockAtMs:
|
|
350
|
+
return e[Dt] || (e[Dt] = {
|
|
351
|
+
role: F("user"),
|
|
352
|
+
rolePending: F(!0),
|
|
353
|
+
rawBlockCount: F(0),
|
|
354
|
+
blockAtMs: F(null),
|
|
251
355
|
watchStarted: !1
|
|
252
|
-
}), e[
|
|
356
|
+
}), e[Dt];
|
|
253
357
|
}
|
|
254
|
-
function
|
|
255
|
-
let e =
|
|
358
|
+
function kt() {
|
|
359
|
+
let e = ue(), { t, locale: n } = D(), r = Ot(), i = Z(), a = we(), o = xe(), { role: s, rolePending: c, rawBlockCount: l, blockAtMs: u } = r;
|
|
256
360
|
function d() {
|
|
257
361
|
let t = e.moderation?.blockBanThreshold;
|
|
258
362
|
return t === 0 ? 0 : typeof t == "number" && t > 0 ? Math.floor(t) : 10;
|
|
259
363
|
}
|
|
260
364
|
function p() {
|
|
261
365
|
let t = e.moderation?.blockBanDecayMs;
|
|
262
|
-
return typeof t == "number" && t > 0 ? t :
|
|
366
|
+
return typeof t == "number" && t > 0 ? t : xt;
|
|
263
367
|
}
|
|
264
368
|
function m(e) {
|
|
265
|
-
return f(i,
|
|
369
|
+
return f(i, j(), e);
|
|
266
370
|
}
|
|
267
371
|
function h(e) {
|
|
268
372
|
if (s.value = [
|
|
@@ -275,25 +379,25 @@ function ht() {
|
|
|
275
379
|
l.value = 0, u.value = null;
|
|
276
380
|
return;
|
|
277
381
|
}
|
|
278
|
-
l.value = Math.max(0, Math.floor(Number(e.moderationBlockCount) || 0)), u.value =
|
|
382
|
+
l.value = Math.max(0, Math.floor(Number(e.moderationBlockCount) || 0)), u.value = Et(e.moderationBlockAt);
|
|
279
383
|
}
|
|
280
384
|
async function g(e) {
|
|
281
|
-
let t = m(e.uid), n =
|
|
282
|
-
if (!r.exists()) return await Promise.all([
|
|
385
|
+
let t = m(e.uid), n = te(i, j(), e.uid), r = await z(t);
|
|
386
|
+
if (!r.exists()) return await Promise.all([q(t, {
|
|
283
387
|
role: "user",
|
|
284
388
|
displayName: e.displayName,
|
|
285
389
|
photoURL: e.photoURL,
|
|
286
|
-
joinedAt:
|
|
287
|
-
lastVisitAt:
|
|
288
|
-
updatedAt:
|
|
289
|
-
}),
|
|
390
|
+
joinedAt: K(),
|
|
391
|
+
lastVisitAt: K(),
|
|
392
|
+
updatedAt: K()
|
|
393
|
+
}), q(n, { email: e.email })]), h({ moderationBlockCount: 0 }), "user";
|
|
290
394
|
let a = r.data();
|
|
291
395
|
h(a);
|
|
292
396
|
let o = typeof a.displayName == "string" && a.displayName.trim(), s = typeof a.photoURL == "string" && a.photoURL.trim(), c = {
|
|
293
|
-
lastVisitAt:
|
|
294
|
-
updatedAt:
|
|
397
|
+
lastVisitAt: K(),
|
|
398
|
+
updatedAt: K()
|
|
295
399
|
};
|
|
296
|
-
return !o && e.displayName && (c.displayName = e.displayName), !s && e.photoURL && (c.photoURL = e.photoURL), await Promise.all([
|
|
400
|
+
return !o && e.displayName && (c.displayName = e.displayName), !s && e.photoURL && (c.photoURL = e.photoURL), await Promise.all([Y(t, c), q(n, { email: e.email }, { merge: !0 })]), [
|
|
297
401
|
"owner",
|
|
298
402
|
"admin",
|
|
299
403
|
"manager",
|
|
@@ -307,10 +411,10 @@ function ht() {
|
|
|
307
411
|
h(void 0);
|
|
308
412
|
return;
|
|
309
413
|
}
|
|
310
|
-
let t = await
|
|
414
|
+
let t = await z(m(e));
|
|
311
415
|
h(t.exists() ? t.data() : void 0);
|
|
312
416
|
}
|
|
313
|
-
r.watchStarted || (r.watchStarted = !0,
|
|
417
|
+
r.watchStarted || (r.watchStarted = !0, L(o, async (e) => {
|
|
314
418
|
if (c.value = !0, !e) {
|
|
315
419
|
s.value = "user", h(void 0), c.value = !1;
|
|
316
420
|
return;
|
|
@@ -323,46 +427,46 @@ function ht() {
|
|
|
323
427
|
c.value = !1;
|
|
324
428
|
}
|
|
325
429
|
}, { immediate: !0 }));
|
|
326
|
-
let v =
|
|
430
|
+
let v = N(() => !!o.value), y = N(() => s.value === "owner"), b = N(() => s.value === "manager"), x = N(() => y.value || s.value === "admin"), S = N(() => b.value || s.value === "staff"), C = N(() => x.value || S.value), w = N(() => St(l.value, u.value, Date.now(), p())), ee = N(() => {
|
|
327
431
|
let e = d();
|
|
328
|
-
return e <= 0 ? !1 :
|
|
329
|
-
}),
|
|
432
|
+
return e <= 0 ? !1 : wt(l.value, u.value, Date.now(), e, p());
|
|
433
|
+
}), T = N(() => {
|
|
330
434
|
let e = d();
|
|
331
435
|
if (e <= 0) return null;
|
|
332
|
-
let t =
|
|
333
|
-
return t == null ? null :
|
|
334
|
-
}),
|
|
335
|
-
if (!
|
|
336
|
-
let e =
|
|
436
|
+
let t = Ct(l.value, u.value, Date.now(), e, p());
|
|
437
|
+
return t == null ? null : Tt(t, Date.now(), n.value);
|
|
438
|
+
}), E = N(() => {
|
|
439
|
+
if (!ee.value) return null;
|
|
440
|
+
let e = T.value;
|
|
337
441
|
return e ? t("auth.writeRestrictedUntil", { until: e }) : t("auth.writeRestricted");
|
|
338
442
|
});
|
|
339
|
-
async function
|
|
443
|
+
async function O() {
|
|
340
444
|
let e = o.value?.uid, r = d();
|
|
341
445
|
if (!e || r <= 0) return {
|
|
342
446
|
effectiveCount: 0,
|
|
343
447
|
restricted: !1,
|
|
344
448
|
messageSuffix: ""
|
|
345
449
|
};
|
|
346
|
-
let i = m(e), a = await
|
|
347
|
-
a.exists() ? await
|
|
348
|
-
moderationBlockCount:
|
|
349
|
-
moderationBlockAt:
|
|
350
|
-
updatedAt:
|
|
351
|
-
}) : await
|
|
450
|
+
let i = m(e), a = await z(i);
|
|
451
|
+
a.exists() ? await Y(i, {
|
|
452
|
+
moderationBlockCount: V(1),
|
|
453
|
+
moderationBlockAt: K(),
|
|
454
|
+
updatedAt: K()
|
|
455
|
+
}) : await q(i, {
|
|
352
456
|
role: "user",
|
|
353
457
|
displayName: o.value?.displayName ?? null,
|
|
354
458
|
moderationBlockCount: 1,
|
|
355
|
-
moderationBlockAt:
|
|
356
|
-
updatedAt:
|
|
459
|
+
moderationBlockAt: K(),
|
|
460
|
+
updatedAt: K()
|
|
357
461
|
});
|
|
358
462
|
let s = (a.exists() ? Math.max(0, Math.floor(Number(a.data().moderationBlockCount) || 0)) : 0) + 1, c = Date.now();
|
|
359
463
|
l.value = s, u.value = c;
|
|
360
|
-
let f =
|
|
464
|
+
let f = St(s, c, c, p()), h = wt(s, c, c, r, p()), g = t("auth.strikeSuffix", {
|
|
361
465
|
count: f,
|
|
362
466
|
threshold: r
|
|
363
467
|
});
|
|
364
468
|
if (h) {
|
|
365
|
-
let e =
|
|
469
|
+
let e = Ct(s, c, c, r, p()), i = e == null ? null : Tt(e, c, n.value);
|
|
366
470
|
g = i ? t("auth.strikeRestrictedUntil", {
|
|
367
471
|
count: f,
|
|
368
472
|
until: i
|
|
@@ -374,101 +478,101 @@ function ht() {
|
|
|
374
478
|
messageSuffix: g
|
|
375
479
|
};
|
|
376
480
|
}
|
|
377
|
-
function
|
|
481
|
+
function k() {
|
|
378
482
|
if (!a) throw Error(t("auth.notInitialized"));
|
|
379
483
|
return a;
|
|
380
484
|
}
|
|
381
|
-
async function
|
|
382
|
-
return
|
|
485
|
+
async function A() {
|
|
486
|
+
return ke(k(), new Te());
|
|
383
487
|
}
|
|
384
|
-
async function
|
|
385
|
-
let e = new
|
|
386
|
-
return e.addScope("email"), e.addScope("name"),
|
|
387
|
-
}
|
|
388
|
-
async function ne(e, t) {
|
|
389
|
-
return De(A(), e, t);
|
|
488
|
+
async function ne() {
|
|
489
|
+
let e = new Ee("apple.com");
|
|
490
|
+
return e.addScope("email"), e.addScope("name"), ke(k(), e);
|
|
390
491
|
}
|
|
391
|
-
async function re(e, t
|
|
392
|
-
|
|
393
|
-
return n && await Ae(r.user, { displayName: n }), r;
|
|
492
|
+
async function re(e, t) {
|
|
493
|
+
return Oe(k(), e, t);
|
|
394
494
|
}
|
|
395
|
-
async function ie() {
|
|
396
|
-
await
|
|
495
|
+
async function ie(e, t, n) {
|
|
496
|
+
let r = await De(k(), e, t);
|
|
497
|
+
return n && await je(r.user, { displayName: n }), r;
|
|
397
498
|
}
|
|
398
|
-
function ae(
|
|
399
|
-
|
|
499
|
+
async function ae() {
|
|
500
|
+
await Ae(k());
|
|
400
501
|
}
|
|
401
502
|
function oe(e) {
|
|
402
|
-
return o.value
|
|
503
|
+
return ht(o.value?.uid, x.value, S.value, e ? yt(e) : void 0);
|
|
403
504
|
}
|
|
404
505
|
function se(e) {
|
|
405
|
-
return
|
|
506
|
+
return o.value ? x.value ? !0 : b.value && !!e && (yt(e)?.allowedManagerUids ?? []).includes(o.value.uid) : !1;
|
|
406
507
|
}
|
|
407
508
|
function ce(e) {
|
|
509
|
+
return v.value && (o.value?.uid === e.authorUid || oe(e.boardId));
|
|
510
|
+
}
|
|
511
|
+
function le(e) {
|
|
408
512
|
return v.value && o.value?.uid === e.authorUid;
|
|
409
513
|
}
|
|
410
|
-
function
|
|
411
|
-
return v.value && (o.value?.uid === e.authorUid ||
|
|
514
|
+
function M(e) {
|
|
515
|
+
return v.value && (o.value?.uid === e.authorUid || oe(e.boardId));
|
|
412
516
|
}
|
|
413
517
|
return {
|
|
414
518
|
user: o,
|
|
415
519
|
role: s,
|
|
416
520
|
isOwner: y,
|
|
417
521
|
isManager: b,
|
|
418
|
-
canConfigureBoard:
|
|
522
|
+
canConfigureBoard: se,
|
|
419
523
|
isSignedIn: v,
|
|
420
524
|
isAdmin: x,
|
|
421
525
|
isStaff: S,
|
|
422
526
|
canManageContent: C,
|
|
423
|
-
canManageBoard:
|
|
527
|
+
canManageBoard: oe,
|
|
424
528
|
rolePending: c,
|
|
425
529
|
moderationBlockCount: w,
|
|
426
|
-
isWriteRestricted:
|
|
427
|
-
restrictedUntilLabel:
|
|
428
|
-
restrictedMessage:
|
|
429
|
-
signInWithGoogle:
|
|
430
|
-
signInWithApple:
|
|
431
|
-
signInWithEmail:
|
|
432
|
-
signUpWithEmail:
|
|
433
|
-
signOut:
|
|
434
|
-
canEdit:
|
|
435
|
-
canDelete:
|
|
436
|
-
canEditComment:
|
|
437
|
-
canDeleteComment:
|
|
438
|
-
recordContentModerationBlock:
|
|
530
|
+
isWriteRestricted: ee,
|
|
531
|
+
restrictedUntilLabel: T,
|
|
532
|
+
restrictedMessage: E,
|
|
533
|
+
signInWithGoogle: A,
|
|
534
|
+
signInWithApple: ne,
|
|
535
|
+
signInWithEmail: re,
|
|
536
|
+
signUpWithEmail: ie,
|
|
537
|
+
signOut: ae,
|
|
538
|
+
canEdit: ce,
|
|
539
|
+
canDelete: ce,
|
|
540
|
+
canEditComment: le,
|
|
541
|
+
canDeleteComment: M,
|
|
542
|
+
recordContentModerationBlock: O,
|
|
439
543
|
refreshBoardUser: _
|
|
440
544
|
};
|
|
441
545
|
}
|
|
442
546
|
//#endregion
|
|
443
547
|
//#region src/utils/imeSafeSubmit.ts
|
|
444
|
-
var
|
|
445
|
-
function
|
|
548
|
+
var At = /* @__PURE__ */ new WeakMap();
|
|
549
|
+
function jt(e) {
|
|
446
550
|
let t = e.currentTarget;
|
|
447
551
|
return t instanceof HTMLButtonElement ? t : t instanceof Element ? t.closest("button") : null;
|
|
448
552
|
}
|
|
449
|
-
function
|
|
553
|
+
function Mt(e) {
|
|
450
554
|
if (e.pointerType !== "mouse" || e.button !== 0) return;
|
|
451
|
-
let t =
|
|
555
|
+
let t = jt(e), n = t?.form;
|
|
452
556
|
if (!t || !n || t.disabled) return;
|
|
453
|
-
|
|
557
|
+
At.set(t, Date.now() + 1e3);
|
|
454
558
|
let r = document.activeElement;
|
|
455
559
|
r instanceof HTMLElement && r.blur(), setTimeout(() => n.requestSubmit(t), 0);
|
|
456
560
|
}
|
|
457
|
-
function
|
|
458
|
-
let t =
|
|
561
|
+
function Nt(e) {
|
|
562
|
+
let t = jt(e);
|
|
459
563
|
if (!t) return;
|
|
460
|
-
let n =
|
|
461
|
-
n && Date.now() < n && (e.preventDefault(),
|
|
564
|
+
let n = At.get(t);
|
|
565
|
+
n && Date.now() < n && (e.preventDefault(), At.delete(t));
|
|
462
566
|
}
|
|
463
567
|
//#endregion
|
|
464
568
|
//#region src/utils/findReadablePost.ts
|
|
465
|
-
async function
|
|
466
|
-
let o = [
|
|
467
|
-
return a ? (await
|
|
569
|
+
async function Pt(e, t, n, r, i, a) {
|
|
570
|
+
let o = [X("boardId", "==", n), X("slug", "==", r)];
|
|
571
|
+
return a ? (await B(G(M(e, t), ...o, H(1)))).docs[0] : (await Promise.all([B(G(M(e, t), ...o, X("listed", "==", !0), X("isPublished", "==", !0), H(1))), ...i ? [B(G(M(e, t), ...o, X("authorUid", "==", i), H(1)))] : []])).flatMap((e) => e.docs)[0];
|
|
468
572
|
}
|
|
469
573
|
//#endregion
|
|
470
574
|
//#region src/utils/extractEditorImageUrls.ts
|
|
471
|
-
function
|
|
575
|
+
function Ft(e) {
|
|
472
576
|
if (!e) return [];
|
|
473
577
|
let t = /* @__PURE__ */ new Set();
|
|
474
578
|
for (let n of e.matchAll(/!\[[^\]]*]\((https?:\/\/[^)\s]+)\)/g)) n[1] && t.add(n[1].replaceAll("&", "&"));
|
|
@@ -477,7 +581,7 @@ function xt(e) {
|
|
|
477
581
|
}
|
|
478
582
|
//#endregion
|
|
479
583
|
//#region src/utils/storagePath.ts
|
|
480
|
-
function
|
|
584
|
+
function It(e) {
|
|
481
585
|
try {
|
|
482
586
|
let t = new URL(e), n = t.hostname.toLowerCase();
|
|
483
587
|
if (!(n === "firebasestorage.googleapis.com" || n.endsWith(".firebasestorage.app"))) return null;
|
|
@@ -487,106 +591,106 @@ function St(e) {
|
|
|
487
591
|
return null;
|
|
488
592
|
}
|
|
489
593
|
}
|
|
490
|
-
function
|
|
594
|
+
function Lt(e) {
|
|
491
595
|
return e.match(/(?:^|\/)posts\/([^/]+)\//)?.[1] ?? null;
|
|
492
596
|
}
|
|
493
597
|
//#endregion
|
|
494
598
|
//#region src/utils/deletePostCascade.ts
|
|
495
|
-
async function
|
|
599
|
+
async function Rt(e) {
|
|
496
600
|
try {
|
|
497
|
-
let t = await
|
|
498
|
-
await Promise.all([...t.items.map((e) =>
|
|
601
|
+
let t = await Pe(e);
|
|
602
|
+
await Promise.all([...t.items.map((e) => Me(e).catch(() => {})), ...t.prefixes.map((e) => Rt(e))]);
|
|
499
603
|
} catch {}
|
|
500
604
|
}
|
|
501
|
-
async function
|
|
502
|
-
await
|
|
605
|
+
async function zt(e, t) {
|
|
606
|
+
await Rt(Fe(e, `${t}/images`)), await Rt(Fe(e, `${t}/attachments`));
|
|
503
607
|
}
|
|
504
|
-
async function
|
|
608
|
+
async function Bt(e, t) {
|
|
505
609
|
let n = [...new Set([...t].filter(Boolean))];
|
|
506
|
-
await Promise.all(n.map((t) =>
|
|
610
|
+
await Promise.all(n.map((t) => Me(Fe(e, t)).catch(() => {})));
|
|
507
611
|
}
|
|
508
|
-
var
|
|
509
|
-
async function
|
|
510
|
-
let n = await
|
|
511
|
-
for (let t = 0; t < n.docs.length; t +=
|
|
512
|
-
let r =
|
|
513
|
-
n.docs.slice(t, t +
|
|
612
|
+
var Vt = 450;
|
|
613
|
+
async function Ht(e, t) {
|
|
614
|
+
let n = await B(t);
|
|
615
|
+
for (let t = 0; t < n.docs.length; t += Vt) {
|
|
616
|
+
let r = ye(e);
|
|
617
|
+
n.docs.slice(t, t + Vt).forEach((e) => r.delete(e.ref)), await r.commit();
|
|
514
618
|
}
|
|
515
619
|
}
|
|
516
|
-
async function
|
|
517
|
-
await
|
|
620
|
+
async function Ut(e, t, n) {
|
|
621
|
+
await Ht(e, G(a(e, t), X("postId", "==", n)));
|
|
518
622
|
}
|
|
519
|
-
async function
|
|
520
|
-
await
|
|
623
|
+
async function Wt(e, t, n) {
|
|
624
|
+
await Ht(e, G(a(e, t), X("commentId", "==", n), X("target", "==", "comment")));
|
|
521
625
|
}
|
|
522
|
-
async function
|
|
523
|
-
let i = l(e, n, r), a = c(e, n, r), [o, s] = await Promise.all([
|
|
626
|
+
async function Gt(e, t, n, r) {
|
|
627
|
+
let i = l(e, n, r), a = c(e, n, r), [o, s] = await Promise.all([z(i), z(a)]);
|
|
524
628
|
if (!o.exists()) return;
|
|
525
629
|
let d = o.data(), f = s.exists() ? String(s.data().content ?? "") : "";
|
|
526
|
-
await
|
|
630
|
+
await Ht(e, G(u(e, n), X("postId", "==", r))), await Ut(e, n, r);
|
|
527
631
|
let p = /* @__PURE__ */ new Set(), h = /* @__PURE__ */ new Set();
|
|
528
632
|
for (let e of d.attachments ?? []) {
|
|
529
633
|
if (!e?.path) continue;
|
|
530
634
|
p.add(e.path);
|
|
531
|
-
let t =
|
|
635
|
+
let t = Lt(e.path);
|
|
532
636
|
t && t !== r && h.add(t);
|
|
533
637
|
}
|
|
534
|
-
for (let e of
|
|
535
|
-
let t =
|
|
638
|
+
for (let e of Ft(f)) {
|
|
639
|
+
let t = It(e);
|
|
536
640
|
if (!t) continue;
|
|
537
641
|
p.add(t);
|
|
538
642
|
let n = t.replace(/\/images\/([^/]+)$/, "/images/thumbnails/$1").replace(/\.[^.]+$/, ".jpg");
|
|
539
643
|
n !== t && n.includes("/images/thumbnails/") && p.add(n);
|
|
540
644
|
let i = t.match(/^(.*\/images\/)([^/]+)\.[^.]+$/);
|
|
541
645
|
i && !t.includes("/thumbnails/") && p.add(`${i[1]}thumbnails/${i[2]}.jpg`);
|
|
542
|
-
let a =
|
|
646
|
+
let a = Lt(t);
|
|
543
647
|
a && a !== r && h.add(a);
|
|
544
648
|
}
|
|
545
|
-
await
|
|
546
|
-
for (let e of h) await
|
|
547
|
-
await
|
|
649
|
+
await fe(a), await zt(t, m(n, r));
|
|
650
|
+
for (let e of h) await zt(t, m(n, e));
|
|
651
|
+
await Bt(t, p), await fe(i);
|
|
548
652
|
}
|
|
549
653
|
//#endregion
|
|
550
654
|
//#region src/utils/postBody.ts
|
|
551
|
-
function
|
|
655
|
+
function Kt(e) {
|
|
552
656
|
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();
|
|
553
657
|
}
|
|
554
|
-
function
|
|
555
|
-
let n =
|
|
658
|
+
function qt(e, t = 40) {
|
|
659
|
+
let n = Kt(e);
|
|
556
660
|
if (!n) return "";
|
|
557
661
|
if (n.length <= t) return n;
|
|
558
662
|
let r = n.slice(0, t), i = r.lastIndexOf(" ");
|
|
559
663
|
return `${(i >= Math.floor(t * .6) ? r.slice(0, i) : r).trimEnd()}…`;
|
|
560
664
|
}
|
|
561
|
-
function
|
|
562
|
-
return
|
|
665
|
+
function Jt(e) {
|
|
666
|
+
return Kt(e).length > 0;
|
|
563
667
|
}
|
|
564
|
-
function
|
|
668
|
+
function Yt(e, t) {
|
|
565
669
|
return /<img\b/i.test(String(e || "")) ? !0 : (t || []).some((e) => !!e?.type?.startsWith("image/"));
|
|
566
670
|
}
|
|
567
|
-
function
|
|
568
|
-
return !
|
|
671
|
+
function Xt(e) {
|
|
672
|
+
return !Jt(e);
|
|
569
673
|
}
|
|
570
674
|
//#endregion
|
|
571
675
|
//#region src/utils/postPreview.ts
|
|
572
|
-
function
|
|
676
|
+
function Zt(e) {
|
|
573
677
|
return e.replace(/&/g, "&").replace(/"/g, "\"").replace(/'/g, "'");
|
|
574
678
|
}
|
|
575
|
-
function
|
|
679
|
+
function Qt(e) {
|
|
576
680
|
if (e) try {
|
|
577
|
-
let t = new URL(
|
|
681
|
+
let t = new URL(Zt(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
|
|
578
682
|
if (n === "youtu.be") return t.pathname.split("/").filter(Boolean)[0];
|
|
579
683
|
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;
|
|
580
684
|
} catch {
|
|
581
685
|
return;
|
|
582
686
|
}
|
|
583
687
|
}
|
|
584
|
-
function
|
|
585
|
-
let t =
|
|
688
|
+
function $t(e) {
|
|
689
|
+
let t = Qt(e.videoUrl);
|
|
586
690
|
return t ? `https://i.ytimg.com/vi/${t}/hqdefault.jpg` : e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
|
|
587
691
|
}
|
|
588
|
-
function
|
|
589
|
-
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 ?
|
|
692
|
+
function en(e, t = []) {
|
|
693
|
+
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 ? Zt(r[1]) : a, s = o ? Qt(o) : void 0, c = n ? Zt(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
|
|
590
694
|
return {
|
|
591
695
|
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),
|
|
592
696
|
...c ? { previewImage: c } : {},
|
|
@@ -595,44 +699,303 @@ function Bt(e, t = []) {
|
|
|
595
699
|
}
|
|
596
700
|
//#endregion
|
|
597
701
|
//#region src/utils/saveBoardVisibility.ts
|
|
598
|
-
function
|
|
702
|
+
function tn(e) {
|
|
599
703
|
return e === "hidden" ? "hidden" : "public";
|
|
600
704
|
}
|
|
601
|
-
async function
|
|
705
|
+
async function nn(e, t, n, r) {
|
|
602
706
|
let i;
|
|
603
707
|
for (;;) {
|
|
604
|
-
let a = await
|
|
708
|
+
let a = await B(G(t, X("boardId", "==", n), W("__name__"), ...i ? [J(i)] : [], H(400)));
|
|
605
709
|
if (a.empty) break;
|
|
606
|
-
let o =
|
|
710
|
+
let o = ye(e), s = 0;
|
|
607
711
|
for (let e of a.docs) e.data().listed !== r && (o.update(e.ref, { listed: r }), s++);
|
|
608
712
|
if (s && await o.commit(), i = a.docs[a.docs.length - 1], a.docs.length < 400) break;
|
|
609
713
|
}
|
|
610
714
|
}
|
|
611
|
-
async function
|
|
612
|
-
await
|
|
715
|
+
async function rn(e, t, n, r) {
|
|
716
|
+
await nn(e, M(e, t), n, r), await nn(e, u(e, t), n, r);
|
|
613
717
|
}
|
|
614
|
-
async function
|
|
615
|
-
let i = o(e, t, n), a =
|
|
616
|
-
await
|
|
718
|
+
async function an(e, t, n, r) {
|
|
719
|
+
let i = o(e, t, n), a = tn(r.visibility);
|
|
720
|
+
await ve(e, async (e) => {
|
|
617
721
|
let t = await e.get(i), n = t.data(), o = n?.visibilityTransition || "";
|
|
618
722
|
if (o && o !== a) throw Error("Finish the pending visibility change before changing it again.");
|
|
619
|
-
return !t.exists() || !o &&
|
|
723
|
+
return !t.exists() || !o && tn(n?.visibility) === a ? (e.set(i, r, { merge: !0 }), !1) : (e.set(i, {
|
|
620
724
|
...r,
|
|
621
|
-
visibility: a === "public" ? "public" :
|
|
725
|
+
visibility: a === "public" ? "public" : tn(n?.visibility),
|
|
622
726
|
visibilityTransition: a
|
|
623
727
|
}, { merge: !0 }), !0);
|
|
624
|
-
}) && (await
|
|
728
|
+
}) && (await rn(e, t, n, a !== "hidden"), await ve(e, async (e) => {
|
|
625
729
|
if ((await e.get(i)).data()?.visibilityTransition !== a) throw Error("The visibility transition changed. Reload and retry.");
|
|
626
730
|
e.update(i, {
|
|
627
731
|
visibility: a,
|
|
628
732
|
visibilityTransition: "",
|
|
629
|
-
updatedAt:
|
|
733
|
+
updatedAt: K()
|
|
630
734
|
});
|
|
631
735
|
}));
|
|
632
736
|
}
|
|
633
737
|
//#endregion
|
|
738
|
+
//#region src/utils/moderation-prompt.ts
|
|
739
|
+
var on = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\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은 \"\"", sn = {
|
|
740
|
+
ko: "Korean",
|
|
741
|
+
en: "English",
|
|
742
|
+
ja: "Japanese",
|
|
743
|
+
de: "German",
|
|
744
|
+
fr: "French",
|
|
745
|
+
es: "Spanish",
|
|
746
|
+
pt: "Portuguese",
|
|
747
|
+
zh: "Simplified Chinese",
|
|
748
|
+
ar: "Arabic",
|
|
749
|
+
id: "Indonesian"
|
|
750
|
+
};
|
|
751
|
+
function cn(e) {
|
|
752
|
+
let t = sn[e] ?? "English", n = E(e).name;
|
|
753
|
+
return t === n ? t : `${t} (${n})`;
|
|
754
|
+
}
|
|
755
|
+
function ln(e) {
|
|
756
|
+
let t = cn(e);
|
|
757
|
+
return `You are a strict moderator reviewing content for a message board.
|
|
758
|
+
If the text below contains profanity, vulgar slang, sexual harassment, hate speech, threats, spam, or repetitive/flooding advertisements, you MUST reject it (flagged=true).
|
|
759
|
+
|
|
760
|
+
The content may be written in ANY language, or a mix of languages — not only the board's language.
|
|
761
|
+
Judge every language equally strictly, including slang, abbreviations, romanizations, deliberate misspellings,
|
|
762
|
+
inserted spaces/symbols, and other obfuscation (e.g. in Korean, Japanese, Chinese, Arabic, Spanish, etc.).
|
|
763
|
+
|
|
764
|
+
flagged=true examples:
|
|
765
|
+
- Profanity/vulgar slang: fuck, shit, bitch, asshole, f*ck, sh1t /
|
|
766
|
+
Korean: 씨발, 시발, ㅅㅂ, 병신, ㅂㅅ, 지랄, 좆, 개새끼, 존나 /
|
|
767
|
+
Japanese: クソ, 死ね, ちんこ / Chinese: 操你妈, 傻逼, 他妈的 /
|
|
768
|
+
Spanish: puta, mierda, pendejo / German: Scheiße, Hurensohn / French: putain, connard /
|
|
769
|
+
Portuguese: porra, caralho / Indonesian: anjing, bangsat, kontol / Arabic: كس, شرموطة, etc.
|
|
770
|
+
- Sexual insults, hate speech, threats
|
|
771
|
+
- Flooding advertisements/spam unrelated to the board's topic
|
|
772
|
+
|
|
773
|
+
flagged=false:
|
|
774
|
+
- Normal posts/comments that fit the board's topic (strong opinions and legitimate criticism are allowed)
|
|
775
|
+
|
|
776
|
+
Output JSON only:
|
|
777
|
+
{"flagged":boolean,"category":"none"|"abuse"|"spam"|"adult"|"violence"|"other","reason":string}
|
|
778
|
+
- If flagged=true, choose a category, and write reason as ONE concrete sentence in ${t} explaining **what the problem is**
|
|
779
|
+
(e.g. "It contains profanity, so it can't be posted.", "It contains advertising/spam, so it can't be posted." — but written in ${t}).
|
|
780
|
+
Do not quote the offensive words at length; clearly state the type (profanity/hate/spam, etc.).
|
|
781
|
+
- If flagged=false, category is "none" and reason is ""`;
|
|
782
|
+
}
|
|
783
|
+
function un(e = "ko") {
|
|
784
|
+
return e === "ko" ? on : ln(e);
|
|
785
|
+
}
|
|
786
|
+
function dn(e, t = "ko") {
|
|
787
|
+
let n = `"""${e.slice(0, 4e3)}"""`;
|
|
788
|
+
return t === "ko" ? `심사할 텍스트:\n${n}` : `Text to review:\n${n}`;
|
|
789
|
+
}
|
|
790
|
+
function fn(e = "ko") {
|
|
791
|
+
return e === "ko" ? "이 이미지를 심사해 주세요." : "Please review this image.";
|
|
792
|
+
}
|
|
793
|
+
var pn = {
|
|
794
|
+
abuse: "욕설·비속어·모욕",
|
|
795
|
+
spam: "스팸·광고",
|
|
796
|
+
adult: "선정적·성인 내용",
|
|
797
|
+
violence: "폭력·위협",
|
|
798
|
+
other: "부적절한 내용",
|
|
799
|
+
none: ""
|
|
800
|
+
};
|
|
801
|
+
function mn(e, t = "ko") {
|
|
802
|
+
return !e || e === "none" ? "" : e in pn ? w(t, `moderationPrompt.categories.${e}`) : e;
|
|
803
|
+
}
|
|
804
|
+
function hn(e) {
|
|
805
|
+
let t = e.locale ?? "ko";
|
|
806
|
+
if (e.via === "local" && e.localHit) {
|
|
807
|
+
let n = e.localHit.trim();
|
|
808
|
+
return n.length > 0 && n.length <= 12 ? w(t, "moderationPrompt.localHit", { hit: n }) : w(t, "moderationPrompt.localGeneric");
|
|
809
|
+
}
|
|
810
|
+
let n = (e.reason || "").trim();
|
|
811
|
+
if (n) return n;
|
|
812
|
+
let r = e.category ? mn(e.category, t) : "";
|
|
813
|
+
return r ? w(t, "moderationPrompt.categoryBlocked", { category: r }) : w(t, "moderationPrompt.genericBlocked");
|
|
814
|
+
}
|
|
815
|
+
var gn = [
|
|
816
|
+
"씨발",
|
|
817
|
+
"시발",
|
|
818
|
+
"시이발",
|
|
819
|
+
"씨팔",
|
|
820
|
+
"시팔",
|
|
821
|
+
"병신",
|
|
822
|
+
"븅신",
|
|
823
|
+
"지랄",
|
|
824
|
+
"좆",
|
|
825
|
+
"존나",
|
|
826
|
+
"개새끼",
|
|
827
|
+
"개새",
|
|
828
|
+
"새끼",
|
|
829
|
+
"꺼져",
|
|
830
|
+
"닥쳐",
|
|
831
|
+
"씹",
|
|
832
|
+
"니미",
|
|
833
|
+
"느금",
|
|
834
|
+
"애미",
|
|
835
|
+
"애비",
|
|
836
|
+
"fuck",
|
|
837
|
+
"shit",
|
|
838
|
+
"bitch",
|
|
839
|
+
"asshole"
|
|
840
|
+
];
|
|
841
|
+
function _n(e) {
|
|
842
|
+
return new RegExp(e.join("|"), "i");
|
|
843
|
+
}
|
|
844
|
+
var vn = /* @__PURE__ */ new Set([
|
|
845
|
+
"none",
|
|
846
|
+
"abuse",
|
|
847
|
+
"spam",
|
|
848
|
+
"adult",
|
|
849
|
+
"violence",
|
|
850
|
+
"other"
|
|
851
|
+
]);
|
|
852
|
+
function yn(e) {
|
|
853
|
+
let t = e.trim();
|
|
854
|
+
if (!t) return null;
|
|
855
|
+
let n;
|
|
856
|
+
try {
|
|
857
|
+
n = JSON.parse(t);
|
|
858
|
+
} catch {
|
|
859
|
+
let e = t.match(/\{[\s\S]*\}/);
|
|
860
|
+
if (!e) return null;
|
|
861
|
+
try {
|
|
862
|
+
n = JSON.parse(e[0]);
|
|
863
|
+
} catch {
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (typeof n != "object" || !n) return null;
|
|
868
|
+
let r = n;
|
|
869
|
+
if (typeof r.flagged != "boolean") return null;
|
|
870
|
+
let i = typeof r.category == "string" && vn.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
|
|
871
|
+
return {
|
|
872
|
+
flagged: r.flagged,
|
|
873
|
+
category: i,
|
|
874
|
+
reason: a
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
//#endregion
|
|
878
|
+
//#region src/composables/useMemiBoardAutoTranslate.ts
|
|
879
|
+
var bn = 3e4, xn = 45e3, Sn = 12e3, Cn = 3;
|
|
880
|
+
function wn(e) {
|
|
881
|
+
return e.trim().replace(/^```(?:html|text)?\s*/i, "").replace(/\s*```$/, "").trim();
|
|
882
|
+
}
|
|
883
|
+
async function Tn(e, t, n) {
|
|
884
|
+
let r = Array(e.length), i = 0;
|
|
885
|
+
async function a() {
|
|
886
|
+
for (; i < e.length;) {
|
|
887
|
+
let t = i++;
|
|
888
|
+
r[t] = await n(e[t]);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
return await Promise.all(Array.from({ length: Math.min(t, e.length) }, () => a())), r;
|
|
892
|
+
}
|
|
893
|
+
function En(e, t) {
|
|
894
|
+
return new Promise((n, r) => {
|
|
895
|
+
let i = setTimeout(() => r(/* @__PURE__ */ Error(`translation timed out after ${t / 1e3}s`)), t);
|
|
896
|
+
e.then((e) => {
|
|
897
|
+
clearTimeout(i), n(e);
|
|
898
|
+
}, (e) => {
|
|
899
|
+
clearTimeout(i), r(e);
|
|
900
|
+
});
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
function Dn() {
|
|
904
|
+
let e = Ce(), { moderation: t } = ue();
|
|
905
|
+
function n(n, r) {
|
|
906
|
+
let i = Re(e, {
|
|
907
|
+
backend: new Ie(),
|
|
908
|
+
useLimitedUseAppCheckTokens: t.useLimitedUseAppCheckTokens !== !1
|
|
909
|
+
}), a = Le.object({ properties: Object.fromEntries(r.map((e) => [e, Le.string()])) });
|
|
910
|
+
return ze(i, {
|
|
911
|
+
model: t.model ?? "gemini-3.5-flash-lite",
|
|
912
|
+
systemInstruction: [
|
|
913
|
+
"You translate short user-interface strings for a website.",
|
|
914
|
+
"Return natural, concise UI wording a native speaker would expect in a menu or title — not literal word-for-word translations.",
|
|
915
|
+
"Keep proper nouns, brand names, product names and game titles as they are unless they have a well-known localized name.",
|
|
916
|
+
"Use the context only to understand meaning; never translate or output it."
|
|
917
|
+
].join("\n"),
|
|
918
|
+
generationConfig: {
|
|
919
|
+
temperature: .2,
|
|
920
|
+
maxOutputTokens: 2048,
|
|
921
|
+
responseMimeType: "application/json",
|
|
922
|
+
responseSchema: Le.object({ properties: Object.fromEntries(n.map((e) => [e, a])) })
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
async function r(e, t, r, i) {
|
|
927
|
+
let a = Object.keys(e.fields), o = [
|
|
928
|
+
`Purpose: ${i}`,
|
|
929
|
+
`Source language: ${cn(t)} (${t})`,
|
|
930
|
+
`Translate into: ${r.map((e) => `${e} = ${cn(e)}`).join(", ")}`,
|
|
931
|
+
e.context ? `Context (do not translate): ${e.context}` : "",
|
|
932
|
+
"Text (JSON):",
|
|
933
|
+
JSON.stringify(e.fields)
|
|
934
|
+
].filter(Boolean).join("\n"), s = await En(n(r, a).generateContent(o), bn), c = JSON.parse(s.response.text()), l = {};
|
|
935
|
+
for (let e of r) {
|
|
936
|
+
let t = c?.[e];
|
|
937
|
+
if (!t) continue;
|
|
938
|
+
let n = {};
|
|
939
|
+
for (let e of a) {
|
|
940
|
+
let r = typeof t[e] == "string" ? t[e].trim() : "";
|
|
941
|
+
r && (n[e] = r);
|
|
942
|
+
}
|
|
943
|
+
Object.keys(n).length && (l[e] = n);
|
|
944
|
+
}
|
|
945
|
+
return l;
|
|
946
|
+
}
|
|
947
|
+
async function i(e, t = {}) {
|
|
948
|
+
let n = t.source ?? "ko", i = (t.targets ?? A.map((e) => e.code)).filter((e) => e !== n), a = t.purpose ?? "website UI labels", o = e.map((e) => ({
|
|
949
|
+
...e,
|
|
950
|
+
fields: Object.fromEntries(Object.entries(e.fields).map(([e, t]) => [e, t.trim()]).filter(([, e]) => e))
|
|
951
|
+
})).filter((e) => Object.keys(e.fields).length);
|
|
952
|
+
if (!o.length || !i.length) return {};
|
|
953
|
+
let s = await Promise.all(o.map((e) => r(e, n, i, a)));
|
|
954
|
+
return Object.fromEntries(o.map((e, t) => [e.id, s[t]]));
|
|
955
|
+
}
|
|
956
|
+
function a() {
|
|
957
|
+
let n = Re(e, {
|
|
958
|
+
backend: new Ie(),
|
|
959
|
+
useLimitedUseAppCheckTokens: t.useLimitedUseAppCheckTokens !== !1
|
|
960
|
+
});
|
|
961
|
+
return ze(n, {
|
|
962
|
+
model: t.model ?? "gemini-3.5-flash-lite",
|
|
963
|
+
systemInstruction: [
|
|
964
|
+
"You translate an HTML fragment of a message-board post.",
|
|
965
|
+
"Keep every tag, attribute, URL, class, and image source exactly as written.",
|
|
966
|
+
"Translate only human-readable text nodes.",
|
|
967
|
+
"Output only the HTML fragment. Do not add explanations or markdown code fences."
|
|
968
|
+
].join("\n"),
|
|
969
|
+
generationConfig: {
|
|
970
|
+
temperature: .2,
|
|
971
|
+
maxOutputTokens: 8192
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
async function o(e, t = {}) {
|
|
976
|
+
let n = t.source ?? "ko", r = (t.targets ?? A.map((e) => e.code)).filter((e) => e !== n), i = e.trim();
|
|
977
|
+
if (!i || !r.length) return {};
|
|
978
|
+
if (i.length > 12e3) throw Error(`translation is limited to ${Sn} characters`);
|
|
979
|
+
let o = await Tn(r, Cn, async (e) => {
|
|
980
|
+
let t = [
|
|
981
|
+
`Source language: ${cn(n)} (${n})`,
|
|
982
|
+
`Translate into: ${e} = ${cn(e)}`,
|
|
983
|
+
"HTML:",
|
|
984
|
+
i
|
|
985
|
+
].join("\n"), r = wn((await En(a().generateContent(t), xn)).response.text());
|
|
986
|
+
if (!r) throw Error(`empty translation for ${e}`);
|
|
987
|
+
return [e, r];
|
|
988
|
+
});
|
|
989
|
+
return Object.fromEntries(o);
|
|
990
|
+
}
|
|
991
|
+
return {
|
|
992
|
+
translateEntries: i,
|
|
993
|
+
translateHtml: o
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
//#endregion
|
|
634
997
|
//#region src/composables/useMemiBoardSettings.ts
|
|
635
|
-
var
|
|
998
|
+
var On = [
|
|
636
999
|
{
|
|
637
1000
|
id: "free",
|
|
638
1001
|
label: "자유",
|
|
@@ -660,17 +1023,17 @@ var Gt = [
|
|
|
660
1023
|
visibility: "public",
|
|
661
1024
|
order: 2
|
|
662
1025
|
}
|
|
663
|
-
],
|
|
664
|
-
function
|
|
1026
|
+
], kn = On;
|
|
1027
|
+
function An(e) {
|
|
665
1028
|
return e === "hidden" ? "hidden" : "public";
|
|
666
1029
|
}
|
|
667
|
-
function
|
|
1030
|
+
function jn(e, t, n) {
|
|
668
1031
|
return {
|
|
669
1032
|
id: e,
|
|
670
1033
|
label: typeof t.label == "string" && t.label.trim() ? t.label.trim() : e,
|
|
671
1034
|
order: typeof t.order == "number" ? t.order : n,
|
|
672
1035
|
description: (typeof t.description == "string" ? t.description : "") || "",
|
|
673
|
-
visibility:
|
|
1036
|
+
visibility: An(t.visibility),
|
|
674
1037
|
visibilityTransition: t.visibilityTransition === "hidden" || t.visibilityTransition === "public" ? t.visibilityTransition : void 0,
|
|
675
1038
|
listView: t.listView ?? "default",
|
|
676
1039
|
editorType: t.editorType ?? (t.listView === "image" ? "image" : "default"),
|
|
@@ -678,93 +1041,136 @@ function Jt(e, t, n) {
|
|
|
678
1041
|
commentWriteRole: t.commentWriteRole ?? "user",
|
|
679
1042
|
allowedStaffUids: Array.isArray(t.allowedStaffUids) ? t.allowedStaffUids : [],
|
|
680
1043
|
allowedManagerUids: Array.isArray(t.allowedManagerUids) ? t.allowedManagerUids : [],
|
|
681
|
-
locale:
|
|
1044
|
+
locale: T(t.locale) ?? "",
|
|
1045
|
+
i18n: Q(t.i18n),
|
|
1046
|
+
i18nSource: Qe(t.i18nSource)
|
|
682
1047
|
};
|
|
683
1048
|
}
|
|
684
|
-
function
|
|
685
|
-
let e = () =>
|
|
686
|
-
function
|
|
687
|
-
if (e) return
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
function b(e) {
|
|
691
|
-
if (e) return p.value.find((t) => t.id === e)?.description?.trim() || void 0;
|
|
1049
|
+
function Mn() {
|
|
1050
|
+
let e = () => j(), t = Z(), n = Ce(), { isSignedIn: i, isAdmin: a, isStaff: s, user: c } = kt(), { t: l, locale: u } = D(), { translateEntries: d } = Dn(), f = N(() => G(y(t, e()), W("order", "asc"))), { data: p, pending: m } = be(f, { ssrKey: r(e(), "boards") }), h = N(() => p.value.map((e, t) => jn(e.id, e, t))), g = h, _ = N(() => h.value.filter((e) => e.visibility !== "hidden")), v = _;
|
|
1051
|
+
function b(e, t) {
|
|
1052
|
+
if (!e) return;
|
|
1053
|
+
let n = h.value.find((t) => t.id === e);
|
|
1054
|
+
return n ? rt(n.i18n, n.i18nSource, "label", n.label, t ?? u.value) || n.label : e;
|
|
692
1055
|
}
|
|
693
1056
|
let x = b;
|
|
694
|
-
function S(e) {
|
|
695
|
-
|
|
1057
|
+
function S(e, t) {
|
|
1058
|
+
if (!e) return;
|
|
1059
|
+
let n = h.value.find((t) => t.id === e), r = n?.description?.trim();
|
|
1060
|
+
if (n && r) return rt(n.i18n, n.i18nSource, "description", r, t ?? u.value) || void 0;
|
|
696
1061
|
}
|
|
697
1062
|
let C = S;
|
|
698
1063
|
function w(e) {
|
|
699
|
-
return
|
|
1064
|
+
return e ? h.value.find((t) => t.id === e)?.visibility ?? "public" : "public";
|
|
700
1065
|
}
|
|
701
|
-
let
|
|
702
|
-
function
|
|
703
|
-
|
|
1066
|
+
let ee = w;
|
|
1067
|
+
function E(e) {
|
|
1068
|
+
return w(e) === "hidden";
|
|
704
1069
|
}
|
|
705
|
-
|
|
1070
|
+
let O = E;
|
|
1071
|
+
function te(e) {
|
|
1072
|
+
if (e) return h.value.find((t) => t.id === e);
|
|
1073
|
+
}
|
|
1074
|
+
vt((e) => te(e));
|
|
706
1075
|
function k(e) {
|
|
707
|
-
return
|
|
1076
|
+
return ht(c.value?.uid, a.value, s.value, e ? te(e) : void 0);
|
|
708
1077
|
}
|
|
709
|
-
let
|
|
710
|
-
function
|
|
1078
|
+
let A = N(() => h.value.filter((e) => k(e.id)));
|
|
1079
|
+
function re(e, t) {
|
|
711
1080
|
return {
|
|
712
1081
|
label: e.label.trim(),
|
|
713
1082
|
description: (e.description ?? "").trim(),
|
|
714
|
-
visibility:
|
|
1083
|
+
visibility: An(e.visibility),
|
|
715
1084
|
listView: e.listView ?? "default",
|
|
716
1085
|
editorType: e.editorType ?? (e.listView === "image" ? "image" : "default"),
|
|
717
1086
|
writeRole: e.writeRole ?? "user",
|
|
718
1087
|
commentWriteRole: e.commentWriteRole ?? "user",
|
|
719
1088
|
allowedStaffUids: e.allowedStaffUids ?? [],
|
|
720
1089
|
allowedManagerUids: e.allowedManagerUids ?? [],
|
|
721
|
-
locale:
|
|
1090
|
+
locale: T(e.locale) ?? "",
|
|
1091
|
+
...e.i18n ? { i18n: e.i18n } : {},
|
|
1092
|
+
...e.i18nSource ? { i18nSource: e.i18nSource } : {},
|
|
722
1093
|
order: t,
|
|
723
|
-
updatedAt:
|
|
1094
|
+
updatedAt: K()
|
|
724
1095
|
};
|
|
725
1096
|
}
|
|
726
|
-
async function
|
|
1097
|
+
async function ie(e) {
|
|
1098
|
+
let t = e.label.trim(), n = (e.description ?? "").trim();
|
|
1099
|
+
if (!t) return e;
|
|
1100
|
+
if (st({
|
|
1101
|
+
...e,
|
|
1102
|
+
label: t,
|
|
1103
|
+
description: n
|
|
1104
|
+
})) return {
|
|
1105
|
+
...e,
|
|
1106
|
+
label: t,
|
|
1107
|
+
description: n
|
|
1108
|
+
};
|
|
1109
|
+
let r = u.value, i = { label: t };
|
|
1110
|
+
n && (i.description = n);
|
|
1111
|
+
let a = et(r, i, (await d([{
|
|
1112
|
+
id: e.id || "board",
|
|
1113
|
+
fields: i,
|
|
1114
|
+
context: e.id
|
|
1115
|
+
}], {
|
|
1116
|
+
source: r,
|
|
1117
|
+
purpose: "board name and short description shown in navigation and page headers"
|
|
1118
|
+
}))[e.id || "board"]);
|
|
1119
|
+
if (!tt(a, Object.keys(i))) throw Error(l("settings.autoTranslate.failed"));
|
|
1120
|
+
return {
|
|
1121
|
+
...e,
|
|
1122
|
+
label: t,
|
|
1123
|
+
description: n,
|
|
1124
|
+
i18n: a,
|
|
1125
|
+
i18nSource: {
|
|
1126
|
+
locale: r,
|
|
1127
|
+
label: t,
|
|
1128
|
+
description: n
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
async function ae(n, r = n.order ?? 0) {
|
|
727
1133
|
if (!i.value) throw Error(l("boardSettings.signInRequired"));
|
|
728
1134
|
let a = n.id.trim(), o = n.label.trim();
|
|
729
1135
|
if (!a || !o) throw Error(l("boardSettings.idAndNameRequired"));
|
|
730
|
-
let s =
|
|
1136
|
+
let s = An(n.visibility), c = re({
|
|
731
1137
|
...n,
|
|
732
1138
|
label: o,
|
|
733
1139
|
visibility: s
|
|
734
1140
|
}, r);
|
|
735
|
-
await
|
|
1141
|
+
await an(t, e(), a, c);
|
|
736
1142
|
}
|
|
737
|
-
let
|
|
738
|
-
async function
|
|
1143
|
+
let oe = ae;
|
|
1144
|
+
async function se(e) {
|
|
739
1145
|
if (!i.value) throw Error(l("boardSettings.signInRequired"));
|
|
740
1146
|
for (let t of e) if (!t.id.trim() || !t.label.trim()) throw Error(l("boardSettings.idAndNameRequired"));
|
|
741
|
-
for (let [t, n] of e.entries()) await
|
|
1147
|
+
for (let [t, n] of e.entries()) await ae(n, t);
|
|
742
1148
|
}
|
|
743
|
-
let
|
|
744
|
-
async function
|
|
1149
|
+
let ce = se;
|
|
1150
|
+
async function le(r) {
|
|
745
1151
|
if (!i.value) throw Error(l("boardSettings.signInRequired"));
|
|
746
1152
|
let a = r.trim();
|
|
747
1153
|
if (!a) throw Error(l("boardSettings.deleteIdRequired"));
|
|
748
1154
|
for (;;) {
|
|
749
|
-
let r = await
|
|
1155
|
+
let r = await B(G(M(t, e()), X("boardId", "==", a), H(25)));
|
|
750
1156
|
if (r.empty) break;
|
|
751
|
-
for (let i of r.docs) await
|
|
1157
|
+
for (let i of r.docs) await Gt(t, Ne(n), e(), i.id);
|
|
752
1158
|
}
|
|
753
|
-
await
|
|
1159
|
+
await fe(o(t, e(), r));
|
|
754
1160
|
}
|
|
755
|
-
let
|
|
756
|
-
async function
|
|
1161
|
+
let ue = le;
|
|
1162
|
+
async function P() {
|
|
757
1163
|
return Promise.resolve();
|
|
758
1164
|
}
|
|
759
|
-
async function
|
|
1165
|
+
async function de(e) {
|
|
760
1166
|
let t = e.trim();
|
|
761
1167
|
if (!t) throw Error(l("boardSettings.nameRequired"));
|
|
762
1168
|
if (!i.value) throw Error(l("boardSettings.signInRequired"));
|
|
763
|
-
let n =
|
|
1169
|
+
let n = h.value.find((e) => e.label === t);
|
|
764
1170
|
if (n) return n.id;
|
|
765
|
-
let r =
|
|
1171
|
+
let r = ne(t) || `board-${Date.now()}`, a = r, o = 2, s = new Set(h.value.map((e) => e.id));
|
|
766
1172
|
for (; s.has(a);) a = `${r}-${o++}`;
|
|
767
|
-
return await
|
|
1173
|
+
return await ae({
|
|
768
1174
|
id: a,
|
|
769
1175
|
label: t,
|
|
770
1176
|
visibility: "public",
|
|
@@ -772,62 +1178,74 @@ function Yt() {
|
|
|
772
1178
|
editorType: "default",
|
|
773
1179
|
writeRole: "user",
|
|
774
1180
|
commentWriteRole: "user",
|
|
775
|
-
order:
|
|
1181
|
+
order: h.value.length
|
|
776
1182
|
}), a;
|
|
777
1183
|
}
|
|
778
1184
|
return {
|
|
779
|
-
boards:
|
|
780
|
-
publicBoards:
|
|
781
|
-
boardLabel:
|
|
782
|
-
boardDescription:
|
|
783
|
-
boardVisibility:
|
|
784
|
-
isBoardHidden:
|
|
785
|
-
getBoard:
|
|
1185
|
+
boards: h,
|
|
1186
|
+
publicBoards: _,
|
|
1187
|
+
boardLabel: b,
|
|
1188
|
+
boardDescription: S,
|
|
1189
|
+
boardVisibility: w,
|
|
1190
|
+
isBoardHidden: E,
|
|
1191
|
+
getBoard: te,
|
|
786
1192
|
canManageBoard: k,
|
|
787
|
-
managedBoards:
|
|
788
|
-
categories:
|
|
789
|
-
publicCategories:
|
|
790
|
-
categoryLabel:
|
|
791
|
-
categoryDescription:
|
|
792
|
-
categoryVisibility:
|
|
793
|
-
isCategoryHidden:
|
|
794
|
-
settingsPending:
|
|
795
|
-
ensureSettings:
|
|
796
|
-
addBoard:
|
|
797
|
-
addCategory:
|
|
798
|
-
saveBoard:
|
|
799
|
-
saveCategory:
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1193
|
+
managedBoards: A,
|
|
1194
|
+
categories: g,
|
|
1195
|
+
publicCategories: v,
|
|
1196
|
+
categoryLabel: x,
|
|
1197
|
+
categoryDescription: C,
|
|
1198
|
+
categoryVisibility: ee,
|
|
1199
|
+
isCategoryHidden: O,
|
|
1200
|
+
settingsPending: m,
|
|
1201
|
+
ensureSettings: P,
|
|
1202
|
+
addBoard: de,
|
|
1203
|
+
addCategory: de,
|
|
1204
|
+
saveBoard: ae,
|
|
1205
|
+
saveCategory: oe,
|
|
1206
|
+
translateBoard: ie,
|
|
1207
|
+
saveBoards: se,
|
|
1208
|
+
saveCategories: ce,
|
|
1209
|
+
deleteBoard: le,
|
|
1210
|
+
deleteCategory: ue,
|
|
1211
|
+
DEFAULT_BOARDS: On,
|
|
1212
|
+
DEFAULT_CATEGORIES: kn
|
|
806
1213
|
};
|
|
807
1214
|
}
|
|
808
1215
|
//#endregion
|
|
809
1216
|
//#region src/composables/useMemiBoardPosts.ts
|
|
810
|
-
var
|
|
811
|
-
function
|
|
1217
|
+
var Nn = /* @__PURE__ */ new Map();
|
|
1218
|
+
function Pn(e) {
|
|
812
1219
|
if (!e || typeof e != "object") return !1;
|
|
813
1220
|
let t = "code" in e ? String(e.code) : "";
|
|
814
1221
|
return t === "permission-denied" || t === "firestore/permission-denied";
|
|
815
1222
|
}
|
|
816
|
-
function
|
|
817
|
-
let n =
|
|
1223
|
+
function Fn(e, t) {
|
|
1224
|
+
let n = I(e)?.trim();
|
|
818
1225
|
if (!n) throw Error(w(t, "posts.boardIdRequired"));
|
|
819
1226
|
return n;
|
|
820
1227
|
}
|
|
821
|
-
function
|
|
822
|
-
let t =
|
|
823
|
-
function g() {
|
|
824
|
-
return
|
|
1228
|
+
function In(e) {
|
|
1229
|
+
let t = Z(), n = Ce(), { isBoardHidden: r, getBoard: i } = Mn(), { user: a, canManageBoard: o } = kt(), { t: s, locale: u } = D(), d = () => j(), f = () => Fn(e, u.value), p = () => M(t, d()), m = (e) => l(t, d(), e), h = (e) => c(t, d(), e);
|
|
1230
|
+
function g(e, t, n) {
|
|
1231
|
+
return {
|
|
1232
|
+
id: e,
|
|
1233
|
+
...t,
|
|
1234
|
+
i18n: Q(t.i18n),
|
|
1235
|
+
i18nSource: Qe(t.i18nSource),
|
|
1236
|
+
content: typeof n?.content == "string" ? n.content : "",
|
|
1237
|
+
contentI18n: Q(n?.i18n),
|
|
1238
|
+
contentI18nSource: $e(n?.i18nSource)
|
|
1239
|
+
};
|
|
825
1240
|
}
|
|
826
1241
|
function _() {
|
|
1242
|
+
return !r(f());
|
|
1243
|
+
}
|
|
1244
|
+
function v() {
|
|
827
1245
|
return i(f())?.editorType === "image";
|
|
828
1246
|
}
|
|
829
|
-
function
|
|
830
|
-
let t =
|
|
1247
|
+
function y(e) {
|
|
1248
|
+
let t = v(), n = Jt(e.content), r = Yt(e.content, e.attachments);
|
|
831
1249
|
if (t) {
|
|
832
1250
|
if (!r) throw Error(s("posts.photoRequired"));
|
|
833
1251
|
return;
|
|
@@ -835,16 +1253,16 @@ function $t(e) {
|
|
|
835
1253
|
if (!n && !r) throw Error(s("posts.bodyRequired"));
|
|
836
1254
|
if (!e.title?.trim()) throw Error(s("posts.titleRequired"));
|
|
837
1255
|
}
|
|
838
|
-
function
|
|
839
|
-
return
|
|
1256
|
+
function b() {
|
|
1257
|
+
return me(p()).id;
|
|
840
1258
|
}
|
|
841
|
-
function
|
|
842
|
-
return
|
|
1259
|
+
function x(e) {
|
|
1260
|
+
return Pt(t, d(), f(), e, a.value?.uid, o(f()));
|
|
843
1261
|
}
|
|
844
|
-
async function
|
|
845
|
-
let t = e.pageSize ?? 20, n = [
|
|
846
|
-
e.publicOnly !== !1 && n.push(
|
|
847
|
-
let r = await
|
|
1262
|
+
async function S(e = {}) {
|
|
1263
|
+
let t = e.pageSize ?? 20, n = [X("boardId", "==", f()), X("isPublished", "==", !0)];
|
|
1264
|
+
e.publicOnly !== !1 && n.push(X("listed", "==", !0)), n.push(W("createdAt", "desc")), e.cursor && n.push(J(e.cursor)), n.push(H(t + 1));
|
|
1265
|
+
let r = await B(G(p(), ...n)), i = r.docs.slice(0, t);
|
|
848
1266
|
return {
|
|
849
1267
|
posts: i.map((e) => ({
|
|
850
1268
|
id: e.id,
|
|
@@ -854,58 +1272,54 @@ function $t(e) {
|
|
|
854
1272
|
hasMore: r.docs.length > t
|
|
855
1273
|
};
|
|
856
1274
|
}
|
|
857
|
-
async function S(e) {
|
|
858
|
-
let [t, n] = await Promise.all([B(m(e)), B(h(e))]);
|
|
859
|
-
return t.exists() ? {
|
|
860
|
-
id: t.id,
|
|
861
|
-
...t.data(),
|
|
862
|
-
content: n.exists() ? n.data().content ?? "" : ""
|
|
863
|
-
} : null;
|
|
864
|
-
}
|
|
865
1275
|
async function C(e) {
|
|
1276
|
+
let [t, n] = await Promise.all([z(m(e)), z(h(e))]);
|
|
1277
|
+
return t.exists() ? g(t.id, t.data(), n.exists() ? n.data() : void 0) : null;
|
|
1278
|
+
}
|
|
1279
|
+
async function w(e) {
|
|
866
1280
|
try {
|
|
867
|
-
let t = await
|
|
1281
|
+
let t = await x(e.trim());
|
|
868
1282
|
if (!t) return { status: "not-found" };
|
|
869
1283
|
try {
|
|
870
|
-
let e = await
|
|
1284
|
+
let e = await C(t.id);
|
|
871
1285
|
return e ? {
|
|
872
1286
|
status: "ok",
|
|
873
1287
|
post: e
|
|
874
1288
|
} : { status: "not-found" };
|
|
875
1289
|
} catch (e) {
|
|
876
|
-
return
|
|
1290
|
+
return Pn(e) ? { status: "permission-denied" } : {
|
|
877
1291
|
status: "error",
|
|
878
1292
|
message: e instanceof Error ? e.message : String(e)
|
|
879
1293
|
};
|
|
880
1294
|
}
|
|
881
1295
|
} catch (e) {
|
|
882
|
-
return
|
|
1296
|
+
return Pn(e) ? { status: "permission-denied" } : {
|
|
883
1297
|
status: "error",
|
|
884
1298
|
message: e instanceof Error ? e.message : String(e)
|
|
885
1299
|
};
|
|
886
1300
|
}
|
|
887
1301
|
}
|
|
888
|
-
async function
|
|
889
|
-
let t = e.trim(), n =
|
|
1302
|
+
async function ee(e) {
|
|
1303
|
+
let t = e.trim(), n = Nn.get(`${f()}/${t}`);
|
|
890
1304
|
if (n) return {
|
|
891
1305
|
status: "ok",
|
|
892
1306
|
id: n
|
|
893
1307
|
};
|
|
894
1308
|
try {
|
|
895
|
-
let e = await
|
|
1309
|
+
let e = await x(t);
|
|
896
1310
|
return e ? {
|
|
897
1311
|
status: "ok",
|
|
898
1312
|
id: e.id
|
|
899
1313
|
} : { status: r(f()) ? "permission-denied" : "not-found" };
|
|
900
1314
|
} catch (e) {
|
|
901
|
-
return
|
|
1315
|
+
return Pn(e) ? { status: "permission-denied" } : {
|
|
902
1316
|
status: "error",
|
|
903
1317
|
message: e instanceof Error ? e.message : String(e)
|
|
904
1318
|
};
|
|
905
1319
|
}
|
|
906
1320
|
}
|
|
907
1321
|
async function T(e) {
|
|
908
|
-
return `${
|
|
1322
|
+
return `${ne(e.trim()) || "post"}-${b()}`;
|
|
909
1323
|
}
|
|
910
1324
|
async function E(e) {
|
|
911
1325
|
if (!e.createdAt) return {
|
|
@@ -914,11 +1328,11 @@ function $t(e) {
|
|
|
914
1328
|
};
|
|
915
1329
|
try {
|
|
916
1330
|
let t = [
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
...e.listed === !1 ? [] : [
|
|
920
|
-
|
|
921
|
-
], [n, r] = await Promise.all([
|
|
1331
|
+
X("boardId", "==", f()),
|
|
1332
|
+
X("isPublished", "==", !0),
|
|
1333
|
+
...e.listed === !1 ? [] : [X("listed", "==", !0)],
|
|
1334
|
+
W("createdAt", "desc")
|
|
1335
|
+
], [n, r] = await Promise.all([B(G(p(), ...t, J(e.createdAt), H(1))), B(G(p(), ...t, ge(e.createdAt), _e(1)))]), i = (e) => {
|
|
922
1336
|
let t = e.docs[0];
|
|
923
1337
|
return t ? {
|
|
924
1338
|
id: t.id,
|
|
@@ -936,11 +1350,11 @@ function $t(e) {
|
|
|
936
1350
|
};
|
|
937
1351
|
}
|
|
938
1352
|
}
|
|
939
|
-
async function
|
|
940
|
-
|
|
941
|
-
let n = e.postId ||
|
|
942
|
-
r ? (i = n, a =
|
|
943
|
-
let o =
|
|
1353
|
+
async function O(e) {
|
|
1354
|
+
y(e);
|
|
1355
|
+
let n = e.postId || b(), r = v(), i, a;
|
|
1356
|
+
r ? (i = n, a = qt(e.content)) : (a = e.title.trim(), i = e.slug?.trim() || await T(a));
|
|
1357
|
+
let o = en(e.content, e.attachments), s = _(), c = ye(t);
|
|
944
1358
|
return c.set(m(n), {
|
|
945
1359
|
boardId: f(),
|
|
946
1360
|
slug: i,
|
|
@@ -958,56 +1372,93 @@ function $t(e) {
|
|
|
958
1372
|
authorPhoto: e.authorPhoto,
|
|
959
1373
|
moderationStatus: "approved",
|
|
960
1374
|
moderationModel: e.moderationModel ?? null,
|
|
961
|
-
createdAt:
|
|
962
|
-
updatedAt:
|
|
963
|
-
|
|
1375
|
+
createdAt: K(),
|
|
1376
|
+
updatedAt: K(),
|
|
1377
|
+
...e.translations ? {
|
|
1378
|
+
i18n: e.translations.i18n,
|
|
1379
|
+
i18nSource: e.translations.i18nSource
|
|
1380
|
+
} : {}
|
|
1381
|
+
}), c.set(h(n), {
|
|
1382
|
+
content: e.content,
|
|
1383
|
+
...e.translations?.contentI18n ? {
|
|
1384
|
+
i18n: e.translations.contentI18n,
|
|
1385
|
+
i18nSource: e.translations.contentI18nSource
|
|
1386
|
+
} : {}
|
|
1387
|
+
}), await c.commit(), Nn.set(`${f()}/${i}`, n), i;
|
|
964
1388
|
}
|
|
965
|
-
async function
|
|
966
|
-
|
|
967
|
-
let n =
|
|
968
|
-
await Promise.all([
|
|
1389
|
+
async function te(e, t) {
|
|
1390
|
+
y(t);
|
|
1391
|
+
let n = v() ? qt(t.content) : t.title.trim(), r = en(t.content, t.attachments), i = _();
|
|
1392
|
+
await Promise.all([Y(m(e), {
|
|
969
1393
|
title: n,
|
|
970
1394
|
...r,
|
|
971
|
-
previewImage: r.previewImage ? r.previewImage :
|
|
972
|
-
videoUrl: r.videoUrl ? r.videoUrl :
|
|
1395
|
+
previewImage: r.previewImage ? r.previewImage : pe(),
|
|
1396
|
+
videoUrl: r.videoUrl ? r.videoUrl : pe(),
|
|
973
1397
|
tags: t.tags ?? [],
|
|
974
1398
|
listed: i,
|
|
975
1399
|
attachments: t.attachments ?? [],
|
|
976
|
-
updatedAt:
|
|
977
|
-
|
|
1400
|
+
updatedAt: K(),
|
|
1401
|
+
...t.translations ? {
|
|
1402
|
+
i18n: t.translations.i18n,
|
|
1403
|
+
i18nSource: t.translations.i18nSource
|
|
1404
|
+
} : {}
|
|
1405
|
+
}), q(h(e), {
|
|
1406
|
+
content: t.content,
|
|
1407
|
+
...t.translations ? t.translations.contentI18n ? {
|
|
1408
|
+
i18n: t.translations.contentI18n,
|
|
1409
|
+
i18nSource: t.translations.contentI18nSource
|
|
1410
|
+
} : {
|
|
1411
|
+
i18n: pe(),
|
|
1412
|
+
i18nSource: pe()
|
|
1413
|
+
} : {}
|
|
1414
|
+
}, { merge: !0 })]);
|
|
978
1415
|
}
|
|
979
|
-
async function
|
|
980
|
-
await
|
|
1416
|
+
async function k(e, t) {
|
|
1417
|
+
await Promise.all([Y(m(e), {
|
|
1418
|
+
i18n: t.i18n,
|
|
1419
|
+
i18nSource: t.i18nSource,
|
|
1420
|
+
updatedAt: K()
|
|
1421
|
+
}), q(h(e), t.contentI18n ? {
|
|
1422
|
+
i18n: t.contentI18n,
|
|
1423
|
+
i18nSource: t.contentI18nSource
|
|
1424
|
+
} : {
|
|
1425
|
+
i18n: pe(),
|
|
1426
|
+
i18nSource: pe()
|
|
1427
|
+
}, { merge: !0 })]);
|
|
1428
|
+
}
|
|
1429
|
+
async function A(e) {
|
|
1430
|
+
await Y(m(e), {
|
|
981
1431
|
isPublished: !0,
|
|
982
|
-
publishedAt:
|
|
983
|
-
updatedAt:
|
|
1432
|
+
publishedAt: K(),
|
|
1433
|
+
updatedAt: K()
|
|
984
1434
|
});
|
|
985
1435
|
}
|
|
986
|
-
async function
|
|
987
|
-
await
|
|
988
|
-
for (let [t, n] of
|
|
1436
|
+
async function re(e) {
|
|
1437
|
+
await Gt(t, Ne(n), d(), e);
|
|
1438
|
+
for (let [t, n] of Nn) n === e && Nn.delete(t);
|
|
989
1439
|
}
|
|
990
1440
|
return {
|
|
991
|
-
createPostId:
|
|
992
|
-
getPosts:
|
|
993
|
-
getPost:
|
|
994
|
-
getPostBySlug:
|
|
995
|
-
resolvePostIdBySlug:
|
|
1441
|
+
createPostId: b,
|
|
1442
|
+
getPosts: S,
|
|
1443
|
+
getPost: C,
|
|
1444
|
+
getPostBySlug: w,
|
|
1445
|
+
resolvePostIdBySlug: ee,
|
|
996
1446
|
resolveUniqueSlug: T,
|
|
997
1447
|
getAdjacentPosts: E,
|
|
998
|
-
createPost:
|
|
999
|
-
updatePost:
|
|
1000
|
-
|
|
1001
|
-
|
|
1448
|
+
createPost: O,
|
|
1449
|
+
updatePost: te,
|
|
1450
|
+
savePostTranslations: k,
|
|
1451
|
+
publishPost: A,
|
|
1452
|
+
deletePost: re
|
|
1002
1453
|
};
|
|
1003
1454
|
}
|
|
1004
|
-
var
|
|
1005
|
-
function
|
|
1006
|
-
let n =
|
|
1455
|
+
var Ln = 10;
|
|
1456
|
+
function Rn(e, t = {}) {
|
|
1457
|
+
let n = Z(), { isAdmin: r, user: i, canManageBoard: a } = kt(), o = () => j(), s = t.pageSize ?? Ln, c = N(() => i.value?.uid ?? ""), l = N(() => Fn(e)), u = () => M(n, o()), d = N(() => t.publicOnly === void 0 ? !(r.value || a(l.value)) : typeof t.publicOnly == "boolean" ? t.publicOnly : t.publicOnly.value), f = F([]), p = F([]), m = F([]), h = F(!0), g = F(!0), _ = F(!1), v = F(""), y = null, b = null, x, S;
|
|
1007
1458
|
function C(e) {
|
|
1008
1459
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
1009
1460
|
}
|
|
1010
|
-
let w =
|
|
1461
|
+
let w = N(() => {
|
|
1011
1462
|
let e = /* @__PURE__ */ new Map();
|
|
1012
1463
|
for (let t of [
|
|
1013
1464
|
...p.value,
|
|
@@ -1016,12 +1467,12 @@ function tn(e, t = {}) {
|
|
|
1016
1467
|
]) t.id && e.set(t.id, t);
|
|
1017
1468
|
return [...e.values()].sort((e, t) => C(t) - C(e) || String(t.id).localeCompare(String(e.id)));
|
|
1018
1469
|
});
|
|
1019
|
-
function
|
|
1020
|
-
let e = [
|
|
1021
|
-
return d.value && e.push(
|
|
1470
|
+
function ee() {
|
|
1471
|
+
let e = [X("boardId", "==", l.value), X("isPublished", "==", !0)];
|
|
1472
|
+
return d.value && e.push(X("listed", "==", !0)), e;
|
|
1022
1473
|
}
|
|
1023
|
-
function
|
|
1024
|
-
x?.(), x =
|
|
1474
|
+
function T() {
|
|
1475
|
+
x?.(), x = U(G(u(), ...ee(), W("createdAt", "desc"), W(R(), "desc"), H(s)), (e) => {
|
|
1025
1476
|
f.value = e.docs.map((e) => ({
|
|
1026
1477
|
id: e.id,
|
|
1027
1478
|
...e.data()
|
|
@@ -1030,14 +1481,14 @@ function tn(e, t = {}) {
|
|
|
1030
1481
|
console.error("[memi-board:postList] onSnapshot failed", e), v.value = e.message || String(e), h.value = !1;
|
|
1031
1482
|
});
|
|
1032
1483
|
}
|
|
1033
|
-
function
|
|
1484
|
+
function E() {
|
|
1034
1485
|
S?.();
|
|
1035
1486
|
let e = c.value;
|
|
1036
1487
|
if (!e) {
|
|
1037
1488
|
m.value = [];
|
|
1038
1489
|
return;
|
|
1039
1490
|
}
|
|
1040
|
-
S =
|
|
1491
|
+
S = U(G(u(), X("boardId", "==", l.value), X("authorUid", "==", e), X("isPublished", "==", !1), W("createdAt", "desc"), H(20)), (e) => {
|
|
1041
1492
|
m.value = e.docs.map((e) => ({
|
|
1042
1493
|
id: e.id,
|
|
1043
1494
|
...e.data()
|
|
@@ -1046,17 +1497,17 @@ function tn(e, t = {}) {
|
|
|
1046
1497
|
console.error("[memi-board:postList] own draft onSnapshot failed", e);
|
|
1047
1498
|
});
|
|
1048
1499
|
}
|
|
1049
|
-
async function
|
|
1500
|
+
async function D() {
|
|
1050
1501
|
if (!(_.value || !g.value || h.value)) {
|
|
1051
1502
|
_.value = !0, v.value = "";
|
|
1052
1503
|
try {
|
|
1053
1504
|
let e = b ?? y, t = [
|
|
1054
|
-
...
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
...e ? [
|
|
1058
|
-
|
|
1059
|
-
], n = await
|
|
1505
|
+
...ee(),
|
|
1506
|
+
W("createdAt", "desc"),
|
|
1507
|
+
W(R(), "desc"),
|
|
1508
|
+
...e ? [J(e)] : [],
|
|
1509
|
+
H(s + 1)
|
|
1510
|
+
], n = await B(G(u(), ...t)), r = n.docs.slice(0, s), i = r.map((e) => ({
|
|
1060
1511
|
id: e.id,
|
|
1061
1512
|
...e.data()
|
|
1062
1513
|
})), a = new Set([...p.value, ...f.value].map((e) => e.id));
|
|
@@ -1068,14 +1519,14 @@ function tn(e, t = {}) {
|
|
|
1068
1519
|
}
|
|
1069
1520
|
}
|
|
1070
1521
|
}
|
|
1071
|
-
function
|
|
1072
|
-
x?.(), f.value = [], p.value = [], y = null, b = null, g.value = !0, v.value = "", h.value = !0,
|
|
1522
|
+
function O() {
|
|
1523
|
+
x?.(), f.value = [], p.value = [], y = null, b = null, g.value = !0, v.value = "", h.value = !0, T(), E();
|
|
1073
1524
|
}
|
|
1074
|
-
return
|
|
1525
|
+
return L([
|
|
1075
1526
|
l,
|
|
1076
1527
|
d,
|
|
1077
1528
|
c
|
|
1078
|
-
],
|
|
1529
|
+
], O, { immediate: !0 }), P(() => {
|
|
1079
1530
|
x?.(), S?.();
|
|
1080
1531
|
}), {
|
|
1081
1532
|
posts: w,
|
|
@@ -1083,25 +1534,33 @@ function tn(e, t = {}) {
|
|
|
1083
1534
|
hasMore: g,
|
|
1084
1535
|
loadingMore: _,
|
|
1085
1536
|
loadError: v,
|
|
1086
|
-
loadMore:
|
|
1537
|
+
loadMore: D
|
|
1087
1538
|
};
|
|
1088
1539
|
}
|
|
1089
|
-
function
|
|
1090
|
-
let n =
|
|
1540
|
+
function zn(e, t) {
|
|
1541
|
+
let n = Z(), r = () => j(), i = N(() => Fn(e)), a = N(() => typeof t == "string" ? t : t.value), o = N(() => l(n, r(), a.value)), s = N(() => c(n, r(), a.value)), { data: u, pending: d } = Se(o), { data: f, pending: p } = Se(s);
|
|
1091
1542
|
return {
|
|
1092
|
-
post:
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1543
|
+
post: N(() => {
|
|
1544
|
+
if (!u.value || u.value.boardId !== i.value) return null;
|
|
1545
|
+
let e = f.value;
|
|
1546
|
+
return {
|
|
1547
|
+
...u.value,
|
|
1548
|
+
id: a.value,
|
|
1549
|
+
i18n: Q(u.value.i18n),
|
|
1550
|
+
i18nSource: Qe(u.value.i18nSource),
|
|
1551
|
+
content: f.value?.content ?? "",
|
|
1552
|
+
contentI18n: Q(e?.i18n),
|
|
1553
|
+
contentI18nSource: $e(e?.i18nSource)
|
|
1554
|
+
};
|
|
1096
1555
|
}),
|
|
1097
|
-
pending:
|
|
1556
|
+
pending: N(() => d.value || p.value)
|
|
1098
1557
|
};
|
|
1099
1558
|
}
|
|
1100
1559
|
//#endregion
|
|
1101
1560
|
//#region src/composables/useMemiBoardUserPosts.ts
|
|
1102
|
-
var
|
|
1103
|
-
function
|
|
1104
|
-
let n =
|
|
1561
|
+
var Bn = 10;
|
|
1562
|
+
function Vn(e, t = {}) {
|
|
1563
|
+
let n = Z(), r = () => j(), i = () => M(n, r()), a = t.pageSize ?? Bn, o = N(() => I(e)?.trim() || ""), s = F([]), c = F([]), l = F(!0), u = F(!0), d = F(!1), f = F(""), p = null, m = null, h;
|
|
1105
1564
|
function g(e) {
|
|
1106
1565
|
return {
|
|
1107
1566
|
id: e.id,
|
|
@@ -1111,16 +1570,16 @@ function an(e, t = {}) {
|
|
|
1111
1570
|
function _(e) {
|
|
1112
1571
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
1113
1572
|
}
|
|
1114
|
-
let v =
|
|
1573
|
+
let v = N(() => {
|
|
1115
1574
|
let e = /* @__PURE__ */ new Map();
|
|
1116
1575
|
for (let t of [...c.value, ...s.value]) t.id && e.set(t.id, t);
|
|
1117
1576
|
return [...e.values()].sort((e, t) => _(t) - _(e) || String(t.id).localeCompare(String(e.id)));
|
|
1118
1577
|
});
|
|
1119
1578
|
function y() {
|
|
1120
1579
|
return [
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1580
|
+
X("authorUid", "==", o.value),
|
|
1581
|
+
X("isPublished", "==", !0),
|
|
1582
|
+
X("listed", "==", !0)
|
|
1124
1583
|
];
|
|
1125
1584
|
}
|
|
1126
1585
|
function b() {
|
|
@@ -1128,7 +1587,7 @@ function an(e, t = {}) {
|
|
|
1128
1587
|
l.value = !1;
|
|
1129
1588
|
return;
|
|
1130
1589
|
}
|
|
1131
|
-
h =
|
|
1590
|
+
h = U(G(i(), ...y(), W("createdAt", "desc"), W(R(), "desc"), H(a)), (e) => {
|
|
1132
1591
|
s.value = e.docs.map(g), p = e.docs.at(-1) ?? null, m || (u.value = e.docs.length >= a), l.value = !1;
|
|
1133
1592
|
}, (e) => {
|
|
1134
1593
|
console.error("[memi-board:userPosts] onSnapshot failed", e), f.value = e.message || String(e), l.value = !1;
|
|
@@ -1140,11 +1599,11 @@ function an(e, t = {}) {
|
|
|
1140
1599
|
try {
|
|
1141
1600
|
let e = m ?? p, t = [
|
|
1142
1601
|
...y(),
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
...e ? [
|
|
1146
|
-
|
|
1147
|
-
], n = await
|
|
1602
|
+
W("createdAt", "desc"),
|
|
1603
|
+
W(R(), "desc"),
|
|
1604
|
+
...e ? [J(e)] : [],
|
|
1605
|
+
H(a + 1)
|
|
1606
|
+
], n = await B(G(i(), ...t)), r = n.docs.slice(0, a), o = r.map(g), l = new Set([...c.value, ...s.value].map((e) => e.id));
|
|
1148
1607
|
c.value.push(...o.filter((e) => !l.has(e.id))), m = r.at(-1) ?? m, u.value = n.docs.length > a;
|
|
1149
1608
|
} catch (e) {
|
|
1150
1609
|
f.value = e.message || String(e), console.error("[memi-board:userPosts] loadMore failed", e);
|
|
@@ -1156,7 +1615,7 @@ function an(e, t = {}) {
|
|
|
1156
1615
|
function S() {
|
|
1157
1616
|
h?.(), s.value = [], c.value = [], p = null, m = null, u.value = !0, f.value = "", l.value = !0, b();
|
|
1158
1617
|
}
|
|
1159
|
-
return
|
|
1618
|
+
return L(o, S, { immediate: !0 }), P(() => h?.()), {
|
|
1160
1619
|
posts: v,
|
|
1161
1620
|
postsPending: l,
|
|
1162
1621
|
hasMore: u,
|
|
@@ -1167,9 +1626,9 @@ function an(e, t = {}) {
|
|
|
1167
1626
|
}
|
|
1168
1627
|
//#endregion
|
|
1169
1628
|
//#region src/composables/useMemiBoardUserComments.ts
|
|
1170
|
-
var
|
|
1171
|
-
function
|
|
1172
|
-
let n =
|
|
1629
|
+
var Hn = 10;
|
|
1630
|
+
function Un(e, t = {}) {
|
|
1631
|
+
let n = Z(), r = () => j(), i = () => u(n, r()), a = t.pageSize ?? Hn, o = N(() => I(e)?.trim() || ""), s = F([]), c = F([]), l = F(!0), d = F(!0), f = F(!1), p = F(""), m = null, h = null, g;
|
|
1173
1632
|
function _(e) {
|
|
1174
1633
|
return {
|
|
1175
1634
|
id: e.id,
|
|
@@ -1179,20 +1638,20 @@ function sn(e, t = {}) {
|
|
|
1179
1638
|
function v(e) {
|
|
1180
1639
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
1181
1640
|
}
|
|
1182
|
-
let y =
|
|
1641
|
+
let y = N(() => {
|
|
1183
1642
|
let e = /* @__PURE__ */ new Map();
|
|
1184
1643
|
for (let t of [...c.value, ...s.value]) t.id && e.set(t.id, t);
|
|
1185
1644
|
return [...e.values()].sort((e, t) => v(t) - v(e) || String(t.id).localeCompare(String(e.id)));
|
|
1186
1645
|
});
|
|
1187
1646
|
function b() {
|
|
1188
|
-
return [
|
|
1647
|
+
return [X("authorUid", "==", o.value), X("listed", "==", !0)];
|
|
1189
1648
|
}
|
|
1190
1649
|
function x() {
|
|
1191
1650
|
if (g?.(), !o.value) {
|
|
1192
1651
|
l.value = !1;
|
|
1193
1652
|
return;
|
|
1194
1653
|
}
|
|
1195
|
-
g =
|
|
1654
|
+
g = U(G(i(), ...b(), W("createdAt", "desc"), W(R(), "desc"), H(a)), (e) => {
|
|
1196
1655
|
s.value = e.docs.map(_), m = e.docs.at(-1) ?? null, h || (d.value = e.docs.length >= a), l.value = !1;
|
|
1197
1656
|
}, (e) => {
|
|
1198
1657
|
console.error("[memi-board:userComments] onSnapshot failed", e), p.value = e.message || String(e), l.value = !1;
|
|
@@ -1204,11 +1663,11 @@ function sn(e, t = {}) {
|
|
|
1204
1663
|
try {
|
|
1205
1664
|
let e = h ?? m, t = [
|
|
1206
1665
|
...b(),
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
...e ? [
|
|
1210
|
-
|
|
1211
|
-
], n = await
|
|
1666
|
+
W("createdAt", "desc"),
|
|
1667
|
+
W(R(), "desc"),
|
|
1668
|
+
...e ? [J(e)] : [],
|
|
1669
|
+
H(a + 1)
|
|
1670
|
+
], n = await B(G(i(), ...t)), r = n.docs.slice(0, a), o = r.map(_), l = new Set([...c.value, ...s.value].map((e) => e.id));
|
|
1212
1671
|
c.value.push(...o.filter((e) => !l.has(e.id))), h = r.at(-1) ?? h, d.value = n.docs.length > a;
|
|
1213
1672
|
} catch (e) {
|
|
1214
1673
|
p.value = e.message || String(e), console.error("[memi-board:userComments] loadMore failed", e);
|
|
@@ -1220,7 +1679,7 @@ function sn(e, t = {}) {
|
|
|
1220
1679
|
function C() {
|
|
1221
1680
|
g?.(), s.value = [], c.value = [], m = null, h = null, d.value = !0, p.value = "", l.value = !0, x();
|
|
1222
1681
|
}
|
|
1223
|
-
return
|
|
1682
|
+
return L(o, C, { immediate: !0 }), P(() => g?.()), {
|
|
1224
1683
|
comments: y,
|
|
1225
1684
|
commentsPending: l,
|
|
1226
1685
|
hasMore: d,
|
|
@@ -1231,12 +1690,12 @@ function sn(e, t = {}) {
|
|
|
1231
1690
|
}
|
|
1232
1691
|
//#endregion
|
|
1233
1692
|
//#region src/composables/useMemiBoardCommentFeed.ts
|
|
1234
|
-
var
|
|
1235
|
-
function
|
|
1236
|
-
let n =
|
|
1237
|
-
let e = Math.floor(Number(
|
|
1238
|
-
return Number.isFinite(e) && e > 0 ? e :
|
|
1239
|
-
}), o =
|
|
1693
|
+
var Wn = 10;
|
|
1694
|
+
function Gn(e = "latest", t = {}) {
|
|
1695
|
+
let n = Z(), r = () => j(), i = () => u(n, r()), a = N(() => {
|
|
1696
|
+
let e = Math.floor(Number(I(t.pageSize)));
|
|
1697
|
+
return Number.isFinite(e) && e > 0 ? e : Wn;
|
|
1698
|
+
}), o = N(() => I(e) === "likes" ? "likes" : "latest"), s = F([]), c = F([]), l = F(!0), d = F(!0), f = F(!1), p = F(""), m = null, h = null, g;
|
|
1240
1699
|
function _(e) {
|
|
1241
1700
|
return {
|
|
1242
1701
|
id: e.id,
|
|
@@ -1253,25 +1712,25 @@ function ln(e = "latest", t = {}) {
|
|
|
1253
1712
|
}
|
|
1254
1713
|
return v(t) - v(e) || String(t.id).localeCompare(String(e.id));
|
|
1255
1714
|
}
|
|
1256
|
-
let b =
|
|
1715
|
+
let b = N(() => {
|
|
1257
1716
|
let e = /* @__PURE__ */ new Map();
|
|
1258
1717
|
for (let t of [...c.value, ...s.value]) t.id && e.set(t.id, t);
|
|
1259
1718
|
return [...e.values()].sort(y);
|
|
1260
1719
|
});
|
|
1261
1720
|
function x() {
|
|
1262
1721
|
return o.value === "likes" ? [
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1722
|
+
X("listed", "==", !0),
|
|
1723
|
+
W("likeCount", "desc"),
|
|
1724
|
+
W("createdAt", "desc"),
|
|
1725
|
+
W(R(), "desc")
|
|
1267
1726
|
] : [
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1727
|
+
X("listed", "==", !0),
|
|
1728
|
+
W("createdAt", "desc"),
|
|
1729
|
+
W(R(), "desc")
|
|
1271
1730
|
];
|
|
1272
1731
|
}
|
|
1273
1732
|
function S() {
|
|
1274
|
-
g?.(), g =
|
|
1733
|
+
g?.(), g = U(G(i(), ...x(), H(a.value)), (e) => {
|
|
1275
1734
|
s.value = e.docs.map(_), m = e.docs.at(-1) ?? null, h || (d.value = e.docs.length >= a.value), l.value = !1;
|
|
1276
1735
|
}, (e) => {
|
|
1277
1736
|
console.error("[memi-board:commentFeed] onSnapshot failed", e), p.value = e.message || String(e), l.value = !1;
|
|
@@ -1281,7 +1740,7 @@ function ln(e = "latest", t = {}) {
|
|
|
1281
1740
|
if (!(f.value || !d.value || l.value)) {
|
|
1282
1741
|
f.value = !0, p.value = "";
|
|
1283
1742
|
try {
|
|
1284
|
-
let e = h ?? m, t = await
|
|
1743
|
+
let e = h ?? m, t = await B(G(i(), ...x(), ...e ? [J(e)] : [], H(a.value + 1))), n = t.docs.slice(0, a.value), r = n.map(_), o = new Set([...c.value, ...s.value].map((e) => e.id));
|
|
1285
1744
|
c.value.push(...r.filter((e) => !o.has(e.id))), h = n.at(-1) ?? h, d.value = t.docs.length > a.value;
|
|
1286
1745
|
} catch (e) {
|
|
1287
1746
|
p.value = e.message || String(e), console.error("[memi-board:commentFeed] loadMore failed", e);
|
|
@@ -1293,7 +1752,7 @@ function ln(e = "latest", t = {}) {
|
|
|
1293
1752
|
function w() {
|
|
1294
1753
|
g?.(), s.value = [], c.value = [], m = null, h = null, d.value = !0, p.value = "", l.value = !0, S();
|
|
1295
1754
|
}
|
|
1296
|
-
return
|
|
1755
|
+
return L([o, a], w, { immediate: !0 }), P(() => g?.()), {
|
|
1297
1756
|
comments: b,
|
|
1298
1757
|
commentsPending: l,
|
|
1299
1758
|
hasMore: d,
|
|
@@ -1305,35 +1764,35 @@ function ln(e = "latest", t = {}) {
|
|
|
1305
1764
|
}
|
|
1306
1765
|
//#endregion
|
|
1307
1766
|
//#region src/composables/useMemiBoardAuthorMemo.ts
|
|
1308
|
-
var
|
|
1767
|
+
var Kn = 100, qn = /* @__PURE__ */ new Map();
|
|
1309
1768
|
function $(e) {
|
|
1310
|
-
let t =
|
|
1311
|
-
return t || (t =
|
|
1769
|
+
let t = qn.get(e);
|
|
1770
|
+
return t || (t = de({
|
|
1312
1771
|
text: "",
|
|
1313
1772
|
sentiment: null,
|
|
1314
1773
|
loaded: !1,
|
|
1315
1774
|
pending: !1,
|
|
1316
1775
|
saving: !1,
|
|
1317
1776
|
error: ""
|
|
1318
|
-
}),
|
|
1777
|
+
}), qn.set(e, t)), t;
|
|
1319
1778
|
}
|
|
1320
|
-
function
|
|
1321
|
-
let t =
|
|
1779
|
+
function Jn(e) {
|
|
1780
|
+
let t = Z(), n = () => j(), { user: r } = kt(), { t: a, formatNumber: o } = D(), s = N(() => I(e)), c = N(() => `${r.value?.uid ?? ""}:${s.value}`), l = N(() => {
|
|
1322
1781
|
let e = r.value?.uid, a = s.value;
|
|
1323
1782
|
return !e || !a ? null : i(t, n(), e, a);
|
|
1324
|
-
}), u =
|
|
1783
|
+
}), u = N({
|
|
1325
1784
|
get: () => $(c.value).text,
|
|
1326
1785
|
set: (e) => {
|
|
1327
1786
|
$(c.value).text = e;
|
|
1328
1787
|
}
|
|
1329
|
-
}), d =
|
|
1788
|
+
}), d = N(() => $(c.value).sentiment), f = N(() => $(c.value).loaded), p = N(() => $(c.value).pending), m = N(() => $(c.value).saving), h = N(() => $(c.value).error);
|
|
1330
1789
|
async function g() {
|
|
1331
1790
|
let e = l.value;
|
|
1332
1791
|
if (!e) return;
|
|
1333
1792
|
let t = $(c.value);
|
|
1334
1793
|
t.pending = !0, t.error = "";
|
|
1335
1794
|
try {
|
|
1336
|
-
let n = (await
|
|
1795
|
+
let n = (await z(e)).data();
|
|
1337
1796
|
t.text = n ? n.text ?? "" : "", t.sentiment = n ? n.sentiment ?? null : null, t.loaded = !0;
|
|
1338
1797
|
} catch (e) {
|
|
1339
1798
|
t.error = e.message || String(e), console.error("[memi-board:authorMemo] load failed", e);
|
|
@@ -1349,10 +1808,10 @@ function fn(e) {
|
|
|
1349
1808
|
let i = $(c.value);
|
|
1350
1809
|
i.saving = !0, i.error = "";
|
|
1351
1810
|
try {
|
|
1352
|
-
await
|
|
1811
|
+
await q(n, {
|
|
1353
1812
|
text: r,
|
|
1354
1813
|
sentiment: t,
|
|
1355
|
-
updatedAt:
|
|
1814
|
+
updatedAt: K()
|
|
1356
1815
|
}), i.text = r, i.sentiment = t, i.loaded = !0;
|
|
1357
1816
|
} catch (e) {
|
|
1358
1817
|
throw i.error = e.message || String(e), console.error("[memi-board:authorMemo] save failed", e), e;
|
|
@@ -1366,7 +1825,7 @@ function fn(e) {
|
|
|
1366
1825
|
let t = $(c.value);
|
|
1367
1826
|
t.saving = !0, t.error = "";
|
|
1368
1827
|
try {
|
|
1369
|
-
await
|
|
1828
|
+
await fe(e), t.text = "", t.sentiment = null, t.loaded = !0;
|
|
1370
1829
|
} catch (e) {
|
|
1371
1830
|
throw t.error = e.message || String(e), console.error("[memi-board:authorMemo] remove failed", e), e;
|
|
1372
1831
|
} finally {
|
|
@@ -1387,37 +1846,37 @@ function fn(e) {
|
|
|
1387
1846
|
}
|
|
1388
1847
|
//#endregion
|
|
1389
1848
|
//#region src/utils/subscribeCommentRange.ts
|
|
1390
|
-
function
|
|
1391
|
-
return
|
|
1849
|
+
function Yn(e, t, n, r, i, a) {
|
|
1850
|
+
return U(G(u(e, t), X("postId", "==", n), ...r.rootId ? [X("rootId", "==", r.rootId), X("isReply", "==", !0)] : [X("parentId", "==", null)], W("createdAt", "asc"), W(R(), "asc"), ...r.hasMore && r.cursor ? [he(r.cursor)] : []), (e) => i(e.docs.map((e) => ({
|
|
1392
1851
|
...e.data(),
|
|
1393
1852
|
id: e.id
|
|
1394
1853
|
}))), a);
|
|
1395
1854
|
}
|
|
1396
1855
|
//#endregion
|
|
1397
1856
|
//#region src/composables/useMemiBoardComments.ts
|
|
1398
|
-
var
|
|
1399
|
-
function
|
|
1857
|
+
var Xn = 10, Zn = 5, Qn = 1e3;
|
|
1858
|
+
function $n(e, { t, formatNumber: n }) {
|
|
1400
1859
|
let r = e.trim();
|
|
1401
1860
|
if (!r) throw Error(t("comments.bodyRequired"));
|
|
1402
|
-
if (r.length > 1e3) throw Error(t("comments.bodyTooLong", { max: n(
|
|
1861
|
+
if (r.length > 1e3) throw Error(t("comments.bodyTooLong", { max: n(Qn) }));
|
|
1403
1862
|
return r;
|
|
1404
1863
|
}
|
|
1405
|
-
function
|
|
1406
|
-
let r =
|
|
1864
|
+
function er(e, t, n = {}) {
|
|
1865
|
+
let r = Z(), i = () => j(), { isBoardHidden: a } = Mn(), o = D(), { t: s } = o, c = () => u(r, i()), d = N(() => I(e)), f = N(() => I(t)), p = () => !a(d.value), m = F([]), h = F(n.subscribe !== !1), g = F([]), _ = F(!1), v = F(n.subscribe !== !1), y = F(n.subscribe !== !1), b = null, x = 0, S = 0, C;
|
|
1407
1866
|
function w(e) {
|
|
1408
1867
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
1409
1868
|
}
|
|
1410
|
-
let
|
|
1869
|
+
let ee = N(() => {
|
|
1411
1870
|
let e = /* @__PURE__ */ new Map();
|
|
1412
1871
|
for (let t of [...m.value, ...g.value]) t.id && e.set(t.id, t);
|
|
1413
1872
|
return [...e.values()].sort((e, t) => w(e) - w(t) || String(e.id).localeCompare(String(t.id)));
|
|
1414
1873
|
});
|
|
1415
|
-
|
|
1874
|
+
L([f, d], async ([e]) => {
|
|
1416
1875
|
let t = ++x;
|
|
1417
1876
|
if (S++, _.value = !1, g.value = [], m.value = [], b = null, C?.(), C = void 0, v.value = n.subscribe !== !1, n.subscribe !== !1) {
|
|
1418
1877
|
h.value = !0, y.value = !0;
|
|
1419
1878
|
try {
|
|
1420
|
-
let n =
|
|
1879
|
+
let n = B(G(c(), X("postId", "==", e), W("createdAt", "desc"), W(R(), "desc")));
|
|
1421
1880
|
if (await Promise.all([n.then((e) => {
|
|
1422
1881
|
if (t !== x) return;
|
|
1423
1882
|
let n = e.docs.map((e) => ({
|
|
@@ -1425,16 +1884,16 @@ function vn(e, t, n = {}) {
|
|
|
1425
1884
|
...e.data()
|
|
1426
1885
|
}));
|
|
1427
1886
|
m.value = n.filter((e) => e.parentId === void 0);
|
|
1428
|
-
}),
|
|
1887
|
+
}), E()]), t !== x) return;
|
|
1429
1888
|
} finally {
|
|
1430
1889
|
t === x && (h.value = !1, y.value = !1);
|
|
1431
1890
|
}
|
|
1432
1891
|
}
|
|
1433
1892
|
}, { immediate: !0 });
|
|
1434
|
-
function
|
|
1893
|
+
function T() {
|
|
1435
1894
|
C?.();
|
|
1436
1895
|
let e = ++S;
|
|
1437
|
-
C =
|
|
1896
|
+
C = Yn(r, i(), f.value, {
|
|
1438
1897
|
cursor: b,
|
|
1439
1898
|
hasMore: v.value
|
|
1440
1899
|
}, (t) => {
|
|
@@ -1443,34 +1902,34 @@ function vn(e, t, n = {}) {
|
|
|
1443
1902
|
console.error("[memi-board:comments] onSnapshot failed", e);
|
|
1444
1903
|
});
|
|
1445
1904
|
}
|
|
1446
|
-
async function
|
|
1905
|
+
async function E() {
|
|
1447
1906
|
if (_.value || !v.value) return;
|
|
1448
1907
|
let e = x;
|
|
1449
1908
|
_.value = !0;
|
|
1450
1909
|
try {
|
|
1451
1910
|
let t = [
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
...b ? [
|
|
1457
|
-
|
|
1458
|
-
], n = await
|
|
1911
|
+
X("postId", "==", f.value),
|
|
1912
|
+
X("parentId", "==", null),
|
|
1913
|
+
W("createdAt", "asc"),
|
|
1914
|
+
W(R(), "asc"),
|
|
1915
|
+
...b ? [J(b)] : [],
|
|
1916
|
+
H(11)
|
|
1917
|
+
], n = await B(G(c(), ...t));
|
|
1459
1918
|
if (e !== x) return;
|
|
1460
|
-
let r = n.docs.slice(0,
|
|
1919
|
+
let r = n.docs.slice(0, Xn), i = r.map((e) => ({
|
|
1461
1920
|
id: e.id,
|
|
1462
1921
|
...e.data()
|
|
1463
1922
|
})), a = new Set(g.value.map((e) => e.id));
|
|
1464
|
-
g.value.push(...i.filter((e) => !a.has(e.id))), b = r.at(-1) ?? b, v.value = n.size >
|
|
1923
|
+
g.value.push(...i.filter((e) => !a.has(e.id))), b = r.at(-1) ?? b, v.value = n.size > Xn, T();
|
|
1465
1924
|
} finally {
|
|
1466
1925
|
e === x && (_.value = !1);
|
|
1467
1926
|
}
|
|
1468
1927
|
}
|
|
1469
|
-
|
|
1928
|
+
P(() => {
|
|
1470
1929
|
x++, S++, C?.();
|
|
1471
1930
|
});
|
|
1472
|
-
async function
|
|
1473
|
-
let t =
|
|
1931
|
+
async function te(e) {
|
|
1932
|
+
let t = $n(e.body, o), n = ye(r), a = me(c());
|
|
1474
1933
|
n.set(a, {
|
|
1475
1934
|
postId: f.value,
|
|
1476
1935
|
boardId: d.value,
|
|
@@ -1488,14 +1947,14 @@ function vn(e, t, n = {}) {
|
|
|
1488
1947
|
replyCount: 0,
|
|
1489
1948
|
likeCount: 0,
|
|
1490
1949
|
isReply: !1,
|
|
1491
|
-
createdAt:
|
|
1492
|
-
updatedAt:
|
|
1493
|
-
}), n.update(l(r, i(), f.value), { commentCount:
|
|
1950
|
+
createdAt: K(),
|
|
1951
|
+
updatedAt: K()
|
|
1952
|
+
}), n.update(l(r, i(), f.value), { commentCount: V(1) }), await n.commit();
|
|
1494
1953
|
}
|
|
1495
|
-
async function
|
|
1496
|
-
let t =
|
|
1954
|
+
async function k(e) {
|
|
1955
|
+
let t = $n(e.body, o);
|
|
1497
1956
|
if (!e.parent.id) throw Error(s("comments.replyTargetNotFound"));
|
|
1498
|
-
let n =
|
|
1957
|
+
let n = ye(r), a = me(c()), u = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
|
|
1499
1958
|
if (!u) throw Error(s("comments.threadNotFound"));
|
|
1500
1959
|
let m = Math.min((e.parent.depth ?? 0) + 1, 2);
|
|
1501
1960
|
n.set(a, {
|
|
@@ -1515,53 +1974,53 @@ function vn(e, t, n = {}) {
|
|
|
1515
1974
|
replyCount: 0,
|
|
1516
1975
|
likeCount: 0,
|
|
1517
1976
|
isReply: !0,
|
|
1518
|
-
createdAt:
|
|
1519
|
-
updatedAt:
|
|
1520
|
-
}), n.update(
|
|
1977
|
+
createdAt: K(),
|
|
1978
|
+
updatedAt: K()
|
|
1979
|
+
}), n.update(O(r, i(), u), { replyCount: V(1) }), n.update(l(r, i(), f.value), { commentCount: V(1) }), await n.commit();
|
|
1521
1980
|
}
|
|
1522
|
-
async function
|
|
1981
|
+
async function A(e) {
|
|
1523
1982
|
if (!e.id) return;
|
|
1524
1983
|
if (e.parentId == null && (e.replyCount ?? 0) > 0) throw Error(s("comments.hasRepliesCannotDelete"));
|
|
1525
|
-
await
|
|
1526
|
-
let t =
|
|
1527
|
-
t.delete(
|
|
1984
|
+
await Wt(r, i(), e.id);
|
|
1985
|
+
let t = ye(r);
|
|
1986
|
+
t.delete(O(r, i(), e.id)), e.parentId && e.rootId && t.update(O(r, i(), e.rootId), { replyCount: V(-1) }), t.update(l(r, i(), f.value), { commentCount: V(-1) }), await t.commit(), g.value = g.value.filter((t) => t.id !== e.id), m.value = m.value.filter((t) => t.id !== e.id);
|
|
1528
1987
|
}
|
|
1529
|
-
async function
|
|
1530
|
-
let n =
|
|
1531
|
-
await
|
|
1988
|
+
async function ne(e, t) {
|
|
1989
|
+
let n = $n(t, o);
|
|
1990
|
+
await Y(O(r, i(), e), {
|
|
1532
1991
|
body: n,
|
|
1533
|
-
updatedAt:
|
|
1992
|
+
updatedAt: K()
|
|
1534
1993
|
});
|
|
1535
1994
|
}
|
|
1536
|
-
async function
|
|
1537
|
-
await
|
|
1995
|
+
async function re(e, t, n) {
|
|
1996
|
+
await Y(O(r, i(), e), {
|
|
1538
1997
|
isBlinded: t,
|
|
1539
|
-
moderatedAt:
|
|
1998
|
+
moderatedAt: K(),
|
|
1540
1999
|
moderatedBy: n
|
|
1541
2000
|
});
|
|
1542
2001
|
}
|
|
1543
2002
|
return {
|
|
1544
|
-
comments:
|
|
1545
|
-
commentsPending:
|
|
2003
|
+
comments: ee,
|
|
2004
|
+
commentsPending: N(() => y.value || h.value),
|
|
1546
2005
|
hasMore: v,
|
|
1547
2006
|
loadingMore: _,
|
|
1548
|
-
loadMore:
|
|
1549
|
-
addComment:
|
|
1550
|
-
addReply:
|
|
1551
|
-
updateComment:
|
|
1552
|
-
setCommentBlinded:
|
|
1553
|
-
deleteComment:
|
|
2007
|
+
loadMore: E,
|
|
2008
|
+
addComment: te,
|
|
2009
|
+
addReply: k,
|
|
2010
|
+
updateComment: ne,
|
|
2011
|
+
setCommentBlinded: re,
|
|
2012
|
+
deleteComment: A
|
|
1554
2013
|
};
|
|
1555
2014
|
}
|
|
1556
|
-
function
|
|
1557
|
-
let r =
|
|
2015
|
+
function tr(e, t, n) {
|
|
2016
|
+
let r = Z(), i = () => j(), a = () => u(r, i()), o = t.trim(), s = F([]), c = F(!1), l = F(!1), d = F(!0), f = null, p, m = 0, h = 0;
|
|
1558
2017
|
function g(e) {
|
|
1559
2018
|
return e.sort((e, t) => (e.createdAt?.toMillis?.() ?? 0) - (t.createdAt?.toMillis?.() ?? 0) || String(e.id).localeCompare(String(t.id)));
|
|
1560
2019
|
}
|
|
1561
2020
|
function _() {
|
|
1562
2021
|
p?.();
|
|
1563
2022
|
let e = ++h;
|
|
1564
|
-
p =
|
|
2023
|
+
p = Yn(r, i(), o, {
|
|
1565
2024
|
rootId: n,
|
|
1566
2025
|
cursor: f,
|
|
1567
2026
|
hasMore: d.value
|
|
@@ -1577,20 +2036,20 @@ function yn(e, t, n) {
|
|
|
1577
2036
|
c.value = !0;
|
|
1578
2037
|
try {
|
|
1579
2038
|
let t = [
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
...f ? [
|
|
1586
|
-
|
|
1587
|
-
], r = await
|
|
2039
|
+
X("postId", "==", o),
|
|
2040
|
+
X("rootId", "==", n),
|
|
2041
|
+
X("isReply", "==", !0),
|
|
2042
|
+
W("createdAt", "asc"),
|
|
2043
|
+
W(R(), "asc"),
|
|
2044
|
+
...f ? [J(f)] : [],
|
|
2045
|
+
H(6)
|
|
2046
|
+
], r = await B(G(a(), ...t));
|
|
1588
2047
|
if (e !== m) return;
|
|
1589
|
-
let i = new Set(s.value.map((e) => e.id)), c = r.docs.slice(0,
|
|
2048
|
+
let i = new Set(s.value.map((e) => e.id)), c = r.docs.slice(0, Zn), u = c.map((e) => ({
|
|
1590
2049
|
id: e.id,
|
|
1591
2050
|
...e.data()
|
|
1592
2051
|
})).filter((e) => !i.has(e.id));
|
|
1593
|
-
s.value = g([...s.value, ...u]), f = c.at(-1) ?? f, l.value = !0, d.value = r.size >
|
|
2052
|
+
s.value = g([...s.value, ...u]), f = c.at(-1) ?? f, l.value = !0, d.value = r.size > Zn, _();
|
|
1594
2053
|
} finally {
|
|
1595
2054
|
e === m && (c.value = !1);
|
|
1596
2055
|
}
|
|
@@ -1601,7 +2060,7 @@ function yn(e, t, n) {
|
|
|
1601
2060
|
async function b() {
|
|
1602
2061
|
y(), await v();
|
|
1603
2062
|
}
|
|
1604
|
-
return
|
|
2063
|
+
return P(() => {
|
|
1605
2064
|
m++, h++, p?.();
|
|
1606
2065
|
}), {
|
|
1607
2066
|
replies: s,
|
|
@@ -1614,22 +2073,22 @@ function yn(e, t, n) {
|
|
|
1614
2073
|
}
|
|
1615
2074
|
//#endregion
|
|
1616
2075
|
//#region src/composables/useMemiBoardLikes.ts
|
|
1617
|
-
function
|
|
1618
|
-
let n = () =>
|
|
2076
|
+
function nr(e, t) {
|
|
2077
|
+
let n = () => j(), r = Z(), i = xe(), { t: a } = D(), o = F(!1), s = F(!1), c;
|
|
1619
2078
|
function u(e) {
|
|
1620
2079
|
return d(r, n(), t, e);
|
|
1621
2080
|
}
|
|
1622
|
-
|
|
2081
|
+
L(i, (e) => {
|
|
1623
2082
|
if (c?.(), c = void 0, !e) {
|
|
1624
2083
|
o.value = !1;
|
|
1625
2084
|
return;
|
|
1626
2085
|
}
|
|
1627
|
-
c =
|
|
2086
|
+
c = U(u(e.uid), (e) => {
|
|
1628
2087
|
o.value = e.exists();
|
|
1629
2088
|
}, (e) => {
|
|
1630
2089
|
console.error("[memi-board:likes] onSnapshot failed", e);
|
|
1631
2090
|
});
|
|
1632
|
-
}, { immediate: !0 }),
|
|
2091
|
+
}, { immediate: !0 }), P(() => c?.());
|
|
1633
2092
|
async function f() {
|
|
1634
2093
|
let c = i.value?.uid;
|
|
1635
2094
|
if (!c) throw Error(a("likes.signInRequired"));
|
|
@@ -1637,12 +2096,12 @@ function bn(e, t) {
|
|
|
1637
2096
|
s.value = !0;
|
|
1638
2097
|
try {
|
|
1639
2098
|
let i = u(c), a = l(r, n(), t);
|
|
1640
|
-
return await
|
|
2099
|
+
return await ve(r, async (n) => (await n.get(i)).exists() ? (n.delete(i), n.update(a, { likeCount: V(-1) }), !1) : (n.set(i, {
|
|
1641
2100
|
uid: c,
|
|
1642
2101
|
postId: t,
|
|
1643
2102
|
boardId: e,
|
|
1644
|
-
createdAt:
|
|
1645
|
-
}), n.update(a, { likeCount:
|
|
2103
|
+
createdAt: K()
|
|
2104
|
+
}), n.update(a, { likeCount: V(1) }), !0));
|
|
1646
2105
|
} finally {
|
|
1647
2106
|
s.value = !1;
|
|
1648
2107
|
}
|
|
@@ -1653,11 +2112,11 @@ function bn(e, t) {
|
|
|
1653
2112
|
toggleLike: f
|
|
1654
2113
|
};
|
|
1655
2114
|
}
|
|
1656
|
-
var
|
|
1657
|
-
function
|
|
1658
|
-
let n = () =>
|
|
1659
|
-
|
|
1660
|
-
f?.(), f = void 0, l.value = /* @__PURE__ */ new Set(), e && t && (f =
|
|
2115
|
+
var rr = Symbol.for("memi-board:comment-likes");
|
|
2116
|
+
function ir(e, t) {
|
|
2117
|
+
let n = () => j(), r = Z(), i = xe(), { t: o } = D(), s = N(() => I(e)), c = N(() => I(t)), l = F(/* @__PURE__ */ new Set()), u = F(null), f;
|
|
2118
|
+
L([i, c], ([e, t]) => {
|
|
2119
|
+
f?.(), f = void 0, l.value = /* @__PURE__ */ new Set(), e && t && (f = U(G(a(r, n()), X("uid", "==", e.uid), X("postId", "==", t), X("target", "==", "comment")), (e) => {
|
|
1661
2120
|
let t = /* @__PURE__ */ new Set();
|
|
1662
2121
|
for (let n of e.docs) {
|
|
1663
2122
|
let e = n.data().commentId;
|
|
@@ -1667,7 +2126,7 @@ function Sn(e, t) {
|
|
|
1667
2126
|
}, (e) => {
|
|
1668
2127
|
console.error("[memi-board:comment-likes] onSnapshot failed", e);
|
|
1669
2128
|
}));
|
|
1670
|
-
}, { immediate: !0 }),
|
|
2129
|
+
}, { immediate: !0 }), P(() => f?.());
|
|
1671
2130
|
function p(e) {
|
|
1672
2131
|
return !!e && l.value.has(e);
|
|
1673
2132
|
}
|
|
@@ -1681,7 +2140,7 @@ function Sn(e, t) {
|
|
|
1681
2140
|
if (u.value) return p(e);
|
|
1682
2141
|
u.value = e;
|
|
1683
2142
|
try {
|
|
1684
|
-
let i = d(r, n(), e, t), a =
|
|
2143
|
+
let i = d(r, n(), e, t), a = O(r, n(), e), u = await ve(r, async (n) => {
|
|
1685
2144
|
let r = await n.get(i), l = await n.get(a);
|
|
1686
2145
|
if (!l.exists()) throw Error(o("likes.commentNotFound"));
|
|
1687
2146
|
let u = Number(l.data()?.likeCount ?? 0);
|
|
@@ -1691,7 +2150,7 @@ function Sn(e, t) {
|
|
|
1691
2150
|
boardId: s.value,
|
|
1692
2151
|
commentId: e,
|
|
1693
2152
|
target: "comment",
|
|
1694
|
-
createdAt:
|
|
2153
|
+
createdAt: K()
|
|
1695
2154
|
}), n.update(a, { likeCount: u + 1 }), !0);
|
|
1696
2155
|
}), f = new Set(l.value);
|
|
1697
2156
|
return u ? f.add(e) : f.delete(e), l.value = f, u;
|
|
@@ -1707,7 +2166,7 @@ function Sn(e, t) {
|
|
|
1707
2166
|
}
|
|
1708
2167
|
//#endregion
|
|
1709
2168
|
//#region src/composables/useMemiBoardReports.ts
|
|
1710
|
-
var
|
|
2169
|
+
var ar = 200, or = [
|
|
1711
2170
|
{
|
|
1712
2171
|
value: "spam",
|
|
1713
2172
|
label: "스팸·광고"
|
|
@@ -1729,34 +2188,34 @@ var Cn = 200, wn = [
|
|
|
1729
2188
|
label: "기타"
|
|
1730
2189
|
}
|
|
1731
2190
|
];
|
|
1732
|
-
function
|
|
1733
|
-
return
|
|
2191
|
+
function sr(e, t) {
|
|
2192
|
+
return or.some((t) => t.value === e) ? w(t, `reports.reason.${e}`) : e || w(t, "reports.fallbackLabel");
|
|
1734
2193
|
}
|
|
1735
|
-
function
|
|
1736
|
-
let r = () =>
|
|
1737
|
-
|
|
2194
|
+
function cr(e, n) {
|
|
2195
|
+
let r = () => j(), i = Z(), a = xe(), { t: o } = D(), s = F(!1), c = F(!1), l;
|
|
2196
|
+
L(a, (e) => {
|
|
1738
2197
|
if (l?.(), l = void 0, !e) {
|
|
1739
2198
|
s.value = !1;
|
|
1740
2199
|
return;
|
|
1741
2200
|
}
|
|
1742
|
-
l =
|
|
2201
|
+
l = U(t(i, r(), n, e.uid), (e) => {
|
|
1743
2202
|
s.value = e.exists();
|
|
1744
2203
|
}, (e) => {
|
|
1745
2204
|
console.error("[memi-board:reports] onSnapshot failed", e);
|
|
1746
2205
|
});
|
|
1747
|
-
}, { immediate: !0 }),
|
|
2206
|
+
}, { immediate: !0 }), P(() => l?.());
|
|
1748
2207
|
async function u(l) {
|
|
1749
2208
|
let u = a.value?.uid;
|
|
1750
2209
|
if (!u) throw Error(o("reports.signInRequired"));
|
|
1751
2210
|
if (c.value) return;
|
|
1752
2211
|
if (l.authorUid && l.authorUid === u) throw Error(o("reports.cannotReportOwn"));
|
|
1753
|
-
let d =
|
|
2212
|
+
let d = or.some((e) => e.value === l.reason) ? l.reason : null;
|
|
1754
2213
|
if (!d) throw Error(o("reports.reasonRequired"));
|
|
1755
2214
|
let f = (l.detail || "").trim();
|
|
1756
2215
|
if (f.length > 200) throw Error(o("reports.detailTooLong", { max: 200 }));
|
|
1757
2216
|
c.value = !0;
|
|
1758
2217
|
try {
|
|
1759
|
-
await
|
|
2218
|
+
await q(t(i, r(), n, u), {
|
|
1760
2219
|
uid: u,
|
|
1761
2220
|
target: "post",
|
|
1762
2221
|
postId: n,
|
|
@@ -1767,7 +2226,7 @@ function En(e, n) {
|
|
|
1767
2226
|
postTitle: (l.postTitle || "").trim().slice(0, 200),
|
|
1768
2227
|
authorUid: l.authorUid || "",
|
|
1769
2228
|
authorName: l.authorName ?? null,
|
|
1770
|
-
createdAt:
|
|
2229
|
+
createdAt: K()
|
|
1771
2230
|
}), s.value = !0;
|
|
1772
2231
|
} finally {
|
|
1773
2232
|
c.value = !1;
|
|
@@ -1779,9 +2238,9 @@ function En(e, n) {
|
|
|
1779
2238
|
submitReport: u
|
|
1780
2239
|
};
|
|
1781
2240
|
}
|
|
1782
|
-
var
|
|
1783
|
-
function
|
|
1784
|
-
let n = () =>
|
|
2241
|
+
var lr = 20;
|
|
2242
|
+
function ur(e = {}) {
|
|
2243
|
+
let n = () => j(), r = Z(), { isAdmin: i, canManageContent: a, canManageBoard: o } = kt(), c = e.pageSize ?? lr, l = N(() => I(e.boardId)?.trim() || ""), u = F([]), d = F(!0), f = F(!1), p = F(!1), m = F(""), h = null, g;
|
|
1785
2244
|
function _(e) {
|
|
1786
2245
|
return {
|
|
1787
2246
|
id: e.id,
|
|
@@ -1794,19 +2253,19 @@ function On(e = {}) {
|
|
|
1794
2253
|
return;
|
|
1795
2254
|
}
|
|
1796
2255
|
d.value = !0;
|
|
1797
|
-
let e = l.value ? [
|
|
1798
|
-
g =
|
|
2256
|
+
let e = l.value ? [X("status", "==", "open"), X("boardId", "==", l.value)] : [X("status", "==", "open"), X("target", "==", "post")];
|
|
2257
|
+
g = U(G(s(r, n()), ...e, W("createdAt", "desc"), W(R(), "desc"), H(c)), (e) => {
|
|
1799
2258
|
u.value = e.docs.map(_).filter((e) => o(e.boardId)), h = e.docs.at(-1) ?? null, p.value = e.docs.length >= c, d.value = !1;
|
|
1800
2259
|
}, (e) => {
|
|
1801
2260
|
console.error("[memi-board:report-queue] onSnapshot failed", e), m.value = e.message || String(e), d.value = !1;
|
|
1802
2261
|
});
|
|
1803
2262
|
}
|
|
1804
|
-
|
|
2263
|
+
L([a, l], v, { immediate: !0 }), P(() => g?.());
|
|
1805
2264
|
async function y() {
|
|
1806
2265
|
if (!f.value && p.value && h && a.value) {
|
|
1807
2266
|
f.value = !0, m.value = "";
|
|
1808
2267
|
try {
|
|
1809
|
-
let e = l.value ? [
|
|
2268
|
+
let e = l.value ? [X("status", "==", "open"), X("boardId", "==", l.value)] : [X("status", "==", "open"), X("target", "==", "post")], t = await B(G(s(r, n()), ...e, W("createdAt", "desc"), W(R(), "desc"), J(h), H(c + 1))), i = t.docs.slice(0, c), a = new Set(u.value.map((e) => e.id));
|
|
1810
2269
|
u.value.push(...i.map(_).filter((e) => !a.has(e.id) && o(e.boardId))), h = i.at(-1) ?? h, p.value = t.docs.length > c;
|
|
1811
2270
|
} catch (e) {
|
|
1812
2271
|
m.value = e.message || String(e);
|
|
@@ -1816,10 +2275,10 @@ function On(e = {}) {
|
|
|
1816
2275
|
}
|
|
1817
2276
|
}
|
|
1818
2277
|
async function b(e, i) {
|
|
1819
|
-
e.id && o(e.boardId) && await
|
|
2278
|
+
e.id && o(e.boardId) && await Y(t(r, n(), e.postId, e.uid), {
|
|
1820
2279
|
status: i,
|
|
1821
|
-
reviewedAt:
|
|
1822
|
-
reviewedBy:
|
|
2280
|
+
reviewedAt: K(),
|
|
2281
|
+
reviewedBy: xe().value?.uid || ""
|
|
1823
2282
|
});
|
|
1824
2283
|
}
|
|
1825
2284
|
return {
|
|
@@ -1835,8 +2294,8 @@ function On(e = {}) {
|
|
|
1835
2294
|
}
|
|
1836
2295
|
//#endregion
|
|
1837
2296
|
//#region src/composables/useMemiBoardViews.ts
|
|
1838
|
-
function
|
|
1839
|
-
let e = () =>
|
|
2297
|
+
function dr() {
|
|
2298
|
+
let e = () => j(), t = Z();
|
|
1840
2299
|
function n(t, n) {
|
|
1841
2300
|
let { postsCollection: r } = e();
|
|
1842
2301
|
return `memi-board:viewed:${r}:${t}:${n}`;
|
|
@@ -1859,7 +2318,7 @@ function kn() {
|
|
|
1859
2318
|
if (s && c && !r(s, c)) {
|
|
1860
2319
|
i(s, c);
|
|
1861
2320
|
try {
|
|
1862
|
-
await
|
|
2321
|
+
await Y(l(t, e(), c), { viewCount: V(1) });
|
|
1863
2322
|
} catch (e) {
|
|
1864
2323
|
try {
|
|
1865
2324
|
sessionStorage.removeItem(n(s, c));
|
|
@@ -1874,161 +2333,21 @@ function kn() {
|
|
|
1874
2333
|
};
|
|
1875
2334
|
}
|
|
1876
2335
|
//#endregion
|
|
1877
|
-
//#region src/utils/moderation-prompt.ts
|
|
1878
|
-
var An = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\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은 \"\"", jn = {
|
|
1879
|
-
ko: "Korean",
|
|
1880
|
-
en: "English",
|
|
1881
|
-
ja: "Japanese",
|
|
1882
|
-
de: "German",
|
|
1883
|
-
fr: "French",
|
|
1884
|
-
es: "Spanish",
|
|
1885
|
-
pt: "Portuguese",
|
|
1886
|
-
zh: "Simplified Chinese",
|
|
1887
|
-
ar: "Arabic",
|
|
1888
|
-
id: "Indonesian"
|
|
1889
|
-
};
|
|
1890
|
-
function Mn(e) {
|
|
1891
|
-
let t = jn[e] ?? "English", n = D(e).name;
|
|
1892
|
-
return t === n ? t : `${t} (${n})`;
|
|
1893
|
-
}
|
|
1894
|
-
function Nn(e) {
|
|
1895
|
-
let t = Mn(e);
|
|
1896
|
-
return `You are a strict moderator reviewing content for a message board.
|
|
1897
|
-
If the text below contains profanity, vulgar slang, sexual harassment, hate speech, threats, spam, or repetitive/flooding advertisements, you MUST reject it (flagged=true).
|
|
1898
|
-
|
|
1899
|
-
The content may be written in ANY language, or a mix of languages — not only the board's language.
|
|
1900
|
-
Judge every language equally strictly, including slang, abbreviations, romanizations, deliberate misspellings,
|
|
1901
|
-
inserted spaces/symbols, and other obfuscation (e.g. in Korean, Japanese, Chinese, Arabic, Spanish, etc.).
|
|
1902
|
-
|
|
1903
|
-
flagged=true examples:
|
|
1904
|
-
- Profanity/vulgar slang: fuck, shit, bitch, asshole, f*ck, sh1t /
|
|
1905
|
-
Korean: 씨발, 시발, ㅅㅂ, 병신, ㅂㅅ, 지랄, 좆, 개새끼, 존나 /
|
|
1906
|
-
Japanese: クソ, 死ね, ちんこ / Chinese: 操你妈, 傻逼, 他妈的 /
|
|
1907
|
-
Spanish: puta, mierda, pendejo / German: Scheiße, Hurensohn / French: putain, connard /
|
|
1908
|
-
Portuguese: porra, caralho / Indonesian: anjing, bangsat, kontol / Arabic: كس, شرموطة, etc.
|
|
1909
|
-
- Sexual insults, hate speech, threats
|
|
1910
|
-
- Flooding advertisements/spam unrelated to the board's topic
|
|
1911
|
-
|
|
1912
|
-
flagged=false:
|
|
1913
|
-
- Normal posts/comments that fit the board's topic (strong opinions and legitimate criticism are allowed)
|
|
1914
|
-
|
|
1915
|
-
Output JSON only:
|
|
1916
|
-
{"flagged":boolean,"category":"none"|"abuse"|"spam"|"adult"|"violence"|"other","reason":string}
|
|
1917
|
-
- If flagged=true, choose a category, and write reason as ONE concrete sentence in ${t} explaining **what the problem is**
|
|
1918
|
-
(e.g. "It contains profanity, so it can't be posted.", "It contains advertising/spam, so it can't be posted." — but written in ${t}).
|
|
1919
|
-
Do not quote the offensive words at length; clearly state the type (profanity/hate/spam, etc.).
|
|
1920
|
-
- If flagged=false, category is "none" and reason is ""`;
|
|
1921
|
-
}
|
|
1922
|
-
function Pn(e = "ko") {
|
|
1923
|
-
return e === "ko" ? An : Nn(e);
|
|
1924
|
-
}
|
|
1925
|
-
function Fn(e, t = "ko") {
|
|
1926
|
-
let n = `"""${e.slice(0, 4e3)}"""`;
|
|
1927
|
-
return t === "ko" ? `심사할 텍스트:\n${n}` : `Text to review:\n${n}`;
|
|
1928
|
-
}
|
|
1929
|
-
function In(e = "ko") {
|
|
1930
|
-
return e === "ko" ? "이 이미지를 심사해 주세요." : "Please review this image.";
|
|
1931
|
-
}
|
|
1932
|
-
var Ln = {
|
|
1933
|
-
abuse: "욕설·비속어·모욕",
|
|
1934
|
-
spam: "스팸·광고",
|
|
1935
|
-
adult: "선정적·성인 내용",
|
|
1936
|
-
violence: "폭력·위협",
|
|
1937
|
-
other: "부적절한 내용",
|
|
1938
|
-
none: ""
|
|
1939
|
-
};
|
|
1940
|
-
function Rn(e, t = "ko") {
|
|
1941
|
-
return !e || e === "none" ? "" : e in Ln ? w(t, `moderationPrompt.categories.${e}`) : e;
|
|
1942
|
-
}
|
|
1943
|
-
function zn(e) {
|
|
1944
|
-
let t = e.locale ?? "ko";
|
|
1945
|
-
if (e.via === "local" && e.localHit) {
|
|
1946
|
-
let n = e.localHit.trim();
|
|
1947
|
-
return n.length > 0 && n.length <= 12 ? w(t, "moderationPrompt.localHit", { hit: n }) : w(t, "moderationPrompt.localGeneric");
|
|
1948
|
-
}
|
|
1949
|
-
let n = (e.reason || "").trim();
|
|
1950
|
-
if (n) return n;
|
|
1951
|
-
let r = e.category ? Rn(e.category, t) : "";
|
|
1952
|
-
return r ? w(t, "moderationPrompt.categoryBlocked", { category: r }) : w(t, "moderationPrompt.genericBlocked");
|
|
1953
|
-
}
|
|
1954
|
-
var Bn = [
|
|
1955
|
-
"씨발",
|
|
1956
|
-
"시발",
|
|
1957
|
-
"시이발",
|
|
1958
|
-
"씨팔",
|
|
1959
|
-
"시팔",
|
|
1960
|
-
"병신",
|
|
1961
|
-
"븅신",
|
|
1962
|
-
"지랄",
|
|
1963
|
-
"좆",
|
|
1964
|
-
"존나",
|
|
1965
|
-
"개새끼",
|
|
1966
|
-
"개새",
|
|
1967
|
-
"새끼",
|
|
1968
|
-
"꺼져",
|
|
1969
|
-
"닥쳐",
|
|
1970
|
-
"씹",
|
|
1971
|
-
"니미",
|
|
1972
|
-
"느금",
|
|
1973
|
-
"애미",
|
|
1974
|
-
"애비",
|
|
1975
|
-
"fuck",
|
|
1976
|
-
"shit",
|
|
1977
|
-
"bitch",
|
|
1978
|
-
"asshole"
|
|
1979
|
-
];
|
|
1980
|
-
function Vn(e) {
|
|
1981
|
-
return new RegExp(e.join("|"), "i");
|
|
1982
|
-
}
|
|
1983
|
-
var Hn = /* @__PURE__ */ new Set([
|
|
1984
|
-
"none",
|
|
1985
|
-
"abuse",
|
|
1986
|
-
"spam",
|
|
1987
|
-
"adult",
|
|
1988
|
-
"violence",
|
|
1989
|
-
"other"
|
|
1990
|
-
]);
|
|
1991
|
-
function Un(e) {
|
|
1992
|
-
let t = e.trim();
|
|
1993
|
-
if (!t) return null;
|
|
1994
|
-
let n;
|
|
1995
|
-
try {
|
|
1996
|
-
n = JSON.parse(t);
|
|
1997
|
-
} catch {
|
|
1998
|
-
let e = t.match(/\{[\s\S]*\}/);
|
|
1999
|
-
if (!e) return null;
|
|
2000
|
-
try {
|
|
2001
|
-
n = JSON.parse(e[0]);
|
|
2002
|
-
} catch {
|
|
2003
|
-
return null;
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
if (typeof n != "object" || !n) return null;
|
|
2007
|
-
let r = n;
|
|
2008
|
-
if (typeof r.flagged != "boolean") return null;
|
|
2009
|
-
let i = typeof r.category == "string" && Hn.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
|
|
2010
|
-
return {
|
|
2011
|
-
flagged: r.flagged,
|
|
2012
|
-
category: i,
|
|
2013
|
-
reason: a
|
|
2014
|
-
};
|
|
2015
|
-
}
|
|
2016
|
-
//#endregion
|
|
2017
2336
|
//#region src/composables/useMemiBoardModeration.ts
|
|
2018
|
-
var
|
|
2337
|
+
var fr = {
|
|
2019
2338
|
flagged: !1,
|
|
2020
2339
|
category: "none",
|
|
2021
2340
|
reason: "",
|
|
2022
2341
|
via: "empty"
|
|
2023
2342
|
};
|
|
2024
|
-
function
|
|
2025
|
-
let t =
|
|
2343
|
+
function pr(e = {}) {
|
|
2344
|
+
let t = D(), n = N(() => T(I(e.locale)) ?? t.locale.value), r = ue().moderation ?? {}, i = Ce(), a = (e) => w(n.value, e), { isWriteRestricted: o, restrictedMessage: s, recordContentModerationBlock: c } = kt(), l = _n([...gn, ...r.localBlocklist ?? []]);
|
|
2026
2345
|
function u(e) {
|
|
2027
2346
|
let t = e.match(l);
|
|
2028
2347
|
return t ? {
|
|
2029
2348
|
flagged: !0,
|
|
2030
2349
|
category: "abuse",
|
|
2031
|
-
reason:
|
|
2350
|
+
reason: hn({
|
|
2032
2351
|
via: "local",
|
|
2033
2352
|
localHit: t[0] || "",
|
|
2034
2353
|
category: "abuse"
|
|
@@ -2037,21 +2356,21 @@ function Gn(e = {}) {
|
|
|
2037
2356
|
} : null;
|
|
2038
2357
|
}
|
|
2039
2358
|
function d() {
|
|
2040
|
-
let e = r.model ?? "gemini-3.5-flash-lite", t = r.useLimitedUseAppCheckTokens !== !1, a =
|
|
2041
|
-
backend: new
|
|
2359
|
+
let e = r.model ?? "gemini-3.5-flash-lite", t = r.useLimitedUseAppCheckTokens !== !1, a = Re(i, {
|
|
2360
|
+
backend: new Ie(),
|
|
2042
2361
|
useLimitedUseAppCheckTokens: t
|
|
2043
2362
|
});
|
|
2044
|
-
return
|
|
2363
|
+
return ze(a, {
|
|
2045
2364
|
model: e,
|
|
2046
|
-
systemInstruction:
|
|
2365
|
+
systemInstruction: un(n.value),
|
|
2047
2366
|
generationConfig: {
|
|
2048
2367
|
temperature: 0,
|
|
2049
2368
|
maxOutputTokens: 256,
|
|
2050
2369
|
responseMimeType: "application/json",
|
|
2051
|
-
responseSchema:
|
|
2052
|
-
flagged:
|
|
2053
|
-
category:
|
|
2054
|
-
reason:
|
|
2370
|
+
responseSchema: Le.object({ properties: {
|
|
2371
|
+
flagged: Le.boolean(),
|
|
2372
|
+
category: Le.string(),
|
|
2373
|
+
reason: Le.string()
|
|
2055
2374
|
} })
|
|
2056
2375
|
}
|
|
2057
2376
|
});
|
|
@@ -2072,7 +2391,7 @@ function Gn(e = {}) {
|
|
|
2072
2391
|
};
|
|
2073
2392
|
}
|
|
2074
2393
|
function p(e, t = "") {
|
|
2075
|
-
let n =
|
|
2394
|
+
let n = hn({
|
|
2076
2395
|
reason: e.reason,
|
|
2077
2396
|
category: e.category,
|
|
2078
2397
|
via: e.via
|
|
@@ -2100,26 +2419,26 @@ function Gn(e = {}) {
|
|
|
2100
2419
|
};
|
|
2101
2420
|
let i = e.trim();
|
|
2102
2421
|
if (!i) return {
|
|
2103
|
-
...
|
|
2422
|
+
...fr,
|
|
2104
2423
|
via: "empty"
|
|
2105
2424
|
};
|
|
2106
2425
|
if (t?.skipFilter) return {
|
|
2107
|
-
...
|
|
2426
|
+
...fr,
|
|
2108
2427
|
via: "admin-override"
|
|
2109
2428
|
};
|
|
2110
2429
|
let c = u(i);
|
|
2111
2430
|
if (c) return m(c);
|
|
2112
2431
|
if (r.enabled === !1) return {
|
|
2113
|
-
...
|
|
2432
|
+
...fr,
|
|
2114
2433
|
via: "disabled"
|
|
2115
2434
|
};
|
|
2116
2435
|
try {
|
|
2117
|
-
let e =
|
|
2436
|
+
let e = yn((await d().generateContent(dn(i, n.value))).response.text());
|
|
2118
2437
|
if (!e) return f();
|
|
2119
2438
|
let t = {
|
|
2120
2439
|
flagged: e.flagged,
|
|
2121
2440
|
category: e.category,
|
|
2122
|
-
reason:
|
|
2441
|
+
reason: hn({
|
|
2123
2442
|
reason: e.reason,
|
|
2124
2443
|
category: e.category,
|
|
2125
2444
|
via: "ai"
|
|
@@ -2139,22 +2458,22 @@ function Gn(e = {}) {
|
|
|
2139
2458
|
via: "restricted"
|
|
2140
2459
|
};
|
|
2141
2460
|
if (r.enabled === !1 || !r.moderateImages) return {
|
|
2142
|
-
...
|
|
2461
|
+
...fr,
|
|
2143
2462
|
via: "disabled"
|
|
2144
2463
|
};
|
|
2145
2464
|
try {
|
|
2146
2465
|
let t = await new Promise((t, n) => {
|
|
2147
2466
|
let r = new FileReader();
|
|
2148
2467
|
r.onload = () => t(r.result.split(",")[1] ?? ""), r.onerror = n, r.readAsDataURL(e);
|
|
2149
|
-
}), r =
|
|
2468
|
+
}), r = yn((await d().generateContent([{ inlineData: {
|
|
2150
2469
|
mimeType: e.type || "image/jpeg",
|
|
2151
2470
|
data: t
|
|
2152
|
-
} },
|
|
2471
|
+
} }, fn(n.value)])).response.text());
|
|
2153
2472
|
if (!r) return f();
|
|
2154
2473
|
let i = {
|
|
2155
2474
|
flagged: r.flagged,
|
|
2156
2475
|
category: r.category,
|
|
2157
|
-
reason:
|
|
2476
|
+
reason: hn({
|
|
2158
2477
|
reason: r.reason,
|
|
2159
2478
|
category: r.category,
|
|
2160
2479
|
via: "ai"
|
|
@@ -2173,7 +2492,7 @@ function Gn(e = {}) {
|
|
|
2173
2492
|
}
|
|
2174
2493
|
//#endregion
|
|
2175
2494
|
//#region src/composables/useMemiBoardUsers.ts
|
|
2176
|
-
var
|
|
2495
|
+
var mr = [
|
|
2177
2496
|
"owner",
|
|
2178
2497
|
"admin",
|
|
2179
2498
|
"manager",
|
|
@@ -2184,13 +2503,13 @@ var Kn = [
|
|
|
2184
2503
|
label: w("ko", `common.role.${e}`),
|
|
2185
2504
|
description: w("ko", `boardUsers.roleDescription.${e}`)
|
|
2186
2505
|
}));
|
|
2187
|
-
function
|
|
2188
|
-
let n = () =>
|
|
2506
|
+
function hr(t = {}) {
|
|
2507
|
+
let n = () => j(), i = Z(), { t: a } = D(), o = N(() => I(t.enabled) ?? !0 ? e(i, n()) : null), s = be(o, { ssrKey: r(n(), "users-management") });
|
|
2189
2508
|
async function c(e, t) {
|
|
2190
|
-
if (!
|
|
2191
|
-
await
|
|
2509
|
+
if (!mr.some((e) => e.value === t)) throw Error(a("boardUsers.unsupportedRole"));
|
|
2510
|
+
await Y(f(i, n(), e), {
|
|
2192
2511
|
role: t,
|
|
2193
|
-
updatedAt:
|
|
2512
|
+
updatedAt: K()
|
|
2194
2513
|
});
|
|
2195
2514
|
}
|
|
2196
2515
|
return {
|
|
@@ -2201,11 +2520,11 @@ function qn(t = {}) {
|
|
|
2201
2520
|
}
|
|
2202
2521
|
//#endregion
|
|
2203
2522
|
//#region src/composables/useMemiBoardUserProfile.ts
|
|
2204
|
-
function
|
|
2205
|
-
let t =
|
|
2523
|
+
function gr(e) {
|
|
2524
|
+
let t = Z(), n = () => j(), r = N(() => I(e)), i = N(() => {
|
|
2206
2525
|
let e = r.value;
|
|
2207
2526
|
return e ? f(t, n(), e) : null;
|
|
2208
|
-
}), { data: a, pending: o } =
|
|
2527
|
+
}), { data: a, pending: o } = Se(i);
|
|
2209
2528
|
return {
|
|
2210
2529
|
profile: a,
|
|
2211
2530
|
profilePending: o
|
|
@@ -2213,7 +2532,7 @@ function Jn(e) {
|
|
|
2213
2532
|
}
|
|
2214
2533
|
//#endregion
|
|
2215
2534
|
//#region src/utils/section.ts
|
|
2216
|
-
var
|
|
2535
|
+
var _r = [
|
|
2217
2536
|
3,
|
|
2218
2537
|
4,
|
|
2219
2538
|
5,
|
|
@@ -2223,7 +2542,7 @@ var Yn = [
|
|
|
2223
2542
|
9,
|
|
2224
2543
|
12
|
|
2225
2544
|
];
|
|
2226
|
-
function
|
|
2545
|
+
function vr(e) {
|
|
2227
2546
|
return [
|
|
2228
2547
|
{
|
|
2229
2548
|
value: "list",
|
|
@@ -2239,7 +2558,7 @@ function Xn(e) {
|
|
|
2239
2558
|
}
|
|
2240
2559
|
];
|
|
2241
2560
|
}
|
|
2242
|
-
function
|
|
2561
|
+
function yr(e) {
|
|
2243
2562
|
return [{
|
|
2244
2563
|
value: "latest",
|
|
2245
2564
|
label: w(e, "sectionUtil.sort.latest")
|
|
@@ -2248,18 +2567,18 @@ function Zn(e) {
|
|
|
2248
2567
|
label: w(e, "sectionUtil.sort.likes")
|
|
2249
2568
|
}];
|
|
2250
2569
|
}
|
|
2251
|
-
var
|
|
2252
|
-
function
|
|
2570
|
+
var br = vr("ko"), xr = yr("ko");
|
|
2571
|
+
function Sr(e) {
|
|
2253
2572
|
return e === "list" ? 5 : 4;
|
|
2254
2573
|
}
|
|
2255
|
-
function
|
|
2574
|
+
function Cr(e, t) {
|
|
2256
2575
|
let n = Math.floor(Number(t));
|
|
2257
|
-
return Number.isFinite(n) ? Math.min(e === "list" ? 20 : 10, Math.max(1, n)) :
|
|
2576
|
+
return Number.isFinite(n) ? Math.min(e === "list" ? 20 : 10, Math.max(1, n)) : Sr(e);
|
|
2258
2577
|
}
|
|
2259
|
-
function
|
|
2578
|
+
function wr(e) {
|
|
2260
2579
|
return e === 12 ? null : 12 - e;
|
|
2261
2580
|
}
|
|
2262
|
-
function
|
|
2581
|
+
function Tr(e) {
|
|
2263
2582
|
return {
|
|
2264
2583
|
3: "col-span-12 md:col-span-3",
|
|
2265
2584
|
4: "col-span-12 md:col-span-4",
|
|
@@ -2271,36 +2590,36 @@ function rr(e) {
|
|
|
2271
2590
|
12: "col-span-12"
|
|
2272
2591
|
}[e];
|
|
2273
2592
|
}
|
|
2274
|
-
function
|
|
2593
|
+
function Er(e) {
|
|
2275
2594
|
return e === "sm" ? "min-h-40" : e === "lg" ? "min-h-80" : "min-h-56";
|
|
2276
2595
|
}
|
|
2277
|
-
function
|
|
2596
|
+
function Dr(e) {
|
|
2278
2597
|
let t = e.boardId || e.category || "free";
|
|
2279
2598
|
return e.slug ? `/board/${t}/${e.slug}` : `/board/${t}`;
|
|
2280
2599
|
}
|
|
2281
|
-
function
|
|
2600
|
+
function Or(e) {
|
|
2282
2601
|
return e.kind === "comments" ? e.sort === "likes" ? "/boardComments?sort=likes" : "/boardComments" : e.kind === "list" && e.boardId ? `/board/${e.boardId}` : "/board";
|
|
2283
2602
|
}
|
|
2284
|
-
function
|
|
2603
|
+
function kr(e) {
|
|
2285
2604
|
if (e.boardId && e.postId) return `/board/${e.boardId}/post/${e.postId}`;
|
|
2286
2605
|
}
|
|
2287
|
-
function
|
|
2606
|
+
function Ar(e) {
|
|
2288
2607
|
return e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
|
|
2289
2608
|
}
|
|
2290
|
-
function
|
|
2291
|
-
return e.
|
|
2609
|
+
function jr(e, t) {
|
|
2610
|
+
return it(e, t).trim() || at(e, t).trim() || w(t, "sectionUtil.untitledPost");
|
|
2292
2611
|
}
|
|
2293
|
-
function
|
|
2612
|
+
function Mr(e) {
|
|
2294
2613
|
return e === "image" || e === "video" ? "media" : "list";
|
|
2295
2614
|
}
|
|
2296
|
-
function
|
|
2297
|
-
return w(t,
|
|
2615
|
+
function Nr(e, t) {
|
|
2616
|
+
return w(t, Mr(e) === "media" ? "sectionUtil.view.media" : "sectionUtil.view.list");
|
|
2298
2617
|
}
|
|
2299
|
-
function
|
|
2618
|
+
function Pr(e, t) {
|
|
2300
2619
|
return e ? t(e)?.listView ?? "default" : "default";
|
|
2301
2620
|
}
|
|
2302
|
-
function
|
|
2303
|
-
let n = e.map((e) =>
|
|
2621
|
+
function Fr(e, t) {
|
|
2622
|
+
let n = e.map((e) => Pr(e.boardId, t));
|
|
2304
2623
|
if (!n.length) return "default";
|
|
2305
2624
|
if (n.every((e) => e === n[0])) return n[0];
|
|
2306
2625
|
let r = n.filter((e) => e === "image").length, i = n.filter((e) => e === "video").length;
|
|
@@ -2308,27 +2627,27 @@ function pr(e, t) {
|
|
|
2308
2627
|
}
|
|
2309
2628
|
//#endregion
|
|
2310
2629
|
//#region src/composables/useMemiBoardSections.ts
|
|
2311
|
-
var
|
|
2312
|
-
function
|
|
2630
|
+
var Ir = [];
|
|
2631
|
+
function Lr(e) {
|
|
2313
2632
|
let t = Number(e.cols);
|
|
2314
|
-
if (
|
|
2633
|
+
if (_r.includes(t)) return t;
|
|
2315
2634
|
let n = e.width;
|
|
2316
2635
|
return n === "xs" ? 3 : n === "sm" ? 4 : n === "lg" || Number(e.span) === 2 ? 12 : 6;
|
|
2317
2636
|
}
|
|
2318
|
-
function
|
|
2637
|
+
function Rr(e) {
|
|
2319
2638
|
let t = e.height ?? e.size;
|
|
2320
2639
|
return t === "sm" || t === "lg" ? t : "md";
|
|
2321
2640
|
}
|
|
2322
|
-
function
|
|
2641
|
+
function zr(e) {
|
|
2323
2642
|
return e.kind === "post" || e.kind === "comments" ? e.kind : "list";
|
|
2324
2643
|
}
|
|
2325
|
-
function
|
|
2644
|
+
function Br(e) {
|
|
2326
2645
|
return e.sort === "likes" ? "likes" : "latest";
|
|
2327
2646
|
}
|
|
2328
|
-
function
|
|
2647
|
+
function Vr(e) {
|
|
2329
2648
|
return (typeof e.boardId == "string" ? e.boardId.trim() : "") || null;
|
|
2330
2649
|
}
|
|
2331
|
-
function
|
|
2650
|
+
function Hr(e, t) {
|
|
2332
2651
|
let n = Array.isArray(e.postIds) ? e.postIds : [], r = [];
|
|
2333
2652
|
for (let e of n) {
|
|
2334
2653
|
let n = String(e || "").trim();
|
|
@@ -2336,76 +2655,116 @@ function br(e, t) {
|
|
|
2336
2655
|
}
|
|
2337
2656
|
return r;
|
|
2338
2657
|
}
|
|
2339
|
-
function
|
|
2658
|
+
function Ur(e, t) {
|
|
2340
2659
|
return w(t, "sections.defaultTitle", { n: e + 1 });
|
|
2341
2660
|
}
|
|
2342
|
-
function
|
|
2343
|
-
let r =
|
|
2661
|
+
function Wr(e, t, n) {
|
|
2662
|
+
let r = zr(e), i = Cr(r, e.count), a = Q(e.i18n), o = Qe(e.i18nSource);
|
|
2344
2663
|
return {
|
|
2345
2664
|
id: String(e.id || `box-${t + 1}`),
|
|
2346
|
-
title: String(e.title ||
|
|
2665
|
+
title: String(e.title || Ur(t, n)),
|
|
2347
2666
|
showTitle: e.showTitle !== !1,
|
|
2348
2667
|
kind: r,
|
|
2349
|
-
boardId:
|
|
2668
|
+
boardId: Vr(e),
|
|
2350
2669
|
count: i,
|
|
2351
|
-
postIds:
|
|
2352
|
-
sort:
|
|
2353
|
-
cols:
|
|
2354
|
-
height:
|
|
2355
|
-
order: t
|
|
2670
|
+
postIds: Hr(e, i),
|
|
2671
|
+
sort: Br(e),
|
|
2672
|
+
cols: Lr(e),
|
|
2673
|
+
height: Rr(e),
|
|
2674
|
+
order: t,
|
|
2675
|
+
...a ? { i18n: a } : {},
|
|
2676
|
+
...o ? { i18nSource: o } : {}
|
|
2356
2677
|
};
|
|
2357
2678
|
}
|
|
2358
|
-
function
|
|
2359
|
-
let { locale: e } =
|
|
2360
|
-
async function
|
|
2361
|
-
|
|
2679
|
+
function Gr() {
|
|
2680
|
+
let { locale: e } = D(), { translateEntries: t } = Dn(), n = Z(), r = me(n, "settings", "homeLayout"), { data: i, pending: a } = Se(r), o = N(() => (Array.isArray(i.value?.blocks) ? i.value.blocks : Ir).map((t, n) => Wr(t, n, e.value)));
|
|
2681
|
+
async function s(n) {
|
|
2682
|
+
let r = e.value, i = n.filter((e) => {
|
|
2683
|
+
let t = e.title.trim();
|
|
2684
|
+
return t ? !(e.i18nSource?.title === t && tt(e.i18n, ["title"])) : !1;
|
|
2685
|
+
});
|
|
2686
|
+
if (!i.length) return n;
|
|
2687
|
+
let a = await t(i.map((e) => ({
|
|
2688
|
+
id: e.id,
|
|
2689
|
+
fields: { title: e.title.trim() },
|
|
2690
|
+
context: e.kind
|
|
2691
|
+
})), {
|
|
2692
|
+
source: r,
|
|
2693
|
+
purpose: "section heading on a website home page"
|
|
2694
|
+
});
|
|
2695
|
+
return n.map((e) => {
|
|
2696
|
+
let t = a[e.id];
|
|
2697
|
+
if (!t) return e;
|
|
2698
|
+
let n = e.title.trim(), i = et(r, { title: n }, t);
|
|
2699
|
+
return tt(i, ["title"]) ? {
|
|
2700
|
+
...e,
|
|
2701
|
+
title: n,
|
|
2702
|
+
i18n: i,
|
|
2703
|
+
i18nSource: {
|
|
2704
|
+
locale: r,
|
|
2705
|
+
title: n
|
|
2706
|
+
}
|
|
2707
|
+
} : e;
|
|
2708
|
+
});
|
|
2709
|
+
}
|
|
2710
|
+
function c(t, n) {
|
|
2711
|
+
let r = t, i = zr(r), a = Cr(i, t.count), o = t.title.trim() || Ur(n, e.value);
|
|
2712
|
+
return {
|
|
2713
|
+
id: t.id,
|
|
2714
|
+
title: o,
|
|
2715
|
+
showTitle: t.showTitle !== !1,
|
|
2716
|
+
kind: i,
|
|
2717
|
+
boardId: Vr(r),
|
|
2718
|
+
count: a,
|
|
2719
|
+
postIds: Hr(r, a),
|
|
2720
|
+
sort: Br(r),
|
|
2721
|
+
cols: Lr(r),
|
|
2722
|
+
height: Rr(r),
|
|
2723
|
+
order: n,
|
|
2724
|
+
...t.i18n ? { i18n: t.i18n } : {},
|
|
2725
|
+
...t.i18nSource?.title === o ? { i18nSource: t.i18nSource } : {}
|
|
2726
|
+
};
|
|
2727
|
+
}
|
|
2728
|
+
async function l(e) {
|
|
2729
|
+
let t = e;
|
|
2730
|
+
try {
|
|
2731
|
+
t = await s(e);
|
|
2732
|
+
} catch (e) {
|
|
2733
|
+
console.warn("[memi-board] section translation skipped", e);
|
|
2734
|
+
}
|
|
2735
|
+
await q(r, {
|
|
2362
2736
|
columns: 12,
|
|
2363
|
-
blocks: t.map((
|
|
2364
|
-
|
|
2365
|
-
return {
|
|
2366
|
-
id: t.id,
|
|
2367
|
-
title: t.title.trim() || xr(n, e.value),
|
|
2368
|
-
showTitle: t.showTitle !== !1,
|
|
2369
|
-
kind: i,
|
|
2370
|
-
boardId: yr(r),
|
|
2371
|
-
count: a,
|
|
2372
|
-
postIds: br(r, a),
|
|
2373
|
-
sort: vr(r),
|
|
2374
|
-
cols: hr(r),
|
|
2375
|
-
height: gr(r),
|
|
2376
|
-
order: n
|
|
2377
|
-
};
|
|
2378
|
-
}),
|
|
2379
|
-
updatedAt: q()
|
|
2737
|
+
blocks: t.map((e, t) => c(e, t)),
|
|
2738
|
+
updatedAt: K()
|
|
2380
2739
|
});
|
|
2381
2740
|
}
|
|
2382
2741
|
return {
|
|
2383
|
-
settings:
|
|
2384
|
-
pending:
|
|
2385
|
-
sections:
|
|
2386
|
-
blocks:
|
|
2387
|
-
hasSavedLayout:
|
|
2388
|
-
saveSections:
|
|
2389
|
-
saveBlocks:
|
|
2742
|
+
settings: i,
|
|
2743
|
+
pending: a,
|
|
2744
|
+
sections: o,
|
|
2745
|
+
blocks: o,
|
|
2746
|
+
hasSavedLayout: N(() => Array.isArray(i.value?.blocks)),
|
|
2747
|
+
saveSections: l,
|
|
2748
|
+
saveBlocks: l
|
|
2390
2749
|
};
|
|
2391
2750
|
}
|
|
2392
2751
|
//#endregion
|
|
2393
2752
|
//#region src/composables/useMemiBoardMiniPosts.ts
|
|
2394
|
-
function
|
|
2395
|
-
let n =
|
|
2396
|
-
let r = Math.max(1, Math.floor(Number(
|
|
2397
|
-
return a && o.unshift(
|
|
2398
|
-
}), { data: s, pending: c, error: l } =
|
|
2753
|
+
function Kr(e, t) {
|
|
2754
|
+
let n = Z(), i = () => j(), a = I(e)?.trim() || "all", o = N(() => {
|
|
2755
|
+
let r = Math.max(1, Math.floor(Number(I(t)) || 5)), a = I(e)?.trim() || "", o = [X("isPublished", "==", !0), X("listed", "==", !0)];
|
|
2756
|
+
return a && o.unshift(X("boardId", "==", a)), G(M(n, i()), ...o, W("createdAt", "desc"), H(r));
|
|
2757
|
+
}), { data: s, pending: c, error: l } = be(o, { ssrKey: r(i(), `mini-list/${a}`) });
|
|
2399
2758
|
return {
|
|
2400
|
-
posts:
|
|
2759
|
+
posts: N(() => s.value ?? []),
|
|
2401
2760
|
pending: c,
|
|
2402
2761
|
error: l
|
|
2403
2762
|
};
|
|
2404
2763
|
}
|
|
2405
|
-
function
|
|
2406
|
-
let t =
|
|
2764
|
+
function qr(e) {
|
|
2765
|
+
let t = Z(), n = () => j(), i = N(() => [...new Set(I(e).map((e) => e.trim()).filter(Boolean))].slice(0, 10)), a = i.value.join(",") || "none", o = N(() => i.value.length ? G(M(t, n()), X(R(), "in", i.value)) : null), { data: s, pending: c, error: l } = be(o, { ssrKey: r(n(), `mini-picked/${a}`) });
|
|
2407
2766
|
return {
|
|
2408
|
-
posts:
|
|
2767
|
+
posts: N(() => {
|
|
2409
2768
|
let e = new Map((s.value ?? []).map((e) => [e.id, e])), t = [];
|
|
2410
2769
|
for (let n of i.value) {
|
|
2411
2770
|
let r = e.get(n);
|
|
@@ -2419,7 +2778,16 @@ function Tr(e) {
|
|
|
2419
2778
|
}
|
|
2420
2779
|
//#endregion
|
|
2421
2780
|
//#region src/data/versionHistory.ts
|
|
2422
|
-
var
|
|
2781
|
+
var Jr = [
|
|
2782
|
+
{
|
|
2783
|
+
version: "0.46.1",
|
|
2784
|
+
date: "2026-10-22",
|
|
2785
|
+
highlights: [
|
|
2786
|
+
"게시판 이름과 설명을 저장하면 10개 언어로 번역해 둬요",
|
|
2787
|
+
"글쓰기 AI에서 한 번에 10개 언어로 번역해 저장할 수 있어요",
|
|
2788
|
+
"보는 언어로 제목과 본문이 보이고, 원문을 고치면 번역 대신 원문이 보여요"
|
|
2789
|
+
]
|
|
2790
|
+
},
|
|
2423
2791
|
{
|
|
2424
2792
|
version: "0.46.0",
|
|
2425
2793
|
date: "2026-10-21",
|
|
@@ -2722,7 +3090,7 @@ var Er = [
|
|
|
2722
3090
|
highlights: ["글쓰기·댓글·첨부·로그인 권한으로 게시판을 바로 시작할 수 있어요", "부적절한 글·댓글은 AI 검열로 막을 수 있어요"],
|
|
2723
3091
|
note: "실제 보안은 호스트 Firebase 규칙 등록이 필요합니다."
|
|
2724
3092
|
}
|
|
2725
|
-
],
|
|
3093
|
+
], Yr = 10, Xr = 864e5, Zr = 200, Qr = {
|
|
2726
3094
|
proofread: "맞춤법, 띄어쓰기, 문법 오류만 고쳐라. 의미와 말투는 바꾸지 마라.",
|
|
2727
3095
|
polish: "뜻과 작성자의 말투를 유지하면서 더 자연스럽고 읽기 좋은 문장으로 다듬어라.",
|
|
2728
3096
|
continue: "앞의 맥락과 말투에 맞는 내용을 과장 없이 한두 문단 이어서 작성하라.",
|
|
@@ -2731,7 +3099,7 @@ var Er = [
|
|
|
2731
3099
|
"translate-ko": "자연스러운 한국어로 번역하라. 고유명사와 기술 용어는 정확히 유지하라.",
|
|
2732
3100
|
title: "본문을 대표하는 간결한 한국어 제목 하나를 만들어라. 따옴표나 설명 없이 제목만 출력하라."
|
|
2733
3101
|
};
|
|
2734
|
-
function
|
|
3102
|
+
function $r(e) {
|
|
2735
3103
|
return {
|
|
2736
3104
|
proofread: "Fix only spelling, spacing, and grammar errors. Do not change the meaning or tone.",
|
|
2737
3105
|
polish: "Refine the text into more natural, readable sentences while keeping the meaning and the author's tone.",
|
|
@@ -2742,34 +3110,34 @@ function jr(e) {
|
|
|
2742
3110
|
title: `Create one concise title in ${e} that represents the body. Output only the title, without quotes or explanation.`
|
|
2743
3111
|
};
|
|
2744
3112
|
}
|
|
2745
|
-
function
|
|
2746
|
-
let r = t === "ko", i =
|
|
3113
|
+
function ei(e, t, n) {
|
|
3114
|
+
let r = t === "ko", i = cn(t);
|
|
2747
3115
|
if (e.action !== "custom") {
|
|
2748
|
-
if (r) return
|
|
2749
|
-
let t =
|
|
3116
|
+
if (r) return Qr[e.action];
|
|
3117
|
+
let t = $r(i)[e.action];
|
|
2750
3118
|
return e.action === "translate-en" || e.action === "translate-ko" || e.action === "title" ? t : `${t} Write the result in the same language as the original text; if unclear, write in ${i}.`;
|
|
2751
3119
|
}
|
|
2752
|
-
let a = e.customInstruction?.trim().slice(0,
|
|
3120
|
+
let a = e.customInstruction?.trim().slice(0, Zr);
|
|
2753
3121
|
if (!a) throw Error(n("writingAssistant.customRequired"));
|
|
2754
3122
|
return r ? `뜻은 유지하면서 다음 스타일 지시를 반영해 다시 써라: ${a}` : `Rewrite the text applying the following style instruction while keeping the meaning: ${a}`;
|
|
2755
3123
|
}
|
|
2756
|
-
function
|
|
3124
|
+
function ti(e) {
|
|
2757
3125
|
return e.trim().replace(/^```(?:html|text)?\s*/i, "").replace(/\s*```$/, "").trim();
|
|
2758
3126
|
}
|
|
2759
|
-
function
|
|
3127
|
+
function ni(e) {
|
|
2760
3128
|
return !e || typeof e != "object" ? null : "toMillis" in e && typeof e.toMillis == "function" ? e.toMillis() : null;
|
|
2761
3129
|
}
|
|
2762
|
-
function
|
|
2763
|
-
let t =
|
|
2764
|
-
async function
|
|
3130
|
+
function ri(e = {}) {
|
|
3131
|
+
let t = D(), n = N(() => T(I(e.locale)) ?? t.locale.value), r = (e, t) => w(n.value, e, t), i = Ce(), a = Z(), o = xe(), s = ue(), { translateEntries: c, translateHtml: l } = Dn();
|
|
3132
|
+
async function u() {
|
|
2765
3133
|
let e = o.value?.uid;
|
|
2766
3134
|
if (!e) throw Error(r("writingAssistant.loginRequired"));
|
|
2767
|
-
await
|
|
2768
|
-
let n = f(a,
|
|
3135
|
+
await ve(a, async (t) => {
|
|
3136
|
+
let n = f(a, j(), e), i = await t.get(n);
|
|
2769
3137
|
if (!i.exists()) throw Error(r("writingAssistant.userMissing"));
|
|
2770
|
-
let o = i.data(), s =
|
|
3138
|
+
let o = i.data(), s = ni(o.aiWritingWindowAt), c = Math.max(0, Math.floor(Number(o.aiWritingCount) || 0)), l = Date.now(), u = s != null && l - s < Xr;
|
|
2771
3139
|
if (u && c >= 10) {
|
|
2772
|
-
let e = s +
|
|
3140
|
+
let e = s + Xr, t = Math.max(1, Math.ceil((e - l) / 36e5));
|
|
2773
3141
|
throw Error(r("writingAssistant.dailyLimit", {
|
|
2774
3142
|
limit: 10,
|
|
2775
3143
|
hours: t
|
|
@@ -2777,20 +3145,20 @@ function Fr(e = {}) {
|
|
|
2777
3145
|
}
|
|
2778
3146
|
t.update(n, {
|
|
2779
3147
|
aiWritingCount: u ? c + 1 : 1,
|
|
2780
|
-
aiWritingWindowAt: u ? o.aiWritingWindowAt :
|
|
2781
|
-
updatedAt:
|
|
3148
|
+
aiWritingWindowAt: u ? o.aiWritingWindowAt : K(),
|
|
3149
|
+
updatedAt: K()
|
|
2782
3150
|
});
|
|
2783
3151
|
});
|
|
2784
3152
|
}
|
|
2785
|
-
async function
|
|
2786
|
-
let t = n.value, a = t === "ko", o =
|
|
3153
|
+
async function d(e) {
|
|
3154
|
+
let t = n.value, a = t === "ko", o = ei(e, t, r);
|
|
2787
3155
|
if (!e.content.trim()) throw Error(r("writingAssistant.contentRequired"));
|
|
2788
|
-
await
|
|
2789
|
-
let
|
|
2790
|
-
backend: new
|
|
2791
|
-
useLimitedUseAppCheckTokens:
|
|
2792
|
-
}), d =
|
|
2793
|
-
model:
|
|
3156
|
+
await u();
|
|
3157
|
+
let c = s.moderation ?? {}, l = Re(i, {
|
|
3158
|
+
backend: new Ie(),
|
|
3159
|
+
useLimitedUseAppCheckTokens: c.useLimitedUseAppCheckTokens !== !1
|
|
3160
|
+
}), d = ze(l, {
|
|
3161
|
+
model: c.model ?? "gemini-3.5-flash-lite",
|
|
2794
3162
|
systemInstruction: (a ? [
|
|
2795
3163
|
"너는 게시판 글쓰기 편집 도우미다.",
|
|
2796
3164
|
"사용자가 요청한 편집 결과만 출력하고 설명, 인사, 마크다운 코드 펜스를 붙이지 마라.",
|
|
@@ -2812,94 +3180,64 @@ function Fr(e = {}) {
|
|
|
2812
3180
|
`Task: ${o}`,
|
|
2813
3181
|
e.title ? `Current title: ${e.title}` : "",
|
|
2814
3182
|
`Body:\n${f.slice(0, 12e3)}`
|
|
2815
|
-
]).filter(Boolean).join("\n\n"), m =
|
|
3183
|
+
]).filter(Boolean).join("\n\n"), m = ti((await d.generateContent(p)).response.text());
|
|
2816
3184
|
if (!m) throw Error(r("writingAssistant.emptyResult"));
|
|
2817
3185
|
return m;
|
|
2818
3186
|
}
|
|
2819
|
-
|
|
2820
|
-
}
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
let
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
}
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
return Re(i, {
|
|
2842
|
-
model: t.model ?? "gemini-3.5-flash-lite",
|
|
2843
|
-
systemInstruction: [
|
|
2844
|
-
"You translate short user-interface strings for a website.",
|
|
2845
|
-
"Return natural, concise UI wording a native speaker would expect in a menu or title — not literal word-for-word translations.",
|
|
2846
|
-
"Keep proper nouns, brand names, product names and game titles as they are unless they have a well-known localized name.",
|
|
2847
|
-
"Use the context only to understand meaning; never translate or output it."
|
|
2848
|
-
].join("\n"),
|
|
2849
|
-
generationConfig: {
|
|
2850
|
-
temperature: .2,
|
|
2851
|
-
maxOutputTokens: 2048,
|
|
2852
|
-
responseMimeType: "application/json",
|
|
2853
|
-
responseSchema: Ie.object({ properties: Object.fromEntries(n.map((e) => [e, a])) })
|
|
3187
|
+
async function p(e) {
|
|
3188
|
+
let t = e.source ?? n.value, i = e.title?.trim() ?? "", a = e.summary?.trim() ?? "", o = e.content ?? "", s = {};
|
|
3189
|
+
i && (s.title = i), a && (s.summary = a);
|
|
3190
|
+
let d = !!Kt(o);
|
|
3191
|
+
if (!Object.keys(s).length && !d) throw Error(r("writingAssistant.translateEmpty"));
|
|
3192
|
+
if (d && o.length > 12e3) throw Error(r("writingAssistant.translateTooLong", { limit: Sn }));
|
|
3193
|
+
await u();
|
|
3194
|
+
let [f, p] = await Promise.all([Object.keys(s).length ? c([{
|
|
3195
|
+
id: "post",
|
|
3196
|
+
fields: s,
|
|
3197
|
+
context: "message board post title and short summary"
|
|
3198
|
+
}], {
|
|
3199
|
+
source: t,
|
|
3200
|
+
purpose: "message board post title and short summary"
|
|
3201
|
+
}) : Promise.resolve({}), d ? l(o, { source: t }) : Promise.resolve({})]), m = et(t, s, f.post);
|
|
3202
|
+
if (Object.keys(s).length && !tt(m, Object.keys(s))) throw Error(r("writingAssistant.translateIncomplete"));
|
|
3203
|
+
let h = {
|
|
3204
|
+
i18n: m,
|
|
3205
|
+
i18nSource: {
|
|
3206
|
+
locale: t,
|
|
3207
|
+
title: i,
|
|
3208
|
+
summary: a
|
|
2854
3209
|
}
|
|
2855
|
-
}
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
`Translate into: ${r.map((e) => `${e} = ${Mn(e)}`).join(", ")}`,
|
|
2862
|
-
e.context ? `Context (do not translate): ${e.context}` : "",
|
|
2863
|
-
"Text (JSON):",
|
|
2864
|
-
JSON.stringify(e.fields)
|
|
2865
|
-
].filter(Boolean).join("\n"), s = await Lr(n(r, a).generateContent(o), Ir), c = JSON.parse(s.response.text()), l = {};
|
|
2866
|
-
for (let e of r) {
|
|
2867
|
-
let t = c?.[e];
|
|
2868
|
-
if (!t) continue;
|
|
2869
|
-
let n = {};
|
|
2870
|
-
for (let e of a) {
|
|
2871
|
-
let r = typeof t[e] == "string" ? t[e].trim() : "";
|
|
2872
|
-
r && (n[e] = r);
|
|
3210
|
+
};
|
|
3211
|
+
if (d) {
|
|
3212
|
+
let e = {};
|
|
3213
|
+
for (let [t, n] of Object.entries(p)) {
|
|
3214
|
+
let r = T(t);
|
|
3215
|
+
r && typeof n == "string" && n.trim() && (e[r] = { content: n.trim() });
|
|
2873
3216
|
}
|
|
2874
|
-
|
|
3217
|
+
let n = et(t, { content: o }, e);
|
|
3218
|
+
if (!tt(n, ["content"])) throw Error(r("writingAssistant.translateIncomplete"));
|
|
3219
|
+
h.contentI18n = n, h.contentI18nSource = {
|
|
3220
|
+
locale: t,
|
|
3221
|
+
hash: Xe(o)
|
|
3222
|
+
};
|
|
2875
3223
|
}
|
|
2876
|
-
return
|
|
3224
|
+
return h;
|
|
2877
3225
|
}
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
})).filter((e) => Object.keys(e.fields).length);
|
|
2883
|
-
if (!o.length || !i.length) return {};
|
|
2884
|
-
let s = await Promise.all(o.map((e) => r(e, n, i, a)));
|
|
2885
|
-
return Object.fromEntries(o.map((e, t) => [e.id, s[t]]));
|
|
2886
|
-
}
|
|
2887
|
-
return { translateEntries: i };
|
|
2888
|
-
}
|
|
2889
|
-
function zr(e, t, n, r) {
|
|
2890
|
-
let i = E(t);
|
|
2891
|
-
return !i || i === "ko" ? r : e?.[i]?.[n] || e?.en?.[n] || r;
|
|
3226
|
+
return {
|
|
3227
|
+
assist: d,
|
|
3228
|
+
translatePost: p
|
|
3229
|
+
};
|
|
2892
3230
|
}
|
|
2893
3231
|
//#endregion
|
|
2894
3232
|
//#region src/utils/renderMarkdown.ts
|
|
2895
|
-
function
|
|
3233
|
+
function ii(e) {
|
|
2896
3234
|
return e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2897
3235
|
}
|
|
2898
|
-
function
|
|
3236
|
+
function ai(e) {
|
|
2899
3237
|
let t = e;
|
|
2900
3238
|
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;
|
|
2901
3239
|
}
|
|
2902
|
-
function
|
|
3240
|
+
function oi(e) {
|
|
2903
3241
|
if (!e?.trim()) return "";
|
|
2904
3242
|
let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = 0, i = !1, a = [], o = null, s = [];
|
|
2905
3243
|
function c() {
|
|
@@ -2908,7 +3246,7 @@ function Hr(e) {
|
|
|
2908
3246
|
return;
|
|
2909
3247
|
}
|
|
2910
3248
|
let e = o;
|
|
2911
|
-
n.push(`<${e}>${s.map((e) => `<li>${
|
|
3249
|
+
n.push(`<${e}>${s.map((e) => `<li>${ai(e)}</li>`).join("")}</${e}>`), o = null, s = [];
|
|
2912
3250
|
}
|
|
2913
3251
|
for (; r < t.length;) {
|
|
2914
3252
|
let e = t[r] ?? "", l = e.match(/^```(\w*)\s*$/);
|
|
@@ -2917,7 +3255,7 @@ function Hr(e) {
|
|
|
2917
3255
|
continue;
|
|
2918
3256
|
}
|
|
2919
3257
|
if (i) {
|
|
2920
|
-
a.push(
|
|
3258
|
+
a.push(ii(e)), r++;
|
|
2921
3259
|
continue;
|
|
2922
3260
|
}
|
|
2923
3261
|
if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(e)) {
|
|
@@ -2928,24 +3266,24 @@ function Hr(e) {
|
|
|
2928
3266
|
if (u) {
|
|
2929
3267
|
c();
|
|
2930
3268
|
let e = u[1].length;
|
|
2931
|
-
n.push(`<h${e} class="font-semibold mt-4 mb-2">${
|
|
3269
|
+
n.push(`<h${e} class="font-semibold mt-4 mb-2">${ai(ii(u[2]))}</h${e}>`), r++;
|
|
2932
3270
|
continue;
|
|
2933
3271
|
}
|
|
2934
3272
|
if (e.startsWith("> ")) {
|
|
2935
3273
|
c();
|
|
2936
3274
|
let e = [];
|
|
2937
|
-
for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push(
|
|
2938
|
-
n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${
|
|
3275
|
+
for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push(ii((t[r] ?? "").slice(2))), r++;
|
|
3276
|
+
n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${ai(e.join("<br />"))}</blockquote>`);
|
|
2939
3277
|
continue;
|
|
2940
3278
|
}
|
|
2941
3279
|
let d = e.match(/^[-*+]\s+(.+)$/);
|
|
2942
3280
|
if (d) {
|
|
2943
|
-
o && o !== "ul" && c(), o = "ul", s.push(
|
|
3281
|
+
o && o !== "ul" && c(), o = "ul", s.push(ii(d[1])), r++;
|
|
2944
3282
|
continue;
|
|
2945
3283
|
}
|
|
2946
3284
|
let f = e.match(/^\d+\.\s+(.+)$/);
|
|
2947
3285
|
if (f) {
|
|
2948
|
-
o && o !== "ol" && c(), o = "ol", s.push(
|
|
3286
|
+
o && o !== "ol" && c(), o = "ol", s.push(ii(f[1])), r++;
|
|
2949
3287
|
continue;
|
|
2950
3288
|
}
|
|
2951
3289
|
if (!e.trim()) {
|
|
@@ -2957,13 +3295,13 @@ function Hr(e) {
|
|
|
2957
3295
|
for (; r < t.length;) {
|
|
2958
3296
|
let e = t[r] ?? "";
|
|
2959
3297
|
if (!e.trim() || /^(#{1,3}\s|```|[-*+]\s|\d+\.\s|> |(-{3,}|\*{3,})\s*$)/.test(e)) break;
|
|
2960
|
-
p.push(
|
|
3298
|
+
p.push(ii(e)), r++;
|
|
2961
3299
|
}
|
|
2962
|
-
n.push(`<p class="my-2 leading-relaxed">${
|
|
3300
|
+
n.push(`<p class="my-2 leading-relaxed">${ai(p.join("<br />"))}</p>`);
|
|
2963
3301
|
}
|
|
2964
3302
|
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");
|
|
2965
3303
|
}
|
|
2966
3304
|
//#endregion
|
|
2967
|
-
export {
|
|
3305
|
+
export { Kn as AUTHOR_MEMO_MAX_LENGTH, _r as BOARD_SECTION_COLS, br as BOARD_SECTION_KINDS, xr as BOARD_SECTION_SORTS, mr as BOARD_USER_ROLES, Qn as COMMENT_BODY_MAX_LENGTH, xt as DEFAULT_BLOCK_BAN_DECAY_MS, bt as DEFAULT_BLOCK_BAN_THRESHOLD, On as DEFAULT_BOARDS, kn as DEFAULT_CATEGORIES, S as DEFAULT_COMMENTS_COLLECTION, le as DEFAULT_LIKES_COLLECTION, ee as DEFAULT_MEMI_BOARD_LOCALE, h as DEFAULT_POSTS_COLLECTION, n as DEFAULT_REPORTS_COLLECTION, ce as DEFAULT_SETTINGS_COLLECTION, g as DEFAULT_USERS_COLLECTION, ie as FALLBACK_MEMI_BOARD_LOCALE, Sn as HTML_MAX_CHARS, A as MEMI_BOARD_LOCALES, ar as REPORT_DETAIL_MAX_LENGTH, or as REPORT_REASONS, Yr as WRITING_ASSISTANT_DAILY_LIMIT, Ve as asHttpUrl, st as boardCopyIsTranslated, qe as boardListOgDescription, Ke as boardListOgTitle, Ge as boardPostOgDescription, We as boardPostOgTitle, vr as boardSectionKinds, yr as boardSectionSorts, r as boardSsrKey, f as boardUserDoc, e as boardUsersCol, en as buildPostPreview, ht as canManageBoardByRole, gt as canWriteCommentByRole, Cr as clampSectionCount, O as commentDoc, u as commentsCol, b as configureMemiBoard, tt as coversLocales, Sr as defaultSectionCount, ot as displayPostContent, at as displayPostSummary, it as displayPostTitle, rt as displayStoredField, St as effectiveModerationBlockCount, Ft as extractEditorImageUrls, ft as fetchPublicListForSeo, dt as fetchPublicPostForSeo, x as formatDate, re as formatFullDate, oe as formatRelativeDate, Tt as formatRestrictedUntilLabel, C as formatTimestampDetails, Yt as hasBodyImage, Jt as hasBodyText, Xe as hashText, Nt as imeSafeSubmitClick, Mt as imeSafeSubmitPointerDown, mt as isAssignedStaff, Xt as isContentEmpty, Pn as isFirestorePermissionDenied, k as isMemiBoardLocaleLoaded, wt as isModerationWriteRestricted, d as likeDoc, p as likeDocId, a as likesCol, se as loadMemiBoardLocale, nt as localizedText, rr as memiBoardCommentLikesKey, ae as memiBoardHostLocaleKey, E as memiBoardLocaleInfo, et as mergeSourceFields, Pr as miniBoardView, kr as miniCommentLink, Or as miniMoreLink, Ar as miniPostImage, Dr as miniPostLink, jr as miniPostTitle, Fr as miniPostsView, Mr as miniViewGroup, Nr as miniViewLabel, Ct as moderationWriteRestrictedUntilMs, Je as normalizeBasePath, Wr as normalizeBoardSection, T as normalizeMemiBoardLocale, wr as pairSectionCols, Kt as plainTextFromHtml, c as postBodyDoc, l as postDoc, Lt as postNamespaceFromStoragePath, m as postStorageFolder, M as postsCol, Qe as readI18nSource, Q as readLocalizedFields, oi as renderMarkdownToHtml, t as reportDoc, v as reportDocId, sr as reportReasonLabel, s as reportsCol, _ as resolveBoardPathConfig, ct as resolvePublicSeoDb, Tr as sectionColClass, Er as sectionMinHeight, y as settingsCol, o as settingsDoc, ne as slugify, It as storagePathFromDownloadUrl, qt as titleFromBody, Ue as toAbsoluteUrl, He as toBoardOgImageUrl, w as translate, j as useBoardPathConfig, kt as useMemiBoardAuth, Jn as useMemiBoardAuthorMemo, Dn as useMemiBoardAutoTranslate, Gn as useMemiBoardCommentFeed, ir as useMemiBoardCommentLikes, er as useMemiBoardComments, ue as useMemiBoardConfig, D as useMemiBoardI18n, nr as useMemiBoardLikes, Kr as useMemiBoardMiniLatest, qr as useMemiBoardMiniPicked, pr as useMemiBoardModeration, zn as useMemiBoardPost, Rn as useMemiBoardPostList, In as useMemiBoardPosts, tr as useMemiBoardReplies, ur as useMemiBoardReportQueue, cr as useMemiBoardReports, Gr as useMemiBoardSections, Mn as useMemiBoardSettings, Un as useMemiBoardUserComments, Vn as useMemiBoardUserPosts, gr as useMemiBoardUserProfile, hr as useMemiBoardUsers, dr as useMemiBoardViews, ri as useMemiBoardWritingAssistant, Jr as versionHistory, $t as videoListCoverUrl, Qt as youtubeId };
|
|
2968
3306
|
|
|
2969
3307
|
//# sourceMappingURL=index.js.map
|