From 2505d21456e546dc9f8360ee79e3bab670637d38 Mon Sep 17 00:00:00 2001 From: Tobias Kuhn Date: Mon, 13 Apr 2026 15:04:23 +0200 Subject: [PATCH 01/12] feat: implement ItemListView, fix async col class and page size propagation - Add QueryResultItemList/Builder/HTML for the ItemListView type, with user_iri and template_iri column recognition, filter, and pagination - Dispatch ITEM_LIST_VIEW in ViewList - Fix col-X class not applied to ApiResultComponent wrapper in async loading path (moved from QueryResult constructor to each builder) - Fix page size ignored when using ViewList: use viewDisplay.getPageSize() in QueryResultList, QueryResultItemList, and QueryResultNanopubSetBuilder - Fix .listview img padding rule not to affect user-icon/bot-icon - Fix bullet not hidden for li containing bot-icon - Make bot-icon use same tilted-square mask shape and gray as user-icon Co-Authored-By: Claude Sonnet 4.6 --- .../knowledgepixels/nanodash/QueryResult.java | 3 - .../component/QueryResultItemList.html | 31 +++++ .../component/QueryResultItemList.java | 129 ++++++++++++++++++ .../component/QueryResultItemListBuilder.java | 75 ++++++++++ .../nanodash/component/QueryResultList.java | 2 +- .../component/QueryResultListBuilder.java | 12 +- .../QueryResultNanopubSetBuilder.java | 14 +- .../QueryResultPlainParagraphBuilder.java | 12 +- .../component/QueryResultTableBuilder.java | 12 +- .../nanodash/component/ViewList.java | 7 + src/main/webapp/style.css | 15 +- 11 files changed, 295 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/knowledgepixels/nanodash/component/QueryResultItemList.html create mode 100644 src/main/java/com/knowledgepixels/nanodash/component/QueryResultItemList.java create mode 100644 src/main/java/com/knowledgepixels/nanodash/component/QueryResultItemListBuilder.java diff --git a/src/main/java/com/knowledgepixels/nanodash/QueryResult.java b/src/main/java/com/knowledgepixels/nanodash/QueryResult.java index 5aaa71014..c24c424f7 100644 --- a/src/main/java/com/knowledgepixels/nanodash/QueryResult.java +++ b/src/main/java/com/knowledgepixels/nanodash/QueryResult.java @@ -5,7 +5,6 @@ import com.knowledgepixels.nanodash.domain.AbstractResourceWithProfile; import com.knowledgepixels.nanodash.domain.IndividualAgent; import com.knowledgepixels.nanodash.page.NanodashPage; -import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.AbstractLink; import org.apache.wicket.markup.html.link.BookmarkablePageLink; @@ -49,8 +48,6 @@ public QueryResult(String markupId, QueryRef queryRef, ApiResponse response, Vie this.viewDisplay = viewDisplay; this.response = response; this.grlcQuery = GrlcQuery.get(queryRef); - - add(new AttributeAppender("class", " col-" + viewDisplay.getDisplayWidth())); } @Override diff --git a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultItemList.html b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultItemList.html new file mode 100644 index 000000000..69312e748 --- /dev/null +++ b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultItemList.html @@ -0,0 +1,31 @@ + + + + + + + + + +
+

Query

+ + [np] + +
+ +
+
    +
  • +
