@blogmcp/community 0.0.0-stage → 0.2.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 (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +243 -2
  3. package/dist/blogmcp-community.global.js +10 -0
  4. package/dist/chunks/mount-WadUnaTa.js +6436 -0
  5. package/dist/core/attrs.d.ts +99 -0
  6. package/dist/core/avatars.d.ts +16 -0
  7. package/dist/core/badge.d.ts +30 -0
  8. package/dist/core/centre.d.ts +28 -0
  9. package/dist/core/client.d.ts +27 -0
  10. package/dist/core/digest-token.d.ts +2 -0
  11. package/dist/core/discussion.d.ts +175 -0
  12. package/dist/core/download.d.ts +4 -0
  13. package/dist/core/format.d.ts +24 -0
  14. package/dist/core/i18n.d.ts +28 -0
  15. package/dist/core/names.d.ts +15 -0
  16. package/dist/core/notice.d.ts +50 -0
  17. package/dist/core/onboarding.d.ts +92 -0
  18. package/dist/core/outcomes.d.ts +29 -0
  19. package/dist/core/panel.d.ts +96 -0
  20. package/dist/core/profile.d.ts +40 -0
  21. package/dist/core/push.d.ts +63 -0
  22. package/dist/core/realtime.d.ts +35 -0
  23. package/dist/core/receipt.d.ts +5 -0
  24. package/dist/core/settings.d.ts +53 -0
  25. package/dist/core/text.d.ts +35 -0
  26. package/dist/core/turnstile.d.ts +33 -0
  27. package/dist/element.d.ts +47 -0
  28. package/dist/global.d.ts +11 -0
  29. package/dist/index.d.ts +33 -0
  30. package/dist/index.js +45 -0
  31. package/dist/mount.d.ts +105 -0
  32. package/dist/react.d.ts +28 -0
  33. package/dist/react.js +55 -0
  34. package/dist/ui/AuthorAvatar.vue.d.ts +11 -0
  35. package/dist/ui/BadgeView.vue.d.ts +13 -0
  36. package/dist/ui/ConfirmDialog.vue.d.ts +20 -0
  37. package/dist/ui/DiscussionView.vue.d.ts +44 -0
  38. package/dist/ui/LinkedText.vue.d.ts +6 -0
  39. package/dist/ui/MemberSettingsView.vue.d.ts +13 -0
  40. package/dist/ui/MessageComposer.vue.d.ts +26 -0
  41. package/dist/ui/MessageItem.vue.d.ts +6 -0
  42. package/dist/ui/MessageMenu.vue.d.ts +10 -0
  43. package/dist/ui/MyCommunityView.vue.d.ts +16 -0
  44. package/dist/ui/NoticeFormView.vue.d.ts +26 -0
  45. package/dist/ui/NoticeStatusView.vue.d.ts +15 -0
  46. package/dist/ui/NotificationCentreView.vue.d.ts +12 -0
  47. package/dist/ui/OnboardingSteps.vue.d.ts +11 -0
  48. package/dist/ui/ProfileView.vue.d.ts +13 -0
  49. package/dist/ui/ReactionBar.vue.d.ts +13 -0
  50. package/dist/ui/ReportDialog.vue.d.ts +15 -0
  51. package/dist/ui/StatementView.vue.d.ts +15 -0
  52. package/dist/ui/WelcomeView.vue.d.ts +12 -0
  53. package/dist/ui/context.d.ts +21 -0
  54. package/dist/ui/discussion-context.d.ts +19 -0
  55. package/dist/ui/types.d.ts +7 -0
  56. package/dist/version.d.ts +1 -0
  57. package/dist/vue.d.ts +200 -0
  58. package/dist/vue.js +201 -0
  59. package/package.json +80 -4
  60. package/src/core/attrs.ts +186 -0
  61. package/src/core/avatars.ts +44 -0
  62. package/src/core/badge.ts +120 -0
  63. package/src/core/centre.ts +136 -0
  64. package/src/core/client.ts +54 -0
  65. package/src/core/digest-token.ts +26 -0
  66. package/src/core/discussion.ts +876 -0
  67. package/src/core/download.ts +21 -0
  68. package/src/core/format.ts +112 -0
  69. package/src/core/i18n.ts +49 -0
  70. package/src/core/names.ts +51 -0
  71. package/src/core/notice.ts +178 -0
  72. package/src/core/onboarding.ts +492 -0
  73. package/src/core/outcomes.ts +86 -0
  74. package/src/core/panel.ts +446 -0
  75. package/src/core/profile.ts +144 -0
  76. package/src/core/push.ts +174 -0
  77. package/src/core/realtime.ts +39 -0
  78. package/src/core/receipt.ts +28 -0
  79. package/src/core/settings.ts +363 -0
  80. package/src/core/text.ts +174 -0
  81. package/src/core/turnstile.ts +98 -0
  82. package/src/element.ts +165 -0
  83. package/src/global.ts +26 -0
  84. package/src/index.ts +70 -0
  85. package/src/mount.ts +234 -0
  86. package/src/react.ts +97 -0
  87. package/src/shims.d.ts +11 -0
  88. package/src/ui/AuthorAvatar.vue +26 -0
  89. package/src/ui/BadgeView.vue +51 -0
  90. package/src/ui/ConfirmDialog.vue +38 -0
  91. package/src/ui/DiscussionView.vue +334 -0
  92. package/src/ui/LinkedText.vue +24 -0
  93. package/src/ui/MemberSettingsView.vue +175 -0
  94. package/src/ui/MessageComposer.vue +141 -0
  95. package/src/ui/MessageItem.vue +223 -0
  96. package/src/ui/MessageMenu.vue +67 -0
  97. package/src/ui/MyCommunityView.vue +378 -0
  98. package/src/ui/NoticeFormView.vue +244 -0
  99. package/src/ui/NoticeStatusView.vue +82 -0
  100. package/src/ui/NotificationCentreView.vue +159 -0
  101. package/src/ui/OnboardingSteps.vue +174 -0
  102. package/src/ui/ProfileView.vue +91 -0
  103. package/src/ui/ReactionBar.vue +41 -0
  104. package/src/ui/ReportDialog.vue +88 -0
  105. package/src/ui/StatementView.vue +141 -0
  106. package/src/ui/WelcomeView.vue +72 -0
  107. package/src/ui/context.ts +75 -0
  108. package/src/ui/discussion-context.ts +30 -0
  109. package/src/ui/styles.css +259 -0
  110. package/src/ui/types.ts +7 -0
  111. package/src/version.ts +3 -0
  112. package/src/vue.ts +242 -0
@@ -0,0 +1,96 @@
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';
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';
12
+ import { type TextRef } from './i18n';
13
+ import { type OnboardingModel } from './onboarding';
14
+ export type LoadPhase = 'idle' | 'loading' | 'ready' | 'failed';
15
+ export interface AppealDraft {
16
+ body: string;
17
+ sending: boolean;
18
+ failure: CommunityFailure | null;
19
+ }
20
+ export interface ProfileDraft {
21
+ displayName: string;
22
+ handle: string;
23
+ avatarPreset: string | null;
24
+ sending: boolean;
25
+ failure: CommunityFailure | null;
26
+ fields: string[];
27
+ }
28
+ export interface PanelUi {
29
+ /** The member's part: `signIn` when the session cannot act as a member (or there is none). */
30
+ phase: LoadPhase | 'signIn';
31
+ failure: CommunityFailure | null;
32
+ me: MyState | null;
33
+ notifications: NotificationItem[];
34
+ decisions: DecisionSummary[];
35
+ appeals: AppealItem[];
36
+ reports: ReportItem[];
37
+ blocks: BlockEntry[];
38
+ /** The statement the page opens (`decision-id`). */
39
+ statement: {
40
+ phase: LoadPhase | 'signIn';
41
+ value: Statement | null;
42
+ failure: CommunityFailure | null;
43
+ appeal: AppealDraft;
44
+ };
45
+ /** The notifier's part (`#t=` in the address). `missing`: a `#t=` that holds no receipt. */
46
+ notice: {
47
+ phase: LoadPhase | 'missing';
48
+ value: NoticeStatus | null;
49
+ failure: CommunityFailure | null;
50
+ appeal: AppealDraft;
51
+ };
52
+ profile: ProfileDraft | null;
53
+ /** The profile's deletion: its date once requested here (kb-api's MyState does not carry it). */
54
+ purgeAfter: string | null;
55
+ exporting: boolean;
56
+ busy: Record<string, boolean>;
57
+ announcement: TextRef | null;
58
+ announceId: number;
59
+ }
60
+ export interface PanelController {
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>;
64
+ /** The member's sections (me/get and the lists). */
65
+ loadMember(): Promise<void>;
66
+ loadStatement(decisionId: string): Promise<void>;
67
+ loadNotice(receipt: string | null): Promise<void>;
68
+ unreadCount(): number;
69
+ markAllRead(): Promise<void>;
70
+ fileAppeal(): Promise<void>;
71
+ withdrawAppeal(): Promise<void>;
72
+ fileNoticeAppeal(): Promise<void>;
73
+ withdrawNoticeAppeal(): Promise<void>;
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;
78
+ unblock(memberId: string): Promise<void>;
79
+ editProfile(): void;
80
+ cancelProfile(): void;
81
+ saveProfile(): Promise<void>;
82
+ /** `me/export` as a JSON file the browser saves (`save` does the download; tests pass their own). */
83
+ exportData(save: (data: MemberExport) => void): Promise<void>;
84
+ erase(): Promise<boolean>;
85
+ cancelErase(): Promise<void>;
86
+ /** The member asked for the deletion (here or before). */
87
+ erasing(): boolean;
88
+ appealLength(draft: AppealDraft): number;
89
+ canAppealWith(draft: AppealDraft): boolean;
90
+ dispose(): void;
91
+ }
92
+ export interface PanelOptions {
93
+ client: CommunityClient;
94
+ lang: string;
95
+ }
96
+ export declare function createPanel(o: PanelOptions): PanelController;
@@ -0,0 +1,40 @@
1
+ import { type CommunityClient, type CommunityFailure, type CommunityMessageKey, type CommunityRule, 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
+ export interface ProfileController {
13
+ readonly state: ProfileUi;
14
+ load(handle: string): Promise<void>;
15
+ dispose(): void;
16
+ }
17
+ /** A handle as an attribute may give it (`@jana_n`, `Jana_N`): lower case, without the `@`; null for none. */
18
+ export declare function handleOf(value: unknown): string | null;
19
+ export declare function createProfile(o: {
20
+ client: CommunityClient;
21
+ }): ProfileController;
22
+ export interface WelcomeUi {
23
+ phase: 'idle' | 'loading' | 'ready';
24
+ rules: CommunityRule[];
25
+ /** The rules could not be read: the page still links them. */
26
+ rulesFailed: boolean;
27
+ /** The signed-in member's handle (the profile link), or null for a visitor and a reader who never joined. */
28
+ handle: string | null;
29
+ /** Nobody is signed in: the sign-in link takes the profile link's place. */
30
+ visitor: boolean;
31
+ }
32
+ export interface WelcomeController {
33
+ readonly state: WelcomeUi;
34
+ load(): Promise<void>;
35
+ dispose(): void;
36
+ }
37
+ export declare function createWelcome(o: {
38
+ client: CommunityClient;
39
+ lang: string;
40
+ }): 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,35 @@
1
+ /**
2
+ * The discussion's live updates (m2-contracts.md §3.5, §3.10): `<blogmcp-discussion>` takes a `realtime` PROPERTY —
3
+ * the session factory of `@blogmcp/sdk/community/realtime`, which the HOST imports and passes:
4
+ *
5
+ * import { createRealtimeSession } from '@blogmcp/sdk/community/realtime'
6
+ * mountDiscussion({ articleId, realtime: createRealtimeSession }) // or el.realtime = createRealtimeSession
7
+ *
8
+ * The element calls it with its own client and discussion store after the first `discussions/list`, and `stop()`s
9
+ * the session when it unmounts. The element never imports the realtime entry (nor `centrifuge`): a site that passes no
10
+ * factory ships no socket code, and the discussion polls (`pollDiscussionHead`) as before.
11
+ *
12
+ * The types here are the factory's shape as far as the element uses it, so `createRealtimeSession` fits as it is.
13
+ */
14
+ import type { CommunityClient, DiscussionStore } from '@blogmcp/sdk/community';
15
+ /** `connecting`, `live`, `polling` (live updates are off or the server cut the socket for good), `stopped`. */
16
+ export type DiscussionRealtimeMode = 'connecting' | 'live' | 'polling' | 'stopped' | (string & {});
17
+ /** What the element calls the factory with. */
18
+ export interface DiscussionRealtimeOptions {
19
+ client: CommunityClient;
20
+ store: DiscussionStore;
21
+ /** The discussion's language, for the session's re-reads. */
22
+ languageCode: string;
23
+ /** Whether the discussion is seen now (the fallback polls only then). */
24
+ visible: () => boolean;
25
+ /** The element's poll interval, for the session's fallback poller (absent: the SDK's default). */
26
+ pollIntervalMs?: number;
27
+ onMode: (mode: DiscussionRealtimeMode) => void;
28
+ }
29
+ /** A running session, as far as the element uses it. */
30
+ export interface DiscussionRealtimeSession {
31
+ readonly mode: DiscussionRealtimeMode;
32
+ stop(): void;
33
+ }
34
+ /** `createRealtimeSession` of `@blogmcp/sdk/community/realtime`, or any function of its shape. */
35
+ export type DiscussionRealtime = (options: DiscussionRealtimeOptions) => DiscussionRealtimeSession;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The receipt this element's page carries: a string; `null` for a `#t=` that holds none (a cut link — "incomplete",
3
+ * never an older receipt of the tab); `undefined` when the page carries none for it.
4
+ */
5
+ export declare function receiptForElement(owner: object | null): string | null | undefined;
@@ -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;
@@ -0,0 +1,35 @@
1
+ export type TextSegment = {
2
+ readonly kind: 'text';
3
+ readonly text: string;
4
+ } | {
5
+ readonly kind: 'link';
6
+ readonly text: string;
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;
14
+ };
15
+ /** The rel every link to what a member wrote carries (user-generated, no referrer, no opener). */
16
+ export declare const UGC_LINK_REL = "nofollow ugc noopener noreferrer";
17
+ /** A member's text as text runs and links. */
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[];
26
+ /** Characters in code points, as kb-api counts a body (after NFC and trimming). */
27
+ export declare function codePointLength(text: string): number;
28
+ /** A body as kb-api will measure it: NFC, trimmed. */
29
+ export declare function normalizeBody(text: string): string;
30
+ /**
31
+ * The paragraphs of the tenant's own legal text (`legal/current`'s `body` is HTML with anchors) as plain
32
+ * text, for the art. 9 consent step: tags are dropped and entities decoded AFTER that, so nothing in the
33
+ * text is ever parsed as markup again. String work only — no HTML parser, no live DOM.
34
+ */
35
+ export declare function htmlToParagraphs(html: string): string[];
@@ -0,0 +1,33 @@
1
+ export declare const TURNSTILE_SCRIPT_SRC = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
2
+ interface TurnstileRenderOptions {
3
+ sitekey: string;
4
+ action?: string;
5
+ language?: string;
6
+ appearance?: 'always' | 'execute' | 'interaction-only';
7
+ callback?: (token: string) => void;
8
+ 'expired-callback'?: () => void;
9
+ 'error-callback'?: () => void;
10
+ 'timeout-callback'?: () => void;
11
+ }
12
+ export interface TurnstileApi {
13
+ render(el: HTMLElement, options: TurnstileRenderOptions): string;
14
+ reset(widgetId: string): void;
15
+ remove(widgetId: string): void;
16
+ }
17
+ /** One `<script>` per page however many forms mount; a failed load may be tried again later. */
18
+ export declare function loadTurnstile(nonce?: string): Promise<TurnstileApi>;
19
+ export interface TurnstileWidget {
20
+ /** A fresh token (the last one is spent). */
21
+ reset(): void;
22
+ remove(): void;
23
+ }
24
+ export interface RenderTurnstileOptions {
25
+ siteKey: string;
26
+ language: string;
27
+ /** A new token, or null when it expired or failed (the form cannot be sent until a new one comes). */
28
+ onToken(token: string | null): void;
29
+ nonce?: string;
30
+ }
31
+ /** The widget in `container`; rejects when the script cannot load (an ad blocker, the network). */
32
+ export declare function renderTurnstile(container: HTMLElement, o: RenderTurnstileOptions): Promise<TurnstileWidget>;
33
+ export {};
@@ -0,0 +1,47 @@
1
+ export declare const DISCUSSION_ELEMENT_TAG = "blogmcp-discussion";
2
+ export declare const NOTICE_FORM_ELEMENT_TAG = "blogmcp-notice-form";
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
+ export interface DefineCommunityElementOptions {
11
+ /** Another tag name for the element. */
12
+ tag?: string;
13
+ /** CSP nonce for the element's `<style>` (else each element's own `nonce`). */
14
+ nonce?: string;
15
+ }
16
+ export interface DefineCommunityElementsOptions {
17
+ /** CSP nonce for the elements' `<style>`. */
18
+ nonce?: string;
19
+ tags?: {
20
+ discussion?: string;
21
+ noticeForm?: string;
22
+ myCommunity?: string;
23
+ notificationCentre?: string;
24
+ badge?: string;
25
+ memberSettings?: string;
26
+ profile?: string;
27
+ welcome?: string;
28
+ };
29
+ }
30
+ /** Register `<blogmcp-discussion>` (once; later calls are no-ops). Browser only. */
31
+ export declare function defineDiscussionElement(opts?: DefineCommunityElementOptions): void;
32
+ /** Register `<blogmcp-notice-form>` (once). Browser only. */
33
+ export declare function defineNoticeFormElement(opts?: DefineCommunityElementOptions): void;
34
+ /** Register `<blogmcp-my-community>` (once). Browser only. */
35
+ export declare function defineMyCommunityElement(opts?: DefineCommunityElementOptions): void;
36
+ /** Register `<blogmcp-notification-centre>` (once). Browser only. */
37
+ export declare function defineNotificationCentreElement(opts?: DefineCommunityElementOptions): void;
38
+ /** Register `<blogmcp-badge>` (once). Browser only. */
39
+ export declare function defineBadgeElement(opts?: DefineCommunityElementOptions): void;
40
+ /** Register `<blogmcp-member-settings>` (once). Browser only. */
41
+ export declare function defineMemberSettingsElement(opts?: DefineCommunityElementOptions): void;
42
+ /** Register `<blogmcp-profile>` (once). Browser only. */
43
+ export declare function defineProfileElement(opts?: DefineCommunityElementOptions): void;
44
+ /** Register `<blogmcp-welcome>` (once). Browser only. */
45
+ export declare function defineWelcomeElement(opts?: DefineCommunityElementOptions): void;
46
+ /** Register all eight (once). Browser only. */
47
+ export declare function defineCommunityElements(opts?: DefineCommunityElementsOptions): void;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The `<script>` build: registers every element — `<blogmcp-discussion>`, `<blogmcp-notice-form>`,
3
+ * `<blogmcp-my-community>` and (M2) `<blogmcp-notification-centre>`, `<blogmcp-badge>`, `<blogmcp-member-settings>`,
4
+ * `<blogmcp-profile>`, `<blogmcp-welcome>` — with the script tag's own CSP nonce, and exposes
5
+ * `window.BlogmcpCommunity`. Live updates are not in it: a site that wants them bundles `@blogmcp/sdk/community/realtime`
6
+ * and sets the discussion's `realtime` property.
7
+ */
8
+ import { defineCommunityElements } from './element';
9
+ import { mountBadge, mountDiscussion, mountMemberSettings, mountMyCommunity, mountNoticeForm, mountNotificationCentre, mountProfile, mountWelcome } from './mount';
10
+ import { VERSION } from './version';
11
+ export { defineCommunityElements, mountDiscussion, mountMyCommunity, mountNoticeForm, mountNotificationCentre, mountBadge, mountMemberSettings, mountProfile, mountWelcome, VERSION, };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @blogmcp/community — the community's elements for a site's own pages (m1-contracts.md §10.4; docs: "Add
3
+ * discussions to your frontend"), over `@blogmcp/sdk/community`. Every element talks to the HOST's relative
4
+ * base (`api-base`; default `/api/community` under the site's `site-base`: a headless site's BFF from
5
+ * `@blogmcp/sdk/community/server`, or with `site-base="/blog"` a managed blog's own proxy), never to kb-api directly.
6
+ *
7
+ * - `<blogmcp-discussion>` the discussion under an article;
8
+ * - `<blogmcp-notice-form>` the DSA notice form, for anyone;
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
+ * - `defineCommunityElements()` and one `define…Element()` each — the custom elements;
14
+ * - `mountDiscussion(…)`, `mountNoticeForm(…)`, `mountMyCommunity(…)` — mount one from code;
15
+ * - `@blogmcp/community/vue`, `@blogmcp/community/react` — `<KbDiscussion>`, `<KbNoticeForm>`, `<KbMyCommunity>`;
16
+ * - `@blogmcp/community/global` — one `<script>` for sites without a bundler.
17
+ */
18
+ export { defineCommunityElements, defineDiscussionElement, defineNoticeFormElement, defineMyCommunityElement, defineNotificationCentreElement, defineBadgeElement, defineMemberSettingsElement, defineProfileElement, defineWelcomeElement, 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, type DefineCommunityElementOptions, type DefineCommunityElementsOptions, } from './element';
19
+ export { mountDiscussion, mountNoticeForm, mountMyCommunity, mountNotificationCentre, mountBadge, mountMemberSettings, mountProfile, mountWelcome, type MountDiscussionOptions, type MountNoticeFormOptions, type MountMyCommunityOptions, type MountNotificationCentreOptions, type MountBadgeOptions, type MountMemberSettingsOptions, type MountProfileOptions, type MountWelcomeOptions, } from './mount';
20
+ export { createNotificationCentre, type NotificationCentreController, type CentreUi } from './core/centre';
21
+ export { createBadge, badgeText, BADGE_INTERVAL_MS, type BadgeController, type BadgeOptions, type BadgeUi } from './core/badge';
22
+ export { createMemberSettings, SETTING_KEYS, type MemberSettingsController, type MemberSettingsOptions, type SettingsUi, type SettingKey } from './core/settings';
23
+ export { createProfile, createWelcome, type ProfileController, type WelcomeController } from './core/profile';
24
+ export type { DiscussionRealtime, DiscussionRealtimeOptions, DiscussionRealtimeSession, DiscussionRealtimeMode } from './core/realtime';
25
+ export { createDiscussionController, type DiscussionController, type DiscussionControllerOptions, type DiscussionUi, type ComposerMode, type Draft } from './core/discussion';
26
+ export { createNoticeForm, type NoticeFormController, type NoticeFormOptions, type NoticeFormUi } from './core/notice';
27
+ export { createPanel, type PanelController, type PanelOptions, type PanelUi } from './core/panel';
28
+ export { createOnboarding, type OnboardingModel, type OnboardingOptions, type OnboardingStep } from './core/onboarding';
29
+ export { pickLang, type Lang } from './core/i18n';
30
+ export { AVATAR_PRESETS, type AvatarPreset } from './core/avatars';
31
+ export { DEFAULT_API_BASE } from './core/client';
32
+ export type { CommunityRoutes } from './core/attrs';
33
+ export { VERSION } from './version';
package/dist/index.js ADDED
@@ -0,0 +1,45 @@
1
+ import { H, B, w, I, D, k, M, N, j, P, S, W, v, u, A, x, C, t, F, E, y, z, f, d, a, g, c, b, e, h, i, p, m, q, n, l, o, r, s, G } from "./chunks/mount-WadUnaTa.js";
2
+ const VERSION = "0.2.0";
3
+ export {
4
+ H as AVATAR_PRESETS,
5
+ B as BADGE_ELEMENT_TAG,
6
+ w as BADGE_INTERVAL_MS,
7
+ I as DEFAULT_API_BASE,
8
+ D as DISCUSSION_ELEMENT_TAG,
9
+ k as MEMBER_SETTINGS_ELEMENT_TAG,
10
+ M as MY_COMMUNITY_ELEMENT_TAG,
11
+ N as NOTICE_FORM_ELEMENT_TAG,
12
+ j as NOTIFICATION_CENTRE_ELEMENT_TAG,
13
+ P as PROFILE_ELEMENT_TAG,
14
+ S as SETTING_KEYS,
15
+ VERSION,
16
+ W as WELCOME_ELEMENT_TAG,
17
+ v as badgeText,
18
+ u as createBadge,
19
+ A as createDiscussionController,
20
+ x as createMemberSettings,
21
+ C as createNoticeForm,
22
+ t as createNotificationCentre,
23
+ F as createOnboarding,
24
+ E as createPanel,
25
+ y as createProfile,
26
+ z as createWelcome,
27
+ f as defineBadgeElement,
28
+ d as defineCommunityElements,
29
+ a as defineDiscussionElement,
30
+ g as defineMemberSettingsElement,
31
+ c as defineMyCommunityElement,
32
+ b as defineNoticeFormElement,
33
+ e as defineNotificationCentreElement,
34
+ h as defineProfileElement,
35
+ i as defineWelcomeElement,
36
+ p as mountBadge,
37
+ m as mountDiscussion,
38
+ q as mountMemberSettings,
39
+ n as mountMyCommunity,
40
+ l as mountNoticeForm,
41
+ o as mountNotificationCentre,
42
+ r as mountProfile,
43
+ s as mountWelcome,
44
+ G as pickLang
45
+ };
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Put an element on the page from code: what the Vue and React wrappers do, and what any other framework can
3
+ * call from its client-side mount hook. The element is defined first, so it is created already upgraded (no
4
+ * upgrade of an unknown element later, which some DOMs never run), and every attribute and property is set
5
+ * BEFORE it connects, so it mounts with them; the returned function removes the element again (its poll and
6
+ * requests stop with it).
7
+ */
8
+ import type { CommunityTransport } from '@blogmcp/sdk/community';
9
+ import type { CommunityRoutes } from './core/attrs';
10
+ import type { DiscussionRealtime } from './core/realtime';
11
+ interface CommonMountOptions {
12
+ /** The host's relative base (default `/api/community` under `siteBase`): its proxy or BFF. Never kb-api's URL. */
13
+ apiBase?: string;
14
+ /**
15
+ * The path the site is served under (`/blog` on a managed blog, whose host's own `/api/` is kb-api): the default
16
+ * api-base and the default pages are taken under it. A page or base the host names itself is used as it is.
17
+ */
18
+ siteBase?: string;
19
+ /** `cs` or `en` (`sk` reads Czech); default the page's `<html lang>`. */
20
+ lang?: string;
21
+ /** The host's community pages (statements, the panel, the rules, the notice form, a notifier's status). */
22
+ routes?: CommunityRoutes;
23
+ /** CSP nonce for the element's stylesheet (and the Turnstile script). */
24
+ nonce?: string;
25
+ /** A replacement transport (tests; a signing transport later). */
26
+ transport?: CommunityTransport;
27
+ /** Where the element goes; default `document.body`. */
28
+ container?: Element;
29
+ }
30
+ export interface MountDiscussionOptions extends CommonMountOptions {
31
+ articleId: string;
32
+ /** The site's sign-in page; the current path is put in its fragment. */
33
+ signInHref?: string;
34
+ /** Head poll interval in ms (default 20 000; 0 = off). */
35
+ pollMs?: number;
36
+ /**
37
+ * M2: live updates — `createRealtimeSession` of `@blogmcp/sdk/community/realtime` (the host imports it; the element
38
+ * never does). Absent: the head poll.
39
+ */
40
+ realtime?: DiscussionRealtime;
41
+ }
42
+ export interface MountNoticeFormOptions extends CommonMountOptions {
43
+ /** What is reported: a message, a member (by id or handle) or an article. */
44
+ target?: {
45
+ conversationId: string;
46
+ seq: number;
47
+ } | {
48
+ memberId: string;
49
+ } | {
50
+ handle: string;
51
+ } | {
52
+ articleId: string;
53
+ };
54
+ /** Or the article (when `target` is absent). */
55
+ articleId?: string;
56
+ /** The site's Turnstile site key; without it no widget renders (kb-api then decides alone). */
57
+ turnstileSiteKey?: string;
58
+ }
59
+ export interface MountMyCommunityOptions extends CommonMountOptions {
60
+ /** Open this statement of reasons (the host's statement page). */
61
+ decisionId?: string;
62
+ /** The site's sign-in page. */
63
+ signInHref?: string;
64
+ }
65
+ export interface MountNotificationCentreOptions extends CommonMountOptions {
66
+ /** The site's sign-in page. */
67
+ signInHref?: string;
68
+ }
69
+ export interface MountBadgeOptions extends CommonMountOptions {
70
+ /** The unread count's poll in ms (default 60 000; 0 = once, and when the page is shown again). */
71
+ pollMs?: number;
72
+ }
73
+ export interface MountMemberSettingsOptions extends CommonMountOptions {
74
+ /** The host's service worker route (default `<site-base>/sw.js`, served from `@blogmcp/sdk/community/sw`). */
75
+ swPath?: string;
76
+ /** The site's sign-in page. */
77
+ signInHref?: string;
78
+ }
79
+ export interface MountProfileOptions extends CommonMountOptions {
80
+ /** The member's handle. */
81
+ handle: string;
82
+ /** The site's sign-in page. */
83
+ signInHref?: string;
84
+ }
85
+ export interface MountWelcomeOptions extends CommonMountOptions {
86
+ /** The site's sign-in page (a visitor's link in the profile's place). */
87
+ signInHref?: string;
88
+ }
89
+ /** Mount `<blogmcp-discussion>`; returns the function that removes it. */
90
+ export declare function mountDiscussion(o: MountDiscussionOptions): () => void;
91
+ /** Mount `<blogmcp-notice-form>`; returns the function that removes it. */
92
+ export declare function mountNoticeForm(o: MountNoticeFormOptions): () => void;
93
+ /** Mount `<blogmcp-my-community>`; returns the function that removes it. */
94
+ export declare function mountMyCommunity(o?: MountMyCommunityOptions): () => void;
95
+ /** Mount `<blogmcp-notification-centre>`; returns the function that removes it. */
96
+ export declare function mountNotificationCentre(o?: MountNotificationCentreOptions): () => void;
97
+ /** Mount `<blogmcp-badge>`; returns the function that removes it. */
98
+ export declare function mountBadge(o?: MountBadgeOptions): () => void;
99
+ /** Mount `<blogmcp-member-settings>`; returns the function that removes it. */
100
+ export declare function mountMemberSettings(o?: MountMemberSettingsOptions): () => void;
101
+ /** Mount `<blogmcp-profile>`; returns the function that removes it. */
102
+ export declare function mountProfile(o: MountProfileOptions): () => void;
103
+ /** Mount `<blogmcp-welcome>`; returns the function that removes it. */
104
+ export declare function mountWelcome(o?: MountWelcomeOptions): () => void;
105
+ export {};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `<KbDiscussion>`, `<KbNoticeForm>`, `<KbMyCommunity>` and (M2) `<KbNotificationCentre>`, `<KbBadge>`,
3
+ * `<KbMemberSettings>`, `<KbProfile>`, `<KbWelcome>` for React (18 and 19, Next.js App Router included — this
4
+ * module is a client component): each mounts its element after hydration and removes it on unmount. Each
5
+ * renders one empty `<div>` as the mount point — nothing else, on the server or the client — so the element
6
+ * sits where the component does and the page's markup is otherwise the site's alone.
7
+ *
8
+ * <KbDiscussion articleId={article.id} apiBase="/api/community" signInHref={`/sign-in#${pathname}`} />
9
+ */
10
+ import { type ReactElement } from 'react';
11
+ import type { MountBadgeOptions, MountDiscussionOptions, MountMemberSettingsOptions, MountMyCommunityOptions, MountNoticeFormOptions, MountNotificationCentreOptions, MountProfileOptions, MountWelcomeOptions } from './mount';
12
+ export type KbDiscussionProps = Omit<MountDiscussionOptions, 'container'>;
13
+ export type KbNoticeFormProps = Omit<MountNoticeFormOptions, 'container'>;
14
+ export type KbMyCommunityProps = Omit<MountMyCommunityOptions, 'container'>;
15
+ export type KbNotificationCentreProps = Omit<MountNotificationCentreOptions, 'container'>;
16
+ export type KbBadgeProps = Omit<MountBadgeOptions, 'container'>;
17
+ export type KbMemberSettingsProps = Omit<MountMemberSettingsOptions, 'container'>;
18
+ export type KbProfileProps = Omit<MountProfileOptions, 'container'>;
19
+ export type KbWelcomeProps = Omit<MountWelcomeOptions, 'container'>;
20
+ export declare function KbDiscussion(props: KbDiscussionProps): ReactElement;
21
+ export declare function KbNoticeForm(props: KbNoticeFormProps): ReactElement;
22
+ export declare function KbMyCommunity(props: KbMyCommunityProps): ReactElement;
23
+ export declare function KbNotificationCentre(props: KbNotificationCentreProps): ReactElement;
24
+ export declare function KbBadge(props: KbBadgeProps): ReactElement;
25
+ export declare function KbMemberSettings(props: KbMemberSettingsProps): ReactElement;
26
+ export declare function KbProfile(props: KbProfileProps): ReactElement;
27
+ export declare function KbWelcome(props: KbWelcomeProps): ReactElement;
28
+ export type { MountDiscussionOptions, MountNoticeFormOptions, MountMyCommunityOptions, MountNotificationCentreOptions, MountBadgeOptions, MountMemberSettingsOptions, MountProfileOptions, MountWelcomeOptions, };