barakopress 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +59 -0
  2. package/dist/config.d.ts +5 -1
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +2 -0
  5. package/dist/config.js.map +1 -1
  6. package/dist/delivery.d.ts +14 -0
  7. package/dist/delivery.d.ts.map +1 -1
  8. package/dist/delivery.js +19 -0
  9. package/dist/delivery.js.map +1 -1
  10. package/dist/index.d.ts +8 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/reading-time.d.ts +10 -0
  15. package/dist/reading-time.d.ts.map +1 -0
  16. package/dist/reading-time.js +72 -0
  17. package/dist/reading-time.js.map +1 -0
  18. package/dist/related.d.ts +22 -0
  19. package/dist/related.d.ts.map +1 -0
  20. package/dist/related.js +47 -0
  21. package/dist/related.js.map +1 -0
  22. package/dist/screens/archive.js +2 -2
  23. package/dist/screens/archive.js.map +1 -1
  24. package/dist/screens/blog-index.d.ts.map +1 -1
  25. package/dist/screens/blog-index.js +6 -1
  26. package/dist/screens/blog-index.js.map +1 -1
  27. package/dist/screens/blog-post.d.ts.map +1 -1
  28. package/dist/screens/blog-post.js +7 -2
  29. package/dist/screens/blog-post.js.map +1 -1
  30. package/dist/screens/post-view.d.ts +9 -2
  31. package/dist/screens/post-view.d.ts.map +1 -1
  32. package/dist/screens/post-view.js +173 -6
  33. package/dist/screens/post-view.js.map +1 -1
  34. package/dist/theme.d.ts +68 -0
  35. package/dist/theme.d.ts.map +1 -0
  36. package/dist/theme.js +128 -0
  37. package/dist/theme.js.map +1 -0
  38. package/package.json +3 -1
  39. package/src/config.ts +12 -1
  40. package/src/delivery.ts +53 -0
  41. package/src/index.ts +23 -2
  42. package/src/reading-time.ts +78 -0
  43. package/src/related.ts +81 -0
  44. package/src/screens/archive.tsx +2 -2
  45. package/src/screens/blog-index.tsx +7 -2
  46. package/src/screens/blog-post.tsx +9 -2
  47. package/src/screens/post-view.tsx +392 -53
  48. package/src/theme.ts +203 -0
@@ -1,19 +1,186 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Link from "next/link";
3
3
  import { formatDate } from "../cms.js";
4
4
  import { renderMarkdown } from "../markdown.js";
5
+ import { initials, readingMinutes } from "../reading-time.js";
6
+ import { proseCss, relatedCss } from "../theme.js";
5
7
  /*
6
- * The rendering, shared by the static screen and the preview screen.
8
+ * The post page, shared by the static screen and the preview screen.
7
9
  *
8
10
  * It is its own component because reading `searchParams` forces a route to render dynamically,
9
11
  * and a site built with `output: "export"` is refused outright for it. Preview needs the query
10
12
  * string; a published post does not. Splitting the fetch from the render is what lets one set of
11
13
  * markup serve a static site and a server-rendered one.
12
14
  *
13
- * Every link is built from `config.routes`, so a site that mounts posts at /writing gets /writing
14
- * links here, in the feed and in the sitemap, instead of three files disagreeing.
15
+ * Every link is built from `config.routes`, and every colour, face and radius from `config.theme`,
16
+ * so a site that mounts posts at /writing gets /writing links here and in the feed, and a site
17
+ * with its own palette gets its own palette without touching this file.
18
+ *
19
+ * The styles are inline rather than classes against `barakopress/styles.css`. That stylesheet is
20
+ * opt-in, barakocms.com never imported it, and the result was this screen rendering as an
21
+ * unstyled article at x=0 on a blank page, which is why that site stopped using the screen at all.
22
+ * A screen whose look depends on an import the consumer might not make is a screen that renders
23
+ * broken for somebody. The one exception is the rendered body, which is a string of HTML that
24
+ * inline styles cannot reach, so it gets a generated stylesheet built from the same tokens.
15
25
  */
