memi-board 0.63.0 → 0.64.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.
@@ -3,7 +3,7 @@ import MemiBoardSurface from './BoardSurface.vue'
3
3
  import { ref, computed, onBeforeUnmount, onMounted, watch } from 'vue'
4
4
  import Youtube from '@tiptap/extension-youtube'
5
5
  import type { PostModel } from 'memi-board/runtime'
6
- import { displayPostContent, displayPostTitle, POST_EDIT_LIMIT, readExtend, renderMarkdownToHtml, useMemiBoardI18n } from 'memi-board/runtime'
6
+ import { displayPostContent, displayPostTitle, POST_EDIT_LIMIT, extendEntries, renderMarkdownToHtml, useMemiBoardI18n } from 'memi-board/runtime'
7
7
  import { useMemiBoardPost, useMemiBoardPosts } from 'memi-board/runtime'
8
8
  import { useMemiBoardAuth } from 'memi-board/runtime'
9
9
  import { useMemiBoardSettings } from 'memi-board/runtime'
@@ -49,13 +49,7 @@ const { recordView } = useMemiBoardViews()
49
49
  const { post, pending: loading } = useMemiBoardPost(() => props.boardId, computed(() => props.postId))
50
50
  const notFound = computed(() => !loading.value && !post.value)
51
51
  const isDraft = computed(() => post.value?.isPublished === false)
52
- const shownExtend = computed(() => {
53
- const values = readExtend(post.value?.extend)
54
- return (getBoard(props.boardId)?.customFields ?? []).flatMap((field) => {
55
- const value = values[field.key]
56
- return value ? [{ key: field.key, label: field.label, value }] : []
57
- })
58
- })
52
+ const shownExtend = computed(() => extendEntries(post.value?.extend, getBoard(props.boardId)?.customFields))
59
53
  const deleting = ref(false)
60
54
  const publishing = ref(false)
61
55
  const pinning = ref(false)
@@ -2,7 +2,7 @@
2
2
  import MemiBoardSurface from './BoardSurface.vue'
3
3
  import { resolveComponent } from 'vue'
4
4
  import type { PostModel } from 'memi-board/runtime'
5
- import { displayPostSummary, displayPostTitle, useMemiBoardI18n } from 'memi-board/runtime'
5
+ import { displayPostSummary, displayPostTitle, extendEntries, useMemiBoardI18n, useMemiBoardSettings } from 'memi-board/runtime'
6
6
  import MemiBoardAuthorMenu from './AuthorMenu.vue'
7
7
 
