@simmalugnt-se/payload-content-health 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +36 -6
- package/dist/admin/ContentHealthFieldBridge.d.ts +8 -0
- package/dist/admin/ContentHealthFieldBridge.js +291 -0
- package/dist/admin/ContentHealthNavLink.d.ts +3 -0
- package/dist/admin/ContentHealthNavLink.js +19 -0
- package/dist/admin/reveal-plan.d.ts +50 -0
- package/dist/admin/reveal-plan.js +135 -0
- package/dist/admin/target.d.ts +9 -0
- package/dist/admin/target.js +15 -0
- package/dist/copy.d.ts +47 -2
- package/dist/copy.js +96 -0
- package/dist/exports/client.d.ts +3 -0
- package/dist/exports/client.js +3 -0
- package/dist/exports/rsc.d.ts +1 -0
- package/dist/exports/rsc.js +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/links.d.ts +37 -0
- package/dist/links.js +86 -0
- package/dist/package-name.d.ts +1 -0
- package/dist/package-name.js +1 -0
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.js +41 -1
- package/dist/styles/admin.css +27 -0
- package/dist/styles/view.css +148 -0
- package/dist/styles/widget.css +21 -5
- package/dist/view/ContentHealthView.d.ts +8 -0
- package/dist/view/ContentHealthView.js +77 -0
- package/dist/view/WorkList.d.ts +34 -0
- package/dist/view/WorkList.js +26 -0
- package/dist/widget/ContentHealthWidget.js +15 -20
- package/package.json +16 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type UiLanguage } from "../copy.ts";
|
|
2
|
+
import { type ListFilters } from "../links.ts";
|
|
3
|
+
import type { CheckId } from "../types.ts";
|
|
4
|
+
export type WorkListOption = {
|
|
5
|
+
value: string;
|
|
6
|
+
label: string;
|
|
7
|
+
};
|
|
8
|
+
/** One finding, ready to show: labels and links are made on the server. */
|
|
9
|
+
export type WorkListRow = {
|
|
10
|
+
key: string;
|
|
11
|
+
check: CheckId;
|
|
12
|
+
checkLabel: string;
|
|
13
|
+
collection: string;
|
|
14
|
+
title: string;
|
|
15
|
+
locale?: string;
|
|
16
|
+
detail?: string;
|
|
17
|
+
draft: boolean;
|
|
18
|
+
/** The document in the finding's locale with the field selected. */
|
|
19
|
+
href: string;
|
|
20
|
+
/** The same with a prompt for the editor assistant; only where the assistant works. */
|
|
21
|
+
askHref?: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The findings with filters on check, collection and locale. Filters apply in the browser and are
|
|
25
|
+
* kept in the address, so the view can be linked to and reloaded with them.
|
|
26
|
+
*/
|
|
27
|
+
export declare function WorkList({ checks, collections, initialFilters, language, locales, rows, }: {
|
|
28
|
+
checks: WorkListOption[];
|
|
29
|
+
collections: WorkListOption[];
|
|
30
|
+
initialFilters: ListFilters;
|
|
31
|
+
language: UiLanguage;
|
|
32
|
+
locales: WorkListOption[];
|
|
33
|
+
rows: WorkListRow[];
|
|
34
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button, Link } from "@payloadcms/ui";
|
|
4
|
+
import { useMemo, useState } from "react";
|
|
5
|
+
import { LIST_COPY } from "../copy.js";
|
|
6
|
+
import { filterQuery, matchesFilters } from "../links.js";
|
|
7
|
+
/**
|
|
8
|
+
* The findings with filters on check, collection and locale. Filters apply in the browser and are
|
|
9
|
+
* kept in the address, so the view can be linked to and reloaded with them.
|
|
10
|
+
*/
|
|
11
|
+
export function WorkList({ checks, collections, initialFilters, language, locales, rows, }) {
|
|
12
|
+
const copy = LIST_COPY[language];
|
|
13
|
+
const [filters, setFilters] = useState(initialFilters);
|
|
14
|
+
const shown = useMemo(() => rows.filter((row) => matchesFilters(row, filters)), [rows, filters]);
|
|
15
|
+
const documents = new Set(shown.map((row) => row.href.split("?")[0])).size;
|
|
16
|
+
const filtered = Boolean(filters.check || filters.collection || filters.locale);
|
|
17
|
+
const update = (next) => {
|
|
18
|
+
setFilters(next);
|
|
19
|
+
const query = filterQuery(next);
|
|
20
|
+
window.history.replaceState(window.history.state, "", `${window.location.pathname}${query ? `?${query}` : ""}`);
|
|
21
|
+
};
|
|
22
|
+
return (_jsxs("div", { className: "content-health-list__body", children: [_jsxs("div", { className: "content-health-list__filters", children: [_jsx(Filter, { label: copy.check, all: copy.all, onChange: (check) => update({ ...filters, check: check }), options: checks, value: filters.check }), collections.length > 1 ? (_jsx(Filter, { label: copy.collection, all: copy.all, onChange: (collection) => update({ ...filters, collection }), options: collections, value: filters.collection })) : null, locales.length > 1 ? (_jsx(Filter, { label: copy.locale, all: copy.all, onChange: (locale) => update({ ...filters, locale }), options: locales, value: filters.locale })) : null, filtered ? (_jsx(Button, { buttonStyle: "subtle", margin: false, onClick: () => update({}), size: "small", children: copy.clear })) : null] }), _jsx("p", { className: "content-health-list__summary", "aria-live": "polite", children: shown.length ? copy.summary(shown.length, documents) : null }), shown.length ? (_jsx("ul", { className: "content-health-list__rows", children: shown.map((row) => (_jsxs("li", { className: "content-health-list__row", children: [_jsxs("div", { className: "content-health-list__what", children: [_jsx("span", { className: "content-health-list__check", children: row.checkLabel }), row.detail ? (_jsx("span", { className: "content-health-list__detail", children: row.detail })) : null] }), _jsxs("div", { className: "content-health-list__document", children: [_jsx(Link, { href: row.href, prefetch: false, children: row.title }), row.locale ? (_jsx("span", { className: "content-health-list__pill", children: row.locale })) : null, row.draft ? (_jsx("span", { className: "content-health-list__pill content-health-list__pill--draft", children: copy.draft })) : null] }), _jsx("div", { className: "content-health-list__actions", children: row.askHref ? (_jsx(Button, { buttonStyle: "secondary", el: "link", margin: false, size: "small", url: row.askHref, children: copy.fix })) : null })] }, row.key))) })) : (_jsx("p", { className: "content-health-list__empty", children: filtered ? copy.noneFiltered : copy.none }))] }));
|
|
23
|
+
}
|
|
24
|
+
function Filter({ all, label, onChange, options, value, }) {
|
|
25
|
+
return (_jsxs("label", { className: "content-health-list__filter", children: [_jsx("span", { children: label }), _jsxs("select", { onChange: (event) => onChange(event.target.value || undefined), value: value ?? "", children: [_jsx("option", { value: "", children: all }), options.map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value)))] })] }));
|
|
26
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import { COPY, STATUS, TILE_LABELS, uiLanguage } from "../copy.js";
|
|
2
|
+
import { COPY, findingDetail, STATUS, TILE_LABELS, uiLanguage } from "../copy.js";
|
|
4
3
|
import { checkStats, countDocuments } from "../group.js";
|
|
4
|
+
import { editURL, listURL } from "../links.js";
|
|
5
5
|
import { scanContentHealth } from "../scan.js";
|
|
6
6
|
import { CHECK_IDS } from "../types.js";
|
|
7
7
|
import { Ring } from "./Ring.js";
|
|
@@ -39,23 +39,18 @@ function CheckTile({ adminRoute, language, stat, }) {
|
|
|
39
39
|
: stat.failing
|
|
40
40
|
? STATUS[language][stat.check](stat.checked - stat.failing, stat.checked)
|
|
41
41
|
: copy.checkPasses;
|
|
42
|
-
return (_jsxs("li", { className: "content-health__tile", children: [_jsxs("div", { className: "content-health__tile-head", children: [_jsx(Ring, { className: "content-health__ring", failing: stat.failing, label: status, total: stat.checked }), _jsxs("div", { children: [_jsx("h3", { className: "content-health__tile-title", children: TILE_LABELS[language][stat.check] }), _jsx("p", { className: "content-health__tile-status", children: status })] })] }), stat.documents.length ? (_jsxs("ul", { className: "content-health__rows", children: [stat.documents.slice(0, ROWS_PER_CHECK).map((row) => (
|
|
42
|
+
return (_jsxs("li", { className: "content-health__tile", children: [_jsxs("div", { className: "content-health__tile-head", children: [_jsx(Ring, { className: "content-health__ring", failing: stat.failing, label: status, total: stat.checked }), _jsxs("div", { children: [_jsx("h3", { className: "content-health__tile-title", children: TILE_LABELS[language][stat.check] }), _jsx("p", { className: "content-health__tile-status", children: status })] })] }), stat.documents.length ? (_jsxs("ul", { className: "content-health__rows", children: [stat.documents.slice(0, ROWS_PER_CHECK).map((row) => (_jsx(FindingRow, { adminRoute: adminRoute, language: language, row: row }, `${row.collection}:${row.id}`))), stat.documents.length > ROWS_PER_CHECK ? (_jsx("li", { className: "content-health__more", children: _jsx("a", { href: listURL(adminRoute, { check: stat.check }), children: copy.more(stat.documents.length - ROWS_PER_CHECK) }) })) : null] })) : null] }));
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
function editURL(adminRoute, finding) {
|
|
58
|
-
const path = `/collections/${finding.collection}/${encodeURIComponent(String(finding.id))}`;
|
|
59
|
-
const url = formatAdminURL({ adminRoute, path });
|
|
60
|
-
return finding.locale ? `${url}?locale=${encodeURIComponent(finding.locale)}` : url;
|
|
44
|
+
/**
|
|
45
|
+
* A document on its own line, then its locales: one per line when they carry details (lengths, word
|
|
46
|
+
* counts), otherwise side by side.
|
|
47
|
+
*/
|
|
48
|
+
function FindingRow({ adminRoute, language, row, }) {
|
|
49
|
+
const locales = row.findings.map((finding) => ({
|
|
50
|
+
finding,
|
|
51
|
+
detail: findingDetail(language, finding),
|
|
52
|
+
}));
|
|
53
|
+
const stacked = locales.some(({ detail }) => detail);
|
|
54
|
+
const shown = locales.filter(({ finding, detail }) => finding.locale || detail);
|
|
55
|
+
return (_jsxs("li", { className: "content-health__row", children: [_jsx("a", { href: editURL(adminRoute, row.findings[0]), children: row.title }), shown.length ? (_jsx("ul", { className: `content-health__locales${stacked ? " content-health__locales--stacked" : ""}`, children: shown.map(({ finding, detail }) => (_jsxs("li", { className: "content-health__locale", children: [finding.locale ? (_jsx("a", { className: "content-health__pill", href: editURL(adminRoute, finding), children: finding.locale })) : null, detail ? _jsx("span", { className: "content-health__detail", children: detail }) : null] }, finding.locale ?? ""))) })) : null] }));
|
|
61
56
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simmalugnt-se/payload-content-health",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Content health checks and dashboard widgets for Payload Admin: alt texts, SEO fields and more",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"payload",
|
|
@@ -34,20 +34,34 @@
|
|
|
34
34
|
"import": "./dist/exports/rsc.js",
|
|
35
35
|
"types": "./dist/exports/rsc.d.ts",
|
|
36
36
|
"default": "./dist/exports/rsc.js"
|
|
37
|
+
},
|
|
38
|
+
"./client": {
|
|
39
|
+
"import": "./dist/exports/client.js",
|
|
40
|
+
"types": "./dist/exports/client.d.ts",
|
|
41
|
+
"default": "./dist/exports/client.js"
|
|
37
42
|
}
|
|
38
43
|
},
|
|
39
44
|
"publishConfig": {
|
|
40
45
|
"access": "public"
|
|
41
46
|
},
|
|
42
47
|
"peerDependencies": {
|
|
48
|
+
"@payloadcms/next": ">=3.90.2 <4",
|
|
49
|
+
"@payloadcms/ui": ">=3.90.2 <4",
|
|
50
|
+
"next": ">=16.2.6 <17",
|
|
43
51
|
"payload": ">=3.90.2 <4",
|
|
44
|
-
"react": "^19.0.0"
|
|
52
|
+
"react": "^19.0.0",
|
|
53
|
+
"react-dom": "^19.0.0"
|
|
45
54
|
},
|
|
46
55
|
"devDependencies": {
|
|
56
|
+
"@payloadcms/next": "3.90.2",
|
|
57
|
+
"@payloadcms/ui": "3.90.2",
|
|
47
58
|
"@types/node": "^22",
|
|
48
59
|
"@types/react": "^19.2.18",
|
|
60
|
+
"@types/react-dom": "^19.2.4",
|
|
61
|
+
"next": "16.3.6",
|
|
49
62
|
"payload": "3.90.2",
|
|
50
63
|
"react": "19.2.8",
|
|
64
|
+
"react-dom": "19.2.8",
|
|
51
65
|
"tsx": "^4.21.0",
|
|
52
66
|
"typescript": "5.9.3"
|
|
53
67
|
},
|