@blogmcp/community 0.2.0 → 0.3.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 (82) hide show
  1. package/README.md +64 -12
  2. package/dist/blogmcp-community.global.js +7 -7
  3. package/dist/chunks/{mount-WadUnaTa.js → mount--2_V2q3M.js} +4724 -1097
  4. package/dist/core/attrs.d.ts +17 -2
  5. package/dist/core/discussion.d.ts +92 -18
  6. package/dist/core/follow.d.ts +26 -0
  7. package/dist/core/forum-category.d.ts +85 -0
  8. package/dist/core/forum-search.d.ts +35 -0
  9. package/dist/core/forum-state.d.ts +30 -0
  10. package/dist/core/forum-words.d.ts +8 -0
  11. package/dist/core/forum.d.ts +21 -0
  12. package/dist/core/moderation.d.ts +80 -0
  13. package/dist/core/onboarding.d.ts +7 -0
  14. package/dist/core/profile.d.ts +6 -1
  15. package/dist/core/realtime.d.ts +13 -2
  16. package/dist/core/topic.d.ts +47 -0
  17. package/dist/element.d.ts +26 -1
  18. package/dist/global.d.ts +7 -6
  19. package/dist/index.d.ts +13 -4
  20. package/dist/index.js +54 -27
  21. package/dist/mount.d.ts +50 -1
  22. package/dist/react.d.ts +17 -4
  23. package/dist/react.js +32 -8
  24. package/dist/ui/FollowButtonView.vue.d.ts +17 -0
  25. package/dist/ui/FollowToggle.vue.d.ts +6 -0
  26. package/dist/ui/ForumCategoryView.vue.d.ts +13 -0
  27. package/dist/ui/ForumModerationView.vue.d.ts +12 -0
  28. package/dist/ui/ForumNotices.vue.d.ts +7 -0
  29. package/dist/ui/ForumSearchView.vue.d.ts +11 -0
  30. package/dist/ui/ForumView.vue.d.ts +12 -0
  31. package/dist/ui/MembersOnly.vue.d.ts +15 -0
  32. package/dist/ui/MessageComposer.vue.d.ts +6 -2
  33. package/dist/ui/MessageMenu.vue.d.ts +2 -0
  34. package/dist/ui/ModeratorDialog.vue.d.ts +11 -0
  35. package/dist/ui/TopicRow.vue.d.ts +10 -0
  36. package/dist/ui/TopicView.vue.d.ts +32 -0
  37. package/dist/ui/context.d.ts +7 -1
  38. package/dist/ui/discussion-context.d.ts +11 -3
  39. package/dist/vue.d.ts +164 -5
  40. package/dist/vue.js +134 -8
  41. package/package.json +3 -3
  42. package/src/core/attrs.ts +22 -2
  43. package/src/core/discussion.ts +309 -43
  44. package/src/core/follow.ts +139 -0
  45. package/src/core/forum-category.ts +323 -0
  46. package/src/core/forum-search.ts +153 -0
  47. package/src/core/forum-state.ts +59 -0
  48. package/src/core/forum-words.ts +38 -0
  49. package/src/core/forum.ts +83 -0
  50. package/src/core/moderation.ts +414 -0
  51. package/src/core/onboarding.ts +16 -4
  52. package/src/core/outcomes.ts +10 -0
  53. package/src/core/profile.ts +12 -1
  54. package/src/core/realtime.ts +15 -2
  55. package/src/core/topic.ts +234 -0
  56. package/src/element.ts +63 -1
  57. package/src/global.ts +27 -5
  58. package/src/index.ts +49 -2
  59. package/src/mount.ts +144 -2
  60. package/src/react.ts +45 -2
  61. package/src/ui/FollowButtonView.vue +85 -0
  62. package/src/ui/FollowToggle.vue +39 -0
  63. package/src/ui/ForumCategoryView.vue +243 -0
  64. package/src/ui/ForumModerationView.vue +309 -0
  65. package/src/ui/ForumNotices.vue +23 -0
  66. package/src/ui/ForumSearchView.vue +171 -0
  67. package/src/ui/ForumView.vue +128 -0
  68. package/src/ui/MembersOnly.vue +59 -0
  69. package/src/ui/MessageComposer.vue +51 -9
  70. package/src/ui/MessageItem.vue +12 -1
  71. package/src/ui/MessageMenu.vue +3 -2
  72. package/src/ui/ModeratorDialog.vue +102 -0
  73. package/src/ui/MyCommunityView.vue +3 -0
  74. package/src/ui/NoticeStatusView.vue +15 -2
  75. package/src/ui/NotificationCentreView.vue +37 -3
  76. package/src/ui/ProfileView.vue +15 -4
  77. package/src/ui/TopicRow.vue +70 -0
  78. package/src/ui/TopicView.vue +395 -0
  79. package/src/ui/context.ts +8 -3
  80. package/src/ui/discussion-context.ts +11 -3
  81. package/src/ui/styles.css +14 -0
  82. package/src/vue.ts +145 -3