8
8
  defineProps<{
@@ -17,6 +17,14 @@ defineProps<{
17
17
  const emit = defineEmits<{ select: [post: PostModel] }>()
18
18
  const NuxtLink = resolveComponent('NuxtLink')
19
19
  const { t, locale, formatRelativeDate } = useMemiBoardI18n()
20
+ const { getBoard } = useMemiBoardSettings()
21
+
22
+ /** 목록에서는 필드를 이만큼만 보여 주고 나머지는 +N 으로 줄인다. */
23
+ const EXTEND_SHOWN = 3
24
+ function extendOf(post: PostModel) {
25
+ const entries = extendEntries(post.extend, getBoard(post.boardId || post.category || '')?.customFields)
26
+ return { shown: entries.slice(0, EXTEND_SHOWN), more: Math.max(0, entries.length - EXTEND_SHOWN), all: entries }
27
+ }
20
28
 
21
29
  function titleOf(post: PostModel) {
22
30
  return displayPostTitle(post, locale.value).trim() || displayPostSummary(post, locale.value).trim() || t('list.photo')
@@ -60,6 +68,19 @@ function image(post: PostModel): string | undefined {
60
68
  <p v-if="summaryOf(post)" class="line-clamp-3 break-words whitespace-pre-line text-sm leading-relaxed text-muted">
61
69
  {{ summaryOf(post) }}
62
70
  </p>
71
+ <dl v-if="extendOf(post).shown.length" class="grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-1 rounded-lg bg-elevated/50 px-3 py-2 text-xs">
72
+ <template v-for="field in extendOf(post).shown" :key="field.key">
73
+ <dt class="max-w-28 truncate text-muted">{{ field.label }}</dt>
74
+ <dd class="truncate font-medium text-default" :title="field.value">{{ field.value }}</dd>
75
+ </template>
76
+ <dd
77
+ v-if="extendOf(post).more"
78
+ class="col-span-2 text-dimmed"
79
+ :title="extendOf(post).all.slice(3).map(field => `${field.label}: ${field.value}`).join('\n')"
80
+ >
81
+ +{{ extendOf(post).more }}
82
+ </dd>
83
+ </dl>
63
84
  <p class="mt-auto pt-1 text-xs text-dimmed">
64
85
  <time :datetime="post.createdAt?.toDate?.().toISOString()">{{ formatRelativeDate(post.createdAt, now) }}</time>
65
86
  <template v-if="(post.commentCount ?? 0) > 0"> · {{ t('list.commentCount', { count: post.commentCount }) }}</template>
@@ -2,7 +2,7 @@
2
2
  import MemiBoardSurface from './BoardSurface.vue'
3
3
  import { resolveComponent } from 'vue'
4
4
  import type { PostModel } from 'memi-board/runtime'
5
- import { displayPostSummary, displayPostTitle, useMemiBoardI18n, useMemiBoardSettings } from 'memi-board/runtime'
5
+ import { displayPostSummary, displayPostTitle, extendEntries, useMemiBoardI18n, useMemiBoardSettings } from 'memi-board/runtime'
6
6
  import MemiBoardAuthorMenu from './AuthorMenu.vue'
7
7
 
8
8
  withDefaults(defineProps<{
@@ -15,7 +15,7 @@ withDefaults(defineProps<{
15
15
  authorCommentsTo?: (authorUid: string) => string | undefined
16
16
  }>(), { showCategory: true })
17
17
  const emit = defineEmits<{ select: [post: PostModel] }>()
18
- const { categoryLabel } = useMemiBoardSettings()
18
+ const { categoryLabel, getBoard } = useMemiBoardSettings()
19
19
  const { t, locale, formatRelativeDate, formatTimestampDetails } = useMemiBoardI18n()
20
20
 
21
21
  function titleOf(post: PostModel) {
@@ -29,6 +29,13 @@ function summaryOf(post: PostModel) {
29
29
  }
30
30
  const NuxtLink = resolveComponent('NuxtLink')
31
31
 
32
+ /** 목록에서는 필드를 이만큼만 보여 주고 나머지는 +N 으로 줄인다. */
33
+ const EXTEND_SHOWN = 3
34
+ function extendOf(post: PostModel) {
35
+ const entries = extendEntries(post.extend, getBoard(post.boardId || post.category || '')?.customFields)
36
+ return { shown: entries.slice(0, EXTEND_SHOWN), more: Math.max(0, entries.length - EXTEND_SHOWN), all: entries }
37
+ }
38
+
32
39
  function image(post: PostModel): string | undefined {
33
40
  return post.previewImage || post.attachments?.find(item => item.type.startsWith('image/'))?.url
34
41
  }
@@ -88,6 +95,29 @@ function image(post: PostModel): string | undefined {
88
95
  >
89
96
  {{ summaryOf(post) }}
90
97
  </p>
98
+ <div v-if="extendOf(post).shown.length" class="flex min-w-0 flex-wrap items-center gap-1 pt-0.5">
99
+ <UBadge
100
+ v-for="field in extendOf(post).shown"
101
+ :key="field.key"
102
+ color="neutral"
103
+ variant="outline"
104
+ size="sm"
105
+ class="max-w-48 min-w-0 font-normal"
106
+ :title="`${field.label}: ${field.value}`"
107
+ >
108
+ <span class="shrink-0 text-dimmed">{{ field.label }}</span>
109
+ <span class="truncate text-default">{{ field.value }}</span>
110
+ </UBadge>
111
+ <UBadge
112
+ v-if="extendOf(post).more"
113
+ color="neutral"
114
+ variant="soft"
115
+ size="sm"
116
+ class="font-normal"
117
+ :label="`+${extendOf(post).more}`"
118
+ :title="extendOf(post).all.slice(3).map(field => `${field.label}: ${field.value}`).join('\n')"
119
+ />
120
+ </div>
91
121
  </div>
92
122
 
93
123
  <div
@@ -31,3 +31,9 @@ export declare function prepareCustomFields(fields: BoardCustomField[] | undefin
31
31
  export declare function readExtend(value: unknown): Record<string, string>;
32
32
  /** 보드에 선언된 키의 값만 모은다. 비어 있는 칸은 저장하지 않는다. */
33
33
  export declare function extendForFields(values: Record<string, string>, fields: BoardCustomField[]): Record<string, string>;
34
+ /** 글의 extend 를 보드 필드 순서대로, 값이 있는 것만 이름과 함께 돌려준다. */
35
+ export declare function extendEntries(extend: unknown, fields: BoardCustomField[] | undefined): Array<{
36
+ key: string;
37
+ label: string;
38
+ value: string;
39
+ }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memi-board",
3
- "version": "0.63.0",
3
+ "version": "0.64.0",
4
4
  "type": "module",
5
5
  "description": "Board (forum) module for Nuxt 4 + Nuxt UI + VueFire/Firebase",
6
6
  "license": "MIT",