fix(freelancer-directory): render an uploaded photo (data:image/), not only a linked URL

Both card renderers emitted an <img> only for a URL, so a record whose Image is an
inline data:image/... URI (what the upload step stores today) fell through to the
monogram. The avatar check now allowlists exactly two schemes — https:// and
data:image/ — in build-directory.py and in the page script; anything else (empty,
junk like "gineoirn", another scheme) still falls back to the monogram, and the
value never reaches src unchecked. The lede copy is corrected to match: a photo
shows where the freelancer gave a link OR uploaded an image.
This commit is contained in:
webmaster committed 2026-11-03 13:04:06 +00:00
1 parent e067fec168
commit 5ce14ea666
1 file changed
+12 -3
+12 -3
View File
@@ -782,7 +782,7 @@
<section class="section-dark" id="directory">
<div class="container">
<h2 class="section-title">Freelancers available</h2>
<p class="dir-lede">Listings are for freelancers who work on their own — that is the declaration we ask each of them for, and we do not verify it. Where a card shows a number of projects, that is a number we recorded — not a rating, a score or an endorsement. A card shows a photo only where the freelancer gave us a link to one; otherwise it shows their initials. We make no claim about a freelancer beyond what they wrote and what we can count ourselves.</p>
<p class="dir-lede">Listings are for freelancers who work on their own — that is the declaration we ask each of them for, and we do not verify it. Where a card shows a number of projects, that is a number we recorded — not a rating, a score or an endorsement. A card shows a photo whether the freelancer gave us a link to one or uploaded an image; otherwise it shows their initials. We make no claim about a freelancer beyond what they wrote and what we can count ourselves.</p>
<!-- Rendered at build time from our freelancer webhook (one call, no CRM),
so the list works with JavaScript disabled. The filter controls below are
@@ -1741,6 +1741,15 @@
return v.indexOf("http://") === 0 || v.indexOf("https://") === 0 || v.indexOf("/") === 0;
}
/* A card renders a photo only for an allowlisted scheme: an https URL or an inline
data:image/ URI. Anything else — empty, junk like "gineoirn", another scheme —
falls back to the monogram. The freelancer controls this field, so it never
reaches src unchecked. (Separate from isLink(), which the *link* renderer also
uses and which accepts http:// and relative paths.) */
function isImageSrc(v) {
return v.indexOf("https://") === 0 || v.indexOf("data:image/") === 0;
}
function liveEl(tag, cls, text) {
var node = document.createElement(tag);
if (cls) node.className = cls;
@@ -1786,8 +1795,8 @@
var photo = String(rec.Image || "").trim();
var avatar;
if (isLink(photo)) {
avatar = liveEl("img", "dev-photo"); // a real URL only — never a base64 blob
if (isImageSrc(photo)) {
avatar = liveEl("img", "dev-photo"); // https URL or data:image/ URI only
avatar.src = photo;
avatar.alt = "";
avatar.width = 72;