wiki-formant 0.40.0 → 0.41.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/README.md +2 -1
- package/dist/base.css +3 -4
- package/dist/chart.d.ts +7 -1
- package/dist/chart.js +2 -2
- package/dist/stats-figures.d.ts +3 -1
- package/dist/stats-figures.js +2 -2
- package/dist/stats.js +6 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -732,7 +732,8 @@ return <Stats digest={await digest(sql, q)} {...q} />;
|
|
|
732
732
|
own subpath, because it imports the optional peer `@simplewebauthn/browser`.
|
|
733
733
|
`Stats` (`wiki-formant/stats`) renders a `digest` as one page: the
|
|
734
734
|
headline figures against the window before, one of them per day, every non-empty
|
|
735
|
-
ranked list, and `?days=` links between windows, all time among them
|
|
735
|
+
ranked list, and `?days=` links between windows, all time among them, pinned in
|
|
736
|
+
the chart's frame as its own buttons are (`TimeChart`'s `controls`). Each
|
|
736
737
|
figure is a button that charts it: visitors, page views, bounce rate, visit
|
|
737
738
|
length, visitors with agents and clicks to X. `digest`'s `by_day` carries every
|
|
738
739
|
one of those per UTC day, a visit on the day it began, so the days add up to the
|
package/dist/base.css
CHANGED
|
@@ -190,15 +190,14 @@ pre:hover > .code-copy-btn,
|
|
|
190
190
|
gap: 0.5rem;
|
|
191
191
|
}
|
|
192
192
|
.chart-controls > div { display: flex; gap: 2px; }
|
|
193
|
-
.chart-controls button { font: inherit; color: inherit; background: none; cursor: pointer; opacity: 0.6; }
|
|
194
|
-
|
|
193
|
+
.chart-controls :is(button, a) { font: inherit; color: inherit; text-decoration: none; background: none; cursor: pointer; opacity: 0.6; }
|
|
194
|
+
/* A button is pressed; a link (a stats page's window) is the page shown. */
|
|
195
|
+
.chart-controls :is([aria-pressed='true'], [aria-current]) { opacity: 1; }
|
|
195
196
|
|
|
196
197
|
/* ---- Stats ----------------------------------------------------------------
|
|
197
198
|
`--share` is a row's value over the largest in its list, 0 to 1. Bars take
|
|
198
199
|
`currentColor` so they show before a stylesheet colours them. */
|
|
199
200
|
.stats { display: grid; gap: 2rem; }
|
|
200
|
-
.stats-ranges { display: flex; flex-wrap: wrap; gap: 1rem; }
|
|
201
|
-
.stats-ranges a[aria-current] { text-decoration: underline; text-underline-offset: 0.3em; }
|
|
202
201
|
/* Each figure is a button that charts it; pressed is the one drawn. */
|
|
203
202
|
.stats-figures {
|
|
204
203
|
display: grid;
|
package/dist/chart.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
1
2
|
/**
|
|
2
3
|
* One value at a time in Unix seconds, UTC. A price may carry the rest of its
|
|
3
4
|
* candle, `value` being the close, and the volume traded in it.
|
|
@@ -52,6 +53,11 @@ export interface TimeChartProps {
|
|
|
52
53
|
candles?: boolean;
|
|
53
54
|
/** In pixels. Otherwise the stylesheet's `--chart-height`, else 15rem. */
|
|
54
55
|
height?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Groups pinned before the chart's own, styled as its buttons: a stats page's
|
|
58
|
+
* window links, which load another digest rather than slice this one.
|
|
59
|
+
*/
|
|
60
|
+
controls?: ReactNode;
|
|
55
61
|
className?: string;
|
|
56
62
|
}
|
|
57
63
|
/**
|
|
@@ -59,4 +65,4 @@ export interface TimeChartProps {
|
|
|
59
65
|
* `.chart-controls button[aria-pressed]`, and a site's stylesheet gives them
|
|
60
66
|
* and the box their look; the line and fill are the canvas box's `color`.
|
|
61
67
|
*/
|
|
62
|
-
export declare function TimeChart({ series, label, ranges, range: initial, aggregate, fromZero, format, candles: initialCandles, height, className, }: TimeChartProps): import("react").JSX.Element;
|
|
68
|
+
export declare function TimeChart({ series, label, ranges, range: initial, aggregate, fromZero, format, candles: initialCandles, height, controls, className, }: TimeChartProps): import("react").JSX.Element;
|
package/dist/chart.js
CHANGED
|
@@ -101,7 +101,7 @@ const fromZeroScale = (base) => {
|
|
|
101
101
|
* `.chart-controls button[aria-pressed]`, and a site's stylesheet gives them
|
|
102
102
|
* and the box their look; the line and fill are the canvas box's `color`.
|
|
103
103
|
*/
|
|
104
|
-
export function TimeChart({ series, label, ranges = typeof series === 'function' ? CHART_RANGES : DAILY_RANGES, range: initial = ranges.includes('30d') ? '30d' : ranges[0], aggregate = 'last', fromZero = false, format = formatChartValue, candles: initialCandles = false, height, className, }) {
|
|
104
|
+
export function TimeChart({ series, label, ranges = typeof series === 'function' ? CHART_RANGES : DAILY_RANGES, range: initial = ranges.includes('30d') ? '30d' : ranges[0], aggregate = 'last', fromZero = false, format = formatChartValue, candles: initialCandles = false, height, controls, className, }) {
|
|
105
105
|
const box = useRef(null);
|
|
106
106
|
const chart = useRef(null);
|
|
107
107
|
const formatRef = useRef(format);
|
|
@@ -261,5 +261,5 @@ export function TimeChart({ series, label, ranges = typeof series === 'function'
|
|
|
261
261
|
c.api.timeScale().fitContent();
|
|
262
262
|
}, [ready, source, view, candles]);
|
|
263
263
|
const status = source && 'error' in source ? source.error : points && !view?.shown.length ? 'No data in this range' : null;
|
|
264
|
-
return (_jsxs("figure", { className: className ? `chart ${className}` : 'chart', children: [_jsx("div", { ref: box, className: "chart-canvas", role: "img", "aria-label": label, style: height ? { height } : undefined }), (!ready || (!points && !status)) && _jsx("div", { className: "chart-status skeleton", "aria-hidden": true }), status && _jsx("p", { className: "chart-status", children: status }), (ranges.length > 1 || (view && (view.steps.length > 1 || view.ohlc))) && (_jsxs("div", { className: "chart-controls", children: [ranges.length > 1 && (_jsx("div", { role: "group", "aria-label": "Range", children: ranges.map(r => (_jsx("button", { type: "button", "aria-pressed": r === range, onClick: () => { setRange(r); setStep(null); }, children: RANGE[r].label }, r))) })), view && view.steps.length > 1 && (_jsx("div", { role: "group", "aria-label": "Step", children: view.steps.map(s => (_jsx("button", { type: "button", "aria-pressed": s === view.step, onClick: () => setStep(s), title: `One point per ${s}`, children: STEP[s].label }, s))) })), view?.ohlc && (_jsxs("div", { role: "group", "aria-label": "Style", children: [_jsx("button", { type: "button", "aria-pressed": !candles, onClick: () => setCandles(false), children: "Line" }), _jsx("button", { type: "button", "aria-pressed": candles, onClick: () => setCandles(true), children: "Candles" })] }))] }))] }));
|
|
264
|
+
return (_jsxs("figure", { className: className ? `chart ${className}` : 'chart', children: [_jsx("div", { ref: box, className: "chart-canvas", role: "img", "aria-label": label, style: height ? { height } : undefined }), (!ready || (!points && !status)) && _jsx("div", { className: "chart-status skeleton", "aria-hidden": true }), status && _jsx("p", { className: "chart-status", children: status }), (controls || ranges.length > 1 || (view && (view.steps.length > 1 || view.ohlc))) && (_jsxs("div", { className: "chart-controls", children: [controls, ranges.length > 1 && (_jsx("div", { role: "group", "aria-label": "Range", children: ranges.map(r => (_jsx("button", { type: "button", "aria-pressed": r === range, onClick: () => { setRange(r); setStep(null); }, children: RANGE[r].label }, r))) })), view && view.steps.length > 1 && (_jsx("div", { role: "group", "aria-label": "Step", children: view.steps.map(s => (_jsx("button", { type: "button", "aria-pressed": s === view.step, onClick: () => setStep(s), title: `One point per ${s}`, children: STEP[s].label }, s))) })), view?.ohlc && (_jsxs("div", { role: "group", "aria-label": "Style", children: [_jsx("button", { type: "button", "aria-pressed": !candles, onClick: () => setCandles(false), children: "Line" }), _jsx("button", { type: "button", "aria-pressed": candles, onClick: () => setCandles(true), children: "Candles" })] }))] }))] }));
|
|
265
265
|
}
|
package/dist/stats-figures.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
1
2
|
import type { Digest } from './analytics.js';
|
|
2
3
|
/**
|
|
3
4
|
* The figures as `.stats-figures button[aria-pressed]` and the pressed one per
|
|
4
5
|
* day from `first` (a UTC day number) to today. A count's empty day is a zero;
|
|
5
6
|
* a rate's is left out, since no visits is not a bounce rate of nothing.
|
|
6
7
|
*/
|
|
7
|
-
export declare function StatsFigures({ digest, first }: {
|
|
8
|
+
export declare function StatsFigures({ digest, first, windows }: {
|
|
8
9
|
digest: Digest;
|
|
9
10
|
first: number;
|
|
11
|
+
windows: ReactNode;
|
|
10
12
|
}): import("react").JSX.Element;
|
package/dist/stats-figures.js
CHANGED
|
@@ -41,7 +41,7 @@ const EMPTY = { date: '', visitors: 0, pageviews: 0, visits: 0, bounces: 0, seco
|
|
|
41
41
|
* day from `first` (a UTC day number) to today. A count's empty day is a zero;
|
|
42
42
|
* a rate's is left out, since no visits is not a bounce rate of nothing.
|
|
43
43
|
*/
|
|
44
|
-
export function StatsFigures({ digest, first }) {
|
|
44
|
+
export function StatsFigures({ digest, first, windows }) {
|
|
45
45
|
const figures = useMemo(() => figuresOf(digest), [digest]);
|
|
46
46
|
const [chosen, choose] = useState(0);
|
|
47
47
|
const figure = figures[chosen] ?? figures[0];
|
|
@@ -54,5 +54,5 @@ export function StatsFigures({ digest, first }) {
|
|
|
54
54
|
return value == null ? null : { time, value };
|
|
55
55
|
}).filter(p => p !== null);
|
|
56
56
|
}, [byDay, first, figure]);
|
|
57
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "stats-figures", role: "group", "aria-label": "Chart", children: figures.map((f, i) => (_jsxs("button", { type: "button", "aria-pressed": i === chosen, onClick: () => choose(i), children: [_jsx("span", { children: f.label }), _jsxs("strong", { children: [f.value, f.delta && _jsx("small", { children: ` ${f.delta}` })] })] }, f.label))) }), _jsx(TimeChart, { series: series, label: `${figure.label} per day`, ranges: ['all'], aggregate: "mean", fromZero: true, format: figure.format, className: "stats-chart" })] }));
|
|
57
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "stats-figures", role: "group", "aria-label": "Chart", children: figures.map((f, i) => (_jsxs("button", { type: "button", "aria-pressed": i === chosen, onClick: () => choose(i), children: [_jsx("span", { children: f.label }), _jsxs("strong", { children: [f.value, f.delta && _jsx("small", { children: ` ${f.delta}` })] })] }, f.label))) }), _jsx(TimeChart, { series: series, label: `${figure.label} per day`, ranges: ['all'], aggregate: "mean", fromZero: true, format: figure.format, controls: windows, className: "stats-chart" })] }));
|
|
58
58
|
}
|
package/dist/stats.js
CHANGED
|
@@ -5,6 +5,8 @@ import { StatsFigures } from './stats-figures.js';
|
|
|
5
5
|
export const STATS_RANGES = [1, 7, 30, 90, 365, Infinity];
|
|
6
6
|
/** A window as its `?days=` value. */
|
|
7
7
|
const daysParam = (n) => (Number.isFinite(n) ? String(n) : 'all');
|
|
8
|
+
/** A window as `TimeChart` names its ranges, since its buttons are where the windows sit. */
|
|
9
|
+
const windowLabel = (n) => (n === 1 ? '24H' : n === 365 ? '1Y' : Number.isFinite(n) ? `${n}D` : 'All');
|
|
8
10
|
/** A `?days=` value as one of `ranges`, else `fallback`. */
|
|
9
11
|
export const statsDays = (param, ranges = STATS_RANGES, fallback = 30) => ranges.find(n => daysParam(n) === param) ?? fallback;
|
|
10
12
|
/**
|
|
@@ -67,5 +69,8 @@ export function Stats({ digest: d, days, filter = {}, ranges = STATS_RANGES }) {
|
|
|
67
69
|
: Number.isFinite(start) ? start : Math.min(today, ...d.by_day.map(r => Date.parse(r.date) / DAY_MS));
|
|
68
70
|
const rows = (list, k, m) => list.map(r => [r[k], r[m]]);
|
|
69
71
|
const to = (k) => (v) => href(days, { ...filter, [k]: v });
|
|
70
|
-
|
|
72
|
+
// Each window is another digest, so a link rather than a button, pinned
|
|
73
|
+
// with the chart's own controls.
|
|
74
|
+
const windows = (_jsx("div", { role: "group", "aria-label": "Window", children: ranges.map(n => (_jsx("a", { href: href(n, filter), "aria-current": n === days ? 'page' : undefined, children: windowLabel(n) }, n))) }));
|
|
75
|
+
return (_jsxs("div", { className: "stats", children: [narrowed.length > 0 && (_jsxs("div", { className: "stats-filters", children: [_jsx("ul", { "aria-label": "Narrowed to", children: narrowed.map(k => (_jsx("li", { children: _jsxs("a", { href: href(days, { ...filter, [k]: undefined }), title: "Remove", children: [NAMES[k], ": ", k === 'country' ? country(filter[k]) : filter[k], " \u00D7"] }) }, k))) }), start < counted && d.counted_from && (_jsxs("p", { children: ["Narrowed figures start on ", d.counted_from, ", the first day this site counted its own visits."] }))] })), _jsx(StatsFigures, { digest: d, first: first, windows: windows }), _jsxs("div", { className: "stats-lists", children: [_jsx(StatsList, { title: "Pages", rows: rows(d.top_pages, 'page', 'visitors'), to: to('page') }), _jsx(StatsList, { title: "Entry pages", rows: rows(d.entry_pages, 'page', 'visitors'), to: to('entry') }), _jsx(StatsList, { title: "Sources", rows: rows(d.top_sources, 'source', 'visitors'), to: to('source') }), _jsx(StatsList, { title: "From X posts", rows: rows(d.from_posts, 'page', 'visitors'), to: v => href(days, { ...filter, source: 'x', entry: v }) }), _jsx(StatsList, { title: "Sibling sites", rows: rows(d.sibling_referrals, 'source', 'visitors'), to: to('source') }), _jsx(StatsList, { title: "Countries", rows: rows(d.countries, 'country', 'visitors'), label: country, to: to('country') }), _jsx(StatsList, { title: "Devices", rows: rows(d.devices, 'device', 'visitors'), to: to('device') }), _jsx(StatsList, { title: "Agent tool calls", rows: rows(d.agent_tool_calls, 'tool', 'events') }), _jsx(StatsList, { title: "Searches that found nothing", rows: rows(d.gaps, 'query', 'events') })] })] }));
|
|
71
76
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wiki-formant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
4
4
|
"description": "The portable half of a wiki: derived taxonomy and facet controls, a version-negotiating MCP transport, markdown twins, block rendering, a rich-text editor engine, conditional-GET plumbing, and the headers and CSP every origin sends.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|