memi-board 0.44.0 → 0.45.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 (84) hide show
  1. package/README.md +54 -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/useMemiBoardModeration.d.ts +5 -1
  7. package/dist/composables/useMemiBoardReports.d.ts +6 -1
  8. package/dist/composables/useMemiBoardSections.d.ts +2 -1
  9. package/dist/composables/useMemiBoardUsers.d.ts +4 -0
  10. package/dist/composables/useMemiBoardWritingAssistant.d.ts +9 -1
  11. package/dist/config.d.ts +5 -0
  12. package/dist/de-ddWQNaqs.js +622 -0
  13. package/dist/de-ddWQNaqs.js.map +1 -0
  14. package/dist/en-BVUBLkxc.js +648 -0
  15. package/dist/en-BVUBLkxc.js.map +1 -0
  16. package/dist/es-7JHjGVlM.js +622 -0
  17. package/dist/es-7JHjGVlM.js.map +1 -0
  18. package/dist/fr-C3NCMNoy.js +622 -0
  19. package/dist/fr-C3NCMNoy.js.map +1 -0
  20. package/dist/id-C8hCAMPJ.js +622 -0
  21. package/dist/id-C8hCAMPJ.js.map +1 -0
  22. package/dist/index.d.ts +10 -1
  23. package/dist/index.js +1218 -1487
  24. package/dist/index.js.map +1 -1
  25. package/dist/ja-oTWri4hY.js +622 -0
  26. package/dist/ja-oTWri4hY.js.map +1 -0
  27. package/dist/module.mjs +55 -31
  28. package/dist/module.mjs.map +1 -1
  29. package/dist/pt-B9EH4scA.js +622 -0
  30. package/dist/pt-B9EH4scA.js.map +1 -0
  31. package/dist/runtime/components/Attachments.vue +4 -2
  32. package/dist/runtime/components/AuthorMenu.vue +14 -12
  33. package/dist/runtime/components/AvatarCropModal.vue +8 -5
  34. package/dist/runtime/components/CategorySettings.vue +65 -43
  35. package/dist/runtime/components/CommentFeed.vue +9 -7
  36. package/dist/runtime/components/CommentForm.vue +16 -10
  37. package/dist/runtime/components/CommentItem.vue +25 -26
  38. package/dist/runtime/components/CommentList.vue +7 -4
  39. package/dist/runtime/components/CommentThread.vue +7 -5
  40. package/dist/runtime/components/Detail.vue +20 -15
  41. package/dist/runtime/components/Editor.vue +145 -135
  42. package/dist/runtime/components/Footer.vue +14 -13
  43. package/dist/runtime/components/List.vue +31 -29
  44. package/dist/runtime/components/ListDefault.vue +4 -3
  45. package/dist/runtime/components/ListDense.vue +6 -5
  46. package/dist/runtime/components/ListImage.vue +4 -2
  47. package/dist/runtime/components/ListVideo.vue +3 -2
  48. package/dist/runtime/components/ListViewSwitch.vue +10 -6
  49. package/dist/runtime/components/MiniCard.vue +3 -2
  50. package/dist/runtime/components/MiniComments.vue +5 -4
  51. package/dist/runtime/components/MiniList.vue +3 -2
  52. package/dist/runtime/components/MiniPicker.vue +10 -9
  53. package/dist/runtime/components/MiniPost.vue +3 -2
  54. package/dist/runtime/components/MiniPosts.vue +5 -4
  55. package/dist/runtime/components/Profile.vue +12 -7
  56. package/dist/runtime/components/ProfileEditModal.vue +14 -13
  57. package/dist/runtime/components/ReportButton.vue +20 -10
  58. package/dist/runtime/components/ReportList.vue +11 -10
  59. package/dist/runtime/components/Section.vue +3 -2
  60. package/dist/runtime/components/Settings.vue +84 -62
  61. package/dist/runtime/components/ShareButton.vue +3 -1
  62. package/dist/runtime/components/SignIn.vue +14 -8
  63. package/dist/runtime/components/SwipeHint.vue +5 -3
  64. package/dist/runtime/components/UserCommentList.vue +8 -6
  65. package/dist/runtime/components/UserPostList.vue +6 -4
  66. package/dist/runtime/components/Users.vue +16 -10
  67. package/dist/runtime/composables/useMemiBoardSeo.ts +16 -10
  68. package/dist/slugify-6bG6GSO_.js +942 -0
  69. package/dist/slugify-6bG6GSO_.js.map +1 -0
  70. package/dist/storage.js +72 -71
  71. package/dist/storage.js.map +1 -1
  72. package/dist/types.d.ts +4 -0
  73. package/dist/utils/boardSeo.d.ts +5 -5
  74. package/dist/utils/compressImage.d.ts +2 -1
  75. package/dist/utils/formatDate.d.ts +6 -4
  76. package/dist/utils/imeSafeSubmit.d.ts +2 -0
  77. package/dist/utils/moderation-prompt.d.ts +18 -1
  78. package/dist/utils/moderation-strike.d.ts +2 -1
  79. package/dist/utils/section.d.ts +15 -2
  80. package/dist/zh-D5rDYV6E.js +622 -0
  81. package/dist/zh-D5rDYV6E.js.map +1 -0
  82. package/package.json +2 -3
  83. package/dist/slugify-Cm_pi2al.js +0 -124
  84. package/dist/slugify-Cm_pi2al.js.map +0 -1
