summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Peter Christen <mc@yacy.net>2026-01-03 02:55:13 +0100
committerMichael Peter Christen <mc@yacy.net>2026-01-03 02:55:13 +0100
commite2d0050a1c15df8fda56edb5bc559d84149e6633 (patch)
tree86c90e82408efad8993108c1b5bcb2028b51514e
parent5645c3c55deba7a15033ee3d13c173eac44877d8 (diff)
Fix image preview licensing and show 20 images per page, fixes
https://github.com/yacy/yacy_search_server/issues/438 Use separate one‑time licenses for image thumbnails and full previews to prevent preview failures after thumbnail loads, and set the Images tab to request 20 results by default via the content‑domain switch form.
-rw-r--r--htroot/js/yacysearch.js30
-rw-r--r--htroot/yacysearchitem.html4
-rw-r--r--htroot/yacysearchtrailer.html28
-rw-r--r--source/net/yacy/htroot/yacysearch.java4
-rw-r--r--source/net/yacy/htroot/yacysearchitem.java26
5 files changed, 64 insertions, 28 deletions
diff --git a/htroot/js/yacysearch.js b/htroot/js/yacysearch.js
index dc9a19868..aaf317ff2 100644
--- a/htroot/js/yacysearch.js
+++ b/htroot/js/yacysearch.js
@@ -294,10 +294,32 @@ function toggleMoreTags(button, moreTagsId) {
* {HTMLImageElement} the html img element that could not be rendered
*/
function handleResultThumbError(imgElem) {
+ if (imgElem == null) {
+ return;
+ }
+ var stage = imgElem.getAttribute("data-error-stage") || "0";
+ if (stage === "0") {
+ var original = imgElem.getAttribute("data-original");
+ if (original && imgElem.src !== original) {
+ imgElem.setAttribute("data-error-stage", "1");
+ imgElem.src = original;
+ return;
+ }
+ }
+ if (stage === "1") {
+ var fallback = imgElem.getAttribute("data-fallback");
+ if (fallback && imgElem.src.indexOf(fallback) < 0) {
+ imgElem.setAttribute("data-error-stage", "2");
+ imgElem.src = fallback;
+ return;
+ }
+ }
if (imgElem.parentNode != null && imgElem.parentNode.parentNode != null
- && imgElem.parentNode.parentNode.className == "thumbcontainer") {
- /* Hide the thumbnail container */
- imgElem.parentNode.parentNode.className = "thumbcontainer thumbError hidden";
+ && imgElem.parentNode.parentNode.className.indexOf("thumbcontainer") >= 0) {
+ /* Keep the thumbnail container visible, but mark it as failed */
+ if (imgElem.parentNode.parentNode.className.indexOf("thumbError") < 0) {
+ imgElem.parentNode.parentNode.className += " thumbError";
+ }
var errorsInfoElem = document.getElementById("imageErrorsInfo");
if (errorsInfoElem != null && errorsInfoElem.className.indexOf("hidden") >= 0) {
/* Show the image errors information block */
@@ -348,4 +370,4 @@ function hideErrThumbnails() {
if(hideBtn != null) {
hideBtn.className = hideBtn.className + " hidden";
}
-} \ No newline at end of file
+}
diff --git a/htroot/yacysearchitem.html b/htroot/yacysearchitem.html
index 4b76ae33c..9692fc1f0 100644
--- a/htroot/yacysearchitem.html
+++ b/htroot/yacysearchitem.html
@@ -47,7 +47,7 @@
#(showMetadata)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="solr/select?q=id:%22#[urlhash]#%22&start=0&rows=1&core=collection1&wt=html&omitHeader=true" target="_blank">Metadata</a>#(/showMetadata)#
#(showParser)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="ViewFile.html?urlHash=#[urlhash]#&amp;words=#[words]#" target="_blank">Parser</a>#(/showParser)#
#(showCitation)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="api/citation.html?hash=#[urlhash]#&filter=true" target="_blank">Citations</a>#(/showCitation)#
- #(showPictures)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="yacysearch.html?contentdom=image#(authSearch)#::&auth#(/authSearch)#&url=#[link]#&query=#[former]#+inurl:#[link]#" target="_blank">Pictures</a>#(/showPictures)#
+ #(showPictures)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="yacysearch.html?contentdom=image#(authSearch)#::&auth#(/authSearch)#&maximumRecords=20&url=#[link]#&query=#[former]#+inurl:#[link]#" target="_blank">Pictures</a>#(/showPictures)#
#(showCache)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="CacheResource_p.html?url=#[link]#" target="_blank">Cache</a>#(/showCache)#
#(showProxy)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="proxy.html?url=#[link]#" target="_blank">View via proxy</a>#(/showProxy)#
#(showIndexBrowser)#::<span role="separator" aria-orientation="vertical">&nbsp;|&nbsp;</span><a href="IndexBrowser_p.html?path=#[link]#"><img src="env/grafics/minitree.png" width="15" height="8" title="Browse index" alt="Browse index"/></a>#(/showIndexBrowser)#
@@ -59,7 +59,7 @@
::
#(item)#::<div class="thumbcontainer">
<a href="#[hrefFullPreview]#" target="#[target]#" class="thumblink" onclick="return hs.expand(this)">
- <img src="#[hrefCache]#" width="256" height="256" style="#[style]#" alt="#[name]#" onerror="handleResultThumbError(this)"/>
+ <img src="#[hrefCache]#" width="256" height="256" style="#[style]#" alt="#[name]#" data-original="#[href]#" data-fallback="env/grafics/nok.png" onerror="handleResultThumbError(this)"/>
</a>
<div class="highslide-caption"><a href="#[href]#" target="#[target]#" #(noreferrer)#::rel="noreferrer"#(/noreferrer)#>#[name]#</a><br /><a href="#[source]#" target="#[target]#" #(noreferrer)#::rel="noreferrer"#(/noreferrer)#>#[sourcedom]#</a></div>
</div>#(/item)#
diff --git a/htroot/yacysearchtrailer.html b/htroot/yacysearchtrailer.html
index ad4aad107..cdbf32093 100644
--- a/htroot/yacysearchtrailer.html
+++ b/htroot/yacysearchtrailer.html
@@ -22,6 +22,7 @@
<input type="hidden" name="strictContentDom" value="#[strictContentDom]#" />
<input type="hidden" name="former" value="#[former]#" />
<input type="hidden" name="maximumRecords" value="#[maximumRecords]#" />
+ <input type="hidden" name="maximumRecords" value="#[maximumRecords]#" />
<input type="hidden" name="startRecord" value="#[startRecord]#" />
<input type="hidden" name="verify" value="#[search.verify]#" />
<input type="hidden" name="nav" value="#[search.navigation]#" />
@@ -112,28 +113,33 @@ $('#resource-switch-form').popover()
<input id="contentDomTimezoneOffset" type="hidden" name="timezoneOffset" value=""><script>document.getElementById("contentDomTimezoneOffset").value = new Date().getTimezoneOffset();</script>
<div class="btn-group btn-group-justified">
#(searchtext)#::<div class="btn-group btn-group-xs">
- <button type="#(check)#submit::button#(/check)#" name="contentdom" value="text"
- class="btn btn-default#(check)#:: active#(/check)#">Documents</button>
+ <button type="submit" name="contentdom" value="text"
+ class="btn btn-default#(check)#:: active#(/check)#"
+ onclick="this.form.maximumRecords.value = '10';">Documents</button>
</div>
#(/searchtext)#
#(searchimage)#::<div class="btn-group btn-group-xs">
- <button type="#(check)#submit::button#(/check)#" name="contentdom" value="image"
- class="btn btn-default#(check)#:: active#(/check)#">Images</button>
+ <button type="submit" name="contentdom" value="image"
+ class="btn btn-default#(check)#:: active#(/check)#"
+ onclick="this.form.maximumRecords.value = '20';">Images</button>
</div>
#(/searchimage)#
#(searchaudio)#::<div class="btn-group btn-group-xs">
- <button type="#(check)#submit::button#(/check)#" name="contentdom" value="audio"
- class="btn btn-default#(check)#:: active#(/check)#">Audio</button>
+ <button type="submit" name="contentdom" value="audio"
+ class="btn btn-default#(check)#:: active#(/check)#"
+ onclick="this.form.maximumRecords.value = '10';">Audio</button>
</div>
#(/searchaudio)#
#(searchvideo)#::<div class="btn-group btn-group-xs">
- <button type="#(check)#submit::button#(/check)#" name="contentdom" value="video"
- class="btn btn-default#(check)#:: active#(/check)#">Video</button>
+ <button type="submit" name="contentdom" value="video"
+ class="btn btn-default#(check)#:: active#(/check)#"
+ onclick="this.form.maximumRecords.value = '10';">Video</button>
</div>
#(/searchvideo)#
#(searchapp)#::<div class="btn-group btn-group-xs">
- <button type="#(check)#submit::button#(/check)#" name="contentdom" value="app"
- class="btn btn-default#(check)#:: active#(/check)#">Apps</button>
+ <button type="submit" name="contentdom" value="app"
+ class="btn btn-default#(check)#:: active#(/check)#"
+ onclick="this.form.maximumRecords.value = '10';">Apps</button>
</div>
#(/searchapp)#
</div>
@@ -316,4 +322,4 @@ function toggleVisibility(name, count) {
<li><h3>#[headline]#</h3></li>
<li>#[body]#</li>
</ul>
-#(/nav-about)# \ No newline at end of file
+#(/nav-about)#
diff --git a/source/net/yacy/htroot/yacysearch.java b/source/net/yacy/htroot/yacysearch.java
index fa1e452bd..dbe77d1ec 100644
--- a/source/net/yacy/htroot/yacysearch.java
+++ b/source/net/yacy/htroot/yacysearch.java
@@ -256,6 +256,7 @@ public class yacysearch {
// check an determine items per page (max of [100 or configured default]}
final int defaultItemsPerPage = sb.getConfigInt(SwitchboardConstants.SEARCH_ITEMS, 10);
+ final boolean itemsPerPageExplicit = post != null && (post.containsKey("maximumRecords") || post.containsKey("count") || post.containsKey("rows"));
int itemsPerPage = post.getInt("maximumRecords", post.getInt("count", post.getInt("rows", defaultItemsPerPage))); // requested or default // SRU syntax with old property as alternative
// whatever admin has set as default, that's always ok
if (itemsPerPage > defaultItemsPerPage && itemsPerPage > 100) { // if above hardcoded 100 limit restrict request (except default allows more)
@@ -301,6 +302,9 @@ public class yacysearch {
// find search domain
final Classification.ContentDomain contentdom = post == null || !post.containsKey("contentdom") ? ContentDomain.ALL : ContentDomain.contentdomParser(post.get("contentdom", "all"));
+ if (contentdom == ContentDomain.IMAGE && itemsPerPage == defaultItemsPerPage) {
+ itemsPerPage = 20;
+ }
// Strict/extended content domain constraint : configured setting may be overriden by request param
final boolean strictContentDom = !Boolean.FALSE.toString().equalsIgnoreCase(post.get("strictContentDom",
diff --git a/source/net/yacy/htroot/yacysearchitem.java b/source/net/yacy/htroot/yacysearchitem.java
index d488480f6..28a7ecac9 100644
--- a/source/net/yacy/htroot/yacysearchitem.java
+++ b/source/net/yacy/htroot/yacysearchitem.java
@@ -783,28 +783,32 @@ public class yacysearchitem {
final String imageUrlExt = MultiProtocolURL.getFileExtension(image.imageUrl.getFileName());
final String target = sb.getConfig(imageUrlstring.matches(target_special_pattern) ? SwitchboardConstants.SEARCH_TARGET_SPECIAL : SwitchboardConstants.SEARCH_TARGET_DEFAULT, "_self");
- final String license = URLLicense.aquireLicense(image.imageUrl); // this is just the license key to get the image forwarded through the YaCy thumbnail viewer, not an actual lawful license
+ String license = ""; // this is just the license key to get the image forwarded through the YaCy thumbnail viewer, not an actual lawful license
/* Image format ouput for ViewImage servlet : default is png, except with gif and svg images */
final String viewImageExt = !imageUrlExt.isEmpty() && ImageViewer.isBrowserRendered(imageUrlExt) ? imageUrlExt : "png";
/* Thumb URL */
- final StringBuilder thumbURLBuilder = new StringBuilder("ViewImage.").append(viewImageExt).append("?maxwidth=")
+ final StringBuilder thumbURLBuilder = new StringBuilder("ViewImage.").append(viewImageExt).append("?maxwidth=")
.append(DEFAULT_IMG_WIDTH).append("&maxheight=").append(DEFAULT_IMG_HEIGHT)
.append("&isStatic=true&quadratic");
/* Only use licence code for non authentified users. For authenticated users licence would never be released and would unnecessarily fill URLLicense.permissions. */
+ final String thumbURL;
+ final String fullPreviewURL;
if(fullViewingRights) {
thumbURLBuilder.append("&url=").append(imageUrlstring);
+ thumbURL = thumbURLBuilder.toString();
+ /* Full size preview URL */
+ fullPreviewURL = "ViewImage." + viewImageExt + "?isStatic=true&url=" + imageUrlstring;
} else {
- thumbURLBuilder.append("&code=").append(URLLicense.aquireLicense(image.imageUrl));
+ final String thumbLicense = URLLicense.aquireLicense(image.imageUrl);
+ final String fullPreviewLicense = URLLicense.aquireLicense(image.imageUrl);
+ final String baseThumbURL = thumbURLBuilder.toString();
+ thumbURL = baseThumbURL + "&code=" + thumbLicense;
+ license = thumbLicense;
+ /* Not authenticated : full preview URL must be the same as thumb URL, but with a separate one-time license */
+ fullPreviewURL = baseThumbURL + "&code=" + fullPreviewLicense;
}
- final String thumbURL = thumbURLBuilder.toString();
prop.putHTML("content_item_hrefCache", thumbURL);
- /* Full size preview URL */
- if(fullViewingRights) {
- prop.putHTML("content_item_hrefFullPreview", "ViewImage." + viewImageExt + "?isStatic=true&url=" + imageUrlstring);
- } else {
- /* Not authenticated : full preview URL must be the same as thumb URL */
- prop.putHTML("content_item_hrefFullPreview", thumbURL);
- }
+ prop.putHTML("content_item_hrefFullPreview", fullPreviewURL);
prop.putHTML("content_item_href", imageUrlstring);
prop.putHTML("content_item_target", target);
prop.put("content_item_code", license);