memi-board 0.31.0 → 0.36.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 (47) hide show
  1. package/README.md +2 -1
  2. package/dist/composables/boardLookup.d.ts +8 -0
  3. package/dist/composables/useMemiBoardAuth.d.ts +4 -3
  4. package/dist/composables/useMemiBoardCommentFeed.d.ts +18 -0
  5. package/dist/composables/useMemiBoardComments.d.ts +2 -0
  6. package/dist/composables/useMemiBoardLikes.d.ts +9 -0
  7. package/dist/composables/useMemiBoardMiniPosts.d.ts +12 -0
  8. package/dist/composables/useMemiBoardReports.d.ts +114 -0
  9. package/dist/composables/useMemiBoardSections.d.ts +13 -0
  10. package/dist/composables/useMemiBoardSettings.d.ts +2 -0
  11. package/dist/config.d.ts +2 -0
  12. package/dist/{heic2any-BWRKffWN.js → heic2any-D2QKy06h.js} +2 -2
  13. package/dist/{heic2any-BWRKffWN.js.map → heic2any-D2QKy06h.js.map} +1 -1
  14. package/dist/index.d.ts +12 -3
  15. package/dist/index.js +1163 -632
  16. package/dist/index.js.map +1 -1
  17. package/dist/runtime/components/CategorySettings.vue +3 -5
  18. package/dist/runtime/components/CommentFeed.vue +141 -0
  19. package/dist/runtime/components/CommentForm.vue +2 -2
  20. package/dist/runtime/components/CommentItem.vue +55 -5
  21. package/dist/runtime/components/CommentList.vue +10 -2
  22. package/dist/runtime/components/Detail.vue +8 -0
  23. package/dist/runtime/components/Editor.vue +203 -13
  24. package/dist/runtime/components/Mini.vue +37 -0
  25. package/dist/runtime/components/MiniCard.vue +51 -0
  26. package/dist/runtime/components/MiniComments.vue +75 -0
  27. package/dist/runtime/components/MiniList.vue +36 -0
  28. package/dist/runtime/components/MiniPicker.vue +131 -0
  29. package/dist/runtime/components/MiniPost.vue +52 -0
  30. package/dist/runtime/components/MiniPosts.vue +115 -0
  31. package/dist/runtime/components/ReportButton.vue +117 -0
  32. package/dist/runtime/components/ReportList.vue +124 -0
  33. package/dist/runtime/components/Section.vue +62 -0
  34. package/dist/runtime/components/Settings.vue +23 -7
  35. package/dist/runtime/components/UserCommentList.vue +3 -0
  36. package/dist/{slugify-B5XHr4SZ.js → slugify-Cyq-Gg8X.js} +47 -36
  37. package/dist/slugify-Cyq-Gg8X.js.map +1 -0
  38. package/dist/storage.js +11 -11
  39. package/dist/types.d.ts +54 -3
  40. package/dist/utils/boardAccess.d.ts +7 -0
  41. package/dist/utils/boardPaths.d.ts +10 -3
  42. package/dist/utils/deletePostCascade.d.ts +11 -0
  43. package/dist/utils/section.d.ts +31 -0
  44. package/docs/firestore.indexes.json.example +38 -0
  45. package/docs/firestore.rules.example +78 -22
  46. package/package.json +1 -1
  47. package/dist/slugify-B5XHr4SZ.js.map +0 -1
package/README.md CHANGED
@@ -377,7 +377,8 @@ const router = useRouter()
377
377
  **Editor props:** `postId`(수정), `fixedCategory`(path 고정 시 선택 UI 숨김)
378
378
  본문은 Nuxt UI **`UEditor`**(TipTap, `content-type="html"`). 호스트 `@nuxt/ui` 4.x 에 Editor 포함 필요.
379
379
 
380
- **에디터 이미지:** 툴바 / 붙여넣기 / 드롭 → Firebase Storage
380
+ **에디터 이미지:** 툴바에서 기기 파일 또는 외부 HTTPS 이미지 URL을 추가할 수 있고, 붙여넣기 / 드롭 파일은 Firebase Storage에 업로드한다. 외부 URL은 Storage에 복사하지 않으며 원본이 삭제되거나 핫링크가 차단되면 표시되지 않는다. 첫 본문 이미지는 업로드·외부 링크 구분 없이 목록과 공유 대표 이미지로 사용한다.
381
+
381
382
  `memiBoardPosts/{postId}/images/*` + `.../images/thumbnails/*` (최적화 이미지+썸네일).
382
383
  글 문서는 아직 없을 수 있고, 붙여넣기는 `image/png` 등이 온다. Storage 예시 규칙은 쓰기를 **로그인 + 5MB + `image/*`** 로 본다. 규칙은 **앱 `storageBucket`과 같은 버킷**에 배포해야 한다.
