mirror of
https://github.com/MarginaliaSearch/MarginaliaSearch.git
synced 2025-02-23 21:18:58 +00:00
(search) New frontend look
This commit is contained in:
parent
fa7534a362
commit
2f4500be5a
@ -1,6 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
|
id 'io.freefair.sass-base' version '8.4'
|
||||||
|
id 'io.freefair.sass-java' version '8.4'
|
||||||
id 'com.palantir.docker' version '0.35.0'
|
id 'com.palantir.docker' version '0.35.0'
|
||||||
id 'application'
|
id 'application'
|
||||||
id 'jvm-test-suite'
|
id 'jvm-test-suite'
|
||||||
@ -20,6 +21,11 @@ java {
|
|||||||
languageVersion.set(JavaLanguageVersion.of(21))
|
languageVersion.set(JavaLanguageVersion.of(21))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sass {
|
||||||
|
sourceMapEnabled = true
|
||||||
|
sourceMapEmbed = true
|
||||||
|
outputStyle = EXPANDED
|
||||||
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':code:common:db')
|
implementation project(':code:common:db')
|
||||||
implementation project(':code:common:model')
|
implementation project(':code:common:model')
|
||||||
|
@ -4,12 +4,14 @@ import com.google.inject.Inject;
|
|||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import io.reactivex.rxjava3.core.Observable;
|
import io.reactivex.rxjava3.core.Observable;
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
import nu.marginalia.WebsiteUrl;
|
||||||
import nu.marginalia.assistant.client.AssistantClient;
|
import nu.marginalia.assistant.client.AssistantClient;
|
||||||
import nu.marginalia.model.EdgeDomain;
|
import nu.marginalia.model.EdgeDomain;
|
||||||
import nu.marginalia.db.DbDomainQueries;
|
import nu.marginalia.db.DbDomainQueries;
|
||||||
import nu.marginalia.query.client.QueryClient;
|
import nu.marginalia.query.client.QueryClient;
|
||||||
import nu.marginalia.query.model.QueryResponse;
|
import nu.marginalia.query.model.QueryResponse;
|
||||||
import nu.marginalia.search.command.SearchParameters;
|
import nu.marginalia.search.command.SearchParameters;
|
||||||
|
import nu.marginalia.search.model.SearchFilters;
|
||||||
import nu.marginalia.search.model.SearchProfile;
|
import nu.marginalia.search.model.SearchProfile;
|
||||||
import nu.marginalia.search.model.UrlDetails;
|
import nu.marginalia.search.model.UrlDetails;
|
||||||
import nu.marginalia.client.Context;
|
import nu.marginalia.client.Context;
|
||||||
@ -41,6 +43,7 @@ public class SearchOperator {
|
|||||||
private final QueryClient queryClient;
|
private final QueryClient queryClient;
|
||||||
private final SearchQueryIndexService searchQueryService;
|
private final SearchQueryIndexService searchQueryService;
|
||||||
private final SearchQueryParamFactory paramFactory;
|
private final SearchQueryParamFactory paramFactory;
|
||||||
|
private final WebsiteUrl websiteUrl;
|
||||||
private final SearchUnitConversionService searchUnitConversionService;
|
private final SearchUnitConversionService searchUnitConversionService;
|
||||||
|
|
||||||
|
|
||||||
@ -50,6 +53,7 @@ public class SearchOperator {
|
|||||||
QueryClient queryClient,
|
QueryClient queryClient,
|
||||||
SearchQueryIndexService searchQueryService,
|
SearchQueryIndexService searchQueryService,
|
||||||
SearchQueryParamFactory paramFactory,
|
SearchQueryParamFactory paramFactory,
|
||||||
|
WebsiteUrl websiteUrl,
|
||||||
SearchUnitConversionService searchUnitConversionService)
|
SearchUnitConversionService searchUnitConversionService)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -59,6 +63,7 @@ public class SearchOperator {
|
|||||||
|
|
||||||
this.searchQueryService = searchQueryService;
|
this.searchQueryService = searchQueryService;
|
||||||
this.paramFactory = paramFactory;
|
this.paramFactory = paramFactory;
|
||||||
|
this.websiteUrl = websiteUrl;
|
||||||
this.searchUnitConversionService = searchUnitConversionService;
|
this.searchUnitConversionService = searchUnitConversionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,6 +94,7 @@ public class SearchOperator {
|
|||||||
.problems(getProblems(ctx, evalResult, queryResults, queryResponse))
|
.problems(getProblems(ctx, evalResult, queryResults, queryResponse))
|
||||||
.evalResult(evalResult)
|
.evalResult(evalResult)
|
||||||
.results(queryResults)
|
.results(queryResults)
|
||||||
|
.filters(new SearchFilters(websiteUrl, userParams))
|
||||||
.focusDomain(queryResponse.domain())
|
.focusDomain(queryResponse.domain())
|
||||||
.focusDomainId(getDomainId(queryResponse.domain()))
|
.focusDomainId(getDomainId(queryResponse.domain()))
|
||||||
.build();
|
.build();
|
||||||
|
@ -1,9 +1,30 @@
|
|||||||
package nu.marginalia.search.command;
|
package nu.marginalia.search.command;
|
||||||
|
|
||||||
|
import nu.marginalia.WebsiteUrl;
|
||||||
import nu.marginalia.search.model.SearchProfile;
|
import nu.marginalia.search.model.SearchProfile;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
public record SearchParameters(String query, SearchProfile profile, SearchJsParameter js) {
|
public record SearchParameters(String query, SearchProfile profile, SearchJsParameter js) {
|
||||||
public String profileStr() {
|
public String profileStr() {
|
||||||
return profile.name;
|
return profile.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SearchParameters withProfile(SearchProfile profile) {
|
||||||
|
return new SearchParameters(query, profile, js);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchParameters withJs(SearchJsParameter js) {
|
||||||
|
return new SearchParameters(query, profile, js);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String renderUrl(WebsiteUrl baseUrl) {
|
||||||
|
String path = String.format("/search?query=%s&profile=%s&js=%s",
|
||||||
|
URLEncoder.encode(query, StandardCharsets.UTF_8),
|
||||||
|
URLEncoder.encode(profile.name, StandardCharsets.UTF_8),
|
||||||
|
URLEncoder.encode(js.value, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
return baseUrl.withPath(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ public class DecoratedSearchResults {
|
|||||||
|
|
||||||
private final String focusDomain;
|
private final String focusDomain;
|
||||||
private final int focusDomainId;
|
private final int focusDomainId;
|
||||||
|
private final SearchFilters filters;
|
||||||
public String getQuery() {
|
public String getQuery() {
|
||||||
return params.query();
|
return params.query();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
package nu.marginalia.search.model;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import nu.marginalia.WebsiteUrl;
|
||||||
|
import nu.marginalia.search.command.SearchJsParameter;
|
||||||
|
import nu.marginalia.search.command.SearchParameters;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Models the search filters displayed next to the search results */
|
||||||
|
public class SearchFilters {
|
||||||
|
private final WebsiteUrl url;
|
||||||
|
|
||||||
|
// These are necessary for the renderer to access the data
|
||||||
|
@Getter
|
||||||
|
public final RemoveJsOption removeJsOption;
|
||||||
|
@Getter
|
||||||
|
public final List<List<Filter>> filterGroups;
|
||||||
|
|
||||||
|
public SearchFilters(WebsiteUrl url, SearchParameters parameters) {
|
||||||
|
this.url = url;
|
||||||
|
|
||||||
|
removeJsOption = new RemoveJsOption(parameters);
|
||||||
|
|
||||||
|
filterGroups = List.of(
|
||||||
|
List.of(
|
||||||
|
new Filter("No Filter", SearchProfile.NO_FILTER, parameters),
|
||||||
|
new Filter("Popular", SearchProfile.DEFAULT, parameters),
|
||||||
|
new Filter("Small Web", SearchProfile.SMALLWEB, parameters),
|
||||||
|
new Filter("Blogosphere", SearchProfile.BLOGOSPHERE, parameters),
|
||||||
|
new Filter("Academia", SearchProfile.ACADEMIA, parameters)
|
||||||
|
),
|
||||||
|
List.of(
|
||||||
|
new Filter("Vintage", SearchProfile.VINTAGE, parameters),
|
||||||
|
new Filter("Plain Text", SearchProfile.PLAIN_TEXT, parameters),
|
||||||
|
new Filter("~tilde", SearchProfile.TILDE, parameters)
|
||||||
|
),
|
||||||
|
List.of(
|
||||||
|
new Filter("Wiki", SearchProfile.WIKI, parameters),
|
||||||
|
new Filter("Forum", SearchProfile.FORUM, parameters),
|
||||||
|
new Filter("Docs", SearchProfile.DOCS, parameters),
|
||||||
|
new Filter("Recipes", SearchProfile.FOOD, parameters)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RemoveJsOption {
|
||||||
|
private final SearchJsParameter value;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public final String url;
|
||||||
|
|
||||||
|
public boolean isSet() {
|
||||||
|
return value.equals(SearchJsParameter.DENY_JS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String name() {
|
||||||
|
return "Remove Javascript";
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoveJsOption(SearchParameters parameters) {
|
||||||
|
this.value = parameters.js();
|
||||||
|
|
||||||
|
var toggledValue = switch (parameters.js()) {
|
||||||
|
case DENY_JS -> SearchJsParameter.DEFAULT;
|
||||||
|
default -> SearchJsParameter.DENY_JS;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.url = parameters.withJs(toggledValue).renderUrl(SearchFilters.this.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Filter {
|
||||||
|
@Getter
|
||||||
|
public final String displayName;
|
||||||
|
|
||||||
|
public final SearchProfile profile;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public final boolean current;
|
||||||
|
@Getter
|
||||||
|
public final String url;
|
||||||
|
|
||||||
|
public Filter(String displayName, SearchProfile profile, SearchParameters parameters) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.profile = profile;
|
||||||
|
this.current = profile.equals(parameters.profile());
|
||||||
|
|
||||||
|
this.url = parameters.withProfile(profile).renderUrl(SearchFilters.this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -9,9 +9,9 @@ import java.util.Objects;
|
|||||||
|
|
||||||
public enum SearchProfile {
|
public enum SearchProfile {
|
||||||
DEFAULT("default", SearchSetIdentifier.RETRO),
|
DEFAULT("default", SearchSetIdentifier.RETRO),
|
||||||
MODERN("modern", SearchSetIdentifier.SMALLWEB),
|
SMALLWEB("modern", SearchSetIdentifier.SMALLWEB),
|
||||||
BLOGOSPHERE("blogosphere", SearchSetIdentifier.BLOGS),
|
BLOGOSPHERE("blogosphere", SearchSetIdentifier.BLOGS),
|
||||||
CORPO("corpo", SearchSetIdentifier.NONE),
|
NO_FILTER("corpo", SearchSetIdentifier.NONE),
|
||||||
YOLO("yolo", SearchSetIdentifier.NONE),
|
YOLO("yolo", SearchSetIdentifier.NONE),
|
||||||
VINTAGE("vintage", SearchSetIdentifier.NONE),
|
VINTAGE("vintage", SearchSetIdentifier.NONE),
|
||||||
TILDE("tilde", SearchSetIdentifier.NONE),
|
TILDE("tilde", SearchSetIdentifier.NONE),
|
||||||
@ -38,7 +38,7 @@ public enum SearchProfile {
|
|||||||
private final static SearchProfile[] values = values();
|
private final static SearchProfile[] values = values();
|
||||||
public static SearchProfile getSearchProfile(String param) {
|
public static SearchProfile getSearchProfile(String param) {
|
||||||
if (null == param) {
|
if (null == param) {
|
||||||
return YOLO;
|
return NO_FILTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var profile : values) {
|
for (var profile : values) {
|
||||||
@ -47,7 +47,7 @@ public enum SearchProfile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return YOLO;
|
return NO_FILTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addTacitTerms(SearchSubquery subquery) {
|
public void addTacitTerms(SearchSubquery subquery) {
|
||||||
@ -82,7 +82,7 @@ public enum SearchProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SpecificationLimit getYearLimit() {
|
public SpecificationLimit getYearLimit() {
|
||||||
if (this == MODERN) {
|
if (this == SMALLWEB) {
|
||||||
return SpecificationLimit.greaterThan(2015);
|
return SpecificationLimit.greaterThan(2015);
|
||||||
}
|
}
|
||||||
if (this == VINTAGE) {
|
if (this == VINTAGE) {
|
||||||
@ -92,7 +92,7 @@ public enum SearchProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SpecificationLimit getSizeLimit() {
|
public SpecificationLimit getSizeLimit() {
|
||||||
if (this == MODERN) {
|
if (this == SMALLWEB) {
|
||||||
return SpecificationLimit.lessThan(500);
|
return SpecificationLimit.lessThan(500);
|
||||||
}
|
}
|
||||||
else return SpecificationLimit.none();
|
else return SpecificationLimit.none();
|
||||||
@ -100,7 +100,7 @@ public enum SearchProfile {
|
|||||||
|
|
||||||
|
|
||||||
public SpecificationLimit getQualityLimit() {
|
public SpecificationLimit getQualityLimit() {
|
||||||
if (this == MODERN) {
|
if (this == SMALLWEB) {
|
||||||
return SpecificationLimit.lessThan(5);
|
return SpecificationLimit.lessThan(5);
|
||||||
}
|
}
|
||||||
else return SpecificationLimit.none();
|
else return SpecificationLimit.none();
|
||||||
|
@ -14,8 +14,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
import spark.Request;
|
import spark.Request;
|
||||||
import spark.Response;
|
import spark.Response;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class SearchQueryService {
|
public class SearchQueryService {
|
||||||
|
|
||||||
private final WebsiteUrl websiteUrl;
|
private final WebsiteUrl websiteUrl;
|
||||||
@ -38,23 +36,8 @@ public class SearchQueryService {
|
|||||||
|
|
||||||
final var ctx = Context.fromRequest(request);
|
final var ctx = Context.fromRequest(request);
|
||||||
|
|
||||||
final String queryParam = request.queryParams("query");
|
|
||||||
if (null == queryParam || queryParam.isBlank()) {
|
|
||||||
response.redirect(websiteUrl.url());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String profileStr = Optional.ofNullable(request.queryParams("profile")).orElse(SearchProfile.YOLO.name);
|
|
||||||
final String humanQuery = queryParam.trim();
|
|
||||||
|
|
||||||
var params = new SearchParameters(
|
|
||||||
humanQuery,
|
|
||||||
SearchProfile.getSearchProfile(profileStr),
|
|
||||||
SearchJsParameter.parse(request.queryParams("js"))
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return searchCommandEvaulator.eval(ctx, params);
|
return searchCommandEvaulator.eval(ctx, parseParameters(request));
|
||||||
}
|
}
|
||||||
catch (RedirectException ex) {
|
catch (RedirectException ex) {
|
||||||
response.redirect(ex.newUrl);
|
response.redirect(ex.newUrl);
|
||||||
@ -67,4 +50,15 @@ public class SearchQueryService {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SearchParameters parseParameters(Request request) {
|
||||||
|
final String queryParam = request.queryParams("query");
|
||||||
|
|
||||||
|
if (null == queryParam || queryParam.isBlank()) {
|
||||||
|
throw new RedirectException(websiteUrl.url());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SearchParameters(queryParam.trim(),
|
||||||
|
SearchProfile.getSearchProfile(request.queryParams("profile")),
|
||||||
|
SearchJsParameter.parse(request.queryParams("js")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
document.getElementsByTagName('body')[0].setAttribute('data-has-js', 'true');
|
||||||
|
|
||||||
|
function setDisplay(element, value) {
|
||||||
|
element.style.display = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('mcfeast').onclick = (event) => {
|
||||||
|
setDisplay(document.getElementById('filters'), 'block');
|
||||||
|
event.stopPropagation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('menu-close').onclick = (event) => {
|
||||||
|
setDisplay(document.getElementById('filters'), 'none');
|
||||||
|
event.stopPropagation();
|
||||||
|
return false;
|
||||||
|
}
|
@ -0,0 +1,330 @@
|
|||||||
|
$nicotine-dark: #acae89;
|
||||||
|
$nicotine-light: #f8f8ee;
|
||||||
|
$fg-dark: #000;
|
||||||
|
$fg-light: #fff;
|
||||||
|
$highlight-dark: #2f4858;
|
||||||
|
$highlight-light: #3F5F6F;
|
||||||
|
$highlight-light2: #eee;
|
||||||
|
$border-color: #ccc;
|
||||||
|
$heading-fonts: serif;
|
||||||
|
$visited: #fcc;
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: $nicotine-light;
|
||||||
|
color: $fg-dark;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-left: 2ch;
|
||||||
|
margin-right: 4ch;
|
||||||
|
max-width: 120ch;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background-color: $nicotine-dark;
|
||||||
|
color: #fff;
|
||||||
|
border-bottom: 1px solid $border-color;
|
||||||
|
margin-bottom: 1ch;
|
||||||
|
|
||||||
|
nav {
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #000;
|
||||||
|
|
||||||
|
padding: .5ch;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.extra {
|
||||||
|
background: #ccc linear-gradient(45deg,
|
||||||
|
rgba(255,100,100,1) 0%,
|
||||||
|
rgba(100,255,100,1) 50%,
|
||||||
|
rgba(100,100,255,1) 100%);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover, a:focus {
|
||||||
|
background: #2f4858;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.positions {
|
||||||
|
box-shadow: 0 0 2px #888;
|
||||||
|
background-color: #e4e4e4;
|
||||||
|
padding: 2px;
|
||||||
|
margin-right: -1ch;
|
||||||
|
margin-left: 1ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
clear: both;
|
||||||
|
|
||||||
|
padding: 2ch;
|
||||||
|
margin: 16ch 0 0 0;
|
||||||
|
|
||||||
|
font-size: 12pt;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: normal;
|
||||||
|
border-bottom: 4px solid $highlight-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 14pt;
|
||||||
|
font-weight: normal;
|
||||||
|
border-bottom: 2px solid $highlight-dark;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
line-height: 1.5;
|
||||||
|
flex-basis: 40ch;
|
||||||
|
flex-grow: 1.1;
|
||||||
|
|
||||||
|
background-color: #fff;
|
||||||
|
border-left: 1px solid $border-color;
|
||||||
|
box-shadow: -1px -1px 5px $border-color;
|
||||||
|
|
||||||
|
padding-left: 1ch;
|
||||||
|
padding-right: 1ch;
|
||||||
|
margin-left: 1ch;
|
||||||
|
padding-bottom: 1ch;
|
||||||
|
margin-bottom: 1ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#mcfeast, #menu-close {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadowbox {
|
||||||
|
box-shadow: 0 0 1ch $border-color;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5ch;
|
||||||
|
background-color: $highlight-dark;
|
||||||
|
font-family: $heading-fonts;
|
||||||
|
font-weight: normal;
|
||||||
|
color: $fg-light;
|
||||||
|
font-size: 12pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-narrow {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto max-content;
|
||||||
|
grid-gap: 1ch;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-box {
|
||||||
|
@extend .shadowbox;
|
||||||
|
|
||||||
|
padding: 0.5ch;
|
||||||
|
background-color: $fg-light;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content auto max-content;
|
||||||
|
grid-gap: 0.5ch;
|
||||||
|
grid-auto-rows: minmax(1ch, auto);
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5ch;
|
||||||
|
font-size: 14pt;
|
||||||
|
word-break: keep-all;
|
||||||
|
background-color: $highlight-dark;
|
||||||
|
color: $fg-light;
|
||||||
|
font-family: $heading-fonts;
|
||||||
|
font-weight: normal;
|
||||||
|
border: 1px solid;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14pt;
|
||||||
|
padding: 0.5ch;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[type="submit"] {
|
||||||
|
font-size: 14pt;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-toggle-on {
|
||||||
|
a:before {
|
||||||
|
content: '✓';
|
||||||
|
margin-right: 1.5ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter-toggle-off {
|
||||||
|
a:before {
|
||||||
|
content: '✗';
|
||||||
|
margin-right: 1.5ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#filters {
|
||||||
|
@extend .shadowbox;
|
||||||
|
margin-top: 1ch;
|
||||||
|
background-color: $fg-light;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
@extend .heading;
|
||||||
|
background-color: $highlight-light;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
@extend .heading;
|
||||||
|
background-color: $highlight-light2;
|
||||||
|
font-family: sans-serif;
|
||||||
|
color: #000;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border-top: 0.5px solid $border-color;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding: 1ch;
|
||||||
|
a {
|
||||||
|
color: $fg-dark;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover, a:focus {
|
||||||
|
border-bottom: 1px solid $highlight-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li.current {
|
||||||
|
border-left: 4px solid $highlight-light;
|
||||||
|
background-color: $highlight-light2;
|
||||||
|
a {
|
||||||
|
margin-left: -4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result {
|
||||||
|
@extend .shadowbox;
|
||||||
|
margin: 1ch;
|
||||||
|
|
||||||
|
.url {
|
||||||
|
background-color: $highlight-light;
|
||||||
|
padding-left: 0.5ch;
|
||||||
|
a {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 8pt;
|
||||||
|
color: $fg-light;
|
||||||
|
}
|
||||||
|
a:visited {
|
||||||
|
color: $visited;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
a {
|
||||||
|
color: $fg-light;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
font-size: 12pt;
|
||||||
|
@extend .heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
background-color: $fg-light;
|
||||||
|
padding: 1ch;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.utils {
|
||||||
|
display: flex;
|
||||||
|
font-size: 10pt;
|
||||||
|
padding: 1ch;
|
||||||
|
background-color: #eee;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin-right: 1ch;
|
||||||
|
margin-left: 1ch;
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
flex-grow: 2;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.meta > * {
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-device-width: 624px) {
|
||||||
|
body[data-has-js="true"] {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 0 0 0 !important;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
#mcfeast {
|
||||||
|
display: inline;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu-close {
|
||||||
|
float: right;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#filters {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-narrow {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-box {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#filters {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<h2>Filters <button id="menu-close">X</button></h2>
|
||||||
|
<ul>
|
||||||
|
{{#with removeJsOption}}
|
||||||
|
<li class="filter-toggle-{{#if set}}on{{/if}}{{#unless set}}off{{/unless}}"><a href="{{url}}">{{name}}</a></li>
|
||||||
|
{{/with}}
|
||||||
|
</ul>
|
||||||
|
<h3>Domains</h3>
|
||||||
|
<ul>
|
||||||
|
{{#each filterGroups}}
|
||||||
|
{{#each .}}
|
||||||
|
<li {{#if current}}class="current"{{/if}}><a href="{{url}}">{{displayName}}</a></li>
|
||||||
|
{{/each}}
|
||||||
|
<hr>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
@ -112,4 +112,6 @@
|
|||||||
|
|
||||||
</section>
|
</section>
|
||||||
</footer>
|
</footer>
|
||||||
<script src="/tts.js" rel="javascript"></script>
|
|
||||||
|
<script src="js/tts.js"></script>
|
||||||
|
<script src="js/serp.js"></script>
|
@ -1,41 +1,12 @@
|
|||||||
<form method="get" action="/search">
|
<form action="search" method="get">
|
||||||
<section class="search-box">
|
<div id="search-box">
|
||||||
<h1>Search the Internet</h1>
|
<h1>
|
||||||
<div class="input">
|
Search The Internet
|
||||||
<input id="query" name="query" placeholder="Search terms" value="{{query}}">
|
<button id="mcfeast" aria-controls="#filters">
|
||||||
<input value="Go" type="submit">
|
Filters
|
||||||
</div>
|
</button>
|
||||||
<div class="settings">
|
</h1>
|
||||||
<select name="profile" id="profile">
|
<input type="text" name="query" placeholder="Search..." value="{{query}}" autocomplete="off">
|
||||||
<optgroup label="General Search">
|
<button type="submit">Search</button>
|
||||||
<option {{#eq profile "default"}}selected{{/eq}} value="default">Popular Sites</option>
|
</div>
|
||||||
<option {{#eq profile "modern"}}selected{{/eq}} value="modern">Personal Websites</option>
|
|
||||||
<option {{#eq profile "academia"}}selected{{/eq}} value="academia">Academia</option>
|
|
||||||
<option {{#eq profile "corpo"}}selected{{/eq}} value="corpo">No Domain Ranking</option>
|
|
||||||
</optgroup>
|
|
||||||
<optgroup label="Vintage">
|
|
||||||
<option {{#eq profile "vintage"}}selected{{/eq}} value="vintage">Web 1.0</option>
|
|
||||||
<option {{#eq profile "tilde"}}selected{{/eq}} value="tilde">~tilde/</option>
|
|
||||||
<option {{#eq profile "plain-text"}}selected{{/eq}} value="plain-text">Text Files</option>
|
|
||||||
</optgroup>
|
|
||||||
<optgroup label="Category">
|
|
||||||
<option {{#eq profile "blogosphere"}}selected{{/eq}} value="blogosphere">Blogosphere (NEW)</option>
|
|
||||||
<option {{#eq profile "wiki"}}selected{{/eq}} value="wiki">Wiki</option>
|
|
||||||
<option {{#eq profile "forum"}}selected{{/eq}} value="forum">Forum</option>
|
|
||||||
<option {{#eq profile "docs"}}selected{{/eq}} value="docs">Docs (experimental)</option>
|
|
||||||
</optgroup>
|
|
||||||
<optgroup label="Topics Search">
|
|
||||||
<option {{#eq profile "food"}}selected{{/eq}} value="food">Recipes 🍳</option>
|
|
||||||
<option {{#eq profile "crafts"}}selected{{/eq}} value="crafts">Crafts 🪡🔨 (WIP; mostly textile-craft)</option>
|
|
||||||
<option {{#eq profile "classics"}}selected{{/eq}} value="classics">Classics and Antiquity 📜</option>
|
|
||||||
</optgroup>
|
|
||||||
|
|
||||||
</select>
|
|
||||||
<select name="js" id="js">
|
|
||||||
<option {{#eq js "default"}}selected{{/eq}} value="default">Allow JS</option>
|
|
||||||
<option {{#eq js "no-js"}}selected{{/eq}} value="no-js">Deny JS</option>
|
|
||||||
<option {{#eq js "yes-js"}}selected{{/eq}} value="yes-js">Require JS</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</form>
|
</form>
|
@ -0,0 +1,21 @@
|
|||||||
|
<section class="card search-result {{#unless hideRanking}}rs-rank-{{logRank}} ms-rank-{{matchRank}}{{/unless}} {{#if specialDomain}}special-domain{{/if}}" >
|
||||||
|
<div class="url"><a rel="nofollow external" href="{{url}}">{{url}}</a></div>
|
||||||
|
<h2> <a tabindex="-1" class="title" rel="nofollow external" href="{{url}}">{{title}}</a> </h2>
|
||||||
|
<p class="description">{{description}}</p>
|
||||||
|
|
||||||
|
<div class="utils">
|
||||||
|
{{#unless focusDomain}}
|
||||||
|
<a href="/site/{{url.domain}}" title="Domain Information">Info</a>
|
||||||
|
{{#if hasMoreResults}}<a href="/site-search/{{url.domain}}/{{query}}?profile={{profile}}" title="More results from this domain">{{resultsFromSameDomain}}+</a>{{/if}}
|
||||||
|
{{/unless}}
|
||||||
|
<div class="meta">
|
||||||
|
{{#if problems}} <span class="problems" title="{{problems}}"> ⚠ {{problemCount}} </span> {{/if}}
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="meta positions"
|
||||||
|
title="Positions where keywords were found within the document">
|
||||||
|
{{positions}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
@ -1,8 +0,0 @@
|
|||||||
{{#if problems}} <span class="problems" title="{{problems}}"> ⚠ {{problemCount}} </span> {{/if}}
|
|
||||||
|
|
||||||
<span
|
|
||||||
aria-hidden="true"
|
|
||||||
class="meta positions"
|
|
||||||
title="Positions where keywords were found within the document">
|
|
||||||
{{positions}}
|
|
||||||
</span>
|
|
@ -1,23 +0,0 @@
|
|||||||
<!-- RankingID: {{rankingId}}
|
|
||||||
ID: {{id}} - {{combinedId}}
|
|
||||||
Ranking: {{ranking}}
|
|
||||||
TermScore: {{termScore}}
|
|
||||||
Quality: {{urlQuality}}
|
|
||||||
-->
|
|
||||||
<!--
|
|
||||||
{{#each keywordScores}} {{{.}}} {{/each}}
|
|
||||||
-->
|
|
||||||
<section class="card search-result {{#unless hideRanking}}rs-rank-{{logRank}} ms-rank-{{matchRank}}{{/unless}} {{#if specialDomain}}special-domain{{/if}}" >
|
|
||||||
<div class="url"><a rel="nofollow external" href="{{url}}">{{url}}</a></div>
|
|
||||||
<h2> <a tabindex="-1" class="title" rel="nofollow external" href="{{url}}">{{title}}</a> </h2>
|
|
||||||
<p class="description">{{description}}</p>
|
|
||||||
|
|
||||||
<div class="utils">
|
|
||||||
{{#unless focusDomain}}
|
|
||||||
<a href="/site/{{url.domain}}" title="Domain Information">Info</a>
|
|
||||||
{{#if hasMoreResults}}<a href="/site-search/{{url.domain}}/{{query}}?profile={{profile}}" title="More results from this domain">{{resultsFromSameDomain}}+</a>{{/if}}
|
|
||||||
{{/unless}}
|
|
||||||
<div class="meta">{{>search/search-result-metadata}}</div>
|
|
||||||
</div>
|
|
||||||
<hr class="w3m-helper" />
|
|
||||||
</section>
|
|
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Marginalia Search - {{query}}</title>
|
<title>Marginalia Search - {{query}}</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="/style-new.css" />
|
<link rel="stylesheet" href="/serp.css" />
|
||||||
<link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="Marginalia">
|
<link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="Marginalia">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="robots" content="noindex" />
|
<meta name="robots" content="noindex" />
|
||||||
@ -16,37 +16,20 @@
|
|||||||
|
|
||||||
{{>search/parts/search-header}}
|
{{>search/parts/search-header}}
|
||||||
|
|
||||||
<article>
|
|
||||||
{{>search/parts/search-form}}
|
{{>search/parts/search-form}}
|
||||||
|
|
||||||
|
<section class="sidebar-narrow">
|
||||||
<hr class="w3m-helper" />
|
<section id="results" class="sb-left">
|
||||||
<section class="cards">
|
{{#each results}}{{>search/parts/search-result}}{{/each}}
|
||||||
|
|
||||||
{{#if maintenanceMessage}}<section class="card problems onlyscreen"><h2>Maintenance</h2><p class="description">{{maintenanceMessage}}</p></section>{{/if}}
|
|
||||||
{{#if evalResult}}<section class="card semantic onlyscreen"><h2>Evaluation</h2><p class="description">{{query}} = {{evalResult}}</p><hr class="w3m-helper" /></section>{{/if}}
|
|
||||||
{{#each wiki.entries}}<section class="card semantic onlyscreen"><h2>Encyclopedia</h2><p class="description"><a href="https://encyclopedia.marginalia.nu/wiki/{{.}}"><em>{{.}}</em> Encyclopedia Page</a></p><hr class="w3m-helper" /></section>{{/each}}
|
|
||||||
|
|
||||||
{{#if focusDomain}}
|
|
||||||
<section class="card semantic">
|
|
||||||
<h2>{{focusDomain}}</h2>
|
|
||||||
<p class="description">
|
|
||||||
Showing results from <tt>{{focusDomain}}</tt>
|
|
||||||
</p>
|
|
||||||
<div class="utils">
|
|
||||||
<a href="/site/{{focusDomain}}">Info</a>
|
|
||||||
<a href="/explore/{{focusDomain}}">Similar Domains</a>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#unless evalResult}}{{#if problems}}<section class="card problems onlyscreen"><h2>Suggestions</h2><ul class="onlyscreen search-problems">{{#each problems}}<li>{{{.}}}</li>{{/each}}</ul></section> {{/if}}{{/unless}}
|
{{#with filters}}
|
||||||
|
<section id="filters" class="sb-right">
|
||||||
{{#each domainResults}}{{>search/browse-result}}{{/each}}
|
{{>search/parts/search-filters}}
|
||||||
{{#each results}}{{>search/search-result}}{{/each}}
|
</section>
|
||||||
|
{{/with}}
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
</article>
|
|
||||||
|
|
||||||
{{>search/parts/search-footer}}
|
{{>search/parts/search-footer}}
|
||||||
</body>
|
</body>
|
||||||
|
</html>
|
@ -27,7 +27,7 @@
|
|||||||
{{>search/parts/site-info-index}}
|
{{>search/parts/site-info-index}}
|
||||||
{{>search/parts/site-info-links}}
|
{{>search/parts/site-info-links}}
|
||||||
|
|
||||||
{{#each results}}{{>search/search-result}}{{/each}}
|
{{#each results}}{{>search/parts/search-result}}{{/each}}
|
||||||
</section>
|
</section>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
|
1
run/env/service.env
vendored
1
run/env/service.env
vendored
@ -6,3 +6,4 @@ CONVERTER_PROCESS_OPTS="-Dservice-name=converter -Dservice-host=0.0.0.0"
|
|||||||
CRAWLER_PROCESS_OPTS="-Dservice-name=crawler -Dservice-host=0.0.0.0"
|
CRAWLER_PROCESS_OPTS="-Dservice-name=crawler -Dservice-host=0.0.0.0"
|
||||||
LOADER_PROCESS_OPTS="-Dservice-name=loader -Dservice-host=0.0.0.0"
|
LOADER_PROCESS_OPTS="-Dservice-name=loader -Dservice-host=0.0.0.0"
|
||||||
INDEX_CONSTRUCTION_PROCESS_OPTS="-Dservice-name=index-constructor -Djava.util.concurrent.ForkJoinPool.common.parallelism=4"
|
INDEX_CONSTRUCTION_PROCESS_OPTS="-Dservice-name=index-constructor -Djava.util.concurrent.ForkJoinPool.common.parallelism=4"
|
||||||
|
SEARCH_SERVICE_OPTS="-Dwebsite-url=http://localhost:8080"
|
Loading…
Reference in New Issue
Block a user