* Serve the legacy /web/*.js names from their .mjs siblings The build emitted <name>.js alongside <name>.mjs until the two compilations were collapsed into one. Dropping the second compilation was right, but it removed URLs the project itself had published: the v1.16.4 SPA documentation named /web/embed.js directly and its loader snippet requested .js. Pages that hard-coded those names now 404 with no deprecation. webFiles.Open retries a missing .js against the .mjs sibling. The bundles contain no import or export, so the same bytes serve both names. The retry runs only once both sources report the name missing, so a real .js still wins, and an unreadable sibling reports its own error rather than being flattened into the requested file's 404. Related to #2178 * Reuse only the comments iframe embed created createInstance took root.firstElementChild as its iframe, so anything a page left inside #remark42 was adopted instead. A <noscript> fallback became the "iframe", createIframe never ran, and the height messages went to an element that cannot show comments. That also defeats the placeholder support, which promises content in the root is cleared once the iframe reports inited: a text placeholder works, but any element placeholder is mistaken for the iframe, so inited never arrives and the cleanup never runs. The iframe now carries data-remark42-iframe and the lookup is scoped to a direct child, so a second createInstance still reuses it while nothing else in the root can be adopted. Related to #1990 * Assert the backup contents rather than the compressed size TestBackup_MakeBackup and TestBackup_Do pinned the gzip output at 52 bytes, which ties them to the exact output of compress/flate. The same input encodes to 57 bytes on go 1.27, so both fail for anyone building on a toolchain newer than the one CI pins. They now read the backup back and compare it against what the exporter wrote, which is what the tests were reaching for and does not move with the compressor. The payload is a shared constant so the two cannot drift.
71 lines
2.3 KiB
Go
71 lines
2.3 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"io/fs"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// webFiles serves /web from two sources: a name present in the frontend build is served from there,
|
|
// and any other name from the assets embedded in the binary.
|
|
type webFiles struct {
|
|
frontend fs.FS
|
|
embedded fs.FS
|
|
}
|
|
|
|
// Open resolves the name against both sources, and answers a missing .js with the .mjs sibling.
|
|
// The build stopped emitting .js while integrations still request it; the bundles carry no module
|
|
// syntax, so the same bytes serve both names.
|
|
func (w webFiles) Open(name string) (fs.File, error) {
|
|
// fs.ValidPath alone is not enough: it accepts names the operating system rejects, NUL among
|
|
// them, and os.DirFS turns those into fs.ErrInvalid, which renders as 500 rather than 404
|
|
if _, err := filepath.Localize(name); err != nil || !fs.ValidPath(name) {
|
|
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
|
|
}
|
|
|
|
f, err := w.open(name)
|
|
if err == nil {
|
|
return f, nil
|
|
}
|
|
if !errors.Is(err, fs.ErrNotExist) || !strings.HasSuffix(name, ".js") {
|
|
return nil, err
|
|
}
|
|
|
|
alias, aliasErr := w.open(strings.TrimSuffix(name, ".js") + ".mjs")
|
|
if aliasErr == nil {
|
|
return alias, nil
|
|
}
|
|
if !errors.Is(aliasErr, fs.ErrNotExist) {
|
|
return nil, aliasErr
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// open looks the name up in the frontend build first. Only a missing file falls through to the
|
|
// embedded assets; every other error is returned so an unreadable file keeps reporting as one
|
|
// rather than being replaced by the embedded copy or reported as missing.
|
|
func (w webFiles) open(name string) (fs.File, error) {
|
|
f, err := w.frontend.Open(name)
|
|
if err == nil {
|
|
return f, nil
|
|
}
|
|
if !errors.Is(err, fs.ErrNotExist) {
|
|
return nil, err
|
|
}
|
|
if name == "." {
|
|
// the embedded set is a flat list of files; only the frontend build answers for the
|
|
// directory itself, so a missing web root reports as missing rather than listing them
|
|
return nil, err
|
|
}
|
|
return w.embedded.Open(name)
|
|
}
|
|
|
|
// emptyFS stands in for a frontend source that could not be opened, so a misconfigured one serves
|
|
// nothing instead of panicking or serving the build at paths it does not belong at
|
|
type emptyFS struct{}
|
|
|
|
func (emptyFS) Open(name string) (fs.File, error) {
|
|
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
|
|
}
|