diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 65dbfe7e1..cb6ec6d84 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -50,6 +50,8 @@ jobs: uses: actions/configure-pages@v5 - name: Install Node.js dependencies run: "npm ci" + - name: Build admin CMS + run: "npm run build:admin" - name: Cache Hugo Resources uses: actions/cache@v4 with: diff --git a/.github/workflows/pullrequest.yaml b/.github/workflows/pullrequest.yaml index dd6e5fd63..ce188720b 100644 --- a/.github/workflows/pullrequest.yaml +++ b/.github/workflows/pullrequest.yaml @@ -44,6 +44,8 @@ jobs: uses: actions/configure-pages@v5 - name: Install Node.js dependencies run: "npm ci" + - name: Build admin CMS + run: "npm run build:admin" - name: Cache Hugo Resources uses: actions/cache@v4 with: @@ -66,7 +68,7 @@ jobs: run: npx -y pagefind --site public - name: Check for unrendered footnotes run: | - if grep -rnEo '\[\^[0-9]+\]' public; then + if grep -rnEo '\[\^[0-9]+\]' public --exclude-dir=admin; then echo "FAIL: Unrendered footnotes found. See lines above." >&2 exit 1 fi diff --git a/.gitignore b/.gitignore index b0228f713..5594abba1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules/ public/ resources/ +static/admin/ hugo.exe .hugo_build.lock .DS_Store diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49723a8ca..52f3ebf70 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,6 +33,12 @@ First of all, we have to the install the node packages by running: npm i ``` +The CMS admin interface (`/admin`) is bundled separately with Vite and is not rebuilt automatically by Hugo. Run the following command whenever you change files in `assets/admin/` (or after installing/updating packages) to (re)generate it: + +```zsh +npm run build:admin +``` + Then, we can start the integrated hugo server with: ```zsh @@ -47,7 +53,7 @@ To initialize the search index, run the following command in a separate terminal npx -y pagefind --site public ``` -### Run prek hooks +### Run pre-commit hooks We use the [prek framework](https://prek.dev/) to run some checks to ensure a consistent code quality. @@ -67,6 +73,13 @@ prek install ## Content Contributions +### Use the CMS for local changes + +If you have built the admin interface before using `npm run build:admin` and started the development server with `hugo server`, you can access the CMS at `http://localhost:1313/admin`. On the login screen select "Work with Local Repository" and then select the root of the repository on your disk. Changes are then written back to your local repository. Once you have finished editing, you can commit and push the changes manually using Git. + +> [!NOTE] +> The CMS for local changes does not work with Firefox or Safari, so use a Chromium based browser instead. + ### Add new pages To add a new country: diff --git a/assets/admin/fields.js b/assets/admin/fields.js new file mode 100644 index 000000000..46f8cfdba --- /dev/null +++ b/assets/admin/fields.js @@ -0,0 +1,114 @@ +import { React, getFieldType, registerFieldType } from "@sveltia/cms"; + +var h = React.createElement; + +var SelectControl = getFieldType("select").control; + +function FipValiditySelectControl(props) { + var groups = props.entry ? props.entry.getIn(["data", "groups"]) : null; + groups = groups && typeof groups.toJS === "function" ? groups.toJS() : []; + var groupType = props.field ? props.field.get("group_type") : null; + if (groupType) { + groups = groups.filter(function (group) { + return !group.type || group.type === groupType; + }); + } + var options = groups + .filter(function (group) { + return !!group.name; + }) + .map(function (group) { + return { label: group.name, value: group.name }; + }); + + return h(SelectControl, { + field: { name: props.field.get("name"), options }, + value: props.value, + forID: props.forID, + onChange: props.onChange, + }); +} + +registerFieldType( + "fip-validity-select", + FipValiditySelectControl, + FipValiditySelectControl, +); + +function CmsEditLinkControl(props) { + var field = props.field; + var getValue = field.get("get_value"); + var hrefTemplate = field.get("href"); + var labelTemplate = field.get("label_template"); + var emptyHint = field.get("empty_hint"); + var isStatic = field.get("static"); + + var value = isStatic ? "" : String(getValue(props) || ""); + + if (!isStatic && !value) { + return h("p", { className: "o-cms-edit-link__info" }, emptyHint); + } + + var href = hrefTemplate.replace(/\{value\}/g, value); + var label = labelTemplate.replace(/\{value\}/g, value); + return h( + "p", + { className: "o-cms-edit-link__info" }, + h( + "a", + { + href: href, + onClick: function (event) { + event.preventDefault(); + if ( + event.metaKey || + event.ctrlKey || + event.shiftKey || + event.button === 1 + ) { + window.open(href, "_blank"); + return; + } + window.location.hash = href; + }, + onAuxClick: function (event) { + event.preventDefault(); + if (event.button === 1) { + window.open(href, "_blank"); + } + }, + }, + label, + ), + ); +} + +registerFieldType("cms-edit-link", CmsEditLinkControl, CmsEditLinkControl); + +var NumberControl = getFieldType("number").control; + +function CmsNumberWithUsageControl(props) { + var field = props.field; + var template = field.get("template"); + var value = props.value; + + var text = template.replace(/\{value\}/g, String(value ?? "")); + + return h( + "div", + {}, + h(NumberControl, { + field: { name: field.get("name") }, + value: value, + forID: props.forID, + onChange: props.onChange, + }), + h("p", { className: "o-cms-display__info" }, text), + ); +} + +registerFieldType( + "cms-number-with-usage", + CmsNumberWithUsageControl, + CmsNumberWithUsageControl, +); diff --git a/static/admin/index.html b/assets/admin/index.html similarity index 66% rename from static/admin/index.html rename to assets/admin/index.html index 7bf579281..e4060cf4b 100644 --- a/static/admin/index.html +++ b/assets/admin/index.html @@ -7,8 +7,6 @@
- - - +