memi-board 0.19.0 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -1
- package/dist/index.js +317 -242
- package/dist/index.js.map +1 -1
- package/dist/runtime/components/Detail.vue +4 -1
- package/dist/runtime/components/Editor.vue +552 -73
- package/dist/runtime/components/ListDefault.vue +1 -1
- package/dist/runtime/components/ListDense.vue +2 -2
- package/dist/runtime/components/ListImage.vue +10 -4
- package/dist/runtime/composables/useMemiBoardSeo.ts +6 -1
- package/dist/utils/boardSeo.d.ts +10 -1
- package/dist/utils/postBody.d.ts +19 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { computed as e, onScopeDispose as t, reactive as n, ref as r, toValue as i, watch as a } from "vue";
|
|
2
2
|
import { collection as o, deleteDoc as s, deleteField as c, doc as l, documentId as u, endBefore as d, getDoc as f, getDocs as p, increment as m, limit as h, limitToLast as g, onSnapshot as _, orderBy as v, query as y, runTransaction as b, serverTimestamp as x, setDoc as S, startAfter as C, updateDoc as w, where as T, writeBatch as E } from "firebase/firestore";
|
|
3
3
|
import { useCollection as D, useCurrentUser as O, useDocument as k, useFirebaseApp as A, useFirebaseAuth as j, useFirestore as M } from "vuefire";
|
|
4
|
-
import { GoogleAuthProvider as N, OAuthProvider as P, createUserWithEmailAndPassword as F, signInWithEmailAndPassword as I, signInWithPopup as L, signOut as R, updateProfile as
|
|
5
|
-
import { deleteObject as
|
|
6
|
-
import { GoogleAIBackend as
|
|
4
|
+
import { GoogleAuthProvider as N, OAuthProvider as P, createUserWithEmailAndPassword as F, signInWithEmailAndPassword as I, signInWithPopup as L, signOut as R, updateProfile as z } from "firebase/auth";
|
|
5
|
+
import { deleteObject as B, getDownloadURL as V, getStorage as H, listAll as ee, ref as U, uploadBytes as te, uploadBytesResumable as ne } from "firebase/storage";
|
|
6
|
+
import { GoogleAIBackend as re, Schema as W, getAI as ie, getGenerativeModel as ae } from "firebase/ai";
|
|
7
7
|
//#region \0rolldown/runtime.js
|
|
8
|
-
var
|
|
9
|
-
if (t && typeof t == "object" || typeof t == "function") for (var i =
|
|
8
|
+
var oe = Object.create, se = Object.defineProperty, ce = Object.getOwnPropertyDescriptor, le = Object.getOwnPropertyNames, ue = Object.getPrototypeOf, de = Object.prototype.hasOwnProperty, fe = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t.exports), pe = (e, t, n, r) => {
|
|
9
|
+
if (t && typeof t == "object" || typeof t == "function") for (var i = le(t), a = 0, o = i.length, s; a < o; a++) s = i[a], !de.call(e, s) && s !== n && se(e, s, {
|
|
10
10
|
get: ((e) => t[e]).bind(null, s),
|
|
11
|
-
enumerable: !(r =
|
|
11
|
+
enumerable: !(r = ce(t, s)) || r.enumerable
|
|
12
12
|
});
|
|
13
13
|
return e;
|
|
14
|
-
},
|
|
14
|
+
}, me = (e, t, n) => (n = e == null ? {} : oe(ue(e)), pe(t || !e || !e.__esModule || !de.call(e, "default") ? se(n, "default", {
|
|
15
15
|
value: e,
|
|
16
16
|
enumerable: !0
|
|
17
|
-
}) : n, e)),
|
|
18
|
-
function
|
|
17
|
+
}) : n, e)), he = "__MEMI_BOARD_CONFIG__";
|
|
18
|
+
function ge() {
|
|
19
19
|
let e = globalThis;
|
|
20
|
-
return e[
|
|
20
|
+
return e[he] || (e[he] = n({
|
|
21
21
|
collectionPrefix: "memiBoard",
|
|
22
22
|
auth: { providers: ["google", "apple"] },
|
|
23
23
|
moderation: {
|
|
@@ -36,29 +36,47 @@ function _e() {
|
|
|
36
36
|
defaultOgImage: "",
|
|
37
37
|
basePath: "/board"
|
|
38
38
|
}
|
|
39
|
-
})), e[
|
|
39
|
+
})), e[he];
|
|
40
40
|
}
|
|
41
|
-
function
|
|
42
|
-
let t =
|
|
41
|
+
function _e(e) {
|
|
42
|
+
let t = ge();
|
|
43
43
|
e.collectionPrefix && (t.collectionPrefix = e.collectionPrefix), e.auth && Object.assign(t.auth, e.auth), e.moderation && Object.assign(t.moderation, e.moderation), e.seo && Object.assign(t.seo, e.seo);
|
|
44
44
|
}
|
|
45
|
-
function
|
|
46
|
-
return
|
|
45
|
+
function G() {
|
|
46
|
+
return ge();
|
|
47
47
|
}
|
|
48
48
|
//#endregion
|
|
49
49
|
//#region src/utils/boardSeo.ts
|
|
50
|
-
function
|
|
50
|
+
function ve(e) {
|
|
51
51
|
if (typeof e != "string") return null;
|
|
52
52
|
let t = e.trim();
|
|
53
53
|
return /^https?:\/\//i.test(t) ? t : null;
|
|
54
54
|
}
|
|
55
|
+
function ye(e) {
|
|
56
|
+
let t = ve(e);
|
|
57
|
+
if (!t) return null;
|
|
58
|
+
if (!/firebasestorage\.googleapis\.com/i.test(t)) return t;
|
|
59
|
+
try {
|
|
60
|
+
let e = new URL(t), n = e.pathname.split("/"), r = n[3], i = n.indexOf("o");
|
|
61
|
+
if (!r || i < 0 || !n[i + 1]) return t;
|
|
62
|
+
let a = decodeURIComponent(n.slice(i + 1).join("/")), o = a;
|
|
63
|
+
if (!a.includes("/images/thumbnails/")) {
|
|
64
|
+
let e = a.replace(/\/images\/([^/]+)$/, (e, t) => `/images/thumbnails/${String(t).replace(/\.[^.]+$/, "")}.jpg`);
|
|
65
|
+
if (e === a) return t;
|
|
66
|
+
o = e;
|
|
67
|
+
}
|
|
68
|
+
return `${e.origin}/v0/b/${r}/o/${encodeURIComponent(o)}?alt=media`;
|
|
69
|
+
} catch {
|
|
70
|
+
return t;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
55
73
|
function be(e, t, n = "") {
|
|
56
74
|
let r = t.replace(/\/$/, ""), i = e.trim();
|
|
57
75
|
return i ? /^https?:\/\//i.test(i) ? i : `${r}${i.startsWith("/") ? i : `/${i}`}` : n ? be(n, t) : r;
|
|
58
76
|
}
|
|
59
|
-
function xe(e, t, n) {
|
|
60
|
-
let r = e.trim() || "
|
|
61
|
-
return
|
|
77
|
+
function xe(e, t, n, r) {
|
|
78
|
+
let i = r?.trim().slice(0, 40), a = e.trim() || i || "사진", o = t?.trim(), s = n.trim() || "Board";
|
|
79
|
+
return o ? `${a} · ${o} | ${s}` : `${a} | ${s}`;
|
|
62
80
|
}
|
|
63
81
|
function Se(e) {
|
|
64
82
|
let t = [];
|
|
@@ -82,7 +100,7 @@ function Te(e) {
|
|
|
82
100
|
function Ee() {
|
|
83
101
|
return {
|
|
84
102
|
db: M(),
|
|
85
|
-
prefix:
|
|
103
|
+
prefix: G().collectionPrefix
|
|
86
104
|
};
|
|
87
105
|
}
|
|
88
106
|
function De(e) {
|
|
@@ -205,8 +223,8 @@ async function Ne(e, t) {
|
|
|
205
223
|
}
|
|
206
224
|
//#endregion
|
|
207
225
|
//#region src/utils/moderation-strike.ts
|
|
208
|
-
var Pe = 3,
|
|
209
|
-
function Fe(e, t, n = Date.now(), r =
|
|
226
|
+
var Pe = 3, K = 864e5;
|
|
227
|
+
function Fe(e, t, n = Date.now(), r = K) {
|
|
210
228
|
let i = Math.max(0, Math.floor(Number(e) || 0));
|
|
211
229
|
if (i <= 0) return 0;
|
|
212
230
|
let a = typeof t == "number" && Number.isFinite(t) ? t : null;
|
|
@@ -214,7 +232,7 @@ function Fe(e, t, n = Date.now(), r = G) {
|
|
|
214
232
|
let o = Math.floor((n - a) / r);
|
|
215
233
|
return Math.max(0, i - o);
|
|
216
234
|
}
|
|
217
|
-
function
|
|
235
|
+
function q(e, t, n = Date.now(), r = 3, i = K) {
|
|
218
236
|
let a = Math.max(0, Math.floor(Number(e) || 0));
|
|
219
237
|
if (a < r) return null;
|
|
220
238
|
let o = typeof t == "number" && Number.isFinite(t) ? t : null;
|
|
@@ -222,8 +240,8 @@ function K(e, t, n = Date.now(), r = 3, i = G) {
|
|
|
222
240
|
let s = o + (a - (r - 1)) * i;
|
|
223
241
|
return s > n ? s : null;
|
|
224
242
|
}
|
|
225
|
-
function Ie(e, t, n = Date.now(), r = 3, i =
|
|
226
|
-
return
|
|
243
|
+
function Ie(e, t, n = Date.now(), r = 3, i = K) {
|
|
244
|
+
return q(e, t, n, r, i) != null;
|
|
227
245
|
}
|
|
228
246
|
function Le(e, t = Date.now()) {
|
|
229
247
|
let n = Math.max(0, e - t), r = Math.ceil(n / 36e5);
|
|
@@ -244,26 +262,26 @@ function Re(e) {
|
|
|
244
262
|
}
|
|
245
263
|
//#endregion
|
|
246
264
|
//#region src/composables/useMemiBoardAuth.ts
|
|
247
|
-
var
|
|
248
|
-
function
|
|
265
|
+
var ze = "__MEMI_BOARD_AUTH_STATE__";
|
|
266
|
+
function Be() {
|
|
249
267
|
let e = globalThis;
|
|
250
|
-
return e[
|
|
268
|
+
return e[ze] || (e[ze] = {
|
|
251
269
|
role: r("user"),
|
|
252
270
|
rolePending: r(!0),
|
|
253
271
|
rawBlockCount: r(0),
|
|
254
272
|
blockAtMs: r(null),
|
|
255
273
|
watchStarted: !1
|
|
256
|
-
}), e[
|
|
274
|
+
}), e[ze];
|
|
257
275
|
}
|
|
258
276
|
function J() {
|
|
259
|
-
let t =
|
|
277
|
+
let t = G(), n = Be(), r = M(), i = j(), o = O(), { role: s, rolePending: c, rawBlockCount: u, blockAtMs: d } = n;
|
|
260
278
|
function p() {
|
|
261
279
|
let e = t.moderation?.blockBanThreshold;
|
|
262
280
|
return e === 0 ? 0 : typeof e == "number" && e > 0 ? Math.floor(e) : 3;
|
|
263
281
|
}
|
|
264
282
|
function h() {
|
|
265
283
|
let e = t.moderation?.blockBanDecayMs;
|
|
266
|
-
return typeof e == "number" && e > 0 ? e :
|
|
284
|
+
return typeof e == "number" && e > 0 ? e : K;
|
|
267
285
|
}
|
|
268
286
|
function g(e) {
|
|
269
287
|
return l(r, `${t.collectionPrefix}Users`, e);
|
|
@@ -324,14 +342,14 @@ function J() {
|
|
|
324
342
|
}), A = e(() => {
|
|
325
343
|
let e = p();
|
|
326
344
|
if (e <= 0) return null;
|
|
327
|
-
let t =
|
|
345
|
+
let t = q(u.value, d.value, Date.now(), e, h());
|
|
328
346
|
return t == null ? null : Le(t);
|
|
329
|
-
}),
|
|
347
|
+
}), B = e(() => {
|
|
330
348
|
if (!k.value) return null;
|
|
331
349
|
let e = A.value;
|
|
332
350
|
return e ? `콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요. (${e})` : "콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요.";
|
|
333
351
|
});
|
|
334
|
-
async function
|
|
352
|
+
async function V() {
|
|
335
353
|
let e = o.value?.uid, t = p();
|
|
336
354
|
if (!e || t <= 0) return {
|
|
337
355
|
effectiveCount: 0,
|
|
@@ -354,7 +372,7 @@ function J() {
|
|
|
354
372
|
u.value = i, d.value = a;
|
|
355
373
|
let s = Fe(i, a, a, h()), c = Ie(i, a, a, t, h()), l = ` (경고 ${s}/${t})`;
|
|
356
374
|
if (c) {
|
|
357
|
-
let e =
|
|
375
|
+
let e = q(i, a, a, t, h()), n = e == null ? null : Le(e, a);
|
|
358
376
|
l = n ? ` 경고 ${s}회가 되어 글·댓글 작성이 제한됩니다 (${n}).` : ` 경고 ${s}회가 되어 글·댓글 작성이 제한됩니다.`;
|
|
359
377
|
}
|
|
360
378
|
return {
|
|
@@ -363,34 +381,34 @@ function J() {
|
|
|
363
381
|
messageSuffix: l
|
|
364
382
|
};
|
|
365
383
|
}
|
|
366
|
-
function
|
|
384
|
+
function H() {
|
|
367
385
|
if (!i) throw Error("[memi-board] Firebase Auth가 초기화되지 않았습니다.");
|
|
368
386
|
return i;
|
|
369
387
|
}
|
|
370
|
-
async function
|
|
371
|
-
return L(
|
|
388
|
+
async function ee() {
|
|
389
|
+
return L(H(), new N());
|
|
372
390
|
}
|
|
373
|
-
async function
|
|
391
|
+
async function U() {
|
|
374
392
|
let e = new P("apple.com");
|
|
375
|
-
return e.addScope("email"), e.addScope("name"), L(
|
|
393
|
+
return e.addScope("email"), e.addScope("name"), L(H(), e);
|
|
376
394
|
}
|
|
377
|
-
async function
|
|
378
|
-
return I(
|
|
395
|
+
async function te(e, t) {
|
|
396
|
+
return I(H(), e, t);
|
|
379
397
|
}
|
|
380
|
-
async function
|
|
381
|
-
let r = await F(
|
|
382
|
-
return n && await
|
|
398
|
+
async function ne(e, t, n) {
|
|
399
|
+
let r = await F(H(), e, t);
|
|
400
|
+
return n && await z(r.user, { displayName: n }), r;
|
|
383
401
|
}
|
|
384
|
-
async function
|
|
385
|
-
await R(
|
|
402
|
+
async function re() {
|
|
403
|
+
await R(H());
|
|
386
404
|
}
|
|
387
|
-
function
|
|
405
|
+
function W(e) {
|
|
388
406
|
return b.value && (o.value?.uid === e.authorUid || E.value);
|
|
389
407
|
}
|
|
390
|
-
function
|
|
408
|
+
function ie(e) {
|
|
391
409
|
return b.value && o.value?.uid === e.authorUid;
|
|
392
410
|
}
|
|
393
|
-
function
|
|
411
|
+
function ae(e) {
|
|
394
412
|
return b.value && (o.value?.uid === e.authorUid || E.value);
|
|
395
413
|
}
|
|
396
414
|
return {
|
|
@@ -403,31 +421,31 @@ function J() {
|
|
|
403
421
|
moderationBlockCount: D,
|
|
404
422
|
isWriteRestricted: k,
|
|
405
423
|
restrictedUntilLabel: A,
|
|
406
|
-
restrictedMessage:
|
|
407
|
-
signInWithGoogle:
|
|
408
|
-
signInWithApple:
|
|
409
|
-
signInWithEmail:
|
|
410
|
-
signUpWithEmail:
|
|
411
|
-
signOut:
|
|
412
|
-
canEdit:
|
|
413
|
-
canDelete:
|
|
414
|
-
canEditComment:
|
|
415
|
-
canDeleteComment:
|
|
416
|
-
recordContentModerationBlock:
|
|
424
|
+
restrictedMessage: B,
|
|
425
|
+
signInWithGoogle: ee,
|
|
426
|
+
signInWithApple: U,
|
|
427
|
+
signInWithEmail: te,
|
|
428
|
+
signUpWithEmail: ne,
|
|
429
|
+
signOut: re,
|
|
430
|
+
canEdit: W,
|
|
431
|
+
canDelete: W,
|
|
432
|
+
canEditComment: ie,
|
|
433
|
+
canDeleteComment: ae,
|
|
434
|
+
recordContentModerationBlock: V,
|
|
417
435
|
refreshBoardUser: y
|
|
418
436
|
};
|
|
419
437
|
}
|
|
420
438
|
//#endregion
|
|
421
439
|
//#region src/utils/slugify.ts
|
|
422
|
-
function
|
|
440
|
+
function Ve(e) {
|
|
423
441
|
return e.trim().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9가-힣ㄱ-ㅎㅏ-ㅣ-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
424
442
|
}
|
|
425
|
-
function
|
|
443
|
+
function He(e, t = 60) {
|
|
426
444
|
return e.replace(/[^a-zA-Z0-9가-힣ㄱ-ㅎ._-]/g, "-").slice(0, t);
|
|
427
445
|
}
|
|
428
446
|
//#endregion
|
|
429
447
|
//#region src/utils/extractEditorImageUrls.ts
|
|
430
|
-
function
|
|
448
|
+
function Ue(e) {
|
|
431
449
|
if (!e) return [];
|
|
432
450
|
let t = /* @__PURE__ */ new Set();
|
|
433
451
|
for (let n of e.matchAll(/!\[[^\]]*]\((https?:\/\/[^)\s]+)\)/g)) n[1] && t.add(n[1].replaceAll("&", "&"));
|
|
@@ -436,7 +454,7 @@ function He(e) {
|
|
|
436
454
|
}
|
|
437
455
|
//#endregion
|
|
438
456
|
//#region src/utils/storagePath.ts
|
|
439
|
-
function
|
|
457
|
+
function We(e) {
|
|
440
458
|
try {
|
|
441
459
|
let t = e.match(/\/o\/([^?]+)/);
|
|
442
460
|
return t?.[1] ? decodeURIComponent(t[1]) : null;
|
|
@@ -444,29 +462,50 @@ function Ue(e) {
|
|
|
444
462
|
return null;
|
|
445
463
|
}
|
|
446
464
|
}
|
|
447
|
-
function
|
|
465
|
+
function Ge(e) {
|
|
448
466
|
return e.match(/(?:^|\/)posts\/([^/]+)\//)?.[1] ?? null;
|
|
449
467
|
}
|
|
450
468
|
//#endregion
|
|
469
|
+
//#region src/utils/postBody.ts
|
|
470
|
+
function Ke(e) {
|
|
471
|
+
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();
|
|
472
|
+
}
|
|
473
|
+
function qe(e, t = 40) {
|
|
474
|
+
let n = Ke(e);
|
|
475
|
+
if (!n) return "";
|
|
476
|
+
if (n.length <= t) return n;
|
|
477
|
+
let r = n.slice(0, t), i = r.lastIndexOf(" ");
|
|
478
|
+
return `${(i >= Math.floor(t * .6) ? r.slice(0, i) : r).trimEnd()}…`;
|
|
479
|
+
}
|
|
480
|
+
function Je(e) {
|
|
481
|
+
return Ke(e).length > 0;
|
|
482
|
+
}
|
|
483
|
+
function Ye(e, t) {
|
|
484
|
+
return /<img\b/i.test(String(e || "")) ? !0 : (t || []).some((e) => !!e?.type?.startsWith("image/"));
|
|
485
|
+
}
|
|
486
|
+
function Xe(e) {
|
|
487
|
+
return !Je(e);
|
|
488
|
+
}
|
|
489
|
+
//#endregion
|
|
451
490
|
//#region src/utils/postPreview.ts
|
|
452
|
-
function
|
|
491
|
+
function Ze(e) {
|
|
453
492
|
return e.replace(/&/g, "&").replace(/"/g, "\"").replace(/'/g, "'");
|
|
454
493
|
}
|
|
455
|
-
function
|
|
494
|
+
function Qe(e) {
|
|
456
495
|
if (e) try {
|
|
457
|
-
let t = new URL(
|
|
496
|
+
let t = new URL(Ze(e)), n = t.hostname.toLowerCase().replace(/^www\./, "");
|
|
458
497
|
if (n === "youtu.be") return t.pathname.split("/").filter(Boolean)[0];
|
|
459
498
|
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;
|
|
460
499
|
} catch {
|
|
461
500
|
return;
|
|
462
501
|
}
|
|
463
502
|
}
|
|
464
|
-
function
|
|
465
|
-
let t =
|
|
503
|
+
function $e(e) {
|
|
504
|
+
let t = Qe(e.videoUrl);
|
|
466
505
|
return t ? `https://i.ytimg.com/vi/${t}/hqdefault.jpg` : e.previewImage || e.attachments?.find((e) => e.type.startsWith("image/"))?.url;
|
|
467
506
|
}
|
|
468
|
-
function
|
|
469
|
-
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 ?
|
|
507
|
+
function et(e, t = []) {
|
|
508
|
+
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 ? Ze(r[1]) : a, s = o ? Qe(o) : void 0, c = n ? Ze(n[1]) : i || (s ? `https://i.ytimg.com/vi/${s}/hqdefault.jpg` : void 0);
|
|
470
509
|
return {
|
|
471
510
|
summary: e.replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/&/g, "&").replace(/\s+/g, " ").trim().slice(0, 160),
|
|
472
511
|
...c ? { previewImage: c } : {},
|
|
@@ -475,7 +514,7 @@ function Je(e, t = []) {
|
|
|
475
514
|
}
|
|
476
515
|
//#endregion
|
|
477
516
|
//#region src/composables/useMemiBoardSettings.ts
|
|
478
|
-
var
|
|
517
|
+
var tt = [
|
|
479
518
|
{
|
|
480
519
|
id: "free",
|
|
481
520
|
label: "자유",
|
|
@@ -504,16 +543,16 @@ var Ye = [
|
|
|
504
543
|
order: 2
|
|
505
544
|
}
|
|
506
545
|
];
|
|
507
|
-
function
|
|
546
|
+
function nt(e) {
|
|
508
547
|
return e === "hidden" ? "hidden" : "public";
|
|
509
548
|
}
|
|
510
|
-
function
|
|
511
|
-
let t =
|
|
549
|
+
function rt() {
|
|
550
|
+
let t = G(), n = M(), { isSignedIn: r } = J(), i = e(() => l(n, `${t.collectionPrefix}Settings`, "config")), a = e(() => o(n, `${t.collectionPrefix}Settings`, "config", "categories")), c = e(() => y(a.value, v("order", "asc"))), { data: u, pending: d } = D(c, { ssrKey: `${t.collectionPrefix}Settings/config/categories` }), f = e(() => u.value.map((e, t) => ({
|
|
512
551
|
...e,
|
|
513
552
|
id: e.id,
|
|
514
553
|
order: typeof e.order == "number" ? e.order : t,
|
|
515
554
|
description: e.description ?? e.desc ?? "",
|
|
516
|
-
visibility:
|
|
555
|
+
visibility: nt(e.visibility),
|
|
517
556
|
listView: e.listView ?? "default",
|
|
518
557
|
writeRole: e.writeRole ?? "user",
|
|
519
558
|
commentWriteRole: e.commentWriteRole ?? "user",
|
|
@@ -545,7 +584,7 @@ function Ze() {
|
|
|
545
584
|
if (!r.value) throw Error("로그인이 필요합니다.");
|
|
546
585
|
let n = e.id.trim(), o = e.label.trim();
|
|
547
586
|
if (!n || !o) throw Error("카테고리 ID와 이름을 입력해 주세요.");
|
|
548
|
-
let s =
|
|
587
|
+
let s = nt(e.visibility), c = f.value.find((e) => e.id === n)?.visibility ?? "public";
|
|
549
588
|
await S(i.value, { updatedAt: x() }, { merge: !0 }), await S(l(a.value, n), {
|
|
550
589
|
label: o,
|
|
551
590
|
description: (e.description ?? "").trim(),
|
|
@@ -566,7 +605,7 @@ function Ze() {
|
|
|
566
605
|
e.forEach((e, n) => {
|
|
567
606
|
let r = e.id.trim(), i = e.label.trim();
|
|
568
607
|
if (!r || !i) throw Error("카테고리 ID와 이름을 입력해 주세요.");
|
|
569
|
-
let c =
|
|
608
|
+
let c = nt(e.visibility);
|
|
570
609
|
o.set(l(a.value, r), {
|
|
571
610
|
label: i,
|
|
572
611
|
description: (e.description ?? "").trim(),
|
|
@@ -597,7 +636,7 @@ function Ze() {
|
|
|
597
636
|
if (!r.value) throw Error("로그인이 필요합니다.");
|
|
598
637
|
let n = f.value.find((e) => e.label === t);
|
|
599
638
|
if (n) return n.id;
|
|
600
|
-
let i =
|
|
639
|
+
let i = Ve(t) || `cat-${Date.now()}`, a = i, o = 2, s = new Set(f.value.map((e) => e.id));
|
|
601
640
|
for (; s.has(a);) a = `${i}-${o++}`;
|
|
602
641
|
return await k({
|
|
603
642
|
id: a,
|
|
@@ -622,39 +661,51 @@ function Ze() {
|
|
|
622
661
|
saveCategory: k,
|
|
623
662
|
saveCategories: A,
|
|
624
663
|
deleteCategory: j,
|
|
625
|
-
DEFAULT_CATEGORIES:
|
|
664
|
+
DEFAULT_CATEGORIES: tt
|
|
626
665
|
};
|
|
627
666
|
}
|
|
628
667
|
//#endregion
|
|
629
668
|
//#region src/composables/useMemiBoardPosts.ts
|
|
630
|
-
async function
|
|
631
|
-
let t = await
|
|
669
|
+
async function it(e) {
|
|
670
|
+
let t = await ee(e).catch(() => ({
|
|
632
671
|
items: [],
|
|
633
672
|
prefixes: []
|
|
634
673
|
}));
|
|
635
|
-
await Promise.all([...t.items.map((e) =>
|
|
674
|
+
await Promise.all([...t.items.map((e) => B(e)), ...t.prefixes.map((e) => it(e))]);
|
|
636
675
|
}
|
|
637
|
-
async function
|
|
676
|
+
async function at(e, t) {
|
|
638
677
|
let n = [...new Set([...t].filter(Boolean))];
|
|
639
|
-
await Promise.all(n.map((t) =>
|
|
678
|
+
await Promise.all(n.map((t) => B(U(e, t)).catch(() => {})));
|
|
640
679
|
}
|
|
641
|
-
function
|
|
680
|
+
function ot(e) {
|
|
642
681
|
if (!e || typeof e != "object") return !1;
|
|
643
682
|
let t = "code" in e ? String(e.code) : "";
|
|
644
683
|
return t === "permission-denied" || t === "firestore/permission-denied";
|
|
645
684
|
}
|
|
646
|
-
function
|
|
647
|
-
let e =
|
|
648
|
-
function
|
|
685
|
+
function st() {
|
|
686
|
+
let e = G(), t = M(), n = A(), { isCategoryHidden: r, categories: i } = rt(), a = () => e.collectionPrefix, u = () => o(t, `${a()}Posts`), m = (e) => l(t, `${a()}Posts`, e), _ = (e) => l(t, `${a()}Posts`, e, "body", "main"), b = (e) => o(t, `${a()}Posts`, e, "comments");
|
|
687
|
+
function D(e) {
|
|
649
688
|
return !r(e);
|
|
650
689
|
}
|
|
651
|
-
function
|
|
652
|
-
return
|
|
690
|
+
function O(e) {
|
|
691
|
+
return e ? i.value.find((t) => t.id === e)?.listView === "image" : !1;
|
|
653
692
|
}
|
|
654
|
-
|
|
693
|
+
function k(e) {
|
|
694
|
+
let t = O(e.category);
|
|
695
|
+
if (!Je(e.content)) throw Error(t ? "내용을 입력해 주세요." : "본문에 글자를 입력해 주세요. 이미지나 첨부만으로는 게시할 수 없습니다.");
|
|
696
|
+
if (t) {
|
|
697
|
+
if (!Ye(e.content, e.attachments)) throw Error("사진을 올려 주세요.");
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
if (!e.title?.trim()) throw Error("제목을 입력해 주세요.");
|
|
701
|
+
}
|
|
702
|
+
function j() {
|
|
703
|
+
return l(u()).id;
|
|
704
|
+
}
|
|
705
|
+
async function N(e = {}) {
|
|
655
706
|
let t = e.pageSize ?? 20, n = [];
|
|
656
707
|
e.category && n.push(T("category", "==", e.category)), e.publicOnly !== !1 && n.push(T("listed", "==", !0)), n.push(v("createdAt", "desc")), e.cursor && n.push(C(e.cursor)), n.push(h(t + 1));
|
|
657
|
-
let r = await p(y(
|
|
708
|
+
let r = await p(y(u(), ...n)), i = r.docs.slice(0, t);
|
|
658
709
|
return {
|
|
659
710
|
posts: i.map((e) => ({
|
|
660
711
|
id: e.id,
|
|
@@ -664,38 +715,38 @@ function tt() {
|
|
|
664
715
|
hasMore: r.docs.length > t
|
|
665
716
|
};
|
|
666
717
|
}
|
|
667
|
-
async function
|
|
668
|
-
let [t, n] = await Promise.all([f(
|
|
718
|
+
async function P(e) {
|
|
719
|
+
let [t, n] = await Promise.all([f(m(e)), f(_(e))]);
|
|
669
720
|
return t.exists() ? {
|
|
670
721
|
id: t.id,
|
|
671
722
|
...t.data(),
|
|
672
723
|
content: n.exists() ? n.data().content ?? "" : ""
|
|
673
724
|
} : null;
|
|
674
725
|
}
|
|
675
|
-
async function
|
|
726
|
+
async function F(e, t) {
|
|
676
727
|
try {
|
|
677
|
-
let n = (await p(y(
|
|
728
|
+
let n = (await p(y(u(), T("category", "==", e), T("slug", "==", t), h(1)))).docs[0];
|
|
678
729
|
if (!n) return { status: "not-found" };
|
|
679
730
|
try {
|
|
680
|
-
let e = await
|
|
731
|
+
let e = await P(n.id);
|
|
681
732
|
return e ? {
|
|
682
733
|
status: "ok",
|
|
683
734
|
post: e
|
|
684
735
|
} : { status: "not-found" };
|
|
685
736
|
} catch (e) {
|
|
686
|
-
return
|
|
737
|
+
return ot(e) ? { status: "permission-denied" } : {
|
|
687
738
|
status: "error",
|
|
688
739
|
message: e instanceof Error ? e.message : String(e)
|
|
689
740
|
};
|
|
690
741
|
}
|
|
691
742
|
} catch (e) {
|
|
692
|
-
return
|
|
743
|
+
return ot(e) ? { status: "permission-denied" } : {
|
|
693
744
|
status: "error",
|
|
694
745
|
message: e instanceof Error ? e.message : String(e)
|
|
695
746
|
};
|
|
696
747
|
}
|
|
697
748
|
}
|
|
698
|
-
async function
|
|
749
|
+
async function I(e) {
|
|
699
750
|
if (!e.category || !e.createdAt) return {
|
|
700
751
|
previous: null,
|
|
701
752
|
next: null
|
|
@@ -705,7 +756,7 @@ function tt() {
|
|
|
705
756
|
T("category", "==", e.category),
|
|
706
757
|
...e.listed === !1 ? [] : [T("listed", "==", !0)],
|
|
707
758
|
v("createdAt", "desc")
|
|
708
|
-
], [n, r] = await Promise.all([p(y(
|
|
759
|
+
], [n, r] = await Promise.all([p(y(u(), ...t, C(e.createdAt), h(1))), p(y(u(), ...t, d(e.createdAt), g(1)))]), i = (e) => {
|
|
709
760
|
let t = e.docs[0];
|
|
710
761
|
return t ? {
|
|
711
762
|
id: t.id,
|
|
@@ -723,21 +774,29 @@ function tt() {
|
|
|
723
774
|
};
|
|
724
775
|
}
|
|
725
776
|
}
|
|
726
|
-
async function
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
777
|
+
async function L(e) {
|
|
778
|
+
k(e);
|
|
779
|
+
let t = e.postId || j(), n = O(e.category), r, i;
|
|
780
|
+
if (n) r = t, i = qe(e.content);
|
|
781
|
+
else {
|
|
782
|
+
i = e.title.trim();
|
|
783
|
+
let t = Ve(i) || "post";
|
|
784
|
+
r = t;
|
|
785
|
+
let n = 1;
|
|
786
|
+
for (; !(await p(y(u(), T("category", "==", e.category || ""), T("slug", "==", r), h(1)))).empty;) n++, r = `${t}-${n}`;
|
|
787
|
+
}
|
|
788
|
+
let a = et(e.content, e.attachments), o = D(e.category);
|
|
789
|
+
return await S(m(t), {
|
|
790
|
+
slug: r,
|
|
791
|
+
title: i,
|
|
792
|
+
...a,
|
|
734
793
|
tags: e.tags ?? [],
|
|
735
794
|
...e.category ? { category: e.category } : {},
|
|
736
795
|
attachments: e.attachments ?? [],
|
|
737
796
|
commentCount: 0,
|
|
738
797
|
likeCount: 0,
|
|
739
798
|
viewCount: 0,
|
|
740
|
-
listed:
|
|
799
|
+
listed: o,
|
|
741
800
|
authorUid: e.authorUid,
|
|
742
801
|
authorName: e.authorName,
|
|
743
802
|
authorPhoto: e.authorPhoto,
|
|
@@ -745,65 +804,66 @@ function tt() {
|
|
|
745
804
|
moderationModel: e.moderationModel ?? null,
|
|
746
805
|
createdAt: x(),
|
|
747
806
|
updatedAt: x()
|
|
748
|
-
}), await S(
|
|
807
|
+
}), await S(_(t), { content: e.content }), r;
|
|
749
808
|
}
|
|
750
|
-
async function
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
809
|
+
async function R(e, t) {
|
|
810
|
+
k(t);
|
|
811
|
+
let n = O(t.category) ? qe(t.content) : t.title.trim(), r = et(t.content, t.attachments), i = D(t.category);
|
|
812
|
+
await Promise.all([w(m(e), {
|
|
813
|
+
title: n,
|
|
814
|
+
...r,
|
|
815
|
+
previewImage: r.previewImage ? r.previewImage : c(),
|
|
816
|
+
videoUrl: r.videoUrl ? r.videoUrl : c(),
|
|
757
817
|
tags: t.tags ?? [],
|
|
758
818
|
category: t.category ? t.category : c(),
|
|
759
|
-
listed:
|
|
819
|
+
listed: i,
|
|
760
820
|
attachments: t.attachments ?? [],
|
|
761
821
|
updatedAt: x()
|
|
762
|
-
}), S(
|
|
822
|
+
}), S(_(e), { content: t.content }, { merge: !0 })]);
|
|
763
823
|
}
|
|
764
|
-
async function
|
|
765
|
-
let [r,
|
|
824
|
+
async function z(e) {
|
|
825
|
+
let [r, i] = await Promise.all([f(m(e)), f(_(e))]);
|
|
766
826
|
if (!r.exists()) return;
|
|
767
|
-
let o = r.data(), c =
|
|
827
|
+
let o = r.data(), c = i.exists() ? String(i.data().content ?? "") : "", l = await p(b(e));
|
|
768
828
|
for (let e = 0; e < l.docs.length; e += 450) {
|
|
769
829
|
let n = E(t);
|
|
770
830
|
l.docs.slice(e, e + 450).forEach((e) => n.delete(e.ref)), await n.commit();
|
|
771
831
|
}
|
|
772
|
-
let
|
|
832
|
+
let u = H(n), d = /* @__PURE__ */ new Set(), h = /* @__PURE__ */ new Set();
|
|
773
833
|
for (let t of o.attachments ?? []) if (t?.path) {
|
|
774
|
-
|
|
775
|
-
let n =
|
|
776
|
-
n && n !== e &&
|
|
834
|
+
d.add(t.path);
|
|
835
|
+
let n = Ge(t.path);
|
|
836
|
+
n && n !== e && h.add(n);
|
|
777
837
|
}
|
|
778
|
-
for (let t of
|
|
779
|
-
let n =
|
|
838
|
+
for (let t of Ue(c)) {
|
|
839
|
+
let n = We(t);
|
|
780
840
|
if (!n) continue;
|
|
781
|
-
|
|
841
|
+
d.add(n);
|
|
782
842
|
let r = n.replace(/\/images\/([^/]+)$/, "/images/thumbnails/$1").replace(/\.[^.]+$/, ".jpg");
|
|
783
|
-
r !== n && r.includes("/images/thumbnails/") &&
|
|
843
|
+
r !== n && r.includes("/images/thumbnails/") && d.add(r);
|
|
784
844
|
let i = n.match(/^(.*\/images\/)([^/]+)\.[^.]+$/);
|
|
785
|
-
i && !n.includes("/thumbnails/") &&
|
|
786
|
-
let a =
|
|
787
|
-
a && a !== e &&
|
|
845
|
+
i && !n.includes("/thumbnails/") && d.add(`${i[1]}thumbnails/${i[2]}.jpg`);
|
|
846
|
+
let a = Ge(n);
|
|
847
|
+
a && a !== e && h.add(a);
|
|
788
848
|
}
|
|
789
|
-
await s(
|
|
790
|
-
for (let e of
|
|
791
|
-
await
|
|
849
|
+
await s(_(e)), await it(U(u, `${a()}/posts/${e}`));
|
|
850
|
+
for (let e of h) await it(U(u, `${a()}/posts/${e}`));
|
|
851
|
+
await at(u, d), await s(m(e));
|
|
792
852
|
}
|
|
793
853
|
return {
|
|
794
|
-
createPostId:
|
|
795
|
-
getPosts:
|
|
796
|
-
getPost:
|
|
797
|
-
getPostBySlug:
|
|
798
|
-
getAdjacentPosts:
|
|
799
|
-
createPost:
|
|
800
|
-
updatePost:
|
|
801
|
-
deletePost:
|
|
854
|
+
createPostId: j,
|
|
855
|
+
getPosts: N,
|
|
856
|
+
getPost: P,
|
|
857
|
+
getPostBySlug: F,
|
|
858
|
+
getAdjacentPosts: I,
|
|
859
|
+
createPost: L,
|
|
860
|
+
updatePost: R,
|
|
861
|
+
deletePost: z
|
|
802
862
|
};
|
|
803
863
|
}
|
|
804
|
-
var
|
|
805
|
-
function
|
|
806
|
-
let s =
|
|
864
|
+
var ct = 10;
|
|
865
|
+
function lt(n, i = {}) {
|
|
866
|
+
let s = G(), c = M(), { isAdmin: l, isStaff: d } = J(), f = () => s.collectionPrefix, m = () => o(c, `${f()}Posts`), g = i.pageSize ?? ct, b = e(() => typeof n == "string" || n === void 0 ? n : n.value), x = e(() => i.publicOnly === void 0 ? !(l.value || d.value) : typeof i.publicOnly == "boolean" ? i.publicOnly : i.publicOnly.value), S = r([]), w = r([]), E = r(!0), D = r(!0), O = r(!1), k = r(""), A = null, j = null, N;
|
|
807
867
|
function P(e) {
|
|
808
868
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
809
869
|
}
|
|
@@ -848,10 +908,10 @@ function rt(n, i = {}) {
|
|
|
848
908
|
}
|
|
849
909
|
}
|
|
850
910
|
}
|
|
851
|
-
function
|
|
911
|
+
function z() {
|
|
852
912
|
N?.(), S.value = [], w.value = [], A = null, j = null, D.value = !0, k.value = "", E.value = !0, L();
|
|
853
913
|
}
|
|
854
|
-
return a([b, x],
|
|
914
|
+
return a([b, x], z, { immediate: !0 }), t(() => N?.()), {
|
|
855
915
|
posts: F,
|
|
856
916
|
postsPending: E,
|
|
857
917
|
hasMore: D,
|
|
@@ -860,8 +920,8 @@ function rt(n, i = {}) {
|
|
|
860
920
|
loadMore: R
|
|
861
921
|
};
|
|
862
922
|
}
|
|
863
|
-
function
|
|
864
|
-
let n =
|
|
923
|
+
function ut(t) {
|
|
924
|
+
let n = G(), r = M(), i = () => n.collectionPrefix, a = e(() => typeof t == "string" ? t : t.value), o = e(() => l(r, `${i()}Posts`, a.value)), s = e(() => l(r, `${i()}Posts`, a.value, "body", "main")), { data: c, pending: u } = k(o), { data: d, pending: f } = k(s);
|
|
865
925
|
return {
|
|
866
926
|
post: e(() => c.value ? {
|
|
867
927
|
...c.value,
|
|
@@ -873,15 +933,15 @@ function it(t) {
|
|
|
873
933
|
}
|
|
874
934
|
//#endregion
|
|
875
935
|
//#region src/composables/useMemiBoardComments.ts
|
|
876
|
-
var
|
|
877
|
-
function
|
|
936
|
+
var dt = 10, ft = 5, pt = 1e3;
|
|
937
|
+
function mt(e) {
|
|
878
938
|
let t = e.trim();
|
|
879
939
|
if (!t) throw Error("댓글 내용을 입력해 주세요.");
|
|
880
|
-
if (t.length > 1e3) throw Error(`댓글은 ${
|
|
940
|
+
if (t.length > 1e3) throw Error(`댓글은 ${pt.toLocaleString()}자까지 작성할 수 있습니다.`);
|
|
881
941
|
return t;
|
|
882
942
|
}
|
|
883
|
-
function
|
|
884
|
-
let s =
|
|
943
|
+
function ht(n, i = {}) {
|
|
944
|
+
let s = G(), c = M(), d = () => s.collectionPrefix, f = e(() => typeof n == "string" ? n : n.value), g = r([]), b = r([]), S = r(i.subscribe !== !1), D = r([]), O = r(!1), k = r(i.subscribe !== !1), A = r(i.subscribe !== !1), j = null, N, P;
|
|
885
945
|
function F(e) {
|
|
886
946
|
return e.createdAt?.toMillis?.() ?? 0;
|
|
887
947
|
}
|
|
@@ -934,11 +994,11 @@ function lt(n, i = {}) {
|
|
|
934
994
|
v(u(), "asc"),
|
|
935
995
|
...j ? [C(j)] : [],
|
|
936
996
|
h(11)
|
|
937
|
-
], t = await p(y(o(c, `${d()}Posts`, f.value, "comments"), ...e)), n = t.docs.slice(0,
|
|
997
|
+
], t = await p(y(o(c, `${d()}Posts`, f.value, "comments"), ...e)), n = t.docs.slice(0, dt), r = n.map((e) => ({
|
|
938
998
|
id: e.id,
|
|
939
999
|
...e.data()
|
|
940
1000
|
})), i = new Set(D.value.map((e) => e.id));
|
|
941
|
-
D.value.push(...r.filter((e) => !i.has(e.id))), j = n.at(-1) ?? j, t.size <=
|
|
1001
|
+
D.value.push(...r.filter((e) => !i.has(e.id))), j = n.at(-1) ?? j, t.size <= dt && (k.value = !1, N = setTimeout(() => {
|
|
942
1002
|
N = void 0, L();
|
|
943
1003
|
}, 1e3));
|
|
944
1004
|
} finally {
|
|
@@ -949,8 +1009,8 @@ function lt(n, i = {}) {
|
|
|
949
1009
|
t(() => {
|
|
950
1010
|
clearTimeout(N), P?.();
|
|
951
1011
|
});
|
|
952
|
-
async function
|
|
953
|
-
let t =
|
|
1012
|
+
async function z(e) {
|
|
1013
|
+
let t = mt(e.body), n = E(c), r = d(), i = l(o(c, `${r}Posts`, f.value, "comments"));
|
|
954
1014
|
n.set(i, {
|
|
955
1015
|
postId: f.value,
|
|
956
1016
|
body: t,
|
|
@@ -969,8 +1029,8 @@ function lt(n, i = {}) {
|
|
|
969
1029
|
updatedAt: x()
|
|
970
1030
|
}), n.update(l(c, `${r}Posts`, f.value), { commentCount: m(1) }), await n.commit();
|
|
971
1031
|
}
|
|
972
|
-
async function
|
|
973
|
-
let t =
|
|
1032
|
+
async function B(e) {
|
|
1033
|
+
let t = mt(e.body);
|
|
974
1034
|
if (!e.parent.id) throw Error("답글 대상 댓글을 찾을 수 없습니다.");
|
|
975
1035
|
let n = E(c), r = d(), i = o(c, `${r}Posts`, f.value, "comments"), a = l(i), s = e.parent.parentId == null ? e.parent.id : e.parent.rootId;
|
|
976
1036
|
if (!s) throw Error("댓글 스레드를 찾을 수 없습니다.");
|
|
@@ -993,20 +1053,20 @@ function lt(n, i = {}) {
|
|
|
993
1053
|
updatedAt: x()
|
|
994
1054
|
}), n.update(l(i, s), { replyCount: m(1) }), n.update(l(c, `${r}Posts`, f.value), { commentCount: m(1) }), await n.commit();
|
|
995
1055
|
}
|
|
996
|
-
async function
|
|
1056
|
+
async function V(e) {
|
|
997
1057
|
if (!e.id) return;
|
|
998
1058
|
if (e.parentId == null && (e.replyCount ?? 0) > 0) throw Error("답글이 있는 댓글은 현재 삭제할 수 없습니다.");
|
|
999
1059
|
let t = E(c), n = d();
|
|
1000
1060
|
t.delete(l(c, `${n}Posts`, f.value, "comments", e.id)), e.parentId && e.rootId && t.update(l(c, `${n}Posts`, f.value, "comments", e.rootId), { replyCount: m(-1) }), t.update(l(c, `${n}Posts`, f.value), { commentCount: m(-1) }), await t.commit(), D.value = D.value.filter((t) => t.id !== e.id), b.value = b.value.filter((t) => t.id !== e.id), g.value = g.value.filter((t) => t.id !== e.id);
|
|
1001
1061
|
}
|
|
1002
|
-
async function
|
|
1003
|
-
let n =
|
|
1062
|
+
async function H(e, t) {
|
|
1063
|
+
let n = mt(t);
|
|
1004
1064
|
await w(l(c, `${d()}Posts`, f.value, "comments", e), {
|
|
1005
1065
|
body: n,
|
|
1006
1066
|
updatedAt: x()
|
|
1007
1067
|
});
|
|
1008
1068
|
}
|
|
1009
|
-
async function
|
|
1069
|
+
async function ee(e, t, n) {
|
|
1010
1070
|
await w(l(c, `${d()}Posts`, f.value, "comments", e), {
|
|
1011
1071
|
isBlinded: t,
|
|
1012
1072
|
moderatedAt: x(),
|
|
@@ -1019,15 +1079,15 @@ function lt(n, i = {}) {
|
|
|
1019
1079
|
hasMore: k,
|
|
1020
1080
|
loadingMore: O,
|
|
1021
1081
|
loadMore: R,
|
|
1022
|
-
addComment:
|
|
1023
|
-
addReply:
|
|
1024
|
-
updateComment:
|
|
1025
|
-
setCommentBlinded:
|
|
1026
|
-
deleteComment:
|
|
1082
|
+
addComment: z,
|
|
1083
|
+
addReply: B,
|
|
1084
|
+
updateComment: H,
|
|
1085
|
+
setCommentBlinded: ee,
|
|
1086
|
+
deleteComment: V
|
|
1027
1087
|
};
|
|
1028
1088
|
}
|
|
1029
|
-
function
|
|
1030
|
-
let i =
|
|
1089
|
+
function gt(e, n) {
|
|
1090
|
+
let i = G(), a = M(), s = r([]), c = r(!1), l = r(!1), d = r(!0), f = null, m;
|
|
1031
1091
|
function g(e) {
|
|
1032
1092
|
return e.sort((e, t) => (e.createdAt?.toMillis?.() ?? 0) - (t.createdAt?.toMillis?.() ?? 0) || String(e.id).localeCompare(String(t.id)));
|
|
1033
1093
|
}
|
|
@@ -1054,11 +1114,11 @@ function ut(e, n) {
|
|
|
1054
1114
|
v(u(), "asc"),
|
|
1055
1115
|
...f ? [C(f)] : [],
|
|
1056
1116
|
h(6)
|
|
1057
|
-
], r = await p(y(o(a, `${i.collectionPrefix}Posts`, e, "comments"), ...t)), c = new Set(s.value.map((e) => e.id)), m = r.docs.slice(0,
|
|
1117
|
+
], r = await p(y(o(a, `${i.collectionPrefix}Posts`, e, "comments"), ...t)), c = new Set(s.value.map((e) => e.id)), m = r.docs.slice(0, ft), _ = m.map((e) => ({
|
|
1058
1118
|
id: e.id,
|
|
1059
1119
|
...e.data()
|
|
1060
1120
|
})).filter((e) => !c.has(e.id));
|
|
1061
|
-
s.value = g([...s.value, ..._]), f = m.at(-1) ?? f, l.value = !0, r.size <=
|
|
1121
|
+
s.value = g([...s.value, ..._]), f = m.at(-1) ?? f, l.value = !0, r.size <= ft && (d.value = !1, b());
|
|
1062
1122
|
} finally {
|
|
1063
1123
|
c.value = !1;
|
|
1064
1124
|
}
|
|
@@ -1081,8 +1141,8 @@ function ut(e, n) {
|
|
|
1081
1141
|
}
|
|
1082
1142
|
//#endregion
|
|
1083
1143
|
//#region src/composables/useMemiBoardLikes.ts
|
|
1084
|
-
function
|
|
1085
|
-
let n =
|
|
1144
|
+
function _t(e) {
|
|
1145
|
+
let n = G(), i = M(), o = O(), s = () => n.collectionPrefix, c = r(!1), u = r(!1), d;
|
|
1086
1146
|
function f(t) {
|
|
1087
1147
|
return l(i, `${s()}Posts`, e, "likes", t);
|
|
1088
1148
|
}
|
|
@@ -1120,8 +1180,8 @@ function dt(e) {
|
|
|
1120
1180
|
}
|
|
1121
1181
|
//#endregion
|
|
1122
1182
|
//#region src/composables/useMemiBoardViews.ts
|
|
1123
|
-
function
|
|
1124
|
-
let e =
|
|
1183
|
+
function vt() {
|
|
1184
|
+
let e = G(), t = M(), n = e.collectionPrefix;
|
|
1125
1185
|
function r(e) {
|
|
1126
1186
|
return `memi-board:viewed:${n}:${e}`;
|
|
1127
1187
|
}
|
|
@@ -1159,7 +1219,7 @@ function ft() {
|
|
|
1159
1219
|
}
|
|
1160
1220
|
//#endregion
|
|
1161
1221
|
//#region src/utils/moderation-prompt.ts
|
|
1162
|
-
var
|
|
1222
|
+
var yt = "당신은 게시판 콘텐츠를 심사하는 엄격한 모더레이터입니다.\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은 \"\"", bt = {
|
|
1163
1223
|
abuse: "욕설·비속어·모욕",
|
|
1164
1224
|
spam: "스팸·광고",
|
|
1165
1225
|
adult: "선정적·성인 내용",
|
|
@@ -1174,10 +1234,10 @@ function Y(e) {
|
|
|
1174
1234
|
}
|
|
1175
1235
|
let t = (e.reason || "").trim();
|
|
1176
1236
|
if (t) return t;
|
|
1177
|
-
let n = e.category && e.category !== "none" ?
|
|
1237
|
+
let n = e.category && e.category !== "none" ? bt[e.category] || e.category : "";
|
|
1178
1238
|
return n ? `${n}이(가) 포함되어 게시할 수 없습니다. 내용을 수정해 주세요.` : "게시할 수 없는 내용이 포함되어 있습니다. 내용을 수정해 주세요.";
|
|
1179
1239
|
}
|
|
1180
|
-
var
|
|
1240
|
+
var xt = [
|
|
1181
1241
|
"씨발",
|
|
1182
1242
|
"시발",
|
|
1183
1243
|
"시이발",
|
|
@@ -1203,10 +1263,10 @@ var ht = [
|
|
|
1203
1263
|
"bitch",
|
|
1204
1264
|
"asshole"
|
|
1205
1265
|
];
|
|
1206
|
-
function
|
|
1266
|
+
function St(e) {
|
|
1207
1267
|
return new RegExp(e.join("|"), "i");
|
|
1208
1268
|
}
|
|
1209
|
-
var
|
|
1269
|
+
var Ct = /* @__PURE__ */ new Set([
|
|
1210
1270
|
"none",
|
|
1211
1271
|
"abuse",
|
|
1212
1272
|
"spam",
|
|
@@ -1214,7 +1274,7 @@ var _t = /* @__PURE__ */ new Set([
|
|
|
1214
1274
|
"violence",
|
|
1215
1275
|
"other"
|
|
1216
1276
|
]);
|
|
1217
|
-
function
|
|
1277
|
+
function wt(e) {
|
|
1218
1278
|
let t = e.trim();
|
|
1219
1279
|
if (!t) return null;
|
|
1220
1280
|
let n;
|
|
@@ -1232,7 +1292,7 @@ function vt(e) {
|
|
|
1232
1292
|
if (typeof n != "object" || !n) return null;
|
|
1233
1293
|
let r = n;
|
|
1234
1294
|
if (typeof r.flagged != "boolean") return null;
|
|
1235
|
-
let i = typeof r.category == "string" &&
|
|
1295
|
+
let i = typeof r.category == "string" && Ct.has(r.category) ? r.category : r.flagged ? "other" : "none", a = typeof r.reason == "string" ? r.reason : "";
|
|
1236
1296
|
return {
|
|
1237
1297
|
flagged: r.flagged,
|
|
1238
1298
|
category: i,
|
|
@@ -1241,14 +1301,14 @@ function vt(e) {
|
|
|
1241
1301
|
}
|
|
1242
1302
|
//#endregion
|
|
1243
1303
|
//#region src/composables/useMemiBoardModeration.ts
|
|
1244
|
-
var
|
|
1304
|
+
var Tt = {
|
|
1245
1305
|
flagged: !1,
|
|
1246
1306
|
category: "none",
|
|
1247
1307
|
reason: "",
|
|
1248
1308
|
via: "empty"
|
|
1249
1309
|
};
|
|
1250
|
-
function
|
|
1251
|
-
let e =
|
|
1310
|
+
function Et() {
|
|
1311
|
+
let e = G().moderation ?? {}, t = A(), { isWriteRestricted: n, restrictedMessage: r, recordContentModerationBlock: i } = J(), a = St([...xt, ...e.localBlocklist ?? []]);
|
|
1252
1312
|
function o(e) {
|
|
1253
1313
|
let t = e.match(a);
|
|
1254
1314
|
return t ? {
|
|
@@ -1263,21 +1323,21 @@ function bt() {
|
|
|
1263
1323
|
} : null;
|
|
1264
1324
|
}
|
|
1265
1325
|
function s() {
|
|
1266
|
-
let n = e.model ?? "gemini-3.5-flash-lite", r = e.useLimitedUseAppCheckTokens !== !1, i =
|
|
1267
|
-
backend: new
|
|
1326
|
+
let n = e.model ?? "gemini-3.5-flash-lite", r = e.useLimitedUseAppCheckTokens !== !1, i = ie(t, {
|
|
1327
|
+
backend: new re(),
|
|
1268
1328
|
useLimitedUseAppCheckTokens: r
|
|
1269
1329
|
});
|
|
1270
|
-
return
|
|
1330
|
+
return ae(i, {
|
|
1271
1331
|
model: n,
|
|
1272
|
-
systemInstruction:
|
|
1332
|
+
systemInstruction: yt,
|
|
1273
1333
|
generationConfig: {
|
|
1274
1334
|
temperature: 0,
|
|
1275
1335
|
maxOutputTokens: 256,
|
|
1276
1336
|
responseMimeType: "application/json",
|
|
1277
|
-
responseSchema:
|
|
1278
|
-
flagged:
|
|
1279
|
-
category:
|
|
1280
|
-
reason:
|
|
1337
|
+
responseSchema: W.object({ properties: {
|
|
1338
|
+
flagged: W.boolean(),
|
|
1339
|
+
category: W.string(),
|
|
1340
|
+
reason: W.string()
|
|
1281
1341
|
} })
|
|
1282
1342
|
}
|
|
1283
1343
|
});
|
|
@@ -1326,17 +1386,17 @@ function bt() {
|
|
|
1326
1386
|
};
|
|
1327
1387
|
let i = t.trim();
|
|
1328
1388
|
if (!i) return {
|
|
1329
|
-
...
|
|
1389
|
+
...Tt,
|
|
1330
1390
|
via: "empty"
|
|
1331
1391
|
};
|
|
1332
1392
|
let a = o(i);
|
|
1333
1393
|
if (a) return u(a);
|
|
1334
1394
|
if (e.enabled === !1) return {
|
|
1335
|
-
...
|
|
1395
|
+
...Tt,
|
|
1336
1396
|
via: "disabled"
|
|
1337
1397
|
};
|
|
1338
1398
|
try {
|
|
1339
|
-
let e =
|
|
1399
|
+
let e = wt((await s().generateContent(`심사할 텍스트:\n"""${i.slice(0, 4e3)}"""`)).response.text());
|
|
1340
1400
|
if (!e) return c();
|
|
1341
1401
|
let t = {
|
|
1342
1402
|
flagged: e.flagged,
|
|
@@ -1361,14 +1421,14 @@ function bt() {
|
|
|
1361
1421
|
via: "restricted"
|
|
1362
1422
|
};
|
|
1363
1423
|
if (e.enabled === !1 || !e.moderateImages) return {
|
|
1364
|
-
...
|
|
1424
|
+
...Tt,
|
|
1365
1425
|
via: "disabled"
|
|
1366
1426
|
};
|
|
1367
1427
|
try {
|
|
1368
1428
|
let e = await new Promise((e, n) => {
|
|
1369
1429
|
let r = new FileReader();
|
|
1370
1430
|
r.onload = () => e(r.result.split(",")[1] ?? ""), r.onerror = n, r.readAsDataURL(t);
|
|
1371
|
-
}), n =
|
|
1431
|
+
}), n = wt((await s().generateContent([{ inlineData: {
|
|
1372
1432
|
mimeType: t.type || "image/jpeg",
|
|
1373
1433
|
data: e
|
|
1374
1434
|
} }, "이 이미지를 심사해 주세요."])).response.text());
|
|
@@ -1415,9 +1475,9 @@ async function X(e, { maxWidth: t = 400, quality: n = .8 } = {}) {
|
|
|
1415
1475
|
}
|
|
1416
1476
|
//#endregion
|
|
1417
1477
|
//#region src/composables/useMemiBoardStorage.ts
|
|
1418
|
-
var
|
|
1419
|
-
async function
|
|
1420
|
-
if (e.size > 26214400) throw Error(`원본 이미지는 ${
|
|
1478
|
+
var Dt = 5242880, Ot = 26214400;
|
|
1479
|
+
async function kt(e) {
|
|
1480
|
+
if (e.size > 26214400) throw Error(`원본 이미지는 ${Ot / 1024 / 1024}MB 이하여야 합니다.`);
|
|
1421
1481
|
if (e.size <= 5242880) return {
|
|
1422
1482
|
blob: e,
|
|
1423
1483
|
compressed: !1
|
|
@@ -1439,10 +1499,10 @@ async function Ct(e) {
|
|
|
1439
1499
|
throw e instanceof Error && e.message.includes("압축 후에도") ? e : Error("이 이미지 형식은 브라우저에서 최적화할 수 없습니다. JPG, PNG 또는 WebP로 변환해 주세요.");
|
|
1440
1500
|
}
|
|
1441
1501
|
}
|
|
1442
|
-
function
|
|
1443
|
-
let e =
|
|
1502
|
+
function At() {
|
|
1503
|
+
let e = G(), t = A(), n = () => e.collectionPrefix;
|
|
1444
1504
|
function r(e, r, i) {
|
|
1445
|
-
let a =
|
|
1505
|
+
let a = H(t), o = `${n()}/posts/${r}/attachments/${Date.now()}-${He(e.name)}`, s = U(a, o), c = ne(s, e, {
|
|
1446
1506
|
contentType: e.type || "application/octet-stream",
|
|
1447
1507
|
contentDisposition: `attachment; filename*=UTF-8''${encodeURIComponent(e.name)}`
|
|
1448
1508
|
});
|
|
@@ -1451,7 +1511,7 @@ function wt() {
|
|
|
1451
1511
|
c.on("state_changed", (e) => {
|
|
1452
1512
|
i && e.totalBytes > 0 && i(e.bytesTransferred / e.totalBytes);
|
|
1453
1513
|
}, n, async () => {
|
|
1454
|
-
let n = await
|
|
1514
|
+
let n = await V(s);
|
|
1455
1515
|
t({
|
|
1456
1516
|
name: e.name,
|
|
1457
1517
|
url: n,
|
|
@@ -1465,27 +1525,27 @@ function wt() {
|
|
|
1465
1525
|
};
|
|
1466
1526
|
}
|
|
1467
1527
|
async function i(e) {
|
|
1468
|
-
let n =
|
|
1469
|
-
await
|
|
1528
|
+
let n = H(t);
|
|
1529
|
+
await B(U(n, e.path)).catch(() => {});
|
|
1470
1530
|
}
|
|
1471
1531
|
async function a(e, r) {
|
|
1472
1532
|
if (!e.type.startsWith("image/")) throw Error("이미지 파일만 업로드할 수 있습니다.");
|
|
1473
|
-
let i = await
|
|
1474
|
-
await
|
|
1475
|
-
let f = await
|
|
1533
|
+
let i = await kt(e), a = H(t), o = (i.compressed ? "jpg" : e.name.split(".").pop() || e.type.split("/")[1] || "png").replace(/[^a-zA-Z0-9]/g, "").slice(0, 8) || "png", s = He(e.name.replace(/\.[^.]+$/, "") || "image", 40), c = `${Date.now()}-${s || "image"}`, l = r || `new-${Date.now()}`, u = `${n()}/posts/${l}/images/${c}.${o}`, d = U(a, u);
|
|
1534
|
+
await te(d, i.blob, { contentType: i.blob.type || "image/jpeg" });
|
|
1535
|
+
let f = await V(d), p = await X(i.blob, {
|
|
1476
1536
|
maxWidth: 400,
|
|
1477
1537
|
quality: .8
|
|
1478
|
-
}), m = `${n()}/posts/${l}/images/thumbnails/${c}.jpg`, h =
|
|
1479
|
-
return await
|
|
1538
|
+
}), m = `${n()}/posts/${l}/images/thumbnails/${c}.jpg`, h = U(a, m);
|
|
1539
|
+
return await te(h, p, { contentType: "image/jpeg" }), {
|
|
1480
1540
|
originalUrl: f,
|
|
1481
1541
|
originalPath: u,
|
|
1482
|
-
thumbnailUrl: await
|
|
1542
|
+
thumbnailUrl: await V(h),
|
|
1483
1543
|
thumbnailPath: m
|
|
1484
1544
|
};
|
|
1485
1545
|
}
|
|
1486
1546
|
async function o(e) {
|
|
1487
|
-
let n =
|
|
1488
|
-
await Promise.all([
|
|
1547
|
+
let n = H(t);
|
|
1548
|
+
await Promise.all([B(U(n, e.originalPath)).catch(() => {}), B(U(n, e.thumbnailPath)).catch(() => {})]);
|
|
1489
1549
|
}
|
|
1490
1550
|
return {
|
|
1491
1551
|
uploadAttachment: r,
|
|
@@ -1496,7 +1556,7 @@ function wt() {
|
|
|
1496
1556
|
}
|
|
1497
1557
|
//#endregion
|
|
1498
1558
|
//#region src/composables/useMemiBoardUsers.ts
|
|
1499
|
-
var
|
|
1559
|
+
var jt = [
|
|
1500
1560
|
{
|
|
1501
1561
|
value: "admin",
|
|
1502
1562
|
label: "관리자",
|
|
@@ -1513,10 +1573,10 @@ var Tt = [
|
|
|
1513
1573
|
description: "글과 댓글을 작성하고 본인이 작성한 콘텐츠만 수정·삭제할 수 있습니다."
|
|
1514
1574
|
}
|
|
1515
1575
|
];
|
|
1516
|
-
function
|
|
1517
|
-
let n =
|
|
1576
|
+
function Mt(t = {}) {
|
|
1577
|
+
let n = G(), r = M(), a = `${n.collectionPrefix}Users`, s = e(() => i(t.enabled) ?? !0 ? o(r, a) : null), c = D(s, { ssrKey: `${a}-management` });
|
|
1518
1578
|
async function u(e, t) {
|
|
1519
|
-
if (!
|
|
1579
|
+
if (!jt.some((e) => e.value === t)) throw Error("지원하지 않는 역할입니다.");
|
|
1520
1580
|
await w(l(r, a, e), {
|
|
1521
1581
|
role: t,
|
|
1522
1582
|
updatedAt: x()
|
|
@@ -1530,7 +1590,22 @@ function Et(t = {}) {
|
|
|
1530
1590
|
}
|
|
1531
1591
|
//#endregion
|
|
1532
1592
|
//#region src/data/versionHistory.ts
|
|
1533
|
-
var
|
|
1593
|
+
var Nt = [
|
|
1594
|
+
{
|
|
1595
|
+
version: "0.19.3",
|
|
1596
|
+
date: "2026-08-06",
|
|
1597
|
+
highlights: ["이미지 목록 게시판은 인스타처럼 제목 없이 사진+내용만 올리면 돼요", "사진을 여러 장 올리고 끌어 순서를 바꿀 수 있어요 (첫 장이 대표)"]
|
|
1598
|
+
},
|
|
1599
|
+
{
|
|
1600
|
+
version: "0.19.2",
|
|
1601
|
+
date: "2026-08-06",
|
|
1602
|
+
highlights: ["이미지만 넣고 글 없이 올리는 건 막았어요 — 본문에 글자를 넣어야 해요"]
|
|
1603
|
+
},
|
|
1604
|
+
{
|
|
1605
|
+
version: "0.19.1",
|
|
1606
|
+
date: "2026-08-06",
|
|
1607
|
+
highlights: ["공유 미리보기 이미지를 가벼운 썸네일로 쓰도록 바꿨어요 (카톡 등에 더 잘 맞아요)"]
|
|
1608
|
+
},
|
|
1534
1609
|
{
|
|
1535
1610
|
version: "0.19.0",
|
|
1536
1611
|
date: "2026-08-06",
|
|
@@ -1633,7 +1708,7 @@ var Dt = [
|
|
|
1633
1708
|
highlights: ["글쓰기·댓글·첨부·로그인 권한으로 게시판을 바로 시작할 수 있어요", "부적절한 글·댓글은 AI 검열로 막을 수 있어요"],
|
|
1634
1709
|
note: "실제 보안은 호스트 Firebase 규칙 등록이 필요합니다."
|
|
1635
1710
|
}
|
|
1636
|
-
],
|
|
1711
|
+
], Pt = /* @__PURE__ */ fe(((e, t) => {
|
|
1637
1712
|
(function(n, r) {
|
|
1638
1713
|
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();
|
|
1639
1714
|
})(e, (function() {
|
|
@@ -1927,7 +2002,7 @@ var Dt = [
|
|
|
1927
2002
|
return w(1e3 * e);
|
|
1928
2003
|
}, w.en = b[y], w.Ls = b, w.p = {}, w;
|
|
1929
2004
|
}));
|
|
1930
|
-
})),
|
|
2005
|
+
})), Ft = /* @__PURE__ */ fe(((e, t) => {
|
|
1931
2006
|
(function(n, r) {
|
|
1932
2007
|
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();
|
|
1933
2008
|
})(e, (function() {
|
|
@@ -2031,9 +2106,9 @@ var Dt = [
|
|
|
2031
2106
|
};
|
|
2032
2107
|
};
|
|
2033
2108
|
}));
|
|
2034
|
-
})),
|
|
2109
|
+
})), It = /* @__PURE__ */ fe(((e, t) => {
|
|
2035
2110
|
(function(n, r) {
|
|
2036
|
-
typeof e == "object" && t !== void 0 ? t.exports = r(
|
|
2111
|
+
typeof e == "object" && t !== void 0 ? t.exports = r(Pt()) : typeof define == "function" && define.amd ? define(["dayjs"], r) : (n = typeof globalThis < "u" ? globalThis : n || self).dayjs_locale_ko = r(n.dayjs);
|
|
2037
2112
|
})(e, (function(e) {
|
|
2038
2113
|
function t(e) {
|
|
2039
2114
|
return e && typeof e == "object" && "default" in e ? e : { default: e };
|
|
@@ -2081,20 +2156,20 @@ var Dt = [
|
|
|
2081
2156
|
};
|
|
2082
2157
|
return n.default.locale(r, null, !0), r;
|
|
2083
2158
|
}));
|
|
2084
|
-
})), Z = /* @__PURE__ */
|
|
2085
|
-
|
|
2086
|
-
function
|
|
2159
|
+
})), Z = /* @__PURE__ */ me(Pt(), 1), Lt = /* @__PURE__ */ me(Ft(), 1);
|
|
2160
|
+
It(), Z.default.extend(Lt.default), Z.default.locale("ko");
|
|
2161
|
+
function Rt(e) {
|
|
2087
2162
|
return e?.toDate ? e.toDate().toLocaleDateString("ko-KR") : "";
|
|
2088
2163
|
}
|
|
2089
|
-
function
|
|
2164
|
+
function zt(e, t = Date.now()) {
|
|
2090
2165
|
return e?.toDate ? (0, Z.default)(e.toDate()).from((0, Z.default)(t)) : "방금 전";
|
|
2091
2166
|
}
|
|
2092
|
-
function
|
|
2167
|
+
function Bt(e) {
|
|
2093
2168
|
return e?.toDate ? (0, Z.default)(e.toDate()).format("YYYY년 M월 D일 HH:mm:ss") : "시간 확인 중";
|
|
2094
2169
|
}
|
|
2095
|
-
function
|
|
2096
|
-
let n =
|
|
2097
|
-
return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${
|
|
2170
|
+
function Vt(e, t) {
|
|
2171
|
+
let n = Bt(e), r = e?.toMillis?.(), i = t?.toMillis?.();
|
|
2172
|
+
return i == null || r == null || r === i ? [n] : [`생성시간: ${n}`, `수정시간: ${Bt(t)}`];
|
|
2098
2173
|
}
|
|
2099
2174
|
//#endregion
|
|
2100
2175
|
//#region src/utils/renderMarkdown.ts
|
|
@@ -2105,7 +2180,7 @@ function $(e) {
|
|
|
2105
2180
|
let t = e;
|
|
2106
2181
|
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;
|
|
2107
2182
|
}
|
|
2108
|
-
function
|
|
2183
|
+
function Ht(e) {
|
|
2109
2184
|
if (!e?.trim()) return "";
|
|
2110
2185
|
let t = e.replace(/\r\n/g, "\n").split("\n"), n = [], r = 0, i = !1, a = [], o = null, s = [];
|
|
2111
2186
|
function c() {
|
|
@@ -2170,6 +2245,6 @@ function It(e) {
|
|
|
2170
2245
|
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");
|
|
2171
2246
|
}
|
|
2172
2247
|
//#endregion
|
|
2173
|
-
export {
|
|
2248
|
+
export { jt as BOARD_USER_ROLES, pt as COMMENT_BODY_MAX_LENGTH, K as DEFAULT_BLOCK_BAN_DECAY_MS, Pe as DEFAULT_BLOCK_BAN_THRESHOLD, tt as DEFAULT_CATEGORIES, Dt as EDITOR_IMAGE_MAX_BYTES, Ot as EDITOR_IMAGE_SOURCE_MAX_BYTES, ve as asHttpUrl, we as boardListOgDescription, Ce as boardListOgTitle, Se as boardPostOgDescription, xe as boardPostOgTitle, et as buildPostPreview, X as compressImage, _e as configureMemiBoard, Fe as effectiveModerationBlockCount, Ue as extractEditorImageUrls, Me as fetchPublicListForSeo, je as fetchPublicPostForSeo, Rt as formatDate, Bt as formatFullDate, zt as formatRelativeDate, Le as formatRestrictedUntilLabel, Vt as formatTimestampDetails, Ye as hasBodyImage, Je as hasBodyText, Xe as isContentEmpty, ot as isFirestorePermissionDenied, Ie as isModerationWriteRestricted, q as moderationWriteRestrictedUntilMs, Te as normalizeBasePath, Ke as plainTextFromHtml, Ge as postNamespaceFromStoragePath, Ht as renderMarkdownToHtml, Ee as resolvePublicSeoDb, Ve as slugify, We as storagePathFromDownloadUrl, qe as titleFromBody, be as toAbsoluteUrl, ye as toBoardOgImageUrl, J as useMemiBoardAuth, ht as useMemiBoardComments, G as useMemiBoardConfig, _t as useMemiBoardLikes, Et as useMemiBoardModeration, ut as useMemiBoardPost, lt as useMemiBoardPostList, st as useMemiBoardPosts, gt as useMemiBoardReplies, rt as useMemiBoardSettings, At as useMemiBoardStorage, Mt as useMemiBoardUsers, vt as useMemiBoardViews, Nt as versionHistory, $e as videoListCoverUrl, Qe as youtubeId };
|
|
2174
2249
|
|
|
2175
2250
|
//# sourceMappingURL=index.js.map
|