memi-board 0.10.1 → 0.18.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 (32) hide show
  1. package/dist/composables/useMemiBoardLikes.d.ts +12 -0
  2. package/dist/composables/useMemiBoardPosts.d.ts +46 -1
  3. package/dist/composables/useMemiBoardSettings.d.ts +5 -1
  4. package/dist/composables/useMemiBoardUsers.d.ts +9 -1
  5. package/dist/composables/useMemiBoardViews.d.ts +11 -0
  6. package/dist/index.d.ts +7 -3
  7. package/dist/index.js +769 -519
  8. package/dist/index.js.map +1 -1
  9. package/dist/module.mjs +4 -0
  10. package/dist/module.mjs.map +1 -1
  11. package/dist/runtime/components/CategorySettings.vue +108 -12
  12. package/dist/runtime/components/CommentForm.vue +26 -5
  13. package/dist/runtime/components/CommentItem.vue +1 -0
  14. package/dist/runtime/components/CommentList.vue +2 -1
  15. package/dist/runtime/components/CommentThread.vue +2 -1
  16. package/dist/runtime/components/Detail.vue +47 -30
  17. package/dist/runtime/components/Editor.vue +36 -11
  18. package/dist/runtime/components/Footer.vue +1 -1
  19. package/dist/runtime/components/LikeButton.vue +35 -0
  20. package/dist/runtime/components/List.vue +172 -61
  21. package/dist/runtime/components/ListDefault.vue +81 -23
  22. package/dist/runtime/components/ListDense.vue +112 -0
  23. package/dist/runtime/components/ListImage.vue +35 -22
  24. package/dist/runtime/components/ListVideo.vue +31 -15
  25. package/dist/runtime/components/ListViewSwitch.vue +30 -0
  26. package/dist/runtime/components/OptionCards.vue +30 -0
  27. package/dist/runtime/components/Settings.vue +86 -11
  28. package/dist/runtime/components/Users.vue +1 -1
  29. package/dist/types.d.ts +30 -1
  30. package/dist/utils/postPreview.d.ts +8 -0
  31. package/docs/firestore.rules.example +106 -15
  32. package/package.json +1 -1
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 게시글 좋아요 상태 구독 + 토글.
3
+ * `{prefix}Posts/{postId}/likes/{uid}` 문서 존재 여부가 "좋아요함"의 기준이고,
4
+ * `likeCount`는 그 문서 수를 반영하는 클라이언트 편의 카운터일 뿐이다(commentCount와 동일한 성격).
5
+ * 토글은 배치가 아니라 트랜잭션으로 처리한다 — "있으면 취소, 없으면 추가"라는 read-then-branch라
6
+ * 다중 탭 경합 시에도 likeCount가 실제 likes 문서 수와 어긋나지 않게 하기 위함이다.
7
+ */
8
+ export declare function useMemiBoardLikes(postId: string): {
9
+ isLiked: import('vue').Ref<boolean, boolean>;
10
+ likePending: import('vue').Ref<boolean, boolean>;
11
+ toggleLike: () => Promise<boolean>;
12
+ };
@@ -1,3 +1,4 @@
1
+ import { Ref } from 'vue';
1
2
  import { DocumentData, QueryDocumentSnapshot } from 'firebase/firestore';
2
3
  import { Attachment, PostDetail, PostModel } from '../types';
