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