memi-board 0.44.1 → 0.45.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +64 -3
  2. package/dist/ar-BCQdRw_S.js +622 -0
  3. package/dist/ar-BCQdRw_S.js.map +1 -0
  4. package/dist/composables/fetchPublicSeo.d.ts +4 -1
  5. package/dist/composables/useMemiBoardAutoTranslate.d.ts +33 -0
  6. package/dist/composables/useMemiBoardComments.d.ts +2 -3
  7. package/dist/composables/useMemiBoardModeration.d.ts +5 -1
  8. package/dist/composables/useMemiBoardReports.d.ts +6 -1
  9. package/dist/composables/useMemiBoardSections.d.ts +2 -1
  10. package/dist/composables/useMemiBoardUsers.d.ts +4 -0
  11. package/dist/composables/useMemiBoardWritingAssistant.d.ts +9 -1
  12. package/dist/config.d.ts +5 -0
  13. package/dist/de-ddWQNaqs.js +622 -0
  14. package/dist/de-ddWQNaqs.js.map +1 -0
  15. package/dist/en-DxkqyZq2.js +649 -0
  16. package/dist/en-DxkqyZq2.js.map +1 -0
  17. package/dist/es-7JHjGVlM.js +622 -0
  18. package/dist/es-7JHjGVlM.js.map +1 -0
  19. package/dist/fr-C3NCMNoy.js +622 -0
  20. package/dist/fr-C3NCMNoy.js.map +1 -0
  21. package/dist/id-C8hCAMPJ.js +622 -0
  22. package/dist/id-C8hCAMPJ.js.map +1 -0
  23. package/dist/index.d.ts +9 -1
  24. package/dist/index.js +1370 -1630
  25. package/dist/index.js.map +1 -1
  26. package/dist/ja-oTWri4hY.js +622 -0
  27. package/dist/ja-oTWri4hY.js.map +1 -0
  28. package/dist/module.mjs +55 -31
  29. package/dist/module.mjs.map +1 -1
  30. package/dist/pt-B9EH4scA.js +622 -0
  31. package/dist/pt-B9EH4scA.js.map +1 -0
  32. package/dist/runtime/components/Attachments.vue +4 -2
  33. package/dist/runtime/components/AuthorMenu.vue +14 -12
  34. package/dist/runtime/components/AvatarCropModal.vue +8 -5
  35. package/dist/runtime/components/CategorySettings.vue +68 -44
  36. package/dist/runtime/components/CommentFeed.vue +9 -7
  37. package/dist/runtime/components/CommentForm.vue +12 -10
  38. package/dist/runtime/components/CommentItem.vue +21 -26
  39. package/dist/runtime/components/CommentList.vue +7 -4
  40. package/dist/runtime/components/CommentThread.vue +8 -6
  41. package/dist/runtime/components/Detail.vue +20 -15
  42. package/dist/runtime/components/Editor.vue +142 -135
  43. package/dist/runtime/components/Footer.vue +14 -13
  44. package/dist/runtime/components/List.vue +31 -29
  45. package/dist/runtime/components/ListDefault.vue +4 -3
  46. package/dist/runtime/components/ListDense.vue +6 -5
  47. package/dist/runtime/components/ListImage.vue +4 -2
  48. package/dist/runtime/components/ListVideo.vue +3 -2
  49. package/dist/runtime/components/ListViewSwitch.vue +10 -6
  50. package/dist/runtime/components/MiniCard.vue +3 -2
  51. package/dist/runtime/components/MiniComments.vue +5 -4
  52. package/dist/runtime/components/MiniList.vue +3 -2
  53. package/dist/runtime/components/MiniPicker.vue +10 -9
  54. package/dist/runtime/components/MiniPost.vue +3 -2
  55. package/dist/runtime/components/MiniPosts.vue +5 -4
  56. package/dist/runtime/components/Profile.vue +12 -7
  57. package/dist/runtime/components/ProfileEditModal.vue +14 -13
  58. package/dist/runtime/components/ReportButton.vue +16 -10
  59. package/dist/runtime/components/ReportList.vue +11 -10
  60. package/dist/runtime/components/Section.vue +3 -2
  61. package/dist/runtime/components/Settings.vue +84 -62
  62. package/dist/runtime/components/ShareButton.vue +3 -1
  63. package/dist/runtime/components/SignIn.vue +10 -8
  64. package/dist/runtime/components/SwipeHint.vue +5 -3
  65. package/dist/runtime/components/UserCommentList.vue +8 -6
  66. package/dist/runtime/components/UserPostList.vue +6 -4
  67. package/dist/runtime/components/Users.vue +16 -10
  68. package/dist/runtime/composables/useMemiBoardSeo.ts +16 -10
  69. package/dist/slugify-BuOMFLxN.js +943 -0
  70. package/dist/slugify-BuOMFLxN.js.map +1 -0
  71. package/dist/storage.js +88 -74
  72. package/dist/storage.js.map +1 -1
  73. package/dist/types.d.ts +6 -0
  74. package/dist/utils/boardSeo.d.ts +5 -5
  75. package/dist/utils/compressImage.d.ts +2 -1
  76. package/dist/utils/deletePostCascade.d.ts +0 -2
  77. package/dist/utils/findReadablePost.d.ts +4 -0
  78. package/dist/utils/formatDate.d.ts +6 -4
  79. package/dist/utils/moderation-prompt.d.ts +18 -1
  80. package/dist/utils/moderation-strike.d.ts +2 -1
  81. package/dist/utils/saveBoardVisibility.d.ts +3 -0
  82. package/dist/utils/section.d.ts +15 -2
  83. package/dist/utils/subscribeCommentRange.d.ts +9 -0
  84. package/dist/zh-D5rDYV6E.js +622 -0
  85. package/dist/zh-D5rDYV6E.js.map +1 -0
  86. package/docs/firebase-setup.md +20 -1
  87. package/docs/firestore.rules.example +36 -7
  88. package/docs/storage.rules.example +25 -14
  89. package/package.json +2 -3
  90. package/dist/slugify-Cm_pi2al.js +0 -124
  91. package/dist/slugify-Cm_pi2al.js.map +0 -1