16
- export function PostView({ config, post, preview = false, }) {
17
- return (_jsxs("article", { children: [preview && (_jsx("div", { className: "notice preview-banner", children: "Preview. This is how the post will look. It is not published, and it is served uncached so nothing here reaches another reader." })), _jsx("p", { className: "meta", children: _jsx(Link, { href: "/", children: "Back" }) }), _jsx("h1", { children: post.title }), _jsxs("p", { className: "meta", children: [post.publishedAt && (_jsx("time", { dateTime: post.publishedAt, children: formatDate(config, post.publishedAt) })), post.author && config.routes.author && (_jsxs(_Fragment, { children: [" by ", _jsx(Link, { href: `${config.routes.author}/${post.author.slug}`, children: post.author.name })] })), post.author && !config.routes.author && _jsx(_Fragment, { children: ` by ${post.author.name}` }), post.category && config.routes.category && (_jsxs(_Fragment, { children: [" in ", _jsx(Link, { href: `${config.routes.category}/${post.category.slug}`, children: post.category.name })] }))] }), post.coverImage && (_jsx("img", { className: "cover", src: post.coverImage, alt: post.coverImageAlt ?? "" })), _jsx("div", { className: "prose", dangerouslySetInnerHTML: { __html: renderMarkdown(post.body) } }), post.tags.length > 0 && (_jsx("p", { className: "tags", children: post.tags.map((t) => (_jsx("span", { className: "tag", children: t }, t))) }))] }));
26
+ /** Scopes the generated body stylesheet. Prefixed so it cannot collide with a consumer's own `.prose`. */
27
+ const PROSE_CLASS = "bp-prose";
28
+ /** Same reason as PROSE_CLASS: a hover rule needs a selector, and a style attribute has none. */
29
+ const RELATED_CLASS = "bp-related-card";
30
+ export function PostView({ config, post, preview = false, headerBackdrop, beforeBody, afterBody, related = [], }) {
31
+ const t = config.theme;
32
+ const c = t.colors;
33
+ const gutter = t.layout.gutter;
34
+ const backHref = config.routes.post || "/";
35
+ // "/writing" reads as WRITING. The route is the only thing that knows what the index is called,
36
+ // and asking for a label as well would be a second place to keep the same word.
37
+ const backLabel = backHref.split("/").filter(Boolean).pop() ?? "Home";
38
+ const minutes = readingMinutes(post.body);
39
+ const monogram = post.author ? initials(post.author.name) : "";
40
+ const band = { padding: `0 ${gutter}` };
41
+ const meta = { fontFamily: t.fonts.mono, fontSize: "12.5px", color: c.muted };
42
+ return (_jsxs("div", { style: { background: c.pageBg, color: c.ink, fontFamily: t.fonts.body }, children: [_jsx("style", { dangerouslySetInnerHTML: {
43
+ __html: proseCss(t, PROSE_CLASS) + relatedCss(t, RELATED_CLASS),
44
+ } }), preview && (_jsx("div", { style: { ...band, paddingTop: "16px", background: c.surface }, children: _jsx("div", { style: {
45
+ maxWidth: t.layout.prose,
46
+ margin: "0 auto",
47
+ padding: "14px 18px",
48
+ borderRadius: t.radii.panel,
49
+ background: c.accentTint,
50
+ border: `1px solid ${c.accentTintBorder}`,
51
+ color: c.accentInk,
52
+ fontSize: "14px",
53
+ lineHeight: 1.6,
54
+ }, children: "Preview. This is how the post will look. It is not published, and it is served uncached so nothing here reaches another reader." }) })), _jsxs("header", { style: {
55
+ ...band,
56
+ paddingTop: "64px",
57
+ paddingBottom: "56px",
58
+ background: c.surface,
59
+ borderBottom: `1px solid ${c.hairline}`,
60
+ position: "relative",
61
+ overflow: "hidden",
62
+ }, children: [headerBackdrop && (_jsx("div", { style: { position: "absolute", inset: 0, pointerEvents: "none" }, "aria-hidden": true, children: headerBackdrop })), _jsxs("div", { style: { maxWidth: t.layout.prose, margin: "0 auto", position: "relative" }, children: [_jsxs(Link, { href: backHref, style: {
63
+ display: "inline-flex",
64
+ alignItems: "center",
65
+ gap: "8px",
66
+ fontFamily: t.fonts.mono,
67
+ fontSize: "11.5px",
68
+ letterSpacing: ".14em",
69
+ textTransform: "uppercase",
70
+ color: c.muted,
71
+ }, children: [_jsx("svg", { viewBox: "0 0 16 16", width: "12", height: "12", fill: "currentColor", "aria-hidden": true, children: _jsx("path", { d: "M7.2 2.3 1.5 8l5.7 5.7 1.1-1.1L4.7 8.8H14.5V7.2H4.7l3.6-3.8z" }) }), backLabel] }), _jsx("h1", { style: {
72
+ margin: "22px 0 0",
73
+ fontFamily: t.fonts.heading,
74
+ fontWeight: 600,
75
+ fontSize: "clamp(34px, 4.6vw, 58px)",
76
+ lineHeight: 1.06,
77
+ letterSpacing: "-.038em",
78
+ color: c.ink,
79
+ textWrap: "balance",
80
+ }, children: post.title }), post.excerpt && (_jsx("p", { style: {
81
+ margin: "24px 0 0",
82
+ maxWidth: "60ch",
83
+ fontSize: "19.5px",
84
+ lineHeight: 1.6,
85
+ color: c.secondaryInk,
86
+ textWrap: "pretty",
87
+ }, children: post.excerpt })), _jsxs("div", { style: {
88
+ marginTop: "30px",
89
+ display: "flex",
90
+ flexWrap: "wrap",
91
+ alignItems: "center",
92
+ gap: "10px 26px",
93
+ fontSize: "14px",
94
+ color: c.muted,
95
+ }, children: [post.author && (_jsxs("span", { style: { display: "flex", alignItems: "center", gap: "9px" }, children: [_jsx("span", { "aria-hidden": true, style: {
96
+ width: "28px",
97
+ height: "28px",
98
+ flexShrink: 0,
99
+ borderRadius: t.radii.pill,
100
+ background: c.accentTint,
101
+ color: c.accentInk,
102
+ display: "flex",
103
+ alignItems: "center",
104
+ justifyContent: "center",
105
+ fontFamily: t.fonts.heading,
106
+ fontSize: "12px",
107
+ fontWeight: 600,
108
+ }, children: monogram }), "by", " ", config.routes.author ? (_jsx(Link, { href: `${config.routes.author}/${post.author.slug}`, style: { color: c.ink, fontWeight: 600 }, children: post.author.name })) : (_jsx("span", { style: { color: c.ink, fontWeight: 600 }, children: post.author.name }))] })), post.publishedAt && (_jsx("time", { dateTime: post.publishedAt, style: meta, children: formatDate(config, post.publishedAt) })), _jsxs("span", { style: meta, children: [minutes, " min read"] }), post.category && config.routes.category && (_jsx(Link, { href: `${config.routes.category}/${post.category.slug}`, style: meta, children: post.category.name })), post.tags.map((tag) => (_jsx("span", { style: {
109
+ display: "inline-flex",
110
+ alignItems: "center",
111
+ padding: "5px 11px",
112
+ borderRadius: t.radii.pill,
113
+ background: c.accentTint,
114
+ color: c.accentInk,
115
+ fontFamily: t.fonts.mono,
116
+ fontSize: "11px",
117
+ letterSpacing: ".1em",
118
+ textTransform: "uppercase",
119
+ }, children: tag }, tag)))] })] })] }), beforeBody && (_jsx("section", { style: { ...band, paddingTop: "56px", paddingBottom: "8px" }, children: _jsx("div", { style: { maxWidth: t.layout.wide, margin: "0 auto" }, children: beforeBody }) })), _jsx("article", { style: { ...band, paddingTop: "48px", paddingBottom: "72px" }, children: _jsxs("div", { style: { maxWidth: t.layout.prose, margin: "0 auto" }, children: [post.coverImage && (_jsx("img", { src: post.coverImage, alt: post.coverImageAlt ?? "", style: {
120
+ width: "100%",
121
+ marginBottom: "36px",
122
+ borderRadius: t.radii.panel,
123
+ border: `1px solid ${c.hairline}`,
124
+ } })), _jsx("div", { className: PROSE_CLASS, dangerouslySetInnerHTML: { __html: renderMarkdown(post.body) } }), afterBody] }) }), related.length > 0 && (_jsx("section", { style: {
125
+ ...band,
126
+ paddingTop: "52px",
127
+ paddingBottom: "60px",
128
+ background: c.surface,
129
+ borderTop: `1px solid ${c.hairline}`,
130
+ }, children: _jsxs("div", { style: { maxWidth: t.layout.wide, margin: "0 auto" }, children: [_jsxs("div", { style: {
131
+ display: "flex",
132
+ flexWrap: "wrap",
133
+ alignItems: "baseline",
134
+ gap: "12px",
135
+ }, children: [_jsx("h2", { style: {
136
+ margin: 0,
137
+ fontFamily: t.fonts.heading,
138
+ fontSize: "24px",
139
+ fontWeight: 600,
140
+ letterSpacing: "-.025em",
141
+ color: c.ink,
142
+ }, children: "Related" }), _jsx("span", { style: {
143
+ fontFamily: t.fonts.mono,
144
+ fontSize: "11.5px",
145
+ color: c.muted,
146
+ }, children: "cosine similarity, computed on load, not curated" })] }), _jsx("div", { style: {
147
+ marginTop: "20px",
148
+ display: "grid",
149
+ gridTemplateColumns: "repeat(auto-fit, minmax(min(260px, 100%), 1fr))",
150
+ gap: "14px",
151
+ }, children: related.map((r) => (_jsxs(Link, { href: `${config.routes.post}/${r.slug}`, className: RELATED_CLASS, style: {
152
+ display: "flex",
153
+ flexDirection: "column",
154
+ gap: "10px",
155
+ minWidth: 0,
156
+ padding: "20px",
157
+ borderRadius: t.radii.panel,
158
+ border: `1px solid ${c.hairline}`,
159
+ background: c.pageBg,
160
+ }, children: [_jsxs("span", { style: {
161
+ display: "flex",
162
+ alignItems: "center",
163
+ gap: "10px",
164
+ fontFamily: t.fonts.mono,
165
+ fontSize: "11px",
166
+ color: c.muted,
167
+ }, children: [_jsx("span", { style: {
168
+ padding: "3px 8px",
169
+ borderRadius: t.radii.pill,
170
+ background: c.accentTint,
171
+ color: c.accentInk,
172
+ fontVariantNumeric: "tabular-nums",
173
+ }, children: r.score.toFixed(4) }), r.publishedAt && formatDate(config, r.publishedAt)] }), _jsx("span", { style: {
174
+ fontFamily: t.fonts.heading,
175
+ fontSize: "17px",
176
+ fontWeight: 600,
177
+ lineHeight: 1.3,
178
+ letterSpacing: "-.02em",
179
+ color: c.ink,
180
+ }, children: r.title }), r.excerpt && (_jsx("span", { style: {
181
+ fontSize: "14px",
182
+ lineHeight: 1.6,
183
+ color: c.secondaryInk,
184
+ }, children: r.excerpt }))] }, r.slug))) })] }) }))] }));
18
185
  }
19
186
  //# sourceMappingURL=post-view.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"post-view.js","sourceRoot":"","sources":["../../src/screens/post-view.tsx"],"names":[],"mappings":";AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,UAAU,EAAa,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,QAAQ,CAAC,EACrB,MAAM,EACN,IAAI,EACJ,OAAO,GAAG,KAAK,GAKlB;IACG,OAAO,CACH,8BACK,OAAO,IAAI,CACR,cAAK,SAAS,EAAC,uBAAuB,gJAGhC,CACT,EAED,YAAG,SAAS,EAAC,MAAM,YACf,KAAC,IAAI,IAAC,IAAI,EAAC,GAAG,qBAAY,GAC1B,EAEJ,uBAAK,IAAI,CAAC,KAAK,GAAM,EAErB,aAAG,SAAS,EAAC,MAAM,aACd,IAAI,CAAC,WAAW,IAAI,CACjB,eAAM,QAAQ,EAAE,IAAI,CAAC,WAAW,YAAG,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,GAAQ,CAClF,EACA,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,CACpC,8BACK,MAAM,EACP,KAAC,IAAI,IAAC,IAAI,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,YACpD,IAAI,CAAC,MAAM,CAAC,IAAI,GACd,IACR,CACN,EACA,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,4BAAG,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAI,EACxE,IAAI,CAAC,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,IAAI,CACxC,8BACK,MAAM,EACP,KAAC,IAAI,IAAC,IAAI,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,YACxD,IAAI,CAAC,QAAQ,CAAC,IAAI,GAChB,IACR,CACN,IACD,EAEH,IAAI,CAAC,UAAU,IAAI,CAIhB,cAAK,SAAS,EAAC,OAAO,EAAC,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,EAAE,IAAI,CAAC,aAAa,IAAI,EAAE,GAAI,CACjF,EAED,cAAK,SAAS,EAAC,OAAO,EAAC,uBAAuB,EAAE,EAAE,MAAM,EAAE,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAI,EAExF,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CACrB,YAAG,SAAS,EAAC,MAAM,YACd,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAClB,eAAc,SAAS,EAAC,KAAK,YACxB,CAAC,IADK,CAAC,CAEL,CACV,CAAC,GACF,CACP,IACK,CACb,CAAC;AACN,CAAC"}
1
+ {"version":3,"file":"post-view.js","sourceRoot":"","sources":["../../src/screens/post-view.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ export interface ThemeColors {
2
+ /** The page behind the bands. */
3
+ pageBg: string;
4
+ /** Cards, panels, and the bands that sit above the page. */
5
+ surface: string;
6
+ /** Headings and anything that has to read as primary. */
7
+ ink: string;
8
+ /** Body copy in the prose column, which is deliberately lighter than a heading. */
9
+ proseInk: string;
10
+ /** Supporting copy outside the prose column. */
11
+ secondaryInk: string;
12
+ /** Meta: dates, labels, captions. */
13
+ muted: string;
14
+ hairline: string;
15
+ accent: string;
16
+ accentHover: string;
17
+ /** Accent dark enough to carry small text on the tint. */
18
+ accentInk: string;
19
+ accentTint: string;
20
+ accentTintBorder: string;
21
+ accentTintBorderStrong: string;
22
+ /** Code panels and the footer band. */
23
+ darkPanel: string;
24
+ /** Controls sitting on the dark panel. */
25
+ darkPanelChrome: string;
26
+ darkPanelInk: string;
27
+ darkPanelAccent: string;
28
+ codeGreen: string;
29
+ success: string;
30
+ }
31
+ export interface ThemeFonts {
32
+ /** Headings and the wordmark. */
33
+ heading: string;
34
+ /** Body and UI. */
35
+ body: string;
36
+ /** Code, meta and labels. The distinction is load bearing: every machine-produced value is mono. */
37
+ mono: string;
38
+ }
39
+ export interface ThemeRadii {
40
+ panel: string;
41
+ control: string;
42
+ pill: string;
43
+ }
44
+ export interface ThemeLayout {
45
+ /** The reading column. */
46
+ prose: string;
47
+ /** Bands that deliberately break out of the reading column. */
48
+ wide: string;
49
+ /** Side padding on every band. */
50
+ gutter: string;
51
+ }
52
+ export interface PressTheme {
53
+ colors: ThemeColors;
54
+ fonts: ThemeFonts;
55
+ radii: ThemeRadii;
56
+ layout: ThemeLayout;
57
+ }
58
+ export type PressThemeInput = {
59
+ colors?: Partial<ThemeColors>;
60
+ fonts?: Partial<ThemeFonts>;
61
+ radii?: Partial<ThemeRadii>;
62
+ layout?: Partial<ThemeLayout>;
63
+ };
64
+ export declare const DEFAULT_THEME: PressTheme;
65
+ export declare function resolveTheme(input: PressThemeInput | undefined): PressTheme;
66
+ export declare function proseCss(theme: PressTheme, scope: string): string;
67
+ export declare function relatedCss(theme: PressTheme, scope: string): string;
68
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,WAAW;IACxB,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC;IACZ,mFAAmF;IACnF,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACvB,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,oGAAoG;IACpG,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IACxB,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACvB,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,WAAW,CAAC;CACvB;AAED,MAAM,MAAM,eAAe,GAAG;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;CACjC,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,UAqC3B,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,GAAG,UAAU,CAO3E;AAgBD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAiCjE;AAUD,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAUnE"}
package/dist/theme.js ADDED
@@ -0,0 +1,128 @@
1
+ /*
2
+ * What the post page looks like, as configuration.
3
+ *
4
+ * The seam in config.ts covers data: which type holds posts, what the fields are called, where the
5
+ * routes are mounted. It says nothing about appearance, and that gap is why barakocms.com does not
6
+ * use `PostView` at all. It reimplemented the whole page inline, because the screen came with a
7
+ * stylesheet the site never imports and rendered on a blank page. The second consumer would have
8
+ * done the same, and two forks of a screen is the thing this package exists to prevent.
9
+ *
10
+ * So the look is tokens, and the screens style themselves from them inline. Inline rather than a
11
+ * stylesheet on purpose: `barakopress/styles.css` is opt-in, and a screen whose look depends on an
12
+ * import the consumer may not make is a screen that renders unstyled for somebody. Inline styles
13
+ * arrive with the markup and cannot be forgotten.
14
+ *
15
+ * The defaults are the barakoCMS palette, the same values styles.css carries, so a site that wants
16
+ * the house look passes nothing.
17
+ */
18
+ export const DEFAULT_THEME = {
19
+ colors: {
20
+ pageBg: "#FAFAFC",
21
+ surface: "#FFFFFF",
22
+ ink: "#101223",
23
+ proseInk: "#2B2E45",
24
+ secondaryInk: "#4A4E66",
25
+ muted: "#63687D",
26
+ hairline: "#E7E8F1",
27
+ accent: "#5A46D6",
28
+ accentHover: "#4A38C0",
29
+ accentInk: "#4034A8",
30
+ accentTint: "#EEEBFD",
31
+ accentTintBorder: "#DED8FB",
32
+ accentTintBorderStrong: "#C9C1F5",
33
+ darkPanel: "#101223",
34
+ darkPanelChrome: "#2A2C45",
35
+ darkPanelInk: "#DED8FB",
36
+ darkPanelAccent: "#A99BF7",
37
+ codeGreen: "#7BE0C4",
38
+ success: "#0B7A6B",
39
+ },
40
+ fonts: {
41
+ heading: "'Sora', ui-sans-serif, system-ui, sans-serif",
42
+ body: "'Manrope', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif",
43
+ mono: "'JetBrains Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace",
44
+ },
45
+ radii: {
46
+ panel: "14px",
47
+ control: "11px",
48
+ pill: "999px",
49
+ },
50
+ layout: {
51
+ prose: "780px",
52
+ wide: "1160px",
53
+ gutter: "40px",
54
+ },
55
+ };
56
+ export function resolveTheme(input) {
57
+ return {
58
+ colors: { ...DEFAULT_THEME.colors, ...input?.colors },
59
+ fonts: { ...DEFAULT_THEME.fonts, ...input?.fonts },
60
+ radii: { ...DEFAULT_THEME.radii, ...input?.radii },
61
+ layout: { ...DEFAULT_THEME.layout, ...input?.layout },
62
+ };
63
+ }
64
+ /*
65
+ * A rendered body is a string of HTML, so it is the one part of a screen inline styles cannot
66
+ * reach. This is the stylesheet for it, generated from the same tokens so the prose column and the
67
+ * markup around it cannot drift.
68
+ *
69
+ * Values are interpolated into CSS, and a token is a developer-supplied string rather than a
70
+ * constant, so they are stripped of the two characters that could close the element early. Nobody
71
+ * is expected to put a bracket in a colour; this is here so that a config that somehow carries one
72
+ * produces a broken rule instead of an injected tag.
73
+ */
74
+ function css(value) {
75
+ return value.replace(/[<>]/g, "");
76
+ }
77
+ export function proseCss(theme, scope) {
78
+ const c = theme.colors;
79
+ const f = theme.fonts;
80
+ const s = `.${scope}`;
81
+ return [
82
+ `${s}{font-family:${css(f.body)};font-size:17.5px;line-height:1.75;color:${css(c.proseInk)};text-wrap:pretty}`,
83
+ `${s} > *:first-child{margin-top:0}`,
84
+ `${s} p{margin:18px 0 0}`,
85
+ `${s} h2{margin:56px 0 0;font-family:${css(f.heading)};font-weight:600;font-size:clamp(24px,2.8vw,32px);line-height:1.15;letter-spacing:-.03em;color:${css(c.ink)};text-wrap:balance}`,
86
+ `${s} h3{margin:38px 0 0;font-family:${css(f.heading)};font-weight:600;font-size:21px;line-height:1.25;letter-spacing:-.025em;color:${css(c.ink)}}`,
87
+ `${s} h4{margin:30px 0 0;font-family:${css(f.heading)};font-weight:600;font-size:17px;color:${css(c.ink)}}`,
88
+ `${s} ul,${s} ol{margin:18px 0 0;padding-left:1.4rem}`,
89
+ `${s} ul{list-style:disc}`,
90
+ `${s} ol{list-style:decimal}`,
91
+ `${s} li{margin:.5rem 0}`,
92
+ `${s} li > ul,${s} li > ol{margin:.4rem 0}`,
93
+ `${s} a{color:${css(c.accent)};font-weight:600;text-decoration:underline;text-underline-offset:2px}`,
94
+ `${s} a:hover{color:${css(c.accentHover)}}`,
95
+ `${s} strong{color:${css(c.ink)};font-weight:700}`,
96
+ // No background on inline code. The handoff calls for it, and a tinted chip inside a
97
+ // 17.5px line is the thing that makes body copy look like documentation.
98
+ `${s} code{font-family:${css(f.mono)};font-size:16px;color:${css(c.ink)}}`,
99
+ `${s} pre{margin:30px 0 0;padding:18px 16px;border-radius:${css(theme.radii.panel)};background:${css(c.darkPanel)};color:${css(c.darkPanelInk)};font-family:${css(f.mono)};font-size:13.5px;line-height:1.85;overflow-x:auto}`,
100
+ `${s} pre code{font-size:inherit;color:inherit}`,
101
+ `${s} blockquote{margin:24px 0 0;padding:2px 0 2px 18px;border-left:2px solid ${css(c.accentTintBorder)};color:${css(c.secondaryInk)}}`,
102
+ `${s} img{max-width:100%;height:auto;border-radius:${css(theme.radii.panel)};border:1px solid ${css(c.hairline)}}`,
103
+ `${s} hr{margin:40px 0 0;border:0;border-top:1px solid ${css(c.hairline)}}`,
104
+ // Its own scroller, so a wide table cannot make the page scroll sideways.
105
+ `${s} table{display:block;overflow-x:auto;width:100%;margin:24px 0 0;border-collapse:collapse;font-size:14.5px}`,
106
+ `${s} th,${s} td{border:1px solid ${css(c.hairline)};padding:.55rem .75rem;text-align:left;vertical-align:top}`,
107
+ `${s} th{background:${css(c.pageBg)};color:${css(c.ink)};font-weight:700;white-space:nowrap}`,
108
+ ].join("");
109
+ }
110
+ /*
111
+ * Hover and focus for the related cards.
112
+ *
113
+ * These cannot be inline: a style attribute has no `:hover`, no `:focus-visible` and no media
114
+ * query. It is the same generated-from-tokens trick the prose stylesheet uses, for the same
115
+ * reason, and it stays small on purpose. The handoff's motion contract is deliberately minimal,
116
+ * and the lift is the only movement on this part of the page.
117
+ */
118
+ export function relatedCss(theme, scope) {
119
+ const c = theme.colors;
120
+ const s = `.${scope}`;
121
+ return [
122
+ `${s}{transition:border-color .2s ease,transform .2s ease}`,
123
+ `${s}:hover{border-color:${css(c.accent)};transform:translateY(-2px)}`,
124
+ `${s}:focus-visible{outline:2px solid ${css(c.accent)};outline-offset:2px}`,
125
+ `@media (prefers-reduced-motion: reduce){${s}{transition:none}${s}:hover{transform:none}}`,
126
+ ].join("");
127
+ }
128
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "barakopress",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A configurable blog engine for barakoCMS: Next.js server components, cached until the CMS says otherwise.",
5
5
  "license": "MIT",
6
6
  "author": "Arnel Robles",
@@ -18,6 +18,8 @@
18
18
  "files": [
19
19
  "dist",
20
20
  "src",
21
+ "!src/**/*.test.ts",
22
+ "!src/**/*.test.tsx",
21
23
  "README.md",
22
24
  "LICENSE"
23
25
  ],
package/src/config.ts CHANGED
@@ -14,6 +14,8 @@
14
14
  * `createBlogIndex(config)` and exports the result. One import, one call, full control.
15
15
  */
16
16
 
17
+ import { resolveTheme, type PressTheme, type PressThemeInput } from "./theme.js";
18
+
17
19
  export interface TypeNames {
18
20
  /** The content type holding posts. */
19
21
  post: string;
@@ -84,6 +86,8 @@ export interface PressConfig {
84
86
  cmsUrl: string;
85
87
  /** Tenant slug, for a multi-tenant deployment. */
86
88
  tenant?: string;
89
+ /** What the screens look like. See theme.ts for why appearance is config and not a stylesheet. */
90
+ theme: PressTheme;
87
91
  }
88
92
 
89
93
  export type PressConfigInput = {
@@ -92,7 +96,13 @@ export type PressConfigInput = {
92
96
  routes?: Partial<RouteMap>;
93
97
  site?: Partial<SiteIdentity>;
94
98
  pageSizes?: Partial<PageSizes>;
95
- } & Partial<Omit<PressConfig, "types" | "fields" | "routes" | "site" | "pageSizes">>;
99
+ /*
100
+ * Nested partials, so a site overriding one colour keeps the other seventeen. A flat
101
+ * Partial<PressTheme> would take the whole colours object or none of it, which in practice
102
+ * means every consumer pastes the full palette to change an accent.
103
+ */
104
+ theme?: PressThemeInput;
105
+ } & Partial<Omit<PressConfig, "types" | "fields" | "routes" | "site" | "pageSizes" | "theme">>;
96
106
 
97
107
  /** The `blog` blueprint, which is what `POST /api/content-types/blueprints/blog` creates. */
98
108
  const BLOG_BLUEPRINT: Pick<PressConfig, "types" | "fields"> = {
@@ -140,6 +150,7 @@ export function defineConfig(input: PressConfigInput & { site: SiteIdentity }):
140
150
  locale: input.locale ?? "en-GB",
141
151
  cmsUrl: trimSlash(input.cmsUrl ?? process.env.CMS_URL ?? "http://localhost:5005"),
142
152
  tenant: input.tenant ?? process.env.CMS_TENANT ?? undefined,
153
+ theme: resolveTheme(input.theme),
143
154
  };
144
155
  }
145
156
 
package/src/delivery.ts CHANGED
@@ -132,3 +132,56 @@ export async function bySlugPreview(
132
132
  `/api/public/${encodeURIComponent(type)}/${encodeURIComponent(slug)}?preview=${encodeURIComponent(token)}`,
133
133
  );
134
134
  }
135
+
136
+ /*
137
+ * Semantic search, from the optional BarakoCMS.AI module.
138
+ *
139
+ * Optional is the whole design constraint. A CMS without the module answers 404 on this route, a
140
+ * type that is not publicly deliverable answers 404, and a module installed but not enabled
141
+ * answers 200 with an empty list, because it ships inert until `Ai:Enabled`. All three are normal
142
+ * and none of them is an error, so this returns an empty list for every one of them rather than
143
+ * throwing. A post page must not fail because a module the site never installed is not there.
144
+ */
145
+ export interface SemanticHit {
146
+ contentType: string;
147
+ /** Nullable in the API. A hit with no slug is not linkable, so callers drop it. */
148
+ slug?: string;
149
+ title: string;
150
+ /** Cosine similarity, rounded to 4 decimals by the API. Anything under 0.4 is already dropped. */
151
+ score: number;
152
+ }
153
+
154
+ export interface SemanticResponse {
155
+ results: SemanticHit[];
156
+ count: number;
157
+ query: string;
158
+ }
159
+
160
+ /** The API clamps this itself. It is repeated here so a caller asking for 50 sends a legal request. */
161
+ const MAX_SEMANTIC_LIMIT = 20;
162
+
163
+ export async function semantic(
164
+ config: PressConfig,
165
+ type: string,
166
+ query: string,
167
+ limit: number,
168
+ ): Promise<SemanticHit[]> {
169
+ const q = query.trim();
170
+ // The API answers empty under two characters. Not spending a request to be told that.
171
+ if (q.length < 2) return [];
172
+
173
+ const params = new URLSearchParams({
174
+ q,
175
+ limit: String(Math.max(1, Math.min(limit, MAX_SEMANTIC_LIMIT))),
176
+ });
177
+
178
+ try {
179
+ const res = await get<SemanticResponse>(
180
+ config,
181
+ `/api/public/${encodeURIComponent(type)}/semantic?${params}`,
182
+ );
183
+ return Array.isArray(res.results) ? res.results : [];
184
+ } catch {
185
+ return [];
186
+ }
187
+ }
package/src/index.ts CHANGED
@@ -26,6 +26,16 @@
26
26
  */
27
27
 
28
28
  export { defineConfig, includesFor } from "./config.js";
29
+ export { DEFAULT_THEME, resolveTheme, proseCss, relatedCss } from "./theme.js";
30
+ export type {
31
+ PressTheme,
32
+ PressThemeInput,
33
+ ThemeColors,
34
+ ThemeFonts,
35
+ ThemeRadii,
36
+ ThemeLayout,
37
+ } from "./theme.js";
38
+ export { readingMinutes, initials } from "./reading-time.js";
29
39
  export type {
30
40
  PressConfig,
31
41
  PressConfigInput,
@@ -36,8 +46,18 @@ export type {
36
46
  SiteIdentity,
37
47
  } from "./config.js";
38
48
 
39
- export { list, bySlug, bySlugPreview } from "./delivery.js";
40
- export type { PublicContent, Seo, ListOptions, Paged } from "./delivery.js";
49
+ export { list, bySlug, bySlugPreview, semantic } from "./delivery.js";
50
+ export type {
51
+ PublicContent,
52
+ Seo,
53
+ ListOptions,
54
+ Paged,
55
+ SemanticHit,
56
+ SemanticResponse,
57
+ } from "./delivery.js";
58
+
59
+ export { listRelated, pickRelated } from "./related.js";
60
+ export type { RelatedPost } from "./related.js";
41
61
 
42
62
  export {
43
63
  listPosts,
@@ -61,6 +81,7 @@ export {
61
81
  } from "./screens/blog-post.js";
62
82
  export { createArchive, createArchiveStaticParams } from "./screens/archive.js";
63
83
  export { PostView } from "./screens/post-view.js";
84
+ export type { PostViewProps } from "./screens/post-view.js";
64
85
 
65
86
  export { createRevalidateRoute } from "./routes/revalidate.js";
66
87
  export type { RevalidateOptions } from "./routes/revalidate.js";