@blogmcp/community 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +146 -2
- package/dist/blogmcp-community.global.js +10 -0
- package/dist/chunks/mount-DzjwnHyx.js +4597 -0
- package/dist/core/attrs.d.ts +73 -0
- package/dist/core/avatars.d.ts +16 -0
- package/dist/core/client.d.ts +27 -0
- package/dist/core/discussion.d.ts +144 -0
- package/dist/core/download.d.ts +4 -0
- package/dist/core/format.d.ts +17 -0
- package/dist/core/i18n.d.ts +28 -0
- package/dist/core/names.d.ts +15 -0
- package/dist/core/notice.d.ts +50 -0
- package/dist/core/onboarding.d.ts +52 -0
- package/dist/core/outcomes.d.ts +28 -0
- package/dist/core/panel.d.ts +80 -0
- package/dist/core/receipt.d.ts +5 -0
- package/dist/core/text.d.ts +22 -0
- package/dist/core/turnstile.d.ts +33 -0
- package/dist/element.d.ts +26 -0
- package/dist/global.d.ts +8 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +22 -0
- package/dist/mount.d.ts +65 -0
- package/dist/react.d.ts +17 -0
- package/dist/react.js +35 -0
- package/dist/ui/AuthorAvatar.vue.d.ts +11 -0
- package/dist/ui/ConfirmDialog.vue.d.ts +20 -0
- package/dist/ui/DiscussionView.vue.d.ts +33 -0
- package/dist/ui/LinkedText.vue.d.ts +5 -0
- package/dist/ui/MessageComposer.vue.d.ts +24 -0
- package/dist/ui/MessageItem.vue.d.ts +6 -0
- package/dist/ui/MessageMenu.vue.d.ts +10 -0
- package/dist/ui/MyCommunityView.vue.d.ts +16 -0
- package/dist/ui/NoticeFormView.vue.d.ts +26 -0
- package/dist/ui/NoticeStatusView.vue.d.ts +15 -0
- package/dist/ui/OnboardingSteps.vue.d.ts +11 -0
- package/dist/ui/ReactionBar.vue.d.ts +13 -0
- package/dist/ui/ReportDialog.vue.d.ts +15 -0
- package/dist/ui/StatementView.vue.d.ts +15 -0
- package/dist/ui/context.d.ts +21 -0
- package/dist/ui/discussion-context.d.ts +19 -0
- package/dist/ui/types.d.ts +7 -0
- package/dist/version.d.ts +1 -0
- package/dist/vue.d.ts +89 -0
- package/dist/vue.js +97 -0
- package/package.json +80 -4
- package/src/core/attrs.ts +147 -0
- package/src/core/avatars.ts +44 -0
- package/src/core/client.ts +54 -0
- package/src/core/discussion.ts +691 -0
- package/src/core/download.ts +21 -0
- package/src/core/format.ts +90 -0
- package/src/core/i18n.ts +49 -0
- package/src/core/names.ts +51 -0
- package/src/core/notice.ts +178 -0
- package/src/core/onboarding.ts +345 -0
- package/src/core/outcomes.ts +74 -0
- package/src/core/panel.ts +411 -0
- package/src/core/receipt.ts +28 -0
- package/src/core/text.ts +142 -0
- package/src/core/turnstile.ts +98 -0
- package/src/element.ts +109 -0
- package/src/global.ts +12 -0
- package/src/index.ts +35 -0
- package/src/mount.ts +127 -0
- package/src/react.ts +53 -0
- package/src/shims.d.ts +11 -0
- package/src/ui/AuthorAvatar.vue +26 -0
- package/src/ui/ConfirmDialog.vue +38 -0
- package/src/ui/DiscussionView.vue +294 -0
- package/src/ui/LinkedText.vue +18 -0
- package/src/ui/MessageComposer.vue +130 -0
- package/src/ui/MessageItem.vue +205 -0
- package/src/ui/MessageMenu.vue +67 -0
- package/src/ui/MyCommunityView.vue +369 -0
- package/src/ui/NoticeFormView.vue +244 -0
- package/src/ui/NoticeStatusView.vue +82 -0
- package/src/ui/OnboardingSteps.vue +163 -0
- package/src/ui/ReactionBar.vue +41 -0
- package/src/ui/ReportDialog.vue +88 -0
- package/src/ui/StatementView.vue +141 -0
- package/src/ui/context.ts +75 -0
- package/src/ui/discussion-context.ts +30 -0
- package/src/ui/styles.css +243 -0
- package/src/ui/types.ts +7 -0
- package/src/version.ts +3 -0
- package/src/vue.ts +116 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lukas Zubal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,147 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @blogmcp/community
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The community elements for your own site, over `@blogmcp/sdk/community`:
|
|
4
|
+
|
|
5
|
+
- `<blogmcp-discussion>`: the discussion under an article. A two-level thread, the composer, reactions,
|
|
6
|
+
reports, blocks, the inline onboarding, and the states of a member's own messages ("awaiting review",
|
|
7
|
+
removed, deleted);
|
|
8
|
+
- `<blogmcp-notice-form>`: the notice form for anyone, signed in or not (DSA art. 16), with Cloudflare
|
|
9
|
+
Turnstile;
|
|
10
|
+
- `<blogmcp-my-community>`: the reader's „Moje komunita" panel. It holds statements of reasons and appeals,
|
|
11
|
+
reports, notifications, the consent, the export and the profile's deletion. It also renders a statement
|
|
12
|
+
page (`decision-id`) and a notifier's status page (`#t=<receipt>` in the address).
|
|
13
|
+
|
|
14
|
+
Each element lives in a shadow root and renders text only. A member's links pass through `sanitizeUrl` and
|
|
15
|
+
carry `rel="nofollow ugc noopener noreferrer"`. Every text comes from the SDK's catalogue
|
|
16
|
+
(`communityMessage`), in Czech or English.
|
|
17
|
+
|
|
18
|
+
## Your site's side: one relative base
|
|
19
|
+
|
|
20
|
+
The elements never call kb-api. They POST to **your site's** relative base: `api-base`, by default
|
|
21
|
+
`/api/community` under your `site-base` (the root when you set none):
|
|
22
|
+
|
|
23
|
+
- a headless site or the storefront, served at the root: the BFF from `@blogmcp/sdk/community/server`,
|
|
24
|
+
mounted at `/api/community`;
|
|
25
|
+
- a managed blog, served under `/blog`: its own `/api` proxy, so set `site-base="/blog"`. The element then
|
|
26
|
+
posts to `/blog/api/community`, and its default pages are `/blog/community/…`.
|
|
27
|
+
|
|
28
|
+
> **On a managed blog, never `api-base="/api/community"`.** On the tenant's host, `/api/` is kb-api itself, not
|
|
29
|
+
> the blog. A request there skips the blog's proxy, so it carries no reader session. Every reader reads as
|
|
30
|
+
> signed out, every write is refused, and the notice form reaches no route. The defaults have the same trap:
|
|
31
|
+
> without `site-base`, the links go to `/community/…`, outside the blog.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// e.g. app/routes/api/community.$.ts (POST only)
|
|
35
|
+
import { createCommunityBff, communityServerEnv, fromWebRequest } from '@blogmcp/sdk/community/server'
|
|
36
|
+
|
|
37
|
+
const bff = createCommunityBff({
|
|
38
|
+
// kb-api as your server reaches it, WITH the `/api` prefix your instance serves it under:
|
|
39
|
+
// KB_API_URL = https://<your instance>/api (or `${process.env.BLOGMCP_API_URL}/api`).
|
|
40
|
+
kbApiUrl: process.env.KB_API_URL!,
|
|
41
|
+
siteUrls: ['https://www.example.com'],
|
|
42
|
+
memberToken: async (http) => /* your session → the member's JWT, or null */ null,
|
|
43
|
+
...communityServerEnv(process.env), // COMMUNITY_NOTICE_KEY
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
export async function action({ request }: { request: Request }) {
|
|
47
|
+
const { http, respond } = fromWebRequest(request)
|
|
48
|
+
return respond(await bff.handle(http))
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
An absolute `api-base` (or `site-base`) is refused, and the element renders nothing: kb-api's community
|
|
53
|
+
routes answer no CORS.
|
|
54
|
+
|
|
55
|
+
## Mount the elements
|
|
56
|
+
|
|
57
|
+
**Vue / Nuxt**
|
|
58
|
+
|
|
59
|
+
```vue
|
|
60
|
+
<script setup lang="ts">
|
|
61
|
+
import { KbDiscussion } from '@blogmcp/community/vue'
|
|
62
|
+
</script>
|
|
63
|
+
<template>
|
|
64
|
+
<article>…</article>
|
|
65
|
+
<KbDiscussion :article-id="article.id" api-base="/api/community" sign-in-href="/prihlaseni" />
|
|
66
|
+
</template>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**React / Next.js**
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { KbDiscussion } from '@blogmcp/community/react' // a client component
|
|
73
|
+
|
|
74
|
+
<KbDiscussion articleId={article.id} apiBase="/api/community" signInHref="/sign-in" />
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`KbNoticeForm` and `KbMyCommunity` work the same way. Each wrapper mounts its element after hydration and
|
|
78
|
+
removes it on unmount. It renders a single empty `<div data-blogmcp-community="…">` as the mount point: the
|
|
79
|
+
discussion is part of the page's flow, so the element goes where the wrapper is.
|
|
80
|
+
|
|
81
|
+
`mountDiscussion(o)`, `mountNoticeForm(o)` and `mountMyCommunity(o)` do the same from any client-side hook.
|
|
82
|
+
Each returns the unmount function.
|
|
83
|
+
|
|
84
|
+
**Plain HTML**
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<blogmcp-discussion article-id="…" api-base="/api/community" sign-in-href="/prihlaseni"></blogmcp-discussion>
|
|
88
|
+
<script src="https://cdn.jsdelivr.net/npm/@blogmcp/community@0.1.0/dist/blogmcp-community.global.js" nonce="…"></script>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The script build (`@blogmcp/community/global`) registers all three elements. It exposes
|
|
92
|
+
`window.BlogmcpCommunity` and gives the elements its own `nonce`.
|
|
93
|
+
|
|
94
|
+
## Attributes
|
|
95
|
+
|
|
96
|
+
| Element | Attribute | |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| all | `api-base` | your relative base (default `/api/community` under `site-base`) |
|
|
99
|
+
| all | `site-base` | the path your site is served under (`/blog` on a managed blog); the defaults go under it |
|
|
100
|
+
| all | `lang` | `cs` or `en` (`sk` reads Czech); default `<html lang>` |
|
|
101
|
+
| all | `routes` | JSON with your pages (below) |
|
|
102
|
+
| all | `nonce` | CSP nonce for the element's stylesheet and the Turnstile script |
|
|
103
|
+
| `<blogmcp-discussion>` | `article-id` | the article |
|
|
104
|
+
| | `sign-in-href` | your sign-in page; the current path goes in its fragment |
|
|
105
|
+
| | `poll-ms` | how often to check for new comments while the discussion is in view (default 20000; 0 = off) |
|
|
106
|
+
| `<blogmcp-notice-form>` | `target` | JSON: `{ conversationId, seq }`, `{ memberId }`, `{ handle }` or `{ articleId }` |
|
|
107
|
+
| | `article-id` | or the article; without either, the discussion's report link (`#c=…&s=…`). With no target at all, the element shows where a report starts and links to `rulesPath` instead of the form |
|
|
108
|
+
| | `turnstile-site-key` | your Turnstile site key; without it no widget renders |
|
|
109
|
+
| `<blogmcp-my-community>` | `decision-id` | open this statement of reasons |
|
|
110
|
+
| | `sign-in-href` | your sign-in page |
|
|
111
|
+
|
|
112
|
+
`routes` names your community pages: `statementPath` (with `{id}`), `myCommunityPath`, `rulesPath`,
|
|
113
|
+
`reportPath` and `noticesPath`. Each is a full path on your site, used as it is. A page you leave out
|
|
114
|
+
falls back to the managed blog's pages: `${site-base}/community/…`. The tenant's own statement and „Moje
|
|
115
|
+
komunita" paths, set in its community settings, come before that fallback.
|
|
116
|
+
`consultationPath` (no default) turns the regulated preset's adverse-event note into a link to your
|
|
117
|
+
consultation page.
|
|
118
|
+
The receipt in a status link always goes in the fragment (`#t=…`). The page clears it from the address bar
|
|
119
|
+
as soon as it reads it.
|
|
120
|
+
|
|
121
|
+
A `transport` property replaces the HTTP layer, for tests.
|
|
122
|
+
|
|
123
|
+
Events (bubbling, composed):
|
|
124
|
+
- `bcm-posted`, `{ state: 'visible' | 'held' }`: the reader's comment went out;
|
|
125
|
+
- `bcm-notice-submitted`, `{ contactKept }`: a notice was received.
|
|
126
|
+
|
|
127
|
+
## Look
|
|
128
|
+
|
|
129
|
+
Theme the elements with the custom properties on them: `blogmcp-discussion { --bcm-accent: #2b7a4b }`.
|
|
130
|
+
The properties are `--bcm-bg`, `-fg`, `-muted`, `-line`, `-soft`, `-accent`, `-accent-fg`, `-danger`,
|
|
131
|
+
`-ok`, `-warn-bg`, `-shadow` and `-z`, plus `--bcm-font` and `--bcm-font-size`. Dark mode follows
|
|
132
|
+
`prefers-color-scheme`.
|
|
133
|
+
|
|
134
|
+
## Content Security Policy
|
|
135
|
+
|
|
136
|
+
- The styles are one `<style>` in each shadow root, carrying the nonce you pass.
|
|
137
|
+
- `connect-src 'self'` is enough: the elements only talk to your own base.
|
|
138
|
+
- The notice form with Turnstile needs `script-src` and `frame-src` for `https://challenges.cloudflare.com`.
|
|
139
|
+
Only a page with the notice form loads that script.
|
|
140
|
+
|
|
141
|
+
## Development
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npm run build # ESM + the <script> build (+ size budget, 200 KiB gzip)
|
|
145
|
+
npm test # vitest (happy-dom): controllers, store, onboarding, outcomes, elements, text-only and closed-path scans
|
|
146
|
+
npm run test:e2e # Playwright: desktop + iPhone profile + a plain bundler, mocked community API (ports 8286/8287)
|
|
147
|
+
```
|