@blogmcp/community 0.1.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 (114) hide show
  1. package/README.md +162 -13
  2. package/dist/blogmcp-community.global.js +8 -8
  3. package/dist/chunks/mount--2_V2q3M.js +10063 -0
  4. package/dist/core/attrs.d.ts +42 -1
  5. package/dist/core/badge.d.ts +30 -0
  6. package/dist/core/centre.d.ts +28 -0
  7. package/dist/core/digest-token.d.ts +2 -0
  8. package/dist/core/discussion.d.ts +120 -15
  9. package/dist/core/follow.d.ts +26 -0
  10. package/dist/core/format.d.ts +7 -0
  11. package/dist/core/forum-category.d.ts +85 -0
  12. package/dist/core/forum-search.d.ts +35 -0
  13. package/dist/core/forum-state.d.ts +30 -0
  14. package/dist/core/forum-words.d.ts +8 -0
  15. package/dist/core/forum.d.ts +21 -0
  16. package/dist/core/moderation.d.ts +80 -0
  17. package/dist/core/onboarding.d.ts +49 -2
  18. package/dist/core/outcomes.d.ts +2 -1
  19. package/dist/core/panel.d.ts +17 -1
  20. package/dist/core/profile.d.ts +45 -0
  21. package/dist/core/push.d.ts +63 -0
  22. package/dist/core/realtime.d.ts +46 -0
  23. package/dist/core/settings.d.ts +53 -0
  24. package/dist/core/text.d.ts +13 -0
  25. package/dist/core/topic.d.ts +47 -0
  26. package/dist/element.d.ts +47 -1
  27. package/dist/global.d.ts +8 -4
  28. package/dist/index.d.ts +20 -3
  29. package/dist/index.js +62 -12
  30. package/dist/mount.d.ts +89 -0
  31. package/dist/react.d.ts +27 -3
  32. package/dist/react.js +48 -4
  33. package/dist/ui/BadgeView.vue.d.ts +13 -0
  34. package/dist/ui/DiscussionView.vue.d.ts +11 -0
  35. package/dist/ui/FollowButtonView.vue.d.ts +17 -0
  36. package/dist/ui/FollowToggle.vue.d.ts +6 -0
  37. package/dist/ui/ForumCategoryView.vue.d.ts +13 -0
  38. package/dist/ui/ForumModerationView.vue.d.ts +12 -0
  39. package/dist/ui/ForumNotices.vue.d.ts +7 -0
  40. package/dist/ui/ForumSearchView.vue.d.ts +11 -0
  41. package/dist/ui/ForumView.vue.d.ts +12 -0
  42. package/dist/ui/LinkedText.vue.d.ts +1 -0
  43. package/dist/ui/MemberSettingsView.vue.d.ts +13 -0
  44. package/dist/ui/MembersOnly.vue.d.ts +15 -0
  45. package/dist/ui/MessageComposer.vue.d.ts +8 -2
  46. package/dist/ui/MessageMenu.vue.d.ts +2 -0
  47. package/dist/ui/ModeratorDialog.vue.d.ts +11 -0
  48. package/dist/ui/NotificationCentreView.vue.d.ts +12 -0
  49. package/dist/ui/OnboardingSteps.vue.d.ts +1 -1
  50. package/dist/ui/ProfileView.vue.d.ts +13 -0
  51. package/dist/ui/TopicRow.vue.d.ts +10 -0
  52. package/dist/ui/TopicView.vue.d.ts +32 -0
  53. package/dist/ui/WelcomeView.vue.d.ts +12 -0
  54. package/dist/ui/context.d.ts +7 -1
  55. package/dist/ui/discussion-context.d.ts +11 -3
  56. package/dist/vue.d.ts +273 -3
  57. package/dist/vue.js +235 -5
  58. package/package.json +3 -3
  59. package/src/core/attrs.ts +61 -2
  60. package/src/core/badge.ts +120 -0
  61. package/src/core/centre.ts +136 -0
  62. package/src/core/digest-token.ts +26 -0
  63. package/src/core/discussion.ts +497 -46
  64. package/src/core/follow.ts +139 -0
  65. package/src/core/format.ts +22 -0
  66. package/src/core/forum-category.ts +323 -0
  67. package/src/core/forum-search.ts +153 -0
  68. package/src/core/forum-state.ts +59 -0
  69. package/src/core/forum-words.ts +38 -0
  70. package/src/core/forum.ts +83 -0
  71. package/src/core/moderation.ts +414 -0
  72. package/src/core/onboarding.ts +177 -18
  73. package/src/core/outcomes.ts +23 -1
  74. package/src/core/panel.ts +39 -4
  75. package/src/core/profile.ts +155 -0
  76. package/src/core/push.ts +174 -0
  77. package/src/core/realtime.ts +52 -0
  78. package/src/core/settings.ts +363 -0
  79. package/src/core/text.ts +32 -0
  80. package/src/core/topic.ts +234 -0
  81. package/src/element.ts +124 -6
  82. package/src/global.ts +40 -4
  83. package/src/index.ts +84 -2
  84. package/src/mount.ts +250 -1
  85. package/src/react.ts +90 -3
  86. package/src/ui/BadgeView.vue +51 -0
  87. package/src/ui/DiscussionView.vue +43 -3
  88. package/src/ui/FollowButtonView.vue +85 -0
  89. package/src/ui/FollowToggle.vue +39 -0
  90. package/src/ui/ForumCategoryView.vue +243 -0
  91. package/src/ui/ForumModerationView.vue +309 -0
  92. package/src/ui/ForumNotices.vue +23 -0
  93. package/src/ui/ForumSearchView.vue +171 -0
  94. package/src/ui/ForumView.vue +128 -0
  95. package/src/ui/LinkedText.vue +11 -5
  96. package/src/ui/MemberSettingsView.vue +175 -0
  97. package/src/ui/MembersOnly.vue +59 -0
  98. package/src/ui/MessageComposer.vue +64 -11
  99. package/src/ui/MessageItem.vue +36 -7
  100. package/src/ui/MessageMenu.vue +3 -2
  101. package/src/ui/ModeratorDialog.vue +102 -0
  102. package/src/ui/MyCommunityView.vue +12 -0
  103. package/src/ui/NoticeStatusView.vue +15 -2
  104. package/src/ui/NotificationCentreView.vue +193 -0
  105. package/src/ui/OnboardingSteps.vue +17 -6
  106. package/src/ui/ProfileView.vue +102 -0
  107. package/src/ui/TopicRow.vue +70 -0
  108. package/src/ui/TopicView.vue +395 -0
  109. package/src/ui/WelcomeView.vue +72 -0
  110. package/src/ui/context.ts +8 -3
  111. package/src/ui/discussion-context.ts +11 -3
  112. package/src/ui/styles.css +30 -0
  113. package/src/vue.ts +271 -3
  114. package/dist/chunks/mount-DzjwnHyx.js +0 -4597
