memi-board 0.56.1 → 0.57.0
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 +40 -8
- package/dist/{commentImages-DsW2yCqu.js → commentImages-mIfyNHgz.js} +143 -59
- package/dist/commentImages-mIfyNHgz.js.map +1 -0
- package/dist/composables/useMemiBoardModeration.d.ts +2 -0
- package/dist/composables/useMemiBoardPosts.d.ts +1 -1
- package/dist/composables/useMemiBoardUserDirectory.d.ts +20 -0
- package/dist/composables/useMemiBoardUserProfile.d.ts +1 -1
- package/dist/composables/useMemiBoardUsers.d.ts +1 -1
- package/dist/{en-BDkfivhc.js → en-Dc7zQOy-.js} +85 -1
- package/dist/en-Dc7zQOy-.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +927 -711
- package/dist/index.js.map +1 -1
- package/dist/module.mjs +4 -0
- package/dist/module.mjs.map +1 -1
- package/dist/runtime/components/AccountDeletion.vue +64 -0
- package/dist/runtime/components/AuthorMenu.vue +4 -4
- package/dist/runtime/components/BoardInfo.vue +18 -1
- package/dist/runtime/components/BoardInfoPerson.vue +17 -11
- package/dist/runtime/components/CommentFeed.vue +17 -13
- package/dist/runtime/components/CommentForm.vue +58 -56
- package/dist/runtime/components/CommentImageField.vue +6 -3
- package/dist/runtime/components/CommentItem.vue +16 -3
- package/dist/runtime/components/CommentList.vue +10 -1
- package/dist/runtime/components/CommentThread.vue +16 -2
- package/dist/runtime/components/Detail.vue +7 -1
- package/dist/runtime/components/Editor.vue +1 -5
- package/dist/runtime/components/List.vue +3 -0
- package/dist/runtime/components/Profile.vue +14 -2
- package/dist/runtime/components/ProfileEditModal.vue +20 -3
- package/dist/runtime/components/UserListControls.vue +29 -0
- package/dist/runtime/components/UserListPagination.vue +17 -0
- package/dist/runtime/components/UserManagementEmail.vue +21 -0
- package/dist/runtime/components/UserRoleGuide.vue +38 -0
- package/dist/runtime/components/Users.vue +21 -59
- package/dist/runtime/components/UsersSettings.vue +119 -0
- package/dist/storage.js +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/utils/postSlug.d.ts +12 -0
- package/dist/utils/profileNameModeration.d.ts +8 -0
- package/dist/utils/userDirectoryPage.d.ts +19 -0
- package/docs/account-deletion.md +60 -0
- package/docs/firestore.rules.example +61 -2
- package/docs/host-integration.md +72 -9
- package/docs/storage.rules.example +7 -2
- package/package.json +3 -2
- package/scripts/backfill-post-slugs.mjs +38 -0
- package/dist/commentImages-DsW2yCqu.js.map +0 -1
- package/dist/en-BDkfivhc.js.map +0 -1
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@ Any host app on the same stack (Nuxt 4 · `@nuxt/ui` · `nuxt-vuefire` · Fireba
|
|
|
17
17
|
| Task | Guide |
|
|
18
18
|
|---|---|
|
|
19
19
|
| First install on another site | [Host integration guide](docs/host-integration.md) — minimal setup, routes, first board |
|
|
20
|
+
| Separating public views from management screens | [Management component placement](docs/host-integration.md#keep-management-components-on-separate-routes) — recommended `/admin/` routes, guards, and search exclusion |
|
|
21
|
+
| Confirmed account deletion and large datasets | [Account deletion](docs/account-deletion.md) — content checkbox, reauthentication, bounded processing, retry, and host server/Rules requirements |
|
|
20
22
|
| Single language, or choosing multilingual UI and translation | [Internationalization and content translation](docs/internationalization.md) — per-mode setup and when translations are saved |
|
|
21
23
|
| Connecting Firebase and deploying rules and indexes | [Firebase setup](docs/firebase-setup.md) |
|
|
22
24
|
| Per-store/organization assigned permissions, migrating existing roles | [Roles and migration](docs/roles-migration.md) |
|
|
@@ -137,8 +139,9 @@ export default defineNuxtConfig({
|
|
|
137
139
|
'/board/**/new': { ssr: false },
|
|
138
140
|
'/board/**/**/edit': { ssr: false },
|
|
139
141
|
'/board/version-history': { ssr: false },
|
|
140
|
-
'/board-settings/**': { ssr: false },
|
|
141
142
|
'/board-users': { ssr: false },
|
|
143
|
+
'/admin': { ssr: false, headers: { 'X-Robots-Tag': 'noindex, nofollow' } },
|
|
144
|
+
'/admin/**': { ssr: false, headers: { 'X-Robots-Tag': 'noindex, nofollow' } },
|
|
142
145
|
},
|
|
143
146
|
})
|
|
144
147
|
```
|
|
@@ -340,8 +343,8 @@ Rules prevent regular users from making themselves admins. So the first time, fo
|
|
|
340
343
|
2. In Firebase Console → Firestore, find the `memiBoardUsers/{uid}` document.
|
|
341
344
|
3. Change that document's `role` to the string `owner`.
|
|
342
345
|
- You can also find the UID in Firebase Console → Authentication → Users.
|
|
343
|
-
4. Create the first category at `/board-settings`. Posts cannot be written without a category.
|
|
344
|
-
5. After that, change roles in `
|
|
346
|
+
4. Create the first category at `/admin/board-settings`. Posts cannot be written without a category.
|
|
347
|
+
5. After that, change roles in `MemiBoardUsersSettings` at `/admin/board-users-settings`.
|
|
345
348
|
|
|
346
349
|
> Only a trusted project operator should assign the initial admin, and only in Firebase Console. Do not put admin UIDs or bypass rules in client code.
|
|
347
350
|
|
|
@@ -376,12 +379,19 @@ For the detail page, use `<MemiBoardDetail :board-id="boardId" :post-id="postId"
|
|
|
376
379
|
|
|
377
380
|
### Admin pages
|
|
378
381
|
|
|
382
|
+
Prefer separate pages and layouts for management components, ideally under `/admin/`. Place `MemiBoardSettings`, `MemiBoardUsersSettings`, and `MemiBoardCategorySettings` on protected management routes rather than embedding them in a public list, profile, or account page. Keep the public `MemiBoardUsers` directory at `/board-users`; put email and role management at `/admin/board-users-settings`. Show links into management only to users who have the corresponding permission. See [recommended component placement and route protection](docs/host-integration.md#keep-management-components-on-separate-routes).
|
|
383
|
+
|
|
384
|
+
The `/admin/` prefix and hiding a link do not enforce permissions. The host must add route guards, retain Firestore/Storage Rules, set `noindex, nofollow` for management responses, and omit management URLs from its sitemap. The package does not automatically create routes or middleware. Assigned managers may access their own per-board settings; they should not receive access to global settings or user-role management.
|
|
385
|
+
|
|
379
386
|
Global settings and user management use the `owner` or `admin` role. In a default install, do not pass `authorized`. Only the owner can assign the admin role, and changing the owner itself is done from a trusted server or the console. For per-board settings by an assigned manager, use `MemiBoardCategorySettings` and `canConfigureBoard(boardId)`.
|
|
380
387
|
|
|
381
388
|
```vue
|
|
382
|
-
<!-- app/pages/board-settings.vue -->
|
|
389
|
+
<!-- app/pages/admin/board-settings/index.vue -->
|
|
390
|
+
<script setup lang="ts">
|
|
391
|
+
definePageMeta({ middleware: 'admin' }) // host-provided owner/admin guard
|
|
392
|
+
</script>
|
|
383
393
|
<template>
|
|
384
|
-
<MemiBoardSettings />
|
|
394
|
+
<MemiBoardSettings settings-base="/admin/board-settings" />
|
|
385
395
|
</template>
|
|
386
396
|
```
|
|
387
397
|
|
|
@@ -392,16 +402,32 @@ To use it, apply the admin read and delete permissions from the [Firestore rules
|
|
|
392
402
|
```vue
|
|
393
403
|
<!-- app/pages/board-users.vue -->
|
|
394
404
|
<template>
|
|
395
|
-
<MemiBoardUsers />
|
|
405
|
+
<MemiBoardUsers :author-profile-to="uid => `/board-user/${uid}`" />
|
|
396
406
|
</template>
|
|
397
407
|
```
|
|
398
408
|
|
|
399
|
-
|
|
409
|
+
`MemiBoardUsers` is a public directory showing names, avatars, bios, and role badges. It never reads private email documents or displays role editing controls.
|
|
410
|
+
|
|
411
|
+
Both user screens support name/role sorting (role ascending by default), ascending/descending order, and cursor pagination with 10, 25 (default), or 50 users per page. Sorting or changing page size returns to page 1. Role order is owner → admin → manager → staff → user (reversed for descending). Name ties and users within the same role are ordered by document ID. Queries fetch only a page plus one lookahead user; role sorting walks role groups rather than sorting all users in memory. Email documents are loaded only for the currently displayed admin page.
|
|
412
|
+
|
|
413
|
+
Directory documents must have `role` and `displayName` fields (`displayName: null` is valid). Firestore name ordering excludes legacy documents without `displayName`; use role sorting to include those users until their profile is updated. `useMemiBoardUserDirectory` exposes reactive sort/direction/page-size state, current page, navigation, refresh, and load errors. The existing `useMemiBoardUsers` remains an unpaged source for manager/staff assignment pickers so assignments are never limited to the current directory page.
|
|
414
|
+
|
|
415
|
+
```vue
|
|
416
|
+
<!-- app/pages/admin/board-users-settings.vue -->
|
|
417
|
+
<script setup lang="ts">
|
|
418
|
+
definePageMeta({ middleware: 'admin' }) // host-provided owner/admin guard
|
|
419
|
+
</script>
|
|
420
|
+
<template>
|
|
421
|
+
<MemiBoardUsersSettings />
|
|
422
|
+
</template>
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
`MemiBoardUsersSettings` is the admin-only email and role management screen. Use its `authorized` prop only when integrating the host's own site-admin system. This prop only allows the UI to be shown and does not bypass Firestore Rules. Even then, you must register those users as board admin/owner or change the host Rules to match the same admin system. Add host route guards to protect the settings URL.
|
|
400
426
|
|
|
401
427
|
### Provided API
|
|
402
428
|
|
|
403
429
|
**Components (auto-import):**
|
|
404
|
-
`MemiBoardList`, `MemiBoardDetail`, `MemiBoardEditor`, `MemiBoardCommentList`, `MemiBoardCommentForm`, `MemiBoardAttachments`, `MemiBoardSignIn`, `MemiBoardSettings`, `MemiBoardUsers`, `MemiBoardVersionHistory`
|
|
430
|
+
`MemiBoardList`, `MemiBoardDetail`, `MemiBoardEditor`, `MemiBoardCommentList`, `MemiBoardCommentForm`, `MemiBoardAttachments`, `MemiBoardSignIn`, `MemiBoardSettings`, `MemiBoardUsers`, `MemiBoardUsersSettings`, `MemiBoardVersionHistory`
|
|
405
431
|
|
|
406
432
|
**List props:** `boardId` (required), `pageSize`, `category` (former name), `postLinkBase`, `getPostLink(post)`, `linkBase` (deprecated)
|
|
407
433
|
|
|
@@ -409,6 +435,12 @@ Use `authorized` only when integrating the host's own site-admin system. This pr
|
|
|
409
435
|
`MemiBoardEditor` automatically confirms leaving when there are unsaved changes, including browser Back and links to other routes. Empty forms, reverted changes, and navigation after a successful save do not prompt. Refreshing or closing the tab uses the browser's standard warning, whose wording is controlled by the browser and whose display may depend on prior user interaction. Hosts do not need to add route guards. If a host hides or replaces the editor without changing routes, it must handle that separate UI action itself.
|
|
410
436
|
`MemiBoardCommentForm` also protects unfinished comments and replies, including attached images and uploads in progress. Canceling a reply asks before discarding its draft. Successful submission clears the warning.
|
|
411
437
|
|
|
438
|
+
**Profile account link:** Prefer a dedicated `/board-account` page so the host can keep `/account` for its own account system. Pass `account-to="/board-account"` to `MemiBoardProfile` on public user/activity pages to show a **My account** link instead of the edit button for the signed-in user's own profile. Other users do not see this action. The host supplies the account URL; the package does not assume or reserve an account route. On the board account page itself, omit `accountTo` to retain the profile edit modal. Protect that page with the host's sign-in middleware and exclude it from search.
|
|
439
|
+
|
|
440
|
+
**Profile name review:** Changing a display name in `MemiBoardProfileEditModal` runs local checks and then a dedicated Firebase AI Logic review before uploading a new avatar or writing profile/byline updates. Ordinary nicknames and names in any language are allowed; abuse, hate, spam, sexual obscenity, threats, and deceptive staff impersonation are rejected. A failed or malformed AI verdict blocks saving even when post moderation uses `onError: 'allow'`; the user can retry. Name checks do not consume writing-assistant quota or add content-warning strikes. Unchanged names do not trigger another review when editing a bio or photo. The existing site-wide AI switch and `moderation.enabled: false` disable AI requests, leaving local checks in place. This browser-side review is a UI safeguard; hosts needing authoritative enforcement for every write, including social-login name imports, must add a trusted server validation path and matching Rules.
|
|
441
|
+
|
|
442
|
+
**Account deletion:** `MemiBoardAccountDeletion` provides a content-deletion checkbox, acknowledgement plus typed confirmation, and loading/progress/retry UI. Supply the host's own server-backed handler; it must reauthenticate, protect owners, process data in bounded resumable chunks, and delete Firebase Auth last. See [account deletion integration and deployment requirements](docs/account-deletion.md). Do not wire it to a bare client `deleteUser()` call or place it in public profiles.
|
|
443
|
+
|
|
412
444
|
**About memi-board:** List views, detail, editor, profiles, comment feeds, and management screens automatically include the package footer. Hosts should remove manually placed `<MemiBoardFooter />`. It is visible by default; an admin can hide it site-wide with the **Show About memi-board** switch on the board settings screen. Nested views and multiple views on the same page share one visible footer. Its content-width modal, about as wide as a board column, contains the introduction, version history, and links to the detailed introduction at memi.dev/memi-board, plus memi.dev, YouTube, npm, GitHub, and email.
|
|
413
445
|
|
|
414
446
|
**Comment sign-in:** Signed-out visitors see an input-shaped login link. Set `memiBoard.auth.loginPath` if the host uses a login route other than `/auth/login`. The host login page should validate the `next` query parameter as a local path and navigate there after sign-in. The package does not change comment write permissions.
|
|
@@ -225,6 +225,33 @@ function ge() {
|
|
|
225
225
|
//#endregion
|
|
226
226
|
//#region src/i18n/messages/ko/index.ts
|
|
227
227
|
var _e = {
|
|
228
|
+
accountDeletion: {
|
|
229
|
+
title: "회원 탈퇴",
|
|
230
|
+
intro: "계정과 개인정보를 삭제하려면 탈퇴를 진행해 주세요.",
|
|
231
|
+
open: "회원 탈퇴",
|
|
232
|
+
description: "삭제할 내용을 확인하고 로그인 계정을 다시 인증해 주세요.",
|
|
233
|
+
ownerBlocked: "소유자는 먼저 다른 계정으로 소유자 권한을 이전해야 탈퇴할 수 있습니다.",
|
|
234
|
+
authWarning: "Firebase 로그인 계정이 삭제됩니다. 같은 계정을 공유하는 호스트 서비스에서도 로그인할 수 없게 됩니다. 처리 완료 후에는 복구할 수 없습니다.",
|
|
235
|
+
deleteContent: "내 글·댓글과 첨부파일도 삭제",
|
|
236
|
+
contentWarning: "내 글에 달린 다른 사람의 댓글과 첨부파일도 함께 삭제됩니다. 다른 사람 글의 답글 연결에 필요한 댓글은 내용 없는 삭제 표시로 남습니다.",
|
|
237
|
+
retention: "체크하지 않으면 글·댓글과 첨부파일은 남기고 작성자 이름·사진·계정 연결을 익명화합니다. 본문에 직접 작성한 개인정보는 남을 수 있습니다. 프로필·이메일·사진·개인 메모·좋아요·보드 담당 배정은 선택과 관계없이 삭제됩니다.",
|
|
238
|
+
acknowledge: "탈퇴를 시작하면 되돌릴 수 없음을 이해했습니다.",
|
|
239
|
+
typeConfirmation: "확인을 위해 DELETE를 입력해 주세요.",
|
|
240
|
+
confirm: "재인증 후 영구 탈퇴",
|
|
241
|
+
resume: "이어서 탈퇴 처리",
|
|
242
|
+
resumePolicy: "이미 시작한 탈퇴는 이전에 선택한 삭제 범위로 이어서 처리합니다.",
|
|
243
|
+
authenticating: "로그인 계정을 다시 확인하고 있습니다…",
|
|
244
|
+
working: "탈퇴 처리 중입니다…",
|
|
245
|
+
progress: "{count}건 처리했습니다. 데이터가 많으면 시간이 걸립니다. 이 화면을 유지해 주세요.",
|
|
246
|
+
retry: "처리를 마치지 못했습니다. 다시 눌러 이어서 처리해 주세요.",
|
|
247
|
+
unavailable: "회원 탈퇴 기능이 아직 준비되지 않았습니다. 운영자에게 문의해 주세요.",
|
|
248
|
+
signInAgain: "로그인 계정을 다시 인증해 주세요.",
|
|
249
|
+
unsupportedProvider: "이 로그인 방식은 자동 재인증을 지원하지 않습니다. 운영자에게 문의해 주세요.",
|
|
250
|
+
cancelled: "재인증이 취소되었습니다. 계정은 삭제되지 않았습니다.",
|
|
251
|
+
sessionExpired: "처리 세션이 만료되었습니다. 로그인 계정을 다시 인증해 이어서 처리해 주세요.",
|
|
252
|
+
leaveWarning: "탈퇴 처리가 진행 중입니다. 이동하면 일시 중단됩니다. 나중에 이 화면에서 이어서 처리할 수 있습니다. 이동할까요?",
|
|
253
|
+
success: "회원 탈퇴가 완료되었습니다."
|
|
254
|
+
},
|
|
228
255
|
attachments: {
|
|
229
256
|
attach: "파일 첨부 ({count} / {max})",
|
|
230
257
|
dropOrClick: "파일을 놓거나 클릭해서 추가",
|
|
@@ -259,16 +286,45 @@ var _e = {
|
|
|
259
286
|
boardSettings: {
|
|
260
287
|
signInRequired: "로그인이 필요합니다.",
|
|
261
288
|
idAndNameRequired: "보드 ID와 이름을 입력해 주세요.",
|
|
262
|
-
deleteIdRequired: "삭제할
|
|
289
|
+
deleteIdRequired: "삭제할 보드 ID가 필요합니다.",
|
|
263
290
|
nameRequired: "보드 이름을 입력해 주세요."
|
|
264
291
|
},
|
|
265
292
|
boardUsers: {
|
|
266
293
|
unsupportedRole: "지원하지 않는 역할입니다.",
|
|
294
|
+
guide: {
|
|
295
|
+
title: "사용자 역할 안내",
|
|
296
|
+
assignmentNote: "매니저·스태프의 실제 담당 보드는 각 보드 설정에서 지정합니다. 역할만 변경해도 담당 보드가 자동으로 배정되지는 않습니다.",
|
|
297
|
+
owner: {
|
|
298
|
+
permissions: "사용자 역할, 보드 설정과 모든 콘텐츠를 관리합니다.",
|
|
299
|
+
roles: "관리자를 지정·해제하고 매니저·스태프·일반 사용자의 역할을 변경합니다.",
|
|
300
|
+
restriction: "소유자 권한의 지정·이전은 이 화면에서 할 수 없습니다. 서버·콘솔에서 설정합니다."
|
|
301
|
+
},
|
|
302
|
+
admin: {
|
|
303
|
+
permissions: "보드 설정과 모든 보드의 글·댓글을 관리합니다.",
|
|
304
|
+
roles: "매니저·스태프·일반 사용자의 역할을 변경합니다.",
|
|
305
|
+
restriction: "다른 관리자와 소유자의 역할은 변경할 수 없습니다."
|
|
306
|
+
},
|
|
307
|
+
manager: {
|
|
308
|
+
permissions: "담당 보드의 설정을 변경하고 스태프를 지정합니다.",
|
|
309
|
+
roles: "담당 보드의 글·댓글을 관리합니다.",
|
|
310
|
+
restriction: "서비스 전체 설정과 사용자 역할은 변경할 수 없습니다."
|
|
311
|
+
},
|
|
312
|
+
staff: {
|
|
313
|
+
permissions: "담당 보드의 글·댓글을 관리합니다.",
|
|
314
|
+
roles: "보드 설정과 담당자 배정은 변경할 수 없습니다.",
|
|
315
|
+
restriction: "서비스 전체 설정과 사용자 역할은 변경할 수 없습니다."
|
|
316
|
+
},
|
|
317
|
+
user: {
|
|
318
|
+
permissions: "보드 권한에 따라 글·댓글을 작성합니다.",
|
|
319
|
+
roles: "본인이 작성한 글·댓글을 수정·삭제합니다.",
|
|
320
|
+
restriction: "사용자 관리와 보드 설정에는 접근할 수 없습니다."
|
|
321
|
+
}
|
|
322
|
+
},
|
|
267
323
|
roleDescription: {
|
|
268
324
|
owner: "관리자 지정과 서비스 전체를 관리합니다.",
|
|
269
|
-
manager: "담당
|
|
270
|
-
admin: "사용자 역할,
|
|
271
|
-
staff: "담당
|
|
325
|
+
manager: "담당 보드의 설정과 스태프 배정을 관리합니다.",
|
|
326
|
+
admin: "사용자 역할, 보드 설정과 모든 콘텐츠를 관리합니다.",
|
|
327
|
+
staff: "담당 보드의 게시글과 댓글을 관리합니다. 설정과 담당자 배정은 바꿀 수 없습니다.",
|
|
272
328
|
user: "글과 댓글을 작성하고 본인이 작성한 콘텐츠만 수정·삭제할 수 있습니다."
|
|
273
329
|
}
|
|
274
330
|
},
|
|
@@ -282,8 +338,8 @@ var _e = {
|
|
|
282
338
|
confirmLeave: "작성 중인 댓글이 있습니다. 저장하지 않고 나가시겠습니까?",
|
|
283
339
|
translationPending: "{count}개 언어 번역을 준비했습니다. 댓글을 저장하면 함께 기록됩니다.",
|
|
284
340
|
translationSkipped: "번역하지 못해 원문만 등록했습니다. 나중에 수정 화면에서 번역할 수 있습니다.",
|
|
285
|
-
hiddenBoardDenied: "숨김
|
|
286
|
-
denied: "이
|
|
341
|
+
hiddenBoardDenied: "숨김 보드에는 담당 스태프만 댓글을 남길 수 있습니다.",
|
|
342
|
+
denied: "이 보드에는 댓글쓰기 권한이 없습니다.",
|
|
287
343
|
signInRequired: "로그인이 필요합니다.",
|
|
288
344
|
writeRestricted: "콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요.",
|
|
289
345
|
moderationBlocked: "작성할 수 없는 내용이 포함되어 있습니다.",
|
|
@@ -534,7 +590,7 @@ var _e = {
|
|
|
534
590
|
photoRequired: "사진을 한 장 이상 올려 주세요.",
|
|
535
591
|
bodyRequired: "본문에 글자를 입력하거나 이미지를 첨부해 주세요.",
|
|
536
592
|
signInRequired: "로그인이 필요합니다.",
|
|
537
|
-
boardMissing: "
|
|
593
|
+
boardMissing: "보드(보드)이 지정되지 않았습니다.",
|
|
538
594
|
writeRestricted: "콘텐츠 경고가 누적되어 글·댓글 작성이 잠시 제한됐어요.",
|
|
539
595
|
moderationBlocked: "게시할 수 없는 내용이 포함되어 있습니다."
|
|
540
596
|
},
|
|
@@ -591,7 +647,7 @@ var _e = {
|
|
|
591
647
|
commentNotFound: "댓글을 찾을 수 없습니다."
|
|
592
648
|
},
|
|
593
649
|
list: {
|
|
594
|
-
introduction: "이
|
|
650
|
+
introduction: "이 보드는 Nuxt 4와 Vue 3, TypeScript를 바탕으로 만들었어요. Nuxt UI와 Tailwind CSS로 편안한 화면을 구성하고, Firebase Firestore·Auth·Storage와 nuxt-vuefire로 글과 댓글을 자연스럽게 이어갑니다.",
|
|
595
651
|
settings: "설정",
|
|
596
652
|
loadFailed: "목록을 불러오지 못했습니다: {error}",
|
|
597
653
|
empty: "아직 게시글이 없습니다.",
|
|
@@ -605,8 +661,8 @@ var _e = {
|
|
|
605
661
|
staff: "담당 스태프"
|
|
606
662
|
},
|
|
607
663
|
info: {
|
|
608
|
-
title: "
|
|
609
|
-
about: "이
|
|
664
|
+
title: "보드 정보",
|
|
665
|
+
about: "이 보드는",
|
|
610
666
|
history: "버전 히스토리"
|
|
611
667
|
},
|
|
612
668
|
stack: {
|
|
@@ -618,7 +674,7 @@ var _e = {
|
|
|
618
674
|
nuxt: "Vue 기반 SSR 프레임워크",
|
|
619
675
|
vue: "Composition API · script setup",
|
|
620
676
|
typescript: "안전한 데이터와 컴포넌트 타입",
|
|
621
|
-
nuxtUi: "
|
|
677
|
+
nuxtUi: "보드·폼·모달 UI",
|
|
622
678
|
tailwind: "반응형 · 라이트/다크 스타일",
|
|
623
679
|
lucide: "가볍고 일관된 아이콘",
|
|
624
680
|
dayjs: "읽기 편한 날짜와 상대 시각",
|
|
@@ -677,6 +733,7 @@ var _e = {
|
|
|
677
733
|
titleRequired: "제목을 입력해 주세요."
|
|
678
734
|
},
|
|
679
735
|
profile: {
|
|
736
|
+
myAccount: "내 계정",
|
|
680
737
|
notFound: "사용자를 찾을 수 없습니다.",
|
|
681
738
|
noName: "이름 없음",
|
|
682
739
|
joined: "가입 {time}",
|
|
@@ -692,6 +749,10 @@ var _e = {
|
|
|
692
749
|
profileEditModal: {
|
|
693
750
|
imageOnly: "이미지 파일만 선택할 수 있습니다.",
|
|
694
751
|
nameRequired: "이름을 입력해 주세요.",
|
|
752
|
+
nameReviewHelp: "새 이름은 저장 전에 욕설·혐오·스팸·운영진 사칭 여부를 검사합니다.",
|
|
753
|
+
checkingName: "이름 검사 중…",
|
|
754
|
+
nameRejected: "사용할 수 없는 이름입니다. 다른 이름을 입력해 주세요.",
|
|
755
|
+
nameReviewFailed: "AI가 이름을 확인하지 못했습니다. 잠시 후 다시 시도해 주세요.",
|
|
695
756
|
saveFailed: "프로필을 저장하지 못했습니다.",
|
|
696
757
|
title: "프로필 수정",
|
|
697
758
|
changePhoto: "사진 변경",
|
|
@@ -750,28 +811,28 @@ var _e = {
|
|
|
750
811
|
sections: { defaultTitle: "섹션 {n}" },
|
|
751
812
|
seo: {
|
|
752
813
|
allCategory: "전체",
|
|
753
|
-
boardDescription: "{name}
|
|
754
|
-
allDescription: "
|
|
814
|
+
boardDescription: "{name} 보드",
|
|
815
|
+
allDescription: "보드 — 공지와 커뮤니티 소식을 확인하세요.",
|
|
755
816
|
postFallbackTitle: "게시글 | {site}",
|
|
756
|
-
postFallbackDescription: "
|
|
817
|
+
postFallbackDescription: "보드",
|
|
757
818
|
photo: "사진",
|
|
758
|
-
postDescriptionFallback: "
|
|
759
|
-
listTitle: "{name}
|
|
760
|
-
listTitleAll: "
|
|
761
|
-
listDescription: "{name}
|
|
762
|
-
listDescriptionAll: "
|
|
819
|
+
postDescriptionFallback: "보드 글",
|
|
820
|
+
listTitle: "{name} 보드 | {site}",
|
|
821
|
+
listTitleAll: "보드 | {site}",
|
|
822
|
+
listDescription: "{name} 보드 — 소식을 확인하세요.",
|
|
823
|
+
listDescriptionAll: "보드 — 공지와 이야기를 확인하세요."
|
|
763
824
|
},
|
|
764
825
|
settings: {
|
|
765
826
|
managers: "담당 매니저",
|
|
766
827
|
loading: "설정을 불러오고 있습니다.",
|
|
767
828
|
noAccess: "접근 권한이 없습니다",
|
|
768
829
|
notFound: {
|
|
769
|
-
title: "없는
|
|
830
|
+
title: "없는 보드입니다",
|
|
770
831
|
checkAddress: "주소를 다시 확인해 주세요.",
|
|
771
|
-
checkBoardAddress: "
|
|
832
|
+
checkBoardAddress: "보드 주소를 다시 확인해 주세요."
|
|
772
833
|
},
|
|
773
834
|
newBoard: {
|
|
774
|
-
title: "아직 없는
|
|
835
|
+
title: "아직 없는 보드입니다",
|
|
775
836
|
description: "아래 정보를 입력하고 저장하면 이 ID로 새로 만들어집니다."
|
|
776
837
|
},
|
|
777
838
|
noLabel: "라벨 없음",
|
|
@@ -794,7 +855,7 @@ var _e = {
|
|
|
794
855
|
label: "댓글쓰기 권한",
|
|
795
856
|
help: "이 카테고리에 댓글을 쓸 수 있는 최소 역할",
|
|
796
857
|
helpShort: "댓글을 쓸 수 있는 최소 역할",
|
|
797
|
-
hiddenHelp: "숨김
|
|
858
|
+
hiddenHelp: "숨김 보드는 댓글이 공개 피드에 안 나오고, 담당 스태프만 작성할 수 있습니다",
|
|
798
859
|
hiddenFixed: "담당 스태프만 (숨김 보드 고정)"
|
|
799
860
|
},
|
|
800
861
|
staff: {
|
|
@@ -808,8 +869,8 @@ var _e = {
|
|
|
808
869
|
},
|
|
809
870
|
description: {
|
|
810
871
|
label: "설명",
|
|
811
|
-
help: "
|
|
812
|
-
placeholder: "이
|
|
872
|
+
help: "보드 상단 등에 보이는 안내 문구",
|
|
873
|
+
placeholder: "이 보드에 대한 짧은 설명 (선택)"
|
|
813
874
|
},
|
|
814
875
|
list: {
|
|
815
876
|
filterAll: "전체",
|
|
@@ -818,7 +879,7 @@ var _e = {
|
|
|
818
879
|
sortName: "이름",
|
|
819
880
|
sortDescription: "설명",
|
|
820
881
|
sortId: "주소",
|
|
821
|
-
empty: "해당하는
|
|
882
|
+
empty: "해당하는 보드가 없습니다.",
|
|
822
883
|
none: "없음",
|
|
823
884
|
fieldCount: "{count}개",
|
|
824
885
|
localeAuto: "자동"
|
|
@@ -871,21 +932,21 @@ var _e = {
|
|
|
871
932
|
labelRequired: "키 {key}의 이름을 입력해 주세요."
|
|
872
933
|
},
|
|
873
934
|
locale: {
|
|
874
|
-
label: "
|
|
875
|
-
help: "비우면 사이트 언어를 따릅니다. 고르면 이
|
|
935
|
+
label: "보드 언어",
|
|
936
|
+
help: "비우면 사이트 언어를 따릅니다. 고르면 이 보드는 항상 그 언어로 보입니다.",
|
|
876
937
|
auto: "자동 (사이트 언어 따름)"
|
|
877
938
|
},
|
|
878
939
|
bulk: {
|
|
879
|
-
title: "모든
|
|
880
|
-
help: "체크한 항목만 모든
|
|
881
|
-
apply: "
|
|
940
|
+
title: "모든 보드에 적용",
|
|
941
|
+
help: "체크한 항목만 모든 보드에 덮어씁니다. 보드별로 편집 중인 다른 값은 저장하지 않습니다.",
|
|
942
|
+
apply: "보드 {count}개에 적용",
|
|
882
943
|
applyShort: "적용",
|
|
883
|
-
confirmTitle: "모든
|
|
884
|
-
confirm: "체크한 항목을
|
|
885
|
-
applied: "
|
|
886
|
-
translateUntranslated: "번역 안 된
|
|
887
|
-
translated: "
|
|
888
|
-
translateFailed: "
|
|
944
|
+
confirmTitle: "모든 보드에 적용",
|
|
945
|
+
confirm: "체크한 항목을 보드 {count}개에 적용할까요?",
|
|
946
|
+
applied: "보드 {count}개에 적용했습니다.",
|
|
947
|
+
translateUntranslated: "번역 안 된 보드 번역 ({count})",
|
|
948
|
+
translated: "보드 {count}개의 이름·설명을 번역해 저장했습니다.",
|
|
949
|
+
translateFailed: "보드 {count}개는 번역하지 못했습니다. 다시 시도해 주세요."
|
|
889
950
|
},
|
|
890
951
|
translationLocales: {
|
|
891
952
|
label: "번역 언어",
|
|
@@ -907,22 +968,22 @@ var _e = {
|
|
|
907
968
|
},
|
|
908
969
|
action: {
|
|
909
970
|
moveDown: "아래로",
|
|
910
|
-
goToBoard: "
|
|
911
|
-
deleteBoard: "
|
|
971
|
+
goToBoard: "보드로 이동",
|
|
972
|
+
deleteBoard: "보드 삭제",
|
|
912
973
|
create: "만들기"
|
|
913
974
|
},
|
|
914
|
-
newCategoryPlaceholder: "새
|
|
915
|
-
newCategoryHelp: "입력한 이름이
|
|
975
|
+
newCategoryPlaceholder: "새 보드 이름",
|
|
976
|
+
newCategoryHelp: "입력한 이름이 보드 주소가 됩니다. 40자까지이고, 공백은 - 가 되며, 영문·숫자·한글만 남습니다.",
|
|
916
977
|
newCategoryUrl: "주소: {path}",
|
|
917
|
-
deleteConfirm: "‘{label}’
|
|
978
|
+
deleteConfirm: "‘{label}’ 보드를 삭제하시겠습니까?\n\n이 보드의 모든 게시물, 댓글, 대댓글, 본문 이미지, 댓글 이미지와 첨부파일이 영구 삭제됩니다. 이 작업은 되돌릴 수 없습니다.",
|
|
918
979
|
savedCategory: "‘{label}’ 카테고리를 저장했습니다.",
|
|
919
|
-
savedSettings: "
|
|
920
|
-
created: "
|
|
980
|
+
savedSettings: "보드 설정을 저장했습니다.",
|
|
981
|
+
created: "보드를 만들었습니다.",
|
|
921
982
|
error: {
|
|
922
983
|
orderFailed: "카테고리 순서를 저장하지 못했습니다.",
|
|
923
|
-
deleteFailed: "
|
|
924
|
-
saveFailed: "
|
|
925
|
-
deleteAllFailed: "
|
|
984
|
+
deleteFailed: "보드와 연결 데이터를 삭제하지 못했습니다.",
|
|
985
|
+
saveFailed: "보드 설정을 저장하지 못했습니다.",
|
|
986
|
+
deleteAllFailed: "보드 데이터를 삭제하지 못했습니다."
|
|
926
987
|
},
|
|
927
988
|
aiTitle: {
|
|
928
989
|
label: "AI 제목 자동 만들기",
|
|
@@ -954,16 +1015,16 @@ var _e = {
|
|
|
954
1015
|
},
|
|
955
1016
|
i18nBoard: {
|
|
956
1017
|
label: "국제화",
|
|
957
|
-
help: "켜면
|
|
1018
|
+
help: "켜면 보드 이름·설명과 새 글·댓글을 {count}개 언어로 자동 번역해 함께 저장합니다. 끄면 원문만 보입니다.",
|
|
958
1019
|
existing: "기존 글도 번역하기"
|
|
959
1020
|
},
|
|
960
1021
|
about: {
|
|
961
1022
|
label: "About memi-board 표시",
|
|
962
|
-
help: "화면 아래의 About memi-board 링크와 소개 팝업을 모든
|
|
1023
|
+
help: "화면 아래의 About memi-board 링크와 소개 팝업을 모든 보드 화면에 보여 줍니다. 끄면 사이트 전체에서 숨겨집니다."
|
|
963
1024
|
},
|
|
964
1025
|
postTranslations: {
|
|
965
1026
|
label: "기존 글 번역",
|
|
966
|
-
help: "번역이 없거나 원문과 달라진 글을 찾아 한꺼번에 번역하고 저장합니다. 자동 번역을 끈
|
|
1027
|
+
help: "번역이 없거나 원문과 달라진 글을 찾아 한꺼번에 번역하고 저장합니다. 자동 번역을 끈 보드나, 번역이 빠진 글을 채울 때 쓰세요. AI 사용 횟수는 차감하지 않습니다.",
|
|
967
1028
|
scan: "번역할 글 찾기",
|
|
968
1029
|
run: "{count}개 글 번역하기",
|
|
969
1030
|
confirm: "{count}개 글을 번역해 저장합니다. 글 수만큼 AI 호출이 일어납니다. 계속할까요?",
|
|
@@ -973,9 +1034,9 @@ var _e = {
|
|
|
973
1034
|
failed: "{count}개 글은 번역하지 못했습니다. 다시 찾아서 시도해 주세요."
|
|
974
1035
|
},
|
|
975
1036
|
cleanup: {
|
|
976
|
-
contextChanged: "조회 이후 계정이나
|
|
1037
|
+
contextChanged: "조회 이후 계정이나 보드 설정이 변경되었습니다. 다시 조회해 주세요.",
|
|
977
1038
|
title: "고아 파일·문서 정리",
|
|
978
|
-
description: "전체
|
|
1039
|
+
description: "전체 보드에서 부모 글이 없는 파일·댓글, 삭제된 댓글의 이미지, 대상이 없는 좋아요를 조회합니다. 최근 24시간 내 파일과 사용 중인 파일, 신고 기록은 보존합니다.",
|
|
979
1040
|
scan: "고아 항목 조회",
|
|
980
1041
|
empty: "조회된 고아 항목이 없습니다.",
|
|
981
1042
|
selectAll: "전체 선택 ({count}개)",
|
|
@@ -991,11 +1052,11 @@ var _e = {
|
|
|
991
1052
|
},
|
|
992
1053
|
danger: {
|
|
993
1054
|
title: "위험 영역",
|
|
994
|
-
description: "모든 게시글, 댓글, 댓글 이미지, 첨부파일과 카테고리 설정을 영구 삭제합니다.
|
|
1055
|
+
description: "모든 게시글, 댓글, 댓글 이미지, 첨부파일과 카테고리 설정을 영구 삭제합니다. 보드 사용자와 관리자 권한은 유지합니다.",
|
|
995
1056
|
confirmLabel: "확인을 위해 ‘{phrase}’를 입력하세요",
|
|
996
|
-
phrase: "
|
|
1057
|
+
phrase: "보드 데이터 삭제",
|
|
997
1058
|
button: "모든 데이터 삭제",
|
|
998
|
-
done: "
|
|
1059
|
+
done: "보드 데이터를 모두 삭제했습니다."
|
|
999
1060
|
}
|
|
1000
1061
|
},
|
|
1001
1062
|
shareButton: { copied: "링크 복사됨" },
|
|
@@ -1035,14 +1096,37 @@ var _e = {
|
|
|
1035
1096
|
empty: "작성한 글이 없습니다."
|
|
1036
1097
|
},
|
|
1037
1098
|
users: {
|
|
1099
|
+
title: "보드 사용자",
|
|
1100
|
+
sort: {
|
|
1101
|
+
label: "사용자 정렬 기준",
|
|
1102
|
+
name: "이름순",
|
|
1103
|
+
role: "역할순",
|
|
1104
|
+
direction: "정렬 방향",
|
|
1105
|
+
asc: "오름차순",
|
|
1106
|
+
desc: "내림차순"
|
|
1107
|
+
},
|
|
1108
|
+
pagination: {
|
|
1109
|
+
label: "사용자 목록 페이지 이동",
|
|
1110
|
+
pageSize: "페이지당 사용자 수",
|
|
1111
|
+
perPage: "{count}명씩",
|
|
1112
|
+
status: "{page}페이지 · {count}명"
|
|
1113
|
+
},
|
|
1114
|
+
loadFailed: "사용자 목록을 불러오지 못했습니다.",
|
|
1038
1115
|
changeRoleFailed: "역할을 변경하지 못했습니다.",
|
|
1116
|
+
confirmRole: {
|
|
1117
|
+
title: "사용자 권한 변경",
|
|
1118
|
+
description: "{name}님의 역할을 {from}에서 {to}(으)로 변경하시겠습니까?",
|
|
1119
|
+
confirm: "변경하기"
|
|
1120
|
+
},
|
|
1039
1121
|
loading: "사용자 목록을 불러오고 있습니다.",
|
|
1040
1122
|
noPermission: {
|
|
1041
1123
|
title: "권한이 없습니다",
|
|
1042
|
-
description: "
|
|
1124
|
+
description: "보드 관리자만 사용자 역할을 관리할 수 있습니다."
|
|
1043
1125
|
},
|
|
1044
|
-
empty: "
|
|
1126
|
+
empty: "보드에 로그인한 사용자가 없습니다.",
|
|
1045
1127
|
noName: "이름 없음",
|
|
1128
|
+
noEmail: "이메일 없음",
|
|
1129
|
+
emailLoadFailed: "이메일을 불러오지 못했습니다.",
|
|
1046
1130
|
moderationWarnings: "콘텐츠 경고 {count}회"
|
|
1047
1131
|
},
|
|
1048
1132
|
writingAssistant: {
|
|
@@ -1069,7 +1153,7 @@ var _e = {
|
|
|
1069
1153
|
}
|
|
1070
1154
|
}
|
|
1071
1155
|
}, ve = {
|
|
1072
|
-
en: () => import("./en-
|
|
1156
|
+
en: () => import("./en-Dc7zQOy-.js"),
|
|
1073
1157
|
ja: () => import("./ja-xDttH9CE.js"),
|
|
1074
1158
|
de: () => import("./de-B4GZqxQj.js"),
|
|
1075
1159
|
fr: () => import("./fr-Zf5mcs8m.js"),
|
|
@@ -1231,4 +1315,4 @@ function Ae(e, t) {
|
|
|
1231
1315
|
//#endregion
|
|
1232
1316
|
export { f as $, b as A, se as B, R as C, v as D, N as E, k as F, de as G, oe as H, A as I, D as J, ne as K, O as L, S as M, ue as N, y as O, fe as P, te as Q, ae as R, I as S, ge as T, re as U, E as V, T as W, C as X, ce as Y, w as Z, me as _, Ee as a, P as b, Ce as c, Y as d, p as et, Z as f, z as g, G as h, we as i, x as j, _ as k, q as l, U as m, Oe as n, g as nt, Te as o, ye as p, le as q, ke as r, h as rt, Q as s, Ae as t, d as tt, X as u, he as v, L as w, F as x, pe as y, ie as z };
|
|
1233
1317
|
|
|
1234
|
-
//# sourceMappingURL=commentImages-
|
|
1318
|
+
//# sourceMappingURL=commentImages-mIfyNHgz.js.map
|