@@ -1,9 +1,14 @@
1
- /** What the discussion's components share: the controller, the host's routes, what the reader opened. */
1
+ /**
2
+ * What the discussion's components share: the controller, the host's routes, what the reader opened — M3: under a
3
+ * topic too (`<blogmcp-topic>` provides its thread's controller, and the moderator's items of a message's menu).
4
+ */
2
5
  import { type ComputedRef } from 'vue';
3
- import type { DiscussionController } from '../core/discussion';
6
+ import type { DiscussionMessage } from '@blogmcp/sdk/community';
7
+ import type { ThreadController } from '../core/discussion';
4
8
  import type { routesWithDefaults } from '../core/attrs';
9
+ import type { MenuItem } from './types';
5
10
  export interface DiscussionContext {
6
- ctl: DiscussionController;
11
+ ctl: ThreadController;
7
12
  routes: ComputedRef<ReturnType<typeof routesWithDefaults>>;
8
13
  /** Messages of a blocked author the reader opened anyway. */
9
14
  opened: Set<number>;
@@ -14,6 +19,9 @@ export interface DiscussionContext {
14
19
  /** Where the focus goes when the element it came from is gone (a deleted message). */
15
20
  focusHeading(): HTMLElement | null;
16
21
  deleteMessage(seq: number): void;
22
+ /** M3: more items of a message's menu (a category moderator's), and what picking one does. */
23
+ extraMenu?(m: DiscussionMessage): MenuItem[];
24
+ onExtraMenu?(id: string, m: DiscussionMessage): void;
17
25
  }
18
26
  export declare function provideDiscussion(ctx: DiscussionContext): void;
19
27
  export declare function useDiscussion(): DiscussionContext;
package/dist/vue.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
- * `<KbDiscussion>`, `<KbNoticeForm>`, `<KbMyCommunity>` and (M2) `<KbNotificationCentre>`, `<KbBadge>`,
3
- * `<KbMemberSettings>`, `<KbProfile>`, `<KbWelcome>` for Vue / Nuxt: each mounts its element on the
2
+ * `<KbDiscussion>`, `<KbNoticeForm>`, `<KbMyCommunity>`, (M2) `<KbNotificationCentre>`, `<KbBadge>`,
3
+ * `<KbMemberSettings>`, `<KbProfile>`, `<KbWelcome>` and (M3) `<KbForum>`, `<KbForumCategory>`, `<KbTopic>`,
4
+ * `<KbForumSearch>`, `<KbFollowButton>`, `<KbForumModeration>` for Vue / Nuxt: each mounts its element on the
4
5
  * client, after hydration, and removes it on unmount. Each renders one empty `<div>` as the mount point —
5
6
  * nothing else, on the server or the client — so the element sits where the component does and the page's
6
7
  * markup is otherwise the site's alone.
@@ -13,8 +14,8 @@
13
14
  import { type PropType } from 'vue';
14
15
  import type { CommunityTransport } from '@blogmcp/sdk/community';
15
16
  import type { CommunityRoutes } from './core/attrs';
16
- import type { DiscussionRealtime } from './core/realtime';
17
- import type { MountBadgeOptions, MountDiscussionOptions, MountMemberSettingsOptions, MountMyCommunityOptions, MountNoticeFormOptions, MountNotificationCentreOptions, MountProfileOptions, MountWelcomeOptions } from './mount';
17
+ import type { DiscussionRealtime, TopicRealtime } from './core/realtime';
18
+ import type { MountBadgeOptions, MountDiscussionOptions, MountFollowButtonOptions, MountForumCategoryOptions, MountForumModerationOptions, MountForumOptions, MountForumSearchOptions, MountMemberSettingsOptions, MountMyCommunityOptions, MountNoticeFormOptions, MountNotificationCentreOptions, MountProfileOptions, MountTopicOptions, MountWelcomeOptions } from './mount';
18
19
  export declare const KbDiscussion: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
19
20
  articleId: {
20
21
  type: StringConstructor;
@@ -197,4 +198,162 @@ export declare const KbWelcome: import("vue").DefineComponent<import("vue").Extr
197
198
  nonce: StringConstructor;
198
199
  transport: PropType<CommunityTransport>;
199
200
  }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
200
- export type { MountDiscussionOptions, MountNoticeFormOptions, MountMyCommunityOptions, MountNotificationCentreOptions, MountBadgeOptions, MountMemberSettingsOptions, MountProfileOptions, MountWelcomeOptions, CommunityRoutes, DiscussionRealtime, };
201
+ export declare const KbForum: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
202
+ apiBase: StringConstructor;
203
+ siteBase: StringConstructor;
204
+ lang: StringConstructor;
205
+ signInHref: StringConstructor;
206
+ routes: PropType<CommunityRoutes>;
207
+ nonce: StringConstructor;
208
+ transport: PropType<CommunityTransport>;
209
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
210
+ [key: string]: any;
211
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
212
+ apiBase: StringConstructor;
213
+ siteBase: StringConstructor;
214
+ lang: StringConstructor;
215
+ signInHref: StringConstructor;
216
+ routes: PropType<CommunityRoutes>;
217
+ nonce: StringConstructor;
218
+ transport: PropType<CommunityTransport>;
219
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
220
+ export declare const KbForumCategory: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
221
+ slug: {
222
+ type: StringConstructor;
223
+ required: true;
224
+ };
225
+ apiBase: StringConstructor;
226
+ siteBase: StringConstructor;
227
+ lang: StringConstructor;
228
+ signInHref: StringConstructor;
229
+ routes: PropType<CommunityRoutes>;
230
+ nonce: StringConstructor;
231
+ transport: PropType<CommunityTransport>;
232
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
233
+ [key: string]: any;
234
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
235
+ slug: {
236
+ type: StringConstructor;
237
+ required: true;
238
+ };
239
+ apiBase: StringConstructor;
240
+ siteBase: StringConstructor;
241
+ lang: StringConstructor;
242
+ signInHref: StringConstructor;
243
+ routes: PropType<CommunityRoutes>;
244
+ nonce: StringConstructor;
245
+ transport: PropType<CommunityTransport>;
246
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
247
+ export declare const KbTopic: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
248
+ conversationId: {
249
+ type: StringConstructor;
250
+ required: true;
251
+ };
252
+ apiBase: StringConstructor;
253
+ siteBase: StringConstructor;
254
+ lang: StringConstructor;
255
+ signInHref: StringConstructor;
256
+ routes: PropType<CommunityRoutes>;
257
+ pollMs: NumberConstructor;
258
+ nonce: StringConstructor;
259
+ transport: PropType<CommunityTransport>;
260
+ /** `createRealtimeSession` of `@blogmcp/sdk/community/realtime`. */
261
+ realtime: PropType<TopicRealtime>;
262
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
263
+ [key: string]: any;
264
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
265
+ conversationId: {
266
+ type: StringConstructor;
267
+ required: true;
268
+ };
269
+ apiBase: StringConstructor;
270
+ siteBase: StringConstructor;
271
+ lang: StringConstructor;
272
+ signInHref: StringConstructor;
273
+ routes: PropType<CommunityRoutes>;
274
+ pollMs: NumberConstructor;
275
+ nonce: StringConstructor;
276
+ transport: PropType<CommunityTransport>;
277
+ /** `createRealtimeSession` of `@blogmcp/sdk/community/realtime`. */
278
+ realtime: PropType<TopicRealtime>;
279
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
280
+ export declare const KbForumSearch: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
281
+ apiBase: StringConstructor;
282
+ siteBase: StringConstructor;
283
+ lang: StringConstructor;
284
+ signInHref: StringConstructor;
285
+ routes: PropType<CommunityRoutes>;
286
+ nonce: StringConstructor;
287
+ transport: PropType<CommunityTransport>;
288
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
289
+ [key: string]: any;
290
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
291
+ apiBase: StringConstructor;
292
+ siteBase: StringConstructor;
293
+ lang: StringConstructor;
294
+ signInHref: StringConstructor;
295
+ routes: PropType<CommunityRoutes>;
296
+ nonce: StringConstructor;
297
+ transport: PropType<CommunityTransport>;
298
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
299
+ export declare const KbFollowButton: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
300
+ targetKind: {
301
+ type: PropType<"conversation" | "category">;
302
+ required: true;
303
+ };
304
+ targetId: {
305
+ type: StringConstructor;
306
+ required: true;
307
+ };
308
+ following: {
309
+ type: BooleanConstructor;
310
+ default: undefined;
311
+ };
312
+ apiBase: StringConstructor;
313
+ siteBase: StringConstructor;
314
+ lang: StringConstructor;
315
+ nonce: StringConstructor;
316
+ transport: PropType<CommunityTransport>;
317
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
318
+ [key: string]: any;
319
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
320
+ targetKind: {
321
+ type: PropType<"conversation" | "category">;
322
+ required: true;
323
+ };
324
+ targetId: {
325
+ type: StringConstructor;
326
+ required: true;
327
+ };
328
+ following: {
329
+ type: BooleanConstructor;
330
+ default: undefined;
331
+ };
332
+ apiBase: StringConstructor;
333
+ siteBase: StringConstructor;
334
+ lang: StringConstructor;
335
+ nonce: StringConstructor;
336
+ transport: PropType<CommunityTransport>;
337
+ }>> & Readonly<{}>, {
338
+ following: boolean;
339
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
340
+ export declare const KbForumModeration: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
341
+ categoryId: StringConstructor;
342
+ apiBase: StringConstructor;
343
+ siteBase: StringConstructor;
344
+ lang: StringConstructor;
345
+ routes: PropType<CommunityRoutes>;
346
+ nonce: StringConstructor;
347
+ transport: PropType<CommunityTransport>;
348
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
349
+ [key: string]: any;
350
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
351
+ categoryId: StringConstructor;
352
+ apiBase: StringConstructor;
353
+ siteBase: StringConstructor;
354
+ lang: StringConstructor;
355
+ routes: PropType<CommunityRoutes>;
356
+ nonce: StringConstructor;
357
+ transport: PropType<CommunityTransport>;
358
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
359
+ export type { MountDiscussionOptions, MountNoticeFormOptions, MountMyCommunityOptions, MountNotificationCentreOptions, MountBadgeOptions, MountMemberSettingsOptions, MountProfileOptions, MountWelcomeOptions, MountForumOptions, MountForumCategoryOptions, MountTopicOptions, MountForumSearchOptions, MountFollowButtonOptions, MountForumModerationOptions, CommunityRoutes, DiscussionRealtime, TopicRealtime, };
package/dist/vue.js CHANGED
@@ -47,7 +47,7 @@ const KbDiscussion = defineComponent({
47
47
  return useMount(
48
48
  "discussion",
49
49
  () => defined({ ...props }),
50
- async () => (await import("./chunks/mount-WadUnaTa.js").then((n) => n.J)).mountDiscussion
50
+ async () => (await import("./chunks/mount--2_V2q3M.js").then((n) => n.ad)).mountDiscussion
51
51
  );
52
52
  }
53
53
  });
