memi-board 0.56.2 → 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.
Files changed (46) hide show
  1. package/README.md +40 -8
  2. package/dist/{commentImages-DsW2yCqu.js → commentImages-mIfyNHgz.js} +143 -59
  3. package/dist/commentImages-mIfyNHgz.js.map +1 -0
  4. package/dist/composables/useMemiBoardModeration.d.ts +2 -0
  5. package/dist/composables/useMemiBoardPosts.d.ts +1 -1
  6. package/dist/composables/useMemiBoardUserDirectory.d.ts +20 -0
  7. package/dist/composables/useMemiBoardUserProfile.d.ts +1 -1
  8. package/dist/composables/useMemiBoardUsers.d.ts +1 -1
  9. package/dist/{en-BDkfivhc.js → en-Dc7zQOy-.js} +85 -1
  10. package/dist/en-Dc7zQOy-.js.map +1 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +921 -711
  13. package/dist/index.js.map +1 -1
  14. package/dist/module.mjs +4 -0
  15. package/dist/module.mjs.map +1 -1
  16. package/dist/runtime/components/AccountDeletion.vue +64 -0
  17. package/dist/runtime/components/AuthorMenu.vue +4 -4
  18. package/dist/runtime/components/CommentFeed.vue +17 -13
  19. package/dist/runtime/components/CommentForm.vue +58 -56
  20. package/dist/runtime/components/CommentImageField.vue +6 -3
  21. package/dist/runtime/components/CommentItem.vue +16 -3
  22. package/dist/runtime/components/CommentList.vue +10 -1
  23. package/dist/runtime/components/CommentThread.vue +16 -2
  24. package/dist/runtime/components/Detail.vue +7 -1
  25. package/dist/runtime/components/Editor.vue +1 -5
  26. package/dist/runtime/components/Profile.vue +14 -2
  27. package/dist/runtime/components/ProfileEditModal.vue +20 -3
  28. package/dist/runtime/components/UserListControls.vue +29 -0
  29. package/dist/runtime/components/UserListPagination.vue +17 -0
  30. package/dist/runtime/components/UserManagementEmail.vue +21 -0
  31. package/dist/runtime/components/UserRoleGuide.vue +38 -0
  32. package/dist/runtime/components/Users.vue +21 -59
  33. package/dist/runtime/components/UsersSettings.vue +119 -0
  34. package/dist/storage.js +1 -1
  35. package/dist/types.d.ts +2 -0
  36. package/dist/utils/postSlug.d.ts +12 -0
  37. package/dist/utils/profileNameModeration.d.ts +8 -0
  38. package/dist/utils/userDirectoryPage.d.ts +19 -0
  39. package/docs/account-deletion.md +60 -0
  40. package/docs/firestore.rules.example +61 -2
  41. package/docs/host-integration.md +72 -9
  42. package/docs/storage.rules.example +7 -2
  43. package/package.json +3 -2
  44. package/scripts/backfill-post-slugs.mjs +38 -0
  45. package/dist/commentImages-DsW2yCqu.js.map +0 -1
  46. 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 `MemiBoardUsers` at `/board-users`.
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
- Use `authorized` 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. Adding host route guards is recommended so admin URLs are not exposed to regular users.
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: "삭제할 게시판 ID가 필요합니다.",
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: "이 게시판은 Nuxt 4와 Vue 3, TypeScript를 바탕으로 만들었어요. Nuxt UI와 Tailwind CSS로 편안한 화면을 구성하고, Firebase Firestore·Auth·Storage와 nuxt-vuefire로 글과 댓글을 자연스럽게 이어갑니다.",
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: "게시판·폼·모달 UI",
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} 게시판 | {site}",
760
- listTitleAll: "게시판 | {site}",
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: "게시판 {count}개에 적용",
940
+ title: "모든 보드에 적용",
941
+ help: "체크한 항목만 모든 보드에 덮어씁니다. 보드별로 편집 중인 다른 값은 저장하지 않습니다.",
942
+ apply: "보드 {count}개에 적용",
882
943
  applyShort: "적용",
883
- confirmTitle: "모든 게시판에 적용",
884
- confirm: "체크한 항목을 게시판 {count}개에 적용할까요?",
885
- applied: "게시판 {count}개에 적용했습니다.",
886
- translateUntranslated: "번역 안 된 게시판 번역 ({count})",
887
- translated: "게시판 {count}개의 이름·설명을 번역해 저장했습니다.",
888
- translateFailed: "게시판 {count}개는 번역하지 못했습니다. 다시 시도해 주세요."
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: "입력한 이름이 게시판 주소가 됩니다. 40자까지이고, 공백은 - 가 되며, 영문·숫자·한글만 남습니다.",
975
+ newCategoryPlaceholder: "새 보드 이름",
976
+ newCategoryHelp: "입력한 이름이 보드 주소가 됩니다. 40자까지이고, 공백은 - 가 되며, 영문·숫자·한글만 남습니다.",
916
977
  newCategoryUrl: "주소: {path}",
917
- deleteConfirm: "‘{label}’ 게시판을 삭제하시겠습니까?\n\n이 게시판의 모든 게시물, 댓글, 대댓글, 본문 이미지, 댓글 이미지와 첨부파일이 영구 삭제됩니다. 이 작업은 되돌릴 수 없습니다.",
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: "켜면 게시판 이름·설명과 새 글·댓글을 {count}개 언어로 자동 번역해 함께 저장합니다. 끄면 원문만 보입니다.",
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: "번역이 없거나 원문과 달라진 글을 찾아 한꺼번에 번역하고 저장합니다. 자동 번역을 끈 게시판이나, 번역이 빠진 글을 채울 때 쓰세요. AI 사용 횟수는 차감하지 않습니다.",
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: "전체 게시판에서 부모 글이 없는 파일·댓글, 삭제된 댓글의 이미지, 대상이 없는 좋아요를 조회합니다. 최근 24시간 내 파일과 사용 중인 파일, 신고 기록은 보존합니다.",
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-BDkfivhc.js"),
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-DsW2yCqu.js.map
1318
+ //# sourceMappingURL=commentImages-mIfyNHgz.js.map