fix: scroll to tab header + add crowdin config

This commit is contained in:
NGPixel
2026-08-22 18:31:50 -04:00
parent aae079731b
commit b1261f4606
6 changed files with 66 additions and 14 deletions
+11 -2
View File
@@ -29,6 +29,7 @@ jobs:
run: |
yq -iP '.version = strenv(REL_VERSION)' backend/package.json -o json
yq -iP '.version = strenv(REL_VERSION)' frontend/package.json -o json
- name: Build Assets
working-directory: frontend
run: |
@@ -57,8 +58,8 @@ jobs:
context: .
file: dev/build/Dockerfile
push: true
platforms: linux/amd64
# platforms: linux/amd64,linux/arm64
# platforms: linux/amd64
platforms: linux/amd64,linux/arm64
tags: |
ghcr.io/requarks/wiki:3.0.0-alpha
ghcr.io/requarks/wiki:${{ env.REL_VERSION }}
@@ -82,3 +83,11 @@ jobs:
name: build
path: wiki-js.tar.gz
- name: Upload translations source file
uses: crowdin/github-action@v2
env:
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
with:
command: 'upload'
command_args: '--config dev/crowdin.yml'
+7
View File
@@ -45,6 +45,13 @@ from inside `backend/`. It boots in three phases: `preBoot()` (config → db →
scheduler → event emitters), `initHTTPServer()` (Fastify plugins, auth, routes), `postBoot()`
(refresh locales/strategies/sites from disk & db, start scheduler).
Started with **`--no-experimental-webstorage`** — by the npm scripts and by the production image's
`CMD`, which is the only reason a bare `node backend` still opens with an experimental warning about
`localStorage`. Nothing here uses Web Storage; `lib0`, under yjs, probes for it as it loads the way a
library that runs in a browser too has to, and Node 26 answers that probe with a warning instead of a
value unless `--localstorage-file` is given. Off, the global is absent and the probe takes its node
path in silence.
- `api/` — REST route plugins, one file per resource (`sites.ts`, `users.ts`, `pages.ts`,
`system.ts`, `locales.ts`, `authentication.ts`), registered by `api/index.ts` under the `/_api`
prefix.
+2 -2
View File
@@ -25,8 +25,8 @@
"type": "module",
"main": "index.ts",
"scripts": {
"start": "cd .. && node backend",
"dev": "cd .. && nodemon backend --watch backend --ext js,ts,json",
"start": "cd .. && node --no-experimental-webstorage backend",
"dev": "cd .. && nodemon --no-experimental-webstorage backend --watch backend --ext js,ts,json",
"typecheck": "tsc",
"typecheck:watch": "tsc --watch",
"ncu": "ncu -i",
+32 -9
View File
@@ -12,6 +12,16 @@ import { DarkMode } from '../shared/theme.js'
*/
const REVEAL_EVENT = 'block-reveal'
/**
* The room a heading in an article is given above it when it is scrolled to, in pixels.
*
* The article scrolls in its own column inside a fixed shell, so a heading brought into view stops
* clear of the column's top edge rather than flush against it. Written out because a block cannot read
* the app's stylesheet: this is the `scroll-margin-top: 1.25rem` that `_page-contents.scss` puts on
* every heading, and the two have to be kept in step.
*/
const HEADING_CLEARANCE = 20
/**
* Block Tabs
*/
@@ -230,27 +240,40 @@ Content of the second tab.
}
/**
* Keep the strip on screen when something inside a panel is scrolled to.
* Keep the whole block on screen when something inside a panel is scrolled to.
*
* A heading carries a `scroll-margin-top` so it does not land flush against the top edge, but that
* margin knows nothing about the strip standing above it — following a link to a heading in a tab
* would scroll the tabs themselves out of view, leaving the reader in a panel with no way to see
* which one they were in. Set on the elements because the content is slotted, and measured because
* the strip is as tall as the labels wrapped onto however many rows.
* a block cannot be told in CSS how tall its own strip is.
*
* Measured from the panel back up to the block, so that what a scroll clears is everything above
* the panel rather than the strip alone: the strip may have wrapped onto two rows, the frame draws
* a border, and the panel pads itself. Clearing only the strip's height put the tabs *just* on the
* edge of the column with nothing above them, and a tab acting as a page heading — `header` on
* `block-tab`, anchored on the panel because the label is an attribute and no heading in the page
* carries it — is aimed at from the contents list, so it landed with its own label against the
* edge. `HEADING_CLEARANCE` on top is what every heading in an article gets, so a tab arrived at
* from the contents list sits where a section heading would.
*/
_applyScrollMargin() {
const strip = this.renderRoot.querySelector('.strip')
if (!strip) {
return
}
const margin = `${strip.offsetHeight + 20}px`
/*
From the open panel, which is the only one with a box to measure. They all sit in the same
place, so its offset is every panel's — and a block inside a panel that is not showing measures
nothing at all, which is why the strip's height stands in until there is something to read.
*/
const open = this._tabs[this.active]?.panel
const above = open
? Math.round(open.getBoundingClientRect().top - this.getBoundingClientRect().top)
: strip.offsetHeight
const margin = `${above + HEADING_CLEARANCE}px`
for (const { panel } of this._tabs) {
/*
The panel as well as what is in it. A tab whose label is a page heading — `header` on
`block-tab` — is anchored on the panel element itself, since the label is an attribute and
there is no heading in the page to carry the anchor, so the panel is what a contents click
scrolls to and it needs the same margin as any heading in it.
*/
// -> The panel as well as what is in it: either can be what a link or the contents list aims at
panel.style.setProperty('scroll-margin-top', margin)
for (const child of panel.children) {
child.style.setProperty('scroll-margin-top', margin)
+8 -1
View File
@@ -58,4 +58,11 @@ VOLUME ["/wiki/data/content"]
EXPOSE 3000
EXPOSE 3443
CMD ["node", "backend"]
# Web Storage off, which is what it was before Node 26 turned it on by default.
#
# Nothing here uses `localStorage`, but `lib0` -- under yjs, which is what makes an editing session
# collaborative -- probes for it as it loads, the way a library that runs in both a browser and node
# has to. Without `--localstorage-file` that probe is answered with an experimental warning rather
# than a value, so the first line of every container's log was a warning about a feature the server
# does not use. Off, the global is absent and the probe takes its node path in silence.
CMD ["node", "--no-experimental-webstorage", "backend"]
+6
View File
@@ -0,0 +1,6 @@
project_id: '921805'
preserve_hierarchy: false
files:
- source: 'backend/locales/en.json'
dest: 'app.json'
update_option: 'update_as_unapproved'