@@ -68,7 +68,7 @@ const KbNoticeForm = defineComponent({
68
68
  return useMount(
69
69
  "notice-form",
70
70
  () => defined({ ...props }),
71
- async () => (await import("./chunks/mount-WadUnaTa.js").then((n) => n.J)).mountNoticeForm
71
+ async () => (await import("./chunks/mount--2_V2q3M.js").then((n) => n.ad)).mountNoticeForm
72
72
  );
73
73
  }
74
74
  });
@@ -88,7 +88,7 @@ const KbMyCommunity = defineComponent({
88
88
  return useMount(
89
89
  "my-community",
90
90
  () => defined({ ...props }),
91
- async () => (await import("./chunks/mount-WadUnaTa.js").then((n) => n.J)).mountMyCommunity
91
+ async () => (await import("./chunks/mount--2_V2q3M.js").then((n) => n.ad)).mountMyCommunity
92
92
  );
93
93
  }
94
94
  });
@@ -107,7 +107,7 @@ const KbNotificationCentre = defineComponent({
107
107
  return useMount(
108
108
  "notification-centre",
109
109
  () => defined({ ...props }),
110
- async () => (await import("./chunks/mount-WadUnaTa.js").then((n) => n.J)).mountNotificationCentre
110
+ async () => (await import("./chunks/mount--2_V2q3M.js").then((n) => n.ad)).mountNotificationCentre
111
111
  );
112
112
  }
113
113
  });
