chives-{image,video}: allow setting a basePath for media files
- ID
9e5d7ce- date
2026-10-31 20:20:42+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
e70630e- message
chives-{image,video}: allow setting a basePath for media files This allows you to prepend a path for the rendered media files. This is useful if the HTML file is in a subfolder and not the root of the media collection.- changed files
Changed files
CHANGELOG.md (9111 → 9375)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c404286..a7c411d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
# CHANGELOG
+## v59 - 2026-10-31
+
+The `<chives-image>` and `<chives-video>` web components now allow setting a `basePath`, which prepends a path to the rendered media files.
+This is useful if they're being used in an HTML file which isn't in the root of the media collection.
+
## v58 - 2026-10-29
Add a missing `pytest` dependency for the `browser_fixtures` extra.
javascript/chives-image.js (6561 → 6918)
diff --git a/javascript/chives-image.js b/javascript/chives-image.js
index 92f2d99..f058b0f 100644
--- a/javascript/chives-image.js
+++ b/javascript/chives-image.js
@@ -1,5 +1,5 @@
// javascript/chives-image.js
-// chives version: 58
+// chives version: 59
// ChivesImage provides a <chives-image> component that renders
// an ImageEntity on a web page.
@@ -26,6 +26,9 @@ class ChivesImage extends HTMLElement {
// Defaults to "full".
"variant",
//
+ // Optional path prepended to the path/thumbnail path.
+ "base-path",
+ //
// Fields from ImageEntity
"path",
"thumbnail-path",
@@ -99,6 +102,11 @@ class ChivesImage extends HTMLElement {
this.setAttribute("variant", "thumbnail");
}
+ // basePath allows you to set the basePath where media is loaded from.
+ set basePath(p) {
+ this.setAttribute("base-path", p);
+ }
+
// Rather than re-rendering the component on every attribute change
// (which will fire seven times when we set imageEntity), batch render
// changes together into the next event loop tick.
@@ -127,6 +135,9 @@ class ChivesImage extends HTMLElement {
const href = this.getAttribute("href") || "";
const variant = this.getAttribute("variant") || "full";
+ const basePath = this.getAttribute("base-path") !== null
+ ? this.getAttribute("base-path") + "/"
+ : "";
const path = this.getAttribute("path");
const thumbnailPath = this.getAttribute("thumbnail-path");
@@ -137,12 +148,12 @@ class ChivesImage extends HTMLElement {
const hasTransparency = this.hasAttribute("has-transparency");
// Decide which variant of the image will be displayed.
- const displayPath =
- variant === "full"
+ const displayPath = basePath +
+ (variant === "full"
? path
: typeof thumbnailPath === 'string'
? thumbnailPath
- : path;
+ : path);
// Choose which wrapper element to use. Use a link if there's
// an href, otherwise a wrapper div.
javascript/chives-pagination.js (4929 → 4929)
diff --git a/javascript/chives-pagination.js b/javascript/chives-pagination.js
index 1c7e6c8..44a0028 100644
--- a/javascript/chives-pagination.js
+++ b/javascript/chives-pagination.js
@@ -1,5 +1,5 @@
// javascript/chives-pagination.js
-// chives version: 58
+// chives version: 59
// ChivesPagination manages the pagination of a collection of items.
//
javascript/chives-sorting.js (5034 → 5034)
diff --git a/javascript/chives-sorting.js b/javascript/chives-sorting.js
index 28df98e..dc98319 100644
--- a/javascript/chives-sorting.js
+++ b/javascript/chives-sorting.js
@@ -1,5 +1,5 @@
// javascript/chives-sorting.js
-// chives version: 58
+// chives version: 59
// ChivesSorting manages the sorting of a collection of items.
//
javascript/chives-video.js (5147 → 5521)
diff --git a/javascript/chives-video.js b/javascript/chives-video.js
index abab4fd..be5514c 100644
--- a/javascript/chives-video.js
+++ b/javascript/chives-video.js
@@ -1,5 +1,5 @@
// javascript/chives-video.js
-// chives version: 58
+// chives version: 59
// ChivesVideo provides a <chives-video> component that renders
// a VideoEntity on a web page.
@@ -22,6 +22,9 @@ class ChivesVideo extends HTMLElement {
"poster-path",
"subtitles",
"autoplay",
+ //
+ // Optional path prepended to the path/poster path.
+ "base-path",
];
}
@@ -56,6 +59,11 @@ class ChivesVideo extends HTMLElement {
}
}
+ // basePath allows you to set the basePath where media is loaded from.
+ set basePath(p) {
+ this.setAttribute("base-path", p);
+ }
+
// Rather than re-rendering the component on every attribute change
// (which will fire seven times when we set imageEntity), batch render
// changes together into the next event loop tick.
@@ -87,6 +95,9 @@ class ChivesVideo extends HTMLElement {
const height = this.getAttribute("height");
const posterPath = this.getAttribute("poster-path");
+ const basePath = this.getAttribute("base-path") !== null
+ ? this.getAttribute("base-path") + "/"
+ : "";
const subtitles = (this.getAttribute("subtitles") || "")
.split(";")
@@ -134,14 +145,14 @@ class ChivesVideo extends HTMLElement {
}
video.preload = "none";
- video.src = path;
- video.poster = posterPath;
+ video.src = basePath + path;
+ video.poster = basePath + posterPath;
subtitles.forEach(s => {
const track = document.createElement("track");
track.kind = "subtitles";
track.label = s.label;
- track.src = s.path;
+ track.src = basePath + s.path;
track.srclang = s.label.substring(0, 2).toLowerCase()
video.appendChild(track);
});
python/chives/__init__.py (391 → 391)
diff --git a/python/chives/__init__.py b/python/chives/__init__.py
index 0ce13e4..35b9dcc 100644
--- a/python/chives/__init__.py
+++ b/python/chives/__init__.py
@@ -11,4 +11,4 @@ I share across multiple sites.
"""
-__version__ = "58"
+__version__ = "59"
python/chives/browser_fixtures.py (12115 → 12144)
diff --git a/python/chives/browser_fixtures.py b/python/chives/browser_fixtures.py
index c26f5a3..3c0715b 100644
--- a/python/chives/browser_fixtures.py
+++ b/python/chives/browser_fixtures.py
@@ -123,10 +123,10 @@ def page(browser: Browser) -> Iterator[Page]:
for msg in console_messages
if msg.type == "error" or msg.type == "warning"
]
- assert console_errors == []
+ assert console_errors == [], console_errors
# Check there weren't any page errors
- assert page_errors == []
+ assert page_errors == [], page_errors
class PageError(Exception):
tests/javascript/test_chives_image.py (7026 → 9206)
diff --git a/tests/javascript/test_chives_image.py b/tests/javascript/test_chives_image.py
index e083e51..00ffb08 100644
--- a/tests/javascript/test_chives_image.py
+++ b/tests/javascript/test_chives_image.py
@@ -2,12 +2,15 @@
Tests for the <chives-image> web component.
"""
+import contextlib
import json
from pathlib import Path
+import shutil
from playwright.sync_api import expect, Page
import pytest
+from chives.browser_fixtures import file_uri
from chives.media import create_image_entity
@@ -245,3 +248,71 @@ def test_animated_gif_with_video_thumbnail(page: Page, tmp_path: Path) -> None:
video_elem = chives_elem.locator("video")
expect(video_elem).to_be_visible()
assert (video_elem).get_attribute("src") == e["thumbnail_path"]
+
+
+def test_image_with_base_path(page: Page, tmp_path: Path) -> None:
+ """
+ If you set the basePath on an image, it's prepended to the path.
+ """
+ # Create an HTML file at tmp_path/viewer/index.html
+ with open("javascript/chives-image.js") as f:
+ component_js = f.read()
+
+ (tmp_path / "viewer").mkdir()
+ html_path = tmp_path / "viewer/index.html"
+ html_path.write_text("<script>" + component_js + "</script>")
+
+ page.goto(file_uri(html_path))
+
+ # Copy the media files to tmp_path/media
+ (tmp_path / "media").mkdir()
+ shutil.copyfile(
+ "tests/fixtures/media/470906.png",
+ tmp_path / "media/470906.png",
+ )
+
+ # Create the video entity with the working directory set to tmp_path/media
+ with contextlib.chdir(tmp_path / "media"):
+ e = create_image_entity(
+ "470906.png",
+ thumbnail_config={
+ "out_dir": ".thumbnails",
+ "width": 25,
+ },
+ )
+
+ # Check we get the correct path if we render the full-sized image.
+ page.evaluate(
+ """
+ const elem = document.createElement("chives-image");
+ elem.imageEntity = %s;
+ elem.basePath = '../media';
+ document.body.appendChild(elem);
+ """
+ % json.dumps(e),
+ )
+
+ chives_elem = page.locator("chives-image")
+
+ img_elem = chives_elem.locator("img")
+ expect(img_elem).to_be_visible()
+ assert (img_elem).get_attribute("src") == "../media/470906.png"
+
+ # Check we get the correct path if we render the thumbnail image.
+ page.evaluate(
+ """
+ document.querySelector("chives-image").remove();
+ const elem = document.createElement("chives-image");
+ elem.imageEntity = %s;
+ elem.variant = 'thumbnail';
+ elem.basePath = '../media';
+ document.body.appendChild(elem);
+ """
+ % json.dumps(e),
+ )
+
+ chives_elem = page.locator("chives-image")
+
+ img_elem = chives_elem.locator("img")
+ expect(img_elem).to_be_visible()
+ assert (img_elem).get_attribute("src") == "../media/.thumbnails/470906.png"
tests/javascript/test_chives_video.py (5095 → 7022)
diff --git a/tests/javascript/test_chives_video.py b/tests/javascript/test_chives_video.py
index b91ef6b..78db232 100644
--- a/tests/javascript/test_chives_video.py
+++ b/tests/javascript/test_chives_video.py
@@ -2,11 +2,15 @@
Tests for the <chives-image> web component.
"""
+import contextlib
import json
+from pathlib import Path
+import shutil
from playwright.sync_api import expect, Page
import pytest
+from chives.browser_fixtures import file_uri
from chives.media import create_video_entity
@@ -79,6 +83,60 @@ def test_video_with_autoplay(page: Page) -> None:
assert video_elem.get_attribute("autoplay") == ""
+def test_video_with_base_path(page: Page, tmp_path: Path) -> None:
+ """
+ If you set the basePath on a video, it's prepended to the path.
+ """
+ # Create an HTML file at tmp_path/viewer/index.html
+ with open("javascript/chives-video.js") as f:
+ component_js = f.read()
+
+ (tmp_path / "viewer").mkdir()
+ html_path = tmp_path / "viewer/index.html"
+ html_path.write_text("<script>" + component_js + "</script>")
+
+ page.goto(file_uri(html_path))
+
+ # Copy the media files to tmp_path/media
+ (tmp_path / "media").mkdir()
+ shutil.copyfile(
+ "tests/fixtures/media/wings_tracking_shot.mp4",
+ tmp_path / "media/wings_tracking_shot.mp4",
+ )
+ shutil.copyfile(
+ "tests/fixtures/media/wings_tracking_shot.jpg",
+ tmp_path / "media/wings_tracking_shot.jpg",
+ )
+
+ # Create the video entity with the working directory set to tmp_path/media
+ with contextlib.chdir(tmp_path / "media"):
+ e = create_video_entity(
+ video_path="wings_tracking_shot.mp4",
+ poster_path="wings_tracking_shot.jpg",
+ subtitles_path="wings_tracking_shot.en.vtt",
+ )
+
+ page.evaluate(
+ """
+ const elem = document.createElement("chives-video");
+ elem.videoEntity = %s;
+ elem.basePath = "../media";
+ document.body.appendChild(elem);
+ """
+ % json.dumps(e),
+ )
+
+ chives_elem = page.locator("chives-video")
+
+ video_elem = chives_elem.locator("video")
+ expect(video_elem).to_be_visible()
+ assert video_elem.get_attribute("src") == "../media/wings_tracking_shot.mp4"
+ assert video_elem.get_attribute("poster") == "../media/wings_tracking_shot.jpg"
+
+ track_elem = video_elem.locator("track")
+ assert track_elem.get_attribute("src") == "../media/wings_tracking_shot.en.vtt"
+
+
@pytest.mark.parametrize(
"width, height, orientation",
[