@@ -1,12 +1,18 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref } from 'vue'
3
- import { BOARD_USER_ROLES, useMemiBoardAuth, useMemiBoardUsers } from 'memi-board/runtime'
3
+ import { BOARD_USER_ROLES, useMemiBoardAuth, useMemiBoardI18n, useMemiBoardUsers } from 'memi-board/runtime'
4
4
  import type { BoardUserRole } from 'memi-board/runtime'
5
5
 
6
6
  const props = withDefaults(defineProps<{ authorized?: boolean }>(), { authorized: false })
7
7
  const { isAdmin, rolePending } = useMemiBoardAuth()
8
8
  const canManage = computed(() => isAdmin.value || props.authorized)
9
9
  const { users, usersPending, updateUserRole } = useMemiBoardUsers({ enabled: canManage })
10
+ const { t, formatNumber } = useMemiBoardI18n()
11
+ const roleItems = computed(() => BOARD_USER_ROLES.map(item => ({
12
+ ...item,
13
+ label: t(`common.role.${item.value}`),
14
+ description: t(`boardUsers.roleDescription.${item.value}`),
15
+ })))
10
16
  const savingUid = ref<string | null>(null)
11
17
  const savedUid = ref<string | null>(null)
12
18
  const error = ref('')
@@ -21,7 +27,7 @@ async function changeRole(uid: string, role: BoardUserRole) {
21
27
  savedUid.value = uid
22
28
  }
23
29
  catch (cause) {
24
- error.value = cause instanceof Error ? cause.message : '역할을 변경하지 못했습니다.'
30
+ error.value = cause instanceof Error ? cause.message : t('users.changeRoleFailed')
25
31
  }