383
384
 
@@ -0,0 +1,8 @@
1
+ type BoardLookup = (boardId: string) => {
2
+ allowedStaffUids?: string[];
3
+ } | undefined;
4
+ export declare function registerBoardLookup(fn: BoardLookup): void;
5
+ export declare function resolveBoardLookup(boardId: string): {
6
+ allowedStaffUids?: string[];
7
+ } | undefined;
8
+ export {};
@@ -8,6 +8,7 @@ export interface UseMemiBoardAuthReturn {
8
8
  isAdmin: ComputedRef<boolean>;
9
9
  isStaff: ComputedRef<boolean>;
10
10
  canManageContent: ComputedRef<boolean>;
11
+ canManageBoard: (boardId: string | undefined | null) => boolean;
11
12
  rolePending: Ref<boolean>;
12
13
  /** 유효 검열 차단 누적 (lazy decay 반영) */
13
14
  moderationBlockCount: ComputedRef<number>;
@@ -20,10 +21,10 @@ export interface UseMemiBoardAuthReturn {
20
21
  signInWithEmail: (email: string, password: string) => ReturnType<typeof signInWithEmailAndPassword>;
21
22
  signUpWithEmail: (email: string, password: string, displayName?: string) => ReturnType<typeof createUserWithEmailAndPassword>;
22
23
  signOut: () => Promise<void>;
23
- canEdit: (post: Pick<PostModel, 'authorUid'>) => boolean;
24
- canDelete: (post: Pick<PostModel, 'authorUid'>) => boolean;
24
+ canEdit: (post: Pick<PostModel, 'authorUid' | 'boardId'>) => boolean;
25
+ canDelete: (post: Pick<PostModel, 'authorUid' | 'boardId'>) => boolean;
25
26
  canEditComment: (comment: Pick<CommentModel, 'authorUid'>) => boolean;
26
- canDeleteComment: (comment: Pick<CommentModel, 'authorUid'>) => boolean;
27
+ canDeleteComment: (comment: Pick<CommentModel, 'authorUid' | 'boardId'>) => boolean;
27
28
  /** 콘텐츠 검열 차단 1회 기록 후 메시지 조각 반환 */
28
29
  recordContentModerationBlock: () => Promise<{
29
30
  effectiveCount: number;
@@ -0,0 +1,18 @@
1
+ import { MaybeRefOrGetter } from 'vue';
2
+ import { CommentModel } from '../types';
3
+ export type CommentFeedSort = 'latest' | 'likes';
4
+ /**
5
+ * 보드를 가로질러 댓글을 모은다. 최신순은 createdAt, 좋아요순은 likeCount.
6
+ * likeCount 가 없는 예전 댓글은 좋아요순 쿼리에 나오지 않는다(표시 시 0).
7
+ */
8
+ export declare function useMemiBoardCommentFeed(sort?: MaybeRefOrGetter<CommentFeedSort>, options?: {
9
+ pageSize?: MaybeRefOrGetter<number>;
10
+ }): {
11
+ comments: import('vue').ComputedRef<CommentModel[]>;
12
+ commentsPending: import('vue').Ref<boolean, boolean>;
13
+ hasMore: import('vue').Ref<boolean, boolean>;
14
+ loadingMore: import('vue').Ref<boolean, boolean>;
15
+ loadError: import('vue').Ref<string, string>;
16
+ loadMore: () => Promise<void>;
17
+ sort: import('vue').ComputedRef<CommentFeedSort>;
18
+ };
@@ -90,6 +90,7 @@ export declare function useMemiBoardReplies(boardId: string, postId: string, roo
90
90
  replyToName?: string | null | undefined;
91
91
  replyCount?: number | undefined;
92
92
  isReply?: boolean | undefined;
93
+ likeCount?: number | undefined;
93
94
  }[], CommentModel[] | {
94
95
  id?: string | undefined;
95
96
  postId: string;
@@ -150,6 +151,7 @@ export declare function useMemiBoardReplies(boardId: string, postId: string, roo
150
151
  replyToName?: string | null | undefined;
151
152
  replyCount?: number | undefined;
152
153
  isReply?: boolean | undefined;
154
+ likeCount?: number | undefined;
153
155
  }[]>;
154
156
  loading: Ref<boolean, boolean>;
155
157
  loaded: Ref<boolean, boolean>;
@@ -1,5 +1,14 @@
1
+ import { InjectionKey, MaybeRefOrGetter } from 'vue';
1
2
  export declare function useMemiBoardLikes(boardId: string, postId: string): {
2
3
  isLiked: import('vue').Ref<boolean, boolean>;
3
4
  likePending: import('vue').Ref<boolean, boolean>;
4
5
  toggleLike: () => Promise<boolean>;
5
6
  };
7
+ export interface MemiBoardCommentLikesApi {
8
+ isLiked: (commentId: string | undefined) => boolean;
9
+ isPending: (commentId: string | undefined) => boolean;
10
+ toggleLike: (commentId: string) => Promise<boolean>;
11
+ }
12
+ export declare const memiBoardCommentLikesKey: InjectionKey<MemiBoardCommentLikesApi>;
13
+ /** 글 하나에서 내가 누른 댓글 좋아요를 쿼리 1번으로 구독한다. 댓글마다 리스너를 달지 않는다. */
14
+ export declare function useMemiBoardCommentLikes(boardId: MaybeRefOrGetter<string>, postId: MaybeRefOrGetter<string>): MemiBoardCommentLikesApi;
@@ -0,0 +1,12 @@
1
+ import { MaybeRefOrGetter } from 'vue';
2
+ import { PostModel } from '../types';
3
+ export declare function useMemiBoardMiniLatest(boardId: MaybeRefOrGetter<string | null>, count: MaybeRefOrGetter<number>): {
4
+ posts: import('vue').ComputedRef<import('vuefire').VueFirestoreQueryData<PostModel>>;
5
+ pending: import('vue').Ref<boolean, boolean>;
6
+ error: import('vue').Ref<import('firebase/firestore').FirestoreError | undefined, import('firebase/firestore').FirestoreError | undefined>;
7
+ };
8
+ export declare function useMemiBoardMiniPicked(postIds: MaybeRefOrGetter<string[]>): {
9
+ posts: import('vue').ComputedRef<PostModel[]>;
10
+ pending: import('vue').Ref<boolean, boolean>;
11
+ error: import('vue').Ref<import('firebase/firestore').FirestoreError | undefined, import('firebase/firestore').FirestoreError | undefined>;
12
+ };
@@ -0,0 +1,114 @@
1
+ import { MaybeRefOrGetter } from 'vue';
2
+ import { BoardReportModel, BoardReportReason, BoardReportStatus } from '../types';
3
+ export declare const REPORT_DETAIL_MAX_LENGTH = 200;
4
+ export declare const REPORT_REASONS: {
5
+ value: BoardReportReason;
6
+ label: string;
7
+ }[];
8
+ export declare function reportReasonLabel(reason: BoardReportReason | string | undefined): string;
9
+ export declare function useMemiBoardReports(boardId: string, postId: string): {
10
+ hasReported: import('vue').Ref<boolean, boolean>;
11
+ pending: import('vue').Ref<boolean, boolean>;
12
+ submitReport: (input: {
13
+ reason: BoardReportReason;
14
+ detail?: string;
15
+ postTitle?: string;
16
+ authorUid?: string;
17
+ authorName?: string | null;
18
+ }) => Promise<void>;
19
+ };
20
+ export declare function useMemiBoardReportQueue(options?: {
21
+ pageSize?: number;
22
+ boardId?: MaybeRefOrGetter<string>;
23
+ }): {
24
+ reports: import('vue').Ref<{
25
+ id?: string | undefined;
26
+ uid: string;
27
+ target: import('..').BoardReportTarget;
28
+ postId: string;
29
+ boardId: string;
30
+ reason: BoardReportReason;
31
+ detail: string;
32
+ status: BoardReportStatus;
33
+ createdAt: {
34
+ readonly seconds: number;
35
+ readonly nanoseconds: number;
36
+ toDate: () => Date;
37
+ toMillis: () => number;
38
+ isEqual: (other: import('firebase/firestore').Timestamp) => boolean;
39
+ toString: () => string;
40
+ toJSON: () => {
41
+ seconds: number;
42
+ nanoseconds: number;
43
+ type: string;
44
+ };
45
+ valueOf: () => string;
46
+ };
47
+ postTitle?: string | undefined;
48
+ authorUid?: string | undefined;
49
+ authorName?: string | null | undefined;
50
+ reviewedAt?: {
51
+ readonly seconds: number;
52
+ readonly nanoseconds: number;
53
+ toDate: () => Date;
54
+ toMillis: () => number;
55
+ isEqual: (other: import('firebase/firestore').Timestamp) => boolean;
56
+ toString: () => string;
57
+ toJSON: () => {
58
+ seconds: number;
59
+ nanoseconds: number;
60
+ type: string;
61
+ };
62
+ valueOf: () => string;
63
+ } | undefined;
64
+ reviewedBy?: string | undefined;
65
+ }[], BoardReportModel[] | {
66
+ id?: string | undefined;
67
+ uid: string;
68
+ target: import('..').BoardReportTarget;
69
+ postId: string;
70
+ boardId: string;
71
+ reason: BoardReportReason;
72
+ detail: string;
73
+ status: BoardReportStatus;
74
+ createdAt: {
75
+ readonly seconds: number;
76
+ readonly nanoseconds: number;
77
+ toDate: () => Date;
78
+ toMillis: () => number;
79
+ isEqual: (other: import('firebase/firestore').Timestamp) => boolean;
80
+ toString: () => string;
81
+ toJSON: () => {
82
+ seconds: number;
83
+ nanoseconds: number;
84
+ type: string;
85
+ };
86
+ valueOf: () => string;
87
+ };
88
+ postTitle?: string | undefined;
89
+ authorUid?: string | undefined;
90
+ authorName?: string | null | undefined;
91
+ reviewedAt?: {
92
+ readonly seconds: number;
93
+ readonly nanoseconds: number;
94
+ toDate: () => Date;
95
+ toMillis: () => number;
96
+ isEqual: (other: import('firebase/firestore').Timestamp) => boolean;
97
+ toString: () => string;
98
+ toJSON: () => {
99
+ seconds: number;
100
+ nanoseconds: number;
101
+ type: string;
102
+ };
103
+ valueOf: () => string;
104
+ } | undefined;
105
+ reviewedBy?: string | undefined;
106
+ }[]>;
107
+ pending: import('vue').Ref<boolean, boolean>;
108
+ loadingMore: import('vue').Ref<boolean, boolean>;
109
+ hasMore: import('vue').Ref<boolean, boolean>;
110
+ loadError: import('vue').Ref<string, string>;
111
+ loadMore: () => Promise<void>;
112
+ dismissReport: (report: BoardReportModel) => Promise<void>;
113
+ actionReport: (report: BoardReportModel) => Promise<void>;
114
+ };
@@ -0,0 +1,13 @@
1
+ import { BoardSection, BoardSectionLayout } from '../types';
2
+ export declare const DEFAULT_BOARD_SECTIONS: BoardSection[];
3
+ export declare function normalizeBoardSection(raw: Record<string, unknown>, order: number): BoardSection;
4
+ /** 호스트 `settings/homeLayout` 문서를 읽는다. 기존 메인 레이아웃 경로를 유지한다. */
5
+ export declare function useMemiBoardSections(): {
6
+ settings: import('vue').Ref<import('vuefire').VueFirestoreDocumentData<BoardSectionLayout> | undefined, import('vuefire').VueFirestoreDocumentData<BoardSectionLayout> | undefined>;
7
+ pending: import('vue').Ref<boolean, boolean>;
8
+ sections: import('vue').ComputedRef<BoardSection[]>;
9
+ blocks: import('vue').ComputedRef<BoardSection[]>;
10
+ hasSavedLayout: import('vue').ComputedRef<boolean>;
11
+ saveSections: (next: BoardSection[]) => Promise<void>;
12
+ saveBlocks: (next: BoardSection[]) => Promise<void>;
13
+ };
@@ -16,6 +16,8 @@ export declare function useMemiBoardSettings(): {
16
16
  boardVisibility: (id: string | undefined) => BoardVisibility;
17
17
  isBoardHidden: (id: string | undefined) => boolean;
18
18
  getBoard: (id: string | undefined) => BoardModel | undefined;
19
+ canManageBoard: (boardId: string | undefined | null) => boolean;
20
+ managedBoards: import('vue').ComputedRef<BoardModel[]>;
19
21
  categories: import('vue').ComputedRef<BoardModel[]>;
20
22
  publicCategories: import('vue').ComputedRef<BoardModel[]>;
21
23
  categoryLabel: (id: string | undefined) => string | undefined;
package/dist/config.d.ts CHANGED
@@ -30,6 +30,8 @@ export interface MemiBoardConfig {
30
30
  commentsCollection: string;
31
31
  /** 좋아요 컬렉션. 기본 `memiBoardLikes` */
32
32
  likesCollection: string;
33
+ /** 신고 컬렉션. 기본 `memiBoardReports` */
34
+ reportsCollection: string;
33
35
  /** 보드 설정 컬렉션. 기본 `memiBoardSettings` */
34
36
  settingsCollection: string;
35
37
  /** 역할 문서 컬렉션. 기본 `memiBoardUsers` */
@@ -1,4 +1,4 @@
1
- import { O as e } from "./slugify-B5XHr4SZ.js";
1
+ import { M as e } from "./slugify-Cyq-Gg8X.js";
2
2
  //#region node_modules/.pnpm/heic2any@0.0.4/node_modules/heic2any/dist/heic2any.js
3
3
  var t = /* @__PURE__ */ e(((e, t) => {
4
4
  (function(n, r, i, a) {
@@ -974,4 +974,4 @@ var t = /* @__PURE__ */ e(((e, t) => {
974
974
  //#endregion
975
975
  export default t();
976
976
 
977
- //# sourceMappingURL=heic2any-BWRKffWN.js.map
977
+ //# sourceMappingURL=heic2any-D2QKy06h.js.map