package/README.md CHANGED
@@ -146,6 +146,7 @@ export default defineNuxtConfig({
146
146
  | `seo.siteUrl` | `''` | 절대 URL origin. 비우면 `runtimeConfig.public.siteUrl` → 요청 origin 순 |
147
147
  | `seo.defaultOgImage` | `''` | 대표 이미지 없을 때 (상대경로 또는 `https://…`) |
148
148
  | `seo.basePath` | `'/board'` | canonical: `{base}`, `{base}/{category}`, `{base}/{category}/{slug}` |
149
+ | `locale` | `'ko'` | 기본 화면 언어. 호스트에 `@nuxtjs/i18n`이 있으면 그 언어가 우선 (아래 다국어 절) |
149
150
 
150
151
  #### routeRules 원칙
151
152
 
@@ -252,7 +253,57 @@ watch([categoryId, id], async ([category, slug]) => {
252
253
  @source "../../../node_modules/memi-board/dist/runtime/components";
253
254
  ```
254
255
 
255
- ### 5. 읽은 글 표시 (선택)
256
+ ### 5. 다국어 (@nuxtjs/i18n, 권장)
257
+
258
+ 게시판 UI는 10개 언어를 지원한다: `ko` `en` `ja` `de` `fr` `es` `pt` `zh` `ar` `id` (`MEMI_BOARD_LOCALES`).
259
+
260
+ - **언어 결정 순서**: 보드 설정의 "게시판 언어" → 호스트 `@nuxtjs/i18n`의 현재 locale → `memiBoard.locale` → `'ko'`.
261
+ - **번역 범위**: ko·en은 모든 문구, 나머지 8개 언어는 짧은 화면 문구만 번역돼 있다. 긴 안내문(삭제 확인 등)은 영어로 폴백된다.
262
+ - **로딩**: 언어별 번역 파일은 별도 청크로 그 언어를 쓸 때만 받는다(언어당 gzip 약 7KB). ko는 기본 번들에 들어 있다.
263
+ - **날짜**: `Intl`로 그 언어 형식에 맞춰 표시한다. dayjs는 더 이상 쓰지 않는다.
264
+ - **AI**: 검열·글쓰기 도우미 프롬프트도 보드 언어를 따른다(ko는 한국어 프롬프트, 그 외는 영어 프롬프트 + 해당 언어로 응답).
265
+ - **아랍어**: 게시판 목록·상세·글쓰기 루트에 `dir="rtl"`이 붙는다.
266
+
267
+ 호스트에 `@nuxtjs/i18n`을 설치하면 사이트 언어 전환에 게시판이 자동으로 따라간다. locale `code`는 위 코드와 같게 둔다(`en-US`처럼 써도 앞 두 글자로 맞춘다).
268
+
269
+ ```bash
270
+ pnpm add @nuxtjs/i18n
271
+ ```
272
+
273
+ ```ts
274
+ // nuxt.config.ts — '@nuxtjs/i18n'은 'memi-board'보다 앞에 둔다
275
+ modules: ['@nuxt/ui', '@nuxtjs/i18n', 'nuxt-vuefire', 'memi-board'],
276
+ i18n: {
277
+ locales: [
278
+ { code: 'ko', language: 'ko-KR', name: '한국어', file: 'ko.json' },
279
+ { code: 'en', language: 'en-US', name: 'English', file: 'en.json' },
280
+ // ja, de, fr, es, pt, zh, ar(dir: 'rtl'), id …
281
+ ],
282
+ defaultLocale: 'ko',
283
+ strategy: 'no_prefix',
284
+ langDir: 'locales/',
285
+ },
286
+ ```
287
+
288
+ Nuxt UI 내부 문구(닫기·검색 등)와 `<html lang dir>`도 맞추려면 `app.vue`에서:
289
+
290
+ ```vue
291
+ <script setup lang="ts">
292
+ import { ar, de, en, es, fr, id, ja, ko, pt_br, zh_cn } from '@nuxt/ui/locale'
293
+ const { locale } = useI18n()
294
+ const uiLocales = { ko, en, ja, de, fr, es, pt: pt_br, zh: zh_cn, ar, id }
295
+ const uiLocale = computed(() => uiLocales[locale.value as keyof typeof uiLocales] ?? ko)
296
+ useHead({ htmlAttrs: { lang: () => uiLocale.value.code, dir: () => uiLocale.value.dir } })
297
+ </script>
298
+
299
+ <template>
300
+ <UApp :locale="uiLocale">…</UApp>
301
+ </template>
302
+ ```
303
+
304
+ `@nuxtjs/i18n` 없이 한 언어로만 쓰려면 `memiBoard: { locale: 'en' }`만 지정한다. 호스트 화면에서 같은 번역을 쓰려면 `useMemiBoardI18n()`의 `t`, `formatRelativeDate` 등을 쓴다.
305
+
306
+ ### 6. 읽은 글 표시 (선택)
256
307
 
257
308
  목록·미니 컴포넌트의 글 링크는 `.memi-board-post-link` 클래스가 붙어 있고, 제목 텍스트는 `text-inherit`로 링크 색을 따른다. 패키지는 색을 강제하지 않으므로, 방문한 글을 흐리게 보이고 싶으면 호스트 CSS에 `:visited` 스타일만 추가하면 된다.
258
309
 
@@ -271,7 +322,7 @@ a.memi-board-post-link:visited {
271
322
 
272
323
  브라우저 네이티브 `:visited` 라서 서버·DB 저장이 필요 없다. 단, SPA 클라이언트 라우팅(`NuxtLink`)으로만 이동한 경우 브라우저에 "실제 방문"으로 기록되지 않을 수 있어 완전히 신뢰할 수는 없다 — 정확한 읽음 추적이 필요하면 별도로 방문 글 ID를 저장하는 방식을 구현해야 한다.
273
324
 
274
- ### 6. Security Rules + 인덱스 배포
325
+ ### 7. Security Rules + 인덱스 배포
275
326
 
276
327
  1. [docs/firestore.rules.example](docs/firestore.rules.example) → 호스트 `firestore.rules`에 병합
277
328
  (`memiBoardPosts`/`memiBoardComments`/`memiBoardLikes`/`memiBoardSettings`/`memiBoardUsers` — 전부 최상위 flat 컬렉션)
@@ -283,6 +334,7 @@ a.memi-board-post-link:visited {
283
334
  ```bash
284
335
  firebase deploy --only firestore:rules,storage,firestore:indexes
285
336
  ```
337
+ 6. Storage의 Firestore 조회 권한 설정: 배포 시 교차 서비스 IAM 역할 추가 안내를 승인하고, 호스트 Firebase 프로젝트의 Storage 서비스 계정에 `roles/firebaserules.firestoreServiceAgent`가 있는지 확인한다. [부모/호스트 설정 절차](docs/firebase-setup.md#storage-규칙의-firestore-조회-권한-부모호스트-프로젝트-필수-설정) 참고. CI 배포 성공만으로 이 권한 설정을 보장하지 않는다.
286
338
 
287
339
  **Rules를 파일에만 넣고 배포 안 하면** 글쓰기에서 `permission-denied` — 가장 흔한 설치 실수.
288
340
  **Storage 규칙을 앱이 쓰는 버킷이 아닌 곳에 배포하면** 본문 이미지에서 `storage/unauthorized`.
@@ -293,7 +345,7 @@ a.memi-board-post-link:visited {
293
345
 
294
346
  > ⚠️ npm 패키지 tarball에도 `docs/` 가 포함된다(0.4.7+). 구버전이면 GitHub의 `docs/`를 보면 된다.
295
347
 
296
- ### 7. 최초 관리자와 카테고리 만들기
348
+ ### 8. 최초 관리자와 카테고리 만들기
297
349
 
298
350
  Rules는 일반 사용자가 스스로 관리자가 되는 것을 막는다. 따라서 최초 한 번은 다음 순서가 필요하다.
299
351
 
@@ -538,6 +590,7 @@ export default defineNuxtPlugin(() => {
538
590
  - [ ] CSS `@source` …/memi-board/dist/runtime/components
539
591
  - [ ] (선택) `.memi-board-post-link:visited` 읽은 글 표시 CSS
540
592
  - [ ] Rules 예시 병합 + **deploy**
593
+ - [ ] Storage 서비스 계정의 Firestore 조회 IAM 역할 확인 ([호스트 설정](docs/firebase-setup.md#storage-규칙의-firestore-조회-권한-부모호스트-프로젝트-필수-설정))
541
594
  - [ ] 카테고리·listed 쿼리 사용 시 indexes deploy
542
595
  - [ ] 페이지·미들웨어 직접 작성 (또는 playground 복사)
543
596
  - [ ] 최초 로그인 후 Firebase Console에서 `memiBoardUsers/{uid}.role = 'admin'`
@@ -570,6 +623,12 @@ App Check 토큰, `appId` 일치, 모델명(3.x), AI Logic 활성화, 네트워
570
623
  3. 호스트 `storage.rules`에 `docs/storage.rules.example`의 `memiBoardPosts/{postId}/images` 규칙이 있는지 확인. 쓰기는 `image/*` 이어야 한다(붙여넣기 PNG).
571
624
  4. `firebase deploy --only storage` 후 다시 올려 본다.
572
625
 
626
+ ### ZIP 등 첨부파일 `storage/unauthorized`
627
+
628
+ `memiBoardPosts/{postId}/attachments/...`에서 실패하면 로그인 상태, 앱이 쓰는 버킷에 배포된 첨부 규칙, **파일당 20MiB 미만** 조건을 먼저 확인한다. 예시 규칙은 ZIP의 MIME 형식이나 `.exe.zip` 이름을 차단하지 않는다.
629
+
630
+ 조건을 충족해도 실패하면 **호스트 Firebase 프로젝트의 Storage 서비스 계정에 Firestore 조회 역할이 있는지** 확인한다. 첨부 규칙은 `postExists`·`isPostOwner`·`isBoardStaff`에서 Firestore를 조회한다. 조회 권한이 없으면 새 글의 문서 존재 여부 확인부터 실패할 수 있다. [부모/호스트 설정 절차](docs/firebase-setup.md#storage-규칙의-firestore-조회-권한-부모호스트-프로젝트-필수-설정)에 따라 `roles/firebaserules.firestoreServiceAgent`를 설정한다.
631
+
573
632
  ### 글 삭제 `storage/unauthorized`
574
633
 
575
634
  작성자 본인 글 삭제가 `User does not have permission to access 'memiBoardPosts/{postId}'` 로 실패하면, 파일 삭제가 아니라 **폴더 목록(`listAll`)** 이 막힌 것이다.
@@ -586,6 +645,8 @@ match /memiBoardPosts/{postId}/{allPaths=**} {
586
645
 
587
646
  `write` 는 이 블록에 넣지 않는다(용량·`image/*` 제한은 `images`/`attachments` match 가 담당). `firebase deploy --only storage` 후 다시 삭제한다. 0.38.1+ 패키지는 `images/`·`attachments/`만 목록으로 비운다.
588
647
 
648
+ 목록 조회는 성공하지만 **개별 파일 삭제**가 거부되면, 위 [첨부파일 오류](#zip-등-첨부파일-storageunauthorized)와 같은 Firestore 조회 IAM 역할도 확인한다. 삭제 규칙 역시 글 소유권·사용자 역할을 Firestore에서 읽는다.
649
+
589
650
  ### monorepo `link:` / `file:` 로 개발할 때
590
651
 
591
652
  링크하기 전에 패키지 의존성과 `dist`를 준비한다.