26
32
  finally {
27
33
  savingUid.value = null
@@ -32,25 +38,25 @@ async function changeRole(uid: string, role: BoardUserRole) {
32
38
  <template>
33
39
  <div class="flex flex-col gap-6">
34
40
  <div class="grid gap-3 md:grid-cols-3">
35
- <UAlert v-for="item in BOARD_USER_ROLES" :key="item.value" color="neutral" variant="subtle" :title="item.label" :description="item.description" />
41
+ <UAlert v-for="item in roleItems" :key="item.value" color="neutral" variant="subtle" :title="item.label" :description="item.description" />
36
42
  </div>
37
43
  <UAlert v-if="error" color="error" variant="subtle" icon="i-lucide-circle-alert" :description="error" />
38
- <div v-if="rolePending || usersPending" class="py-12 text-center text-sm text-muted">사용자 목록을 불러오고 있습니다.</div>
39
- <UAlert v-else-if="!canManage" color="error" variant="subtle" icon="i-lucide-shield-alert" title="권한이 없습니다" description="게시판 관리자만 사용자 역할을 관리할 수 있습니다." />
40
- <div v-else-if="users.length === 0" class="py-12 text-center text-sm text-muted">게시판에 로그인한 사용자가 없습니다.</div>
44
+ <div v-if="rolePending || usersPending" class="py-12 text-center text-sm text-muted">{{ t('users.loading') }}</div>
45
+ <UAlert v-else-if="!canManage" color="error" variant="subtle" icon="i-lucide-shield-alert" :title="t('users.noPermission.title')" :description="t('users.noPermission.description')" />
46
+ <div v-else-if="users.length === 0" class="py-12 text-center text-sm text-muted">{{ t('users.empty') }}</div>
41
47
  <div v-else class="divide-y divide-default rounded-lg border border-default">
42
48
  <div v-for="boardUser in users" :key="boardUser.id" class="grid gap-4 p-4 md:grid-cols-[minmax(0,1fr)_12rem] md:items-center">
43
49
  <div class="min-w-0">
44
50
  <div class="flex items-center gap-2">
45
- <p class="truncate font-medium text-highlighted">{{ boardUser.displayName || '이름 없음' }}</p>
46
- <UBadge v-if="savedUid === boardUser.id" color="success" variant="subtle" label="저장됨" />
51
+ <p class="truncate font-medium text-highlighted">{{ boardUser.displayName || t('users.noName') }}</p>
52
+ <UBadge v-if="savedUid === boardUser.id" color="success" variant="subtle" :label="t('common.action.saved')" />
47
53
  </div>
48
54
  <p class="truncate text-sm text-muted">{{ boardUser.id }}</p>
49
- <p v-if="boardUser.moderationBlockCount" class="mt-1 text-xs text-warning">콘텐츠 경고 {{ boardUser.moderationBlockCount }}회</p>
55
+ <p v-if="boardUser.moderationBlockCount" class="mt-1 text-xs text-warning">{{ t('users.moderationWarnings', { count: formatNumber(boardUser.moderationBlockCount) }) }}</p>
50
56
  </div>
51
57
  <USelect
52
58
  :model-value="boardUser.role || 'user'"
53
- :items="BOARD_USER_ROLES"
59
+ :items="roleItems"
54
60
  value-key="value"
55
61
  label-key="label"
56
62
  :loading="savingUid === boardUser.id"
@@ -22,6 +22,7 @@ import {
22
22
  resolvePublicSeoDb,
23
23
  toAbsoluteUrl,
24
24
  useMemiBoardConfig,
25
+ useMemiBoardI18n,
25
26
  type BoardListSeoPayload,
26
27
  type BoardPostSeoPayload,
27
28
  } from 'memi-board/runtime'
@@ -68,6 +69,8 @@ export async function useMemiBoardPostSeo(opts?: {
68
69
  const siteName = seo.siteName?.trim() || 'Board'
69
70
  const origin = resolveOrigin(seo.siteUrl)
70
71
  const basePath = normalizeBasePath(seo.basePath)
72
+ const { locale, intlLocale, t } = useMemiBoardI18n()
73
+ const ogLocale = computed(() => intlLocale.value.replace('-', '_'))
71
74
 
72
75
  if (seo.enabled === false) {
73
76
  return { post: computed(() => null as BoardPostSeoPayload | null) }
@@ -100,8 +103,9 @@ export async function useMemiBoardPostSeo(opts?: {
100
103
  post.value.categoryLabel,
101
104
  siteName,
102
105
  post.value.summary,
106
+ locale.value,
103
107
  )
104
- : `게시글 | ${siteName}`,
108
+ : t('seo.postFallbackTitle', { site: siteName }),
105
109
  )
106
110
  const description = computed(() =>
107
111
  post.value
@@ -109,8 +113,8 @@ export async function useMemiBoardPostSeo(opts?: {
109
113
  summary: post.value.summary,
110
114
  categoryLabel: post.value.categoryLabel,
111
115
  authorName: post.value.authorName,
112
- })
113
- : '게시판',
116
+ }, locale.value)
117
+ : t('seo.postFallbackDescription'),
114
118
  )
115
119
  const ogImage = computed(() => {
116
120
  const img = post.value?.previewImage || seo.defaultOgImage || ''
@@ -133,7 +137,7 @@ export async function useMemiBoardPostSeo(opts?: {
133
137
  ogUrl: canonical,
134
138
  ogType: 'article',
135
139
  ogSiteName: siteName,
136
- ogLocale: 'ko_KR',
140
+ ogLocale,
137
141
  twitterCard: 'summary_large_image',
138
142
  twitterTitle: title,
139
143
  twitterDescription: description,
@@ -174,6 +178,8 @@ export async function useMemiBoardListSeo(opts?: {
174
178
  const siteName = seo.siteName?.trim() || 'Board'
175
179
  const origin = resolveOrigin(seo.siteUrl)
176
180
  const basePath = normalizeBasePath(seo.basePath)
181
+ const { locale, intlLocale, t } = useMemiBoardI18n()
182
+ const ogLocale = computed(() => intlLocale.value.replace('-', '_'))
177
183
 
178
184
  if (seo.enabled === false) {
179
185
  return { list: computed(() => null as BoardListSeoPayload | null) }
@@ -189,20 +195,20 @@ export async function useMemiBoardListSeo(opts?: {
189
195
  const seoDb = resolvePublicSeoDb()
190
196
 
191
197
  const listAsync = useAsyncData(
192
- () => `memi-board-seo-list-${category.value || 'all'}`,
193
- () => fetchPublicListForSeo(category.value, seoDb),
194
- { watch: [category], server: true, lazy: false },
198
+ () => `memi-board-seo-list-${category.value || 'all'}-${locale.value}`,
199
+ () => fetchPublicListForSeo(category.value, seoDb, locale.value),
200
+ { watch: [category, locale], server: true, lazy: false },
195
201
  )
196
202
  const list = computed(() => listAsync.data.value ?? null)
197
203
 
198
204
  const title = computed(() =>
199
- boardListOgTitle(list.value?.categoryLabel || category.value, siteName),
205
+ boardListOgTitle(list.value?.categoryLabel || category.value, siteName, locale.value),
200
206
  )
201
207
  const description = computed(() => boardListOgDescription({
202
208
  categoryLabel: list.value?.categoryLabel,
203
209
  description: list.value?.description,
204
210
  recentTitles: list.value?.recentTitles,
205
- }))
211
+ }, locale.value))
206
212
  const ogImage = computed(() => {
207
213
  const img = list.value?.ogImage || seo.defaultOgImage || ''
208
214
  return origin ? toAbsoluteUrl(img, origin, seo.defaultOgImage || '') : img
@@ -235,7 +241,7 @@ export async function useMemiBoardListSeo(opts?: {
235
241
  ogUrl: canonical,
236
242
  ogType: 'website',
237
243
  ogSiteName: siteName,
238
- ogLocale: 'ko_KR',
244
+ ogLocale,
239
245
  twitterCard: 'summary_large_image',
240
246
  twitterTitle: title,
241
247
  twitterDescription: description,