diff --git a/code/api/assistant-api/src/main/java/nu/marginalia/assistant/client/model/SimilarDomain.java b/code/api/assistant-api/src/main/java/nu/marginalia/assistant/client/model/SimilarDomain.java index ccd7fe5b..1bdae22e 100644 --- a/code/api/assistant-api/src/main/java/nu/marginalia/assistant/client/model/SimilarDomain.java +++ b/code/api/assistant-api/src/main/java/nu/marginalia/assistant/client/model/SimilarDomain.java @@ -36,6 +36,10 @@ public record SimilarDomain(EdgeUrl url, BIDIRECTIONAL, NONE; + public boolean isLinked() { + return this != NONE; + } + public static LinkType find(boolean linkStod, boolean linkDtos) { if (linkDtos && linkStod) diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchOperator.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchOperator.java index 73ed1251..cd630b36 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchOperator.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchOperator.java @@ -85,6 +85,14 @@ public class SearchOperator { return searchQueryService.getResultsFromQuery(queryResponse); } + + public List doLinkSearch(Context context, String source, String dest) { + var queryParams = paramFactory.forLinkSearch(source, dest); + var queryResponse = queryClient.search(context, queryParams); + + return searchQueryService.getResultsFromQuery(queryResponse); + } + public DecoratedSearchResults doSearch(Context ctx, SearchParameters userParams) { Future eval = searchUnitConversionService.tryEval(ctx, userParams.query()); @@ -181,4 +189,5 @@ public class SearchOperator { } + } diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchQueryParamFactory.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchQueryParamFactory.java index 7430f6bb..e2efb9d5 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchQueryParamFactory.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchQueryParamFactory.java @@ -69,4 +69,21 @@ public class SearchQueryParamFactory { SearchSetIdentifier.NONE ); } + + public QueryParams forLinkSearch(String sourceDomain, String destDomain) { + return new QueryParams(STR."site:\{sourceDomain} links:\{destDomain}", + null, + List.of(), + List.of(), + List.of(), + List.of(), + SpecificationLimit.none(), + SpecificationLimit.none(), + SpecificationLimit.none(), + SpecificationLimit.none(), + List.of(), + new QueryLimits(100, 100, 100, 512), + SearchSetIdentifier.NONE + ); + } } diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchService.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchService.java index 1ed36a98..b2d54cb5 100644 --- a/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchService.java +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/SearchService.java @@ -32,6 +32,7 @@ public class SearchService extends Service { SearchErrorPageService errorPageService, SearchAddToCrawlQueueService addToCrawlQueueService, SearchSiteInfoService siteInfoService, + SearchCrosstalkService crosstalkService, SearchQueryService searchQueryService ) { super(params); @@ -55,6 +56,8 @@ public class SearchService extends Service { Spark.get("/public/site/:site", siteInfoService::handle); Spark.post("/public/site/:site", siteInfoService::handlePost); + Spark.get("/public/crosstalk/", crosstalkService::handle); + Spark.exception(Exception.class, (e,p,q) -> { logger.error("Error during processing", e); errorPageService.serveError(Context.fromRequest(p), p, q); diff --git a/code/services-application/search-service/src/main/java/nu/marginalia/search/svc/SearchCrosstalkService.java b/code/services-application/search-service/src/main/java/nu/marginalia/search/svc/SearchCrosstalkService.java new file mode 100644 index 00000000..910348e0 --- /dev/null +++ b/code/services-application/search-service/src/main/java/nu/marginalia/search/svc/SearchCrosstalkService.java @@ -0,0 +1,70 @@ +package nu.marginalia.search.svc; + +import com.google.inject.Inject; +import nu.marginalia.client.Context; +import nu.marginalia.renderer.MustacheRenderer; +import nu.marginalia.renderer.RendererFactory; +import nu.marginalia.search.SearchOperator; +import nu.marginalia.search.model.UrlDetails; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spark.Request; +import spark.Response; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; + +public class SearchCrosstalkService { + private static final Logger logger = LoggerFactory.getLogger(SearchCrosstalkService.class); + private final SearchOperator searchOperator; + private final MustacheRenderer renderer; + + @Inject + public SearchCrosstalkService(SearchOperator searchOperator, + RendererFactory rendererFactory) throws IOException + { + this.searchOperator = searchOperator; + this.renderer = rendererFactory.renderer("search/site-info/site-crosstalk"); + } + + public Object handle(Request request, Response response) throws SQLException { + String domains = request.queryParams("domains"); + String[] parts = StringUtils.split(domains, ','); + + if (parts.length != 2) { + throw new IllegalArgumentException("Expected exactly two domains"); + } + + response.type("text/html"); + + for (int i = 0; i < parts.length; i++) { + parts[i] = parts[i].trim(); + } + + var resAtoB = searchOperator.doLinkSearch(Context.fromRequest(request), parts[0], parts[1]); + var resBtoA = searchOperator.doLinkSearch(Context.fromRequest(request), parts[1], parts[0]); + + var model = new CrosstalkResult(parts[0], parts[1], resAtoB, resBtoA); + + return renderer.render(model); + } + + + + private record CrosstalkResult(String domainA, + String domainB, + List forward, + List backward) + { + + public boolean isFocusDomain() { + return true; // Hack to get the search result templates behave well + } + public boolean hasBoth() { + return !forward.isEmpty() && !backward.isEmpty(); + } + + } +} diff --git a/code/services-application/search-service/src/main/resources/static/search/serp.scss b/code/services-application/search-service/src/main/resources/static/search/serp.scss index c19f04ee..3ab845bc 100644 --- a/code/services-application/search-service/src/main/resources/static/search/serp.scss +++ b/code/services-application/search-service/src/main/resources/static/search/serp.scss @@ -352,6 +352,15 @@ footer { align-items: start; } +#crosstalk-view { + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: auto 1fr; + grid-gap: 1ch; + align-content: start; + justify-content: start; + align-items: start; +} #similar-view { display: grid; @@ -405,7 +414,7 @@ footer { } @media (max-device-width: 900px) { - #similar-view { + #similar-view, #crosstalk-view { display: block; * { margin-bottom: 1ch; diff --git a/code/services-application/search-service/src/main/resources/templates/search/site-info/site-crosstalk.hdb b/code/services-application/search-service/src/main/resources/templates/search/site-info/site-crosstalk.hdb new file mode 100644 index 00000000..3f11b0a3 --- /dev/null +++ b/code/services-application/search-service/src/main/resources/templates/search/site-info/site-crosstalk.hdb @@ -0,0 +1,39 @@ + + + + + Marginalia Search - {{domainA}} and {{domainB}} + + + + + + + + +{{>search/parts/search-header}} + +{{>search/parts/search-form}} + +
+ Showing results containing links between {{domainA}} and {{domainB}}. +
+{{#each tests}}{{.}}{{/each}} +
+
+ {{#each forward}} + {{>search/parts/search-result}} + {{/each}} +
+
+ {{#each backward}} + {{>search/parts/search-result}} + {{/each}} +
+
+ + +{{>search/parts/search-footer}} + + + diff --git a/code/services-application/search-service/src/main/resources/templates/search/site-info/site-info-summary.hdb b/code/services-application/search-service/src/main/resources/templates/search/site-info/site-info-summary.hdb index 89954e98..fd1c7590 100644 --- a/code/services-application/search-service/src/main/resources/templates/search/site-info/site-info-summary.hdb +++ b/code/services-application/search-service/src/main/resources/templates/search/site-info/site-info-summary.hdb @@ -44,7 +44,9 @@ {{#if screenshot}}📷{{/if}} - {{{linkType}}} + {{#if linkType.isLinked}} + {{{linkType}}} + {{/if}} {{{rankSymbols}}} @@ -92,7 +94,9 @@ {{#if screenshot}}📷{{/if}} - {{{linkType}}} + {{#if linkType.isLinked}} + {{{linkType}}} + {{/if}} {{{rankSymbols}}}