@@ -126,7 +126,7 @@ const KbBadge = defineComponent({
126
126
  return useMount(
127
127
  "badge",
128
128
  () => defined({ ...props }),
129
- async () => (await import("./chunks/mount-WadUnaTa.js").then((n) => n.J)).mountBadge
129
+ async () => (await import("./chunks/mount--2_V2q3M.js").then((n) => n.ad)).mountBadge
130
130
  );
131
131
  }
132
132
  });
@@ -146,7 +146,7 @@ const KbMemberSettings = defineComponent({
146
146
  return useMount(
147
147
  "member-settings",
148
148
  () => defined({ ...props }),
149
- async () => (await import("./chunks/mount-WadUnaTa.js").then((n) => n.J)).mountMemberSettings
149
+ async () => (await import("./chunks/mount--2_V2q3M.js").then((n) => n.ad)).mountMemberSettings
150
150
  );
151
151
  }
152
152
  });
@@ -166,7 +166,7 @@ const KbProfile = defineComponent({
166
166
  return useMount(
167
167
  "profile",
168
168
  () => defined({ ...props }),
169
- async () => (await import("./chunks/mount-WadUnaTa.js").then((n) => n.J)).mountProfile
169
+ async () => (await import("./chunks/mount--2_V2q3M.js").then((n) => n.ad)).mountProfile
170
170
  );
171
171
  }
172
172
  });