@@ -1,7 +1,15 @@
1
- import { type AgeBand, type CommunityClient, type CommunityFailure, type CommunityRule, type CurrentLegalText, type MyState } from '@blogmcp/sdk/community';
2
- import type { Lang } from './i18n';
1
+ import { type AgeBand, type Art9ConsentLayout, type Art9Refusal, type CommunityClient, type CommunityFailure, type CommunityRule, type CurrentLegalText, type MyState } from '@blogmcp/sdk/community';
2
+ import type { Lang, TextRef } from './i18n';
3
3
  export type OnboardingStep = 'age' | 'rules' | 'art9' | 'profile';
4
4
  export type OnboardingPhase = 'loading' | 'ready' | 'sending' | 'done' | 'failed' | 'refused';
5
+ /** One consent of the art. 9 step: a controller the text names (or the tenant, when it names none) and its tick. */
6
+ export interface Art9Consent {
7
+ /** The controller's registry ref from the text's `bodyJson`; empty for the tenant of a text that names none. */
8
+ readonly entity: string;
9
+ /** The name the reader sees; empty for the tenant of a text that names none (the step's own sentence then). */
10
+ readonly label: string;
11
+ accepted: boolean;
12
+ }
5
13
  export interface OnboardingState {
6
14
  /** `refused`: the age band is below the minimum — nothing was stored, the reader may go on reading. */
7
15
  phase: OnboardingPhase;
@@ -12,10 +20,24 @@ export interface OnboardingState {
12
20
  rules: CommunityRule[];
13
21
  art9Text: CurrentLegalText | null;
14
22
  art9Paragraphs: string[];
23
+ /** me/get's `settings.art9ConsentLayout` (absent: `joint`). */
24
+ art9Layout: Art9ConsentLayout;
25
+ /** me/get's `settings.art9Refusal` (absent: `read_only`). */
26
+ art9Refusal: Art9Refusal;
27
+ /**
28
+ * The consents the step asks for, each ticked on its own: one under `joint` (or a text naming fewer than two
29
+ * controllers), the text's two controllers under `split`. Empty while there is no art. 9 text.
30
+ */
31
+ art9Controllers: Art9Consent[];
32
+ /** The reader chose „Nesouhlasím" (only where the refusal is offered). */
33
+ art9Refused: boolean;
34
+ /** The me/onboard kb-api took carried the refusal (the done note says what it leaves). */
35
+ art9RefusedSent: boolean;
15
36
  /** The member accepted an older rules text; this one asks again. */
16
37
  reconsent: boolean;
17
38
  ageBand: AgeBand | null;
18
39
  rulesAccepted: boolean;
40
+ /** Every consent of the step ticked (setting it ticks or unticks them all). */
19
41
  art9Accepted: boolean;
20
42
  displayName: string;
21
43
  handle: string;
@@ -37,6 +59,15 @@ export interface OnboardingModel {
37
59
  back(): void;
38
60
  suggestedHandle(): string | null;
39
61
  hasFieldError(field: string): boolean;
62
+ /**
63
+ * The tenant takes a refusal of the art. 9 consent here (`art9Refusal = public_write`, not the give-consent flow): the
64
+ * step's lede then does not make writing depend on the consent (community.md §9.8 q8, art. 9(2)(a) — freely given).
65
+ */
66
+ refusalOffered(): boolean;
67
+ /** The art. 9 step offers „Nesouhlasím" (`art9Refusal = public_write`, never in the give-consent flow). */
68
+ canRefuse(): boolean;
69
+ /** „Nesouhlasím": the step is answered without the consent; the next step, or — on the last one — `me/onboard`. */
70
+ refuse(): Promise<void>;
40
71
  }
41
72
  export interface OnboardingOptions {
42
73
  client: Pick<CommunityClient, 'me' | 'legal'>;
@@ -47,6 +78,22 @@ export interface OnboardingOptions {
47
78
  onSignIn(): void;
48
79
  /** The age band was below the tenant's minimum (AGE_BELOW_MINIMUM): kb-api kept nothing, the element keeps this. */
49
80
  onRefused?(minAge: number): void;
81
+ /**
82
+ * The give-consent flow („Moje komunita", M2 T9): the art. 9 step is asked also when `needs` does not name it — a
83
+ * member who refused it, or has not given it, where the tenant asks for it — and no refusal is offered.
84
+ */
85
+ offerArt9?: boolean;
86
+ /**
87
+ * M3 (m3-contracts.md §3.1 step 5, U14): a forum write needs the art. 9 consent whatever the tenant's
88
+ * `require_art9_consent` says, so the step is asked whenever the consent is not in force — me/get names no
89
+ * `art9_consent` need (and may report the consent as null) where the tenant does not ask for it elsewhere. A refusal
90
+ * is offered as for the discussion (`art9Refusal = public_write`), and a member who refused there is not asked again.
91
+ */
92
+ forceArt9?: boolean;
50
93
  signal?: AbortSignal;
51
94
  }
95
+ /** kb-api's issue code for a refusal the tenant does not take (`art9Refusal = read_only`, §3.8). */
96
+ export declare const ART9_REFUSAL_READ_ONLY = "art9_refusal_read_only";
97
+ /** The sentence a refusal of the onboarding shows: a refused refusal says the consent is needed to write. */
98
+ export declare function onboardingFailureText(failure: Pick<CommunityFailure, 'code' | 'until' | 'fields' | 'issueCodes'>, lang: Lang): TextRef;
52
99
  export declare function createOnboarding(o: OnboardingOptions): OnboardingModel;
@@ -20,7 +20,8 @@ export declare const COUNTDOWN_MAX_S = 120;
20
20
  * - `onboard` the inline onboarding opens (ONBOARDING_REQUIRED);
21
21
  * - `reload` the discussion is read again (the viewer's state changed: a sanction, a session);
22
22
  * - `notMoved` the discussion does not take writes yet: one sentence instead of the composer;
23
- * - `hide` discussions are switched off: the element renders nothing;
23
+ * - `hide` discussions are switched off: the element renders nothing (`realtimeOff`: only live updates are —
24
+ * the element keeps polling and never hides the discussion for it);
24
25
  * - `retry` nothing was decided (no answer, the ledger): the same write may go again as it is;
25
26
  * - `show` anything else: the sentence, the text stays for the author to change.
26
27
  */
@@ -1,5 +1,16 @@
1
+ /**
2
+ * `<blogmcp-my-community>`'s brain (m1-contracts.md §7.4, §8.3, §10.4; community.md §5): the reader's
3
+ * „Moje komunita" — the statements of reasons about their content (the art. 17(3) fields, the appeal within
4
+ * its window, an open appeal's state), their reports, their notifications, the art. 9 consent's withdrawal (and,
5
+ * M2 T9, a refused one given after all — the onboarding's art. 9 step, opened here), their blocks, the export (a JSON download) and the profile's deletion with its grace and cancel.
6
+ *
7
+ * With a decision id it shows that one statement (the host's statement page). With a notifier's receipt it
8
+ * shows that notice's status and appeal — `notices/status`, `notices/appeal`: no session needed for that.
9
+ */
10
+ import { type ShallowRef } from 'vue';
1
11
  import { type AppealItem, type BlockEntry, type CommunityClient, type CommunityFailure, type DecisionSummary, type MemberExport, type MyState, type NoticeStatus, type NotificationItem, type ReportItem, type Statement } from '@blogmcp/sdk/community';
2
- import type { TextRef } from './i18n';
12
+ import { type TextRef } from './i18n';
13
+ import { type OnboardingModel } from './onboarding';
3
14
  export type LoadPhase = 'idle' | 'loading' | 'ready' | 'failed';
4
15
  export interface AppealDraft {
5
16
  body: string;
@@ -48,6 +59,8 @@ export interface PanelUi {
48
59
  }
49
60
  export interface PanelController {
50
61
  readonly state: PanelUi;
62
+ /** The give-consent flow (the onboarding's art. 9 step), while it is open. */
63
+ readonly consent: ShallowRef<OnboardingModel | null>;
51
64
  /** The member's sections (me/get and the lists). */
52
65
  loadMember(): Promise<void>;
53
66
  loadStatement(decisionId: string): Promise<void>;
@@ -59,6 +72,9 @@ export interface PanelController {
59
72
  fileNoticeAppeal(): Promise<void>;
60
73
  withdrawNoticeAppeal(): Promise<void>;
61
74
  withdrawConsent(): Promise<boolean>;
75
+ /** A refused art. 9 consent (`acceptances.art9Refused`): opens its step to give it after all. */
76
+ giveConsent(): void;
77
+ closeConsent(): void;
62
78
  unblock(memberId: string): Promise<void>;
63
79
  editProfile(): void;
64
80
  cancelProfile(): void;
@@ -0,0 +1,45 @@
1
+ import { type CommunityClient, type CommunityFailure, type CommunityMessageKey, type CommunityRule, type ForumCategoryRef, type KnownAuthor } from '@blogmcp/sdk/community';
2
+ export type LoadPhase = 'idle' | 'loading' | 'ready' | 'failed' | 'signIn' | 'notFound';
3
+ export interface ProfileUi {
4
+ phase: LoadPhase;
5
+ failure: CommunityFailure | null;
6
+ author: KnownAuthor | null;
7
+ memberSince: string | null;
8
+ bio: string | null;
9
+ /** The trust level's catalogue key (`trust.<n>`), or null for a label this SDK does not know. */
10
+ trustKey: CommunityMessageKey | null;
11
+ /**
12
+ * M3 (m3-contracts.md §3.5, §4.5; D-M3-7): the categories the member moderates now — „Moderátor" names this
13
+ * appointment on the profile, never a trust level; kb-api sends it to a forum member only (else none).
14
+ */
15
+ moderates: ForumCategoryRef[];
16
+ }
17
+ export interface ProfileController {
18
+ readonly state: ProfileUi;
19
+ load(handle: string): Promise<void>;
20
+ dispose(): void;
21
+ }
22
+ /** A handle as an attribute may give it (`@jana_n`, `Jana_N`): lower case, without the `@`; null for none. */
23
+ export declare function handleOf(value: unknown): string | null;
24
+ export declare function createProfile(o: {
25
+ client: CommunityClient;
26
+ }): ProfileController;
27
+ export interface WelcomeUi {
28
+ phase: 'idle' | 'loading' | 'ready';
29
+ rules: CommunityRule[];
30
+ /** The rules could not be read: the page still links them. */
31
+ rulesFailed: boolean;
32
+ /** The signed-in member's handle (the profile link), or null for a visitor and a reader who never joined. */
33
+ handle: string | null;
34
+ /** Nobody is signed in: the sign-in link takes the profile link's place. */
35
+ visitor: boolean;
36
+ }
37
+ export interface WelcomeController {
38
+ readonly state: WelcomeUi;
39
+ load(): Promise<void>;
40
+ dispose(): void;
41
+ }
42
+ export declare function createWelcome(o: {
43
+ client: CommunityClient;
44
+ lang: string;
45
+ }): WelcomeController;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The browser's half of web push (m2-contracts.md §3.4): the one module of the elements that touches the service
3
+ * worker and the PushManager (test/closed-paths.spec.ts lets `serviceWorker` appear here and nowhere else).
4
+ *
5
+ * The worker is registered only from `<blogmcp-member-settings>`, on the member's click („Zapnout upozornění"),
6
+ * never on page load; the worker's script is the host's route (`sw-path`, default `<site-base>/sw.js`, served from
7
+ * `@blogmcp/sdk/community/sw`). The subscription's keys go to kb-api through the SDK client only.
8
+ *
9
+ * Feature checks read the browser's globals on call (never on import), so a server render and a test can swap them.
10
+ */
11
+ import { type PushUserAgentClass } from '@blogmcp/sdk/community';
12
+ /** What the browser can do about push, in the order the settings element asks. */
13
+ export type PushSupport =
14
+ /** Facebook's or Instagram's in-app browser: „Otevřít v prohlížeči" with the page's URL instead. */
15
+ 'inApp'
16
+ /** iOS outside an installed web app (`navigator.standalone !== true`): the install guide instead. */
17
+ | 'iosInstall'
18
+ /** No service worker, PushManager or Notification: one sentence. */
19
+ | 'unsupported' | 'supported';
20
+ /** The navigator as far as push reads it (Safari's `standalone` included). */
21
+ interface PushNavigator {
22
+ readonly userAgent?: string;
23
+ readonly platform?: string;
24
+ readonly maxTouchPoints?: number;
25
+ readonly standalone?: boolean;
26
+ readonly serviceWorker?: ServiceWorkerContainer;
27
+ }
28
+ /**
29
+ * iOS (an iPhone, an iPod, an iPad — which reports itself as a Mac with a touch screen): its browsers take web push
30
+ * only from a website installed to the home screen. A layout hint, never sent anywhere but as `userAgentClass`.
31
+ */
32
+ export declare function isIos(n?: PushNavigator | null): boolean;
33
+ /** What `me/push/subscribe` is told this browser is (a class, never the user agent itself). */
34
+ export declare function userAgentClass(n?: PushNavigator | null): PushUserAgentClass;
35
+ /** What this browser can do about push (the in-app browser first, then iOS outside the home screen). */
36
+ export declare function pushSupport(): PushSupport;
37
+ /** The browser's notification permission (`default` where there is no Notification API). */
38
+ export declare function notificationPermission(): NotificationPermission;
39
+ /** Ask for the permission (only on the member's click); `denied` where it cannot be asked. */
40
+ export declare function requestNotificationPermission(): Promise<NotificationPermission>;
41
+ /** The worker's registration for this page (null where none is registered or it cannot be asked). */
42
+ export declare function currentRegistration(): Promise<ServiceWorkerRegistration | null>;
43
+ /** Register the host's worker script (the member's click). Throws what the browser throws. */
44
+ export declare function registerWorker(swPath: string): Promise<ServiceWorkerRegistration>;
45
+ /** The browser's current subscription of a registration, or null. */
46
+ export declare function currentSubscription(reg: ServiceWorkerRegistration | null): Promise<PushSubscription | null>;
47
+ /** base64url (the VAPID public key `me/push/vapid` sends) → the bytes `applicationServerKey` takes; null when malformed. */
48
+ export declare function base64UrlBytes(value: string): Uint8Array | null;
49
+ /** A subscription as `me/push/subscribe` takes it (`toJSON()`'s endpoint and keys), or null when one is missing. */
50
+ export declare function subscriptionInput(sub: PushSubscription): {
51
+ endpoint: string;
52
+ keys: {
53
+ p256dh: string;
54
+ auth: string;
55
+ };
56
+ } | null;
57
+ export interface StoredPush {
58
+ readonly memberId: string;
59
+ readonly keyId: string;
60
+ }
61
+ export declare function storedPush(): StoredPush | null;
62
+ export declare function storePush(value: StoredPush | null): void;
63
+ export {};
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The discussion's live updates (m2-contracts.md §3.5, §3.10): `<blogmcp-discussion>` (and M3 `<blogmcp-topic>`) takes a
3
+ * `realtime` PROPERTY —
4
+ * the session factory of `@blogmcp/sdk/community/realtime`, which the HOST imports and passes:
5
+ *
6
+ * import { createRealtimeSession } from '@blogmcp/sdk/community/realtime'
7
+ * mountDiscussion({ articleId, realtime: createRealtimeSession }) // or el.realtime = createRealtimeSession
8
+ *
9
+ * The element calls it with its own client and discussion store after the first `discussions/list`, and `stop()`s
10
+ * the session when it unmounts. The element never imports the realtime entry (nor `centrifuge`): a site that passes no
11
+ * factory ships no socket code, and the discussion polls (`pollDiscussionHead`) as before.
12
+ *
13
+ * The types here are the factory's shape as far as the element uses it, so `createRealtimeSession` fits as it is.
14
+ */
15
+ import type { CommunityClient, DiscussionStore, TopicStore } from '@blogmcp/sdk/community';
16
+ /** `connecting`, `live`, `polling` (live updates are off or the server cut the socket for good), `stopped`. */
17
+ export type DiscussionRealtimeMode = 'connecting' | 'live' | 'polling' | 'stopped' | (string & {});
18
+ /** What the element calls the factory with. */
19
+ export interface DiscussionRealtimeOptions {
20
+ client: CommunityClient;
21
+ store: DiscussionStore;
22
+ /** The discussion's language, for the session's re-reads. */
23
+ languageCode: string;
24
+ /** Whether the discussion is seen now (the fallback polls only then). */
25
+ visible: () => boolean;
26
+ /** The element's poll interval, for the session's fallback poller (absent: the SDK's default). */
27
+ pollIntervalMs?: number;
28
+ onMode: (mode: DiscussionRealtimeMode) => void;
29
+ }
30
+ /** A running session, as far as the element uses it. */
31
+ export interface DiscussionRealtimeSession {
32
+ readonly mode: DiscussionRealtimeMode;
33
+ stop(): void;
34
+ }
35
+ /** `createRealtimeSession` of `@blogmcp/sdk/community/realtime`, or any function of its shape. */
36
+ export type DiscussionRealtime = (options: DiscussionRealtimeOptions) => DiscussionRealtimeSession;
37
+ /**
38
+ * M3 (m3-contracts.md §3.5, §5.3): what `<blogmcp-topic>` calls the same factory with — the topic's store
39
+ * (`createTopicStore`) in place of a discussion's; `createRealtimeSession` takes both, re-reads a topic with
40
+ * `forum/topics/get` and falls back to `forum/topics/head`.
41
+ */
42
+ export interface TopicRealtimeOptions extends Omit<DiscussionRealtimeOptions, 'store'> {
43
+ store: TopicStore;
44
+ }
45
+ /** `createRealtimeSession`, as `<blogmcp-topic>`'s `realtime` property takes it. */
46
+ export type TopicRealtime = (options: TopicRealtimeOptions) => DiscussionRealtimeSession;
@@ -0,0 +1,53 @@
1
+ import { type CommunityClient, type CommunityFailure, type MemberSettings, type ModuleInfo } from '@blogmcp/sdk/community';
2
+ import type { TextRef } from './i18n';
3
+ import { type PushSupport } from './push';
4
+ /** The six switches, in the order the page shows them. */
5
+ export declare const SETTING_KEYS: readonly ["notifyReply", "notifyMention", "notifyTopic", "pushEnabled", "digestEmail", "quietHours"];
6
+ export type SettingKey = (typeof SETTING_KEYS)[number];
7
+ export type SettingsPhase = 'idle' | 'loading' | 'ready' | 'failed' | 'signIn';
8
+ /** This browser's push: off, on, or on its way (`busy`), and what the page tells the member about it. */
9
+ export interface PushUi {
10
+ support: PushSupport;
11
+ /** `checking` until the browser's own subscription was looked at. */
12
+ status: 'checking' | 'off' | 'on' | 'busy';
13
+ /** `denied` the browser's permission; `refused` kb-api's (pushRefused); `reset` switch it on again. */
14
+ note: 'denied' | 'refused' | 'reset' | null;
15
+ failure: CommunityFailure | null;
16
+ }
17
+ export interface DigestUi {
18
+ /** An unsubscribe link brought the member here: the button is offered. */
19
+ offered: boolean;
20
+ sending: boolean;
21
+ outcome: 'unsubscribed' | 'staleLink' | null;
22
+ failure: CommunityFailure | null;
23
+ }
24
+ export interface SettingsUi {
25
+ phase: SettingsPhase;
26
+ failure: CommunityFailure | null;
27
+ module: ModuleInfo | null;
28
+ settings: MemberSettings | null;
29
+ saving: Partial<Record<SettingKey, boolean>>;
30
+ push: PushUi;
31
+ digest: DigestUi;
32
+ announcement: TextRef | null;
33
+ announceId: number;
34
+ }
35
+ export interface MemberSettingsController {
36
+ readonly state: SettingsUi;
37
+ load(): Promise<void>;
38
+ toggle(key: SettingKey, value: boolean): Promise<void>;
39
+ /** Whether this browser's push block is shown (the module's feature; never for a visitor). */
40
+ pushShown(): boolean;
41
+ enablePush(): Promise<void>;
42
+ disablePush(): Promise<void>;
43
+ unsubscribeDigest(): Promise<void>;
44
+ dispose(): void;
45
+ }
46
+ export interface MemberSettingsOptions {
47
+ client: CommunityClient;
48
+ /** The host's worker script (`<site-base>/sw.js`). */
49
+ swPath: string;
50
+ /** The digest's unsubscribe token this page's address carried (read and cleared by the element), or null. */
51
+ digestToken: string | null;
52
+ }
53
+ export declare function createMemberSettings(o: MemberSettingsOptions): MemberSettingsController;
@@ -5,11 +5,24 @@ export type TextSegment = {
5
5
  readonly kind: 'link';
6
6
  readonly text: string;
7
7
  readonly href: string;
8
+ }
9
+ /** M2: an `@handle` with the link to the member's profile page on the host's own site (never a member's URL). */
10
+ | {
11
+ readonly kind: 'mention';
12
+ readonly text: string;
13
+ readonly href: string;
8
14
  };
9
15
  /** The rel every link to what a member wrote carries (user-generated, no referrer, no opener). */
10
16
  export declare const UGC_LINK_REL = "nofollow ugc noopener noreferrer";
11
17
  /** A member's text as text runs and links. */
12
18
  export declare function linkify(text: string): TextSegment[];
19
+ /**
20
+ * A member's text as text runs, web-address links and `@handle` mentions (m2-contracts.md §3.3: „the element renders
21
+ * `@handle` as text with a link to `/u/{handle}`"). The mentions are the SDK's (kb-api's token rule), found only in the
22
+ * text between addresses — an address's `/@x` stays part of its link; `profileHref` builds the host's profile page
23
+ * (profilePath) and a handle it gives no page for stays text. Joining every segment's `text` gives the body back.
24
+ */
25
+ export declare function linkifyWithMentions(text: string, profileHref: ((handle: string) => string | null) | null): TextSegment[];
13
26
  /** Characters in code points, as kb-api counts a body (after NFC and trimming). */
14
27
  export declare function codePointLength(text: string): number;
15
28
  /** A body as kb-api will measure it: NFC, trimmed. */
@@ -0,0 +1,47 @@
1
+ import { type CaseCategory, type CommunityFailure, type CommunityRule, type DiscussionMessage, type ForumCategory } from '@blogmcp/sdk/community';
2
+ import { type TopicThreadController, type TopicThreadControllerOptions } from './discussion';
3
+ import { type FollowController } from './follow';
4
+ import type { TextRef } from './i18n';
5
+ export type ModeratorDialogKind = 'remove' | 'escalate' | 'move';
6
+ /** A moderator's open dialog: a message's removal or escalation, or the topic's move. */
7
+ export interface ModeratorDialog {
8
+ kind: ModeratorDialogKind;
9
+ /** The message (remove, escalate). */
10
+ seq: number | null;
11
+ ruleId: string;
12
+ facts: string;
13
+ category: CaseCategory | '';
14
+ categoryId: string;
15
+ sending: boolean;
16
+ failure: CommunityFailure | null;
17
+ /** A removal's statement of reasons, as kb-api rendered it. */
18
+ statement: string | null;
19
+ done: boolean;
20
+ }
21
+ export interface TopicExtrasUi {
22
+ /** The rules of the tenant's current rules text (a moderator's removal cites one), once read. */
23
+ rules: CommunityRule[] | null;
24
+ /** The categories a moderator may move the topic into (theirs, but this one), once read. */
25
+ moveTargets: ForumCategory[] | null;
26
+ dialog: ModeratorDialog | null;
27
+ busy: Record<string, boolean>;
28
+ announcement: TextRef | null;
29
+ announceId: number;
30
+ }
31
+ export interface TopicController {
32
+ readonly thread: TopicThreadController;
33
+ readonly follow: FollowController | null;
34
+ readonly extras: TopicExtrasUi;
35
+ start(): Promise<void>;
36
+ /** The viewer moderates the topic's category (kb-api's `viewer.moderator`, me/get's appointments beside it). */
37
+ isModerator(): boolean;
38
+ /** A moderator's message actions apply (another member's live message). */
39
+ canModerate(m: DiscussionMessage): boolean;
40
+ setState(state: 'open' | 'locked'): Promise<void>;
41
+ setPinned(pinned: boolean): Promise<void>;
42
+ openDialog(kind: ModeratorDialogKind, seq?: number | null): void;
43
+ closeDialog(): void;
44
+ submitDialog(): Promise<void>;
45
+ dispose(): void;
46
+ }
47
+ export declare function createTopicController(o: TopicThreadControllerOptions): TopicController;
package/dist/element.d.ts CHANGED
@@ -1,6 +1,19 @@
1
1
  export declare const DISCUSSION_ELEMENT_TAG = "blogmcp-discussion";
2
2
  export declare const NOTICE_FORM_ELEMENT_TAG = "blogmcp-notice-form";
3
3
  export declare const MY_COMMUNITY_ELEMENT_TAG = "blogmcp-my-community";
4
+ /** M2 (m2-contracts.md §3.10). */
5
+ export declare const NOTIFICATION_CENTRE_ELEMENT_TAG = "blogmcp-notification-centre";
6
+ export declare const BADGE_ELEMENT_TAG = "blogmcp-badge";
7
+ export declare const MEMBER_SETTINGS_ELEMENT_TAG = "blogmcp-member-settings";
8
+ export declare const PROFILE_ELEMENT_TAG = "blogmcp-profile";
9
+ export declare const WELCOME_ELEMENT_TAG = "blogmcp-welcome";
10
+ /** M3 (m3-contracts.md §5.3): the forum's elements — they live here, not in the SDK (A33). */
11
+ export declare const FORUM_ELEMENT_TAG = "blogmcp-forum";
12
+ export declare const FORUM_CATEGORY_ELEMENT_TAG = "blogmcp-forum-category";
13
+ export declare const TOPIC_ELEMENT_TAG = "blogmcp-topic";
14
+ export declare const FORUM_SEARCH_ELEMENT_TAG = "blogmcp-forum-search";
15
+ export declare const FOLLOW_BUTTON_ELEMENT_TAG = "blogmcp-follow-button";
16
+ export declare const FORUM_MODERATION_ELEMENT_TAG = "blogmcp-forum-moderation";
4
17
  export interface DefineCommunityElementOptions {
5
18
  /** Another tag name for the element. */
6
19
  tag?: string;
@@ -14,6 +27,17 @@ export interface DefineCommunityElementsOptions {
14
27
  discussion?: string;
15
28
  noticeForm?: string;
16
29
  myCommunity?: string;
30
+ notificationCentre?: string;
31
+ badge?: string;
32
+ memberSettings?: string;
33
+ profile?: string;
34
+ welcome?: string;
35
+ forum?: string;
36
+ forumCategory?: string;
37
+ topic?: string;
38
+ forumSearch?: string;
39
+ followButton?: string;
40
+ forumModeration?: string;
17
41
  };
18
42
  }
19
43
  /** Register `<blogmcp-discussion>` (once; later calls are no-ops). Browser only. */
@@ -22,5 +46,27 @@ export declare function defineDiscussionElement(opts?: DefineCommunityElementOpt
22
46
  export declare function defineNoticeFormElement(opts?: DefineCommunityElementOptions): void;
23
47
  /** Register `<blogmcp-my-community>` (once). Browser only. */
24
48
  export declare function defineMyCommunityElement(opts?: DefineCommunityElementOptions): void;
25
- /** Register all three (once). Browser only. */
49
+ /** Register `<blogmcp-notification-centre>` (once). Browser only. */
50
+ export declare function defineNotificationCentreElement(opts?: DefineCommunityElementOptions): void;
51
+ /** Register `<blogmcp-badge>` (once). Browser only. */
52
+ export declare function defineBadgeElement(opts?: DefineCommunityElementOptions): void;
53
+ /** Register `<blogmcp-member-settings>` (once). Browser only. */
54
+ export declare function defineMemberSettingsElement(opts?: DefineCommunityElementOptions): void;
55
+ /** Register `<blogmcp-profile>` (once). Browser only. */
56
+ export declare function defineProfileElement(opts?: DefineCommunityElementOptions): void;
57
+ /** Register `<blogmcp-welcome>` (once). Browser only. */
58
+ export declare function defineWelcomeElement(opts?: DefineCommunityElementOptions): void;
59
+ /** M3: register `<blogmcp-forum>` (once). Browser only. */
60
+ export declare function defineForumElement(opts?: DefineCommunityElementOptions): void;
61
+ /** M3: register `<blogmcp-forum-category>` (once). Browser only. */
62
+ export declare function defineForumCategoryElement(opts?: DefineCommunityElementOptions): void;
63
+ /** M3: register `<blogmcp-topic>` (once). Browser only. */
64
+ export declare function defineTopicElement(opts?: DefineCommunityElementOptions): void;
65
+ /** M3: register `<blogmcp-forum-search>` (once). Browser only. */
66
+ export declare function defineForumSearchElement(opts?: DefineCommunityElementOptions): void;
67
+ /** M3: register `<blogmcp-follow-button>` (once). Browser only. */
68
+ export declare function defineFollowButtonElement(opts?: DefineCommunityElementOptions): void;
69
+ /** M3: register `<blogmcp-forum-moderation>` (once). Browser only. */
70
+ export declare function defineForumModerationElement(opts?: DefineCommunityElementOptions): void;
71
+ /** Register all fourteen (once). Browser only. */
26
72
  export declare function defineCommunityElements(opts?: DefineCommunityElementsOptions): void;
package/dist/global.d.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  /**
2
- * The `<script>` build: registers `<blogmcp-discussion>`, `<blogmcp-notice-form>` and `<blogmcp-my-community>`
3
- * with the script tag's own CSP nonce, and exposes `window.BlogmcpCommunity`.
2
+ * The `<script>` build: registers every element — `<blogmcp-discussion>`, `<blogmcp-notice-form>`,
3
+ * `<blogmcp-my-community>`, (M2) `<blogmcp-notification-centre>`, `<blogmcp-badge>`, `<blogmcp-member-settings>`,
4
+ * `<blogmcp-profile>`, `<blogmcp-welcome>` and (M3) `<blogmcp-forum>`, `<blogmcp-forum-category>`, `<blogmcp-topic>`,
5
+ * `<blogmcp-forum-search>`, `<blogmcp-follow-button>`, `<blogmcp-forum-moderation>` — with the script tag's own CSP
6
+ * nonce, and exposes `window.BlogmcpCommunity`. Live updates are not in it: a site that wants them bundles
7
+ * `@blogmcp/sdk/community/realtime` and sets the discussion's (or the topic's) `realtime` property.
4
8
  */
5
9
  import { defineCommunityElements } from './element';
6
- import { mountDiscussion, mountMyCommunity, mountNoticeForm } from './mount';
10
+ import { mountBadge, mountDiscussion, mountFollowButton, mountForum, mountForumCategory, mountForumModeration, mountForumSearch, mountMemberSettings, mountMyCommunity, mountNoticeForm, mountNotificationCentre, mountProfile, mountTopic, mountWelcome } from './mount';
7
11
  import { VERSION } from './version';
8
- export { defineCommunityElements, mountDiscussion, mountMyCommunity, mountNoticeForm, VERSION };
12
+ export { defineCommunityElements, mountDiscussion, mountMyCommunity, mountNoticeForm, mountNotificationCentre, mountBadge, mountMemberSettings, mountProfile, mountWelcome, mountForum, mountForumCategory, mountTopic, mountForumSearch, mountFollowButton, mountForumModeration, VERSION, };
package/dist/index.d.ts CHANGED
@@ -7,14 +7,31 @@
7
7
  * - `<blogmcp-discussion>` the discussion under an article;
8
8
  * - `<blogmcp-notice-form>` the DSA notice form, for anyone;
9
9
  * - `<blogmcp-my-community>` the reader's „Moje komunita", a statement page, a notifier's status page;
10
+ * - M2: `<blogmcp-notification-centre>` the notifications, `<blogmcp-badge>` the header's unread count,
11
+ * `<blogmcp-member-settings>` the switches, push and the digest's unsubscribe, `<blogmcp-profile>` a member's
12
+ * public profile, `<blogmcp-welcome>` the welcome page; the discussion's `realtime` property (live updates);
13
+ * - M3, the forum: `<blogmcp-forum>` its index, `<blogmcp-forum-category slug>` a category, `<blogmcp-topic
14
+ * conversation-id>` a topic (the discussion's view, with its `realtime` property), `<blogmcp-forum-search>`,
15
+ * `<blogmcp-follow-button target-kind target-id>`, `<blogmcp-forum-moderation>` a category moderator's queue;
10
16
  * - `defineCommunityElements()` and one `define…Element()` each — the custom elements;
11
17
  * - `mountDiscussion(…)`, `mountNoticeForm(…)`, `mountMyCommunity(…)` — mount one from code;
12
18
  * - `@blogmcp/community/vue`, `@blogmcp/community/react` — `<KbDiscussion>`, `<KbNoticeForm>`, `<KbMyCommunity>`;
13
19
  * - `@blogmcp/community/global` — one `<script>` for sites without a bundler.
14
20
  */
15
- export { defineCommunityElements, defineDiscussionElement, defineNoticeFormElement, defineMyCommunityElement, DISCUSSION_ELEMENT_TAG, NOTICE_FORM_ELEMENT_TAG, MY_COMMUNITY_ELEMENT_TAG, type DefineCommunityElementOptions, type DefineCommunityElementsOptions, } from './element';
16
- export { mountDiscussion, mountNoticeForm, mountMyCommunity, type MountDiscussionOptions, type MountNoticeFormOptions, type MountMyCommunityOptions } from './mount';
17
- export { createDiscussionController, type DiscussionController, type DiscussionControllerOptions, type DiscussionUi, type ComposerMode, type Draft } from './core/discussion';
21
+ export { defineCommunityElements, defineDiscussionElement, defineNoticeFormElement, defineMyCommunityElement, defineNotificationCentreElement, defineBadgeElement, defineMemberSettingsElement, defineProfileElement, defineWelcomeElement, defineForumElement, defineForumCategoryElement, defineTopicElement, defineForumSearchElement, defineFollowButtonElement, defineForumModerationElement, DISCUSSION_ELEMENT_TAG, NOTICE_FORM_ELEMENT_TAG, MY_COMMUNITY_ELEMENT_TAG, NOTIFICATION_CENTRE_ELEMENT_TAG, BADGE_ELEMENT_TAG, MEMBER_SETTINGS_ELEMENT_TAG, PROFILE_ELEMENT_TAG, WELCOME_ELEMENT_TAG, FORUM_ELEMENT_TAG, FORUM_CATEGORY_ELEMENT_TAG, TOPIC_ELEMENT_TAG, FORUM_SEARCH_ELEMENT_TAG, FOLLOW_BUTTON_ELEMENT_TAG, FORUM_MODERATION_ELEMENT_TAG, type DefineCommunityElementOptions, type DefineCommunityElementsOptions, } from './element';
22
+ export { mountDiscussion, mountNoticeForm, mountMyCommunity, mountNotificationCentre, mountBadge, mountMemberSettings, mountProfile, mountWelcome, mountForum, mountForumCategory, mountTopic, mountForumSearch, mountFollowButton, mountForumModeration, type MountDiscussionOptions, type MountNoticeFormOptions, type MountMyCommunityOptions, type MountNotificationCentreOptions, type MountBadgeOptions, type MountMemberSettingsOptions, type MountProfileOptions, type MountWelcomeOptions, type MountForumOptions, type MountForumCategoryOptions, type MountTopicOptions, type MountForumSearchOptions, type MountFollowButtonOptions, type MountForumModerationOptions, } from './mount';
23
+ export { createNotificationCentre, type NotificationCentreController, type CentreUi } from './core/centre';
24
+ export { createBadge, badgeText, BADGE_INTERVAL_MS, type BadgeController, type BadgeOptions, type BadgeUi } from './core/badge';
25
+ export { createMemberSettings, SETTING_KEYS, type MemberSettingsController, type MemberSettingsOptions, type SettingsUi, type SettingKey } from './core/settings';
26
+ export { createProfile, createWelcome, type ProfileController, type WelcomeController } from './core/profile';
27
+ export type { DiscussionRealtime, DiscussionRealtimeOptions, DiscussionRealtimeSession, DiscussionRealtimeMode, TopicRealtime, TopicRealtimeOptions } from './core/realtime';
28
+ export { createDiscussionController, createTopicThreadController, type DiscussionController, type DiscussionControllerOptions, type DiscussionUi, type ComposerMode, type Draft, type ThreadController, type ThreadState, type ThreadUi, type TopicThreadController, type TopicThreadControllerOptions, } from './core/discussion';
29
+ export { createForum, type ForumController, type ForumUi } from './core/forum';
30
+ export { createCategory, type CategoryController, type CategoryUi, type TopicDraft } from './core/forum-category';
31
+ export { createTopicController, type TopicController, type ModeratorDialog } from './core/topic';
32
+ export { createSearch, queryFromHash, searchLink, type SearchController, type SearchUi } from './core/forum-search';
33
+ export { createFollow, type FollowController, type FollowUi } from './core/follow';
34
+ export { createModeration, type ModerationController, type ModerationUi } from './core/moderation';
18
35
  export { createNoticeForm, type NoticeFormController, type NoticeFormOptions, type NoticeFormUi } from './core/notice';
19
36
  export { createPanel, type PanelController, type PanelOptions, type PanelUi } from './core/panel';
20
37
  export { createOnboarding, type OnboardingModel, type OnboardingOptions, type OnboardingStep } from './core/onboarding';
package/dist/index.js CHANGED
@@ -1,22 +1,72 @@
1
- import { A, k, D, M, N, g, h, j, i, d, a, c, b, m, f, e, p } from "./chunks/mount-DzjwnHyx.js";
2
- const VERSION = "0.1.0";
1
+ import { ab, B, S, ac, D, t, r, F, u, s, q, M, N, p, P, V, T, W, R, Q, a0, Z, a5, $, U, a6, a7, O, a9, a8, X, a2, a1, _, Y, f, d, a, n, k, j, o, m, g, c, b, e, h, l, i, z, v, K, G, H, L, J, A, x, w, y, C, I, E, aa, a3, a4 } from "./chunks/mount--2_V2q3M.js";
2
+ const VERSION = "0.3.0";
3
3
  export {
4
- A as AVATAR_PRESETS,
5
- k as DEFAULT_API_BASE,
4
+ ab as AVATAR_PRESETS,
5
+ B as BADGE_ELEMENT_TAG,
6
+ S as BADGE_INTERVAL_MS,
7
+ ac as DEFAULT_API_BASE,
6
8
  D as DISCUSSION_ELEMENT_TAG,
9
+ t as FOLLOW_BUTTON_ELEMENT_TAG,
10
+ r as FORUM_CATEGORY_ELEMENT_TAG,
11
+ F as FORUM_ELEMENT_TAG,
12
+ u as FORUM_MODERATION_ELEMENT_TAG,
13
+ s as FORUM_SEARCH_ELEMENT_TAG,
14
+ q as MEMBER_SETTINGS_ELEMENT_TAG,
7
15
  M as MY_COMMUNITY_ELEMENT_TAG,
8
16
  N as NOTICE_FORM_ELEMENT_TAG,
17
+ p as NOTIFICATION_CENTRE_ELEMENT_TAG,
18
+ P as PROFILE_ELEMENT_TAG,
19
+ V as SETTING_KEYS,
20
+ T as TOPIC_ELEMENT_TAG,
9
21
  VERSION,
10
- g as createDiscussionController,
11
- h as createNoticeForm,
12
- j as createOnboarding,
13
- i as createPanel,
22
+ W as WELCOME_ELEMENT_TAG,
23
+ R as badgeText,
24
+ Q as createBadge,
25
+ a0 as createCategory,
26
+ Z as createDiscussionController,
27
+ a5 as createFollow,
28
+ $ as createForum,
29
+ U as createMemberSettings,
30
+ a6 as createModeration,
31
+ a7 as createNoticeForm,
32
+ O as createNotificationCentre,
33
+ a9 as createOnboarding,
34
+ a8 as createPanel,
35
+ X as createProfile,
36
+ a2 as createSearch,
37
+ a1 as createTopicController,
38
+ _ as createTopicThreadController,
39
+ Y as createWelcome,
40
+ f as defineBadgeElement,
14
41
  d as defineCommunityElements,
15
42
  a as defineDiscussionElement,
43
+ n as defineFollowButtonElement,
44
+ k as defineForumCategoryElement,
45
+ j as defineForumElement,
46
+ o as defineForumModerationElement,
47
+ m as defineForumSearchElement,
48
+ g as defineMemberSettingsElement,
16
49
  c as defineMyCommunityElement,
17
50
  b as defineNoticeFormElement,
18
- m as mountDiscussion,
19
- f as mountMyCommunity,
20
- e as mountNoticeForm,
21
- p as pickLang
51
+ e as defineNotificationCentreElement,
52
+ h as defineProfileElement,
53
+ l as defineTopicElement,
54
+ i as defineWelcomeElement,
55
+ z as mountBadge,
56
+ v as mountDiscussion,
57
+ K as mountFollowButton,
58
+ G as mountForum,
59
+ H as mountForumCategory,
60
+ L as mountForumModeration,
61
+ J as mountForumSearch,
62
+ A as mountMemberSettings,
63
+ x as mountMyCommunity,
64
+ w as mountNoticeForm,
65
+ y as mountNotificationCentre,
66
+ C as mountProfile,
67
+ I as mountTopic,
68
+ E as mountWelcome,
69
+ aa as pickLang,
70
+ a3 as queryFromHash,
71
+ a4 as searchLink
22
72
  };