memi-board 0.9.1 → 0.10.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.
- package/README.md +110 -43
- package/dist/composables/useMemiBoardAuth.d.ts +1 -0
- package/dist/composables/useMemiBoardComments.d.ts +63 -0
- package/dist/composables/useMemiBoardStorage.d.ts +2 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +739 -203
- package/dist/index.js.map +1 -1
- package/dist/module.d.ts +2 -1
- package/dist/module.mjs +51 -30
- package/dist/module.mjs.map +1 -1
- package/dist/runtime/components/Attachments.vue +2 -2
- package/dist/runtime/components/CategorySettings.vue +90 -0
- package/dist/runtime/components/CommentForm.vue +6 -4
- package/dist/runtime/components/CommentItem.vue +187 -22
- package/dist/runtime/components/CommentList.vue +1 -1
- package/dist/runtime/components/CommentThread.vue +4 -2
- package/dist/runtime/components/Detail.vue +26 -8
- package/dist/runtime/components/Editor.vue +6 -6
- package/dist/runtime/components/Footer.vue +45 -0
- package/dist/runtime/components/List.vue +163 -4
- package/dist/runtime/components/ListDefault.vue +17 -5
- package/dist/runtime/components/ListImage.vue +17 -5
- package/dist/runtime/components/ListVideo.vue +17 -5
- package/dist/runtime/components/Settings.vue +21 -9
- package/dist/runtime/components/SignIn.vue +2 -2
- package/dist/runtime/components/Users.vue +2 -2
- package/dist/runtime/components/VersionHistory.vue +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/utils/formatDate.d.ts +4 -0
- package/docs/firebase-setup.md +1 -1
- package/docs/firestore.rules.example +37 -5
- package/package.json +20 -43
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Nuxt 4 + Nuxt UI + Firebase 프로젝트에 **클라이언트 전용 게시판**
|
|
|
14
14
|
|
|
15
15
|
| 항목 | 요구 |
|
|
16
16
|
|------|------|
|
|
17
|
-
| 런타임 | Nuxt **4+**
|
|
17
|
+
| 런타임 | Nuxt **4+** |
|
|
18
18
|
| UI | `@nuxt/ui` **4+** (컴포넌트가 `UButton` 등 auto-import 전제) |
|
|
19
19
|
| 데이터 | Firebase **Firestore + Auth + Storage** |
|
|
20
20
|
| Vue | `vue` 3.5+, `vuefire` 3.2+, `firebase` 12+ |
|
|
@@ -22,21 +22,20 @@ Nuxt 4 + Nuxt UI + Firebase 프로젝트에 **클라이언트 전용 게시판**
|
|
|
22
22
|
|
|
23
23
|
**의도적으로 하지 않는 일:** Firebase 초기화, 라우트/미들웨어 등록, App Check 초기화, 서버 검열.
|
|
24
24
|
|
|
25
|
-
## 구조
|
|
25
|
+
## 구조
|
|
26
26
|
|
|
27
27
|
| 엔트리 | 역할 |
|
|
28
28
|
|--------|------|
|
|
29
|
-
| `memi-board` | **
|
|
30
|
-
| `memi-board/
|
|
29
|
+
| `memi-board` | **Nuxt 모듈** — 설정, SFC auto-import, peer dedupe, `dayjs` 개발 서버 최적화 |
|
|
30
|
+
| `memi-board/runtime` | composables, types, 유틸리티 (**Vue 컴포넌트 export 없음**) |
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
일반 사용자는 `nuxt.config.ts`의 `memiBoard`만 설정한다. 별도 Nuxt 플러그인은 필요 없다.
|
|
33
33
|
|
|
34
34
|
```
|
|
35
35
|
호스트 Nuxt 앱
|
|
36
36
|
├── nuxt-vuefire + firebaseConfig ← 인증·Firestore
|
|
37
37
|
├── (선택) App Check 플러그인 ← AI Logic Enforce 시 필수에 가까움
|
|
38
|
-
├── memi-board
|
|
39
|
-
├── plugins/memi-board.ts ← configureMemiBoard
|
|
38
|
+
├── memi-board ← Nuxt 모듈 + 런타임 설정
|
|
40
39
|
├── pages/board/* ← 라우트는 호스트 소유
|
|
41
40
|
└── firestore.rules + storage.rules ← docs 예시 병합 후 배포
|
|
42
41
|
```
|
|
@@ -46,10 +45,10 @@ Nuxt 4 + Nuxt UI + Firebase 프로젝트에 **클라이언트 전용 게시판**
|
|
|
46
45
|
**사전 준비:** Node + pnpm, Firebase 프로젝트(Firestore·Storage·Auth). 처음부터면 → [docs/firebase-setup.md](docs/firebase-setup.md).
|
|
47
46
|
|
|
48
47
|
```bash
|
|
49
|
-
pnpm add memi-board @nuxt/ui nuxt-vuefire vuefire firebase
|
|
48
|
+
pnpm add memi-board @nuxt/ui nuxt-vuefire vuefire firebase dayjs
|
|
50
49
|
```
|
|
51
50
|
|
|
52
|
-
`vuefire` / `firebase` / `nuxt-vuefire`
|
|
51
|
+
`vuefire` / `firebase` / `nuxt-vuefire` / `dayjs`는 **호스트에 직접 설치**한다. TipTap은 `@nuxt/ui`와 `memi-board`가 관리하므로 호스트에서 별도로 설치하거나 버전을 맞출 필요가 없다.
|
|
53
52
|
|
|
54
53
|
### 1. modules + vuefire
|
|
55
54
|
|
|
@@ -59,8 +58,21 @@ export default defineNuxtConfig({
|
|
|
59
58
|
modules: [
|
|
60
59
|
'@nuxt/ui',
|
|
61
60
|
'nuxt-vuefire',
|
|
62
|
-
'memi-board
|
|
61
|
+
'memi-board',
|
|
63
62
|
],
|
|
63
|
+
memiBoard: {
|
|
64
|
+
// Firestore: {prefix}Posts, {prefix}Users, {prefix}Settings
|
|
65
|
+
collectionPrefix: 'board',
|
|
66
|
+
auth: { providers: ['google', 'apple'] }, // + 'emailPassword'
|
|
67
|
+
moderation: {
|
|
68
|
+
enabled: true,
|
|
69
|
+
model: 'gemini-3.5-flash-lite',
|
|
70
|
+
onError: 'block', // 'allow' | 'block'
|
|
71
|
+
moderateImages: false,
|
|
72
|
+
useLimitedUseAppCheckTokens: false,
|
|
73
|
+
// localBlocklist: ['금칙어'],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
64
76
|
vuefire: {
|
|
65
77
|
// 콘솔 → 프로젝트 설정 → 내 앱 → 웹 앱 SDK snippet 과 동일해야 함
|
|
66
78
|
// (appId 가 다른 웹 앱이면 App Check / AI Logic 이 실패한다)
|
|
@@ -74,33 +86,18 @@ export default defineNuxtConfig({
|
|
|
74
86
|
},
|
|
75
87
|
auth: { enabled: true },
|
|
76
88
|
},
|
|
89
|
+
routeRules: {
|
|
90
|
+
// 게시판은 Firebase Auth/Firestore를 사용하는 클라이언트 전용 화면이다.
|
|
91
|
+
'/board/**': { ssr: false },
|
|
92
|
+
'/board-settings': { ssr: false },
|
|
93
|
+
'/board-users': { ssr: false },
|
|
94
|
+
},
|
|
77
95
|
})
|
|
78
96
|
```
|
|
79
97
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
```ts
|
|
83
|
-
// app/plugins/memi-board.ts
|
|
84
|
-
import { configureMemiBoard } from 'memi-board'
|
|
85
|
-
|
|
86
|
-
export default defineNuxtPlugin(() => {
|
|
87
|
-
configureMemiBoard({
|
|
88
|
-
// Firestore: {prefix}Posts, {prefix}Users, {prefix}Settings
|
|
89
|
-
collectionPrefix: 'board',
|
|
90
|
-
auth: { providers: ['google', 'apple'] }, // + 'emailPassword'
|
|
91
|
-
moderation: {
|
|
92
|
-
enabled: true,
|
|
93
|
-
model: 'gemini-3.5-flash-lite', // 권장. 2.5-flash 는 신규 프로젝트에서 미제공일 수 있음
|
|
94
|
-
onError: 'block', // 'allow' | 'block' — AI 실패 시 저장 허용/거부 (기본 allow)
|
|
95
|
-
moderateImages: false,
|
|
96
|
-
useLimitedUseAppCheckTokens: false, // App Check 세션 토큰으로도 가능
|
|
97
|
-
// localBlocklist: ['금칙어'],
|
|
98
|
-
},
|
|
99
|
-
})
|
|
100
|
-
})
|
|
101
|
-
```
|
|
98
|
+
`memiBoard` 옵션은 Nuxt 모듈이 런타임 플러그인으로 자동 전달한다. `app/plugins/memi-board.ts`를 만들지 않는다.
|
|
102
99
|
|
|
103
|
-
###
|
|
100
|
+
### 2. Tailwind `@source` (필수)
|
|
104
101
|
|
|
105
102
|
패키지 SFC 안의 클래스가 purge되지 않도록:
|
|
106
103
|
|
|
@@ -111,7 +108,7 @@ export default defineNuxtPlugin(() => {
|
|
|
111
108
|
@source "../../../node_modules/memi-board/dist/runtime/components";
|
|
112
109
|
```
|
|
113
110
|
|
|
114
|
-
###
|
|
111
|
+
### 3. Security Rules + 인덱스 배포
|
|
115
112
|
|
|
116
113
|
1. [docs/firestore.rules.example](docs/firestore.rules.example) → 호스트 `firestore.rules`에 병합
|
|
117
114
|
(`memiBoard*` 이름을 쓰는 `collectionPrefix`에 맞게 치환)
|
|
@@ -145,15 +142,29 @@ export default defineNuxtPlugin(() => {
|
|
|
145
142
|
|
|
146
143
|
> ⚠️ npm 패키지 tarball에도 `docs/` 가 포함된다(0.4.7+). 구버전이면 GitHub의 `docs/`를 보면 된다.
|
|
147
144
|
|
|
145
|
+
### 4. 최초 관리자와 카테고리 만들기
|
|
146
|
+
|
|
147
|
+
Rules는 일반 사용자가 스스로 관리자가 되는 것을 막는다. 따라서 최초 한 번은 다음 순서가 필요하다.
|
|
148
|
+
|
|
149
|
+
1. 아래 게시판 페이지를 만든 뒤 Google 또는 Apple로 로그인한다.
|
|
150
|
+
2. Firebase Console → Firestore에서 `{prefix}Users/{uid}` 문서를 찾는다.
|
|
151
|
+
3. 해당 문서의 `role`을 문자열 `admin`으로 변경한다.
|
|
152
|
+
- `collectionPrefix: 'board'`라면 `boardUsers/{uid}`
|
|
153
|
+
- UID는 Firebase Console → Authentication → Users에서도 확인할 수 있다.
|
|
154
|
+
4. `/board-settings`에서 첫 카테고리를 만든다. 카테고리가 없으면 글을 작성할 수 없다.
|
|
155
|
+
5. 이후 역할 변경은 `/board-users`의 `MemiBoardUsers`에서 처리한다.
|
|
156
|
+
|
|
157
|
+
> 최초 관리자 지정은 신뢰할 수 있는 프로젝트 운영자가 Firebase Console에서만 수행한다. 클라이언트 코드에 관리자 UID나 우회 규칙을 넣지 않는다.
|
|
158
|
+
|
|
148
159
|
## 사용법
|
|
149
160
|
|
|
150
161
|
라우팅·미들웨어는 **호스트 소유**. 컴포넌트는 모듈 auto-import (`MemiBoardList` 등).
|
|
151
|
-
`import { MemiBoardEditor } from 'memi-board'
|
|
162
|
+
`import { MemiBoardEditor } from 'memi-board/runtime'`는 **안 된다**. 컴포넌트는 모듈이 auto-import하고, composable과 type만 `memi-board/runtime`에서 명시적으로 가져올 수 있다.
|
|
152
163
|
|
|
153
164
|
```vue
|
|
154
165
|
<!-- app/pages/board/index.vue -->
|
|
155
166
|
<script setup lang="ts">
|
|
156
|
-
import { useMemiBoardAuth } from 'memi-board'
|
|
167
|
+
import { useMemiBoardAuth } from 'memi-board/runtime'
|
|
157
168
|
const { isSignedIn } = useMemiBoardAuth()
|
|
158
169
|
</script>
|
|
159
170
|
|
|
@@ -161,7 +172,10 @@ const { isSignedIn } = useMemiBoardAuth()
|
|
|
161
172
|
<div>
|
|
162
173
|
<UButton v-if="isSignedIn" to="/board/new" label="새 글쓰기" />
|
|
163
174
|
<!-- 단순: 상세 /board/{id} -->
|
|
164
|
-
<MemiBoardList
|
|
175
|
+
<MemiBoardList
|
|
176
|
+
post-link-base="/board"
|
|
177
|
+
introduction="우리 서비스의 소식과 이야기를 나누는 게시판입니다."
|
|
178
|
+
/>
|
|
165
179
|
<!-- 카테고리 path: get-post-link 권장 -->
|
|
166
180
|
<!--
|
|
167
181
|
<MemiBoardList
|
|
@@ -205,7 +219,7 @@ export default defineNuxtRouteMiddleware(async () => {
|
|
|
205
219
|
<script setup lang="ts">
|
|
206
220
|
definePageMeta({ middleware: 'board-auth' })
|
|
207
221
|
const router = useRouter()
|
|
208
|
-
// MemiBoardEditor 는 auto-import (import from 'memi-board' 불필요)
|
|
222
|
+
// MemiBoardEditor 는 auto-import (import from 'memi-board/runtime' 불필요)
|
|
209
223
|
</script>
|
|
210
224
|
|
|
211
225
|
<template>
|
|
@@ -219,6 +233,26 @@ const router = useRouter()
|
|
|
219
233
|
|
|
220
234
|
전체 페이지 예시는 `playground/app/pages/board/` 참고.
|
|
221
235
|
|
|
236
|
+
### 관리 페이지
|
|
237
|
+
|
|
238
|
+
기본 Rules는 게시판의 `{prefix}Users/{uid}.role == 'admin'`을 검사한다. 컴포넌트도 같은 role을 확인하므로 기본 설치에서는 `authorized`를 전달하지 않는다.
|
|
239
|
+
|
|
240
|
+
```vue
|
|
241
|
+
<!-- app/pages/board-settings.vue -->
|
|
242
|
+
<template>
|
|
243
|
+
<MemiBoardSettings />
|
|
244
|
+
</template>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
```vue
|
|
248
|
+
<!-- app/pages/board-users.vue -->
|
|
249
|
+
<template>
|
|
250
|
+
<MemiBoardUsers />
|
|
251
|
+
</template>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
호스트의 별도 사이트 관리자 체계를 연동할 때만 `authorized`를 사용할 수 있다. 이 prop은 UI 표시만 허용하며 Firestore Rules를 우회하지 않는다. 이 경우에도 해당 사용자를 게시판 admin으로 등록하거나 호스트 Rules를 같은 관리자 체계에 맞게 수정해야 한다. 관리 URL이 일반 사용자에게 노출되지 않도록 호스트 라우트 가드를 추가하는 것을 권장한다.
|
|
255
|
+
|
|
222
256
|
### 제공하는 API
|
|
223
257
|
|
|
224
258
|
**컴포넌트 (auto-import):**
|
|
@@ -230,7 +264,9 @@ const router = useRouter()
|
|
|
230
264
|
본문은 Nuxt UI **`UEditor`**(TipTap, `content-type="html"`). 호스트 `@nuxt/ui` 4.x 에 Editor 포함 필요.
|
|
231
265
|
|
|
232
266
|
**에디터 이미지:** 툴바 / 붙여넣기 / 드롭 → Firebase Storage
|
|
233
|
-
`{prefix}/posts/{postId}/images/*` + `.../images/thumbnails/*` (
|
|
267
|
+
`{prefix}/posts/{postId}/images/*` + `.../images/thumbnails/*` (최적화 이미지+썸네일).
|
|
268
|
+
|
|
269
|
+
본문 이미지는 휴대폰 원본 기준 최대 25MB까지 선택할 수 있다. 5MB를 넘으면 브라우저에서 최대 2560px·JPEG 85%로 최적화하고, 결과가 여전히 5MB를 넘으면 2048px·75%로 한 번 더 줄인 뒤 업로드한다. 5MB 이하 이미지는 원본을 유지한다.
|
|
234
270
|
본문 HTML에는 원본 URL. 수정 중 버려진 파일은 호스트 스케줄러로 정리 (`extractEditorImageUrls` 로 본문 참조 비교).
|
|
235
271
|
|
|
236
272
|
**composables:**
|
|
@@ -323,13 +359,16 @@ export default defineNuxtPlugin(() => {
|
|
|
323
359
|
## 다른 프로젝트에 붙일 때 체크리스트
|
|
324
360
|
|
|
325
361
|
- [ ] 동일 스택: Nuxt 4 + `@nuxt/ui` 4 + `nuxt-vuefire` + Firebase
|
|
326
|
-
- [ ] `pnpm add memi-board
|
|
327
|
-
- [ ] `modules`에 `memi-board
|
|
328
|
-
- [ ]
|
|
362
|
+
- [ ] `pnpm add memi-board @nuxt/ui nuxt-vuefire vuefire firebase dayjs`
|
|
363
|
+
- [ ] `modules`에 `@nuxt/ui`, `nuxt-vuefire`, `memi-board`
|
|
364
|
+
- [ ] 게시판·관리 경로에 `ssr: false`
|
|
365
|
+
- [ ] `nuxt.config.ts`의 `memiBoard` 설정 (`collectionPrefix` 충돌 없게)
|
|
329
366
|
- [ ] CSS `@source` …/memi-board/dist/runtime/components
|
|
330
367
|
- [ ] Rules 예시 병합 + **deploy**
|
|
331
368
|
- [ ] 카테고리 필터 사용 시 indexes deploy
|
|
332
369
|
- [ ] 페이지·미들웨어 직접 작성 (또는 playground 복사)
|
|
370
|
+
- [ ] 최초 로그인 후 Firebase Console에서 `{prefix}Users/{uid}.role = 'admin'`
|
|
371
|
+
- [ ] 설정 페이지에서 첫 카테고리 생성
|
|
333
372
|
- [ ] (검열) AI Logic 활성화 + 권장 모델 + App Check
|
|
334
373
|
- [ ] `pnpm why vuefire` 로 이중 인스턴스 없는지 확인
|
|
335
374
|
|
|
@@ -351,7 +390,25 @@ App Check 토큰, `appId` 일치, 모델명(3.x), AI Logic 활성화, 네트워
|
|
|
351
390
|
|
|
352
391
|
### monorepo `link:` / `file:` 로 개발할 때
|
|
353
392
|
|
|
354
|
-
|
|
393
|
+
링크하기 전에 패키지 의존성과 `dist`를 준비한다.
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
# memi-board 저장소
|
|
397
|
+
pnpm install
|
|
398
|
+
pnpm build
|
|
399
|
+
|
|
400
|
+
# 호스트 저장소
|
|
401
|
+
pnpm add link:../memi-board
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
`src/components` 수정은 Nuxt 모듈이 링크 소스를 직접 읽으므로 즉시 반영된다. `src/module.ts` 또는 core composable을 수정하면 `pnpm build`를 다시 실행하고 호스트 개발 서버를 재시작한다. 모듈이 Vue·Vue Router·VueFire·Firebase·TipTap dedupe와 `dayjs` 최적화를 자동 적용한다. 배포 전에는 의존성을 **npm 버전**으로 되돌린다.
|
|
405
|
+
|
|
406
|
+
링크 상태에서 새 Tailwind 클래스를 바로 시험하려면 호스트 CSS에 소스 경로를 하나 더 둔다.
|
|
407
|
+
|
|
408
|
+
```css
|
|
409
|
+
@source "../../../node_modules/memi-board/src/components";
|
|
410
|
+
@source "../../../node_modules/memi-board/dist/runtime/components";
|
|
411
|
+
```
|
|
355
412
|
|
|
356
413
|
## 로컬 개발 (이 리포)
|
|
357
414
|
|
|
@@ -370,6 +427,16 @@ firebase emulators:start --project demo-memi-board
|
|
|
370
427
|
NUXT_PUBLIC_USE_EMULATORS=1 pnpm dev
|
|
371
428
|
```
|
|
372
429
|
|
|
430
|
+
## npm 배포
|
|
431
|
+
|
|
432
|
+
npm의 6자리 OTP를 인자로 전달하면 빌드, 타입 검사, 패키지 dry-run을 모두 통과한 뒤에만 배포한다.
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
pnpm release 123456
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
`pnpm publish 123456`은 pnpm이 `123456`을 OTP가 아닌 폴더 경로로 해석하므로 사용하지 않는다.
|
|
439
|
+
|
|
373
440
|
## 버전 · 라이선스
|
|
374
441
|
|
|
375
442
|
- UI: `<MemiBoardVersionHistory />`
|
|
@@ -22,6 +22,7 @@ export interface UseMemiBoardAuthReturn {
|
|
|
22
22
|
signOut: () => Promise<void>;
|
|
23
23
|
canEdit: (post: Pick<PostModel, 'authorUid'>) => boolean;
|
|
24
24
|
canDelete: (post: Pick<PostModel, 'authorUid'>) => boolean;
|
|
25
|
+
canEditComment: (comment: Pick<CommentModel, 'authorUid'>) => boolean;
|
|
25
26
|
canDeleteComment: (comment: Pick<CommentModel, 'authorUid'>) => boolean;
|
|
26
27
|
/** 콘텐츠 검열 차단 1회 기록 후 메시지 조각 반환 */
|
|
27
28
|
recordContentModerationBlock: () => Promise<{
|
|
@@ -9,6 +9,7 @@ export interface AddCommentInput {
|
|
|
9
9
|
export interface AddReplyInput extends AddCommentInput {
|
|
10
10
|
parent: CommentModel;
|
|
11
11
|
}
|
|
12
|
+
export declare const COMMENT_BODY_MAX_LENGTH = 1000;
|
|
12
13
|
/** 10개씩 순차 조회한 뒤 마지막 페이지에서만 최신 1개를 실시간 구독한다. */
|
|
13
14
|
export declare function useMemiBoardComments(postId: string | Ref<string>, options?: {
|
|
14
15
|
subscribe?: boolean;
|
|
@@ -20,6 +21,8 @@ export declare function useMemiBoardComments(postId: string | Ref<string>, optio
|
|
|
20
21
|
loadMore: () => Promise<void>;
|
|
21
22
|
addComment: (input: AddCommentInput) => Promise<void>;
|
|
22
23
|
addReply: (input: AddReplyInput) => Promise<void>;
|
|
24
|
+
updateComment: (commentId: string, body: string) => Promise<void>;
|
|
25
|
+
setCommentBlinded: (commentId: string, isBlinded: boolean, moderatorUid: string) => Promise<void>;
|
|
23
26
|
deleteComment: (comment: CommentModel) => Promise<void>;
|
|
24
27
|
};
|
|
25
28
|
/** 펼친 스레드의 답글을 5개씩 일회성 조회한다. */
|
|
@@ -46,6 +49,36 @@ export declare function useMemiBoardReplies(postId: string, rootId: string): {
|
|
|
46
49
|
};
|
|
47
50
|
valueOf: () => string;
|
|
48
51
|
};
|
|
52
|
+
updatedAt?: {
|
|
53
|
+
readonly seconds: number;
|
|
54
|
+
readonly nanoseconds: number;
|
|
55
|
+
toDate: () => Date;
|
|
56
|
+
toMillis: () => number;
|
|
57
|
+
isEqual: (other: import('firebase/firestore').Timestamp) => boolean;
|
|
58
|
+
toString: () => string;
|
|
59
|
+
toJSON: () => {
|
|
60
|
+
seconds: number;
|
|
61
|
+
nanoseconds: number;
|
|
62
|
+
type: string;
|
|
63
|
+
};
|
|
64
|
+
valueOf: () => string;
|
|
65
|
+
} | undefined;
|
|
66
|
+
isBlinded?: boolean | undefined;
|
|
67
|
+
moderatedAt?: {
|
|
68
|
+
readonly seconds: number;
|
|
69
|
+
readonly nanoseconds: number;
|
|
70
|
+
toDate: () => Date;
|
|
71
|
+
toMillis: () => number;
|
|
72
|
+
isEqual: (other: import('firebase/firestore').Timestamp) => boolean;
|
|
73
|
+
toString: () => string;
|
|
74
|
+
toJSON: () => {
|
|
75
|
+
seconds: number;
|
|
76
|
+
nanoseconds: number;
|
|
77
|
+
type: string;
|
|
78
|
+
};
|
|
79
|
+
valueOf: () => string;
|
|
80
|
+
} | undefined;
|
|
81
|
+
moderatedBy?: string | undefined;
|
|
49
82
|
parentId?: string | null | undefined;
|
|
50
83
|
rootId?: string | undefined;
|
|
51
84
|
depth?: number | undefined;
|
|
@@ -75,6 +108,36 @@ export declare function useMemiBoardReplies(postId: string, rootId: string): {
|
|
|
75
108
|
};
|
|
76
109
|
valueOf: () => string;
|
|
77
110
|
};
|
|
111
|
+
updatedAt?: {
|
|
112
|
+
readonly seconds: number;
|
|
113
|
+
readonly nanoseconds: number;
|
|
114
|
+
toDate: () => Date;
|
|
115
|
+
toMillis: () => number;
|
|
116
|
+
isEqual: (other: import('firebase/firestore').Timestamp) => boolean;
|
|
117
|
+
toString: () => string;
|
|
118
|
+
toJSON: () => {
|
|
119
|
+
seconds: number;
|
|
120
|
+
nanoseconds: number;
|
|
121
|
+
type: string;
|
|
122
|
+
};
|
|
123
|
+
valueOf: () => string;
|
|
124
|
+
} | undefined;
|
|
125
|
+
isBlinded?: boolean | undefined;
|
|
126
|
+
moderatedAt?: {
|
|
127
|
+
readonly seconds: number;
|
|
128
|
+
readonly nanoseconds: number;
|
|
129
|
+
toDate: () => Date;
|
|
130
|
+
toMillis: () => number;
|
|
131
|
+
isEqual: (other: import('firebase/firestore').Timestamp) => boolean;
|
|
132
|
+
toString: () => string;
|
|
133
|
+
toJSON: () => {
|
|
134
|
+
seconds: number;
|
|
135
|
+
nanoseconds: number;
|
|
136
|
+
type: string;
|
|
137
|
+
};
|
|
138
|
+
valueOf: () => string;
|
|
139
|
+
} | undefined;
|
|
140
|
+
moderatedBy?: string | undefined;
|
|
78
141
|
parentId?: string | null | undefined;
|
|
79
142
|
rootId?: string | undefined;
|
|
80
143
|
depth?: number | undefined;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Attachment, EditorImageEntry } from '../types';
|
|
2
2
|
/** 에디터 이미지 최대 크기 (바이트) */
|
|
3
3
|
export declare const EDITOR_IMAGE_MAX_BYTES: number;
|
|
4
|
+
/** 휴대폰 원본 이미지 선택 허용 크기. 큰 이미지는 업로드 전에 최적화한다. */
|
|
5
|
+
export declare const EDITOR_IMAGE_SOURCE_MAX_BYTES: number;
|
|
4
6
|
export declare function useMemiBoardStorage(): {
|
|
5
7
|
uploadAttachment: (file: File, postId: string, onProgress?: (ratio: number) => void) => {
|
|
6
8
|
promise: Promise<Attachment>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Runtime API — composables / config / types only. UI SFC는 루트 Nuxt 모듈이 등록. */
|
|
2
2
|
export { configureMemiBoard, useMemiBoardConfig } from './config';
|
|
3
3
|
export type { MemiBoardConfig, MemiBoardAuthOptions, MemiBoardModerationOptions } from './config';
|
|
4
4
|
export { useMemiBoardAuth } from './composables/useMemiBoardAuth';
|
|
5
5
|
export { useMemiBoardPosts } from './composables/useMemiBoardPosts';
|
|
6
6
|
export type { CreatePostInput, UpdatePostInput } from './composables/useMemiBoardPosts';
|
|
7
|
-
export { useMemiBoardComments, useMemiBoardReplies } from './composables/useMemiBoardComments';
|
|
7
|
+
export { COMMENT_BODY_MAX_LENGTH, useMemiBoardComments, useMemiBoardReplies } from './composables/useMemiBoardComments';
|
|
8
8
|
export type { AddCommentInput, AddReplyInput } from './composables/useMemiBoardComments';
|
|
9
9
|
export { useMemiBoardModeration } from './composables/useMemiBoardModeration';
|
|
10
10
|
export { useMemiBoardStorage } from './composables/useMemiBoardStorage';
|
|
@@ -12,7 +12,7 @@ export { useMemiBoardSettings, DEFAULT_CATEGORIES } from './composables/useMemiB
|
|
|
12
12
|
export { useMemiBoardUsers, BOARD_USER_ROLES } from './composables/useMemiBoardUsers';
|
|
13
13
|
export { versionHistory } from './data/versionHistory';
|
|
14
14
|
export type { VersionHistoryEntry } from './data/versionHistory';
|
|
15
|
-
export { formatDate } from './utils/formatDate';
|
|
15
|
+
export { formatDate, formatFullDate, formatRelativeDate, formatTimestampDetails } from './utils/formatDate';
|
|
16
16
|
export { slugify } from './utils/slugify';
|
|
17
17
|
export { DEFAULT_BLOCK_BAN_THRESHOLD, DEFAULT_BLOCK_BAN_DECAY_MS, effectiveModerationBlockCount, isModerationWriteRestricted, moderationWriteRestrictedUntilMs, formatRestrictedUntilLabel, } from './utils/moderation-strike';
|
|
18
18
|
export type { Attachment, EditorImageEntry, ModerationStatus, PostModel, PostDetail, CommentModel, BoardUserRole, BoardUserModel, BoardCategory, BoardListView, BoardWriteRole, BoardSettingsModel, ModerationResult, } from './types';
|
|
@@ -20,4 +20,4 @@ export { compressImage } from './utils/compressImage';
|
|
|
20
20
|
export { extractEditorImageUrls } from './utils/extractEditorImageUrls';
|
|
21
21
|
export { renderMarkdownToHtml } from './utils/renderMarkdown';
|
|
22
22
|
export { storagePathFromDownloadUrl, postNamespaceFromStoragePath } from './utils/storagePath';
|
|
23
|
-
export { EDITOR_IMAGE_MAX_BYTES } from './composables/useMemiBoardStorage';
|
|
23
|
+
export { EDITOR_IMAGE_MAX_BYTES, EDITOR_IMAGE_SOURCE_MAX_BYTES } from './composables/useMemiBoardStorage';
|