@@ -185,17 +185,143 @@ const KbWelcome = defineComponent({
185
185
  return useMount(
186
186
  "welcome",
187
187
  () => defined({ ...props }),
188
- async () => (await import("./chunks/mount-WadUnaTa.js").then((n) => n.J)).mountWelcome
188
+ async () => (await import("./chunks/mount--2_V2q3M.js").then((n) => n.ad)).mountWelcome
189
+ );
190
+ }
191
+ });
192
+ const KbForum = defineComponent({
193
+ name: "KbForum",
194
+ props: {
195
+ apiBase: String,
196
+ siteBase: String,
197
+ lang: String,
198
+ signInHref: String,
199
+ routes: Object,
200
+ nonce: String,
201
+ transport: Function
202
+ },
203
+ setup(props) {
204
+ return useMount(
205
+ "forum",
206
+ () => defined({ ...props }),
207
+ async () => (await import("./chunks/mount--2_V2q3M.js").then((n) => n.ad)).mountForum
208
+ );
209
+ }
210
+ });
211
+ const KbForumCategory = defineComponent({
212
+ name: "KbForumCategory",
213
+ props: {
214
+ slug: { type: String, required: true },
215
+ apiBase: String,
216
+ siteBase: String,
217
+ lang: String,
218
+ signInHref: String,
219
+ routes: Object,
220
+ nonce: String,
221
+ transport: Function
222
+ },
223
+ setup(props) {
224
+ return useMount(
225
+ "forum-category",
226
+ () => defined({ ...props }),
227
+ async () => (await import("./chunks/mount--2_V2q3M.js").then((n) => n.ad)).mountForumCategory
228
+ );
229
+ }
230
+ });
231
+ const KbTopic = defineComponent({
232
+ name: "KbTopic",
233
+ props: {
234
+ conversationId: { type: String, required: true },
235
+ apiBase: String,
236
+ siteBase: String,
237
+ lang: String,
238
+ signInHref: String,
239
+ routes: Object,
240
+ pollMs: Number,
241
+ nonce: String,
242
+ transport: Function,
243
+ /** `createRealtimeSession` of `@blogmcp/sdk/community/realtime`. */
244
+ realtime: Function
245
+ },
246
+ setup(props) {
247
+ return useMount(
248
+ "topic",
249
+ () => defined({ ...props }),
250
+ async () => (await import("./chunks/mount--2_V2q3M.js").then((n) => n.ad)).mountTopic
251
+ );
252
+ }
253
+ });
254
+ const KbForumSearch = defineComponent({
255
+ name: "KbForumSearch",
256
+ props: {
257
+ apiBase: String,
258
+ siteBase: String,
259
+ lang: String,
260
+ signInHref: String,
261
+ routes: Object,
262
+ nonce: String,
263
+ transport: Function
264
+ },
265
+ setup(props) {
266
+ return useMount(
267
+ "forum-search",
268
+ () => defined({ ...props }),
269
+ async () => (await import("./chunks/mount--2_V2q3M.js").then((n) => n.ad)).mountForumSearch
270
+ );
271
+ }
272
+ });
273
+ const KbFollowButton = defineComponent({
274
+ name: "KbFollowButton",
275
+ props: {
276
+ targetKind: { type: String, required: true },
277
+ targetId: { type: String, required: true },
278
+ following: { type: Boolean, default: void 0 },
279
+ apiBase: String,
280
+ siteBase: String,
281
+ lang: String,
282
+ nonce: String,
283
+ transport: Function
284
+ },
285
+ setup(props) {
286
+ return useMount(
287
+ "follow-button",
288
+ () => defined({ ...props }),
289
+ async () => (await import("./chunks/mount--2_V2q3M.js").then((n) => n.ad)).mountFollowButton
290
+ );
291
+ }
292
+ });
293
+ const KbForumModeration = defineComponent({
294
+ name: "KbForumModeration",
295
+ props: {
296
+ categoryId: String,
297
+ apiBase: String,
298
+ siteBase: String,
299
+ lang: String,
300
+ routes: Object,
301
+ nonce: String,
302
+ transport: Function
303
+ },
304
+ setup(props) {
305
+ return useMount(
306
+ "forum-moderation",
307
+ () => defined({ ...props }),
308
+ async () => (await import("./chunks/mount--2_V2q3M.js").then((n) => n.ad)).mountForumModeration
189
309
  );
190
310
  }
191
311
  });
