@risali/react 0.2.0 → 0.5.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/dist/blog.js ADDED
@@ -0,0 +1,79 @@
1
+ // Blog data helpers for @risali/react v0.3.0 (PR 36c).
2
+ // Pure server-side fetchers around the Risali public blog API. Server
3
+ // components <RisaliBlogList /> and <RisaliBlogPost /> use these.
4
+ // Client components (rare for blog UI) can also call them directly.
5
+ import { resolveApiBase, resolveRevalidateSeconds, resolveSlug, } from "./config.js";
6
+ /**
7
+ * Fetch the published blog post list for the resolved site. Returns
8
+ * an empty array on any failure (unknown site, plan downgrade,
9
+ * network) so the consumer can fall back to an empty grid without
10
+ * dropping the whole page.
11
+ */
12
+ export async function getRisaliBlogPosts(opts = {}) {
13
+ const slug = resolveSlug(opts.siteSlug);
14
+ const base = resolveApiBase();
15
+ const revalidate = resolveRevalidateSeconds();
16
+ const url = new URL(`${base}/api/public/sites/${slug}/blog/posts`);
17
+ if (opts.limit != null)
18
+ url.searchParams.set("limit", String(opts.limit));
19
+ if (opts.offset != null)
20
+ url.searchParams.set("offset", String(opts.offset));
21
+ if (opts.category)
22
+ url.searchParams.set("category", opts.category);
23
+ try {
24
+ const res = await fetch(url.toString(), {
25
+ next: { revalidate },
26
+ });
27
+ if (!res.ok)
28
+ return [];
29
+ const body = (await res.json());
30
+ if (!body || !("ok" in body) || !body.ok)
31
+ return [];
32
+ return Array.isArray(body.posts) ? body.posts : [];
33
+ }
34
+ catch {
35
+ return [];
36
+ }
37
+ }
38
+ /**
39
+ * Fetch a single blog post by slug. Returns null on any failure so
40
+ * the consumer can render a notFound() page instead of throwing.
41
+ * When `previewToken` is supplied, the call goes through the
42
+ * preview path (no cache, drafts allowed).
43
+ */
44
+ export async function getRisaliBlogPost(postSlug, opts = {}) {
45
+ if (typeof postSlug !== "string" || !postSlug)
46
+ return null;
47
+ const slug = resolveSlug(opts.siteSlug);
48
+ const base = resolveApiBase();
49
+ const revalidate = resolveRevalidateSeconds();
50
+ const url = new URL(`${base}/api/public/sites/${slug}/blog/posts/${encodeURIComponent(postSlug)}`);
51
+ if (opts.previewToken) {
52
+ url.searchParams.set("preview", opts.previewToken);
53
+ }
54
+ try {
55
+ const res = await fetch(url.toString(), {
56
+ next: opts.previewToken
57
+ ? // Preview must always be fresh; skip the static cache.
58
+ { revalidate: 0 }
59
+ : { revalidate },
60
+ });
61
+ if (!res.ok)
62
+ return null;
63
+ const body = (await res.json());
64
+ if (!body || !("ok" in body) || !body.ok)
65
+ return null;
66
+ return body.post ?? null;
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
72
+ /**
73
+ * Build a public `/blog/<slug>` URL for the client web. Helper for
74
+ * hand-rolled card grids that don't use `<RisaliBlogList />`.
75
+ */
76
+ export function buildBlogPostHref(postSlug) {
77
+ return `/blog/${encodeURIComponent(postSlug)}`;
78
+ }
79
+ //# sourceMappingURL=blog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog.js","sourceRoot":"","sources":["../src/blog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import * as React from "react";
2
+ export type RisaliEventProps = {
3
+ /**
4
+ * Stable lowercase identifier — must match a tracking_events.key configured
5
+ * in Risali for this site. The snippet runtime fires when a click bubbles
6
+ * through this element AND a tracking_events row with the same key exists.
7
+ *
8
+ * Pure marker: this component does NOT subscribe to clicks itself; it only
9
+ * places a stable `data-risali-event="<key>"` attribute on the wrapper so
10
+ * the marketer can later select it from the Risali dashboard's iframe-based
11
+ * event picker without having to maintain CSS selectors by hand.
12
+ */
13
+ eventKey: string;
14
+ /**
15
+ * Optional element to render around the children. Defaults to <span> for
16
+ * inline contexts; pass "div" for block CTAs / forms.
17
+ */
18
+ as?: "span" | "div" | "button";
19
+ className?: string;
20
+ children: React.ReactNode;
21
+ };
22
+ /**
23
+ * Code-side marker for Next.js sites: place around a button, link, or form
24
+ * and it'll be discoverable by the Risali event picker via the
25
+ * `[data-risali-event="<eventKey>"]` selector.
26
+ *
27
+ * <RisaliEvent eventKey="lead-form" as="div">
28
+ * <ContactForm />
29
+ * </RisaliEvent>
30
+ *
31
+ * On Lovable sites (no Next.js render path) this component is unused — the
32
+ * marketer picks elements via the dashboard's iframe selector flow instead.
33
+ */
34
+ export declare function RisaliEvent({ eventKey, as: Tag, className, children, }: RisaliEventProps): import("react/jsx-runtime").JSX.Element;
35
+ //# sourceMappingURL=event-marker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-marker.d.ts","sourceRoot":"","sources":["../src/event-marker.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;;;;;;;OASG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,EAAE,EAAE,GAAY,EAChB,SAAS,EACT,QAAQ,GACT,EAAE,gBAAgB,2CAMlB"}
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Code-side marker for Next.js sites: place around a button, link, or form
4
+ * and it'll be discoverable by the Risali event picker via the
5
+ * `[data-risali-event="<eventKey>"]` selector.
6
+ *
7
+ * <RisaliEvent eventKey="lead-form" as="div">
8
+ * <ContactForm />
9
+ * </RisaliEvent>
10
+ *
11
+ * On Lovable sites (no Next.js render path) this component is unused — the
12
+ * marketer picks elements via the dashboard's iframe selector flow instead.
13
+ */
14
+ export function RisaliEvent({ eventKey, as: Tag = "span", className, children, }) {
15
+ return (_jsx(Tag, { "data-risali-event": eventKey, className: className, children: children }));
16
+ }
17
+ //# sourceMappingURL=event-marker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-marker.js","sourceRoot":"","sources":["../src/event-marker.tsx"],"names":[],"mappings":";AAuBA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CAAC,EAC1B,QAAQ,EACR,EAAE,EAAE,GAAG,GAAG,MAAM,EAChB,SAAS,EACT,QAAQ,GACS;IACjB,OAAO,CACL,KAAC,GAAG,yBAAoB,QAAQ,EAAE,SAAS,EAAE,SAAS,YACnD,QAAQ,GACL,CACP,CAAC;AACJ,CAAC"}
package/dist/image.d.ts CHANGED
@@ -13,17 +13,21 @@ export declare function RisaliImage(props: RisaliImageProps): Promise<import("re
13
13
  src: string;
14
14
  alt: string;
15
15
  "data-risali-key": string;
16
+ children?: import("react").ReactNode;
17
+ className?: string | undefined | undefined;
16
18
  style?: CSSProperties;
17
- crossOrigin?: "" | "anonymous" | "use-credentials" | undefined;
18
- decoding?: "async" | "auto" | "sync" | undefined | undefined;
19
- fetchPriority?: "high" | "low" | "auto" | undefined | undefined;
19
+ hidden?: boolean | undefined | undefined;
20
+ color?: string | undefined | undefined;
20
21
  height?: number | string | undefined | undefined;
21
- loading?: "eager" | "lazy" | undefined | undefined;
22
- referrerPolicy?: import("react").HTMLAttributeReferrerPolicy | undefined;
23
- sizes?: string | undefined | undefined;
24
- srcSet?: string | undefined | undefined;
25
- useMap?: string | undefined | undefined;
26
22
  width?: number | string | undefined | undefined;
23
+ loading?: "eager" | "lazy" | undefined | undefined;
24
+ rel?: string | undefined | undefined;
25
+ id?: string | undefined | undefined;
26
+ title?: string | undefined | undefined;
27
+ dangerouslySetInnerHTML?: {
28
+ __html: string | TrustedHTML;
29
+ } | undefined | undefined;
30
+ fetchPriority?: "high" | "low" | "auto" | undefined | undefined;
27
31
  defaultChecked?: boolean | undefined | undefined;
28
32
  defaultValue?: string | number | readonly string[] | undefined;
29
33
  suppressContentEditableWarning?: boolean | undefined | undefined;
@@ -31,20 +35,16 @@ export declare function RisaliImage(props: RisaliImageProps): Promise<import("re
31
35
  accessKey?: string | undefined | undefined;
32
36
  autoCapitalize?: "off" | "none" | "on" | "sentences" | "words" | "characters" | undefined | (string & {}) | undefined;
33
37
  autoFocus?: boolean | undefined | undefined;
34
- className?: string | undefined | undefined;
35
- contentEditable?: (boolean | "true" | "false") | "inherit" | "plaintext-only" | undefined;
38
+ contentEditable?: "inherit" | (boolean | "false" | "true") | "plaintext-only" | undefined;
36
39
  contextMenu?: string | undefined | undefined;
37
40
  dir?: string | undefined | undefined;
38
- draggable?: (boolean | "true" | "false") | undefined;
41
+ draggable?: (boolean | "false" | "true") | undefined;
39
42
  enterKeyHint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
40
- hidden?: boolean | undefined | undefined;
41
- id?: string | undefined | undefined;
42
43
  lang?: string | undefined | undefined;
43
44
  nonce?: string | undefined | undefined;
44
45
  slot?: string | undefined | undefined;
45
- spellCheck?: (boolean | "true" | "false") | undefined;
46
+ spellCheck?: (boolean | "false" | "true") | undefined;
46
47
  tabIndex?: number | undefined | undefined;
47
- title?: string | undefined | undefined;
48
48
  translate?: "yes" | "no" | undefined | undefined;
49
49
  radioGroup?: string | undefined | undefined;
50
50
  role?: import("react").AriaRole | undefined;
@@ -53,14 +53,12 @@ export declare function RisaliImage(props: RisaliImageProps): Promise<import("re
53
53
  inlist?: any;
54
54
  prefix?: string | undefined | undefined;
55
55
  property?: string | undefined | undefined;
56
- rel?: string | undefined | undefined;
57
56
  resource?: string | undefined | undefined;
58
57
  rev?: string | undefined | undefined;
59
58
  typeof?: string | undefined | undefined;
60
59
  vocab?: string | undefined | undefined;
61
60
  autoCorrect?: string | undefined | undefined;
62
61
  autoSave?: string | undefined | undefined;
63
- color?: string | undefined | undefined;
64
62
  itemProp?: string | undefined | undefined;
65
63
  itemScope?: boolean | undefined | undefined;
66
64
  itemType?: string | undefined | undefined;
@@ -78,11 +76,11 @@ export declare function RisaliImage(props: RisaliImageProps): Promise<import("re
78
76
  exportparts?: string | undefined | undefined;
79
77
  part?: string | undefined | undefined;
80
78
  "aria-activedescendant"?: string | undefined | undefined;
81
- "aria-atomic"?: (boolean | "true" | "false") | undefined;
79
+ "aria-atomic"?: (boolean | "false" | "true") | undefined;
82
80
  "aria-autocomplete"?: "none" | "inline" | "list" | "both" | undefined | undefined;
83
81
  "aria-braillelabel"?: string | undefined | undefined;
84
82
  "aria-brailleroledescription"?: string | undefined | undefined;
85
- "aria-busy"?: (boolean | "true" | "false") | undefined;
83
+ "aria-busy"?: (boolean | "false" | "true") | undefined;
86
84
  "aria-checked"?: boolean | "false" | "mixed" | "true" | undefined | undefined;
87
85
  "aria-colcount"?: number | undefined | undefined;
88
86
  "aria-colindex"?: number | undefined | undefined;
@@ -93,47 +91,43 @@ export declare function RisaliImage(props: RisaliImageProps): Promise<import("re
93
91
  "aria-describedby"?: string | undefined | undefined;
94
92
  "aria-description"?: string | undefined | undefined;
95
93
  "aria-details"?: string | undefined | undefined;
96
- "aria-disabled"?: (boolean | "true" | "false") | undefined;
94
+ "aria-disabled"?: (boolean | "false" | "true") | undefined;
97
95
  "aria-dropeffect"?: "none" | "copy" | "execute" | "link" | "move" | "popup" | undefined | undefined;
98
96
  "aria-errormessage"?: string | undefined | undefined;
99
- "aria-expanded"?: (boolean | "true" | "false") | undefined;
97
+ "aria-expanded"?: (boolean | "false" | "true") | undefined;
100
98
  "aria-flowto"?: string | undefined | undefined;
101
- "aria-grabbed"?: (boolean | "true" | "false") | undefined;
99
+ "aria-grabbed"?: (boolean | "false" | "true") | undefined;
102
100
  "aria-haspopup"?: boolean | "false" | "true" | "menu" | "listbox" | "tree" | "grid" | "dialog" | undefined | undefined;
103
- "aria-hidden"?: (boolean | "true" | "false") | undefined;
101
+ "aria-hidden"?: (boolean | "false" | "true") | undefined;
104
102
  "aria-invalid"?: boolean | "false" | "true" | "grammar" | "spelling" | undefined | undefined;
105
103
  "aria-keyshortcuts"?: string | undefined | undefined;
106
104
  "aria-label"?: string | undefined | undefined;
107
105
  "aria-labelledby"?: string | undefined | undefined;
108
106
  "aria-level"?: number | undefined | undefined;
109
107
  "aria-live"?: "off" | "assertive" | "polite" | undefined | undefined;
110
- "aria-modal"?: (boolean | "true" | "false") | undefined;
111
- "aria-multiline"?: (boolean | "true" | "false") | undefined;
112
- "aria-multiselectable"?: (boolean | "true" | "false") | undefined;
108
+ "aria-modal"?: (boolean | "false" | "true") | undefined;
109
+ "aria-multiline"?: (boolean | "false" | "true") | undefined;
110
+ "aria-multiselectable"?: (boolean | "false" | "true") | undefined;
113
111
  "aria-orientation"?: "horizontal" | "vertical" | undefined | undefined;
114
112
  "aria-owns"?: string | undefined | undefined;
115
113
  "aria-placeholder"?: string | undefined | undefined;
116
114
  "aria-posinset"?: number | undefined | undefined;
117
115
  "aria-pressed"?: boolean | "false" | "mixed" | "true" | undefined | undefined;
118
- "aria-readonly"?: (boolean | "true" | "false") | undefined;
116
+ "aria-readonly"?: (boolean | "false" | "true") | undefined;
119
117
  "aria-relevant"?: "additions" | "additions removals" | "additions text" | "all" | "removals" | "removals additions" | "removals text" | "text" | "text additions" | "text removals" | undefined | undefined;
120
- "aria-required"?: (boolean | "true" | "false") | undefined;
118
+ "aria-required"?: (boolean | "false" | "true") | undefined;
121
119
  "aria-roledescription"?: string | undefined | undefined;
122
120
  "aria-rowcount"?: number | undefined | undefined;
123
121
  "aria-rowindex"?: number | undefined | undefined;
124
122
  "aria-rowindextext"?: string | undefined | undefined;
125
123
  "aria-rowspan"?: number | undefined | undefined;
126
- "aria-selected"?: (boolean | "true" | "false") | undefined;
124
+ "aria-selected"?: (boolean | "false" | "true") | undefined;
127
125
  "aria-setsize"?: number | undefined | undefined;
128
126
  "aria-sort"?: "none" | "ascending" | "descending" | "other" | undefined | undefined;
129
127
  "aria-valuemax"?: number | undefined | undefined;
130
128
  "aria-valuemin"?: number | undefined | undefined;
131
129
  "aria-valuenow"?: number | undefined | undefined;
132
130
  "aria-valuetext"?: string | undefined | undefined;
133
- children?: import("react").ReactNode;
134
- dangerouslySetInnerHTML?: {
135
- __html: string | TrustedHTML;
136
- } | undefined | undefined;
137
131
  onCopy?: import("react").ClipboardEventHandler<HTMLImageElement> | undefined;
138
132
  onCopyCapture?: import("react").ClipboardEventHandler<HTMLImageElement> | undefined;
139
133
  onCut?: import("react").ClipboardEventHandler<HTMLImageElement> | undefined;
@@ -302,5 +296,11 @@ export declare function RisaliImage(props: RisaliImageProps): Promise<import("re
302
296
  onTransitionRunCapture?: import("react").TransitionEventHandler<HTMLImageElement> | undefined;
303
297
  onTransitionStart?: import("react").TransitionEventHandler<HTMLImageElement> | undefined;
304
298
  onTransitionStartCapture?: import("react").TransitionEventHandler<HTMLImageElement> | undefined;
299
+ crossOrigin?: "" | "anonymous" | "use-credentials" | undefined;
300
+ decoding?: "async" | "auto" | "sync" | undefined | undefined;
301
+ referrerPolicy?: import("react").HTMLAttributeReferrerPolicy | undefined;
302
+ sizes?: string | undefined | undefined;
303
+ srcSet?: string | undefined | undefined;
304
+ useMap?: string | undefined | undefined;
305
305
  }, HTMLElement>>;
306
306
  //# sourceMappingURL=image.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAG9D,OAAO,KAAK,EAAE,aAAa,EAAoB,MAAM,YAAY,CAAC;AAIlE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,iBAAiB,CAAC,gBAAgB,CAAC,EACnC,KAAK,GAAG,KAAK,CACd,GAAG;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAOF,wBAAsB,WAAW,CAAC,KAAK,EAAE,gBAAgB;;;;YAR/C,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0CtB"}
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAG9D,OAAO,KAAK,EAAE,aAAa,EAAoB,MAAM,YAAY,CAAC;AAIlE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,iBAAiB,CAAC,gBAAgB,CAAC,EACnC,KAAK,GAAG,KAAK,CACd,GAAG;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAOF,wBAAsB,WAAW,CAAC,KAAK,EAAE,gBAAgB;;;;;;YAR/C,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0CtB"}
package/dist/index.d.ts CHANGED
@@ -9,4 +9,18 @@ export { RisaliRichText } from "./rich-text.js";
9
9
  export type { RisaliRichTextProps } from "./rich-text.js";
10
10
  export { sanitiseRichText } from "./sanitize.js";
11
11
  export type { RisaliBlock, RisaliBlockType, RisaliContent, RisaliImageValue, RisaliStyleOverrides, } from "./types.js";
12
+ export { getRisaliBlogPosts, getRisaliBlogPost, buildBlogPostHref, } from "./blog.js";
13
+ export type { GetBlogPostOptions, GetBlogPostsOptions, RisaliBlogPost as RisaliBlogPostType, RisaliBlogPostCard, RisaliBlogPostSeo, } from "./blog.js";
14
+ export { RisaliBlogList } from "./blog-list.js";
15
+ export type { RisaliBlogListProps } from "./blog-list.js";
16
+ export { RisaliBlogPost } from "./blog-post.js";
17
+ export type { RisaliBlogPostProps } from "./blog-post.js";
18
+ export { buildBlogPostJsonLd, buildBlogPostMetadata, } from "./blog-jsonld.js";
19
+ export type { BuildBlogPostJsonLdOptions } from "./blog-jsonld.js";
20
+ export { getRisaliTrackingConfig } from "./tracking.js";
21
+ export type { RisaliTrackingConfig, GetTrackingConfigOptions, } from "./tracking.js";
22
+ export { RisaliTracking } from "./tracking-component.js";
23
+ export type { RisaliTrackingProps } from "./tracking-component.js";
24
+ export { RisaliEvent } from "./event-marker.js";
25
+ export type { RisaliEventProps } from "./event-marker.js";
12
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,YAAY,EACV,WAAW,EACX,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,YAAY,EACV,WAAW,EACX,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,IAAI,kBAAkB,EACpC,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAGnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,YAAY,EACV,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,YAAY,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAGnE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -4,4 +4,14 @@ export { RisaliText } from "./text.js";
4
4
  export { RisaliImage } from "./image.js";
5
5
  export { RisaliRichText } from "./rich-text.js";
6
6
  export { sanitiseRichText } from "./sanitize.js";
7
+ // v0.3.0 — blog module (PR 36c).
8
+ export { getRisaliBlogPosts, getRisaliBlogPost, buildBlogPostHref, } from "./blog.js";
9
+ export { RisaliBlogList } from "./blog-list.js";
10
+ export { RisaliBlogPost } from "./blog-post.js";
11
+ export { buildBlogPostJsonLd, buildBlogPostMetadata, } from "./blog-jsonld.js";
12
+ // v0.4.0 — marketing tracking (PR 38a).
13
+ export { getRisaliTrackingConfig } from "./tracking.js";
14
+ export { RisaliTracking } from "./tracking-component.js";
15
+ // v0.5.0 — event marker (PR 38b).
16
+ export { RisaliEvent } from "./event-marker.js";
7
17
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AASjD,iCAAiC;AACjC,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AAQnB,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAG1B,wCAAwC;AACxC,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAKxD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGzD,kCAAkC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAqFA,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA+CtD"}
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAuMA,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA+DtD"}
package/dist/sanitize.js CHANGED
@@ -1,7 +1,12 @@
1
1
  // Minimal HTML sanitiser for Risali rich-text blocks.
2
2
  // Pure (no DOM, no external deps) so it runs in RSC / Node / tests alike.
3
3
  // Defense-in-depth over the editor-side sanitiser planned for PR 26.
4
+ //
5
+ // Tag set grew in PR 36a (blog rendering): added h2/h3/h4, ul/ol/li,
6
+ // blockquote, pre, code, hr, figure/figcaption, img (with src
7
+ // whitelist), iframe (YouTube/Vimeo embed src whitelist).
4
8
  const ALLOWED_TAGS = new Set([
9
+ // Inline (original set)
5
10
  "span",
6
11
  "strong",
7
12
  "b",
@@ -11,10 +16,29 @@ const ALLOWED_TAGS = new Set([
11
16
  "br",
12
17
  "a",
13
18
  "p",
19
+ // Blog block tags (PR 36a)
20
+ "h2",
21
+ "h3",
22
+ "h4",
23
+ "ul",
24
+ "ol",
25
+ "li",
26
+ "blockquote",
27
+ "pre",
28
+ "code",
29
+ "hr",
30
+ "figure",
31
+ "figcaption",
32
+ "img",
33
+ "iframe",
14
34
  ]);
15
- const VOID_TAGS = new Set(["br"]);
35
+ const VOID_TAGS = new Set(["br", "hr", "img"]);
36
+ // Self-closing in the source but with allowed attributes — iframe is a
37
+ // special case because we want to render `<iframe src=... allowfullscreen></iframe>`
38
+ // even though it carries no children in practice.
39
+ const FORCED_PAIRED = new Set(["iframe"]);
16
40
  const ALLOWED_ATTRS = {
17
- a: new Set(["href"]),
41
+ a: new Set(["href", "rel"]),
18
42
  span: new Set(["style"]),
19
43
  p: new Set(["style"]),
20
44
  strong: new Set(["style"]),
@@ -22,12 +46,52 @@ const ALLOWED_ATTRS = {
22
46
  b: new Set(["style"]),
23
47
  i: new Set(["style"]),
24
48
  u: new Set(["style"]),
49
+ // Blog tags
50
+ h2: new Set(["id"]),
51
+ h3: new Set(["id"]),
52
+ h4: new Set(["id"]),
53
+ code: new Set(["class"]), // language-<lang> only
54
+ img: new Set(["src", "alt", "width", "height", "loading"]),
55
+ iframe: new Set([
56
+ "src",
57
+ "width",
58
+ "height",
59
+ "allowfullscreen",
60
+ "loading",
61
+ "title",
62
+ ]),
25
63
  };
26
- const SAFE_HREF_RE = /^(https?:\/\/|mailto:|tel:|\/)/i;
64
+ // href / a tag: http(s), mail/tel, or root-relative path.
65
+ const SAFE_HREF_RE = /^(https?:\/\/|mailto:|tel:|\/[^/])/i;
66
+ // img src: http(s) or root-relative (no protocol-relative // URLs).
67
+ const SAFE_IMG_SRC_RE = /^(https?:\/\/|\/[^/])/i;
68
+ // iframe src: only YouTube + Vimeo embed paths. Blocks everything else
69
+ // including data:, javascript:, and arbitrary 3rd-party embeds. The
70
+ // blog HTML builder (lib/blog/normalize.ts) normalises watch URLs
71
+ // into these embed forms server-side, so by the time we sanitise
72
+ // they always match.
73
+ const SAFE_IFRAME_SRC_RE = /^https?:\/\/(?:www\.)?(?:youtube\.com\/embed\/|youtube-nocookie\.com\/embed\/|player\.vimeo\.com\/video\/)[A-Za-z0-9_-][A-Za-z0-9_\-?&=/.]*$/i;
74
+ // <a rel=...> — whitelist common security/SEO tokens. Anything else
75
+ // (e.g. dns-prefetch via shenanigans) is dropped.
76
+ const ALLOWED_REL = new Set([
77
+ "noopener",
78
+ "noreferrer",
79
+ "nofollow",
80
+ "sponsored",
81
+ "ugc",
82
+ ]);
83
+ // <code class="language-xxx">.
84
+ const SAFE_CODE_CLASS_RE = /^language-[A-Za-z0-9_-]{1,30}$/;
85
+ // Numeric width/height for img/iframe.
86
+ const SAFE_NUMERIC_RE = /^\d{1,5}$/;
87
+ // loading="lazy"|"eager".
88
+ const SAFE_LOADING_RE = /^(lazy|eager)$/;
89
+ // id="…" for heading anchors — keep tame.
90
+ const SAFE_ID_RE = /^[A-Za-z][A-Za-z0-9_-]{0,80}$/;
27
91
  const STYLE_COLOR_RE = /(?:^|;)\s*color\s*:\s*(#[0-9a-fA-F]{6})\s*(?:;|$)/;
28
92
  const STYLE_FONT_SIZE_RE = /(?:^|;)\s*font-size\s*:\s*([0-9]+(?:\.[0-9]+)?)(px|rem|em)\s*(?:;|$)/i;
29
93
  const TAG_RE = /<\/?([a-zA-Z][a-zA-Z0-9]*)((?:\s+[^>]*)?)\/?>|<!--[\s\S]*?-->/g;
30
- const ATTR_RE = /([a-zA-Z][a-zA-Z0-9_-]*)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))/g;
94
+ const ATTR_RE = /([a-zA-Z][a-zA-Z0-9_-]*)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))|([a-zA-Z][a-zA-Z0-9_-]*)(?=\s|$|\/)/g;
31
95
  function sanitiseStyle(raw) {
32
96
  const out = [];
33
97
  const colorMatch = raw.match(STYLE_COLOR_RE);
@@ -44,6 +108,13 @@ function sanitiseStyle(raw) {
44
108
  }
45
109
  return out.join(";");
46
110
  }
111
+ function sanitiseRel(raw) {
112
+ const tokens = raw
113
+ .split(/\s+/)
114
+ .map((t) => t.toLowerCase())
115
+ .filter((t) => ALLOWED_REL.has(t));
116
+ return Array.from(new Set(tokens)).join(" ");
117
+ }
47
118
  function sanitiseAttrs(tag, raw) {
48
119
  const allowed = ALLOWED_ATTRS[tag];
49
120
  if (!allowed || !raw.trim())
@@ -52,20 +123,72 @@ function sanitiseAttrs(tag, raw) {
52
123
  ATTR_RE.lastIndex = 0;
53
124
  let match;
54
125
  while ((match = ATTR_RE.exec(raw)) !== null) {
55
- const name = match[1]?.toLowerCase();
126
+ // Two variants: `name="value"` (groups 1..5) or bare `name` (group 6).
127
+ const name = (match[1] ?? match[6] ?? "").toLowerCase();
56
128
  if (!name || !allowed.has(name))
57
129
  continue;
58
- const value = match[3] ?? match[4] ?? match[5] ?? "";
130
+ const rawValue = match[3] ?? match[4] ?? match[5] ?? "";
131
+ const isBoolean = !match[1] && !!match[6];
59
132
  if (name === "href") {
60
- if (SAFE_HREF_RE.test(value)) {
61
- parts.push(`href="${escapeAttr(value)}"`);
133
+ if (SAFE_HREF_RE.test(rawValue)) {
134
+ parts.push(`href="${escapeAttr(rawValue)}"`);
135
+ }
136
+ }
137
+ else if (name === "rel") {
138
+ const safe = sanitiseRel(rawValue);
139
+ if (safe)
140
+ parts.push(`rel="${escapeAttr(safe)}"`);
141
+ }
142
+ else if (name === "src" && tag === "img") {
143
+ if (SAFE_IMG_SRC_RE.test(rawValue)) {
144
+ parts.push(`src="${escapeAttr(rawValue)}"`);
145
+ }
146
+ }
147
+ else if (name === "src" && tag === "iframe") {
148
+ if (SAFE_IFRAME_SRC_RE.test(rawValue)) {
149
+ parts.push(`src="${escapeAttr(rawValue)}"`);
150
+ }
151
+ }
152
+ else if (name === "alt") {
153
+ // alt may legitimately be empty (decorative images).
154
+ parts.push(`alt="${escapeAttr(rawValue.slice(0, 500))}"`);
155
+ }
156
+ else if (name === "width" || name === "height") {
157
+ if (SAFE_NUMERIC_RE.test(rawValue)) {
158
+ parts.push(`${name}="${rawValue}"`);
159
+ }
160
+ }
161
+ else if (name === "loading") {
162
+ if (SAFE_LOADING_RE.test(rawValue)) {
163
+ parts.push(`${name}="${rawValue}"`);
164
+ }
165
+ }
166
+ else if (name === "allowfullscreen") {
167
+ // Boolean attr — emit canonical form.
168
+ parts.push("allowfullscreen");
169
+ }
170
+ else if (name === "title") {
171
+ parts.push(`title="${escapeAttr(rawValue.slice(0, 200))}"`);
172
+ }
173
+ else if (name === "id") {
174
+ if (SAFE_ID_RE.test(rawValue)) {
175
+ parts.push(`id="${escapeAttr(rawValue)}"`);
176
+ }
177
+ }
178
+ else if (name === "class" && tag === "code") {
179
+ if (SAFE_CODE_CLASS_RE.test(rawValue)) {
180
+ parts.push(`class="${escapeAttr(rawValue)}"`);
62
181
  }
63
182
  }
64
183
  else if (name === "style") {
65
- const safe = sanitiseStyle(value);
184
+ const safe = sanitiseStyle(rawValue);
66
185
  if (safe)
67
186
  parts.push(`style="${escapeAttr(safe)}"`);
68
187
  }
188
+ // Boolean attrs without explicit value (allowfullscreen alone).
189
+ if (isBoolean && name === "allowfullscreen") {
190
+ // Already emitted above.
191
+ }
69
192
  }
70
193
  return parts.length ? " " + parts.join(" ") : "";
71
194
  }
@@ -82,6 +205,10 @@ export function sanitiseRichText(input) {
82
205
  const stack = [];
83
206
  const pieces = [];
84
207
  let lastIndex = 0;
208
+ // Inside <pre><code> we want raw text rendering (no further tag
209
+ // matching). We don't fully suspend the parser — that would break
210
+ // nested allowed tags in rare edge cases — but we leave the
211
+ // children-as-text fallback to the surrounding escape pass.
85
212
  TAG_RE.lastIndex = 0;
86
213
  let match;
87
214
  while ((match = TAG_RE.exec(input)) !== null) {
@@ -106,11 +233,24 @@ export function sanitiseRichText(input) {
106
233
  }
107
234
  }
108
235
  else if (VOID_TAGS.has(tag)) {
109
- pieces.push(`<${tag}/>`);
236
+ const attrs = sanitiseAttrs(tag, match[2] || "");
237
+ // img requires a valid src; if sanitiseAttrs dropped it, the
238
+ // element renders as `<img alt="">` which is benign.
239
+ pieces.push(`<${tag}${attrs}/>`);
110
240
  }
111
241
  else {
112
242
  const attrs = sanitiseAttrs(tag, match[2] || "");
243
+ // iframe with no src is useless and could confuse readers; drop.
244
+ if (tag === "iframe" && !/\ssrc=/.test(attrs))
245
+ continue;
113
246
  pieces.push(`<${tag}${attrs}>`);
247
+ if (FORCED_PAIRED.has(tag)) {
248
+ // Always close iframe — the source may have written
249
+ // `<iframe ...></iframe>` (good) or `<iframe ... />` (sloppy
250
+ // but accepted here). We push the open tag and rely on the
251
+ // close tag in the source. If none arrives, the trailing
252
+ // stack drain below closes it for us.
253
+ }
114
254
  stack.push(tag);
115
255
  }
116
256
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"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"}