memi-board 0.46.0 → 0.46.2
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-D_6hknjh.js} +22 -1
- package/dist/ar-D_6hknjh.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-DQqSsBsZ.js} +22 -1
- package/dist/de-DQqSsBsZ.js.map +1 -0
- package/dist/{en-CaPfPNDy.js → en-Broh-9df.js} +19 -1
- package/dist/en-Broh-9df.js.map +1 -0
- package/dist/{es-7JHjGVlM.js → es-D3eEYN6w.js} +22 -1
- package/dist/es-D3eEYN6w.js.map +1 -0
- package/dist/{fr-C3NCMNoy.js → fr-CbXPRKeU.js} +22 -1
- package/dist/fr-CbXPRKeU.js.map +1 -0
- package/dist/{id-C8hCAMPJ.js → id-DxVqvPpo.js} +22 -1
- package/dist/id-DxVqvPpo.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1266 -921
- package/dist/index.js.map +1 -1
- package/dist/{ja-oTWri4hY.js → ja-D2dIWUse.js} +22 -1
- package/dist/{ja-oTWri4hY.js.map → ja-D2dIWUse.js.map} +1 -1
- package/dist/{pt-B9EH4scA.js → pt-BAuHdglU.js} +22 -1
- package/dist/pt-BAuHdglU.js.map +1 -0
- package/dist/runtime/components/CategorySettings.vue +80 -14
- 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 +77 -11
- package/dist/runtime/composables/useMemiBoardSeo.ts +3 -3
- package/dist/{slugify-DJ82ifCd.js → slugify-B05Qykf_.js} +28 -10
- package/dist/slugify-B05Qykf_.js.map +1 -0
- package/dist/storage.js +1 -1
- package/dist/types.d.ts +48 -0
- package/dist/utils/localizedFields.d.ts +33 -0
- package/dist/{zh-D5rDYV6E.js → zh-CMyzTdQ5.js} +22 -1
- package/dist/zh-CMyzTdQ5.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-B05Qykf_.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,138 @@ 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
|
+
autoTranslate: t.autoTranslate === !0,
|
|
1046
|
+
i18n: Q(t.i18n),
|
|
1047
|
+
i18nSource: Qe(t.i18nSource)
|
|
682
1048
|
};
|
|
683
1049
|
}
|
|
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;
|
|
1050
|
+
function Mn() {
|
|
1051
|
+
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 = _;
|
|
1052
|
+
function b(e, t) {
|
|
1053
|
+
if (!e) return;
|
|
1054
|
+
let n = h.value.find((t) => t.id === e);
|
|
1055
|
+
return n ? rt(n.i18n, n.i18nSource, "label", n.label, t ?? u.value) || n.label : e;
|
|
692
1056
|
}
|
|
693
1057
|
let x = b;
|
|
694
|
-
function S(e) {
|
|
695
|
-
|
|
1058
|
+
function S(e, t) {
|
|
1059
|
+
if (!e) return;
|
|
1060
|
+
let n = h.value.find((t) => t.id === e), r = n?.description?.trim();
|
|
1061
|
+
if (n && r) return rt(n.i18n, n.i18nSource, "description", r, t ?? u.value) || void 0;
|
|
696
1062
|
}
|
|
697
1063
|
let C = S;
|
|
698
1064
|
function w(e) {
|
|
699
|
-
return
|
|
1065
|
+
return e ? h.value.find((t) => t.id === e)?.visibility ?? "public" : "public";
|
|
1066
|
+
}
|
|
1067
|
+
let ee = w;
|
|
1068
|
+
function E(e) {
|
|
1069
|
+
return w(e) === "hidden";
|
|
700
1070
|
}
|
|
701
|
-
let
|
|
702
|
-
function
|
|
703
|
-
if (e) return
|
|
1071
|
+
let O = E;
|
|
1072
|
+
function te(e) {
|
|
1073
|
+
if (e) return h.value.find((t) => t.id === e);
|
|
704
1074
|
}
|
|
705
|
-
|
|
1075
|
+
vt((e) => te(e));
|
|
706
1076
|
function k(e) {
|
|
707
|
-
return
|
|
1077
|
+
return ht(c.value?.uid, a.value, s.value, e ? te(e) : void 0);
|
|
708
1078
|
}
|
|
709
|
-
let
|
|
710
|
-
function
|
|
1079
|
+
let A = N(() => h.value.filter((e) => k(e.id)));
|
|
1080
|
+
function re(e, t) {
|
|
711
1081
|
return {
|
|
712
1082
|
label: e.label.trim(),
|
|
713
1083
|
description: (e.description ?? "").trim(),
|
|
714
|
-
visibility:
|
|
1084
|
+
visibility: An(e.visibility),
|
|
715
1085
|
listView: e.listView ?? "default",
|
|
716
1086
|
editorType: e.editorType ?? (e.listView === "image" ? "image" : "default"),
|
|
717
1087
|
writeRole: e.writeRole ?? "user",
|
|
718
1088
|
commentWriteRole: e.commentWriteRole ?? "user",
|
|
719
1089
|
allowedStaffUids: e.allowedStaffUids ?? [],
|
|
720
1090
|
allowedManagerUids: e.allowedManagerUids ?? [],
|
|
721
|
-
locale:
|
|
1091
|
+
locale: T(e.locale) ?? "",
|
|
1092
|
+
autoTranslate: e.autoTranslate === !0,
|
|
1093
|
+
...e.i18n ? { i18n: e.i18n } : {},
|
|
1094
|
+
...e.i18nSource ? { i18nSource: e.i18nSource } : {},
|
|
722
1095
|
order: t,
|
|
723
|
-
updatedAt:
|
|
1096
|
+
updatedAt: K()
|
|
724
1097
|
};
|
|
725
1098
|
}
|
|
726
|
-
async function
|
|
1099
|
+
async function ie(e) {
|
|
1100
|
+
let t = e.label.trim(), n = (e.description ?? "").trim();
|
|
1101
|
+
if (!t) return e;
|
|
1102
|
+
if (st({
|
|
1103
|
+
...e,
|
|
1104
|
+
label: t,
|
|
1105
|
+
description: n
|
|
1106
|
+
})) return {
|
|
1107
|
+
...e,
|
|
1108
|
+
label: t,
|
|
1109
|
+
description: n
|
|
1110
|
+
};
|
|
1111
|
+
let r = u.value, i = { label: t };
|
|
1112
|
+
n && (i.description = n);
|
|
1113
|
+
let a = et(r, i, (await d([{
|
|
1114
|
+
id: e.id || "board",
|
|
1115
|
+
fields: i,
|
|
1116
|
+
context: e.id
|
|
1117
|
+
}], {
|
|
1118
|
+
source: r,
|
|
1119
|
+
purpose: "board name and short description shown in navigation and page headers"
|
|
1120
|
+
}))[e.id || "board"]);
|
|
1121
|
+
if (!tt(a, Object.keys(i))) throw Error(l("settings.autoTranslate.failed"));
|
|
1122
|
+
return {
|
|
1123
|
+
...e,
|
|
1124
|
+
label: t,
|
|
1125
|
+
description: n,
|
|
1126
|
+
i18n: a,
|
|
1127
|
+
i18nSource: {
|
|
1128
|
+
locale: r,
|
|
1129
|
+
label: t,
|
|
1130
|
+
description: n
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
async function ae(n, r = n.order ?? 0) {
|
|
727
1135
|
if (!i.value) throw Error(l("boardSettings.signInRequired"));
|
|
728
1136
|
let a = n.id.trim(), o = n.label.trim();
|
|
729
1137
|
if (!a || !o) throw Error(l("boardSettings.idAndNameRequired"));
|
|
730
|
-
let s =
|
|
1138
|
+
let s = An(n.visibility), c = re({
|
|
731
1139
|
...n,
|
|
732
1140
|
label: o,
|
|
733
1141
|
visibility: s
|
|
734
1142
|
}, r);
|
|
735
|
-
await
|
|
1143
|
+
await an(t, e(), a, c);
|
|
736
1144
|
}
|
|
737
|
-
let
|
|
738
|
-
async function
|
|
1145
|
+
let oe = ae;
|
|
1146
|
+
async function se(e) {
|
|
739
1147
|
if (!i.value) throw Error(l("boardSettings.signInRequired"));
|
|
740
1148
|
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
|
|
1149
|
+
for (let [t, n] of e.entries()) await ae(n, t);
|
|
742
1150
|
}
|
|
743
|
-
let
|
|
744
|
-
async function
|
|
1151
|
+
let ce = se;
|
|
1152
|
+
async function le(r) {
|
|
745
1153
|
if (!i.value) throw Error(l("boardSettings.signInRequired"));
|
|
746
1154
|
let a = r.trim();
|
|
747
1155
|
if (!a) throw Error(l("boardSettings.deleteIdRequired"));
|
|
748
1156
|
for (;;) {
|
|
749
|
-
let r = await
|
|
1157
|
+
let r = await B(G(M(t, e()), X("boardId", "==", a), H(25)));
|
|
750
1158
|
if (r.empty) break;
|
|
751
|
-
for (let i of r.docs) await
|
|
1159
|
+
for (let i of r.docs) await Gt(t, Ne(n), e(), i.id);
|
|
752
1160
|
}
|
|
753
|
-
await
|
|
1161
|
+
await fe(o(t, e(), r));
|
|
754
1162
|
}
|
|
755
|
-
let
|
|
756
|
-
async function
|
|
1163
|
+
let ue = le;
|
|
1164
|
+
async function P() {
|
|
757
1165
|
return Promise.resolve();
|
|
758
1166
|
}
|
|
759
|
-
async function
|
|
1167
|
+
async function de(e) {
|
|
760
1168
|
let t = e.trim();
|
|
761
1169
|
if (!t) throw Error(l("boardSettings.nameRequired"));
|
|
762
1170
|
if (!i.value) throw Error(l("boardSettings.signInRequired"));
|
|
763
|
-
let n =
|
|
1171
|
+
let n = h.value.find((e) => e.label === t);
|
|
764
1172
|
if (n) return n.id;
|
|
765
|
-
let r =
|
|
1173
|
+
let r = ne(t) || `board-${Date.now()}`, a = r, o = 2, s = new Set(h.value.map((e) => e.id));
|
|
766
1174
|
for (; s.has(a);) a = `${r}-${o++}`;
|
|
767
|
-
return await
|
|
1175
|
+
return await ae({
|
|
768
1176
|
id: a,
|
|
769
1177
|
label: t,
|
|
770
1178
|
visibility: "public",
|
|
@@ -772,62 +1180,74 @@ function Yt() {
|
|
|
772
1180
|
editorType: "default",
|
|
773
1181
|
writeRole: "user",
|
|
774
1182
|
commentWriteRole: "user",
|
|
775
|
-
order:
|
|
1183
|
+
order: h.value.length
|
|
776
1184
|
}), a;
|
|
777
1185
|
}
|
|
778
1186
|
return {
|
|
779
|
-
boards:
|
|
780
|
-
publicBoards:
|
|
781
|
-
boardLabel:
|
|
782
|
-
boardDescription:
|
|
783
|
-
boardVisibility:
|
|
784
|
-
isBoardHidden:
|
|
785
|
-
getBoard:
|
|
1187
|
+
boards: h,
|
|
1188
|
+
publicBoards: _,
|
|
1189
|
+
boardLabel: b,
|
|
1190
|
+
boardDescription: S,
|
|
1191
|
+
boardVisibility: w,
|
|
1192
|
+
isBoardHidden: E,
|
|
1193
|
+
getBoard: te,
|
|
786
1194
|
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
|
-
|
|
1195
|
+
managedBoards: A,
|
|
1196
|
+
categories: g,
|
|
1197
|
+
publicCategories: v,
|
|
1198
|
+
categoryLabel: x,
|
|
1199
|
+
categoryDescription: C,
|
|
1200
|
+
categoryVisibility: ee,
|
|
1201
|
+
isCategoryHidden: O,
|
|
1202
|
+
settingsPending: m,
|
|
1203
|
+
ensureSettings: P,
|
|
1204
|
+
addBoard: de,
|
|
1205
|
+
addCategory: de,
|
|
1206
|
+
saveBoard: ae,
|
|
1207
|
+
saveCategory: oe,
|
|
1208
|
+
translateBoard: ie,
|
|
1209
|
+
saveBoards: se,
|
|
1210
|
+
saveCategories: ce,
|
|
1211
|
+
deleteBoard: le,
|
|
1212
|
+
deleteCategory: ue,
|
|
1213
|
+
DEFAULT_BOARDS: On,
|
|
1214
|
+
DEFAULT_CATEGORIES: kn
|
|
806
1215
|
};
|
|
807
1216
|
}
|
|
808
1217
|
//#endregion
|
|
809
1218
|
//#region src/composables/useMemiBoardPosts.ts
|
|
810
|
-
var
|
|
811
|
-
function
|
|
1219
|
+
var Nn = /* @__PURE__ */ new Map();
|
|
1220
|
+
function Pn(e) {
|
|
812
1221
|
if (!e || typeof e != "object") return !1;
|
|
813
1222
|
let t = "code" in e ? String(e.code) : "";
|
|
814
1223
|
return t === "permission-denied" || t === "firestore/permission-denied";
|
|
815
1224
|
}
|
|
816
|
-
function
|
|
817
|
-
let n =
|
|
1225
|
+
function Fn(e, t) {
|
|
1226
|
+
let n = I(e)?.trim();
|
|
818
1227
|
if (!n) throw Error(w(t, "posts.boardIdRequired"));
|
|
819
1228
|
return n;
|
|
820
1229
|
}
|
|
821
|
-
function
|
|
822
|
-
let t =
|
|
823
|
-
function g() {
|
|
824
|
-
return
|
|
1230
|
+
function In(e) {
|
|
1231
|
+
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);
|
|
1232
|
+
function g(e, t, n) {
|
|
1233
|
+
return {
|
|
1234
|
+
id: e,
|
|
1235
|
+
...t,
|
|
1236
|
+
i18n: Q(t.i18n),
|
|
1237
|
+
i18nSource: Qe(t.i18nSource),
|
|
1238
|
+
content: typeof n?.content == "string" ? n.content : "",
|
|
1239
|
+
contentI18n: Q(n?.i18n),
|
|
1240
|
+
contentI18nSource: $e(n?.i18nSource)
|
|
1241
|
+
};
|
|
825
1242
|
}
|
|
826
1243
|
function _() {
|
|
1244
|
+
return !r(f());
|
|
1245
|
+
}
|
|
1246
|
+
function v() {
|
|
827
1247
|
return i(f())?.editorType === "image";
|
|
828
1248
|
}
|
|
829
|
-
function
|
|
830
|
-
let t =
|
|
1249
|
+
function y(e) {
|
|
1250
|
+
let t = v(), n = Jt(e.content), r = Yt(e.content, e.attachments);
|
|
831
1251
|
if (t) {
|
|
832
1252
|
if (!r) throw Error(s("posts.photoRequired"));
|
|
833
1253
|
return;
|
|
@@ -835,16 +1255,16 @@ function $t(e) {
|
|
|
835
1255
|
if (!n && !r) throw Error(s("posts.bodyRequired"));
|
|
836
1256
|
if (!e.title?.trim()) throw Error(s("posts.titleRequired"));
|
|
837
1257
|
}
|
|
838
|
-
function
|
|
839
|
-
return
|
|
1258
|
+
function b() {
|
|
1259
|
+
return me(p()).id;
|
|
840
1260
|
}
|
|
841
|
-
function
|
|
842
|
-
return
|
|
1261
|
+
function x(e) {
|
|
1262
|
+
return Pt(t, d(), f(), e, a.value?.uid, o(f()));
|
|
843
1263
|
}
|
|
844
|
-
async function
|
|
845
|
-
let t = e.pageSize ?? 20, n = [
|
|
846
|
-
e.publicOnly !== !1 && n.push(
|
|
847
|
-
let r = await
|
|
1264
|
+
async function S(e = {}) {
|
|
1265
|
+
let t = e.pageSize ?? 20, n = [X("boardId", "==", f()), X("isPublished", "==", !0)];
|
|
1266
|
+
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));
|
|
1267
|
+
let r = await B(G(p(), ...n)), i = r.docs.slice(0, t);
|
|
848
1268
|
return {
|
|
849
1269
|
posts: i.map((e) => ({
|
|
850
1270
|
id: e.id,
|
|
@@ -854,58 +1274,54 @@ function $t(e) {
|
|
|
854
1274
|
hasMore: r.docs.length > t
|
|
855
1275
|
};
|
|
856
1276
|
}
|
|
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
1277
|
async function C(e) {
|
|
1278
|
+
let [t, n] = await Promise.all([z(m(e)), z(h(e))]);
|
|
1279
|
+
return t.exists() ? g(t.id, t.data(), n.exists() ? n.data() : void 0) : null;
|
|
1280
|
+
}
|
|
1281
|
+
async function w(e) {
|
|
866
1282
|
try {
|
|
867
|
-
let t = await
|
|
1283
|
+
let t = await x(e.trim());
|
|
868
1284
|
if (!t) return { status: "not-found" };
|
|
869
1285
|
try {
|
|
870
|
-
let e = await
|
|
1286
|
+
let e = await C(t.id);
|
|
871
1287
|
return e ? {
|
|
872
1288
|
status: "ok",
|
|
873
1289
|
post: e
|
|
874
1290
|
} : { status: "not-found" };
|
|
875
1291
|
} catch (e) {
|
|
876
|
-
return
|
|
1292
|
+
return Pn(e) ? { status: "permission-denied" } : {
|
|
877
1293
|
status: "error",
|
|
878
1294
|
message: e instanceof Error ? e.message : String(e)
|
|
879
1295
|
};
|
|
880
1296
|
}
|
|
881
1297
|
} catch (e) {
|
|
882
|
-
return
|
|
1298
|
+
return Pn(e) ? { status: "permission-denied" } : {
|
|
883
1299
|
status: "error",
|
|
884
1300
|
message: e instanceof Error ? e.message : String(e)
|
|
885
1301
|
};
|
|
886
1302
|
}
|
|
887
1303
|
}
|
|
888
|
-
async function
|
|
889
|
-
let t = e.trim(), n =
|
|
1304
|
+
async function ee(e) {
|
|
1305
|
+
let t = e.trim(), n = Nn.get(`${f()}/${t}`);
|
|
890
1306
|
if (n) return {
|
|
891
1307
|
status: "ok",
|
|
892
1308
|
id: n
|
|
893
1309
|
};
|
|
894
1310
|
try {
|
|
895
|
-
let e = await
|
|
1311
|
+
let e = await x(t);
|
|
896
1312
|
return e ? {
|
|
897
1313
|
status: "ok",
|
|
898
1314
|
id: e.id
|
|
899
1315
|
} : { status: r(f()) ? "permission-denied" : "not-found" };
|
|
900
1316
|
} catch (e) {
|
|
901
|
-
return
|
|
1317
|
+
return Pn(e) ? { status: "permission-denied" } : {
|
|
902
1318
|
status: "error",
|
|
903
1319
|
message: e instanceof Error ? e.message : String(e)
|
|
904
1320
|
};
|
|
905
1321
|
}
|
|
906
1322
|
}
|
|
907
1323
|
async function T(e) {
|
|
908
|
-
return `${
|
|
1324
|
+
return `${ne(e.trim()) || "post"}-${b()}`;
|
|
909
1325
|
}
|
|
910
1326
|
async function E(e) {
|
|
911
1327
|
if (!e.createdAt) return {
|
|
@@ -914,11 +1330,11 @@ function $t(e) {
|
|
|
914
1330
|
};
|
|
915
1331
|
try {
|
|
916
1332
|
let t = [
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
...e.listed === !1 ? [] : [
|
|
920
|
-
|
|
921
|
-
], [n, r] = await Promise.all([
|
|
1333
|
+
X("boardId", "==", f()),
|
|
1334
|
+
X("isPublished", "==", !0),
|
|
1335
|
+
...e.listed === !1 ? [] : [X("listed", "==", !0)],
|
|
1336
|
+
W("createdAt", "desc")
|
|
1337
|
+
], [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
1338
|
let t = e.docs[0];
|
|
923
1339
|
return t ? {
|
|
924
1340
|
id: t.id,
|
|
@@ -936,11 +1352,11 @@ function $t(e) {
|
|
|
936
1352
|
};
|
|
937
1353
|
}
|
|
938
1354
|
}
|
|
939
|
-
async function
|
|
940
|
-
|
|
941
|
-
let n = e.postId ||
|
|
942
|
-
r ? (i = n, a =
|
|
943
|
-
let o =
|
|
1355
|
+
async function O(e) {
|
|
1356
|
+
y(e);
|
|
1357
|
+
let n = e.postId || b(), r = v(), i, a;
|
|
1358
|
+
r ? (i = n, a = qt(e.content)) : (a = e.title.trim(), i = e.slug?.trim() || await T(a));
|
|
1359
|
+
let o = en(e.content, e.attachments), s = _(), c = ye(t);
|
|
944
1360
|
return c.set(m(n), {
|
|
945
1361
|
boardId: f(),
|
|
946
1362
|
slug: i,
|
|
@@ -958,56 +1374,93 @@ function $t(e) {
|
|
|
958
1374
|
authorPhoto: e.authorPhoto,
|
|
959
1375
|
moderationStatus: "approved",
|
|
960
1376
|
moderationModel: e.moderationModel ?? null,
|
|
961
|
-
createdAt:
|
|
962
|
-
updatedAt:
|
|
963
|
-
|
|
1377
|
+
createdAt: K(),
|
|
1378
|
+
updatedAt: K(),
|
|
1379
|
+
...e.translations ? {
|
|
1380
|
+
i18n: e.translations.i18n,
|
|
1381
|
+
i18nSource: e.translations.i18nSource
|
|
1382
|
+
} : {}
|
|
1383
|
+
}), c.set(h(n), {
|
|
1384
|
+
content: e.content,
|
|
1385
|
+
...e.translations?.contentI18n ? {
|
|
1386
|
+
i18n: e.translations.contentI18n,
|
|
1387
|
+
i18nSource: e.translations.contentI18nSource
|
|
1388
|
+
} : {}
|
|
1389
|
+
}), await c.commit(), Nn.set(`${f()}/${i}`, n), i;
|
|
964
1390
|
}
|
|
965
|
-
async function
|
|
966
|
-
|
|
967
|
-
let n =
|
|
968
|
-
await Promise.all([
|
|
1391
|
+
async function te(e, t) {
|
|
1392
|
+
y(t);
|
|
1393
|
+
let n = v() ? qt(t.content) : t.title.trim(), r = en(t.content, t.attachments), i = _();
|
|
1394
|
+
await Promise.all([Y(m(e), {
|
|
969
1395
|
title: n,
|
|
970
1396
|
...r,
|
|
971
|
-
previewImage: r.previewImage ? r.previewImage :
|
|
972
|
-
videoUrl: r.videoUrl ? r.videoUrl :
|
|
1397
|
+
previewImage: r.previewImage ? r.previewImage : pe(),
|
|
1398
|
+
videoUrl: r.videoUrl ? r.videoUrl : pe(),
|
|
973
1399
|
tags: t.tags ?? [],
|
|
974
1400
|
listed: i,
|
|
975
1401
|
attachments: t.attachments ?? [],
|
|
976
|
-
updatedAt:
|
|
977
|
-
|
|
1402
|
+
updatedAt: K(),
|
|
1403
|
+
...t.translations ? {
|
|
1404
|
+
i18n: t.translations.i18n,
|
|
1405
|
+
i18nSource: t.translations.i18nSource
|
|
1406
|
+
} : {}
|
|
1407
|
+
}), q(h(e), {
|
|
1408
|
+
content: t.content,
|
|
1409
|
+
...t.translations ? t.translations.contentI18n ? {
|
|
1410
|
+
i18n: t.translations.contentI18n,
|
|
1411
|
+
i18nSource: t.translations.contentI18nSource
|
|
1412
|
+
} : {
|
|
1413
|
+
i18n: pe(),
|
|
1414
|
+
i18nSource: pe()
|
|
1415
|
+
} : {}
|
|
1416
|
+
}, { merge: !0 })]);
|
|
978
1417
|
}
|
|
979
|
-
async function
|
|
980
|
-
await
|
|
1418
|
+
async function k(e, t) {
|
|
1419
|
+
await Promise.all([Y(m(e), {
|
|
1420
|
+
i18n: t.i18n,
|
|
1421
|
+
i18nSource: t.i18nSource,
|
|
1422
|
+
updatedAt: K()
|
|
1423
|
+
}), q(h(e), t.contentI18n ? {
|
|
1424
|
+
i18n: t.contentI18n,
|
|
1425
|
+
i18nSource: t.contentI18nSource
|
|
1426
|
+
} : {
|
|
1427
|
+
i18n: pe(),
|
|
1428
|
+
i18nSource: pe()
|
|
1429
|
+
}, { merge: !0 })]);
|
|
1430
|
+
}
|
|
1431
|
+
async function A(e) {
|
|
1432
|
+
await Y(m(e), {
|
|
981
1433
|
isPublished: !0,
|
|
982
|
-
publishedAt:
|
|
983
|
-
updatedAt:
|
|
1434
|
+
publishedAt: K(),
|
|
1435
|
+
updatedAt: K()
|
|
984
1436
|
});
|
|
985
1437
|
}
|
|
986
|
-
async function
|
|
987
|
-
await
|
|
988
|
-
for (let [t, n] of
|
|
1438
|
+
async function re(e) {
|
|
1439
|
+
await Gt(t, Ne(n), d(), e);
|
|
1440
|
+
for (let [t, n] of Nn) n === e && Nn.delete(t);
|
|
989
1441
|
}
|
|
990
1442
|
return {
|
|
991
|
-
createPostId:
|
|
992
|
-
getPosts:
|
|
993
|
-
getPost:
|
|
994
|
-
getPostBySlug:
|
|
995
|
-
resolvePostIdBySlug:
|
|
1443
|
+
createPostId: b,
|
|
1444
|
+
getPosts: S,
|
|
1445
|
+
getPost: C,
|
|
1446
|
+
getPostBySlug: w,
|
|
1447
|
+
resolvePostIdBySlug: ee,
|
|
996
1448
|
resolveUniqueSlug: T,
|
|
997
1449
|
getAdjacentPosts: E,
|
|
998
|
-
createPost:
|
|
999
|
-
updatePost:
|
|
1000
|
-
|
|
1001
|
-
|
|
1450
|
+
createPost: O,
|
|
1451
|
+
updatePost: te,
|
|
1452
|
+
savePostTranslations: k,
|
|
1453
|
+
publishPost: A,
|
|
1454
|
+
deletePost: re
|
|
1002
1455
|
};
|
|
1003
1456
|
}
|
|
1004
|
-
var
|
|
1005
|
-
function
|
|
1006
|
-
let n =
|
|
1457
|
+
var Ln = 10;
|
|
1458
|
+
function Rn(e, t = {}) {
|
|
1459
|
+
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
1460
|
function C(e) {
|
|
1008
1461
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
1009
1462
|
}
|
|
1010
|
-
let w =
|
|
1463
|
+
let w = N(() => {
|
|
1011
1464
|
let e = /* @__PURE__ */ new Map();
|
|
1012
1465
|
for (let t of [
|
|
1013
1466
|
...p.value,
|
|
@@ -1016,12 +1469,12 @@ function tn(e, t = {}) {
|
|
|
1016
1469
|
]) t.id && e.set(t.id, t);
|
|
1017
1470
|
return [...e.values()].sort((e, t) => C(t) - C(e) || String(t.id).localeCompare(String(e.id)));
|
|
1018
1471
|
});
|
|
1019
|
-
function
|
|
1020
|
-
let e = [
|
|
1021
|
-
return d.value && e.push(
|
|
1472
|
+
function ee() {
|
|
1473
|
+
let e = [X("boardId", "==", l.value), X("isPublished", "==", !0)];
|
|
1474
|
+
return d.value && e.push(X("listed", "==", !0)), e;
|
|
1022
1475
|
}
|
|
1023
|
-
function
|
|
1024
|
-
x?.(), x =
|
|
1476
|
+
function T() {
|
|
1477
|
+
x?.(), x = U(G(u(), ...ee(), W("createdAt", "desc"), W(R(), "desc"), H(s)), (e) => {
|
|
1025
1478
|
f.value = e.docs.map((e) => ({
|
|
1026
1479
|
id: e.id,
|
|
1027
1480
|
...e.data()
|
|
@@ -1030,14 +1483,14 @@ function tn(e, t = {}) {
|
|
|
1030
1483
|
console.error("[memi-board:postList] onSnapshot failed", e), v.value = e.message || String(e), h.value = !1;
|
|
1031
1484
|
});
|
|
1032
1485
|
}
|
|
1033
|
-
function
|
|
1486
|
+
function E() {
|
|
1034
1487
|
S?.();
|
|
1035
1488
|
let e = c.value;
|
|
1036
1489
|
if (!e) {
|
|
1037
1490
|
m.value = [];
|
|
1038
1491
|
return;
|
|
1039
1492
|
}
|
|
1040
|
-
S =
|
|
1493
|
+
S = U(G(u(), X("boardId", "==", l.value), X("authorUid", "==", e), X("isPublished", "==", !1), W("createdAt", "desc"), H(20)), (e) => {
|
|
1041
1494
|
m.value = e.docs.map((e) => ({
|
|
1042
1495
|
id: e.id,
|
|
1043
1496
|
...e.data()
|
|
@@ -1046,17 +1499,17 @@ function tn(e, t = {}) {
|
|
|
1046
1499
|
console.error("[memi-board:postList] own draft onSnapshot failed", e);
|
|
1047
1500
|
});
|
|
1048
1501
|
}
|
|
1049
|
-
async function
|
|
1502
|
+
async function D() {
|
|
1050
1503
|
if (!(_.value || !g.value || h.value)) {
|
|
1051
1504
|
_.value = !0, v.value = "";
|
|
1052
1505
|
try {
|
|
1053
1506
|
let e = b ?? y, t = [
|
|
1054
|
-
...
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
...e ? [
|
|
1058
|
-
|
|
1059
|
-
], n = await
|
|
1507
|
+
...ee(),
|
|
1508
|
+
W("createdAt", "desc"),
|
|
1509
|
+
W(R(), "desc"),
|
|
1510
|
+
...e ? [J(e)] : [],
|
|
1511
|
+
H(s + 1)
|
|
1512
|
+
], n = await B(G(u(), ...t)), r = n.docs.slice(0, s), i = r.map((e) => ({
|
|
1060
1513
|
id: e.id,
|
|
1061
1514
|
...e.data()
|
|
1062
1515
|
})), a = new Set([...p.value, ...f.value].map((e) => e.id));
|
|
@@ -1068,14 +1521,14 @@ function tn(e, t = {}) {
|
|
|
1068
1521
|
}
|
|
1069
1522
|
}
|
|
1070
1523
|
}
|
|
1071
|
-
function
|
|
1072
|
-
x?.(), f.value = [], p.value = [], y = null, b = null, g.value = !0, v.value = "", h.value = !0,
|
|
1524
|
+
function O() {
|
|
1525
|
+
x?.(), f.value = [], p.value = [], y = null, b = null, g.value = !0, v.value = "", h.value = !0, T(), E();
|
|
1073
1526
|
}
|
|
1074
|
-
return
|
|
1527
|
+
return L([
|
|
1075
1528
|
l,
|
|
1076
1529
|
d,
|
|
1077
1530
|
c
|
|
1078
|
-
],
|
|
1531
|
+
], O, { immediate: !0 }), P(() => {
|
|
1079
1532
|
x?.(), S?.();
|
|
1080
1533
|
}), {
|
|
1081
1534
|
posts: w,
|
|
@@ -1083,25 +1536,33 @@ function tn(e, t = {}) {
|
|
|
1083
1536
|
hasMore: g,
|
|
1084
1537
|
loadingMore: _,
|
|
1085
1538
|
loadError: v,
|
|
1086
|
-
loadMore:
|
|
1539
|
+
loadMore: D
|
|
1087
1540
|
};
|
|
1088
1541
|
}
|
|
1089
|
-
function
|
|
1090
|
-
let n =
|
|
1542
|
+
function zn(e, t) {
|
|
1543
|
+
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
1544
|
return {
|
|
1092
|
-
post:
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1545
|
+
post: N(() => {
|
|
1546
|
+
if (!u.value || u.value.boardId !== i.value) return null;
|
|
1547
|
+
let e = f.value;
|
|
1548
|
+
return {
|
|
1549
|
+
...u.value,
|
|
1550
|
+
id: a.value,
|
|
1551
|
+
i18n: Q(u.value.i18n),
|
|
1552
|
+
i18nSource: Qe(u.value.i18nSource),
|
|
1553
|
+
content: f.value?.content ?? "",
|
|
1554
|
+
contentI18n: Q(e?.i18n),
|
|
1555
|
+
contentI18nSource: $e(e?.i18nSource)
|
|
1556
|
+
};
|
|
1096
1557
|
}),
|
|
1097
|
-
pending:
|
|
1558
|
+
pending: N(() => d.value || p.value)
|
|
1098
1559
|
};
|
|
1099
1560
|
}
|
|
1100
1561
|
//#endregion
|
|
1101
1562
|
//#region src/composables/useMemiBoardUserPosts.ts
|
|
1102
|
-
var
|
|
1103
|
-
function
|
|
1104
|
-
let n =
|
|
1563
|
+
var Bn = 10;
|
|
1564
|
+
function Vn(e, t = {}) {
|
|
1565
|
+
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
1566
|
function g(e) {
|
|
1106
1567
|
return {
|
|
1107
1568
|
id: e.id,
|
|
@@ -1111,16 +1572,16 @@ function an(e, t = {}) {
|
|
|
1111
1572
|
function _(e) {
|
|
1112
1573
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
1113
1574
|
}
|
|
1114
|
-
let v =
|
|
1575
|
+
let v = N(() => {
|
|
1115
1576
|
let e = /* @__PURE__ */ new Map();
|
|
1116
1577
|
for (let t of [...c.value, ...s.value]) t.id && e.set(t.id, t);
|
|
1117
1578
|
return [...e.values()].sort((e, t) => _(t) - _(e) || String(t.id).localeCompare(String(e.id)));
|
|
1118
1579
|
});
|
|
1119
1580
|
function y() {
|
|
1120
1581
|
return [
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1582
|
+
X("authorUid", "==", o.value),
|
|
1583
|
+
X("isPublished", "==", !0),
|
|
1584
|
+
X("listed", "==", !0)
|
|
1124
1585
|
];
|
|
1125
1586
|
}
|
|
1126
1587
|
function b() {
|
|
@@ -1128,7 +1589,7 @@ function an(e, t = {}) {
|
|
|
1128
1589
|
l.value = !1;
|
|
1129
1590
|
return;
|
|
1130
1591
|
}
|
|
1131
|
-
h =
|
|
1592
|
+
h = U(G(i(), ...y(), W("createdAt", "desc"), W(R(), "desc"), H(a)), (e) => {
|
|
1132
1593
|
s.value = e.docs.map(g), p = e.docs.at(-1) ?? null, m || (u.value = e.docs.length >= a), l.value = !1;
|
|
1133
1594
|
}, (e) => {
|
|
1134
1595
|
console.error("[memi-board:userPosts] onSnapshot failed", e), f.value = e.message || String(e), l.value = !1;
|
|
@@ -1140,11 +1601,11 @@ function an(e, t = {}) {
|
|
|
1140
1601
|
try {
|
|
1141
1602
|
let e = m ?? p, t = [
|
|
1142
1603
|
...y(),
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
...e ? [
|
|
1146
|
-
|
|
1147
|
-
], n = await
|
|
1604
|
+
W("createdAt", "desc"),
|
|
1605
|
+
W(R(), "desc"),
|
|
1606
|
+
...e ? [J(e)] : [],
|
|
1607
|
+
H(a + 1)
|
|
1608
|
+
], 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
1609
|
c.value.push(...o.filter((e) => !l.has(e.id))), m = r.at(-1) ?? m, u.value = n.docs.length > a;
|
|
1149
1610
|
} catch (e) {
|
|
1150
1611
|
f.value = e.message || String(e), console.error("[memi-board:userPosts] loadMore failed", e);
|
|
@@ -1156,7 +1617,7 @@ function an(e, t = {}) {
|
|
|
1156
1617
|
function S() {
|
|
1157
1618
|
h?.(), s.value = [], c.value = [], p = null, m = null, u.value = !0, f.value = "", l.value = !0, b();
|
|
1158
1619
|
}
|
|
1159
|
-
return
|
|
1620
|
+
return L(o, S, { immediate: !0 }), P(() => h?.()), {
|
|
1160
1621
|
posts: v,
|
|
1161
1622
|
postsPending: l,
|
|
1162
1623
|
hasMore: u,
|
|
@@ -1167,9 +1628,9 @@ function an(e, t = {}) {
|
|
|
1167
1628
|
}
|
|
1168
1629
|
//#endregion
|
|
1169
1630
|
//#region src/composables/useMemiBoardUserComments.ts
|
|
1170
|
-
var
|
|
1171
|
-
function
|
|
1172
|
-
let n =
|
|
1631
|
+
var Hn = 10;
|
|
1632
|
+
function Un(e, t = {}) {
|
|
1633
|
+
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
1634
|
function _(e) {
|
|
1174
1635
|
return {
|
|
1175
1636
|
id: e.id,
|
|
@@ -1179,20 +1640,20 @@ function sn(e, t = {}) {
|
|
|
1179
1640
|
function v(e) {
|
|
1180
1641
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
1181
1642
|
}
|
|
1182
|
-
let y =
|
|
1643
|
+
let y = N(() => {
|
|
1183
1644
|
let e = /* @__PURE__ */ new Map();
|
|
1184
1645
|
for (let t of [...c.value, ...s.value]) t.id && e.set(t.id, t);
|
|
1185
1646
|
return [...e.values()].sort((e, t) => v(t) - v(e) || String(t.id).localeCompare(String(e.id)));
|
|
1186
1647
|
});
|
|
1187
1648
|
function b() {
|
|
1188
|
-
return [
|
|
1649
|
+
return [X("authorUid", "==", o.value), X("listed", "==", !0)];
|
|
1189
1650
|
}
|
|
1190
1651
|
function x() {
|
|
1191
1652
|
if (g?.(), !o.value) {
|
|
1192
1653
|
l.value = !1;
|
|
1193
1654
|
return;
|
|
1194
1655
|
}
|
|
1195
|
-
g =
|
|
1656
|
+
g = U(G(i(), ...b(), W("createdAt", "desc"), W(R(), "desc"), H(a)), (e) => {
|
|
1196
1657
|
s.value = e.docs.map(_), m = e.docs.at(-1) ?? null, h || (d.value = e.docs.length >= a), l.value = !1;
|
|
1197
1658
|
}, (e) => {
|
|
1198
1659
|
console.error("[memi-board:userComments] onSnapshot failed", e), p.value = e.message || String(e), l.value = !1;
|
|
@@ -1204,11 +1665,11 @@ function sn(e, t = {}) {
|
|
|
1204
1665
|
try {
|
|
1205
1666
|
let e = h ?? m, t = [
|
|
1206
1667
|
...b(),
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
...e ? [
|
|
1210
|
-
|
|
1211
|
-
], n = await
|
|
1668
|
+
W("createdAt", "desc"),
|
|
1669
|
+
W(R(), "desc"),
|
|
1670
|
+
...e ? [J(e)] : [],
|
|
1671
|
+
H(a + 1)
|
|
1672
|
+
], 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
1673
|
c.value.push(...o.filter((e) => !l.has(e.id))), h = r.at(-1) ?? h, d.value = n.docs.length > a;
|
|
1213
1674
|
} catch (e) {
|
|
1214
1675
|
p.value = e.message || String(e), console.error("[memi-board:userComments] loadMore failed", e);
|
|
@@ -1220,7 +1681,7 @@ function sn(e, t = {}) {
|
|
|
1220
1681
|
function C() {
|
|
1221
1682
|
g?.(), s.value = [], c.value = [], m = null, h = null, d.value = !0, p.value = "", l.value = !0, x();
|
|
1222
1683
|
}
|
|
1223
|
-
return
|
|
1684
|
+
return L(o, C, { immediate: !0 }), P(() => g?.()), {
|
|
1224
1685
|
comments: y,
|
|
1225
1686
|
commentsPending: l,
|
|
1226
1687
|
hasMore: d,
|
|
@@ -1231,12 +1692,12 @@ function sn(e, t = {}) {
|
|
|
1231
1692
|
}
|
|
1232
1693
|
//#endregion
|
|
1233
1694
|
//#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 =
|
|
1695
|
+
var Wn = 10;
|
|
1696
|
+
function Gn(e = "latest", t = {}) {
|
|
1697
|
+
let n = Z(), r = () => j(), i = () => u(n, r()), a = N(() => {
|
|
1698
|
+
let e = Math.floor(Number(I(t.pageSize)));
|
|
1699
|
+
return Number.isFinite(e) && e > 0 ? e : Wn;
|
|
1700
|
+
}), 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
1701
|
function _(e) {
|
|
1241
1702
|
return {
|
|
1242
1703
|
id: e.id,
|
|
@@ -1253,25 +1714,25 @@ function ln(e = "latest", t = {}) {
|
|
|
1253
1714
|
}
|
|
1254
1715
|
return v(t) - v(e) || String(t.id).localeCompare(String(e.id));
|
|
1255
1716
|
}
|
|
1256
|
-
let b =
|
|
1717
|
+
let b = N(() => {
|
|
1257
1718
|
let e = /* @__PURE__ */ new Map();
|
|
1258
1719
|
for (let t of [...c.value, ...s.value]) t.id && e.set(t.id, t);
|
|
1259
1720
|
return [...e.values()].sort(y);
|
|
1260
1721
|
});
|
|
1261
1722
|
function x() {
|
|
1262
1723
|
return o.value === "likes" ? [
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1724
|
+
X("listed", "==", !0),
|
|
1725
|
+
W("likeCount", "desc"),
|
|
1726
|
+
W("createdAt", "desc"),
|
|
1727
|
+
W(R(), "desc")
|
|
1267
1728
|
] : [
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1729
|
+
X("listed", "==", !0),
|
|
1730
|
+
W("createdAt", "desc"),
|
|
1731
|
+
W(R(), "desc")
|
|
1271
1732
|
];
|
|
1272
1733
|
}
|
|
1273
1734
|
function S() {
|
|
1274
|
-
g?.(), g =
|
|
1735
|
+
g?.(), g = U(G(i(), ...x(), H(a.value)), (e) => {
|
|
1275
1736
|
s.value = e.docs.map(_), m = e.docs.at(-1) ?? null, h || (d.value = e.docs.length >= a.value), l.value = !1;
|
|
1276
1737
|
}, (e) => {
|
|
1277
1738
|
console.error("[memi-board:commentFeed] onSnapshot failed", e), p.value = e.message || String(e), l.value = !1;
|
|
@@ -1281,7 +1742,7 @@ function ln(e = "latest", t = {}) {
|
|
|
1281
1742
|
if (!(f.value || !d.value || l.value)) {
|
|
1282
1743
|
f.value = !0, p.value = "";
|
|
1283
1744
|
try {
|
|
1284
|
-
let e = h ?? m, t = await
|
|
1745
|
+
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
1746
|
c.value.push(...r.filter((e) => !o.has(e.id))), h = n.at(-1) ?? h, d.value = t.docs.length > a.value;
|
|
1286
1747
|
} catch (e) {
|
|
1287
1748
|
p.value = e.message || String(e), console.error("[memi-board:commentFeed] loadMore failed", e);
|
|
@@ -1293,7 +1754,7 @@ function ln(e = "latest", t = {}) {
|
|
|
1293
1754
|
function w() {
|
|
1294
1755
|
g?.(), s.value = [], c.value = [], m = null, h = null, d.value = !0, p.value = "", l.value = !0, S();
|
|
1295
1756
|
}
|
|
1296
|
-
return
|
|
1757
|
+
return L([o, a], w, { immediate: !0 }), P(() => g?.()), {
|
|
1297
1758
|
comments: b,
|
|
1298
1759
|
commentsPending: l,
|
|
1299
1760
|
hasMore: d,
|
|
@@ -1305,35 +1766,35 @@ function ln(e = "latest", t = {}) {
|
|
|
1305
1766
|
}
|
|
1306
1767
|
//#endregion
|
|
1307
1768
|
//#region src/composables/useMemiBoardAuthorMemo.ts
|
|
1308
|
-
var
|
|
1769
|
+
var Kn = 100, qn = /* @__PURE__ */ new Map();
|
|
1309
1770
|
function $(e) {
|
|
1310
|
-
let t =
|
|
1311
|
-
return t || (t =
|
|
1771
|
+
let t = qn.get(e);
|
|
1772
|
+
return t || (t = de({
|
|
1312
1773
|
text: "",
|
|
1313
1774
|
sentiment: null,
|
|
1314
1775
|
loaded: !1,
|
|
1315
1776
|
pending: !1,
|
|
1316
1777
|
saving: !1,
|
|
1317
1778
|
error: ""
|
|
1318
|
-
}),
|
|
1779
|
+
}), qn.set(e, t)), t;
|
|
1319
1780
|
}
|
|
1320
|
-
function
|
|
1321
|
-
let t =
|
|
1781
|
+
function Jn(e) {
|
|
1782
|
+
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
1783
|
let e = r.value?.uid, a = s.value;
|
|
1323
1784
|
return !e || !a ? null : i(t, n(), e, a);
|
|
1324
|
-
}), u =
|
|
1785
|
+
}), u = N({
|
|
1325
1786
|
get: () => $(c.value).text,
|
|
1326
1787
|
set: (e) => {
|
|
1327
1788
|
$(c.value).text = e;
|
|
1328
1789
|
}
|
|
1329
|
-
}), d =
|
|
1790
|
+
}), 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
1791
|
async function g() {
|
|
1331
1792
|
let e = l.value;
|
|
1332
1793
|
if (!e) return;
|
|
1333
1794
|
let t = $(c.value);
|
|
1334
1795
|
t.pending = !0, t.error = "";
|
|
1335
1796
|
try {
|
|
1336
|
-
let n = (await
|
|
1797
|
+
let n = (await z(e)).data();
|
|
1337
1798
|
t.text = n ? n.text ?? "" : "", t.sentiment = n ? n.sentiment ?? null : null, t.loaded = !0;
|
|
1338
1799
|
} catch (e) {
|
|
1339
1800
|
t.error = e.message || String(e), console.error("[memi-board:authorMemo] load failed", e);
|
|
@@ -1349,10 +1810,10 @@ function fn(e) {
|
|
|
1349
1810
|
let i = $(c.value);
|
|
1350
1811
|
i.saving = !0, i.error = "";
|
|
1351
1812
|
try {
|
|
1352
|
-
await
|
|
1813
|
+
await q(n, {
|
|
1353
1814
|
text: r,
|
|
1354
1815
|
sentiment: t,
|
|
1355
|
-
updatedAt:
|
|
1816
|
+
updatedAt: K()
|
|
1356
1817
|
}), i.text = r, i.sentiment = t, i.loaded = !0;
|
|
1357
1818
|
} catch (e) {
|
|
1358
1819
|
throw i.error = e.message || String(e), console.error("[memi-board:authorMemo] save failed", e), e;
|
|
@@ -1366,7 +1827,7 @@ function fn(e) {
|
|
|
1366
1827
|
let t = $(c.value);
|
|
1367
1828
|
t.saving = !0, t.error = "";
|
|
1368
1829
|
try {
|
|
1369
|
-
await
|
|
1830
|
+
await fe(e), t.text = "", t.sentiment = null, t.loaded = !0;
|
|
1370
1831
|
} catch (e) {
|
|
1371
1832
|
throw t.error = e.message || String(e), console.error("[memi-board:authorMemo] remove failed", e), e;
|
|
1372
1833
|
} finally {
|
|
@@ -1387,37 +1848,37 @@ function fn(e) {
|
|
|
1387
1848
|
}
|
|
1388
1849
|
//#endregion
|
|
1389
1850
|
//#region src/utils/subscribeCommentRange.ts
|
|
1390
|
-
function
|
|
1391
|
-
return
|
|
1851
|
+
function Yn(e, t, n, r, i, a) {
|
|
1852
|
+
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
1853
|
...e.data(),
|
|
1393
1854
|
id: e.id
|
|
1394
1855
|
}))), a);
|
|
1395
1856
|
}
|
|
1396
1857
|
//#endregion
|
|
1397
1858
|
//#region src/composables/useMemiBoardComments.ts
|
|
1398
|
-
var
|
|
1399
|
-
function
|
|
1859
|
+
var Xn = 10, Zn = 5, Qn = 1e3;
|
|
1860
|
+
function $n(e, { t, formatNumber: n }) {
|
|
1400
1861
|
let r = e.trim();
|
|
1401
1862
|
if (!r) throw Error(t("comments.bodyRequired"));
|
|
1402
|
-
if (r.length > 1e3) throw Error(t("comments.bodyTooLong", { max: n(
|
|
1863
|
+
if (r.length > 1e3) throw Error(t("comments.bodyTooLong", { max: n(Qn) }));
|
|
1403
1864
|
return r;
|
|
1404
1865
|
}
|
|
1405
|
-
function
|
|
1406
|
-
let r =
|
|
1866
|
+
function er(e, t, n = {}) {
|
|
1867
|
+
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
1868
|
function w(e) {
|
|
1408
1869
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
1409
1870
|
}
|
|
1410
|
-
let
|
|
1871
|
+
let ee = N(() => {
|
|
1411
1872
|
let e = /* @__PURE__ */ new Map();
|
|
1412
1873
|
for (let t of [...m.value, ...g.value]) t.id && e.set(t.id, t);
|
|
1413
1874
|
return [...e.values()].sort((e, t) => w(e) - w(t) || String(e.id).localeCompare(String(t.id)));
|
|
1414
1875
|
});
|
|
1415
|
-
|
|
1876
|
+
L([f, d], async ([e]) => {
|
|
1416
1877
|
let t = ++x;
|
|
1417
1878
|
if (S++, _.value = !1, g.value = [], m.value = [], b = null, C?.(), C = void 0, v.value = n.subscribe !== !1, n.subscribe !== !1) {
|
|
1418
1879
|
h.value = !0, y.value = !0;
|
|
1419
1880
|
try {
|
|
1420
|
-
let n =
|
|
1881
|
+
let n = B(G(c(), X("postId", "==", e), W("createdAt", "desc"), W(R(), "desc")));
|
|
1421
1882
|
if (await Promise.all([n.then((e) => {
|
|
1422
1883
|
if (t !== x) return;
|
|
1423
1884
|
let n = e.docs.map((e) => ({
|
|
@@ -1425,16 +1886,16 @@ function vn(e, t, n = {}) {
|
|
|
1425
1886
|
...e.data()
|
|
1426
1887
|
}));
|
|
1427
1888
|
m.value = n.filter((e) => e.parentId === void 0);
|
|
1428
|
-
}),
|
|
1889
|
+
}), E()]), t !== x) return;
|
|
1429
1890
|
} finally {
|
|
1430
1891
|
t === x && (h.value = !1, y.value = !1);
|
|
1431
1892
|
}
|
|
1432
1893
|
}
|
|
1433
1894
|
}, { immediate: !0 });
|
|
1434
|
-
function
|
|
1895
|
+
function T() {
|
|
1435
1896
|
C?.();
|
|
1436
1897
|
let e = ++S;
|
|
1437
|
-
C =
|
|
1898
|
+
C = Yn(r, i(), f.value, {
|
|
1438
1899
|
cursor: b,
|
|
1439
1900
|
hasMore: v.value
|
|
1440
1901
|
}, (t) => {
|
|
@@ -1443,34 +1904,34 @@ function vn(e, t, n = {}) {
|
|
|
1443
1904
|
console.error("[memi-board:comments] onSnapshot failed", e);
|
|
1444
1905
|
});
|
|
1445
1906
|
}
|
|
1446
|
-
async function
|
|
1907
|
+
async function E() {
|
|
1447
1908
|
if (_.value || !v.value) return;
|
|
1448
1909
|
let e = x;
|
|
1449
1910
|
_.value = !0;
|
|
1450
1911
|
try {
|
|
1451
1912
|
let t = [
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
...b ? [
|
|
1457
|
-
|
|
1458
|
-
], n = await
|
|
1913
|
+
X("postId", "==", f.value),
|
|
1914
|
+
X("parentId", "==", null),
|
|
1915
|
+
W("createdAt", "asc"),
|
|
1916
|
+
W(R(), "asc"),
|
|
1917
|
+
...b ? [J(b)] : [],
|
|
1918
|
+
H(11)
|
|
1919
|
+
], n = await B(G(c(), ...t));
|
|
1459
1920
|
if (e !== x) return;
|
|
1460
|
-
let r = n.docs.slice(0,
|
|
1921
|
+
let r = n.docs.slice(0, Xn), i = r.map((e) => ({
|
|
1461
1922
|
id: e.id,
|
|
1462
1923
|
...e.data()
|
|
1463
1924
|
})), 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 >
|
|
1925
|
+
g.value.push(...i.filter((e) => !a.has(e.id))), b = r.at(-1) ?? b, v.value = n.size > Xn, T();
|
|
1465
1926
|
} finally {
|
|
1466
1927
|
e === x && (_.value = !1);
|
|
1467
1928
|
}
|
|
1468
1929
|
}
|
|
1469
|
-
|
|
1930
|
+
P(() => {
|
|
1470
1931
|
x++, S++, C?.();
|
|
1471
1932
|
});
|
|
1472
|
-
async function
|
|
1473
|
-
let t =
|
|
1933
|
+
async function te(e) {
|
|
1934
|
+
let t = $n(e.body, o), n = ye(r), a = me(c());
|
|
1474
1935
|
n.set(a, {
|
|
1475
1936
|
postId: f.value,
|
|
1476
1937
|
boardId: d.value,
|
|
@@ -1488,14 +1949,14 @@ function vn(e, t, n = {}) {
|
|
|
1488
1949
|
replyCount: 0,
|
|
1489
1950
|
likeCount: 0,
|
|
1490
1951
|
isReply: !1,
|
|
1491
|
-
createdAt:
|
|
1492
|
-
updatedAt:
|
|
1493
|
-
}), n.update(l(r, i(), f.value), { commentCount:
|
|
1952
|
+
createdAt: K(),
|
|
1953
|
+
updatedAt: K()
|
|
1954
|
+
}), n.update(l(r, i(), f.value), { commentCount: V(1) }), await n.commit();
|
|
1494
1955
|
}
|
|
1495
|
-
async function
|
|
1496
|
-
let t =
|
|
1956
|
+
async function k(e) {
|
|
1957
|
+
let t = $n(e.body, o);
|
|
1497
1958
|
if (!e.parent.id) throw Error(s("comments.replyTargetNotFound"));
|
|
1498
|
-
let n =
|
|
1959
|
+
let n = ye(r), a = me(c()), u = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
|
|
1499
1960
|
if (!u) throw Error(s("comments.threadNotFound"));
|
|
1500
1961
|
let m = Math.min((e.parent.depth ?? 0) + 1, 2);
|
|
1501
1962
|
n.set(a, {
|
|
@@ -1515,53 +1976,53 @@ function vn(e, t, n = {}) {
|
|
|
1515
1976
|
replyCount: 0,
|
|
1516
1977
|
likeCount: 0,
|
|
1517
1978
|
isReply: !0,
|
|
1518
|
-
createdAt:
|
|
1519
|
-
updatedAt:
|
|
1520
|
-
}), n.update(
|
|
1979
|
+
createdAt: K(),
|
|
1980
|
+
updatedAt: K()
|
|
1981
|
+
}), n.update(O(r, i(), u), { replyCount: V(1) }), n.update(l(r, i(), f.value), { commentCount: V(1) }), await n.commit();
|
|
1521
1982
|
}
|
|
1522
|
-
async function
|
|
1983
|
+
async function A(e) {
|
|
1523
1984
|
if (!e.id) return;
|
|
1524
1985
|
if (e.parentId == null && (e.replyCount ?? 0) > 0) throw Error(s("comments.hasRepliesCannotDelete"));
|
|
1525
|
-
await
|
|
1526
|
-
let t =
|
|
1527
|
-
t.delete(
|
|
1986
|
+
await Wt(r, i(), e.id);
|
|
1987
|
+
let t = ye(r);
|
|
1988
|
+
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
1989
|
}
|
|
1529
|
-
async function
|
|
1530
|
-
let n =
|
|
1531
|
-
await
|
|
1990
|
+
async function ne(e, t) {
|
|
1991
|
+
let n = $n(t, o);
|
|
1992
|
+
await Y(O(r, i(), e), {
|
|
1532
1993
|
body: n,
|
|
1533
|
-
updatedAt:
|
|
1994
|
+
updatedAt: K()
|
|
1534
1995
|
});
|
|
1535
1996
|
}
|
|
1536
|
-
async function
|
|
1537
|
-
await
|
|
1997
|
+
async function re(e, t, n) {
|
|
1998
|
+
await Y(O(r, i(), e), {
|
|
1538
1999
|
isBlinded: t,
|
|
1539
|
-
moderatedAt:
|
|
2000
|
+
moderatedAt: K(),
|
|
1540
2001
|
moderatedBy: n
|
|
1541
2002
|
});
|
|
1542
2003
|
}
|
|
1543
2004
|
return {
|
|
1544
|
-
comments:
|
|
1545
|
-
commentsPending:
|
|
2005
|
+
comments: ee,
|
|
2006
|
+
commentsPending: N(() => y.value || h.value),
|
|
1546
2007
|
hasMore: v,
|
|
1547
2008
|
loadingMore: _,
|
|
1548
|
-
loadMore:
|
|
1549
|
-
addComment:
|
|
1550
|
-
addReply:
|
|
1551
|
-
updateComment:
|
|
1552
|
-
setCommentBlinded:
|
|
1553
|
-
deleteComment:
|
|
2009
|
+
loadMore: E,
|
|
2010
|
+
addComment: te,
|
|
2011
|
+
addReply: k,
|
|
2012
|
+
updateComment: ne,
|
|
2013
|
+
setCommentBlinded: re,
|
|
2014
|
+
deleteComment: A
|
|
1554
2015
|
};
|
|
1555
2016
|
}
|
|
1556
|
-
function
|
|
1557
|
-
let r =
|
|
2017
|
+
function tr(e, t, n) {
|
|
2018
|
+
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
2019
|
function g(e) {
|
|
1559
2020
|
return e.sort((e, t) => (e.createdAt?.toMillis?.() ?? 0) - (t.createdAt?.toMillis?.() ?? 0) || String(e.id).localeCompare(String(t.id)));
|
|
1560
2021
|
}
|
|
1561
2022
|
function _() {
|
|
1562
2023
|
p?.();
|
|
1563
2024
|
let e = ++h;
|
|
1564
|
-
p =
|
|
2025
|
+
p = Yn(r, i(), o, {
|
|
1565
2026
|
rootId: n,
|
|
1566
2027
|
cursor: f,
|
|
1567
2028
|
hasMore: d.value
|
|
@@ -1577,20 +2038,20 @@ function yn(e, t, n) {
|
|
|
1577
2038
|
c.value = !0;
|
|
1578
2039
|
try {
|
|
1579
2040
|
let t = [
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
...f ? [
|
|
1586
|
-
|
|
1587
|
-
], r = await
|
|
2041
|
+
X("postId", "==", o),
|
|
2042
|
+
X("rootId", "==", n),
|
|
2043
|
+
X("isReply", "==", !0),
|
|
2044
|
+
W("createdAt", "asc"),
|
|
2045
|
+
W(R(), "asc"),
|
|
2046
|
+
...f ? [J(f)] : [],
|
|
2047
|
+
H(6)
|
|
2048
|
+
], r = await B(G(a(), ...t));
|
|
1588
2049
|
if (e !== m) return;
|
|
1589
|
-
let i = new Set(s.value.map((e) => e.id)), c = r.docs.slice(0,
|
|
2050
|
+
let i = new Set(s.value.map((e) => e.id)), c = r.docs.slice(0, Zn), u = c.map((e) => ({
|
|
1590
2051
|
id: e.id,
|
|
1591
2052
|
...e.data()
|
|
1592
2053
|
})).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 >
|
|
2054
|
+
s.value = g([...s.value, ...u]), f = c.at(-1) ?? f, l.value = !0, d.value = r.size > Zn, _();
|
|
1594
2055
|
} finally {
|
|
1595
2056
|
e === m && (c.value = !1);
|
|
1596
2057
|
}
|
|
@@ -1601,7 +2062,7 @@ function yn(e, t, n) {
|
|
|
1601
2062
|
async function b() {
|
|
1602
2063
|
y(), await v();
|
|
1603
2064
|
}
|
|
1604
|
-
return
|
|
2065
|
+
return P(() => {
|
|
1605
2066
|
m++, h++, p?.();
|
|
1606
2067
|
}), {
|
|
1607
2068
|
replies: s,
|
|
@@ -1614,22 +2075,22 @@ function yn(e, t, n) {
|
|
|
1614
2075
|
}
|
|
1615
2076
|
//#endregion
|
|
1616
2077
|
//#region src/composables/useMemiBoardLikes.ts
|
|
1617
|
-
function
|
|
1618
|
-
let n = () =>
|
|
2078
|
+
function nr(e, t) {
|
|
2079
|
+
let n = () => j(), r = Z(), i = xe(), { t: a } = D(), o = F(!1), s = F(!1), c;
|
|
1619
2080
|
function u(e) {
|
|
1620
2081
|
return d(r, n(), t, e);
|
|
1621
2082
|
}
|
|
1622
|
-
|
|
2083
|
+
L(i, (e) => {
|
|
1623
2084
|
if (c?.(), c = void 0, !e) {
|
|
1624
2085
|
o.value = !1;
|
|
1625
2086
|
return;
|
|
1626
2087
|
}
|
|
1627
|
-
c =
|
|
2088
|
+
c = U(u(e.uid), (e) => {
|
|
1628
2089
|
o.value = e.exists();
|
|
1629
2090
|
}, (e) => {
|
|
1630
2091
|
console.error("[memi-board:likes] onSnapshot failed", e);
|
|
1631
2092
|
});
|
|
1632
|
-
}, { immediate: !0 }),
|
|
2093
|
+
}, { immediate: !0 }), P(() => c?.());
|
|
1633
2094
|
async function f() {
|
|
1634
2095
|
let c = i.value?.uid;
|
|
1635
2096
|
if (!c) throw Error(a("likes.signInRequired"));
|
|
@@ -1637,12 +2098,12 @@ function bn(e, t) {
|
|
|
1637
2098
|
s.value = !0;
|
|
1638
2099
|
try {
|
|
1639
2100
|
let i = u(c), a = l(r, n(), t);
|
|
1640
|
-
return await
|
|
2101
|
+
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
2102
|
uid: c,
|
|
1642
2103
|
postId: t,
|
|
1643
2104
|
boardId: e,
|
|
1644
|
-
createdAt:
|
|
1645
|
-
}), n.update(a, { likeCount:
|
|
2105
|
+
createdAt: K()
|
|
2106
|
+
}), n.update(a, { likeCount: V(1) }), !0));
|
|
1646
2107
|
} finally {
|
|
1647
2108
|
s.value = !1;
|
|
1648
2109
|
}
|
|
@@ -1653,11 +2114,11 @@ function bn(e, t) {
|
|
|
1653
2114
|
toggleLike: f
|
|
1654
2115
|
};
|
|
1655
2116
|
}
|
|
1656
|
-
var
|
|
1657
|
-
function
|
|
1658
|
-
let n = () =>
|
|
1659
|
-
|
|
1660
|
-
f?.(), f = void 0, l.value = /* @__PURE__ */ new Set(), e && t && (f =
|
|
2117
|
+
var rr = Symbol.for("memi-board:comment-likes");
|
|
2118
|
+
function ir(e, t) {
|
|
2119
|
+
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;
|
|
2120
|
+
L([i, c], ([e, t]) => {
|
|
2121
|
+
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
2122
|
let t = /* @__PURE__ */ new Set();
|
|
1662
2123
|
for (let n of e.docs) {
|
|
1663
2124
|
let e = n.data().commentId;
|
|
@@ -1667,7 +2128,7 @@ function Sn(e, t) {
|
|
|
1667
2128
|
}, (e) => {
|
|
1668
2129
|
console.error("[memi-board:comment-likes] onSnapshot failed", e);
|
|
1669
2130
|
}));
|
|
1670
|
-
}, { immediate: !0 }),
|
|
2131
|
+
}, { immediate: !0 }), P(() => f?.());
|
|
1671
2132
|
function p(e) {
|
|
1672
2133
|
return !!e && l.value.has(e);
|
|
1673
2134
|
}
|
|
@@ -1681,7 +2142,7 @@ function Sn(e, t) {
|
|
|
1681
2142
|
if (u.value) return p(e);
|
|
1682
2143
|
u.value = e;
|
|
1683
2144
|
try {
|
|
1684
|
-
let i = d(r, n(), e, t), a =
|
|
2145
|
+
let i = d(r, n(), e, t), a = O(r, n(), e), u = await ve(r, async (n) => {
|
|
1685
2146
|
let r = await n.get(i), l = await n.get(a);
|
|
1686
2147
|
if (!l.exists()) throw Error(o("likes.commentNotFound"));
|
|
1687
2148
|
let u = Number(l.data()?.likeCount ?? 0);
|
|
@@ -1691,7 +2152,7 @@ function Sn(e, t) {
|
|
|
1691
2152
|
boardId: s.value,
|
|
1692
2153
|
commentId: e,
|
|
1693
2154
|
target: "comment",
|
|
1694
|
-
createdAt:
|
|
2155
|
+
createdAt: K()
|
|
1695
2156
|
}), n.update(a, { likeCount: u + 1 }), !0);
|
|
1696
2157
|
}), f = new Set(l.value);
|
|
1697
2158
|
return u ? f.add(e) : f.delete(e), l.value = f, u;
|
|
@@ -1707,7 +2168,7 @@ function Sn(e, t) {
|
|
|
1707
2168
|
}
|
|
1708
2169
|
//#endregion
|
|
1709
2170
|
//#region src/composables/useMemiBoardReports.ts
|
|
1710
|
-
var
|
|
2171
|
+
var ar = 200, or = [
|
|
1711
2172
|
{
|
|
1712
2173
|
value: "spam",
|
|
1713
2174
|
label: "스팸·광고"
|
|
@@ -1729,34 +2190,34 @@ var Cn = 200, wn = [
|
|
|
1729
2190
|
label: "기타"
|
|
1730
2191
|
}
|
|
1731
2192
|
];
|
|
1732
|
-
function
|
|
1733
|
-
return
|
|
2193
|
+
function sr(e, t) {
|
|
2194
|
+
return or.some((t) => t.value === e) ? w(t, `reports.reason.${e}`) : e || w(t, "reports.fallbackLabel");
|
|
1734
2195
|
}
|
|
1735
|
-
function
|
|
1736
|
-
let r = () =>
|
|
1737
|
-
|
|
2196
|
+
function cr(e, n) {
|
|
2197
|
+
let r = () => j(), i = Z(), a = xe(), { t: o } = D(), s = F(!1), c = F(!1), l;
|
|
2198
|
+
L(a, (e) => {
|
|
1738
2199
|
if (l?.(), l = void 0, !e) {
|
|
1739
2200
|
s.value = !1;
|
|
1740
2201
|
return;
|
|
1741
2202
|
}
|
|
1742
|
-
l =
|
|
2203
|
+
l = U(t(i, r(), n, e.uid), (e) => {
|
|
1743
2204
|
s.value = e.exists();
|
|
1744
2205
|
}, (e) => {
|
|
1745
2206
|
console.error("[memi-board:reports] onSnapshot failed", e);
|
|
1746
2207
|
});
|
|
1747
|
-
}, { immediate: !0 }),
|
|
2208
|
+
}, { immediate: !0 }), P(() => l?.());
|
|
1748
2209
|
async function u(l) {
|
|
1749
2210
|
let u = a.value?.uid;
|
|
1750
2211
|
if (!u) throw Error(o("reports.signInRequired"));
|
|
1751
2212
|
if (c.value) return;
|
|
1752
2213
|
if (l.authorUid && l.authorUid === u) throw Error(o("reports.cannotReportOwn"));
|
|
1753
|
-
let d =
|
|
2214
|
+
let d = or.some((e) => e.value === l.reason) ? l.reason : null;
|
|
1754
2215
|
if (!d) throw Error(o("reports.reasonRequired"));
|
|
1755
2216
|
let f = (l.detail || "").trim();
|
|
1756
2217
|
if (f.length > 200) throw Error(o("reports.detailTooLong", { max: 200 }));
|
|
1757
2218
|
c.value = !0;
|
|
1758
2219
|
try {
|
|
1759
|
-
await
|
|
2220
|
+
await q(t(i, r(), n, u), {
|
|
1760
2221
|
uid: u,
|
|
1761
2222
|
target: "post",
|
|
1762
2223
|
postId: n,
|
|
@@ -1767,7 +2228,7 @@ function En(e, n) {
|
|
|
1767
2228
|
postTitle: (l.postTitle || "").trim().slice(0, 200),
|
|
1768
2229
|
authorUid: l.authorUid || "",
|
|
1769
2230
|
authorName: l.authorName ?? null,
|
|
1770
|
-
createdAt:
|
|
2231
|
+
createdAt: K()
|
|
1771
2232
|
}), s.value = !0;
|
|
1772
2233
|
} finally {
|
|
1773
2234
|
c.value = !1;
|
|
@@ -1779,9 +2240,9 @@ function En(e, n) {
|
|
|
1779
2240
|
submitReport: u
|
|
1780
2241
|
};
|
|
1781
2242
|
}
|
|
1782
|
-
var
|
|
1783
|
-
function
|
|
1784
|
-
let n = () =>
|
|
2243
|
+
var lr = 20;
|
|
2244
|
+
function ur(e = {}) {
|
|
2245
|
+
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
2246
|
function _(e) {
|
|
1786
2247
|
return {
|
|
1787
2248
|
id: e.id,
|
|
@@ -1794,19 +2255,19 @@ function On(e = {}) {
|
|
|
1794
2255
|
return;
|
|
1795
2256
|
}
|
|
1796
2257
|
d.value = !0;
|
|
1797
|
-
let e = l.value ? [
|
|
1798
|
-
g =
|
|
2258
|
+
let e = l.value ? [X("status", "==", "open"), X("boardId", "==", l.value)] : [X("status", "==", "open"), X("target", "==", "post")];
|
|
2259
|
+
g = U(G(s(r, n()), ...e, W("createdAt", "desc"), W(R(), "desc"), H(c)), (e) => {
|
|
1799
2260
|
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
2261
|
}, (e) => {
|
|
1801
2262
|
console.error("[memi-board:report-queue] onSnapshot failed", e), m.value = e.message || String(e), d.value = !1;
|
|
1802
2263
|
});
|
|
1803
2264
|
}
|
|
1804
|
-
|
|
2265
|
+
L([a, l], v, { immediate: !0 }), P(() => g?.());
|
|
1805
2266
|
async function y() {
|
|
1806
2267
|
if (!f.value && p.value && h && a.value) {
|
|
1807
2268
|
f.value = !0, m.value = "";
|
|
1808
2269
|
try {
|
|
1809
|
-
let e = l.value ? [
|
|
2270
|
+
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
2271
|
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
2272
|
} catch (e) {
|
|
1812
2273
|
m.value = e.message || String(e);
|
|
@@ -1816,10 +2277,10 @@ function On(e = {}) {
|
|
|
1816
2277
|
}
|
|
1817
2278
|
}
|
|
1818
2279
|
async function b(e, i) {
|
|
1819
|
-
e.id && o(e.boardId) && await
|
|
2280
|
+
e.id && o(e.boardId) && await Y(t(r, n(), e.postId, e.uid), {
|
|
1820
2281
|
status: i,
|
|
1821
|
-
reviewedAt:
|
|
1822
|
-
reviewedBy:
|
|
2282
|
+
reviewedAt: K(),
|
|
2283
|
+
reviewedBy: xe().value?.uid || ""
|
|
1823
2284
|
});
|
|
1824
2285
|
}
|
|
1825
2286
|
return {
|
|
@@ -1835,8 +2296,8 @@ function On(e = {}) {
|
|
|
1835
2296
|
}
|
|
1836
2297
|
//#endregion
|
|
1837
2298
|
//#region src/composables/useMemiBoardViews.ts
|
|
1838
|
-
function
|
|
1839
|
-
let e = () =>
|
|
2299
|
+
function dr() {
|
|
2300
|
+
let e = () => j(), t = Z();
|
|
1840
2301
|
function n(t, n) {
|
|
1841
2302
|
let { postsCollection: r } = e();
|
|
1842
2303
|
return `memi-board:viewed:${r}:${t}:${n}`;
|
|
@@ -1859,7 +2320,7 @@ function kn() {
|
|
|
1859
2320
|
if (s && c && !r(s, c)) {
|
|
1860
2321
|
i(s, c);
|
|
1861
2322
|
try {
|
|
1862
|
-
await
|
|
2323
|
+
await Y(l(t, e(), c), { viewCount: V(1) });
|
|
1863
2324
|
} catch (e) {
|
|
1864
2325
|
try {
|
|
1865
2326
|
sessionStorage.removeItem(n(s, c));
|
|
@@ -1874,161 +2335,21 @@ function kn() {
|
|
|
1874
2335
|
};
|
|
1875
2336
|
}
|
|
1876
2337
|
//#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
2338
|
//#region src/composables/useMemiBoardModeration.ts
|
|
2018
|
-
var
|
|
2339
|
+
var fr = {
|
|
2019
2340
|
flagged: !1,
|
|
2020
2341
|
category: "none",
|
|
2021
2342
|
reason: "",
|
|
2022
2343
|
via: "empty"
|
|
2023
2344
|
};
|
|
2024
|
-
function
|
|
2025
|
-
let t =
|
|
2345
|
+
function pr(e = {}) {
|
|
2346
|
+
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
2347
|
function u(e) {
|
|
2027
2348
|
let t = e.match(l);
|
|
2028
2349
|
return t ? {
|
|
2029
2350
|
flagged: !0,
|
|
2030
2351
|
category: "abuse",
|
|
2031
|
-
reason:
|
|
2352
|
+
reason: hn({
|
|
2032
2353
|
via: "local",
|
|
2033
2354
|
localHit: t[0] || "",
|
|
2034
2355
|
category: "abuse"
|
|
@@ -2037,21 +2358,21 @@ function Gn(e = {}) {
|
|
|
2037
2358
|
} : null;
|
|
2038
2359
|
}
|
|
2039
2360
|
function d() {
|
|
2040
|
-
let e = r.model ?? "gemini-3.5-flash-lite", t = r.useLimitedUseAppCheckTokens !== !1, a =
|
|
2041
|
-
backend: new
|
|
2361
|
+
let e = r.model ?? "gemini-3.5-flash-lite", t = r.useLimitedUseAppCheckTokens !== !1, a = Re(i, {
|
|
2362
|
+
backend: new Ie(),
|
|
2042
2363
|
useLimitedUseAppCheckTokens: t
|
|
2043
2364
|
});
|
|
2044
|
-
return
|
|
2365
|
+
return ze(a, {
|
|
2045
2366
|
model: e,
|
|
2046
|
-
systemInstruction:
|
|
2367
|
+
systemInstruction: un(n.value),
|
|
2047
2368
|
generationConfig: {
|
|
2048
2369
|
temperature: 0,
|
|
2049
2370
|
maxOutputTokens: 256,
|
|
2050
2371
|
responseMimeType: "application/json",
|
|
2051
|
-
responseSchema:
|
|
2052
|
-
flagged:
|
|
2053
|
-
category:
|
|
2054
|
-
reason:
|
|
2372
|
+
responseSchema: Le.object({ properties: {
|
|
2373
|
+
flagged: Le.boolean(),
|
|
2374
|
+
category: Le.string(),
|
|
2375
|
+
reason: Le.string()
|
|
2055
2376
|
} })
|
|
2056
2377
|
}
|
|
2057
2378
|
});
|
|
@@ -2072,7 +2393,7 @@ function Gn(e = {}) {
|
|
|
2072
2393
|
};
|
|
2073
2394
|
}
|
|
2074
2395
|
function p(e, t = "") {
|
|
2075
|
-
let n =
|
|
2396
|
+
let n = hn({
|
|
2076
2397
|
reason: e.reason,
|
|
2077
2398
|
category: e.category,
|
|
2078
2399
|
via: e.via
|
|
@@ -2100,26 +2421,26 @@ function Gn(e = {}) {
|
|
|
2100
2421
|
};
|
|
2101
2422
|
let i = e.trim();
|
|
2102
2423
|
if (!i) return {
|
|
2103
|
-
...
|
|
2424
|
+
...fr,
|
|
2104
2425
|
via: "empty"
|
|
2105
2426
|
};
|
|
2106
2427
|
if (t?.skipFilter) return {
|
|
2107
|
-
...
|
|
2428
|
+
...fr,
|
|
2108
2429
|
via: "admin-override"
|
|
2109
2430
|
};
|
|
2110
2431
|
let c = u(i);
|
|
2111
2432
|
if (c) return m(c);
|
|
2112
2433
|
if (r.enabled === !1) return {
|
|
2113
|
-
...
|
|
2434
|
+
...fr,
|
|
2114
2435
|
via: "disabled"
|
|
2115
2436
|
};
|
|
2116
2437
|
try {
|
|
2117
|
-
let e =
|
|
2438
|
+
let e = yn((await d().generateContent(dn(i, n.value))).response.text());
|
|
2118
2439
|
if (!e) return f();
|
|
2119
2440
|
let t = {
|
|
2120
2441
|
flagged: e.flagged,
|
|
2121
2442
|
category: e.category,
|
|
2122
|
-
reason:
|
|
2443
|
+
reason: hn({
|
|
2123
2444
|
reason: e.reason,
|
|
2124
2445
|
category: e.category,
|
|
2125
2446
|
via: "ai"
|
|
@@ -2139,22 +2460,22 @@ function Gn(e = {}) {
|
|
|
2139
2460
|
via: "restricted"
|
|
2140
2461
|
};
|
|
2141
2462
|
if (r.enabled === !1 || !r.moderateImages) return {
|
|
2142
|
-
...
|
|
2463
|
+
...fr,
|
|
2143
2464
|
via: "disabled"
|
|
2144
2465
|
};
|
|
2145
2466
|
try {
|
|
2146
2467
|
let t = await new Promise((t, n) => {
|
|
2147
2468
|
let r = new FileReader();
|
|
2148
2469
|
r.onload = () => t(r.result.split(",")[1] ?? ""), r.onerror = n, r.readAsDataURL(e);
|
|
2149
|
-
}), r =
|
|
2470
|
+
}), r = yn((await d().generateContent([{ inlineData: {
|
|
2150
2471
|
mimeType: e.type || "image/jpeg",
|
|
2151
2472
|
data: t
|
|
2152
|
-
} },
|
|
2473
|
+
} }, fn(n.value)])).response.text());
|
|
2153
2474
|
if (!r) return f();
|
|
2154
2475
|
let i = {
|
|
2155
2476
|
flagged: r.flagged,
|
|
2156
2477
|
category: r.category,
|
|
2157
|
-
reason:
|
|
2478
|
+
reason: hn({
|
|
2158
2479
|
reason: r.reason,
|
|
2159
2480
|
category: r.category,
|
|
2160
2481
|
via: "ai"
|
|
@@ -2173,7 +2494,7 @@ function Gn(e = {}) {
|
|
|
2173
2494
|
}
|
|
2174
2495
|
//#endregion
|
|
2175
2496
|
//#region src/composables/useMemiBoardUsers.ts
|
|
2176
|
-
var
|
|
2497
|
+
var mr = [
|
|
2177
2498
|
"owner",
|
|
2178
2499
|
"admin",
|
|
2179
2500
|
"manager",
|
|
@@ -2184,13 +2505,13 @@ var Kn = [
|
|
|
2184
2505
|
label: w("ko", `common.role.${e}`),
|
|
2185
2506
|
description: w("ko", `boardUsers.roleDescription.${e}`)
|
|
2186
2507
|
}));
|
|
2187
|
-
function
|
|
2188
|
-
let n = () =>
|
|
2508
|
+
function hr(t = {}) {
|
|
2509
|
+
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
2510
|
async function c(e, t) {
|
|
2190
|
-
if (!
|
|
2191
|
-
await
|
|
2511
|
+
if (!mr.some((e) => e.value === t)) throw Error(a("boardUsers.unsupportedRole"));
|
|
2512
|
+
await Y(f(i, n(), e), {
|
|
2192
2513
|
role: t,
|
|
2193
|
-
updatedAt:
|
|
2514
|
+
updatedAt: K()
|
|
2194
2515
|
});
|
|
2195
2516
|
}
|
|
2196
2517
|
return {
|
|
@@ -2201,11 +2522,11 @@ function qn(t = {}) {
|
|
|
2201
2522
|
}
|
|
2202
2523
|
//#endregion
|
|
2203
2524
|
//#region src/composables/useMemiBoardUserProfile.ts
|
|
2204
|
-
function
|
|
2205
|
-
let t =
|
|
2525
|
+
function gr(e) {
|
|
2526
|
+
let t = Z(), n = () => j(), r = N(() => I(e)), i = N(() => {
|
|
2206
2527
|
let e = r.value;
|
|
2207
2528
|
return e ? f(t, n(), e) : null;
|
|
2208
|
-
}), { data: a, pending: o } =
|
|
2529
|
+
}), { data: a, pending: o } = Se(i);
|
|
2209
2530
|
return {
|
|
2210
2531
|
profile: a,
|
|
2211
2532
|
profilePending: o
|
|
@@ -2213,7 +2534,7 @@ function Jn(e) {
|
|
|
2213
2534
|
}
|
|
2214
2535
|
//#endregion
|
|
2215
2536
|
//#region src/utils/section.ts
|
|
2216
|
-
var
|
|
2537
|
+
var _r = [
|
|
2217
2538
|
3,
|
|
2218
2539
|
4,
|
|
2219
2540
|
5,
|
|
@@ -2223,7 +2544,7 @@ var Yn = [
|
|
|
2223
2544
|
9,
|
|
2224
2545
|
12
|
|
2225
2546
|
];
|
|
2226
|
-
function
|
|
2547
|
+
function vr(e) {
|
|
2227
2548
|
return [
|
|
2228
2549
|
{
|
|
2229
2550
|
value: "list",
|
|
@@ -2239,7 +2560,7 @@ function Xn(e) {
|
|
|
2239
2560
|
}
|
|
2240
2561
|
];
|
|
2241
2562
|
}
|
|
2242
|
-
function
|
|
2563
|
+
function yr(e) {
|
|
2243
2564
|
return [{
|
|
2244
2565
|
value: "latest",
|
|
2245
2566
|
label: w(e, "sectionUtil.sort.latest")
|
|
@@ -2248,18 +2569,18 @@ function Zn(e) {
|
|
|
2248
2569
|
label: w(e, "sectionUtil.sort.likes")
|
|
2249
2570
|
}];
|
|
2250
2571
|
}
|
|
2251
|
-
var
|
|
2252
|
-
function
|
|
2572
|
+
var br = vr("ko"), xr = yr("ko");
|
|
2573
|
+
function Sr(e) {
|
|
2253
2574
|
return e === "list" ? 5 : 4;
|
|
2254
2575
|
}
|
|
2255
|
-
function
|
|
2576
|
+
function Cr(e, t) {
|
|
2256
2577
|
let n = Math.floor(Number(t));
|
|
2257
|
-
return Number.isFinite(n) ? Math.min(e === "list" ? 20 : 10, Math.max(1, n)) :
|
|
2578
|
+
return Number.isFinite(n) ? Math.min(e === "list" ? 20 : 10, Math.max(1, n)) : Sr(e);
|
|
2258
2579
|
}
|
|
2259
|
-
function
|
|
2580
|
+
function wr(e) {
|
|
2260
2581
|
return e === 12 ? null : 12 - e;
|
|
2261
2582
|
}
|
|
2262
|
-
function
|
|
2583
|
+
function Tr(e) {
|
|
2263
2584
|
return {
|
|
2264
2585
|
3: "col-span-12 md:col-span-3",
|
|
2265
2586
|
4: "col-span-12 md:col-span-4",
|
|
@@ -2271,36 +2592,36 @@ function rr(e) {
|
|
|
2271
2592
|
12: "col-span-12"
|
|
2272
2593
|
}[e];
|
|
2273
2594
|
}
|
|
2274
|
-
function
|
|
2595
|
+
function Er(e) {
|
|
2275
2596
|
return e === "sm" ? "min-h-40" : e === "lg" ? "min-h-80" : "min-h-56";
|
|
2276
2597
|
}
|
|
2277
|
-
function
|
|
2598
|
+
function Dr(e) {
|
|
2278
2599
|
let t = e.boardId || e.category || "free";
|
|
2279
2600
|
return e.slug ? `/board/${t}/${e.slug}` : `/board/${t}`;
|
|
2280
2601
|
}
|
|
2281
|
-
function
|
|
2602
|
+
function Or(e) {
|
|
2282
2603
|
return e.kind === "comments" ? e.sort === "likes" ? "/boardComments?sort=likes" : "/boardComments" : e.kind === "list" && e.boardId ? `/board/${e.boardId}` : "/board";
|
|
2283
2604
|
}
|
|
2284
|
-
function
|
|
2605
|
+
function kr(e) {
|
|
2285
2606
|
if (e.boardId && e.postId) return `/board/${e.boardId}/post/${e.postId}`;
|
|
2286
2607
|
}
|
|
2287
|
-
function
|
|
2608
|
+
function Ar(e) {
|
|
2288
2609
|
return e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
|
|
2289
2610
|
}
|
|
2290
|
-
function
|
|
2291
|
-
return e.
|
|
2611
|
+
function jr(e, t) {
|
|
2612
|
+
return it(e, t).trim() || at(e, t).trim() || w(t, "sectionUtil.untitledPost");
|
|
2292
2613
|
}
|
|
2293
|
-
function
|
|
2614
|
+
function Mr(e) {
|
|
2294
2615
|
return e === "image" || e === "video" ? "media" : "list";
|
|
2295
2616
|
}
|
|
2296
|
-
function
|
|
2297
|
-
return w(t,
|
|
2617
|
+
function Nr(e, t) {
|
|
2618
|
+
return w(t, Mr(e) === "media" ? "sectionUtil.view.media" : "sectionUtil.view.list");
|
|
2298
2619
|
}
|
|
2299
|
-
function
|
|
2620
|
+
function Pr(e, t) {
|
|
2300
2621
|
return e ? t(e)?.listView ?? "default" : "default";
|
|
2301
2622
|
}
|
|
2302
|
-
function
|
|
2303
|
-
let n = e.map((e) =>
|
|
2623
|
+
function Fr(e, t) {
|
|
2624
|
+
let n = e.map((e) => Pr(e.boardId, t));
|
|
2304
2625
|
if (!n.length) return "default";
|
|
2305
2626
|
if (n.every((e) => e === n[0])) return n[0];
|
|
2306
2627
|
let r = n.filter((e) => e === "image").length, i = n.filter((e) => e === "video").length;
|
|
@@ -2308,27 +2629,27 @@ function pr(e, t) {
|
|
|
2308
2629
|
}
|
|
2309
2630
|
//#endregion
|
|
2310
2631
|
//#region src/composables/useMemiBoardSections.ts
|
|
2311
|
-
var
|
|
2312
|
-
function
|
|
2632
|
+
var Ir = [];
|
|
2633
|
+
function Lr(e) {
|
|
2313
2634
|
let t = Number(e.cols);
|
|
2314
|
-
if (
|
|
2635
|
+
if (_r.includes(t)) return t;
|
|
2315
2636
|
let n = e.width;
|
|
2316
2637
|
return n === "xs" ? 3 : n === "sm" ? 4 : n === "lg" || Number(e.span) === 2 ? 12 : 6;
|
|
2317
2638
|
}
|
|
2318
|
-
function
|
|
2639
|
+
function Rr(e) {
|
|
2319
2640
|
let t = e.height ?? e.size;
|
|
2320
2641
|
return t === "sm" || t === "lg" ? t : "md";
|
|
2321
2642
|
}
|
|
2322
|
-
function
|
|
2643
|
+
function zr(e) {
|
|
2323
2644
|
return e.kind === "post" || e.kind === "comments" ? e.kind : "list";
|
|
2324
2645
|
}
|
|
2325
|
-
function
|
|
2646
|
+
function Br(e) {
|
|
2326
2647
|
return e.sort === "likes" ? "likes" : "latest";
|
|
2327
2648
|
}
|
|
2328
|
-
function
|
|
2649
|
+
function Vr(e) {
|
|
2329
2650
|
return (typeof e.boardId == "string" ? e.boardId.trim() : "") || null;
|
|
2330
2651
|
}
|
|
2331
|
-
function
|
|
2652
|
+
function Hr(e, t) {
|
|
2332
2653
|
let n = Array.isArray(e.postIds) ? e.postIds : [], r = [];
|
|
2333
2654
|
for (let e of n) {
|
|
2334
2655
|
let n = String(e || "").trim();
|
|
@@ -2336,76 +2657,116 @@ function br(e, t) {
|
|
|
2336
2657
|
}
|
|
2337
2658
|
return r;
|
|
2338
2659
|
}
|
|
2339
|
-
function
|
|
2660
|
+
function Ur(e, t) {
|
|
2340
2661
|
return w(t, "sections.defaultTitle", { n: e + 1 });
|
|
2341
2662
|
}
|
|
2342
|
-
function
|
|
2343
|
-
let r =
|
|
2663
|
+
function Wr(e, t, n) {
|
|
2664
|
+
let r = zr(e), i = Cr(r, e.count), a = Q(e.i18n), o = Qe(e.i18nSource);
|
|
2344
2665
|
return {
|
|
2345
2666
|
id: String(e.id || `box-${t + 1}`),
|
|
2346
|
-
title: String(e.title ||
|
|
2667
|
+
title: String(e.title || Ur(t, n)),
|
|
2347
2668
|
showTitle: e.showTitle !== !1,
|
|
2348
2669
|
kind: r,
|
|
2349
|
-
boardId:
|
|
2670
|
+
boardId: Vr(e),
|
|
2350
2671
|
count: i,
|
|
2351
|
-
postIds:
|
|
2352
|
-
sort:
|
|
2353
|
-
cols:
|
|
2354
|
-
height:
|
|
2355
|
-
order: t
|
|
2672
|
+
postIds: Hr(e, i),
|
|
2673
|
+
sort: Br(e),
|
|
2674
|
+
cols: Lr(e),
|
|
2675
|
+
height: Rr(e),
|
|
2676
|
+
order: t,
|
|
2677
|
+
...a ? { i18n: a } : {},
|
|
2678
|
+
...o ? { i18nSource: o } : {}
|
|
2356
2679
|
};
|
|
2357
2680
|
}
|
|
2358
|
-
function
|
|
2359
|
-
let { locale: e } =
|
|
2360
|
-
async function
|
|
2361
|
-
|
|
2681
|
+
function Gr() {
|
|
2682
|
+
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)));
|
|
2683
|
+
async function s(n) {
|
|
2684
|
+
let r = e.value, i = n.filter((e) => {
|
|
2685
|
+
let t = e.title.trim();
|
|
2686
|
+
return t ? !(e.i18nSource?.title === t && tt(e.i18n, ["title"])) : !1;
|
|
2687
|
+
});
|
|
2688
|
+
if (!i.length) return n;
|
|
2689
|
+
let a = await t(i.map((e) => ({
|
|
2690
|
+
id: e.id,
|
|
2691
|
+
fields: { title: e.title.trim() },
|
|
2692
|
+
context: e.kind
|
|
2693
|
+
})), {
|
|
2694
|
+
source: r,
|
|
2695
|
+
purpose: "section heading on a website home page"
|
|
2696
|
+
});
|
|
2697
|
+
return n.map((e) => {
|
|
2698
|
+
let t = a[e.id];
|
|
2699
|
+
if (!t) return e;
|
|
2700
|
+
let n = e.title.trim(), i = et(r, { title: n }, t);
|
|
2701
|
+
return tt(i, ["title"]) ? {
|
|
2702
|
+
...e,
|
|
2703
|
+
title: n,
|
|
2704
|
+
i18n: i,
|
|
2705
|
+
i18nSource: {
|
|
2706
|
+
locale: r,
|
|
2707
|
+
title: n
|
|
2708
|
+
}
|
|
2709
|
+
} : e;
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
function c(t, n) {
|
|
2713
|
+
let r = t, i = zr(r), a = Cr(i, t.count), o = t.title.trim() || Ur(n, e.value);
|
|
2714
|
+
return {
|
|
2715
|
+
id: t.id,
|
|
2716
|
+
title: o,
|
|
2717
|
+
showTitle: t.showTitle !== !1,
|
|
2718
|
+
kind: i,
|
|
2719
|
+
boardId: Vr(r),
|
|
2720
|
+
count: a,
|
|
2721
|
+
postIds: Hr(r, a),
|
|
2722
|
+
sort: Br(r),
|
|
2723
|
+
cols: Lr(r),
|
|
2724
|
+
height: Rr(r),
|
|
2725
|
+
order: n,
|
|
2726
|
+
...t.i18n ? { i18n: t.i18n } : {},
|
|
2727
|
+
...t.i18nSource?.title === o ? { i18nSource: t.i18nSource } : {}
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
async function l(e) {
|
|
2731
|
+
let t = e;
|
|
2732
|
+
try {
|
|
2733
|
+
t = await s(e);
|
|
2734
|
+
} catch (e) {
|
|
2735
|
+
console.warn("[memi-board] section translation skipped", e);
|
|
2736
|
+
}
|
|
2737
|
+
await q(r, {
|
|
2362
2738
|
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()
|
|
2739
|
+
blocks: t.map((e, t) => c(e, t)),
|
|
2740
|
+
updatedAt: K()
|
|
2380
2741
|
});
|
|
2381
2742
|
}
|
|
2382
2743
|
return {
|
|
2383
|
-
settings:
|
|
2384
|
-
pending:
|
|
2385
|
-
sections:
|
|
2386
|
-
blocks:
|
|
2387
|
-
hasSavedLayout:
|
|
2388
|
-
saveSections:
|
|
2389
|
-
saveBlocks:
|
|
2744
|
+
settings: i,
|
|
2745
|
+
pending: a,
|
|
2746
|
+
sections: o,
|
|
2747
|
+
blocks: o,
|
|
2748
|
+
hasSavedLayout: N(() => Array.isArray(i.value?.blocks)),
|
|
2749
|
+
saveSections: l,
|
|
2750
|
+
saveBlocks: l
|
|
2390
2751
|
};
|
|
2391
2752
|
}
|
|
2392
2753
|
//#endregion
|
|
2393
2754
|
//#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 } =
|
|
2755
|
+
function Kr(e, t) {
|
|
2756
|
+
let n = Z(), i = () => j(), a = I(e)?.trim() || "all", o = N(() => {
|
|
2757
|
+
let r = Math.max(1, Math.floor(Number(I(t)) || 5)), a = I(e)?.trim() || "", o = [X("isPublished", "==", !0), X("listed", "==", !0)];
|
|
2758
|
+
return a && o.unshift(X("boardId", "==", a)), G(M(n, i()), ...o, W("createdAt", "desc"), H(r));
|
|
2759
|
+
}), { data: s, pending: c, error: l } = be(o, { ssrKey: r(i(), `mini-list/${a}`) });
|
|
2399
2760
|
return {
|
|
2400
|
-
posts:
|
|
2761
|
+
posts: N(() => s.value ?? []),
|
|
2401
2762
|
pending: c,
|
|
2402
2763
|
error: l
|
|
2403
2764
|
};
|
|
2404
2765
|
}
|
|
2405
|
-
function
|
|
2406
|
-
let t =
|
|
2766
|
+
function qr(e) {
|
|
2767
|
+
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
2768
|
return {
|
|
2408
|
-
posts:
|
|
2769
|
+
posts: N(() => {
|
|
2409
2770
|
let e = new Map((s.value ?? []).map((e) => [e.id, e])), t = [];
|
|
2410
2771
|
for (let n of i.value) {
|
|
2411
2772
|
let r = e.get(n);
|
|
@@ -2419,7 +2780,21 @@ function Tr(e) {
|
|
|
2419
2780
|
}
|
|
2420
2781
|
//#endregion
|
|
2421
2782
|
//#region src/data/versionHistory.ts
|
|
2422
|
-
var
|
|
2783
|
+
var Jr = [
|
|
2784
|
+
{
|
|
2785
|
+
version: "0.46.2",
|
|
2786
|
+
date: "2026-10-23",
|
|
2787
|
+
highlights: ["게시판 표시 라벨 옆 버튼으로 이름과 설명을 10개 언어로 번역할 수 있어요", "저장할 때 자동으로 번역할지도 선택할 수 있어요"]
|
|
2788
|
+
},
|
|
2789
|
+
{
|
|
2790
|
+
version: "0.46.1",
|
|
2791
|
+
date: "2026-10-22",
|
|
2792
|
+
highlights: [
|
|
2793
|
+
"게시판 이름과 설명을 저장하면 10개 언어로 번역해 둬요",
|
|
2794
|
+
"글쓰기 AI에서 한 번에 10개 언어로 번역해 저장할 수 있어요",
|
|
2795
|
+
"보는 언어로 제목과 본문이 보이고, 원문을 고치면 번역 대신 원문이 보여요"
|
|
2796
|
+
]
|
|
2797
|
+
},
|
|
2423
2798
|
{
|
|
2424
2799
|
version: "0.46.0",
|
|
2425
2800
|
date: "2026-10-21",
|
|
@@ -2722,7 +3097,7 @@ var Er = [
|
|
|
2722
3097
|
highlights: ["글쓰기·댓글·첨부·로그인 권한으로 게시판을 바로 시작할 수 있어요", "부적절한 글·댓글은 AI 검열로 막을 수 있어요"],
|
|
2723
3098
|
note: "실제 보안은 호스트 Firebase 규칙 등록이 필요합니다."
|
|
2724
3099
|
}
|
|
2725
|
-
],
|
|
3100
|
+
], Yr = 10, Xr = 864e5, Zr = 200, Qr = {
|
|
2726
3101
|
proofread: "맞춤법, 띄어쓰기, 문법 오류만 고쳐라. 의미와 말투는 바꾸지 마라.",
|
|
2727
3102
|
polish: "뜻과 작성자의 말투를 유지하면서 더 자연스럽고 읽기 좋은 문장으로 다듬어라.",
|
|
2728
3103
|
continue: "앞의 맥락과 말투에 맞는 내용을 과장 없이 한두 문단 이어서 작성하라.",
|
|
@@ -2731,7 +3106,7 @@ var Er = [
|
|
|
2731
3106
|
"translate-ko": "자연스러운 한국어로 번역하라. 고유명사와 기술 용어는 정확히 유지하라.",
|
|
2732
3107
|
title: "본문을 대표하는 간결한 한국어 제목 하나를 만들어라. 따옴표나 설명 없이 제목만 출력하라."
|
|
2733
3108
|
};
|
|
2734
|
-
function
|
|
3109
|
+
function $r(e) {
|
|
2735
3110
|
return {
|
|
2736
3111
|
proofread: "Fix only spelling, spacing, and grammar errors. Do not change the meaning or tone.",
|
|
2737
3112
|
polish: "Refine the text into more natural, readable sentences while keeping the meaning and the author's tone.",
|
|
@@ -2742,34 +3117,34 @@ function jr(e) {
|
|
|
2742
3117
|
title: `Create one concise title in ${e} that represents the body. Output only the title, without quotes or explanation.`
|
|
2743
3118
|
};
|
|
2744
3119
|
}
|
|
2745
|
-
function
|
|
2746
|
-
let r = t === "ko", i =
|
|
3120
|
+
function ei(e, t, n) {
|
|
3121
|
+
let r = t === "ko", i = cn(t);
|
|
2747
3122
|
if (e.action !== "custom") {
|
|
2748
|
-
if (r) return
|
|
2749
|
-
let t =
|
|
3123
|
+
if (r) return Qr[e.action];
|
|
3124
|
+
let t = $r(i)[e.action];
|
|
2750
3125
|
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
3126
|
}
|
|
2752
|
-
let a = e.customInstruction?.trim().slice(0,
|
|
3127
|
+
let a = e.customInstruction?.trim().slice(0, Zr);
|
|
2753
3128
|
if (!a) throw Error(n("writingAssistant.customRequired"));
|
|
2754
3129
|
return r ? `뜻은 유지하면서 다음 스타일 지시를 반영해 다시 써라: ${a}` : `Rewrite the text applying the following style instruction while keeping the meaning: ${a}`;
|
|
2755
3130
|
}
|
|
2756
|
-
function
|
|
3131
|
+
function ti(e) {
|
|
2757
3132
|
return e.trim().replace(/^```(?:html|text)?\s*/i, "").replace(/\s*```$/, "").trim();
|
|
2758
3133
|
}
|
|
2759
|
-
function
|
|
3134
|
+
function ni(e) {
|
|
2760
3135
|
return !e || typeof e != "object" ? null : "toMillis" in e && typeof e.toMillis == "function" ? e.toMillis() : null;
|
|
2761
3136
|
}
|
|
2762
|
-
function
|
|
2763
|
-
let t =
|
|
2764
|
-
async function
|
|
3137
|
+
function ri(e = {}) {
|
|
3138
|
+
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();
|
|
3139
|
+
async function u() {
|
|
2765
3140
|
let e = o.value?.uid;
|
|
2766
3141
|
if (!e) throw Error(r("writingAssistant.loginRequired"));
|
|
2767
|
-
await
|
|
2768
|
-
let n = f(a,
|
|
3142
|
+
await ve(a, async (t) => {
|
|
3143
|
+
let n = f(a, j(), e), i = await t.get(n);
|
|
2769
3144
|
if (!i.exists()) throw Error(r("writingAssistant.userMissing"));
|
|
2770
|
-
let o = i.data(), s =
|
|
3145
|
+
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
3146
|
if (u && c >= 10) {
|
|
2772
|
-
let e = s +
|
|
3147
|
+
let e = s + Xr, t = Math.max(1, Math.ceil((e - l) / 36e5));
|
|
2773
3148
|
throw Error(r("writingAssistant.dailyLimit", {
|
|
2774
3149
|
limit: 10,
|
|
2775
3150
|
hours: t
|
|
@@ -2777,20 +3152,20 @@ function Fr(e = {}) {
|
|
|
2777
3152
|
}
|
|
2778
3153
|
t.update(n, {
|
|
2779
3154
|
aiWritingCount: u ? c + 1 : 1,
|
|
2780
|
-
aiWritingWindowAt: u ? o.aiWritingWindowAt :
|
|
2781
|
-
updatedAt:
|
|
3155
|
+
aiWritingWindowAt: u ? o.aiWritingWindowAt : K(),
|
|
3156
|
+
updatedAt: K()
|
|
2782
3157
|
});
|
|
2783
3158
|
});
|
|
2784
3159
|
}
|
|
2785
|
-
async function
|
|
2786
|
-
let t = n.value, a = t === "ko", o =
|
|
3160
|
+
async function d(e) {
|
|
3161
|
+
let t = n.value, a = t === "ko", o = ei(e, t, r);
|
|
2787
3162
|
if (!e.content.trim()) throw Error(r("writingAssistant.contentRequired"));
|
|
2788
|
-
await
|
|
2789
|
-
let
|
|
2790
|
-
backend: new
|
|
2791
|
-
useLimitedUseAppCheckTokens:
|
|
2792
|
-
}), d =
|
|
2793
|
-
model:
|
|
3163
|
+
await u();
|
|
3164
|
+
let c = s.moderation ?? {}, l = Re(i, {
|
|
3165
|
+
backend: new Ie(),
|
|
3166
|
+
useLimitedUseAppCheckTokens: c.useLimitedUseAppCheckTokens !== !1
|
|
3167
|
+
}), d = ze(l, {
|
|
3168
|
+
model: c.model ?? "gemini-3.5-flash-lite",
|
|
2794
3169
|
systemInstruction: (a ? [
|
|
2795
3170
|
"너는 게시판 글쓰기 편집 도우미다.",
|
|
2796
3171
|
"사용자가 요청한 편집 결과만 출력하고 설명, 인사, 마크다운 코드 펜스를 붙이지 마라.",
|
|
@@ -2812,94 +3187,64 @@ function Fr(e = {}) {
|
|
|
2812
3187
|
`Task: ${o}`,
|
|
2813
3188
|
e.title ? `Current title: ${e.title}` : "",
|
|
2814
3189
|
`Body:\n${f.slice(0, 12e3)}`
|
|
2815
|
-
]).filter(Boolean).join("\n\n"), m =
|
|
3190
|
+
]).filter(Boolean).join("\n\n"), m = ti((await d.generateContent(p)).response.text());
|
|
2816
3191
|
if (!m) throw Error(r("writingAssistant.emptyResult"));
|
|
2817
3192
|
return m;
|
|
2818
3193
|
}
|
|
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])) })
|
|
3194
|
+
async function p(e) {
|
|
3195
|
+
let t = e.source ?? n.value, i = e.title?.trim() ?? "", a = e.summary?.trim() ?? "", o = e.content ?? "", s = {};
|
|
3196
|
+
i && (s.title = i), a && (s.summary = a);
|
|
3197
|
+
let d = !!Kt(o);
|
|
3198
|
+
if (!Object.keys(s).length && !d) throw Error(r("writingAssistant.translateEmpty"));
|
|
3199
|
+
if (d && o.length > 12e3) throw Error(r("writingAssistant.translateTooLong", { limit: Sn }));
|
|
3200
|
+
await u();
|
|
3201
|
+
let [f, p] = await Promise.all([Object.keys(s).length ? c([{
|
|
3202
|
+
id: "post",
|
|
3203
|
+
fields: s,
|
|
3204
|
+
context: "message board post title and short summary"
|
|
3205
|
+
}], {
|
|
3206
|
+
source: t,
|
|
3207
|
+
purpose: "message board post title and short summary"
|
|
3208
|
+
}) : Promise.resolve({}), d ? l(o, { source: t }) : Promise.resolve({})]), m = et(t, s, f.post);
|
|
3209
|
+
if (Object.keys(s).length && !tt(m, Object.keys(s))) throw Error(r("writingAssistant.translateIncomplete"));
|
|
3210
|
+
let h = {
|
|
3211
|
+
i18n: m,
|
|
3212
|
+
i18nSource: {
|
|
3213
|
+
locale: t,
|
|
3214
|
+
title: i,
|
|
3215
|
+
summary: a
|
|
2854
3216
|
}
|
|
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);
|
|
3217
|
+
};
|
|
3218
|
+
if (d) {
|
|
3219
|
+
let e = {};
|
|
3220
|
+
for (let [t, n] of Object.entries(p)) {
|
|
3221
|
+
let r = T(t);
|
|
3222
|
+
r && typeof n == "string" && n.trim() && (e[r] = { content: n.trim() });
|
|
2873
3223
|
}
|
|
2874
|
-
|
|
3224
|
+
let n = et(t, { content: o }, e);
|
|
3225
|
+
if (!tt(n, ["content"])) throw Error(r("writingAssistant.translateIncomplete"));
|
|
3226
|
+
h.contentI18n = n, h.contentI18nSource = {
|
|
3227
|
+
locale: t,
|
|
3228
|
+
hash: Xe(o)
|
|
3229
|
+
};
|
|
2875
3230
|
}
|
|
2876
|
-
return
|
|
3231
|
+
return h;
|
|
2877
3232
|
}
|
|
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;
|
|
3233
|
+
return {
|
|
3234
|
+
assist: d,
|
|
3235
|
+
translatePost: p
|
|
3236
|
+
};
|
|
2892
3237
|
}
|
|
2893
3238
|
//#endregion
|
|
2894
3239
|
//#region src/utils/renderMarkdown.ts
|
|
2895
|
-
function
|
|
3240
|
+
function ii(e) {
|
|
2896
3241
|
return e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2897
3242
|
}
|
|
2898
|
-
function
|
|
3243
|
+
function ai(e) {
|
|
2899
3244
|
let t = e;
|
|
2900
3245
|
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
3246
|
}
|
|
2902
|
-
function
|
|
3247
|
+
function oi(e) {
|
|
2903
3248
|
if (!e?.trim()) return "";
|
|
2904
3249
|
let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = 0, i = !1, a = [], o = null, s = [];
|
|
2905
3250
|
function c() {
|
|
@@ -2908,7 +3253,7 @@ function Hr(e) {
|
|
|
2908
3253
|
return;
|
|
2909
3254
|
}
|
|
2910
3255
|
let e = o;
|
|
2911
|
-
n.push(`<${e}>${s.map((e) => `<li>${
|
|
3256
|
+
n.push(`<${e}>${s.map((e) => `<li>${ai(e)}</li>`).join("")}</${e}>`), o = null, s = [];
|
|
2912
3257
|
}
|
|
2913
3258
|
for (; r < t.length;) {
|
|
2914
3259
|
let e = t[r] ?? "", l = e.match(/^```(\w*)\s*$/);
|
|
@@ -2917,7 +3262,7 @@ function Hr(e) {
|
|
|
2917
3262
|
continue;
|
|
2918
3263
|
}
|
|
2919
3264
|
if (i) {
|
|
2920
|
-
a.push(
|
|
3265
|
+
a.push(ii(e)), r++;
|
|
2921
3266
|
continue;
|
|
2922
3267
|
}
|
|
2923
3268
|
if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(e)) {
|
|
@@ -2928,24 +3273,24 @@ function Hr(e) {
|
|
|
2928
3273
|
if (u) {
|
|
2929
3274
|
c();
|
|
2930
3275
|
let e = u[1].length;
|
|
2931
|
-
n.push(`<h${e} class="font-semibold mt-4 mb-2">${
|
|
3276
|
+
n.push(`<h${e} class="font-semibold mt-4 mb-2">${ai(ii(u[2]))}</h${e}>`), r++;
|
|
2932
3277
|
continue;
|
|
2933
3278
|
}
|
|
2934
3279
|
if (e.startsWith("> ")) {
|
|
2935
3280
|
c();
|
|
2936
3281
|
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">${
|
|
3282
|
+
for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push(ii((t[r] ?? "").slice(2))), r++;
|
|
3283
|
+
n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${ai(e.join("<br />"))}</blockquote>`);
|
|
2939
3284
|
continue;
|
|
2940
3285
|
}
|
|
2941
3286
|
let d = e.match(/^[-*+]\s+(.+)$/);
|
|
2942
3287
|
if (d) {
|
|
2943
|
-
o && o !== "ul" && c(), o = "ul", s.push(
|
|
3288
|
+
o && o !== "ul" && c(), o = "ul", s.push(ii(d[1])), r++;
|
|
2944
3289
|
continue;
|
|
2945
3290
|
}
|
|
2946
3291
|
let f = e.match(/^\d+\.\s+(.+)$/);
|
|
2947
3292
|
if (f) {
|
|
2948
|
-
o && o !== "ol" && c(), o = "ol", s.push(
|
|
3293
|
+
o && o !== "ol" && c(), o = "ol", s.push(ii(f[1])), r++;
|
|
2949
3294
|
continue;
|
|
2950
3295
|
}
|
|
2951
3296
|
if (!e.trim()) {
|
|
@@ -2957,13 +3302,13 @@ function Hr(e) {
|
|
|
2957
3302
|
for (; r < t.length;) {
|
|
2958
3303
|
let e = t[r] ?? "";
|
|
2959
3304
|
if (!e.trim() || /^(#{1,3}\s|```|[-*+]\s|\d+\.\s|> |(-{3,}|\*{3,})\s*$)/.test(e)) break;
|
|
2960
|
-
p.push(
|
|
3305
|
+
p.push(ii(e)), r++;
|
|
2961
3306
|
}
|
|
2962
|
-
n.push(`<p class="my-2 leading-relaxed">${
|
|
3307
|
+
n.push(`<p class="my-2 leading-relaxed">${ai(p.join("<br />"))}</p>`);
|
|
2963
3308
|
}
|
|
2964
3309
|
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
3310
|
}
|
|
2966
3311
|
//#endregion
|
|
2967
|
-
export {
|
|
3312
|
+
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
3313
|
|
|
2969
3314
|
//# sourceMappingURL=index.js.map
|