192
312
  export {
193
313
  KbBadge,
194
314
  KbDiscussion,
315
+ KbFollowButton,
316
+ KbForum,
317
+ KbForumCategory,
318
+ KbForumModeration,
319
+ KbForumSearch,
195
320
  KbMemberSettings,
196
321
  KbMyCommunity,
197
322
  KbNoticeForm,
198
323
  KbNotificationCentre,
199
324
  KbProfile,
325
+ KbTopic,
200
326
  KbWelcome
201
327
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blogmcp/community",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Community elements for your own site: the discussion under an article, the DSA notice form and the reader's „My community” panel. Custom elements + Vue/React wrappers + a script build, over @blogmcp/sdk/community.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -42,8 +42,8 @@
42
42
  "postpack": "node ../../scripts/pack-dist-types.mjs postpack"
43
43
  },
44
44
  "dependencies": {
45
- "@blogmcp/sanitize": "^0.10.0",
46
- "@blogmcp/sdk": "^0.10.0",
45
+ "@blogmcp/sanitize": "^0.11.0",
46
+ "@blogmcp/sdk": "^0.11.0",
47
47
  "vue": "^3.5.0"
48
48
  },
49
49
  "peerDependencies": {
package/src/core/attrs.ts CHANGED
@@ -50,6 +50,17 @@ export interface CommunityRoutes {
50
50
  * message's anchor in the fragment). No default: without it those rows stay unlinked.
51
51
  */
52
52
  articlePath?: string
53
+ /** M3 (m3-contracts.md §5.1, D-M3-12): the forum — its categories, their latest topics, the search box. */
54
+ forumPath?: string
55
+ /** M3: a category's page; `{slug}` is the category's slug (`/komunita/c/{slug}`) — a template, as profilePath is. */
56
+ categoryPath?: string
57
+ /**
58
+ * M3: a topic's page; `{id}` is its conversation id (`/komunita/t/{id}`) — a template; a message's anchor goes in the
59
+ * fragment (`#bcm-msg-<seq>`, topicMessageLink).
60
+ */
61
+ topicPath?: string
62
+ /** M3: the forum's search (the forum's search box goes there with the query in the fragment, `#q=…`). */
63
+ searchPath?: string
53
64
  }
54
65
 
55
66
  const ROUTE_KEYS = [
@@ -65,6 +76,10 @@ const ROUTE_KEYS = [
65
76
  'welcomePath',
66
77
  'discussionsPath',
67
78
  'articlePath',
79
+ 'forumPath',
80
+ 'categoryPath',
81
+ 'topicPath',
82
+ 'searchPath',
68
83
  ] as const
69
84
 
70
85
  /** A path on the host's own site (the SDK's links.ts rule). */
@@ -109,8 +124,8 @@ export function siteBaseOf(value: unknown): string | null {
109
124
  }
110
125
 
111
126
  /**
112
- * The pages with the managed blog's defaults filled in under the site's base (statementPath and profilePath stay
113
- * templates; articlePath, a template too, has no default).
127
+ * The pages with the managed blog's defaults filled in under the site's base (statementPath, profilePath, M3's
128
+ * categoryPath and topicPath stay templates; articlePath, a template too, has no default).
114
129
  * A page the host or the tenant names is its own full path.
115
130
  */
116
131
  export function routesWithDefaults(
@@ -132,6 +147,11 @@ export function routesWithDefaults(
132
147
  welcomePath: routes.welcomePath ?? `${siteBase}${COMMUNITY_ROUTES.welcome}`,
133
148
  discussionsPath: routes.discussionsPath ?? `${siteBase}${COMMUNITY_ROUTES.discussions}`,
134
149
  articlePath: routes.articlePath ?? null,
150
+ // M3 (§5.1): the forum's pages — categoryPath and topicPath are templates (`{slug}`, `{id}`).
151
+ forumPath: routes.forumPath ?? `${siteBase}${COMMUNITY_ROUTES.forum}`,
152
+ categoryPath: routes.categoryPath ?? `${siteBase}${COMMUNITY_ROUTES.category}`,
153
+ topicPath: routes.topicPath ?? `${siteBase}${COMMUNITY_ROUTES.topic}`,
154
+ searchPath: routes.searchPath ?? `${siteBase}${COMMUNITY_ROUTES.search}`,
135
155
  }
136
156
  }
137
157