3
4
  export interface CreatePostInput {
@@ -20,6 +21,19 @@ export interface UpdatePostInput {
20
21
  category?: string;
21
22
  attachments?: Attachment[];
22
23
  }
24
+ /** getPostBySlug 결과 — not-found 와 permission-denied 를 UI 에서 구분한다. */
25
+ export type GetPostBySlugResult = {
26
+ status: 'ok';
27
+ post: PostDetail;
28
+ } | {
29
+ status: 'not-found';
30
+ } | {
31
+ status: 'permission-denied';
32
+ } | {
33
+ status: 'error';
34
+ message: string;
35
+ };
36
+ export declare function isFirestorePermissionDenied(error: unknown): boolean;
23
37
  /** 목록 조회는 posts/{id} 메타만 읽고, 본문(body/main)은 상세 조회 시에만 읽는다. */
24
38
  export declare function useMemiBoardPosts(): {
25
39
  createPostId: () => string;
@@ -28,13 +42,15 @@ export declare function useMemiBoardPosts(): {
28
42
  cursor?: QueryDocumentSnapshot<DocumentData>;
29
43
  /** 지정 시 해당 카테고리 글만 (boardSettings 의 category id) */
30
44
  category?: string;
45
+ /** true 면 listed==true 만 (비권한 목록). staff 전체 보기는 false */
46
+ publicOnly?: boolean;
31
47
  }) => Promise<{
32
48
  posts: PostModel[];
33
49
  cursor: QueryDocumentSnapshot<DocumentData> | undefined;
34
50
  hasMore: boolean;
35
51
  }>;
36
52
  getPost: (id: string) => Promise<PostDetail | null>;
37
- getPostBySlug: (category: string, slug: string) => Promise<PostDetail | null>;
53
+ getPostBySlug: (category: string, slug: string) => Promise<GetPostBySlugResult>;
38
54
  getAdjacentPosts: (current: PostModel) => Promise<{
39
55
  previous: PostModel | null;
40
56
  next: PostModel | null;
@@ -43,3 +59,32 @@ export declare function useMemiBoardPosts(): {
43
59
  updatePost: (id: string, input: UpdatePostInput) => Promise<void>;
44
60
  deletePost: (id: string) => Promise<void>;
45
61
  };
62
+ /**
63
+ * 최신 {pageSize}건은 onSnapshot으로 실시간 구독해 목록에 보이는 동안 좋아요·댓글 수가
64
+ * 바로 갱신되게 하고, 그 이전 글은 "더보기" 클릭 시 1회성 getDocs로 이어서 불러온다.
65
+ * 두 구간이 살짝 겹쳐도 id 기준으로 합쳐 중복 없이 렌더한다.
66
+ *
67
+ * 알려진 한계: "더보기"로 이전 글을 불러온 뒤 새 글이 pageSize개 이상 연달아 올라오면
68
+ * 실시간 구간과 페이지 구간 사이에 짧은 공백이 생길 수 있다 — 새로고침하면 바로 채워지고,
69
+ * 이 정도 트래픽 폭이면 실사용에서 발생 가능성이 낮아 별도 보정 로직은 두지 않았다.
70
+ */
71
+ export declare function useMemiBoardPostList(category?: string | Ref<string | undefined>, options?: {
72
+ pageSize?: number; /** staff 등 숨김 글 포함 시 false. 기본 true */
73
+ publicOnly?: boolean | Ref<boolean>;
74
+ }): {
75
+ posts: import('vue').ComputedRef<PostModel[]>;
76
+ postsPending: Ref<boolean, boolean>;
77
+ hasMore: Ref<boolean, boolean>;
78
+ loadingMore: Ref<boolean, boolean>;
79
+ loadError: Ref<string, string>;
80
+ loadMore: () => Promise<void>;
81
+ };
82
+ /**
83
+ * 게시글 상세를 VueFire useDocument로 실시간 구독한다 (메타 + 본문).
84
+ * 좋아요·댓글 수 등 다른 사람의 변경이 화면에 바로 반영된다 — 좋아요 토글도
85
+ * 별도 로컬 낙관적 갱신 없이 이 구독이 그대로 반영해 준다.
86
+ */
87
+ export declare function useMemiBoardPost(postId: string | Ref<string>): {
88
+ post: import('vue').ComputedRef<PostDetail | null>;
89
+ pending: import('vue').ComputedRef<boolean>;
90
+ };
@@ -1,10 +1,14 @@
1
- import { BoardCategory } from '../types';
1
+ import { BoardCategory, BoardVisibility } from '../types';
2
2
  /** 필요할 때 호스트가 명시적으로 사용할 수 있는 예시 카테고리. 자동 적용하지 않는다. */
3
3
  export declare const DEFAULT_CATEGORIES: BoardCategory[];
4
4
  /** 게시판 카테고리 — `{prefix}Settings/config/categories/{categoryId}` 개별 문서. */
5
5
  export declare function useMemiBoardSettings(): {
6
6
  categories: import('vue').ComputedRef<BoardCategory[]>;
7
+ publicCategories: import('vue').ComputedRef<BoardCategory[]>;
7
8
  categoryLabel: (id: string | undefined) => string | undefined;
9
+ categoryDescription: (id: string | undefined) => string | undefined;
10
+ categoryVisibility: (id: string | undefined) => BoardVisibility;
11
+ isCategoryHidden: (id: string | undefined) => boolean;
8
12
  settingsPending: import('vue').Ref<boolean, boolean>;
9
13
  ensureSettings: () => Promise<void>;
10
14
  addCategory: (label: string) => Promise<string>;
@@ -1,10 +1,18 @@
1
+ import { MaybeRefOrGetter } from 'vue';
1
2
  import { BoardUserModel, BoardUserRole } from '../types';
2
3
  export declare const BOARD_USER_ROLES: Array<{
3
4
  value: BoardUserRole;
4
5
  label: string;
5
6
  description: string;
6
7
  }>;
7
- export declare function useMemiBoardUsers(): {
8
+ /**
9
+ * 전체 사용자 목록은 관리자만 읽을 수 있다(rules). enabled가 false인 동안은
10
+ * 쿼리 자체를 시작하지 않는다 — 그냥 호출만 하면 권한 확인 전에 구독이 걸려서
11
+ * 관리자가 아니거나 아직 role을 확인 중일 때 permission-denied로 화면이 죽는다.
12
+ */
13
+ export declare function useMemiBoardUsers(options?: {
14
+ enabled?: MaybeRefOrGetter<boolean>;
15
+ }): {
8
16
  users: import('vuefire')._RefFirestore<import('vuefire').VueFirestoreQueryData<BoardUserModel>>;
9
17
  usersPending: import('vue').Ref<boolean, boolean>;
10
18
  updateUserRole: (uid: string, role: BoardUserRole) => Promise<void>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 게시글 조회수.
3
+ *
4
+ * - 로그인 불필요: rules 가 viewCount +1 만 허용하면 익명도 증가 가능.
5
+ * - 신뢰 수준은 likeCount/commentCount 와 같은 클라이언트 편의 카운터.
6
+ * - 같은 브라우저·세션에서 같은 글을 반복 열어 폭증하지 않도록 sessionStorage 로 1회만 기록.
7
+ */
8
+ export declare function useMemiBoardViews(): {
9
+ recordView: (postId: string) => Promise<void>;
10
+ hasRecordedView: (postId: string) => boolean;
11
+ };
package/dist/index.d.ts CHANGED
@@ -2,10 +2,13 @@
2
2
  export { configureMemiBoard, useMemiBoardConfig } from './config';
3
3
  export type { MemiBoardConfig, MemiBoardAuthOptions, MemiBoardModerationOptions } from './config';
4
4
  export { useMemiBoardAuth } from './composables/useMemiBoardAuth';
5
- export { useMemiBoardPosts } from './composables/useMemiBoardPosts';
6
- export type { CreatePostInput, UpdatePostInput } from './composables/useMemiBoardPosts';
5
+ export { useMemiBoardPosts, useMemiBoardPostList, useMemiBoardPost } from './composables/useMemiBoardPosts';
6
+ export type { CreatePostInput, UpdatePostInput, GetPostBySlugResult } from './composables/useMemiBoardPosts';
7
+ export { isFirestorePermissionDenied } from './composables/useMemiBoardPosts';
7
8
  export { COMMENT_BODY_MAX_LENGTH, useMemiBoardComments, useMemiBoardReplies } from './composables/useMemiBoardComments';
8
9
  export type { AddCommentInput, AddReplyInput } from './composables/useMemiBoardComments';
10
+ export { useMemiBoardLikes } from './composables/useMemiBoardLikes';
11
+ export { useMemiBoardViews } from './composables/useMemiBoardViews';
9
12
  export { useMemiBoardModeration } from './composables/useMemiBoardModeration';
10
13
  export { useMemiBoardStorage } from './composables/useMemiBoardStorage';
11
14
  export { useMemiBoardSettings, DEFAULT_CATEGORIES } from './composables/useMemiBoardSettings';
@@ -15,9 +18,10 @@ export type { VersionHistoryEntry } from './data/versionHistory';
15
18
  export { formatDate, formatFullDate, formatRelativeDate, formatTimestampDetails } from './utils/formatDate';
16
19
  export { slugify } from './utils/slugify';
17
20
  export { DEFAULT_BLOCK_BAN_THRESHOLD, DEFAULT_BLOCK_BAN_DECAY_MS, effectiveModerationBlockCount, isModerationWriteRestricted, moderationWriteRestrictedUntilMs, formatRestrictedUntilLabel, } from './utils/moderation-strike';
18
- export type { Attachment, EditorImageEntry, ModerationStatus, PostModel, PostDetail, CommentModel, BoardUserRole, BoardUserModel, BoardCategory, BoardListView, BoardWriteRole, BoardSettingsModel, ModerationResult, } from './types';
21
+ export type { Attachment, EditorImageEntry, ModerationStatus, PostModel, PostDetail, CommentModel, BoardUserRole, BoardUserModel, BoardCategory, BoardListView, BoardWriteRole, BoardVisibility, BoardSettingsModel, ModerationResult, } from './types';
19
22
  export { compressImage } from './utils/compressImage';
20
23
  export { extractEditorImageUrls } from './utils/extractEditorImageUrls';
21
24
  export { renderMarkdownToHtml } from './utils/renderMarkdown';
25
+ export { buildPostPreview, youtubeId, videoListCoverUrl } from './utils/postPreview';
22
26
  export { storagePathFromDownloadUrl, postNamespaceFromStoragePath } from './utils/storagePath';
23
27
  export { EDITOR_IMAGE_MAX_BYTES, EDITOR_IMAGE_SOURCE_MAX_BYTES } from './composables/useMemiBoardStorage';