diff --git a/.gitignore b/.gitignore index 34e20d4..b769f55 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ dist/ .astro/ .DS_Store -# Content lives in the ctao/content repo (build overlays it; scripts/link-content.sh locally) +# Content lives in the ctao/content repo (deploy copies it in; npm run content:link links ../content locally) src/content/news/ src/content/pages/ public/uploads/ diff --git a/README.md b/README.md index 8d67bc8..ed36765 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,54 @@ -# CTAO Science Portal — simple proof of concept +# CTAO Science Portal -The `main` branch uses the sibling `mockup` project's layout, assets, CSS, -and header interactions. Astro generates a static site. Sveltia CMS edits -exactly four Markdown pages in the separate `ctao/content` repository. +Static Astro site with a git-based CMS (Sveltia). Code lives here; editorial +content lives in the separate [`ctao/content`](https://astro-git.isl-dev.grid.cyfronet.pl/ctao/content) +repository. Four Example pages are editable; all other pages are placeholders. -## Scope - -| Example submenu | URL | Content file | -|---|---|---| -| Lorem ipsum | `/example/lorem-ipsum/` | `pages/lorem-ipsum.md` | -| Dolor sit amet | `/example/dolor-sit-amet/` | `pages/dolor-sit-amet.md` | -| Consectetur adipiscing | `/example/consectetur-adipiscing/` | `pages/consectetur-adipiscing.md` | -| Sed do eiusmod | `/example/sed-do-eiusmod/` | `pages/sed-do-eiusmod.md` | - -Home, News, Data, Proposals, Dashboard, Sign in, Search, Support, Privacy, -Disclaimer, and Contact have the shared header/footer, a title, and an empty -body. Search submits to its placeholder. Only English is available; the -other language options are inactive. There are no news articles, news -pagination, RSS feeds, search index, service integrations, or public sign-in. -`/admin/` remains the working CMS entry point. +- Live demo: https://astro.isl-dev.grid.cyfronet.pl +- Editor: https://astro.isl-dev.grid.cyfronet.pl/admin/ (Gitea sign-in) ## Run locally -Use Node 22.19 or newer (deployment uses Node 24). +Node 22.19 or newer. Clone the content repository next to this one: ```sh +git clone https://astro-git.isl-dev.grid.cyfronet.pl/ctao/content.git ../content npm ci -npm run dev +npm run dev # http://localhost:4321 ``` -Open `http://localhost:4321/example/lorem-ipsum/` for the full mockup example. -The first run copies bundled `sample-content/pages/` and `uploads/` into the -ignored runtime directories. Existing content is preserved. An incomplete -existing set fails with a missing-file error instead of silently mixing -sample content with editorial content. +The site reads `../content` directly through links, so pull that repository +for the latest content. To use a checkout elsewhere: +`npm run content:link -- `. -```sh -npm run content:setup # explicitly restore the four sample pages -npm run content:sync -- ../ctao-content # copy from a separate content checkout -npm run check # types, static build, internal links -npm run build -npm run preview # inspect the built site -``` +Before pushing code, run `npm run check` (types, build, internal links). -`content:setup` and `content:sync` replace the four runtime Markdown files and -copy uploads, overwriting same-named media. They do not modify the source -content checkout. Only the four allowlisted pages are loaded and rendered; -unrelated Markdown files never create public routes. +## Edit content locally -## Edit with Sveltia locally, without Gitea +With `npm run dev` running, open `http://localhost:4321/admin/` in Chrome or +Edge, choose **Work with Local Repository** and select the `../content` folder. +Saves write the Markdown files and show up on the local site on reload. They +are not committed: commit and push in `../content` to publish. -```sh -npm run content:local -npm run dev -``` +## Publishing -In Chrome or Edge, open `/admin/`, select **Work with Local Repository**, and -choose this project's `.tmp/content` directory. The setup command creates a -local Git repository there, seeded from the bundled content. Re-running it -preserves edits in that directory. - -Save an Example page, then run this in another terminal to refresh the site: - -```sh -npm run content:sync -- .tmp/content -``` - -The local editor writes files without committing them. This follows -[Sveltia's local workflow](https://sveltiacms.app/en/docs/workflows/local). -Choose the content repository root, not the portal code repository root: -the CMS expects `pages/` and `uploads/` directly beneath it. - -## Content and publishing - -The CMS uses a [fixed file collection](https://sveltiacms.app/en/docs/collections/files). -Editors can change title, introduction, optional image/alternative text/caption, -Markdown body, and optional button label/destination. Both button fields are -needed to display the button. A Markdown blockquote receives the mockup's -highlight styling. Images without alternative text are treated as decorative. -Menu labels, ordering, URLs, and the shared site layout remain code-controlled. - -Remote editing uses Gitea OAuth and saves to **`ctao/content`, branch `main`**. -That branch must contain the four files from `sample-content/pages/` and the -sample upload before publishing this version. The code mirror is -**`ctao/portal`, branch `main`**. Deployment independently selects -`CODE_BRANCH` and `CONTENT_BRANCH`, overlays content, runs checks, and publishes -atomically. `CONTENT_BRANCH` must match `backend.branch` in the CMS config. - -See [deploy/README.md](deploy/README.md) for migration and publishing steps. -A push to Bitbucket alone does not deploy the site. +Pushing to `main` of `ctao/portal` (code) or `ctao/content` (content) on +Gitea rebuilds and publishes the live demo automatically; the online editor +commits to `ctao/content` directly. Pushing to Bitbucket does not deploy. +Server setup is in [deploy/README.md](deploy/README.md). ## Where things live | Path | Purpose | |---|---| -| `src/layouts/Base.astro` | Mockup header, navigation, footer, page shell | -| `src/components/PageHero.astro` | Breadcrumb, title, introduction, nebula | -| `src/pages/example/[slug].astro` | One reusable editable-page template | -| `src/data/examples.json` | Fixed submenu labels, order, and slugs | -| `src/data/placeholders.json` | Empty destinations | -| `src/styles/global.css` | Mockup styles plus Markdown/placeholder variants | -| `public/assets/`, `public/header.js` | Mockup assets and menu behavior | -| `sample-content/` | Versioned seed content for local use and Gitea setup | -| `src/content/pages/`, `public/uploads/` | Ignored runtime content overlay | -| `public/admin/config.yml` | Four fixed CMS files and Gitea configuration | -| `scripts/preview-styles.mjs` | Generates CMS body preview styles from site CSS | +| `src/layouts/Base.astro` | Header, navigation, footer, page shell | +| `src/pages/example/[slug].astro` | Template for the editable Example pages | +| `src/data/examples.json` | Example pages: menu labels, order, slugs | +| `src/data/placeholders.json` | Placeholder pages | +| `src/styles/global.css` | All site styles | +| `public/admin/config.yml` | CMS fields and Gitea connection | +| `scripts/preview-styles.mjs` | Builds the CMS preview styles from `global.css` | +| `scripts/content.mjs` | Links the content checkout into the site | -The vendored CMS bundle remains in `public/vendor/sveltia-cms.js`. Dependencies -remain pinned; this simplification adds no application dependencies. +To add an Example page, add it to `src/data/examples.json` and +`public/admin/config.yml`, and commit its Markdown file to `ctao/content`. diff --git a/deploy/README.md b/deploy/README.md index 62adb8b..da633ac 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -31,13 +31,11 @@ missing). Everything else runs inside containers. ## Publish the simplified portal from `main` -1. In a checkout of `ctao/content`, switch to `main`. Copy the missing - Example files from `sample-content/pages/` and - `sample-content/uploads/hero.jpg` from this code repository into its root - `pages/` and `uploads/` directories, then commit the seed files. Preserve - any existing editorial versions of these files. Existing - news or other page files can remain in the content history; this portal - loads only the four Example files. +1. Check that `ctao/content:main` contains the four Example files in `pages/` + (listed in `src/data/examples.json`) and the uploads they reference. They + are already committed there (`a3452d7`, "demo lorem ipsum pages"). News and + other page files can remain in the content repository; this portal loads + only the four Example files. 2. Push the content branch to Gitea as `ctao/content:main`, and the code branch as `ctao/portal:main`. These are separate pushes; Bitbucket is not the deployment remote. @@ -90,9 +88,8 @@ the previous portal may be restored. password never lands in a file or shell history: `podman exec -it ctao-demo-gitea gitea admin user create --admin --username --email --random-password` 7. **[W]** In the Gitea UI: create org `ctao` with repos `portal` and - `content` (both public read). Seed the content `main` branch with - `sample-content/pages/` and `sample-content/uploads/` as described above. - Then, from your workstation, push both over + `content` (both public read). The content `main` branch needs the four + Example pages described above. Then, from your workstation, push both over the vhost with a repo-scoped token: `git push https://:@astro-git.isl-dev.grid.cyfronet.pl/ctao/portal.git main` `git push https://:@astro-git.isl-dev.grid.cyfronet.pl/ctao/content.git main` diff --git a/package.json b/package.json index da6cf7b..dcd206e 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,13 @@ "version": "0.1.0", "private": true, "scripts": { - "content:setup": "node scripts/content.mjs", - "content:sync": "node scripts/content.mjs", - "content:local": "node scripts/local-content.mjs", - "predev": "node scripts/content.mjs --ensure && node scripts/preview-styles.mjs", + "content:link": "node scripts/content.mjs --relink", + "predev": "node scripts/content.mjs && node scripts/preview-styles.mjs", "dev": "astro dev", - "prebuild": "node scripts/content.mjs --ensure && node scripts/preview-styles.mjs", + "prebuild": "node scripts/content.mjs && node scripts/preview-styles.mjs", "build": "astro build", "preview": "astro preview", - "precheck": "node scripts/content.mjs --ensure && node scripts/preview-styles.mjs", + "precheck": "node scripts/content.mjs && node scripts/preview-styles.mjs", "check": "astro check && astro build && node scripts/check-links.mjs" }, "dependencies": { diff --git a/public/admin/config.yml b/public/admin/config.yml index a8bc9b5..efc1a16 100644 --- a/public/admin/config.yml +++ b/public/admin/config.yml @@ -5,7 +5,6 @@ backend: name: gitea # Keep this branch in sync with CONTENT_BRANCH in deploy/build.sh. - # Seed the content branch from sample-content/ before deploying. repo: ctao/content branch: main base_url: https://astro-git.isl-dev.grid.cyfronet.pl diff --git a/sample-content/pages/consectetur-adipiscing.md b/sample-content/pages/consectetur-adipiscing.md deleted file mode 100644 index 4c9c760..0000000 --- a/sample-content/pages/consectetur-adipiscing.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "Consectetur adipiscing" -introduction: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." ---- - -## Consectetur adipiscing - -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - -- Lorem ipsum dolor sit amet. -- Consectetur adipiscing elit. -- Sed do eiusmod tempor incididunt. diff --git a/sample-content/pages/dolor-sit-amet.md b/sample-content/pages/dolor-sit-amet.md deleted file mode 100644 index 311b501..0000000 --- a/sample-content/pages/dolor-sit-amet.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: "Dolor sit amet" -introduction: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." -image: "/uploads/hero.jpg" -imageAlt: "Telescope beneath a star-filled night sky" -imageCaption: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." ---- - -## Dolor sit amet - -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - - - -Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam. - -Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur. At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti. - -Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. diff --git a/sample-content/pages/lorem-ipsum.md b/sample-content/pages/lorem-ipsum.md deleted file mode 100644 index 0a0deca..0000000 --- a/sample-content/pages/lorem-ipsum.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: "Lorem Ipsum Dolor Sit Amet Consectetur" -introduction: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." -image: "/uploads/hero.jpg" -imageAlt: "Telescope beneath a star-filled night sky" -imageCaption: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." -buttonLabel: "Lorem ipsum" -buttonUrl: "/example/dolor-sit-amet/" ---- - -## Lorem Ipsum Dolor - -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat ([lorem ipsum](/example/dolor-sit-amet/)). Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - -Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo [inventore veritatis](/example/consectetur-adipiscing/) et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia [consequuntur magni](/example/sed-do-eiusmod/) dolores eos qui ratione voluptatem sequi nesciunt. - -> Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. - -## Consectetur Adipiscing Elit - -Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam. - -Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur. At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti. - -Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. diff --git a/sample-content/pages/sed-do-eiusmod.md b/sample-content/pages/sed-do-eiusmod.md deleted file mode 100644 index ffe6039..0000000 --- a/sample-content/pages/sed-do-eiusmod.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: "Sed do eiusmod" -introduction: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." ---- - -## Sed do eiusmod - -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - - - -Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam. - -Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur. At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti. - -Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. diff --git a/sample-content/uploads/hero.jpg b/sample-content/uploads/hero.jpg deleted file mode 100644 index fdc915c..0000000 Binary files a/sample-content/uploads/hero.jpg and /dev/null differ diff --git a/scripts/content.mjs b/scripts/content.mjs index 0445c5d..4b082ba 100644 --- a/scripts/content.mjs +++ b/scripts/content.mjs @@ -1,34 +1,50 @@ -// Copy the four editable files into Astro's ignored content directory. -// --ensure seeds only a fresh checkout; an incomplete existing set fails loudly. -import { cpSync, existsSync, mkdirSync, readFileSync } from 'node:fs'; -import { resolve, join } from 'node:path'; +// Point the site at a checkout of the ctao/content repository (default: the +// sibling ../content clone), the same content the live demo builds from. +// src/content/pages and public/uploads become links into that checkout, so +// saves from Sveltia's local-repository mode show up in `npm run dev` directly. +// +// node scripts/content.mjs link if missing, then check the pages (pre-hooks) +// npm run content:link -- [dir] replace existing links or copies +// +// The deploy build copies the content into place instead (deploy/build.sh); +// existing directories are left alone unless --relink is given. +import { existsSync, lstatSync, mkdirSync, readFileSync, rmSync, symlinkSync, unlinkSync } from 'node:fs'; +import { dirname, join, relative, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const root = fileURLToPath(new URL('../', import.meta.url)); const examples = JSON.parse(readFileSync(join(root, 'src/data/examples.json'), 'utf8')); -const ensure = process.argv.includes('--ensure'); -const source = resolve(process.argv[2] && !ensure ? process.argv[2] : join(root, 'sample-content')); +const relink = process.argv.includes('--relink'); +const source = resolve(root, process.argv.slice(2).find((arg) => !arg.startsWith('--')) ?? '../content'); +const targets = [ + { from: join(source, 'pages'), to: join(root, 'src/content/pages') }, + { from: join(source, 'uploads'), to: join(root, 'public/uploads') }, +]; + +const exists = (path) => { + try { lstatSync(path); return true; } catch { return false; } +}; + +for (const { from, to } of targets) { + if (exists(to) && !relink) continue; + if (!existsSync(from)) { + throw new Error(`Missing ${from}. Clone the content repository first:\n` + + ` git clone https://astro-git.isl-dev.grid.cyfronet.pl/ctao/content.git ${relative(root, source) || '.'}\n` + + 'or pass another checkout: npm run content:link -- '); + } + if (exists(to)) { + // Remove only the link itself, never the content it points to. + if (lstatSync(to).isSymbolicLink()) unlinkSync(to); + else rmSync(to, { recursive: true }); // an old copied overlay + } + mkdirSync(dirname(to), { recursive: true }); + symlinkSync(from, to, 'junction'); // a junction needs no admin rights on Windows + console.log(`Linked ${relative(root, to)} -> ${from}`); +} + const pages = join(root, 'src/content/pages'); -const uploads = join(root, 'public/uploads'); - -function validate(directory) { - for (const { slug } of examples) { - if (!existsSync(join(directory, `${slug}.md`))) { - throw new Error(`Missing ${join(directory, `${slug}.md`)}. Run npm run content:setup or npm run content:sync -- .`); - } +for (const { slug } of examples) { + if (!existsSync(join(pages, `${slug}.md`))) { + throw new Error(`Missing ${join(pages, `${slug}.md`)}. Check the content checkout, or run npm run content:link -- .`); } } - -if (ensure && existsSync(pages)) { - validate(pages); -} else { - validate(join(source, 'pages')); - if (!existsSync(join(source, 'uploads'))) throw new Error(`Missing uploads directory in ${source}`); - mkdirSync(pages, { recursive: true }); - mkdirSync(uploads, { recursive: true }); - for (const { slug } of examples) { - cpSync(join(source, 'pages', `${slug}.md`), join(pages, `${slug}.md`)); - } - cpSync(join(source, 'uploads'), uploads, { recursive: true }); - console.log(`Example content copied from ${source}`); -} diff --git a/scripts/link-content.sh b/scripts/link-content.sh deleted file mode 100755 index 3cdb0ea..0000000 --- a/scripts/link-content.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -# Local dev: put the content repo's files where the build expects them. -# Usage: ./scripts/link-content.sh [path-to-content-clone] (default ../ctao-content) -# Cross-platform implementation is shared with npm run content:sync. -set -euo pipefail -cd "$(dirname "$0")/.." -node scripts/content.mjs "${1:-../ctao-content}" diff --git a/scripts/local-content.mjs b/scripts/local-content.mjs deleted file mode 100644 index a75714f..0000000 --- a/scripts/local-content.mjs +++ /dev/null @@ -1,19 +0,0 @@ -// Prepare a disposable content repository for Sveltia's local folder picker. -import { cpSync, existsSync, mkdirSync } from 'node:fs'; -import { execFileSync } from 'node:child_process'; -import { fileURLToPath } from 'node:url'; -import { join } from 'node:path'; - -const root = fileURLToPath(new URL('../', import.meta.url)); -const local = join(root, '.tmp/content'); -if (!existsSync(local)) { - mkdirSync(local, { recursive: true }); - cpSync(join(root, 'sample-content/pages'), join(local, 'pages'), { recursive: true }); - cpSync(join(root, 'sample-content/uploads'), join(local, 'uploads'), { recursive: true }); -} -if (!existsSync(join(local, '.git'))) { - execFileSync('git', ['init', '--quiet', '--initial-branch=main', local], { stdio: 'inherit' }); -} -execFileSync(process.execPath, [join(root, 'scripts/content.mjs'), local], { stdio: 'inherit' }); -console.log(`In Sveltia, choose Work with Local Repository and select: ${local}`); -console.log('After saving, run: npm run content:sync -- .tmp/content'); diff --git a/src/pages/admin/index.astro b/src/pages/admin/index.astro index a252274..e59865c 100644 --- a/src/pages/admin/index.astro +++ b/src/pages/admin/index.astro @@ -35,7 +35,7 @@ const previewNames = examples.map(({ slug }) => slug); // Generated from the site's CSS; see scripts/preview-styles.mjs. window.CMS?.registerPreviewStyle?.('/admin/preview.css'); -