+ +
+
+ + + + diff --git a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultItemList.java b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultItemList.java new file mode 100644 index 000000000..1214eb18d --- /dev/null +++ b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultItemList.java @@ -0,0 +1,129 @@ +package com.knowledgepixels.nanodash.component; + +import com.knowledgepixels.nanodash.*; +import com.knowledgepixels.nanodash.domain.IndividualAgent; +import com.knowledgepixels.nanodash.domain.User; +import com.knowledgepixels.nanodash.page.PublishPage; +import com.knowledgepixels.nanodash.page.UserPage; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; +import org.apache.wicket.ajax.markup.html.navigation.paging.AjaxPagingNavigator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.NavigatorLabel; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.TextField; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.markup.repeater.data.DataView; +import org.apache.wicket.model.Model; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.resource.ContextRelativeResourceReference; +import org.apache.wicket.util.string.Strings; +import org.eclipse.rdf4j.model.IRI; +import org.nanopub.extra.services.ApiResponse; +import org.nanopub.extra.services.ApiResponseEntry; +import org.nanopub.extra.services.QueryRef; + +/** + * Component for displaying query results as a vertical item list. + * Each result row is rendered as a single linked item, with icon handling + * for user_iri and template_iri columns. + */ +public class QueryResultItemList extends QueryResult { + + private FilteredQueryResultDataProvider filteredDataProvider; + private final Model filterModel = Model.of(""); + private WebMarkupContainer itemsContainer; + + QueryResultItemList(String markupId, QueryRef queryRef, ApiResponse response, ViewDisplay viewDisplay) { + super(markupId, queryRef, response, viewDisplay); + + String label = grlcQuery.getLabel(); + if (viewDisplay.getTitle() != null) { + label = viewDisplay.getTitle(); + } + add(new Label("label", label)); + setOutputMarkupId(true); + + TextField filterField = new TextField<>("filter", filterModel); + filterField.setOutputMarkupId(true); + filterField.add(new AjaxFormComponentUpdatingBehavior("change") { + @Override + protected void onUpdate(AjaxRequestTarget target) { + if (filteredDataProvider != null && itemsContainer != null) { + filteredDataProvider.setFilterText(filterModel.getObject()); + target.add(itemsContainer); + } + } + }); + add(filterField); + + populateComponent(); + } + + @Override + protected void populateComponent() { + QueryResultDataProvider dataProvider = new QueryResultDataProvider(response.getData()); + filteredDataProvider = new FilteredQueryResultDataProvider(dataProvider, response); + + DataView dataView = new DataView<>("items", filteredDataProvider) { + @Override + protected void populateItem(Item item) { + ApiResponseEntry entry = item.getModelObject(); + for (String key : response.getHeader()) { + if (key.endsWith("_label") || key.endsWith("_label_multi")) continue; + String value = entry.get(key); + if (value == null || value.isBlank()) continue; + String entryLabel = entry.get(key + "_label"); + + if (key.endsWith("user_iri")) { + IRI userIri = Utils.vf.createIRI(value); + IRI profilePicIri = User.getProfilePicture(userIri); + String imgSrc; + String iconClass; + if (profilePicIri != null) { + imgSrc = Strings.escapeMarkup(profilePicIri.stringValue()).toString(); + iconClass = "user-icon"; + } else if (IndividualAgent.isSoftware(userIri)) { + imgSrc = RequestCycle.get().urlFor(new ContextRelativeResourceReference("images/bot-icon.svg", false), null).toString(); + iconClass = "bot-icon"; + } else { + imgSrc = RequestCycle.get().urlFor(new ContextRelativeResourceReference("images/user-icon.svg", false), null).toString(); + iconClass = "user-icon"; + } + String displayLabel = (entryLabel != null && !entryLabel.isBlank()) ? entryLabel : User.getShortDisplayName(userIri); + String userUrl = UserPage.MOUNT_PATH + "?id=" + Utils.urlEncode(value); + String html = " " + Strings.escapeMarkup(displayLabel) + ""; + item.add(new Label("listItem", html).setEscapeModelStrings(false)); + return; + } else if (key.endsWith("template_iri")) { + String displayLabel = (entryLabel != null && !entryLabel.isBlank()) ? entryLabel : value; + String templateUrl = PublishPage.MOUNT_PATH + "?template=" + Utils.urlEncode(value) + "&template-version=latest"; + String html = " " + Strings.escapeMarkup(displayLabel) + ""; + item.add(new Label("listItem", html).setEscapeModelStrings(false)); + return; + } else if (value.matches("https?://.*")) { + item.add(new NanodashLink("listItem", value, null, null, entryLabel, contextId)); + return; + } else { + item.add(new Label("listItem", value)); + return; + } + } + item.add(new Label("listItem", "")); + } + }; + dataView.setItemsPerPage(viewDisplay.getPageSize()); + + WebMarkupContainer navigation = new WebMarkupContainer("navigation"); + navigation.add(new NavigatorLabel("navigatorLabel", dataView)); + AjaxPagingNavigator pagingNavigator = new AjaxPagingNavigator("navigator", dataView); + navigation.setVisible(dataView.getPageCount() > 1); + navigation.add(pagingNavigator); + + itemsContainer = new WebMarkupContainer("items-container"); + itemsContainer.setOutputMarkupId(true); + itemsContainer.add(dataView); + itemsContainer.add(navigation); + add(itemsContainer); + } +} diff --git a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultItemListBuilder.java b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultItemListBuilder.java new file mode 100644 index 000000000..1e47a3575 --- /dev/null +++ b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultItemListBuilder.java @@ -0,0 +1,75 @@ +package com.knowledgepixels.nanodash.component; + +import com.knowledgepixels.nanodash.ApiCache; +import com.knowledgepixels.nanodash.ViewDisplay; +import org.apache.wicket.behavior.AttributeAppender; +import com.knowledgepixels.nanodash.domain.AbstractResourceWithProfile; +import org.apache.wicket.Component; +import org.nanopub.extra.services.ApiResponse; +import org.nanopub.extra.services.QueryRef; + +import java.io.Serializable; + +/** + * Builder class for creating QueryResultItemList components. + */ +public class QueryResultItemListBuilder implements Serializable { + + private final String markupId; + private final ViewDisplay viewDisplay; + private String contextId = null; + private final QueryRef queryRef; + private AbstractResourceWithProfile pageResource = null; + + private QueryResultItemListBuilder(String markupId, QueryRef queryRef, ViewDisplay viewDisplay) { + this.markupId = markupId; + this.queryRef = queryRef; + this.viewDisplay = viewDisplay; + } + + public static QueryResultItemListBuilder create(String markupId, QueryRef queryRef, ViewDisplay viewDisplay) { + return new QueryResultItemListBuilder(markupId, queryRef, viewDisplay); + } + + public QueryResultItemListBuilder contextId(String contextId) { + this.contextId = contextId; + return this; + } + + public QueryResultItemListBuilder resourceWithProfile(AbstractResourceWithProfile resourceWithProfile) { + return this; + } + + public QueryResultItemListBuilder id(String id) { + return this; + } + + public QueryResultItemListBuilder pageResource(AbstractResourceWithProfile pageResource) { + this.pageResource = pageResource; + return this; + } + + public Component build() { + ApiResponse response = ApiCache.retrieveResponseAsync(queryRef); + String colClass = " col-" + viewDisplay.getDisplayWidth(); + if (response != null) { + QueryResultItemList result = new QueryResultItemList(markupId, queryRef, response, viewDisplay); + result.setContextId(contextId); + result.setPageResource(pageResource); + result.add(new AttributeAppender("class", colClass)); + return result; + } else { + ApiResultComponent comp = new ApiResultComponent(markupId, queryRef) { + @Override + public Component getApiResultComponent(String id, ApiResponse r) { + QueryResultItemList result = new QueryResultItemList(id, queryRef, r, viewDisplay); + result.setContextId(contextId); + result.setPageResource(pageResource); + return result; + } + }; + comp.add(new AttributeAppender("class", colClass)); + return comp; + } + } +} diff --git a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultList.java b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultList.java index a967cce26..1798c0105 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultList.java +++ b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultList.java @@ -226,7 +226,7 @@ protected void populateItem(Item item) { item.add(listItem); } }; - dataView.setItemsPerPage(10); + dataView.setItemsPerPage(viewDisplay.getPageSize()); WebMarkupContainer navigation = new WebMarkupContainer("navigation"); navigation.add(new NavigatorLabel("navigatorLabel", dataView)); diff --git a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultListBuilder.java b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultListBuilder.java index b38f84161..54920e05b 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultListBuilder.java +++ b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultListBuilder.java @@ -7,6 +7,7 @@ import com.knowledgepixels.nanodash.repository.MaintainedResourceRepository; import com.knowledgepixels.nanodash.template.Template; import org.apache.wicket.Component; +import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.eclipse.rdf4j.model.IRI; import org.nanopub.extra.services.ApiResponse; @@ -78,6 +79,7 @@ public QueryResultListBuilder pageResource(AbstractResourceWithProfile pageResou */ public Component build() { ApiResponse response = ApiCache.retrieveResponseAsync(queryRef); + String colClass = " col-" + viewDisplay.getDisplayWidth(); if (resourceWithProfile != null) { if (response != null) { QueryResultList resultList = new QueryResultList(markupId, queryRef, response, viewDisplay); @@ -118,9 +120,10 @@ public Component build() { resultList.addButton(label, PublishPage.class, params); } } + resultList.add(new AttributeAppender("class", colClass)); return resultList; } else { - return new ApiResultComponent(markupId, queryRef) { + ApiResultComponent comp = new ApiResultComponent(markupId, queryRef) { @Override public Component getApiResultComponent(String markupId, ApiResponse response) { QueryResultList resultList = new QueryResultList(markupId, queryRef, response, viewDisplay); @@ -163,15 +166,18 @@ public Component getApiResultComponent(String markupId, ApiResponse response) { return resultList; } }; + comp.add(new AttributeAppender("class", colClass)); + return comp; } } else { if (response != null) { QueryResultList resultList = new QueryResultList(markupId, queryRef, response, viewDisplay); resultList.setPageResource(pageResource); resultList.setContextId(contextId); + resultList.add(new AttributeAppender("class", colClass)); return resultList; } else { - return new ApiResultComponent(markupId, queryRef) { + ApiResultComponent comp = new ApiResultComponent(markupId, queryRef) { @Override public Component getApiResultComponent(String markupId, ApiResponse response) { QueryResultList resultList = new QueryResultList(markupId, queryRef, response, viewDisplay); @@ -180,6 +186,8 @@ public Component getApiResultComponent(String markupId, ApiResponse response) { return resultList; } }; + comp.add(new AttributeAppender("class", colClass)); + return comp; } } } diff --git a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultNanopubSetBuilder.java b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultNanopubSetBuilder.java index b29a15a4c..5a9220fc4 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultNanopubSetBuilder.java +++ b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultNanopubSetBuilder.java @@ -2,6 +2,7 @@ import com.knowledgepixels.nanodash.ApiCache; import com.knowledgepixels.nanodash.ViewDisplay; +import org.apache.wicket.behavior.AttributeAppender; import com.knowledgepixels.nanodash.domain.AbstractResourceWithProfile; import org.apache.wicket.Component; import org.nanopub.extra.services.ApiResponse; @@ -20,7 +21,7 @@ public class QueryResultNanopubSetBuilder implements Serializable { private final QueryRef queryRef; private boolean hasTitle = true; private AbstractResourceWithProfile pageResource = null; - private long itemsPerPage = 10; + private Long itemsPerPage = null; private QueryResultNanopubSetBuilder(String markupId, QueryRef queryRef, ViewDisplay viewDisplay) { this.markupId = markupId; @@ -78,18 +79,21 @@ public QueryResultNanopubSetBuilder noTitle() { */ public Component build() { ApiResponse response = ApiCache.retrieveResponseAsync(queryRef); + String colClass = " col-" + viewDisplay.getDisplayWidth(); + long resolvedItemsPerPage = itemsPerPage != null ? itemsPerPage : viewDisplay.getPageSize(); if (response != null) { - QueryResultNanopubSet queryResultNanopubSet = new QueryResultNanopubSet(markupId, queryRef, response, viewDisplay, itemsPerPage); + QueryResultNanopubSet queryResultNanopubSet = new QueryResultNanopubSet(markupId, queryRef, response, viewDisplay, resolvedItemsPerPage); queryResultNanopubSet.setContextId(contextId); queryResultNanopubSet.setPageResource(pageResource); queryResultNanopubSet.populateComponent(); queryResultNanopubSet.setTitleVisible(hasTitle); + queryResultNanopubSet.add(new AttributeAppender("class", colClass)); return queryResultNanopubSet; } else { - return new ApiResultComponent(markupId, queryRef) { + ApiResultComponent comp = new ApiResultComponent(markupId, queryRef) { @Override public Component getApiResultComponent(String markupId, ApiResponse response) { - QueryResultNanopubSet queryResultNanopubSet = new QueryResultNanopubSet(markupId, queryRef, response, viewDisplay, itemsPerPage); + QueryResultNanopubSet queryResultNanopubSet = new QueryResultNanopubSet(markupId, queryRef, response, viewDisplay, resolvedItemsPerPage); queryResultNanopubSet.setContextId(contextId); queryResultNanopubSet.setPageResource(pageResource); queryResultNanopubSet.populateComponent(); @@ -97,6 +101,8 @@ public Component getApiResultComponent(String markupId, ApiResponse response) { return queryResultNanopubSet; } }; + comp.add(new AttributeAppender("class", colClass)); + return comp; } } diff --git a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultPlainParagraphBuilder.java b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultPlainParagraphBuilder.java index 0b41c2328..19350c448 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultPlainParagraphBuilder.java +++ b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultPlainParagraphBuilder.java @@ -10,6 +10,7 @@ import com.knowledgepixels.nanodash.repository.MaintainedResourceRepository; import com.knowledgepixels.nanodash.template.Template; import org.apache.wicket.Component; +import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.eclipse.rdf4j.model.IRI; import org.nanopub.extra.services.ApiResponse; @@ -114,6 +115,7 @@ public QueryResultPlainParagraphBuilder pageResource(AbstractResourceWithProfile */ public Component build() { ApiResponse response = ApiCache.retrieveResponseAsync(queryRef); + String colClass = " col-" + viewDisplay.getDisplayWidth(); if (space != null) { if (response != null) { QueryResultPlainParagraph resultPlainParagraph = new QueryResultPlainParagraph(markupId, queryRef, response, viewDisplay); @@ -121,9 +123,10 @@ public Component build() { resultPlainParagraph.setPageResource(pageResource); resultPlainParagraph.setContextId(contextId); addResultButtons(resultPlainParagraph); + resultPlainParagraph.add(new AttributeAppender("class", colClass)); return resultPlainParagraph; } else { - return new ApiResultComponent(markupId, queryRef) { + ApiResultComponent comp = new ApiResultComponent(markupId, queryRef) { @Override public Component getApiResultComponent(String markupId, ApiResponse response) { QueryResultPlainParagraph resultPlainParagraph = new QueryResultPlainParagraph(markupId, queryRef, response, viewDisplay); @@ -134,6 +137,8 @@ public Component getApiResultComponent(String markupId, ApiResponse response) { return resultPlainParagraph; } }; + comp.add(new AttributeAppender("class", colClass)); + return comp; } } else { if (response != null) { @@ -141,9 +146,10 @@ public Component getApiResultComponent(String markupId, ApiResponse response) { resultPlainParagraph.setPageResource(pageResource); resultPlainParagraph.setContextId(contextId); addResultButtons(resultPlainParagraph); + resultPlainParagraph.add(new AttributeAppender("class", colClass)); return resultPlainParagraph; } else { - return new ApiResultComponent(markupId, queryRef) { + ApiResultComponent comp = new ApiResultComponent(markupId, queryRef) { @Override public Component getApiResultComponent(String markupId, ApiResponse response) { QueryResultPlainParagraph resultPlainParagraph = new QueryResultPlainParagraph(markupId, queryRef, response, viewDisplay); @@ -153,6 +159,8 @@ public Component getApiResultComponent(String markupId, ApiResponse response) { return resultPlainParagraph; } }; + comp.add(new AttributeAppender("class", colClass)); + return comp; } } } diff --git a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTableBuilder.java b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTableBuilder.java index b27e53fea..77beeb5f6 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTableBuilder.java +++ b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTableBuilder.java @@ -9,6 +9,7 @@ import com.knowledgepixels.nanodash.repository.MaintainedResourceRepository; import com.knowledgepixels.nanodash.template.Template; import org.apache.wicket.Component; +import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.eclipse.rdf4j.model.IRI; import org.nanopub.extra.services.ApiResponse; @@ -98,6 +99,7 @@ public QueryResultTableBuilder contextId(String contextId) { */ public Component build() { ApiResponse response = ApiCache.retrieveResponseAsync(queryRef); + String colClass = " col-" + viewDisplay.getDisplayWidth(); if (resourceWithProfile != null) { if (response != null) { QueryResultTable table = new QueryResultTable(markupId, queryRef, response, viewDisplay, false); @@ -141,9 +143,10 @@ public Component build() { table.addButton(label, PublishPage.class, params); } } + table.add(new AttributeAppender("class", colClass)); return table; } else { - return new ApiResultComponent(markupId, queryRef) { + ApiResultComponent comp = new ApiResultComponent(markupId, queryRef) { @Override public Component getApiResultComponent(String markupId, ApiResponse response) { QueryResultTable table = new QueryResultTable(markupId, queryRef, response, viewDisplay, false); @@ -189,14 +192,17 @@ public Component getApiResultComponent(String markupId, ApiResponse response) { return table; } }; + comp.add(new AttributeAppender("class", colClass)); + return comp; } } else { if (response != null) { QueryResultTable table = new QueryResultTable(markupId, queryRef, response, viewDisplay, plain); table.setContextId(contextId); + table.add(new AttributeAppender("class", colClass)); return table; } else { - return new ApiResultComponent(markupId, queryRef) { + ApiResultComponent comp = new ApiResultComponent(markupId, queryRef) { @Override public Component getApiResultComponent(String markupId, ApiResponse response) { QueryResultTable table = new QueryResultTable(markupId, queryRef, response, viewDisplay, plain); @@ -204,6 +210,8 @@ public Component getApiResultComponent(String markupId, ApiResponse response) { return table; } }; + comp.add(new AttributeAppender("class", colClass)); + return comp; } } } diff --git a/src/main/java/com/knowledgepixels/nanodash/component/ViewList.java b/src/main/java/com/knowledgepixels/nanodash/component/ViewList.java index 6ddb7da46..df87da997 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/ViewList.java +++ b/src/main/java/com/knowledgepixels/nanodash/component/ViewList.java @@ -152,6 +152,13 @@ protected void populateItem(ListItem item) { .pageResource(resourceWithProfile) .contextId(resourceWithProfile.getId()) .build()); + } else if (view.getViewType().equals(KPXL_TERMS.ITEM_LIST_VIEW)) { + item.add(QueryResultItemListBuilder.create("view", queryRef, item.getModelObject()) + .resourceWithProfile(resourceWithProfile) + .pageResource(resourceWithProfile) + .id(id) + .contextId(resourceWithProfile.getId()) + .build()); } else { item.add(new Label("view", "View type \"" + view.getViewType().stringValue() + "\" is supported but its view is not implemented yet").setEscapeModelStrings(false)); logger.error("View type \"{}\" is supported but its view is not implemented yet", view.getViewType().stringValue()); diff --git a/src/main/webapp/style.css b/src/main/webapp/style.css index 1f16f9d13..1702d9a6e 100644 --- a/src/main/webapp/style.css +++ b/src/main/webapp/style.css @@ -543,8 +543,15 @@ img.thirdparty { margin-top: 0; margin-right: 0.3em; width: 1.5em; - border: 1px solid lightblue; - border-radius: 50px; + height: 1.5em; + object-fit: cover; + background-color: lightgray; + mask-image: url("images/mask.svg"); + mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-image: url("images/mask.svg"); + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; } .user-profile-pic { @@ -610,12 +617,14 @@ li:has(.templateref) { } li:has(.user-icon), +li:has(.bot-icon), li:has(.form-icon) { list-style: none; margin-left: -1em; } .users li:has(.user-icon), +.users li:has(.bot-icon), .users li:has(.form-icon) { margin-left: -0.5em; } @@ -1878,7 +1887,7 @@ p.waiting { color: #ccc; } -.listview img { /* for wait icon */ +.listview img:not(.user-icon):not(.bot-icon) { /* for wait icon */ padding: 15px; } From 419aacdbdcf55bac0cdee194572a7a85136f74c6 Mon Sep 17 00:00:00 2001 From: Tobias Kuhn Date: Mon, 13 Apr 2026 15:11:42 +0200 Subject: [PATCH 02/12] feat: drive home page views from configurable maintained resource Replace the four hardcoded views on the home page with a ViewList driven by a maintained resource. The resource IRI defaults to https://w3id.org/spaces/knowledgepixels/nanodash/r/home and can be overridden via the NANODASH_HOME_RESOURCE environment variable. Co-Authored-By: Claude Sonnet 4.6 --- .../nanodash/NanodashPreferences.java | 15 ++++ .../nanodash/page/HomePage.html | 12 +--- .../nanodash/page/HomePage.java | 72 +++++++++++-------- 3 files changed, 57 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/knowledgepixels/nanodash/NanodashPreferences.java b/src/main/java/com/knowledgepixels/nanodash/NanodashPreferences.java index 7795194bb..3477965e2 100644 --- a/src/main/java/com/knowledgepixels/nanodash/NanodashPreferences.java +++ b/src/main/java/com/knowledgepixels/nanodash/NanodashPreferences.java @@ -51,6 +51,7 @@ public static NanodashPreferences get() { private String settingUri; private String umamiScriptUrl; private String umamiWebsiteId; + private String homeResource = "https://w3id.org/spaces/knowledgepixels/nanodash/r/home"; public static final String DEFAULT_SETTING_PATH = "/.nanopub/nanodash-preferences.yml"; /** @@ -252,4 +253,18 @@ public void setUmamiWebsiteId(String umamiWebsiteId) { this.umamiWebsiteId = umamiWebsiteId; } + public String getHomeResource() { + String s = System.getenv("NANODASH_HOME_RESOURCE"); + if (s != null && !s.isBlank()) { + logger.debug("Found environment variable NANODASH_HOME_RESOURCE with value: {}", s); + return s; + } + logger.debug("Environment variable NANODASH_HOME_RESOURCE not set, using default: {}", homeResource); + return homeResource; + } + + public void setHomeResource(String homeResource) { + this.homeResource = homeResource; + } + } diff --git a/src/main/java/com/knowledgepixels/nanodash/page/HomePage.html b/src/main/java/com/knowledgepixels/nanodash/page/HomePage.html index 62624d9b6..c119b85b5 100644 --- a/src/main/java/com/knowledgepixels/nanodash/page/HomePage.html +++ b/src/main/java/com/knowledgepixels/nanodash/page/HomePage.html @@ -14,17 +14,7 @@

nanodash -
-
...
-
...
-
- -
-
...
-
...
-
- +
+ + diff --git a/src/main/java/com/knowledgepixels/nanodash/component/ViewList.java b/src/main/java/com/knowledgepixels/nanodash/component/ViewList.java index df87da997..a3da34045 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/ViewList.java +++ b/src/main/java/com/knowledgepixels/nanodash/component/ViewList.java @@ -8,6 +8,7 @@ import com.knowledgepixels.nanodash.domain.Space; import com.knowledgepixels.nanodash.domain.User; import com.knowledgepixels.nanodash.vocabulary.KPXL_TERMS; +import org.apache.wicket.Component; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.AbstractLink; @@ -184,6 +185,12 @@ protected void populateItem(ListItem item) { footerSection.add(new Label("footer-buttons").setVisible(false)); } add(footerSection); + + add(new WebMarkupContainer("page-footer").setVisible(false)); + } + + public void setPageFooter(Component footer) { + replace(footer); } } diff --git a/src/main/java/com/knowledgepixels/nanodash/page/HomePage.html b/src/main/java/com/knowledgepixels/nanodash/page/HomePage.html index c119b85b5..0c414130b 100644 --- a/src/main/java/com/knowledgepixels/nanodash/page/HomePage.html +++ b/src/main/java/com/knowledgepixels/nanodash/page/HomePage.html @@ -16,12 +16,14 @@

nanodash -
- + diff --git a/src/main/java/com/knowledgepixels/nanodash/page/HomePage.java b/src/main/java/com/knowledgepixels/nanodash/page/HomePage.java index d9a817cb5..216bf04f6 100644 --- a/src/main/java/com/knowledgepixels/nanodash/page/HomePage.java +++ b/src/main/java/com/knowledgepixels/nanodash/page/HomePage.java @@ -6,6 +6,7 @@ import com.knowledgepixels.nanodash.component.ResultComponent; import com.knowledgepixels.nanodash.component.TitleBar; import com.knowledgepixels.nanodash.component.ViewList; +import org.apache.wicket.markup.html.panel.Fragment; import com.knowledgepixels.nanodash.domain.MaintainedResource; import com.knowledgepixels.nanodash.repository.MaintainedResourceRepository; import org.apache.wicket.Component; @@ -71,13 +72,17 @@ public HomePage(final PageParameters parameters) { homeResource.triggerDataUpdate(); if (homeResource.isDataInitialized()) { - add(new ViewList("views", homeResource)); + ViewList viewList = new ViewList("views", homeResource); + viewList.setPageFooter(new Fragment("page-footer", "homeFooterFragment", this)); + add(viewList); } else { add(new AjaxLazyLoadPanel("views") { @Override public Component getLazyLoadComponent(String markupId) { - return new ViewList(markupId, homeResource); + ViewList viewList = new ViewList(markupId, homeResource); + viewList.setPageFooter(new Fragment("page-footer", "homeFooterFragment", HomePage.this)); + return viewList; } @Override From 638fe35ec8db308c774f275ba8706d3f7d6a2422 Mon Sep 17 00:00:00 2001 From: Tobias Kuhn Date: Mon, 13 Apr 2026 16:21:05 +0200 Subject: [PATCH 04/12] fix: remove col wrapper nesting in TemplateList and use item-list for popular templates - Remove double col nesting (HTML col-6 wrapper + builder col-X) that caused async-loaded views to render at 3/12 instead of 6/12 - Add ViewDisplay.withDisplayWidth() for explicit width override - Switch popular-templates to QueryResultItemListBuilder to show only the template link, dropping the count column Co-Authored-By: Claude Sonnet 4.6 --- .../java/com/knowledgepixels/nanodash/ViewDisplay.java | 5 +++++ .../knowledgepixels/nanodash/component/TemplateList.html | 8 -------- .../knowledgepixels/nanodash/component/TemplateList.java | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/knowledgepixels/nanodash/ViewDisplay.java b/src/main/java/com/knowledgepixels/nanodash/ViewDisplay.java index 20317cfd0..4b74dff92 100644 --- a/src/main/java/com/knowledgepixels/nanodash/ViewDisplay.java +++ b/src/main/java/com/knowledgepixels/nanodash/ViewDisplay.java @@ -241,6 +241,11 @@ public Integer getDisplayWidth() { return 12; } + public ViewDisplay withDisplayWidth(int width) { + this.displayWidth = width; + return this; + } + public String getStructuralPosition() { if (structuralPosition != null) return structuralPosition; if (view == null) return "5.5.default"; diff --git a/src/main/java/com/knowledgepixels/nanodash/component/TemplateList.html b/src/main/java/com/knowledgepixels/nanodash/component/TemplateList.html index d8e277068..5042ee280 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/TemplateList.html +++ b/src/main/java/com/knowledgepixels/nanodash/component/TemplateList.html @@ -14,20 +14,12 @@

✏ Publish a new Nanopublication

-
-
-
- -
-
- -
diff --git a/src/main/java/com/knowledgepixels/nanodash/component/TemplateList.java b/src/main/java/com/knowledgepixels/nanodash/component/TemplateList.java index 3fbd3e08e..841cb3c53 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/TemplateList.java +++ b/src/main/java/com/knowledgepixels/nanodash/component/TemplateList.java @@ -35,11 +35,11 @@ public TemplateList(String id) { View popularTemplatesView = View.get("https://w3id.org/np/RAYMZEdmvjIS5QGFASa8L5hygapUlvK3feZBpG6quMYqc/popular-templates"); QueryRef ptQueryRef = new QueryRef(popularTemplatesView.getQuery().getQueryId()); - add(QueryResultListBuilder.create("popular-templates", ptQueryRef, new ViewDisplay(popularTemplatesView)).build()); + add(QueryResultItemListBuilder.create("popular-templates", ptQueryRef, new ViewDisplay(popularTemplatesView).withDisplayWidth(6)).build()); View getStartedView = View.get("https://w3id.org/np/RAeFTjDGTQ-bdulJy4tUlWzRlK8EucXFCxqLrb7Qj35SM/suggested-templates-get-started"); QueryRef gsQueryRef = new QueryRef(getStartedView.getQuery().getQueryId()); - add(QueryResultListBuilder.create("getstarted-templates", gsQueryRef, new ViewDisplay(getStartedView)).build()); + add(QueryResultListBuilder.create("getstarted-templates", gsQueryRef, new ViewDisplay(getStartedView).withDisplayWidth(6)).build()); ArrayList templateList = new ArrayList<>(TemplateData.get().getAssertionTemplates()); templateList.sort((t1, t2) -> { From 476babfaed2bc6d11b1db495d760f15b3cf5d3a5 Mon Sep 17 00:00:00 2001 From: Tobias Kuhn Date: Mon, 13 Apr 2026 16:23:46 +0200 Subject: [PATCH 05/12] fix: remove col wrapper nesting for topcreators and latestusers on /userlist Same double col-nesting issue as TemplateList: HTML col-6 wrappers plus builder col-X caused async-loaded views to render at 3/12. Co-Authored-By: Claude Sonnet 4.6 --- .../knowledgepixels/nanodash/page/UserListPage.html | 12 ++---------- .../knowledgepixels/nanodash/page/UserListPage.java | 4 ++-- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/knowledgepixels/nanodash/page/UserListPage.html b/src/main/java/com/knowledgepixels/nanodash/page/UserListPage.html index 2004da11b..811b6bf3b 100644 --- a/src/main/java/com/knowledgepixels/nanodash/page/UserListPage.html +++ b/src/main/java/com/knowledgepixels/nanodash/page/UserListPage.html @@ -17,17 +17,9 @@

👥 Users

-
- -
- -
+
-
- -
- -
+
diff --git a/src/main/java/com/knowledgepixels/nanodash/page/UserListPage.java b/src/main/java/com/knowledgepixels/nanodash/page/UserListPage.java index 64bafcb1b..47f8014ea 100644 --- a/src/main/java/com/knowledgepixels/nanodash/page/UserListPage.java +++ b/src/main/java/com/knowledgepixels/nanodash/page/UserListPage.java @@ -61,11 +61,11 @@ public UserListPage(final PageParameters parameters) { View topCreatorsView = View.get("https://w3id.org/np/RACcywnbkn6OAd_6E25qZL9-vdO-UwmpO1vXVWzNWJYLo/top-creators-last-30days"); QueryRef tcQueryRef = new QueryRef(topCreatorsView.getQuery().getQueryId()); - add(QueryResultListBuilder.create("topcreators", tcQueryRef, new ViewDisplay(topCreatorsView)).build()); + add(QueryResultListBuilder.create("topcreators", tcQueryRef, new ViewDisplay(topCreatorsView).withDisplayWidth(6)).build()); View latestUsersView = View.get("https://w3id.org/np/RAtwNLvsJbz3pk_UxdKSydsghbX6D_60ivTZpDQhK-9zA/latest-users"); QueryRef luQueryRef = new QueryRef(latestUsersView.getQuery().getQueryId()); - add(QueryResultListBuilder.create("latestusers", luQueryRef, new ViewDisplay(latestUsersView)).build()); + add(QueryResultListBuilder.create("latestusers", luQueryRef, new ViewDisplay(latestUsersView).withDisplayWidth(6)).build()); add(new ItemListPanel( "approved-human-users", From 4ab16c49b28e9ddd8dbecb9fa3d58110b74f0fd2 Mon Sep 17 00:00:00 2001 From: Tobias Kuhn Date: Mon, 13 Apr 2026 17:09:28 +0200 Subject: [PATCH 06/12] feat: enable sortable column headers in tabular views Sort logic applied in FilteredQueryResultDataProvider.iterator() using the shared sort state. AjaxFallbackHeadersToolbar replaces HeadersToolbar so sort clicks refresh the table in-place via AJAX rather than triggering a full page reload (which would re-initialize the AjaxLazyLoadPanel and lose the sort state). Co-Authored-By: Claude Sonnet 4.6 --- .../FilteredQueryResultDataProvider.java | 18 ++++++++++- .../nanodash/QueryResultDataProvider.java | 5 ++++ .../nanodash/component/QueryResultTable.java | 30 ++----------------- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/knowledgepixels/nanodash/FilteredQueryResultDataProvider.java b/src/main/java/com/knowledgepixels/nanodash/FilteredQueryResultDataProvider.java index 3dd0dc2df..5d2d1a378 100644 --- a/src/main/java/com/knowledgepixels/nanodash/FilteredQueryResultDataProvider.java +++ b/src/main/java/com/knowledgepixels/nanodash/FilteredQueryResultDataProvider.java @@ -2,6 +2,7 @@ import org.apache.wicket.extensions.markup.html.repeater.data.sort.ISortState; import org.apache.wicket.extensions.markup.html.repeater.data.table.ISortableDataProvider; +import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; import org.apache.wicket.model.IModel; import org.nanopub.extra.services.ApiResponse; import org.nanopub.extra.services.ApiResponseEntry; @@ -65,7 +66,22 @@ public List getFilteredData() { @Override public Iterator iterator(long first, long count) { - List data = getFilteredData(); + List data = new ArrayList<>(getFilteredData()); + SortParam sortParam = baseProvider.getSortParam(); + if (sortParam != null) { + String prop = sortParam.getProperty(); + data.sort((o1, o2) -> { + String v1 = o1.get(prop); + String v2 = o2.get(prop); + int result; + if (v1 == null && v2 == null) result = 0; + else if (v1 == null) result = 1; + else if (v2 == null) result = -1; + else result = v1.compareTo(v2); + if (!sortParam.isAscending()) result = -result; + return result; + }); + } return Utils.subList(data, first, first + count).iterator(); } diff --git a/src/main/java/com/knowledgepixels/nanodash/QueryResultDataProvider.java b/src/main/java/com/knowledgepixels/nanodash/QueryResultDataProvider.java index 5484b1133..a42581213 100644 --- a/src/main/java/com/knowledgepixels/nanodash/QueryResultDataProvider.java +++ b/src/main/java/com/knowledgepixels/nanodash/QueryResultDataProvider.java @@ -3,6 +3,7 @@ import org.apache.wicket.extensions.markup.html.repeater.data.sort.ISortState; import org.apache.wicket.extensions.markup.html.repeater.data.table.ISortableDataProvider; import org.apache.wicket.extensions.markup.html.repeater.util.SingleSortState; +import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.nanopub.extra.services.ApiResponseEntry; @@ -55,6 +56,10 @@ public ISortState getSortState() { return sortState; } + public SortParam getSortParam() { + return sortState.getSort(); + } + @Override public void detach() { } diff --git a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java index 866f7ac9b..24adf64d1 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java +++ b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java @@ -10,6 +10,7 @@ import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; import org.apache.wicket.behavior.AttributeAppender; +import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackHeadersToolbar; import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxNavigationToolbar; import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; import org.apache.wicket.extensions.markup.html.repeater.data.table.*; @@ -119,7 +120,7 @@ protected void populateComponent() { table.setOutputMarkupId(true); table.addBottomToolbar(new AjaxNavigationToolbar(table)); table.addBottomToolbar(new NoRecordsToolbar(table)); - table.addTopToolbar(new HeadersToolbar(table, dataProvider)); + table.addTopToolbar(new AjaxFallbackHeadersToolbar(table, dataProvider)); add(table); } catch (Exception ex) { logger.error("Error creating table for query {}", grlcQuery.getQueryId(), ex); @@ -267,31 +268,4 @@ public void populateItem(Item> cellItem, String } -// private class ApiResponseComparator implements Comparator, Serializable { -// -// private SortParam sortParam; -// -// public ApiResponseComparator(SortParam sortParam) { -// this.sortParam = sortParam; -// } -// -// @Override -// public int compare(ApiResponseEntry o1, ApiResponseEntry o2) { -// String p = sortParam.getProperty(); -// int result; -// if (o1.get(p) == null && o2.get(p) == null) { -// result = 0; -// } else if (o1.get(p) == null) { -// result = 1; -// } else if (o2.get(p) == null) { -// result = -1; -// } else { -// result = o1.get(p).compareTo(o2.get(p)); -// } -// if (!sortParam.isAscending()) result = -result; -// return result; -// } -// -// } - } From d337a9d6788e5d077ef4ce5b13add058d392b9dd Mon Sep 17 00:00:00 2001 From: Tobias Kuhn Date: Mon, 13 Apr 2026 17:11:59 +0200 Subject: [PATCH 07/12] fix: sort tabular view columns by display label when available When a column has a _label companion (e.g. template_iri_label for template_iri), sort by the label text instead of the raw URI. Also uses case-insensitive comparison. Co-Authored-By: Claude Sonnet 4.6 --- .../nanodash/FilteredQueryResultDataProvider.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/knowledgepixels/nanodash/FilteredQueryResultDataProvider.java b/src/main/java/com/knowledgepixels/nanodash/FilteredQueryResultDataProvider.java index 5d2d1a378..98d593cdf 100644 --- a/src/main/java/com/knowledgepixels/nanodash/FilteredQueryResultDataProvider.java +++ b/src/main/java/com/knowledgepixels/nanodash/FilteredQueryResultDataProvider.java @@ -8,6 +8,7 @@ import org.nanopub.extra.services.ApiResponseEntry; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; @@ -70,14 +71,16 @@ public Iterator iterator(long first, long count) { SortParam sortParam = baseProvider.getSortParam(); if (sortParam != null) { String prop = sortParam.getProperty(); + String labelProp = prop + "_label"; + String sortProp = Arrays.asList(response.getHeader()).contains(labelProp) ? labelProp : prop; data.sort((o1, o2) -> { - String v1 = o1.get(prop); - String v2 = o2.get(prop); + String v1 = o1.get(sortProp); + String v2 = o2.get(sortProp); int result; if (v1 == null && v2 == null) result = 0; else if (v1 == null) result = 1; else if (v2 == null) result = -1; - else result = v1.compareTo(v2); + else result = v1.compareToIgnoreCase(v2); if (!sortParam.isAscending()) result = -result; return result; }); From 0ef2179995ee74e7e418df02ed7ee44841c329b0 Mon Sep 17 00:00:00 2001 From: Tobias Kuhn Date: Mon, 13 Apr 2026 18:09:43 +0200 Subject: [PATCH 08/12] feat: link template_iri columns to /publish page in tabular views Columns ending in template_iri are now rendered as links to /publish?template=&template-version=latest, consistent with how QueryResultItemList handles them. Co-Authored-By: Claude Sonnet 4.6 --- .../nanodash/component/QueryResultTable.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java index 24adf64d1..325c019c9 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java +++ b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java @@ -240,6 +240,13 @@ public void populateItem(Item> cellItem, String } } cellItem.add(new ComponentSequence(componentId, ", ", components)); + } else if (key.endsWith("template_iri")) { + String label = rowModel.getObject().get(key + "_label"); + if (label == null || label.isBlank()) label = value; + PageParameters params = new PageParameters() + .set("template", value) + .set("template-version", "latest"); + cellItem.add(new BookmarkablePageLink(componentId, PublishPage.class, params).setBody(Model.of(label))); } else if (value.matches("https?://.+")) { String label = rowModel.getObject().get(key + "_label"); cellItem.add(new NanodashLink(componentId, value, null, null, label, contextId)); From 6eb3352ee00617dddc39de030479e209d686bc18 Mon Sep 17 00:00:00 2001 From: Tobias Kuhn Date: Mon, 13 Apr 2026 18:31:12 +0200 Subject: [PATCH 09/12] fix: render template_iri cell as proper link BookmarkablePageLink renders on the DataTable's span host element, producing which browsers don't style as a link. Use Label with unescaped HTML instead (same pattern as QueryResultItemList) to emit a real tag regardless of the host element. Co-Authored-By: Claude Sonnet 4.6 --- .../nanodash/component/QueryResultTable.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java index 325c019c9..2c50ea64c 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java +++ b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java @@ -18,6 +18,7 @@ import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.link.AbstractLink; import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.util.string.Strings; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; @@ -243,10 +244,9 @@ public void populateItem(Item> cellItem, String } else if (key.endsWith("template_iri")) { String label = rowModel.getObject().get(key + "_label"); if (label == null || label.isBlank()) label = value; - PageParameters params = new PageParameters() - .set("template", value) - .set("template-version", "latest"); - cellItem.add(new BookmarkablePageLink(componentId, PublishPage.class, params).setBody(Model.of(label))); + String templateUrl = PublishPage.MOUNT_PATH + "?template=" + Utils.urlEncode(value) + "&template-version=latest"; + String html = "" + Strings.escapeMarkup(label) + ""; + cellItem.add(new Label(componentId, html).setEscapeModelStrings(false)); } else if (value.matches("https?://.+")) { String label = rowModel.getObject().get(key + "_label"); cellItem.add(new NanodashLink(componentId, value, null, null, label, contextId)); From 252a21b28073dcbe99c252dada0d12fdc10397d5 Mon Sep 17 00:00:00 2001 From: Tobias Kuhn Date: Mon, 13 Apr 2026 21:14:40 +0200 Subject: [PATCH 10/12] feat: truncate link labels longer than 120 chars to 100 chars in table cells Applies to template_iri, regular IRI, multi-iri, and multi-val link cells. Co-Authored-By: Claude Sonnet 4.6 --- .../nanodash/component/QueryResultTable.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java index 2c50ea64c..b425f3e43 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java +++ b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java @@ -192,7 +192,7 @@ public void populateItem(Item> cellItem, String String[] labels = labelValue != null ? labelValue.split("\n", -1) : null; List links = new ArrayList<>(); for (int i = 0; i < uris.length; i++) { - String label = (labels != null && i < labels.length && !labels[i].isBlank()) ? Utils.unescapeMultiValue(labels[i]) : null; + String label = truncateLabel((labels != null && i < labels.length && !labels[i].isBlank()) ? Utils.unescapeMultiValue(labels[i]) : null); links.add(new NanodashLink("component", uris[i], null, null, label, contextId)); } cellItem.add(new ComponentSequence(componentId, ", ", links)); @@ -204,7 +204,7 @@ public void populateItem(Item> cellItem, String List components = new ArrayList<>(); for (int i = 0; i < parts.length; i++) { String part = parts[i]; - String label = (labels != null && i < labels.length && !labels[i].isBlank()) ? Utils.unescapeMultiValue(labels[i]) : null; + String label = truncateLabel((labels != null && i < labels.length && !labels[i].isBlank()) ? Utils.unescapeMultiValue(labels[i]) : null); if (part.matches("https?://.+")) { components.add(new NanodashLink("component", part, null, null, label, contextId)); } else { @@ -242,13 +242,13 @@ public void populateItem(Item> cellItem, String } cellItem.add(new ComponentSequence(componentId, ", ", components)); } else if (key.endsWith("template_iri")) { - String label = rowModel.getObject().get(key + "_label"); - if (label == null || label.isBlank()) label = value; + String label = truncateLabel(rowModel.getObject().get(key + "_label")); + if (label == null || label.isBlank()) label = truncateLabel(value); String templateUrl = PublishPage.MOUNT_PATH + "?template=" + Utils.urlEncode(value) + "&template-version=latest"; String html = "" + Strings.escapeMarkup(label) + ""; cellItem.add(new Label(componentId, html).setEscapeModelStrings(false)); } else if (value.matches("https?://.+")) { - String label = rowModel.getObject().get(key + "_label"); + String label = truncateLabel(rowModel.getObject().get(key + "_label")); cellItem.add(new NanodashLink(componentId, value, null, null, label, contextId)); } else { if (key.startsWith("pubkey")) { @@ -275,4 +275,11 @@ public void populateItem(Item> cellItem, String } + private static String truncateLabel(String label) { + if (label != null && label.length() > 120) { + return label.substring(0, 100) + "..."; + } + return label; + } + } From 61b58ae108b50e85ba1fb63da9a74e7697d46845 Mon Sep 17 00:00:00 2001 From: Tobias Kuhn Date: Mon, 13 Apr 2026 21:19:48 +0200 Subject: [PATCH 11/12] fix: truncate long labels in nanopub set view header links Moved truncateLabel to Utils and applied it to NanopubItem's header link, which is the main link in the nanopub set view. Co-Authored-By: Claude Sonnet 4.6 --- src/main/java/com/knowledgepixels/nanodash/Utils.java | 7 +++++++ .../knowledgepixels/nanodash/component/NanopubItem.java | 2 +- .../nanodash/component/QueryResultTable.java | 5 +---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/knowledgepixels/nanodash/Utils.java b/src/main/java/com/knowledgepixels/nanodash/Utils.java index dffa29819..10952cb18 100644 --- a/src/main/java/com/knowledgepixels/nanodash/Utils.java +++ b/src/main/java/com/knowledgepixels/nanodash/Utils.java @@ -149,6 +149,13 @@ public static String urlEncode(Object o) { return URLEncoder.encode((o == null ? "" : o.toString()), Charsets.UTF_8); } + public static String truncateLabel(String label) { + if (label != null && label.length() > 120) { + return label.substring(0, 100) + "..."; + } + return label; + } + /** * URL-decodes the string representation of the given object using UTF-8 encoding. * diff --git a/src/main/java/com/knowledgepixels/nanodash/component/NanopubItem.java b/src/main/java/com/knowledgepixels/nanodash/component/NanopubItem.java index 1f4a05280..5f002ac18 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/NanopubItem.java +++ b/src/main/java/com/knowledgepixels/nanodash/component/NanopubItem.java @@ -91,7 +91,7 @@ private void initialize() { add(new Label("header", "").setVisible(false)); } else { WebMarkupContainer header = new WebMarkupContainer("header"); - String labelString = n.getLabel(); + String labelString = Utils.truncateLabel(n.getLabel()); if (labelString == null || labelString.isBlank()) labelString = Utils.getShortNanopubId(n.getUri()); header.add(NanodashLink.createLink("nanopub-id-link", n.getUri(), labelString, null)); if (!hideActionMenu && (actions == null || !actions.isEmpty())) { diff --git a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java index b425f3e43..8d7a52b6b 100644 --- a/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java +++ b/src/main/java/com/knowledgepixels/nanodash/component/QueryResultTable.java @@ -276,10 +276,7 @@ public void populateItem(Item> cellItem, String } private static String truncateLabel(String label) { - if (label != null && label.length() > 120) { - return label.substring(0, 100) + "..."; - } - return label; + return Utils.truncateLabel(label); } } From 376f3d8254d20004b22e8792fb4a891f55798fb3 Mon Sep 17 00:00:00 2001 From: Tobias Kuhn Date: Tue, 14 Apr 2026 07:17:35 +0200 Subject: [PATCH 12/12] fix: use getShortDisplayName for unapproved/unknown pubkey agent links Co-Authored-By: Claude Sonnet 4.6 --- .../java/com/knowledgepixels/nanodash/domain/UserData.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/knowledgepixels/nanodash/domain/UserData.java b/src/main/java/com/knowledgepixels/nanodash/domain/UserData.java index 5cfcb5b72..31468a0ec 100644 --- a/src/main/java/com/knowledgepixels/nanodash/domain/UserData.java +++ b/src/main/java/com/knowledgepixels/nanodash/domain/UserData.java @@ -416,12 +416,12 @@ public String getShortDisplayNameForPubkeyHash(IRI userIri, String pubkeyHash) { if (ids == null || ids.isEmpty()) { ids = unapprovedPubkeyhashIdMap.get(pubkeyHash); if (ids == null || ids.isEmpty()) { - return getShortName(userIri); + return getShortDisplayName(userIri); } else if (ids.size() == 1) { return getShortDisplayName(ids.iterator().next()); } else { // Not showing "contested identity" for now. - return getShortName(userIri); // + " (contested identity)"; + return getShortDisplayName(userIri); // + " (contested identity)"; } } else if (ids.size() == 1) { return getShortDisplayName(ids.iterator().next());