memi-board 0.5.0 → 0.9.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.
- package/README.md +11 -8
- package/dist/composables/useMemiBoardAuth.d.ts +2 -0
- package/dist/composables/useMemiBoardComments.d.ts +81 -4
- package/dist/composables/useMemiBoardUsers.d.ts +11 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +501 -265
- package/dist/index.js.map +1 -1
- package/dist/module.mjs +4 -0
- package/dist/module.mjs.map +1 -1
- package/dist/runtime/components/CommentForm.vue +21 -6
- package/dist/runtime/components/CommentItem.vue +73 -0
- package/dist/runtime/components/CommentList.vue +83 -39
- package/dist/runtime/components/CommentSkeleton.vue +12 -0
- package/dist/runtime/components/CommentThread.vue +121 -0
- package/dist/runtime/components/Detail.vue +3 -6
- package/dist/runtime/components/Settings.vue +24 -4
- package/dist/runtime/components/Users.vue +64 -0
- package/dist/types.d.ts +23 -2
- package/docs/firestore.rules.example +88 -14
- package/docs/storage.rules.example +6 -6
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -222,7 +222,7 @@ const router = useRouter()
|
|
|
222
222
|
### 제공하는 API
|
|
223
223
|
|
|
224
224
|
**컴포넌트 (auto-import):**
|
|
225
|
-
`MemiBoardList`, `MemiBoardDetail`, `MemiBoardEditor`, `MemiBoardCommentList`, `MemiBoardCommentForm`, `MemiBoardAttachments`, `MemiBoardSignIn`, `MemiBoardVersionHistory`
|
|
225
|
+
`MemiBoardList`, `MemiBoardDetail`, `MemiBoardEditor`, `MemiBoardCommentList`, `MemiBoardCommentForm`, `MemiBoardAttachments`, `MemiBoardSignIn`, `MemiBoardSettings`, `MemiBoardUsers`, `MemiBoardVersionHistory`
|
|
226
226
|
|
|
227
227
|
**List props:** `pageSize`, `category`, `postLinkBase`, `getPostLink(post)`, `linkBase`(deprecated)
|
|
228
228
|
|
|
@@ -234,13 +234,15 @@ const router = useRouter()
|
|
|
234
234
|
본문 HTML에는 원본 URL. 수정 중 버려진 파일은 호스트 스케줄러로 정리 (`extractEditorImageUrls` 로 본문 참조 비교).
|
|
235
235
|
|
|
236
236
|
**composables:**
|
|
237
|
-
`useMemiBoardAuth`, `useMemiBoardPosts`, `useMemiBoardComments`, `useMemiBoardStorage`, `useMemiBoardModeration`, `useMemiBoardSettings`
|
|
237
|
+
`useMemiBoardAuth`, `useMemiBoardPosts`, `useMemiBoardComments`, `useMemiBoardStorage`, `useMemiBoardModeration`, `useMemiBoardSettings`, `useMemiBoardUsers`
|
|
238
|
+
|
|
239
|
+
댓글은 시간순으로 10개씩 cursor 조회하며, 실제 `댓글 더보기` 버튼이 화면에 들어오면 1초 후 자동으로 다음 페이지를 읽는다. 기존 댓글을 모두 읽은 뒤에만 최신 댓글 1개를 실시간 구독한다. 대댓글은 5개씩 조회하고 모두 읽은 뒤 최신 답글 1개를 실시간 구독하며, 화면 깊이는 최대 2단계로 표시한다.
|
|
238
240
|
|
|
239
241
|
### 카테고리
|
|
240
242
|
|
|
241
|
-
- 문서: `{prefix}Settings/config/categories/{categoryId}` → `{ label, listView, order, updatedAt }`
|
|
243
|
+
- 문서: `{prefix}Settings/config/categories/{categoryId}` → `{ label, listView, writeRole, order, updatedAt }`
|
|
242
244
|
- 설정이 없으면 빈 목록이며 기본 카테고리를 자동 생성하지 않음
|
|
243
|
-
- `MemiBoardSettings`에서 관리자가 카테고리별
|
|
245
|
+
- `MemiBoardSettings`에서 관리자가 카테고리별 저장·삭제·순서·리스트뷰·글쓰기 최소 역할 관리
|
|
244
246
|
- `categoryId`는 문서 ID이자 게시글의 `category` 참조값이므로 생성 후 변경하지 않는 것을 권장
|
|
245
247
|
|
|
246
248
|
### 로그인 공급자
|
|
@@ -263,10 +265,11 @@ const router = useRouter()
|
|
|
263
265
|
| 동작 | 조건 |
|
|
264
266
|
|------|------|
|
|
265
267
|
| 글/댓글 읽기 | 공개 |
|
|
266
|
-
|
|
|
267
|
-
|
|
|
268
|
-
|
|
|
269
|
-
|
|
|
268
|
+
| 글 작성 | 로그인 + 카테고리 `writeRole` 충족 + `moderationStatus == 'approved'` |
|
|
269
|
+
| 댓글 작성 | 로그인 + `moderationStatus == 'approved'` (클라가 항상 approved 로 씀 — 검열 통과 후에만 write) |
|
|
270
|
+
| 글 수정·삭제 | 작성자 또는 `{prefix}Users/{uid}.role in ['admin', 'staff']` |
|
|
271
|
+
| 댓글 삭제 | 댓글 작성자 · 글 작성자 · admin · staff |
|
|
272
|
+
| 역할 관리 | `MemiBoardUsers`에서 admin이 `admin`·`staff`·`user` 변경 |
|
|
270
273
|
|
|
271
274
|
**알려진 한계:** AI 검열은 클라이언트 전용. SDK로 직접 write 하면 우회 가능. 중요 서비스면 Cloud Functions 검증을 호스트가 추가(패키지 범위 밖).
|
|
272
275
|
|
|
@@ -6,6 +6,8 @@ export interface UseMemiBoardAuthReturn {
|
|
|
6
6
|
user: ReturnType<typeof useCurrentUser>;
|
|
7
7
|
isSignedIn: ComputedRef<boolean>;
|
|
8
8
|
isAdmin: ComputedRef<boolean>;
|
|
9
|
+
isStaff: ComputedRef<boolean>;
|
|
10
|
+
canManageContent: ComputedRef<boolean>;
|
|
9
11
|
rolePending: Ref<boolean>;
|
|
10
12
|
/** 유효 검열 차단 누적 (lazy decay 반영) */
|
|
11
13
|
moderationBlockCount: ComputedRef<number>;
|
|
@@ -6,9 +6,86 @@ export interface AddCommentInput {
|
|
|
6
6
|
authorName: string | null;
|
|
7
7
|
authorPhoto: string | null;
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
export interface AddReplyInput extends AddCommentInput {
|
|
10
|
+
parent: CommentModel;
|
|
11
|
+
}
|
|
12
|
+
/** 10개씩 순차 조회한 뒤 마지막 페이지에서만 최신 1개를 실시간 구독한다. */
|
|
13
|
+
export declare function useMemiBoardComments(postId: string | Ref<string>, options?: {
|
|
14
|
+
subscribe?: boolean;
|
|
15
|
+
}): {
|
|
16
|
+
comments: import('vue').ComputedRef<CommentModel[]>;
|
|
17
|
+
commentsPending: import('vue').ComputedRef<boolean>;
|
|
18
|
+
hasMore: Ref<boolean, boolean>;
|
|
19
|
+
loadingMore: Ref<boolean, boolean>;
|
|
20
|
+
loadMore: () => Promise<void>;
|
|
12
21
|
addComment: (input: AddCommentInput) => Promise<void>;
|
|
13
|
-
|
|
22
|
+
addReply: (input: AddReplyInput) => Promise<void>;
|
|
23
|
+
deleteComment: (comment: CommentModel) => Promise<void>;
|
|
24
|
+
};
|
|
25
|
+
/** 펼친 스레드의 답글을 5개씩 일회성 조회한다. */
|
|
26
|
+
export declare function useMemiBoardReplies(postId: string, rootId: string): {
|
|
27
|
+
replies: Ref<{
|
|
28
|
+
id?: string | undefined;
|
|
29
|
+
postId: string;
|
|
30
|
+
body: string;
|
|
31
|
+
authorUid: string;
|
|
32
|
+
authorName: string | null;
|
|
33
|
+
authorPhoto: string | null;
|
|
34
|
+
moderationStatus: import('..').ModerationStatus;
|
|
35
|
+
createdAt: {
|
|
36
|
+
readonly seconds: number;
|
|
37
|
+
readonly nanoseconds: number;
|
|
38
|
+
toDate: () => Date;
|
|
39
|
+
toMillis: () => number;
|
|
40
|
+
isEqual: (other: import('firebase/firestore').Timestamp) => boolean;
|
|
41
|
+
toString: () => string;
|
|
42
|
+
toJSON: () => {
|
|
43
|
+
seconds: number;
|
|
44
|
+
nanoseconds: number;
|
|
45
|
+
type: string;
|
|
46
|
+
};
|
|
47
|
+
valueOf: () => string;
|
|
48
|
+
};
|
|
49
|
+
parentId?: string | null | undefined;
|
|
50
|
+
rootId?: string | undefined;
|
|
51
|
+
depth?: number | undefined;
|
|
52
|
+
replyToUid?: string | null | undefined;
|
|
53
|
+
replyToName?: string | null | undefined;
|
|
54
|
+
replyCount?: number | undefined;
|
|
55
|
+
isReply?: boolean | undefined;
|
|
56
|
+
}[], CommentModel[] | {
|
|
57
|
+
id?: string | undefined;
|
|
58
|
+
postId: string;
|
|
59
|
+
body: string;
|
|
60
|
+
authorUid: string;
|
|
61
|
+
authorName: string | null;
|
|
62
|
+
authorPhoto: string | null;
|
|
63
|
+
moderationStatus: import('..').ModerationStatus;
|
|
64
|
+
createdAt: {
|
|
65
|
+
readonly seconds: number;
|
|
66
|
+
readonly nanoseconds: number;
|
|
67
|
+
toDate: () => Date;
|
|
68
|
+
toMillis: () => number;
|
|
69
|
+
isEqual: (other: import('firebase/firestore').Timestamp) => boolean;
|
|
70
|
+
toString: () => string;
|
|
71
|
+
toJSON: () => {
|
|
72
|
+
seconds: number;
|
|
73
|
+
nanoseconds: number;
|
|
74
|
+
type: string;
|
|
75
|
+
};
|
|
76
|
+
valueOf: () => string;
|
|
77
|
+
};
|
|
78
|
+
parentId?: string | null | undefined;
|
|
79
|
+
rootId?: string | undefined;
|
|
80
|
+
depth?: number | undefined;
|
|
81
|
+
replyToUid?: string | null | undefined;
|
|
82
|
+
replyToName?: string | null | undefined;
|
|
83
|
+
replyCount?: number | undefined;
|
|
84
|
+
isReply?: boolean | undefined;
|
|
85
|
+
}[]>;
|
|
86
|
+
loading: Ref<boolean, boolean>;
|
|
87
|
+
loaded: Ref<boolean, boolean>;
|
|
88
|
+
hasMore: Ref<boolean, boolean>;
|
|
89
|
+
loadMore: () => Promise<void>;
|
|
90
|
+
refresh: () => Promise<void>;
|
|
14
91
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BoardUserModel, BoardUserRole } from '../types';
|
|
2
|
+
export declare const BOARD_USER_ROLES: Array<{
|
|
3
|
+
value: BoardUserRole;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function useMemiBoardUsers(): {
|
|
8
|
+
users: import('vuefire')._RefFirestore<import('vuefire').VueFirestoreQueryData<BoardUserModel>>;
|
|
9
|
+
usersPending: import('vue').Ref<boolean, boolean>;
|
|
10
|
+
updateUserRole: (uid: string, role: BoardUserRole) => Promise<void>;
|
|
11
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -4,17 +4,18 @@ export type { MemiBoardConfig, MemiBoardAuthOptions, MemiBoardModerationOptions
|
|
|
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 } from './composables/useMemiBoardComments';
|
|
8
|
-
export type { AddCommentInput } from './composables/useMemiBoardComments';
|
|
7
|
+
export { useMemiBoardComments, useMemiBoardReplies } from './composables/useMemiBoardComments';
|
|
8
|
+
export type { AddCommentInput, AddReplyInput } from './composables/useMemiBoardComments';
|
|
9
9
|
export { useMemiBoardModeration } from './composables/useMemiBoardModeration';
|
|
10
10
|
export { useMemiBoardStorage } from './composables/useMemiBoardStorage';
|
|
11
11
|
export { useMemiBoardSettings, DEFAULT_CATEGORIES } from './composables/useMemiBoardSettings';
|
|
12
|
+
export { useMemiBoardUsers, BOARD_USER_ROLES } from './composables/useMemiBoardUsers';
|
|
12
13
|
export { versionHistory } from './data/versionHistory';
|
|
13
14
|
export type { VersionHistoryEntry } from './data/versionHistory';
|
|
14
15
|
export { formatDate } from './utils/formatDate';
|
|
15
16
|
export { slugify } from './utils/slugify';
|
|
16
17
|
export { DEFAULT_BLOCK_BAN_THRESHOLD, DEFAULT_BLOCK_BAN_DECAY_MS, effectiveModerationBlockCount, isModerationWriteRestricted, moderationWriteRestrictedUntilMs, formatRestrictedUntilLabel, } from './utils/moderation-strike';
|
|
17
|
-
export type { Attachment, EditorImageEntry, ModerationStatus, PostModel, PostDetail, CommentModel, BoardUserRole, BoardCategory, BoardListView, BoardSettingsModel, ModerationResult, } from './types';
|
|
18
|
+
export type { Attachment, EditorImageEntry, ModerationStatus, PostModel, PostDetail, CommentModel, BoardUserRole, BoardUserModel, BoardCategory, BoardListView, BoardWriteRole, BoardSettingsModel, ModerationResult, } from './types';
|
|
18
19
|
export { compressImage } from './utils/compressImage';
|
|
19
20
|
export { extractEditorImageUrls } from './utils/extractEditorImageUrls';
|
|
20
21
|
export { renderMarkdownToHtml } from './utils/renderMarkdown';
|