memi-board 0.37.2 → 0.38.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 +22 -5
- package/dist/composables/useMemiBoardCommentFeed.d.ts +2 -1
- package/dist/composables/useMemiBoardComments.d.ts +2 -0
- package/dist/composables/useMemiBoardUserComments.d.ts +2 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +394 -360
- package/dist/index.js.map +1 -1
- package/dist/runtime/components/CategorySettings.vue +22 -4
- package/dist/runtime/components/CommentForm.vue +12 -10
- package/dist/runtime/components/Settings.vue +17 -3
- package/dist/types.d.ts +7 -2
- package/dist/utils/boardAccess.d.ts +10 -0
- package/docs/firebase-setup.md +2 -0
- package/docs/firestore.indexes.json.example +51 -0
- package/docs/firestore.rules.example +36 -16
- package/docs/storage.rules.example +12 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -174,19 +174,25 @@ function Be(e, t) {
|
|
|
174
174
|
function Ve(e, t, n, r) {
|
|
175
175
|
return e ? t ? !0 : n ? Be(e, r) : !1 : !1;
|
|
176
176
|
}
|
|
177
|
+
function He(e, t, n, r) {
|
|
178
|
+
if (!e) return !1;
|
|
179
|
+
if (r?.visibility === "hidden") return Ve(e, t, n, r);
|
|
180
|
+
let i = r?.commentWriteRole ?? "user";
|
|
181
|
+
return i === "admin" ? t : i !== "staff" || Ve(e, t, n, r);
|
|
182
|
+
}
|
|
177
183
|
//#endregion
|
|
178
184
|
//#region src/composables/boardLookup.ts
|
|
179
|
-
var
|
|
180
|
-
function Ue(e) {
|
|
181
|
-
He = e;
|
|
182
|
-
}
|
|
185
|
+
var Ue;
|
|
183
186
|
function We(e) {
|
|
184
|
-
|
|
187
|
+
Ue = e;
|
|
188
|
+
}
|
|
189
|
+
function Ge(e) {
|
|
190
|
+
return Ue?.(e);
|
|
185
191
|
}
|
|
186
192
|
//#endregion
|
|
187
193
|
//#region src/utils/moderation-strike.ts
|
|
188
|
-
var
|
|
189
|
-
function
|
|
194
|
+
var Ke = 10, qe = 864e5;
|
|
195
|
+
function Je(e, t, n = Date.now(), r = qe) {
|
|
190
196
|
let i = Math.max(0, Math.floor(Number(e) || 0));
|
|
191
197
|
if (i <= 0) return 0;
|
|
192
198
|
let a = typeof t == "number" && Number.isFinite(t) ? t : null;
|
|
@@ -194,7 +200,7 @@ function qe(e, t, n = Date.now(), r = Ke) {
|
|
|
194
200
|
let o = Math.floor((n - a) / r);
|
|
195
201
|
return Math.max(0, i - o);
|
|
196
202
|
}
|
|
197
|
-
function
|
|
203
|
+
function Ye(e, t, n = Date.now(), r = 10, i = qe) {
|
|
198
204
|
let a = Math.max(0, Math.floor(Number(e) || 0));
|
|
199
205
|
if (a < r) return null;
|
|
200
206
|
let o = typeof t == "number" && Number.isFinite(t) ? t : null;
|
|
@@ -202,17 +208,17 @@ function Je(e, t, n = Date.now(), r = 10, i = Ke) {
|
|
|
202
208
|
let s = o + (a - (r - 1)) * i;
|
|
203
209
|
return s > n ? s : null;
|
|
204
210
|
}
|
|
205
|
-
function
|
|
206
|
-
return
|
|
211
|
+
function Xe(e, t, n = Date.now(), r = 10, i = qe) {
|
|
212
|
+
return Ye(e, t, n, r, i) != null;
|
|
207
213
|
}
|
|
208
|
-
function
|
|
214
|
+
function Ze(e, t = Date.now()) {
|
|
209
215
|
let n = Math.max(0, e - t), r = Math.ceil(n / 36e5);
|
|
210
216
|
if (r <= 0) return "곧 해제";
|
|
211
217
|
if (r < 24) return `약 ${r}시간 후 해제`;
|
|
212
218
|
let i = Math.floor(r / 24), a = r % 24;
|
|
213
219
|
return a === 0 ? `약 ${i}일 후 해제` : `약 ${i}일 ${a}시간 후 해제`;
|
|
214
220
|
}
|
|
215
|
-
function
|
|
221
|
+
function Qe(e) {
|
|
216
222
|
if (e == null) return null;
|
|
217
223
|
if (typeof e == "number" && Number.isFinite(e)) return e;
|
|
218
224
|
if (typeof e == "object" && e && "toMillis" in e && typeof e.toMillis == "function") return e.toMillis();
|
|
@@ -224,26 +230,26 @@ function Ze(e) {
|
|
|
224
230
|
}
|
|
225
231
|
//#endregion
|
|
226
232
|
//#region src/composables/useMemiBoardAuth.ts
|
|
227
|
-
var
|
|
228
|
-
function
|
|
233
|
+
var $e = "__MEMI_BOARD_AUTH_STATE__";
|
|
234
|
+
function et() {
|
|
229
235
|
let e = globalThis;
|
|
230
|
-
return e[
|
|
236
|
+
return e[$e] || (e[$e] = {
|
|
231
237
|
role: P("user"),
|
|
232
238
|
rolePending: P(!0),
|
|
233
239
|
rawBlockCount: P(0),
|
|
234
240
|
blockAtMs: P(null),
|
|
235
241
|
watchStarted: !1
|
|
236
|
-
}), e[
|
|
242
|
+
}), e[$e];
|
|
237
243
|
}
|
|
238
|
-
function
|
|
239
|
-
let e = u(), t =
|
|
244
|
+
function tt() {
|
|
245
|
+
let e = u(), t = et(), n = Q(), r = ue(), i = Z(), { role: a, rolePending: o, rawBlockCount: s, blockAtMs: c } = t;
|
|
240
246
|
function l() {
|
|
241
247
|
let t = e.moderation?.blockBanThreshold;
|
|
242
248
|
return t === 0 ? 0 : typeof t == "number" && t > 0 ? Math.floor(t) : 10;
|
|
243
249
|
}
|
|
244
250
|
function d() {
|
|
245
251
|
let t = e.moderation?.blockBanDecayMs;
|
|
246
|
-
return typeof t == "number" && t > 0 ? t :
|
|
252
|
+
return typeof t == "number" && t > 0 ? t : qe;
|
|
247
253
|
}
|
|
248
254
|
function f(e) {
|
|
249
255
|
return x(n, _(), e);
|
|
@@ -253,7 +259,7 @@ function et() {
|
|
|
253
259
|
s.value = 0, c.value = null;
|
|
254
260
|
return;
|
|
255
261
|
}
|
|
256
|
-
s.value = Math.max(0, Math.floor(Number(e.moderationBlockCount) || 0)), c.value =
|
|
262
|
+
s.value = Math.max(0, Math.floor(Number(e.moderationBlockCount) || 0)), c.value = Qe(e.moderationBlockAt);
|
|
257
263
|
}
|
|
258
264
|
async function m(e) {
|
|
259
265
|
let t = f(e.uid), r = g(n, _(), e.uid), i = await R(t);
|
|
@@ -299,14 +305,14 @@ function et() {
|
|
|
299
305
|
o.value = !1;
|
|
300
306
|
}
|
|
301
307
|
}, { immediate: !0 }));
|
|
302
|
-
let v = M(() => !!i.value), y = M(() => a.value === "admin"), b = M(() => a.value === "staff"), S = M(() => y.value || b.value), C = M(() =>
|
|
308
|
+
let v = M(() => !!i.value), y = M(() => a.value === "admin"), b = M(() => a.value === "staff"), S = M(() => y.value || b.value), C = M(() => Je(s.value, c.value, Date.now(), d())), w = M(() => {
|
|
303
309
|
let e = l();
|
|
304
|
-
return e <= 0 ? !1 :
|
|
310
|
+
return e <= 0 ? !1 : Xe(s.value, c.value, Date.now(), e, d());
|
|
305
311
|
}), T = M(() => {
|
|
306
312
|
let e = l();
|
|
307
313
|
if (e <= 0) return null;
|
|
308
|
-
let t =
|
|
309
|
-
return t == null ? null :
|
|
314
|
+
let t = Ye(s.value, c.value, Date.now(), e, d());
|
|
315
|
+
return t == null ? null : Ze(t);
|
|
310
316
|
}), E = M(() => {
|
|
311
317
|
if (!w.value) return null;
|
|
312
318
|
let e = T.value;
|
|
@@ -333,9 +339,9 @@ function et() {
|
|
|
333
339
|
});
|
|
334
340
|
let a = (r.exists() ? Math.max(0, Math.floor(Number(r.data().moderationBlockCount) || 0)) : 0) + 1, o = Date.now();
|
|
335
341
|
s.value = a, c.value = o;
|
|
336
|
-
let u =
|
|
342
|
+
let u = Je(a, o, o, d()), p = Xe(a, o, o, t, d()), m = ` (경고 ${u}/${t})`;
|
|
337
343
|
if (p) {
|
|
338
|
-
let e =
|
|
344
|
+
let e = Ye(a, o, o, t, d()), n = e == null ? null : Ze(e, o);
|
|
339
345
|
m = n ? ` 경고 ${u}회가 되어 글·댓글 작성이 제한됩니다 (${n}).` : ` 경고 ${u}회가 되어 글·댓글 작성이 제한됩니다.`;
|
|
340
346
|
}
|
|
341
347
|
return {
|
|
@@ -366,7 +372,7 @@ function et() {
|
|
|
366
372
|
await ge(O());
|
|
367
373
|
}
|
|
368
374
|
function P(e) {
|
|
369
|
-
return Ve(i.value?.uid, y.value, b.value, e ?
|
|
375
|
+
return Ve(i.value?.uid, y.value, b.value, e ? Ge(e) : void 0);
|
|
370
376
|
}
|
|
371
377
|
function F(e) {
|
|
372
378
|
return v.value && (i.value?.uid === e.authorUid || P(e.boardId));
|
|
@@ -404,7 +410,7 @@ function et() {
|
|
|
404
410
|
}
|
|
405
411
|
//#endregion
|
|
406
412
|
//#region src/utils/extractEditorImageUrls.ts
|
|
407
|
-
function
|
|
413
|
+
function nt(e) {
|
|
408
414
|
if (!e) return [];
|
|
409
415
|
let t = /* @__PURE__ */ new Set();
|
|
410
416
|
for (let n of e.matchAll(/!\[[^\]]*]\((https?:\/\/[^)\s]+)\)/g)) n[1] && t.add(n[1].replaceAll("&", "&"));
|
|
@@ -413,7 +419,7 @@ function tt(e) {
|
|
|
413
419
|
}
|
|
414
420
|
//#endregion
|
|
415
421
|
//#region src/utils/storagePath.ts
|
|
416
|
-
function
|
|
422
|
+
function rt(e) {
|
|
417
423
|
try {
|
|
418
424
|
let t = new URL(e), n = t.hostname.toLowerCase();
|
|
419
425
|
if (!(n === "firebasestorage.googleapis.com" || n.endsWith(".firebasestorage.app"))) return null;
|
|
@@ -423,104 +429,109 @@ function nt(e) {
|
|
|
423
429
|
return null;
|
|
424
430
|
}
|
|
425
431
|
}
|
|
426
|
-
function
|
|
432
|
+
function it(e) {
|
|
427
433
|
return e.match(/(?:^|\/)posts\/([^/]+)\//)?.[1] ?? null;
|
|
428
434
|
}
|
|
429
435
|
//#endregion
|
|
430
436
|
//#region src/utils/deletePostCascade.ts
|
|
431
|
-
async function
|
|
432
|
-
|
|
433
|
-
|
|
437
|
+
async function at(e) {
|
|
438
|
+
try {
|
|
439
|
+
let t = await be(e);
|
|
440
|
+
await Promise.all([...t.items.map((e) => ve(e).catch(() => {})), ...t.prefixes.map((e) => at(e))]);
|
|
441
|
+
} catch {}
|
|
434
442
|
}
|
|
435
|
-
async function
|
|
443
|
+
async function ot(e, t) {
|
|
444
|
+
await at(xe(e, `${t}/images`)), await at(xe(e, `${t}/attachments`));
|
|
445
|
+
}
|
|
446
|
+
async function st(e, t) {
|
|
436
447
|
let n = [...new Set([...t].filter(Boolean))];
|
|
437
448
|
await Promise.all(n.map((t) => ve(xe(e, t)).catch(() => {})));
|
|
438
449
|
}
|
|
439
|
-
var
|
|
440
|
-
async function
|
|
450
|
+
var ct = 450;
|
|
451
|
+
async function lt(e, t) {
|
|
441
452
|
let n = await z(t);
|
|
442
|
-
for (let t = 0; t < n.docs.length; t +=
|
|
453
|
+
for (let t = 0; t < n.docs.length; t += ct) {
|
|
443
454
|
let r = X(e);
|
|
444
|
-
n.docs.slice(t, t +
|
|
455
|
+
n.docs.slice(t, t + ct).forEach((e) => r.delete(e.ref)), await r.commit();
|
|
445
456
|
}
|
|
446
457
|
}
|
|
447
|
-
async function
|
|
448
|
-
await
|
|
458
|
+
async function ut(e, t, n) {
|
|
459
|
+
await lt(e, W(A(e, t), Y("postId", "==", n)));
|
|
449
460
|
}
|
|
450
|
-
async function
|
|
451
|
-
await
|
|
461
|
+
async function dt(e, t, n) {
|
|
462
|
+
await lt(e, W(o(e, t), Y("postId", "==", n)));
|
|
452
463
|
}
|
|
453
|
-
async function
|
|
454
|
-
await
|
|
464
|
+
async function ft(e, t, n) {
|
|
465
|
+
await lt(e, W(A(e, t), Y("commentId", "==", n)));
|
|
455
466
|
}
|
|
456
|
-
async function
|
|
467
|
+
async function pt(e, n, r, i) {
|
|
457
468
|
let a = t(e, r, i), o = s(e, r, i), [c, l] = await Promise.all([R(a), R(o)]);
|
|
458
469
|
if (!c.exists()) return;
|
|
459
470
|
let u = c.data(), d = l.exists() ? String(l.data().content ?? "") : "";
|
|
460
|
-
await
|
|
471
|
+
await lt(e, W(O(e, r), Y("postId", "==", i))), await ut(e, r, i), await dt(e, r, i);
|
|
461
472
|
let f = /* @__PURE__ */ new Set(), p = /* @__PURE__ */ new Set();
|
|
462
473
|
for (let e of u.attachments ?? []) {
|
|
463
474
|
if (!e?.path) continue;
|
|
464
475
|
f.add(e.path);
|
|
465
|
-
let t =
|
|
476
|
+
let t = it(e.path);
|
|
466
477
|
t && t !== i && p.add(t);
|
|
467
478
|
}
|
|
468
|
-
for (let e of
|
|
469
|
-
let t =
|
|
479
|
+
for (let e of nt(d)) {
|
|
480
|
+
let t = rt(e);
|
|
470
481
|
if (!t) continue;
|
|
471
482
|
f.add(t);
|
|
472
483
|
let n = t.replace(/\/images\/([^/]+)$/, "/images/thumbnails/$1").replace(/\.[^.]+$/, ".jpg");
|
|
473
484
|
n !== t && n.includes("/images/thumbnails/") && f.add(n);
|
|
474
485
|
let r = t.match(/^(.*\/images\/)([^/]+)\.[^.]+$/);
|
|
475
486
|
r && !t.includes("/thumbnails/") && f.add(`${r[1]}thumbnails/${r[2]}.jpg`);
|
|
476
|
-
let a =
|
|
487
|
+
let a = it(t);
|
|
477
488
|
a && a !== i && p.add(a);
|
|
478
489
|
}
|
|
479
|
-
await te(o), await
|
|
480
|
-
for (let e of p) await
|
|
481
|
-
await
|
|
490
|
+
await te(o), await ot(n, k(r, i));
|
|
491
|
+
for (let e of p) await ot(n, k(r, e));
|
|
492
|
+
await st(n, f), await te(a);
|
|
482
493
|
}
|
|
483
494
|
//#endregion
|
|
484
495
|
//#region src/utils/postBody.ts
|
|
485
|
-
function
|
|
496
|
+
function mt(e) {
|
|
486
497
|
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();
|
|
487
498
|
}
|
|
488
|
-
function
|
|
489
|
-
let n =
|
|
499
|
+
function ht(e, t = 40) {
|
|
500
|
+
let n = mt(e);
|
|
490
501
|
if (!n) return "";
|
|
491
502
|
if (n.length <= t) return n;
|
|
492
503
|
let r = n.slice(0, t), i = r.lastIndexOf(" ");
|
|
493
504
|
return `${(i >= Math.floor(t * .6) ? r.slice(0, i) : r).trimEnd()}…`;
|
|
494
505
|
}
|
|
495
|
-
function
|
|
496
|
-
return
|
|
506
|
+
function gt(e) {
|
|
507
|
+
return mt(e).length > 0;
|
|
497
508
|
}
|
|
498
|
-
function
|
|
509
|
+
function _t(e, t) {
|
|
499
510
|
return /<img\b/i.test(String(e || "")) ? !0 : (t || []).some((e) => !!e?.type?.startsWith("image/"));
|
|
500
511
|
}
|
|
501
|
-
function
|
|
502
|
-
return !
|
|
512
|
+
function vt(e) {
|
|
513
|
+
return !gt(e);
|
|
503
514
|
}
|
|
504
515
|
//#endregion
|
|
505
516
|
//#region src/utils/postPreview.ts
|
|
506
|
-
function
|
|
517
|
+
function yt(e) {
|
|
507
518
|
return e.replace(/&/g, "&").replace(/"/g, "\"").replace(/'/g, "'");
|
|
508
519
|
}
|
|
509
|
-
function
|
|
520
|
+
function bt(e) {
|
|
510
521
|
if (e) try {
|
|
511
|
-
let t = new URL(
|
|
522
|
+
let t = new URL(yt(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
|
|
512
523
|
if (n === "youtu.be") return t.pathname.split("/").filter(Boolean)[0];
|
|
513
524
|
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;
|
|
514
525
|
} catch {
|
|
515
526
|
return;
|
|
516
527
|
}
|
|
517
528
|
}
|
|
518
|
-
function
|
|
519
|
-
let t =
|
|
529
|
+
function xt(e) {
|
|
530
|
+
let t = bt(e.videoUrl);
|
|
520
531
|
return t ? `https://i.ytimg.com/vi/${t}/hqdefault.jpg` : e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
|
|
521
532
|
}
|
|
522
|
-
function
|
|
523
|
-
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 ?
|
|
533
|
+
function St(e, t = []) {
|
|
534
|
+
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 ? yt(r[1]) : a, s = o ? bt(o) : void 0, c = n ? yt(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
|
|
524
535
|
return {
|
|
525
536
|
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),
|
|
526
537
|
...c ? { previewImage: c } : {},
|
|
@@ -529,7 +540,7 @@ function bt(e, t = []) {
|
|
|
529
540
|
}
|
|
530
541
|
//#endregion
|
|
531
542
|
//#region src/composables/useMemiBoardSettings.ts
|
|
532
|
-
var
|
|
543
|
+
var Ct = [
|
|
533
544
|
{
|
|
534
545
|
id: "free",
|
|
535
546
|
label: "자유",
|
|
@@ -557,25 +568,25 @@ var xt = [
|
|
|
557
568
|
visibility: "public",
|
|
558
569
|
order: 2
|
|
559
570
|
}
|
|
560
|
-
],
|
|
561
|
-
function
|
|
571
|
+
], wt = Ct;
|
|
572
|
+
function Tt(e) {
|
|
562
573
|
return e === "hidden" ? "hidden" : "public";
|
|
563
574
|
}
|
|
564
|
-
function
|
|
575
|
+
function Et(e, t, n) {
|
|
565
576
|
return {
|
|
566
577
|
id: e,
|
|
567
578
|
label: typeof t.label == "string" && t.label.trim() ? t.label.trim() : e,
|
|
568
579
|
order: typeof t.order == "number" ? t.order : n,
|
|
569
580
|
description: (typeof t.description == "string" ? t.description : "") || "",
|
|
570
|
-
visibility:
|
|
581
|
+
visibility: Tt(t.visibility),
|
|
571
582
|
listView: t.listView ?? "default",
|
|
572
583
|
writeRole: t.writeRole ?? "user",
|
|
573
584
|
commentWriteRole: t.commentWriteRole ?? "user",
|
|
574
585
|
allowedStaffUids: Array.isArray(t.allowedStaffUids) ? t.allowedStaffUids : []
|
|
575
586
|
};
|
|
576
587
|
}
|
|
577
|
-
function
|
|
578
|
-
let t = () => _(), n = Q(), r = le(), { isSignedIn: i, isAdmin: a, isStaff: o, user: s } =
|
|
588
|
+
function Dt() {
|
|
589
|
+
let t = () => _(), n = Q(), r = le(), { isSignedIn: i, isAdmin: a, isStaff: o, user: s } = tt(), l = M(() => W(e(n, t()), U("order", "asc"))), { data: u, pending: d } = se(l, { ssrKey: w(t(), "boards") }), f = M(() => u.value.map((e, t) => Et(e.id, e, t))), p = f, m = M(() => f.value.filter((e) => e.visibility !== "hidden")), h = m;
|
|
579
590
|
function g(e) {
|
|
580
591
|
if (e) return f.value.find((t) => t.id === e)?.label ?? e;
|
|
581
592
|
}
|
|
@@ -592,29 +603,32 @@ function Tt() {
|
|
|
592
603
|
return C(e) === "hidden";
|
|
593
604
|
}
|
|
594
605
|
let D = E;
|
|
595
|
-
function
|
|
606
|
+
function k(e) {
|
|
596
607
|
if (e) return f.value.find((t) => t.id === e);
|
|
597
608
|
}
|
|
598
|
-
|
|
599
|
-
function
|
|
600
|
-
return Ve(s.value?.uid, a.value, o.value, e ?
|
|
609
|
+
We((e) => k(e));
|
|
610
|
+
function A(e) {
|
|
611
|
+
return Ve(s.value?.uid, a.value, o.value, e ? k(e) : void 0);
|
|
601
612
|
}
|
|
602
|
-
let
|
|
603
|
-
async function
|
|
613
|
+
let j = M(() => f.value.filter((e) => A(e.id)));
|
|
614
|
+
async function N(e, t, r) {
|
|
604
615
|
let i;
|
|
605
616
|
for (;;) {
|
|
606
|
-
let a = await z(W(
|
|
617
|
+
let a = await z(W(e, Y("boardId", "==", t), U("__name__"), ...i ? [q(i)] : [], V(400)));
|
|
607
618
|
if (a.empty) break;
|
|
608
|
-
let o = X(n);
|
|
609
|
-
for (let e of a.docs) o.update(e.ref, { listed: r });
|
|
610
|
-
if (await o.commit(), i = a.docs[a.docs.length - 1], a.docs.length < 400) break;
|
|
619
|
+
let o = X(n), s = 0;
|
|
620
|
+
for (let e of a.docs) e.data().listed !== r && (o.update(e.ref, { listed: r }), s++);
|
|
621
|
+
if (s && await o.commit(), i = a.docs[a.docs.length - 1], a.docs.length < 400) break;
|
|
611
622
|
}
|
|
612
623
|
}
|
|
613
|
-
function
|
|
624
|
+
async function ee(e, r) {
|
|
625
|
+
await N(c(n, t()), e, r), await N(O(n, t()), e, r);
|
|
626
|
+
}
|
|
627
|
+
function P(e, t) {
|
|
614
628
|
return {
|
|
615
629
|
label: e.label.trim(),
|
|
616
630
|
description: (e.description ?? "").trim(),
|
|
617
|
-
visibility:
|
|
631
|
+
visibility: Tt(e.visibility),
|
|
618
632
|
listView: e.listView ?? "default",
|
|
619
633
|
writeRole: e.writeRole ?? "user",
|
|
620
634
|
commentWriteRole: e.commentWriteRole ?? "user",
|
|
@@ -623,53 +637,53 @@ function Tt() {
|
|
|
623
637
|
updatedAt: G()
|
|
624
638
|
};
|
|
625
639
|
}
|
|
626
|
-
async function
|
|
640
|
+
async function F(e, r = e.order ?? 0) {
|
|
627
641
|
if (!i.value) throw Error("로그인이 필요합니다.");
|
|
628
642
|
let a = e.id.trim(), o = e.label.trim();
|
|
629
643
|
if (!a || !o) throw Error("보드 ID와 이름을 입력해 주세요.");
|
|
630
|
-
let s =
|
|
644
|
+
let s = Tt(e.visibility), c = P({
|
|
631
645
|
...e,
|
|
632
646
|
label: o,
|
|
633
647
|
visibility: s
|
|
634
648
|
}, r);
|
|
635
|
-
await K(v(n, t(), a),
|
|
649
|
+
await K(v(n, t(), a), c, { merge: !0 }), await ee(a, s !== "hidden");
|
|
636
650
|
}
|
|
637
|
-
let
|
|
638
|
-
async function
|
|
651
|
+
let I = F;
|
|
652
|
+
async function ne(e) {
|
|
639
653
|
if (!i.value) throw Error("로그인이 필요합니다.");
|
|
640
654
|
let r = new Map(f.value.map((e) => [e.id, e.visibility ?? "public"])), a = X(n), o = [];
|
|
641
655
|
e.forEach((e, i) => {
|
|
642
656
|
let s = e.id.trim(), c = e.label.trim();
|
|
643
657
|
if (!s || !c) throw Error("보드 ID와 이름을 입력해 주세요.");
|
|
644
|
-
let l =
|
|
658
|
+
let l = Tt(e.visibility), u = P({
|
|
645
659
|
...e,
|
|
646
660
|
label: c,
|
|
647
661
|
visibility: l
|
|
648
662
|
}, i);
|
|
649
|
-
a.set(v(n, t(), s), u, { merge: !0 }), (r.get(s) ?? "public") !== l && o.push({
|
|
663
|
+
a.set(v(n, t(), s), u, { merge: !0 }), ((r.get(s) ?? "public") !== l || l === "hidden") && o.push({
|
|
650
664
|
id: s,
|
|
651
665
|
listed: l !== "hidden"
|
|
652
666
|
});
|
|
653
667
|
}), await a.commit();
|
|
654
|
-
for (let e of o) await
|
|
668
|
+
for (let e of o) await ee(e.id, e.listed);
|
|
655
669
|
}
|
|
656
|
-
let
|
|
657
|
-
async function
|
|
670
|
+
let re = ne;
|
|
671
|
+
async function L(e) {
|
|
658
672
|
if (!i.value) throw Error("로그인이 필요합니다.");
|
|
659
673
|
let a = e.trim();
|
|
660
674
|
if (!a) throw Error("삭제할 게시판 ID가 필요합니다.");
|
|
661
675
|
for (;;) {
|
|
662
676
|
let e = await z(W(c(n, t()), Y("boardId", "==", a), V(25)));
|
|
663
677
|
if (e.empty) break;
|
|
664
|
-
for (let i of e.docs) await
|
|
678
|
+
for (let i of e.docs) await pt(n, ye(r), t(), i.id);
|
|
665
679
|
}
|
|
666
680
|
await te(v(n, t(), e));
|
|
667
681
|
}
|
|
668
|
-
let
|
|
669
|
-
async function
|
|
682
|
+
let ie = L;
|
|
683
|
+
async function R() {
|
|
670
684
|
return Promise.resolve();
|
|
671
685
|
}
|
|
672
|
-
async function
|
|
686
|
+
async function B(e) {
|
|
673
687
|
let t = e.trim();
|
|
674
688
|
if (!t) throw Error("보드 이름을 입력해 주세요.");
|
|
675
689
|
if (!i.value) throw Error("로그인이 필요합니다.");
|
|
@@ -677,7 +691,7 @@ function Tt() {
|
|
|
677
691
|
if (n) return n.id;
|
|
678
692
|
let r = S(t) || `board-${Date.now()}`, a = r, o = 2, s = new Set(f.value.map((e) => e.id));
|
|
679
693
|
for (; s.has(a);) a = `${r}-${o++}`;
|
|
680
|
-
return await
|
|
694
|
+
return await F({
|
|
681
695
|
id: a,
|
|
682
696
|
label: t,
|
|
683
697
|
visibility: "public",
|
|
@@ -694,9 +708,9 @@ function Tt() {
|
|
|
694
708
|
boardDescription: b,
|
|
695
709
|
boardVisibility: C,
|
|
696
710
|
isBoardHidden: E,
|
|
697
|
-
getBoard:
|
|
698
|
-
canManageBoard:
|
|
699
|
-
managedBoards:
|
|
711
|
+
getBoard: k,
|
|
712
|
+
canManageBoard: A,
|
|
713
|
+
managedBoards: j,
|
|
700
714
|
categories: p,
|
|
701
715
|
publicCategories: h,
|
|
702
716
|
categoryLabel: y,
|
|
@@ -704,33 +718,33 @@ function Tt() {
|
|
|
704
718
|
categoryVisibility: T,
|
|
705
719
|
isCategoryHidden: D,
|
|
706
720
|
settingsPending: d,
|
|
707
|
-
ensureSettings:
|
|
708
|
-
addBoard:
|
|
709
|
-
addCategory:
|
|
710
|
-
saveBoard:
|
|
711
|
-
saveCategory:
|
|
712
|
-
saveBoards:
|
|
713
|
-
saveCategories:
|
|
714
|
-
deleteBoard:
|
|
715
|
-
deleteCategory:
|
|
716
|
-
DEFAULT_BOARDS:
|
|
717
|
-
DEFAULT_CATEGORIES:
|
|
721
|
+
ensureSettings: R,
|
|
722
|
+
addBoard: B,
|
|
723
|
+
addCategory: B,
|
|
724
|
+
saveBoard: F,
|
|
725
|
+
saveCategory: I,
|
|
726
|
+
saveBoards: ne,
|
|
727
|
+
saveCategories: re,
|
|
728
|
+
deleteBoard: L,
|
|
729
|
+
deleteCategory: ie,
|
|
730
|
+
DEFAULT_BOARDS: Ct,
|
|
731
|
+
DEFAULT_CATEGORIES: wt
|
|
718
732
|
};
|
|
719
733
|
}
|
|
720
734
|
//#endregion
|
|
721
735
|
//#region src/composables/useMemiBoardPosts.ts
|
|
722
|
-
function
|
|
736
|
+
function Ot(e) {
|
|
723
737
|
if (!e || typeof e != "object") return !1;
|
|
724
738
|
let t = "code" in e ? String(e.code) : "";
|
|
725
739
|
return t === "permission-denied" || t === "firestore/permission-denied";
|
|
726
740
|
}
|
|
727
|
-
function
|
|
741
|
+
function kt(e) {
|
|
728
742
|
let t = F(e)?.trim();
|
|
729
743
|
if (!t) throw Error("[memi-board] boardId 가 필요합니다.");
|
|
730
744
|
return t;
|
|
731
745
|
}
|
|
732
|
-
function
|
|
733
|
-
let n = Q(), r = le(), { isBoardHidden: i, getBoard: a } =
|
|
746
|
+
function At(e) {
|
|
747
|
+
let n = Q(), r = le(), { isBoardHidden: i, getBoard: a } = Dt(), o = () => _(), l = () => kt(e), u = () => c(n, o()), d = (e) => t(n, o(), e), f = (e) => s(n, o(), e);
|
|
734
748
|
function p() {
|
|
735
749
|
return !i(l());
|
|
736
750
|
}
|
|
@@ -738,7 +752,7 @@ function Ot(e) {
|
|
|
738
752
|
return a(l())?.listView === "image";
|
|
739
753
|
}
|
|
740
754
|
function h(e) {
|
|
741
|
-
let t = m(), n =
|
|
755
|
+
let t = m(), n = gt(e.content), r = _t(e.content, e.attachments);
|
|
742
756
|
if (t) {
|
|
743
757
|
if (!r) throw Error("사진을 올려 주세요.");
|
|
744
758
|
return;
|
|
@@ -781,13 +795,13 @@ function Ot(e) {
|
|
|
781
795
|
post: e
|
|
782
796
|
} : { status: "not-found" };
|
|
783
797
|
} catch (e) {
|
|
784
|
-
return
|
|
798
|
+
return Ot(e) ? { status: "permission-denied" } : {
|
|
785
799
|
status: "error",
|
|
786
800
|
message: e instanceof Error ? e.message : String(e)
|
|
787
801
|
};
|
|
788
802
|
}
|
|
789
803
|
} catch (e) {
|
|
790
|
-
return
|
|
804
|
+
return Ot(e) ? { status: "permission-denied" } : {
|
|
791
805
|
status: "error",
|
|
792
806
|
message: e instanceof Error ? e.message : String(e)
|
|
793
807
|
};
|
|
@@ -825,7 +839,7 @@ function Ot(e) {
|
|
|
825
839
|
async function C(e) {
|
|
826
840
|
h(e);
|
|
827
841
|
let t = e.postId || g(), n = m(), r, i;
|
|
828
|
-
if (n) r = t, i =
|
|
842
|
+
if (n) r = t, i = ht(e.content);
|
|
829
843
|
else {
|
|
830
844
|
i = e.title.trim();
|
|
831
845
|
let t = S(i) || "post";
|
|
@@ -833,7 +847,7 @@ function Ot(e) {
|
|
|
833
847
|
let n = 1;
|
|
834
848
|
for (; !(await z(W(u(), Y("boardId", "==", l()), Y("slug", "==", r), V(1)))).empty;) n++, r = `${t}-${n}`;
|
|
835
849
|
}
|
|
836
|
-
let a =
|
|
850
|
+
let a = St(e.content, e.attachments), o = p();
|
|
837
851
|
return await K(d(t), {
|
|
838
852
|
boardId: l(),
|
|
839
853
|
slug: r,
|
|
@@ -857,7 +871,7 @@ function Ot(e) {
|
|
|
857
871
|
}
|
|
858
872
|
async function w(e, t) {
|
|
859
873
|
h(t);
|
|
860
|
-
let n = m() ?
|
|
874
|
+
let n = m() ? ht(t.content) : t.title.trim(), r = St(t.content, t.attachments), i = p();
|
|
861
875
|
await Promise.all([J(d(e), {
|
|
862
876
|
title: n,
|
|
863
877
|
...r,
|
|
@@ -877,7 +891,7 @@ function Ot(e) {
|
|
|
877
891
|
});
|
|
878
892
|
}
|
|
879
893
|
async function E(e) {
|
|
880
|
-
await
|
|
894
|
+
await pt(n, ye(r), o(), e);
|
|
881
895
|
}
|
|
882
896
|
return {
|
|
883
897
|
createPostId: g,
|
|
@@ -891,9 +905,9 @@ function Ot(e) {
|
|
|
891
905
|
deletePost: E
|
|
892
906
|
};
|
|
893
907
|
}
|
|
894
|
-
var
|
|
895
|
-
function
|
|
896
|
-
let n = Q(), { isAdmin: r, user: i, canManageBoard: a } =
|
|
908
|
+
var jt = 10;
|
|
909
|
+
function Mt(e, t = {}) {
|
|
910
|
+
let n = Q(), { isAdmin: r, user: i, canManageBoard: a } = tt(), o = () => _(), s = t.pageSize ?? jt, l = M(() => i.value?.uid ?? ""), u = M(() => kt(e)), d = () => c(n, o()), f = M(() => t.publicOnly === void 0 ? !(r.value || a(u.value)) : typeof t.publicOnly == "boolean" ? t.publicOnly : t.publicOnly.value), p = P([]), m = P([]), h = P([]), g = P(!0), v = P(!0), y = P(!1), b = P(""), x = null, S = null, C, w;
|
|
897
911
|
function T(e) {
|
|
898
912
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
899
913
|
}
|
|
@@ -976,8 +990,8 @@ function At(e, t = {}) {
|
|
|
976
990
|
loadMore: A
|
|
977
991
|
};
|
|
978
992
|
}
|
|
979
|
-
function
|
|
980
|
-
let r = Q(), i = () => _(), a = M(() =>
|
|
993
|
+
function Nt(e, n) {
|
|
994
|
+
let r = Q(), i = () => _(), a = M(() => kt(e)), o = M(() => typeof n == "string" ? n : n.value), c = M(() => t(r, i(), o.value)), l = M(() => s(r, i(), o.value)), { data: u, pending: d } = ce(c), { data: f, pending: p } = ce(l);
|
|
981
995
|
return {
|
|
982
996
|
post: M(() => !u.value || u.value.boardId !== a.value ? null : {
|
|
983
997
|
...u.value,
|
|
@@ -989,9 +1003,9 @@ function jt(e, n) {
|
|
|
989
1003
|
}
|
|
990
1004
|
//#endregion
|
|
991
1005
|
//#region src/composables/useMemiBoardUserPosts.ts
|
|
992
|
-
var
|
|
993
|
-
function
|
|
994
|
-
let n = Q(), r = () => _(), i = () => c(n, r()), a = t.pageSize ??
|
|
1006
|
+
var Pt = 10;
|
|
1007
|
+
function Ft(e, t = {}) {
|
|
1008
|
+
let n = Q(), r = () => _(), i = () => c(n, r()), a = t.pageSize ?? Pt, o = M(() => F(e)?.trim() || ""), s = P([]), l = P([]), u = P(!0), d = P(!0), f = P(!1), p = P(""), m = null, h = null, g;
|
|
995
1009
|
function v(e) {
|
|
996
1010
|
return {
|
|
997
1011
|
id: e.id,
|
|
@@ -1057,9 +1071,9 @@ function Nt(e, t = {}) {
|
|
|
1057
1071
|
}
|
|
1058
1072
|
//#endregion
|
|
1059
1073
|
//#region src/composables/useMemiBoardUserComments.ts
|
|
1060
|
-
var
|
|
1061
|
-
function
|
|
1062
|
-
let n = Q(), r = () => _(), i = () => O(n, r()), a = t.pageSize ??
|
|
1074
|
+
var It = 10;
|
|
1075
|
+
function Lt(e, t = {}) {
|
|
1076
|
+
let n = Q(), r = () => _(), i = () => O(n, r()), a = t.pageSize ?? It, o = M(() => F(e)?.trim() || ""), s = P([]), c = P([]), l = P(!0), u = P(!0), d = P(!1), f = P(""), p = null, m = null, h;
|
|
1063
1077
|
function g(e) {
|
|
1064
1078
|
return {
|
|
1065
1079
|
id: e.id,
|
|
@@ -1075,7 +1089,7 @@ function Ft(e, t = {}) {
|
|
|
1075
1089
|
return [...e.values()].sort((e, t) => v(t) - v(e) || String(t.id).localeCompare(String(e.id)));
|
|
1076
1090
|
});
|
|
1077
1091
|
function b() {
|
|
1078
|
-
return [Y("authorUid", "==", o.value)];
|
|
1092
|
+
return [Y("authorUid", "==", o.value), Y("listed", "==", !0)];
|
|
1079
1093
|
}
|
|
1080
1094
|
function x() {
|
|
1081
1095
|
if (h?.(), !o.value) {
|
|
@@ -1121,11 +1135,11 @@ function Ft(e, t = {}) {
|
|
|
1121
1135
|
}
|
|
1122
1136
|
//#endregion
|
|
1123
1137
|
//#region src/composables/useMemiBoardCommentFeed.ts
|
|
1124
|
-
var
|
|
1125
|
-
function
|
|
1138
|
+
var Rt = 10;
|
|
1139
|
+
function zt(e = "latest", t = {}) {
|
|
1126
1140
|
let n = Q(), r = () => _(), i = () => O(n, r()), a = M(() => {
|
|
1127
1141
|
let e = Math.floor(Number(F(t.pageSize)));
|
|
1128
|
-
return Number.isFinite(e) && e > 0 ? e :
|
|
1142
|
+
return Number.isFinite(e) && e > 0 ? e : Rt;
|
|
1129
1143
|
}), o = M(() => F(e) === "likes" ? "likes" : "latest"), s = P([]), c = P([]), l = P(!0), u = P(!0), d = P(!1), f = P(""), p = null, m = null, h;
|
|
1130
1144
|
function g(e) {
|
|
1131
1145
|
return {
|
|
@@ -1150,10 +1164,15 @@ function Lt(e = "latest", t = {}) {
|
|
|
1150
1164
|
});
|
|
1151
1165
|
function x() {
|
|
1152
1166
|
return o.value === "likes" ? [
|
|
1167
|
+
Y("listed", "==", !0),
|
|
1153
1168
|
U("likeCount", "desc"),
|
|
1154
1169
|
U("createdAt", "desc"),
|
|
1155
1170
|
U(L(), "desc")
|
|
1156
|
-
] : [
|
|
1171
|
+
] : [
|
|
1172
|
+
Y("listed", "==", !0),
|
|
1173
|
+
U("createdAt", "desc"),
|
|
1174
|
+
U(L(), "desc")
|
|
1175
|
+
];
|
|
1157
1176
|
}
|
|
1158
1177
|
function S() {
|
|
1159
1178
|
h?.(), h = H(W(i(), ...x(), V(a.value)), (e) => {
|
|
@@ -1190,9 +1209,9 @@ function Lt(e = "latest", t = {}) {
|
|
|
1190
1209
|
}
|
|
1191
1210
|
//#endregion
|
|
1192
1211
|
//#region src/composables/useMemiBoardAuthorMemo.ts
|
|
1193
|
-
var
|
|
1212
|
+
var Bt = 100, Vt = /* @__PURE__ */ new Map();
|
|
1194
1213
|
function $(e) {
|
|
1195
|
-
let t =
|
|
1214
|
+
let t = Vt.get(e);
|
|
1196
1215
|
return t || (t = ee({
|
|
1197
1216
|
text: "",
|
|
1198
1217
|
sentiment: null,
|
|
@@ -1200,10 +1219,10 @@ function $(e) {
|
|
|
1200
1219
|
pending: !1,
|
|
1201
1220
|
saving: !1,
|
|
1202
1221
|
error: ""
|
|
1203
|
-
}),
|
|
1222
|
+
}), Vt.set(e, t)), t;
|
|
1204
1223
|
}
|
|
1205
|
-
function
|
|
1206
|
-
let t = Q(), n = () => _(), { user: r } =
|
|
1224
|
+
function Ht(e) {
|
|
1225
|
+
let t = Q(), n = () => _(), { user: r } = tt(), i = M(() => F(e)), a = M(() => `${r.value?.uid ?? ""}:${i.value}`), o = M(() => {
|
|
1207
1226
|
let e = r.value?.uid, a = i.value;
|
|
1208
1227
|
return !e || !a ? null : m(t, n(), e, a);
|
|
1209
1228
|
}), s = M({
|
|
@@ -1272,95 +1291,96 @@ function Bt(e) {
|
|
|
1272
1291
|
}
|
|
1273
1292
|
//#endregion
|
|
1274
1293
|
//#region src/composables/useMemiBoardComments.ts
|
|
1275
|
-
var
|
|
1276
|
-
function
|
|
1294
|
+
var Ut = 10, Wt = 5, Gt = 1e3;
|
|
1295
|
+
function Kt(e) {
|
|
1277
1296
|
let t = e.trim();
|
|
1278
1297
|
if (!t) throw Error("댓글 내용을 입력해 주세요.");
|
|
1279
|
-
if (t.length > 1e3) throw Error(`댓글은 ${
|
|
1298
|
+
if (t.length > 1e3) throw Error(`댓글은 ${Gt.toLocaleString()}자까지 작성할 수 있습니다.`);
|
|
1280
1299
|
return t;
|
|
1281
1300
|
}
|
|
1282
|
-
function
|
|
1283
|
-
let i = Q(), a = () => _(), o = () => O(i, a()),
|
|
1284
|
-
function
|
|
1301
|
+
function qt(e, n, r = {}) {
|
|
1302
|
+
let i = Q(), a = () => _(), { isBoardHidden: o } = Dt(), s = () => O(i, a()), c = M(() => F(e)), u = M(() => F(n)), d = () => !o(c.value), f = P([]), p = P([]), m = P(r.subscribe !== !1), h = P([]), g = P(!1), v = P(r.subscribe !== !1), y = P(r.subscribe !== !1), b = null, x, S;
|
|
1303
|
+
function C(e) {
|
|
1285
1304
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
1286
1305
|
}
|
|
1287
|
-
let
|
|
1306
|
+
let w = M(() => {
|
|
1288
1307
|
let e = /* @__PURE__ */ new Map();
|
|
1289
1308
|
for (let t of [
|
|
1290
|
-
...
|
|
1291
|
-
...
|
|
1292
|
-
...
|
|
1309
|
+
...f.value,
|
|
1310
|
+
...p.value,
|
|
1311
|
+
...h.value
|
|
1293
1312
|
]) t.id && e.set(t.id, t);
|
|
1294
|
-
return [...e.values()].sort((e, t) =>
|
|
1313
|
+
return [...e.values()].sort((e, t) => C(e) - C(t) || String(e.id).localeCompare(String(t.id)));
|
|
1295
1314
|
});
|
|
1296
|
-
I([
|
|
1297
|
-
if (
|
|
1298
|
-
|
|
1315
|
+
I([u, c], async ([e]) => {
|
|
1316
|
+
if (h.value = [], f.value = [], p.value = [], b = null, clearTimeout(x), x = void 0, S?.(), S = void 0, v.value = r.subscribe !== !1, r.subscribe !== !1) {
|
|
1317
|
+
m.value = !0, y.value = !0;
|
|
1299
1318
|
try {
|
|
1300
|
-
let t = z(W(
|
|
1319
|
+
let t = z(W(s(), Y("postId", "==", e), U("createdAt", "desc"), U(L(), "desc")));
|
|
1301
1320
|
if (await Promise.all([t.then((t) => {
|
|
1302
|
-
if (
|
|
1321
|
+
if (u.value !== e) return;
|
|
1303
1322
|
let n = t.docs.map((e) => ({
|
|
1304
1323
|
id: e.id,
|
|
1305
1324
|
...e.data()
|
|
1306
1325
|
}));
|
|
1307
|
-
|
|
1308
|
-
}),
|
|
1326
|
+
p.value = n.filter((e) => e.parentId === void 0);
|
|
1327
|
+
}), E()]), u.value !== e) return;
|
|
1309
1328
|
} finally {
|
|
1310
|
-
|
|
1329
|
+
u.value === e && (m.value = !1, y.value = !1);
|
|
1311
1330
|
}
|
|
1312
1331
|
}
|
|
1313
1332
|
}, { immediate: !0 });
|
|
1314
|
-
function
|
|
1315
|
-
|
|
1316
|
-
let t = new Map(
|
|
1333
|
+
function T() {
|
|
1334
|
+
S?.(), S = H(W(s(), Y("postId", "==", u.value), Y("parentId", "==", null), U("createdAt", "desc"), U(L(), "desc"), V(1)), (e) => {
|
|
1335
|
+
let t = new Map(f.value.map((e) => [e.id, e]));
|
|
1317
1336
|
for (let n of e.docChanges()) n.type !== "removed" && t.set(n.doc.id, {
|
|
1318
1337
|
id: n.doc.id,
|
|
1319
1338
|
...n.doc.data()
|
|
1320
1339
|
});
|
|
1321
|
-
|
|
1340
|
+
f.value = [...t.values()];
|
|
1322
1341
|
}, (e) => {
|
|
1323
1342
|
console.error("[memi-board:comments] onSnapshot failed", e);
|
|
1324
1343
|
});
|
|
1325
1344
|
}
|
|
1326
|
-
async function
|
|
1327
|
-
if (!(
|
|
1328
|
-
|
|
1345
|
+
async function E() {
|
|
1346
|
+
if (!(g.value || !v.value)) {
|
|
1347
|
+
g.value = !0;
|
|
1329
1348
|
try {
|
|
1330
1349
|
let e = [
|
|
1331
|
-
Y("postId", "==",
|
|
1350
|
+
Y("postId", "==", u.value),
|
|
1332
1351
|
Y("parentId", "==", null),
|
|
1333
1352
|
U("createdAt", "asc"),
|
|
1334
1353
|
U(L(), "asc"),
|
|
1335
|
-
...
|
|
1354
|
+
...b ? [q(b)] : [],
|
|
1336
1355
|
V(11)
|
|
1337
|
-
], t = await z(W(
|
|
1356
|
+
], t = await z(W(s(), ...e)), n = t.docs.slice(0, Ut), r = n.map((e) => ({
|
|
1338
1357
|
id: e.id,
|
|
1339
1358
|
...e.data()
|
|
1340
|
-
})), i = new Set(
|
|
1341
|
-
|
|
1342
|
-
|
|
1359
|
+
})), i = new Set(h.value.map((e) => e.id));
|
|
1360
|
+
h.value.push(...r.filter((e) => !i.has(e.id))), b = n.at(-1) ?? b, t.size <= Ut && (v.value = !1, x = setTimeout(() => {
|
|
1361
|
+
x = void 0, T();
|
|
1343
1362
|
}, 1e3));
|
|
1344
1363
|
} finally {
|
|
1345
|
-
|
|
1364
|
+
g.value = !1;
|
|
1346
1365
|
}
|
|
1347
1366
|
}
|
|
1348
1367
|
}
|
|
1349
1368
|
N(() => {
|
|
1350
|
-
clearTimeout(
|
|
1369
|
+
clearTimeout(x), S?.();
|
|
1351
1370
|
});
|
|
1352
|
-
async function
|
|
1353
|
-
let n =
|
|
1354
|
-
r.set(
|
|
1355
|
-
postId:
|
|
1356
|
-
boardId:
|
|
1371
|
+
async function D(e) {
|
|
1372
|
+
let n = Kt(e.body), r = X(i), o = re(s());
|
|
1373
|
+
r.set(o, {
|
|
1374
|
+
postId: u.value,
|
|
1375
|
+
boardId: c.value,
|
|
1376
|
+
listed: d(),
|
|
1357
1377
|
body: n,
|
|
1358
1378
|
authorUid: e.authorUid,
|
|
1359
1379
|
authorName: e.authorName,
|
|
1360
1380
|
authorPhoto: e.authorPhoto,
|
|
1361
1381
|
moderationStatus: "approved",
|
|
1362
1382
|
parentId: null,
|
|
1363
|
-
rootId:
|
|
1383
|
+
rootId: o.id,
|
|
1364
1384
|
depth: 0,
|
|
1365
1385
|
replyToUid: null,
|
|
1366
1386
|
replyToName: null,
|
|
@@ -1369,25 +1389,26 @@ function Gt(e, n, r = {}) {
|
|
|
1369
1389
|
isReply: !1,
|
|
1370
1390
|
createdAt: G(),
|
|
1371
1391
|
updatedAt: G()
|
|
1372
|
-
}), r.update(t(i, a(),
|
|
1392
|
+
}), r.update(t(i, a(), u.value), { commentCount: B(1) }), await r.commit();
|
|
1373
1393
|
}
|
|
1374
|
-
async function
|
|
1375
|
-
let n =
|
|
1394
|
+
async function k(e) {
|
|
1395
|
+
let n = Kt(e.body);
|
|
1376
1396
|
if (!e.parent.id) throw Error("답글 대상 댓글을 찾을 수 없습니다.");
|
|
1377
|
-
let r = X(i),
|
|
1378
|
-
if (!
|
|
1379
|
-
let
|
|
1380
|
-
r.set(
|
|
1381
|
-
postId:
|
|
1382
|
-
boardId:
|
|
1397
|
+
let r = X(i), o = re(s()), f = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
|
|
1398
|
+
if (!f) throw Error("댓글 스레드를 찾을 수 없습니다.");
|
|
1399
|
+
let p = Math.min((e.parent.depth ?? 0) + 1, 2);
|
|
1400
|
+
r.set(o, {
|
|
1401
|
+
postId: u.value,
|
|
1402
|
+
boardId: c.value,
|
|
1403
|
+
listed: d(),
|
|
1383
1404
|
body: n,
|
|
1384
1405
|
authorUid: e.authorUid,
|
|
1385
1406
|
authorName: e.authorName,
|
|
1386
1407
|
authorPhoto: e.authorPhoto,
|
|
1387
1408
|
moderationStatus: "approved",
|
|
1388
1409
|
parentId: e.parent.id,
|
|
1389
|
-
rootId:
|
|
1390
|
-
depth:
|
|
1410
|
+
rootId: f,
|
|
1411
|
+
depth: p,
|
|
1391
1412
|
replyToUid: e.parent.authorUid,
|
|
1392
1413
|
replyToName: e.parent.authorName,
|
|
1393
1414
|
replyCount: 0,
|
|
@@ -1395,23 +1416,23 @@ function Gt(e, n, r = {}) {
|
|
|
1395
1416
|
isReply: !0,
|
|
1396
1417
|
createdAt: G(),
|
|
1397
1418
|
updatedAt: G()
|
|
1398
|
-
}), r.update(l(i, a(),
|
|
1419
|
+
}), r.update(l(i, a(), f), { replyCount: B(1) }), r.update(t(i, a(), u.value), { commentCount: B(1) }), await r.commit();
|
|
1399
1420
|
}
|
|
1400
|
-
async function
|
|
1421
|
+
async function A(e) {
|
|
1401
1422
|
if (!e.id) return;
|
|
1402
1423
|
if (e.parentId == null && (e.replyCount ?? 0) > 0) throw Error("답글이 있는 댓글은 현재 삭제할 수 없습니다.");
|
|
1403
|
-
await
|
|
1424
|
+
await ft(i, a(), e.id);
|
|
1404
1425
|
let n = X(i);
|
|
1405
|
-
n.delete(l(i, a(), e.id)), e.parentId && e.rootId && n.update(l(i, a(), e.rootId), { replyCount: B(-1) }), n.update(t(i, a(),
|
|
1426
|
+
n.delete(l(i, a(), e.id)), e.parentId && e.rootId && n.update(l(i, a(), e.rootId), { replyCount: B(-1) }), n.update(t(i, a(), u.value), { commentCount: B(-1) }), await n.commit(), h.value = h.value.filter((t) => t.id !== e.id), p.value = p.value.filter((t) => t.id !== e.id), f.value = f.value.filter((t) => t.id !== e.id);
|
|
1406
1427
|
}
|
|
1407
|
-
async function
|
|
1408
|
-
let n =
|
|
1428
|
+
async function j(e, t) {
|
|
1429
|
+
let n = Kt(t);
|
|
1409
1430
|
await J(l(i, a(), e), {
|
|
1410
1431
|
body: n,
|
|
1411
1432
|
updatedAt: G()
|
|
1412
1433
|
});
|
|
1413
1434
|
}
|
|
1414
|
-
async function
|
|
1435
|
+
async function ee(e, t, n) {
|
|
1415
1436
|
await J(l(i, a(), e), {
|
|
1416
1437
|
isBlinded: t,
|
|
1417
1438
|
moderatedAt: G(),
|
|
@@ -1419,74 +1440,75 @@ function Gt(e, n, r = {}) {
|
|
|
1419
1440
|
});
|
|
1420
1441
|
}
|
|
1421
1442
|
return {
|
|
1422
|
-
comments:
|
|
1423
|
-
commentsPending: M(() =>
|
|
1424
|
-
hasMore:
|
|
1425
|
-
loadingMore:
|
|
1426
|
-
loadMore:
|
|
1427
|
-
addComment:
|
|
1428
|
-
addReply:
|
|
1429
|
-
updateComment:
|
|
1430
|
-
setCommentBlinded:
|
|
1431
|
-
deleteComment:
|
|
1443
|
+
comments: w,
|
|
1444
|
+
commentsPending: M(() => y.value || m.value),
|
|
1445
|
+
hasMore: v,
|
|
1446
|
+
loadingMore: g,
|
|
1447
|
+
loadMore: E,
|
|
1448
|
+
addComment: D,
|
|
1449
|
+
addReply: k,
|
|
1450
|
+
updateComment: j,
|
|
1451
|
+
setCommentBlinded: ee,
|
|
1452
|
+
deleteComment: A
|
|
1432
1453
|
};
|
|
1433
1454
|
}
|
|
1434
|
-
function
|
|
1435
|
-
let r = Q(), i = () => _(), a = () => O(r, i()), o = P([]),
|
|
1436
|
-
function
|
|
1455
|
+
function Jt(e, t, n) {
|
|
1456
|
+
let r = Q(), i = () => _(), a = () => O(r, i()), o = t.trim(), s = P([]), c = P(!1), l = P(!1), u = P(!0), d = null, f;
|
|
1457
|
+
function p(e) {
|
|
1437
1458
|
return e.sort((e, t) => (e.createdAt?.toMillis?.() ?? 0) - (t.createdAt?.toMillis?.() ?? 0) || String(e.id).localeCompare(String(t.id)));
|
|
1438
1459
|
}
|
|
1439
|
-
function
|
|
1440
|
-
|
|
1441
|
-
let t = new Map(
|
|
1460
|
+
function m() {
|
|
1461
|
+
f ||= H(W(a(), Y("postId", "==", o), Y("rootId", "==", n), Y("isReply", "==", !0), U("createdAt", "desc"), U(L(), "desc"), V(1)), (e) => {
|
|
1462
|
+
let t = new Map(s.value.map((e) => [e.id, e]));
|
|
1442
1463
|
for (let n of e.docChanges()) n.type !== "removed" && t.set(n.doc.id, {
|
|
1443
1464
|
id: n.doc.id,
|
|
1444
1465
|
...n.doc.data()
|
|
1445
1466
|
});
|
|
1446
|
-
|
|
1467
|
+
s.value = p([...t.values()]);
|
|
1447
1468
|
}, (e) => {
|
|
1448
1469
|
console.error("[memi-board:replies] onSnapshot failed", e);
|
|
1449
1470
|
});
|
|
1450
1471
|
}
|
|
1451
|
-
async function
|
|
1452
|
-
if (!(
|
|
1453
|
-
|
|
1472
|
+
async function h() {
|
|
1473
|
+
if (!(c.value || !u.value)) {
|
|
1474
|
+
c.value = !0;
|
|
1454
1475
|
try {
|
|
1455
1476
|
let e = [
|
|
1477
|
+
Y("postId", "==", o),
|
|
1456
1478
|
Y("rootId", "==", n),
|
|
1457
1479
|
Y("isReply", "==", !0),
|
|
1458
1480
|
U("createdAt", "asc"),
|
|
1459
1481
|
U(L(), "asc"),
|
|
1460
|
-
...
|
|
1482
|
+
...d ? [q(d)] : [],
|
|
1461
1483
|
V(6)
|
|
1462
|
-
], t = await z(W(a(), ...e)), r = new Set(
|
|
1484
|
+
], t = await z(W(a(), ...e)), r = new Set(s.value.map((e) => e.id)), i = t.docs.slice(0, Wt), c = i.map((e) => ({
|
|
1463
1485
|
id: e.id,
|
|
1464
1486
|
...e.data()
|
|
1465
1487
|
})).filter((e) => !r.has(e.id));
|
|
1466
|
-
|
|
1488
|
+
s.value = p([...s.value, ...c]), d = i.at(-1) ?? d, l.value = !0, t.size <= Wt && (u.value = !1, m());
|
|
1467
1489
|
} finally {
|
|
1468
|
-
|
|
1490
|
+
c.value = !1;
|
|
1469
1491
|
}
|
|
1470
1492
|
}
|
|
1471
1493
|
}
|
|
1472
|
-
function
|
|
1473
|
-
|
|
1494
|
+
function g() {
|
|
1495
|
+
f?.(), f = void 0, s.value = [], d = null, l.value = !1, u.value = !0;
|
|
1474
1496
|
}
|
|
1475
|
-
async function
|
|
1476
|
-
|
|
1477
|
-
}
|
|
1478
|
-
return N(() =>
|
|
1479
|
-
replies:
|
|
1480
|
-
loading:
|
|
1481
|
-
loaded:
|
|
1482
|
-
hasMore:
|
|
1483
|
-
loadMore:
|
|
1484
|
-
refresh:
|
|
1497
|
+
async function v() {
|
|
1498
|
+
g(), await h();
|
|
1499
|
+
}
|
|
1500
|
+
return N(() => f?.()), {
|
|
1501
|
+
replies: s,
|
|
1502
|
+
loading: c,
|
|
1503
|
+
loaded: l,
|
|
1504
|
+
hasMore: u,
|
|
1505
|
+
loadMore: h,
|
|
1506
|
+
refresh: v
|
|
1485
1507
|
};
|
|
1486
1508
|
}
|
|
1487
1509
|
//#endregion
|
|
1488
1510
|
//#region src/composables/useMemiBoardLikes.ts
|
|
1489
|
-
function
|
|
1511
|
+
function Yt(e, n) {
|
|
1490
1512
|
let r = () => _(), i = Q(), a = Z(), o = P(!1), s = P(!1), c;
|
|
1491
1513
|
function l(e) {
|
|
1492
1514
|
return j(i, r(), n, e);
|
|
@@ -1525,8 +1547,8 @@ function qt(e, n) {
|
|
|
1525
1547
|
toggleLike: u
|
|
1526
1548
|
};
|
|
1527
1549
|
}
|
|
1528
|
-
var
|
|
1529
|
-
function
|
|
1550
|
+
var Xt = Symbol.for("memi-board:comment-likes");
|
|
1551
|
+
function Zt(e, t) {
|
|
1530
1552
|
let n = () => _(), r = Q(), i = Z(), a = M(() => F(e)), o = M(() => F(t)), s = P(/* @__PURE__ */ new Set()), c = P(null), u;
|
|
1531
1553
|
I([i, o], ([e, t]) => {
|
|
1532
1554
|
u?.(), u = void 0, s.value = /* @__PURE__ */ new Set(), !(!e || !t) && (u = H(W(A(r, n()), Y("uid", "==", e.uid), Y("postId", "==", t), Y("target", "==", "comment")), (e) => {
|
|
@@ -1579,7 +1601,7 @@ function Yt(e, t) {
|
|
|
1579
1601
|
}
|
|
1580
1602
|
//#endregion
|
|
1581
1603
|
//#region src/composables/useMemiBoardReports.ts
|
|
1582
|
-
var
|
|
1604
|
+
var Qt = 200, $t = [
|
|
1583
1605
|
{
|
|
1584
1606
|
value: "spam",
|
|
1585
1607
|
label: "스팸·광고"
|
|
@@ -1601,10 +1623,10 @@ var Xt = 200, Zt = [
|
|
|
1601
1623
|
label: "기타"
|
|
1602
1624
|
}
|
|
1603
1625
|
];
|
|
1604
|
-
function
|
|
1605
|
-
return
|
|
1626
|
+
function en(e) {
|
|
1627
|
+
return $t.find((t) => t.value === e)?.label || e || "신고";
|
|
1606
1628
|
}
|
|
1607
|
-
function
|
|
1629
|
+
function tn(e, t) {
|
|
1608
1630
|
let n = () => _(), i = Q(), a = Z(), o = P(!1), s = P(!1), c;
|
|
1609
1631
|
I(a, (e) => {
|
|
1610
1632
|
if (c?.(), c = void 0, !e) {
|
|
@@ -1622,7 +1644,7 @@ function $t(e, t) {
|
|
|
1622
1644
|
if (!l) throw Error("로그인이 필요합니다.");
|
|
1623
1645
|
if (s.value) return;
|
|
1624
1646
|
if (c.authorUid && c.authorUid === l) throw Error("자신의 글은 신고할 수 없습니다.");
|
|
1625
|
-
let u =
|
|
1647
|
+
let u = $t.some((e) => e.value === c.reason) ? c.reason : null;
|
|
1626
1648
|
if (!u) throw Error("신고 사유를 선택해 주세요.");
|
|
1627
1649
|
let d = (c.detail || "").trim();
|
|
1628
1650
|
if (d.length > 200) throw Error("자세한 내용은 200자까지입니다.");
|
|
@@ -1651,9 +1673,9 @@ function $t(e, t) {
|
|
|
1651
1673
|
submitReport: l
|
|
1652
1674
|
};
|
|
1653
1675
|
}
|
|
1654
|
-
var
|
|
1655
|
-
function
|
|
1656
|
-
let t = () => _(), n = Q(), { canManageContent: i, canManageBoard: a } =
|
|
1676
|
+
var nn = 20;
|
|
1677
|
+
function rn(e = {}) {
|
|
1678
|
+
let t = () => _(), n = Q(), { canManageContent: i, canManageBoard: a } = tt(), s = e.pageSize ?? nn, c = M(() => F(e.boardId)?.trim() || ""), l = P([]), u = P(!0), d = P(!1), f = P(!1), p = P(""), m = null, h;
|
|
1657
1679
|
function g(e) {
|
|
1658
1680
|
return {
|
|
1659
1681
|
id: e.id,
|
|
@@ -1707,7 +1729,7 @@ function tn(e = {}) {
|
|
|
1707
1729
|
}
|
|
1708
1730
|
//#endregion
|
|
1709
1731
|
//#region src/composables/useMemiBoardViews.ts
|
|
1710
|
-
function
|
|
1732
|
+
function an() {
|
|
1711
1733
|
let e = () => _(), n = Q();
|
|
1712
1734
|
function r(t, n) {
|
|
1713
1735
|
let { postsCollection: r } = e();
|
|
@@ -1747,7 +1769,7 @@ function nn() {
|
|
|
1747
1769
|
}
|
|
1748
1770
|
//#endregion
|
|
1749
1771
|
//#region src/utils/moderation-prompt.ts
|
|
1750
|
-
var
|
|
1772
|
+
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 = {
|
|
1751
1773
|
abuse: "욕설·비속어·모욕",
|
|
1752
1774
|
spam: "스팸·광고",
|
|
1753
1775
|
adult: "선정적·성인 내용",
|
|
@@ -1755,17 +1777,17 @@ var rn = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이
|
|
|
1755
1777
|
other: "부적절한 내용",
|
|
1756
1778
|
none: ""
|
|
1757
1779
|
};
|
|
1758
|
-
function
|
|
1780
|
+
function cn(e) {
|
|
1759
1781
|
if (e.via === "local" && e.localHit) {
|
|
1760
1782
|
let t = e.localHit.trim();
|
|
1761
1783
|
return t.length > 0 && t.length <= 12 ? `「${t}」 같은 표현은 사용할 수 없습니다. 다른 말로 바꿔 주세요.` : "욕설·비속어가 포함되어 게시할 수 없습니다. 표현을 바꿔 주세요.";
|
|
1762
1784
|
}
|
|
1763
1785
|
let t = (e.reason || "").trim();
|
|
1764
1786
|
if (t) return t;
|
|
1765
|
-
let n = e.category && e.category !== "none" ?
|
|
1787
|
+
let n = e.category && e.category !== "none" ? sn[e.category] || e.category : "";
|
|
1766
1788
|
return n ? `${n}이(가) 포함되어 게시할 수 없습니다. 내용을 수정해 주세요.` : "게시할 수 없는 내용이 포함되어 있습니다. 내용을 수정해 주세요.";
|
|
1767
1789
|
}
|
|
1768
|
-
var
|
|
1790
|
+
var ln = [
|
|
1769
1791
|
"씨발",
|
|
1770
1792
|
"시발",
|
|
1771
1793
|
"시이발",
|
|
@@ -1791,10 +1813,10 @@ var sn = [
|
|
|
1791
1813
|
"bitch",
|
|
1792
1814
|
"asshole"
|
|
1793
1815
|
];
|
|
1794
|
-
function
|
|
1816
|
+
function un(e) {
|
|
1795
1817
|
return new RegExp(e.join("|"), "i");
|
|
1796
1818
|
}
|
|
1797
|
-
var
|
|
1819
|
+
var dn = /* @__PURE__ */ new Set([
|
|
1798
1820
|
"none",
|
|
1799
1821
|
"abuse",
|
|
1800
1822
|
"spam",
|
|
@@ -1802,7 +1824,7 @@ var ln = /* @__PURE__ */ new Set([
|
|
|
1802
1824
|
"violence",
|
|
1803
1825
|
"other"
|
|
1804
1826
|
]);
|
|
1805
|
-
function
|
|
1827
|
+
function fn(e) {
|
|
1806
1828
|
let t = e.trim();
|
|
1807
1829
|
if (!t) return null;
|
|
1808
1830
|
let n;
|
|
@@ -1820,7 +1842,7 @@ function un(e) {
|
|
|
1820
1842
|
if (typeof n != "object" || !n) return null;
|
|
1821
1843
|
let r = n;
|
|
1822
1844
|
if (typeof r.flagged != "boolean") return null;
|
|
1823
|
-
let i = typeof r.category == "string" &&
|
|
1845
|
+
let i = typeof r.category == "string" && dn.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
|
|
1824
1846
|
return {
|
|
1825
1847
|
flagged: r.flagged,
|
|
1826
1848
|
category: i,
|
|
@@ -1829,20 +1851,20 @@ function un(e) {
|
|
|
1829
1851
|
}
|
|
1830
1852
|
//#endregion
|
|
1831
1853
|
//#region src/composables/useMemiBoardModeration.ts
|
|
1832
|
-
var
|
|
1854
|
+
var pn = {
|
|
1833
1855
|
flagged: !1,
|
|
1834
1856
|
category: "none",
|
|
1835
1857
|
reason: "",
|
|
1836
1858
|
via: "empty"
|
|
1837
1859
|
};
|
|
1838
|
-
function
|
|
1839
|
-
let e = u().moderation ?? {}, t = le(), { isWriteRestricted: n, restrictedMessage: r, recordContentModerationBlock: i } =
|
|
1860
|
+
function mn() {
|
|
1861
|
+
let e = u().moderation ?? {}, t = le(), { isWriteRestricted: n, restrictedMessage: r, recordContentModerationBlock: i } = tt(), a = un([...ln, ...e.localBlocklist ?? []]);
|
|
1840
1862
|
function o(e) {
|
|
1841
1863
|
let t = e.match(a);
|
|
1842
1864
|
return t ? {
|
|
1843
1865
|
flagged: !0,
|
|
1844
1866
|
category: "abuse",
|
|
1845
|
-
reason:
|
|
1867
|
+
reason: cn({
|
|
1846
1868
|
via: "local",
|
|
1847
1869
|
localHit: t[0] || "",
|
|
1848
1870
|
category: "abuse"
|
|
@@ -1857,7 +1879,7 @@ function fn() {
|
|
|
1857
1879
|
});
|
|
1858
1880
|
return Te(i, {
|
|
1859
1881
|
model: n,
|
|
1860
|
-
systemInstruction:
|
|
1882
|
+
systemInstruction: on,
|
|
1861
1883
|
generationConfig: {
|
|
1862
1884
|
temperature: 0,
|
|
1863
1885
|
maxOutputTokens: 256,
|
|
@@ -1886,7 +1908,7 @@ function fn() {
|
|
|
1886
1908
|
};
|
|
1887
1909
|
}
|
|
1888
1910
|
function l(e, t = "") {
|
|
1889
|
-
let n =
|
|
1911
|
+
let n = cn({
|
|
1890
1912
|
reason: e.reason,
|
|
1891
1913
|
category: e.category,
|
|
1892
1914
|
via: e.via
|
|
@@ -1914,22 +1936,22 @@ function fn() {
|
|
|
1914
1936
|
};
|
|
1915
1937
|
let i = t.trim();
|
|
1916
1938
|
if (!i) return {
|
|
1917
|
-
...
|
|
1939
|
+
...pn,
|
|
1918
1940
|
via: "empty"
|
|
1919
1941
|
};
|
|
1920
1942
|
let a = o(i);
|
|
1921
1943
|
if (a) return d(a);
|
|
1922
1944
|
if (e.enabled === !1) return {
|
|
1923
|
-
...
|
|
1945
|
+
...pn,
|
|
1924
1946
|
via: "disabled"
|
|
1925
1947
|
};
|
|
1926
1948
|
try {
|
|
1927
|
-
let e =
|
|
1949
|
+
let e = fn((await s().generateContent(`심사할 텍스트:\n"""${i.slice(0, 4e3)}"""`)).response.text());
|
|
1928
1950
|
if (!e) return c();
|
|
1929
1951
|
let t = {
|
|
1930
1952
|
flagged: e.flagged,
|
|
1931
1953
|
category: e.category,
|
|
1932
|
-
reason:
|
|
1954
|
+
reason: cn({
|
|
1933
1955
|
reason: e.reason,
|
|
1934
1956
|
category: e.category,
|
|
1935
1957
|
via: "ai"
|
|
@@ -1949,14 +1971,14 @@ function fn() {
|
|
|
1949
1971
|
via: "restricted"
|
|
1950
1972
|
};
|
|
1951
1973
|
if (e.enabled === !1 || !e.moderateImages) return {
|
|
1952
|
-
...
|
|
1974
|
+
...pn,
|
|
1953
1975
|
via: "disabled"
|
|
1954
1976
|
};
|
|
1955
1977
|
try {
|
|
1956
1978
|
let e = await new Promise((e, n) => {
|
|
1957
1979
|
let r = new FileReader();
|
|
1958
1980
|
r.onload = () => e(r.result.split(",")[1] ?? ""), r.onerror = n, r.readAsDataURL(t);
|
|
1959
|
-
}), n =
|
|
1981
|
+
}), n = fn((await s().generateContent([{ inlineData: {
|
|
1960
1982
|
mimeType: t.type || "image/jpeg",
|
|
1961
1983
|
data: e
|
|
1962
1984
|
} }, "이 이미지를 심사해 주세요."])).response.text());
|
|
@@ -1964,7 +1986,7 @@ function fn() {
|
|
|
1964
1986
|
let r = {
|
|
1965
1987
|
flagged: n.flagged,
|
|
1966
1988
|
category: n.category,
|
|
1967
|
-
reason:
|
|
1989
|
+
reason: cn({
|
|
1968
1990
|
reason: n.reason,
|
|
1969
1991
|
category: n.category,
|
|
1970
1992
|
via: "ai"
|
|
@@ -1983,7 +2005,7 @@ function fn() {
|
|
|
1983
2005
|
}
|
|
1984
2006
|
//#endregion
|
|
1985
2007
|
//#region src/composables/useMemiBoardUsers.ts
|
|
1986
|
-
var
|
|
2008
|
+
var hn = [
|
|
1987
2009
|
{
|
|
1988
2010
|
value: "admin",
|
|
1989
2011
|
label: "관리자",
|
|
@@ -2000,10 +2022,10 @@ var pn = [
|
|
|
2000
2022
|
description: "글과 댓글을 작성하고 본인이 작성한 콘텐츠만 수정·삭제할 수 있습니다."
|
|
2001
2023
|
}
|
|
2002
2024
|
];
|
|
2003
|
-
function
|
|
2025
|
+
function gn(e = {}) {
|
|
2004
2026
|
let t = () => _(), n = Q(), r = M(() => F(e.enabled) ?? !0 ? h(n, t()) : null), i = se(r, { ssrKey: w(t(), "users-management") });
|
|
2005
2027
|
async function a(e, r) {
|
|
2006
|
-
if (!
|
|
2028
|
+
if (!hn.some((e) => e.value === r)) throw Error("지원하지 않는 역할입니다.");
|
|
2007
2029
|
await J(x(n, t(), e), {
|
|
2008
2030
|
role: r,
|
|
2009
2031
|
updatedAt: G()
|
|
@@ -2017,7 +2039,7 @@ function mn(e = {}) {
|
|
|
2017
2039
|
}
|
|
2018
2040
|
//#endregion
|
|
2019
2041
|
//#region src/composables/useMemiBoardUserProfile.ts
|
|
2020
|
-
function
|
|
2042
|
+
function _n(e) {
|
|
2021
2043
|
let t = Q(), n = () => _(), r = M(() => F(e)), i = M(() => {
|
|
2022
2044
|
let e = r.value;
|
|
2023
2045
|
return e ? x(t, n(), e) : null;
|
|
@@ -2029,7 +2051,7 @@ function hn(e) {
|
|
|
2029
2051
|
}
|
|
2030
2052
|
//#endregion
|
|
2031
2053
|
//#region src/utils/section.ts
|
|
2032
|
-
var
|
|
2054
|
+
var vn = [
|
|
2033
2055
|
3,
|
|
2034
2056
|
4,
|
|
2035
2057
|
5,
|
|
@@ -2038,7 +2060,7 @@ var gn = [
|
|
|
2038
2060
|
8,
|
|
2039
2061
|
9,
|
|
2040
2062
|
12
|
|
2041
|
-
],
|
|
2063
|
+
], yn = [
|
|
2042
2064
|
{
|
|
2043
2065
|
value: "list",
|
|
2044
2066
|
label: "게시목록"
|
|
@@ -2051,24 +2073,24 @@ var gn = [
|
|
|
2051
2073
|
value: "comments",
|
|
2052
2074
|
label: "댓글모음"
|
|
2053
2075
|
}
|
|
2054
|
-
],
|
|
2076
|
+
], bn = [{
|
|
2055
2077
|
value: "latest",
|
|
2056
2078
|
label: "최신순"
|
|
2057
2079
|
}, {
|
|
2058
2080
|
value: "likes",
|
|
2059
2081
|
label: "좋아요순"
|
|
2060
2082
|
}];
|
|
2061
|
-
function
|
|
2083
|
+
function xn(e) {
|
|
2062
2084
|
return e === "list" ? 5 : 4;
|
|
2063
2085
|
}
|
|
2064
|
-
function
|
|
2086
|
+
function Sn(e, t) {
|
|
2065
2087
|
let n = Math.floor(Number(t));
|
|
2066
|
-
return Number.isFinite(n) ? Math.min(e === "list" ? 20 : 10, Math.max(1, n)) :
|
|
2088
|
+
return Number.isFinite(n) ? Math.min(e === "list" ? 20 : 10, Math.max(1, n)) : xn(e);
|
|
2067
2089
|
}
|
|
2068
|
-
function
|
|
2090
|
+
function Cn(e) {
|
|
2069
2091
|
return e === 12 ? null : 12 - e;
|
|
2070
2092
|
}
|
|
2071
|
-
function
|
|
2093
|
+
function wn(e) {
|
|
2072
2094
|
return {
|
|
2073
2095
|
3: "col-span-12 md:col-span-3",
|
|
2074
2096
|
4: "col-span-12 md:col-span-4",
|
|
@@ -2080,36 +2102,36 @@ function Sn(e) {
|
|
|
2080
2102
|
12: "col-span-12"
|
|
2081
2103
|
}[e];
|
|
2082
2104
|
}
|
|
2083
|
-
function
|
|
2105
|
+
function Tn(e) {
|
|
2084
2106
|
return e === "sm" ? "min-h-40" : e === "lg" ? "min-h-80" : "min-h-56";
|
|
2085
2107
|
}
|
|
2086
|
-
function
|
|
2108
|
+
function En(e) {
|
|
2087
2109
|
let t = e.boardId || e.category || "free";
|
|
2088
2110
|
return e.slug ? `/board/${t}/${e.slug}` : `/board/${t}`;
|
|
2089
2111
|
}
|
|
2090
|
-
function
|
|
2112
|
+
function Dn(e) {
|
|
2091
2113
|
return e.kind === "comments" ? e.sort === "likes" ? "/boardComments?sort=likes" : "/boardComments" : e.kind === "list" && e.boardId ? `/board/${e.boardId}` : "/board";
|
|
2092
2114
|
}
|
|
2093
|
-
function
|
|
2115
|
+
function On(e) {
|
|
2094
2116
|
if (!(!e.boardId || !e.postId)) return `/board/${e.boardId}/post/${e.postId}`;
|
|
2095
2117
|
}
|
|
2096
|
-
function
|
|
2118
|
+
function kn(e) {
|
|
2097
2119
|
return e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
|
|
2098
2120
|
}
|
|
2099
|
-
function
|
|
2121
|
+
function An(e) {
|
|
2100
2122
|
return e.title?.trim() || e.summary || "게시물";
|
|
2101
2123
|
}
|
|
2102
|
-
function
|
|
2124
|
+
function jn(e) {
|
|
2103
2125
|
return e === "image" || e === "video" ? "media" : "list";
|
|
2104
2126
|
}
|
|
2105
|
-
function
|
|
2106
|
-
return
|
|
2127
|
+
function Mn(e) {
|
|
2128
|
+
return jn(e) === "media" ? "미디어" : "목록";
|
|
2107
2129
|
}
|
|
2108
|
-
function
|
|
2130
|
+
function Nn(e, t) {
|
|
2109
2131
|
return e ? t(e)?.listView ?? "default" : "default";
|
|
2110
2132
|
}
|
|
2111
|
-
function
|
|
2112
|
-
let n = e.map((e) =>
|
|
2133
|
+
function Pn(e, t) {
|
|
2134
|
+
let n = e.map((e) => Nn(e.boardId, t));
|
|
2113
2135
|
if (!n.length) return "default";
|
|
2114
2136
|
if (n.every((e) => e === n[0])) return n[0];
|
|
2115
2137
|
let r = n.filter((e) => e === "image").length, i = n.filter((e) => e === "video").length;
|
|
@@ -2117,27 +2139,27 @@ function Mn(e, t) {
|
|
|
2117
2139
|
}
|
|
2118
2140
|
//#endregion
|
|
2119
2141
|
//#region src/composables/useMemiBoardSections.ts
|
|
2120
|
-
var
|
|
2121
|
-
function
|
|
2142
|
+
var Fn = [];
|
|
2143
|
+
function In(e) {
|
|
2122
2144
|
let t = Number(e.cols);
|
|
2123
|
-
if (
|
|
2145
|
+
if (vn.includes(t)) return t;
|
|
2124
2146
|
let n = e.width;
|
|
2125
2147
|
return n === "xs" ? 3 : n === "sm" ? 4 : n === "lg" || Number(e.span) === 2 ? 12 : 6;
|
|
2126
2148
|
}
|
|
2127
|
-
function
|
|
2149
|
+
function Ln(e) {
|
|
2128
2150
|
let t = e.height ?? e.size;
|
|
2129
2151
|
return t === "sm" || t === "lg" ? t : "md";
|
|
2130
2152
|
}
|
|
2131
|
-
function
|
|
2153
|
+
function Rn(e) {
|
|
2132
2154
|
return e.kind === "post" || e.kind === "comments" ? e.kind : "list";
|
|
2133
2155
|
}
|
|
2134
|
-
function
|
|
2156
|
+
function zn(e) {
|
|
2135
2157
|
return e.sort === "likes" ? "likes" : "latest";
|
|
2136
2158
|
}
|
|
2137
|
-
function
|
|
2159
|
+
function Bn(e) {
|
|
2138
2160
|
return (typeof e.boardId == "string" ? e.boardId.trim() : "") || null;
|
|
2139
2161
|
}
|
|
2140
|
-
function
|
|
2162
|
+
function Vn(e, t) {
|
|
2141
2163
|
let n = Array.isArray(e.postIds) ? e.postIds : [], r = [];
|
|
2142
2164
|
for (let e of n) {
|
|
2143
2165
|
let n = String(e || "").trim();
|
|
@@ -2145,40 +2167,40 @@ function zn(e, t) {
|
|
|
2145
2167
|
}
|
|
2146
2168
|
return r;
|
|
2147
2169
|
}
|
|
2148
|
-
function
|
|
2149
|
-
let n =
|
|
2170
|
+
function Hn(e, t) {
|
|
2171
|
+
let n = Rn(e), r = Sn(n, e.count);
|
|
2150
2172
|
return {
|
|
2151
2173
|
id: String(e.id || `box-${t + 1}`),
|
|
2152
2174
|
title: String(e.title || `섹션 ${t + 1}`),
|
|
2153
2175
|
showTitle: e.showTitle !== !1,
|
|
2154
2176
|
kind: n,
|
|
2155
|
-
boardId:
|
|
2177
|
+
boardId: Bn(e),
|
|
2156
2178
|
count: r,
|
|
2157
|
-
postIds:
|
|
2158
|
-
sort:
|
|
2159
|
-
cols:
|
|
2160
|
-
height:
|
|
2179
|
+
postIds: Vn(e, r),
|
|
2180
|
+
sort: zn(e),
|
|
2181
|
+
cols: In(e),
|
|
2182
|
+
height: Ln(e),
|
|
2161
2183
|
order: t
|
|
2162
2184
|
};
|
|
2163
2185
|
}
|
|
2164
|
-
function
|
|
2165
|
-
let e = Q(), t = re(e, "settings", "homeLayout"), { data: n, pending: r } = ce(t), i = M(() => (Array.isArray(n.value?.blocks) ? n.value.blocks :
|
|
2186
|
+
function Un() {
|
|
2187
|
+
let e = Q(), t = re(e, "settings", "homeLayout"), { data: n, pending: r } = ce(t), i = M(() => (Array.isArray(n.value?.blocks) ? n.value.blocks : Fn).map((e, t) => Hn(e, t)));
|
|
2166
2188
|
async function a(e) {
|
|
2167
2189
|
await K(t, {
|
|
2168
2190
|
columns: 12,
|
|
2169
2191
|
blocks: e.map((e, t) => {
|
|
2170
|
-
let n = e, r =
|
|
2192
|
+
let n = e, r = Rn(n), i = Sn(r, e.count);
|
|
2171
2193
|
return {
|
|
2172
2194
|
id: e.id,
|
|
2173
2195
|
title: e.title.trim() || `섹션 ${t + 1}`,
|
|
2174
2196
|
showTitle: e.showTitle !== !1,
|
|
2175
2197
|
kind: r,
|
|
2176
|
-
boardId:
|
|
2198
|
+
boardId: Bn(n),
|
|
2177
2199
|
count: i,
|
|
2178
|
-
postIds:
|
|
2179
|
-
sort:
|
|
2180
|
-
cols:
|
|
2181
|
-
height:
|
|
2200
|
+
postIds: Vn(n, i),
|
|
2201
|
+
sort: zn(n),
|
|
2202
|
+
cols: In(n),
|
|
2203
|
+
height: Ln(n),
|
|
2182
2204
|
order: t
|
|
2183
2205
|
};
|
|
2184
2206
|
}),
|
|
@@ -2197,7 +2219,7 @@ function Vn() {
|
|
|
2197
2219
|
}
|
|
2198
2220
|
//#endregion
|
|
2199
2221
|
//#region src/composables/useMemiBoardMiniPosts.ts
|
|
2200
|
-
function
|
|
2222
|
+
function Wn(e, t) {
|
|
2201
2223
|
let n = Q(), r = () => _(), i = M(() => {
|
|
2202
2224
|
if (!import.meta.client) return null;
|
|
2203
2225
|
let i = Math.max(1, Math.floor(Number(F(t)) || 5)), a = F(e)?.trim() || "", o = [Y("isPublished", "==", !0), Y("listed", "==", !0)];
|
|
@@ -2209,7 +2231,7 @@ function Hn(e, t) {
|
|
|
2209
2231
|
error: s
|
|
2210
2232
|
};
|
|
2211
2233
|
}
|
|
2212
|
-
function
|
|
2234
|
+
function Gn(e) {
|
|
2213
2235
|
let t = Q(), n = () => _(), r = M(() => [...new Set(F(e).map((e) => e.trim()).filter(Boolean))].slice(0, 10)), i = M(() => !import.meta.client || !r.value.length ? null : W(c(t, n()), Y(L(), "in", r.value))), { data: a, pending: o, error: s } = se(i);
|
|
2214
2236
|
return {
|
|
2215
2237
|
posts: M(() => {
|
|
@@ -2226,7 +2248,19 @@ function Un(e) {
|
|
|
2226
2248
|
}
|
|
2227
2249
|
//#endregion
|
|
2228
2250
|
//#region src/data/versionHistory.ts
|
|
2229
|
-
var
|
|
2251
|
+
var Kn = [
|
|
2252
|
+
{
|
|
2253
|
+
version: "0.38.1",
|
|
2254
|
+
date: "2026-08-21",
|
|
2255
|
+
highlights: ["내가 쓴 글을 지울 때 첨부 사진 때문에 막히던 문제를 고쳤어요"],
|
|
2256
|
+
note: "호스트는 Storage 예시 규칙의 memiBoardPosts/{postId}/{allPaths=**} 목록 블록을 넣고 배포해야 해요."
|
|
2257
|
+
},
|
|
2258
|
+
{
|
|
2259
|
+
version: "0.38.0",
|
|
2260
|
+
date: "2026-08-21",
|
|
2261
|
+
highlights: ["숨김 게시판에는 댓글을 거의 쓰지 않아요. 필요하면 담당 스태프만 남길 수 있어요", "숨김 게시판 댓글은 공개 댓글 모음에 나오지 않아요"],
|
|
2262
|
+
note: "호스트는 Firestore 규칙·댓글 listed 인덱스를 다시 배포해야 해요. 공개 보드는 한 번 저장하면 예전 댓글이 목록에 다시 채워져요."
|
|
2263
|
+
},
|
|
2230
2264
|
{
|
|
2231
2265
|
version: "0.36.0",
|
|
2232
2266
|
date: "2026-08-19",
|
|
@@ -2459,7 +2493,7 @@ var Wn = [
|
|
|
2459
2493
|
highlights: ["글쓰기·댓글·첨부·로그인 권한으로 게시판을 바로 시작할 수 있어요", "부적절한 글·댓글은 AI 검열로 막을 수 있어요"],
|
|
2460
2494
|
note: "실제 보안은 호스트 Firebase 규칙 등록이 필요합니다."
|
|
2461
2495
|
}
|
|
2462
|
-
],
|
|
2496
|
+
], qn = /* @__PURE__ */ i(((e, t) => {
|
|
2463
2497
|
(function(n, r) {
|
|
2464
2498
|
typeof e == "object" && t !== void 0 ? t.exports = r() : typeof define == "function" && define.amd ? define(r) : (n = typeof globalThis < "u" ? globalThis : n || self).dayjs = r();
|
|
2465
2499
|
})(e, (function() {
|
|
@@ -2753,7 +2787,7 @@ var Wn = [
|
|
|
2753
2787
|
return w(1e3 * e);
|
|
2754
2788
|
}, w.en = b[y], w.Ls = b, w.p = {}, w;
|
|
2755
2789
|
}));
|
|
2756
|
-
})),
|
|
2790
|
+
})), Jn = /* @__PURE__ */ i(((e, t) => {
|
|
2757
2791
|
(function(n, r) {
|
|
2758
2792
|
typeof e == "object" && t !== void 0 ? t.exports = r() : typeof define == "function" && define.amd ? define(r) : (n = typeof globalThis < "u" ? globalThis : n || self).dayjs_plugin_relativeTime = r();
|
|
2759
2793
|
})(e, (function() {
|
|
@@ -2857,9 +2891,9 @@ var Wn = [
|
|
|
2857
2891
|
};
|
|
2858
2892
|
};
|
|
2859
2893
|
}));
|
|
2860
|
-
})),
|
|
2894
|
+
})), Yn = /* @__PURE__ */ i(((e, t) => {
|
|
2861
2895
|
(function(n, r) {
|
|
2862
|
-
typeof e == "object" && t !== void 0 ? t.exports = r(
|
|
2896
|
+
typeof e == "object" && t !== void 0 ? t.exports = r(qn()) : typeof define == "function" && define.amd ? define(["dayjs"], r) : (n = typeof globalThis < "u" ? globalThis : n || self).dayjs_locale_ko = r(n.dayjs);
|
|
2863
2897
|
})(e, (function(e) {
|
|
2864
2898
|
function t(e) {
|
|
2865
2899
|
return e && typeof e == "object" && "default" in e ? e : { default: e };
|
|
@@ -2907,24 +2941,24 @@ var Wn = [
|
|
|
2907
2941
|
};
|
|
2908
2942
|
return n.default.locale(r, null, !0), r;
|
|
2909
2943
|
}));
|
|
2910
|
-
})),
|
|
2911
|
-
|
|
2912
|
-
function
|
|
2944
|
+
})), Xn = /* @__PURE__ */ a(qn(), 1), Zn = /* @__PURE__ */ a(Jn(), 1);
|
|
2945
|
+
Yn(), Xn.default.extend(Zn.default), Xn.default.locale("ko");
|
|
2946
|
+
function Qn(e) {
|
|
2913
2947
|
return e?.toDate ? e.toDate().toLocaleDateString("ko-KR") : "";
|
|
2914
2948
|
}
|
|
2915
|
-
function
|
|
2916
|
-
return e?.toDate ? (0,
|
|
2949
|
+
function $n(e, t = Date.now()) {
|
|
2950
|
+
return e?.toDate ? (0, Xn.default)(e.toDate()).from((0, Xn.default)(t)) : "방금 전";
|
|
2917
2951
|
}
|
|
2918
|
-
function
|
|
2919
|
-
return e?.toDate ? (0,
|
|
2952
|
+
function er(e) {
|
|
2953
|
+
return e?.toDate ? (0, Xn.default)(e.toDate()).format("YYYY년 M월 D일 HH:mm:ss") : "시간 확인 중";
|
|
2920
2954
|
}
|
|
2921
|
-
function
|
|
2922
|
-
let n =
|
|
2923
|
-
return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${
|
|
2955
|
+
function tr(e, t) {
|
|
2956
|
+
let n = er(e), r = e?.toMillis?.(), i = t?.toMillis?.();
|
|
2957
|
+
return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${er(t)}`];
|
|
2924
2958
|
}
|
|
2925
2959
|
//#endregion
|
|
2926
2960
|
//#region src/composables/useMemiBoardWritingAssistant.ts
|
|
2927
|
-
var
|
|
2961
|
+
var nr = 10, rr = 864e5, ir = {
|
|
2928
2962
|
proofread: "맞춤법, 띄어쓰기, 문법 오류만 고쳐라. 의미와 말투는 바꾸지 마라.",
|
|
2929
2963
|
polish: "뜻과 작성자의 말투를 유지하면서 더 자연스럽고 읽기 좋은 문장으로 다듬어라.",
|
|
2930
2964
|
continue: "앞의 맥락과 말투에 맞는 내용을 과장 없이 한두 문단 이어서 작성하라.",
|
|
@@ -2933,13 +2967,13 @@ var er = 10, tr = 864e5, nr = {
|
|
|
2933
2967
|
"translate-ko": "자연스러운 한국어로 번역하라. 고유명사와 기술 용어는 정확히 유지하라.",
|
|
2934
2968
|
title: "본문을 대표하는 간결한 한국어 제목 하나를 만들어라. 따옴표나 설명 없이 제목만 출력하라."
|
|
2935
2969
|
};
|
|
2936
|
-
function
|
|
2970
|
+
function ar(e) {
|
|
2937
2971
|
return e.trim().replace(/^```(?:html|text)?\s*/i, "").replace(/\s*```$/, "").trim();
|
|
2938
2972
|
}
|
|
2939
|
-
function
|
|
2973
|
+
function or(e) {
|
|
2940
2974
|
return !e || typeof e != "object" ? null : "toMillis" in e && typeof e.toMillis == "function" ? e.toMillis() : null;
|
|
2941
2975
|
}
|
|
2942
|
-
function
|
|
2976
|
+
function sr() {
|
|
2943
2977
|
let e = le(), t = Q(), n = Z(), r = u();
|
|
2944
2978
|
async function i() {
|
|
2945
2979
|
let e = n.value?.uid;
|
|
@@ -2947,9 +2981,9 @@ function ar() {
|
|
|
2947
2981
|
await oe(t, async (n) => {
|
|
2948
2982
|
let r = x(t, _(), e), i = await n.get(r);
|
|
2949
2983
|
if (!i.exists()) throw Error("사용자 정보를 확인한 뒤 다시 시도해 주세요.");
|
|
2950
|
-
let a = i.data(), o =
|
|
2984
|
+
let a = i.data(), o = or(a.aiWritingWindowAt), s = Math.max(0, Math.floor(Number(a.aiWritingCount) || 0)), c = Date.now(), l = o != null && c - o < rr;
|
|
2951
2985
|
if (l && s >= 10) {
|
|
2952
|
-
let e = o +
|
|
2986
|
+
let e = o + rr, t = Math.max(1, Math.ceil((e - c) / 36e5));
|
|
2953
2987
|
throw Error(`AI 글쓰기 도우미는 하루 10회까지 사용할 수 있습니다. 약 ${t}시간 후 다시 이용해 주세요.`);
|
|
2954
2988
|
}
|
|
2955
2989
|
n.update(r, {
|
|
@@ -2978,10 +3012,10 @@ function ar() {
|
|
|
2978
3012
|
}), s = t.content.trim();
|
|
2979
3013
|
if (!s) throw Error("AI로 다듬을 내용을 먼저 입력해 주세요.");
|
|
2980
3014
|
let c = [
|
|
2981
|
-
`작업: ${
|
|
3015
|
+
`작업: ${ir[t.action]}`,
|
|
2982
3016
|
t.title ? `현재 제목: ${t.title}` : "",
|
|
2983
3017
|
`본문:\n${s.slice(0, 12e3)}`
|
|
2984
|
-
].filter(Boolean).join("\n\n"), l =
|
|
3018
|
+
].filter(Boolean).join("\n\n"), l = ar((await o.generateContent(c)).response.text());
|
|
2985
3019
|
if (!l) throw Error("AI가 결과를 만들지 못했습니다. 다시 시도해 주세요.");
|
|
2986
3020
|
return l;
|
|
2987
3021
|
}
|
|
@@ -2989,14 +3023,14 @@ function ar() {
|
|
|
2989
3023
|
}
|
|
2990
3024
|
//#endregion
|
|
2991
3025
|
//#region src/utils/renderMarkdown.ts
|
|
2992
|
-
function
|
|
3026
|
+
function cr(e) {
|
|
2993
3027
|
return e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2994
3028
|
}
|
|
2995
|
-
function
|
|
3029
|
+
function lr(e) {
|
|
2996
3030
|
let t = e;
|
|
2997
3031
|
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;
|
|
2998
3032
|
}
|
|
2999
|
-
function
|
|
3033
|
+
function ur(e) {
|
|
3000
3034
|
if (!e?.trim()) return "";
|
|
3001
3035
|
let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = 0, i = !1, a = [], o = null, s = [];
|
|
3002
3036
|
function c() {
|
|
@@ -3005,7 +3039,7 @@ function cr(e) {
|
|
|
3005
3039
|
return;
|
|
3006
3040
|
}
|
|
3007
3041
|
let e = o;
|
|
3008
|
-
n.push(`<${e}>${s.map((e) => `<li>${
|
|
3042
|
+
n.push(`<${e}>${s.map((e) => `<li>${lr(e)}</li>`).join("")}</${e}>`), o = null, s = [];
|
|
3009
3043
|
}
|
|
3010
3044
|
for (; r < t.length;) {
|
|
3011
3045
|
let e = t[r] ?? "", l = e.match(/^```(\w*)\s*$/);
|
|
@@ -3014,7 +3048,7 @@ function cr(e) {
|
|
|
3014
3048
|
continue;
|
|
3015
3049
|
}
|
|
3016
3050
|
if (i) {
|
|
3017
|
-
a.push(
|
|
3051
|
+
a.push(cr(e)), r++;
|
|
3018
3052
|
continue;
|
|
3019
3053
|
}
|
|
3020
3054
|
if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(e)) {
|
|
@@ -3025,24 +3059,24 @@ function cr(e) {
|
|
|
3025
3059
|
if (u) {
|
|
3026
3060
|
c();
|
|
3027
3061
|
let e = u[1].length;
|
|
3028
|
-
n.push(`<h${e} class="font-semibold mt-4 mb-2">${
|
|
3062
|
+
n.push(`<h${e} class="font-semibold mt-4 mb-2">${lr(cr(u[2]))}</h${e}>`), r++;
|
|
3029
3063
|
continue;
|
|
3030
3064
|
}
|
|
3031
3065
|
if (e.startsWith("> ")) {
|
|
3032
3066
|
c();
|
|
3033
3067
|
let e = [];
|
|
3034
|
-
for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push(
|
|
3035
|
-
n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${
|
|
3068
|
+
for (; r < t.length && (t[r] ?? "").startsWith("> ");) e.push(cr((t[r] ?? "").slice(2))), r++;
|
|
3069
|
+
n.push(`<blockquote class="border-l-4 border-default pl-3 text-muted my-3">${lr(e.join("<br />"))}</blockquote>`);
|
|
3036
3070
|
continue;
|
|
3037
3071
|
}
|
|
3038
3072
|
let d = e.match(/^[-*+]\s+(.+)$/);
|
|
3039
3073
|
if (d) {
|
|
3040
|
-
o && o !== "ul" && c(), o = "ul", s.push(
|
|
3074
|
+
o && o !== "ul" && c(), o = "ul", s.push(cr(d[1])), r++;
|
|
3041
3075
|
continue;
|
|
3042
3076
|
}
|
|
3043
3077
|
let f = e.match(/^\d+\.\s+(.+)$/);
|
|
3044
3078
|
if (f) {
|
|
3045
|
-
o && o !== "ol" && c(), o = "ol", s.push(
|
|
3079
|
+
o && o !== "ol" && c(), o = "ol", s.push(cr(f[1])), r++;
|
|
3046
3080
|
continue;
|
|
3047
3081
|
}
|
|
3048
3082
|
if (!e.trim()) {
|
|
@@ -3054,13 +3088,13 @@ function cr(e) {
|
|
|
3054
3088
|
for (; r < t.length;) {
|
|
3055
3089
|
let e = t[r] ?? "";
|
|
3056
3090
|
if (!e.trim() || /^(#{1,3}\s|```|[-*+]\s|\d+\.\s|> |(-{3,}|\*{3,})\s*$)/.test(e)) break;
|
|
3057
|
-
p.push(
|
|
3091
|
+
p.push(cr(e)), r++;
|
|
3058
3092
|
}
|
|
3059
|
-
n.push(`<p class="my-2 leading-relaxed">${
|
|
3093
|
+
n.push(`<p class="my-2 leading-relaxed">${lr(p.join("<br />"))}</p>`);
|
|
3060
3094
|
}
|
|
3061
3095
|
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");
|
|
3062
3096
|
}
|
|
3063
3097
|
//#endregion
|
|
3064
|
-
export {
|
|
3098
|
+
export { Bt as AUTHOR_MEMO_MAX_LENGTH, vn as BOARD_SECTION_COLS, yn as BOARD_SECTION_KINDS, bn as BOARD_SECTION_SORTS, hn as BOARD_USER_ROLES, Gt as COMMENT_BODY_MAX_LENGTH, qe as DEFAULT_BLOCK_BAN_DECAY_MS, Ke as DEFAULT_BLOCK_BAN_THRESHOLD, Ct as DEFAULT_BOARDS, wt as DEFAULT_CATEGORIES, C as DEFAULT_COMMENTS_COLLECTION, E as DEFAULT_LIKES_COLLECTION, f as DEFAULT_POSTS_COLLECTION, b as DEFAULT_REPORTS_COLLECTION, D as DEFAULT_SETTINGS_COLLECTION, p as DEFAULT_USERS_COLLECTION, Qt as REPORT_DETAIL_MAX_LENGTH, $t as REPORT_REASONS, nr as WRITING_ASSISTANT_DAILY_LIMIT, Ee as asHttpUrl, Me as boardListOgDescription, je as boardListOgTitle, Ae as boardPostOgDescription, ke as boardPostOgTitle, w as boardSsrKey, x as boardUserDoc, h as boardUsersCol, St as buildPostPreview, Ve as canManageBoardByRole, He as canWriteCommentByRole, Sn as clampSectionCount, l as commentDoc, O as commentsCol, T as configureMemiBoard, xn as defaultSectionCount, Je as effectiveModerationBlockCount, nt as extractEditorImageUrls, Re as fetchPublicListForSeo, Le as fetchPublicPostForSeo, Qn as formatDate, er as formatFullDate, $n as formatRelativeDate, Ze as formatRestrictedUntilLabel, tr as formatTimestampDetails, _t as hasBodyImage, gt as hasBodyText, Be as isAssignedStaff, vt as isContentEmpty, Ot as isFirestorePermissionDenied, Xe as isModerationWriteRestricted, j as likeDoc, d as likeDocId, A as likesCol, Xt as memiBoardCommentLikesKey, Nn as miniBoardView, On as miniCommentLink, Dn as miniMoreLink, kn as miniPostImage, En as miniPostLink, An as miniPostTitle, Pn as miniPostsView, jn as miniViewGroup, Mn as miniViewLabel, Ye as moderationWriteRestrictedUntilMs, Ne as normalizeBasePath, Hn as normalizeBoardSection, Cn as pairSectionCols, mt as plainTextFromHtml, s as postBodyDoc, t as postDoc, it as postNamespaceFromStoragePath, k as postStorageFolder, c as postsCol, ur as renderMarkdownToHtml, r as reportDoc, n as reportDocId, en as reportReasonLabel, o as reportsCol, y as resolveBoardPathConfig, Pe as resolvePublicSeoDb, wn as sectionColClass, Tn as sectionMinHeight, e as settingsCol, v as settingsDoc, S as slugify, rt as storagePathFromDownloadUrl, ht as titleFromBody, Oe as toAbsoluteUrl, De as toBoardOgImageUrl, _ as useBoardPathConfig, tt as useMemiBoardAuth, Ht as useMemiBoardAuthorMemo, zt as useMemiBoardCommentFeed, Zt as useMemiBoardCommentLikes, qt as useMemiBoardComments, u as useMemiBoardConfig, Yt as useMemiBoardLikes, Wn as useMemiBoardMiniLatest, Gn as useMemiBoardMiniPicked, mn as useMemiBoardModeration, Nt as useMemiBoardPost, Mt as useMemiBoardPostList, At as useMemiBoardPosts, Jt as useMemiBoardReplies, rn as useMemiBoardReportQueue, tn as useMemiBoardReports, Un as useMemiBoardSections, Dt as useMemiBoardSettings, Lt as useMemiBoardUserComments, Ft as useMemiBoardUserPosts, _n as useMemiBoardUserProfile, gn as useMemiBoardUsers, an as useMemiBoardViews, sr as useMemiBoardWritingAssistant, Kn as versionHistory, xt as videoListCoverUrl, bt as youtubeId };
|
|
3065
3099
|
|
|
3066
3100
|
//# sourceMappingURL=index.js.map
|