diff options
| -rw-r--r-- | modules/config.nix | 1 | ||||
| -rw-r--r-- | modules/network/default.nix | 6 | ||||
| -rw-r--r-- | modules/search/default.nix | 13 | ||||
| -rw-r--r-- | services/omnisearch/default.nix | 77 | ||||
| -rw-r--r-- | services/omnisearch/pkg.nix | 40 | ||||
| -rw-r--r-- | services/omnisearch/static/calculation.svg | 1 | ||||
| -rw-r--r-- | services/omnisearch/static/dictionary.jpg | bin | 0 -> 36614 bytes | |||
| -rw-r--r-- | services/omnisearch/static/favicon.ico | bin | 0 -> 270398 bytes | |||
| -rw-r--r-- | services/omnisearch/static/main.css | 676 | ||||
| -rw-r--r-- | services/omnisearch/templates/home.html | 33 | ||||
| -rw-r--r-- | services/omnisearch/templates/images.html | 104 | ||||
| -rw-r--r-- | services/omnisearch/templates/opensearch.xml | 11 | ||||
| -rw-r--r-- | services/omnisearch/templates/results.html | 134 | ||||
| -rw-r--r-- | settings.nix | 1 |
14 files changed, 1097 insertions, 0 deletions
diff --git a/modules/config.nix b/modules/config.nix index 5ca3cdc..952306f 100644 --- a/modules/config.nix +++ b/modules/config.nix @@ -11,6 +11,7 @@ # ./monitoring ./rss ./calendar + ./search ./packages.nix ]; diff --git a/modules/network/default.nix b/modules/network/default.nix index 25cd5aa..de349e6 100644 --- a/modules/network/default.nix +++ b/modules/network/default.nix @@ -36,9 +36,15 @@ in # "librarian.local" = { inherit host; port = ports.lazylibrarian; }; "paperless.local" = { inherit host; port = ports.paperless; configureNginx = true; }; "calendar.local" = { inherit host; port = ports.caldav; }; + "search.local" = { inherit host; port = ports.search; }; }; }; + # DNS-only mappings + services.bacchus-dns.hosts = { + "smartfridge.local" = settings.network.smartfridgeIP; + }; + # Host mappings defined by service-router services.bacchus-dns = { enable = true; diff --git a/modules/search/default.nix b/modules/search/default.nix new file mode 100644 index 0000000..1663c25 --- /dev/null +++ b/modules/search/default.nix @@ -0,0 +1,13 @@ +{ settings, ... }: +{ + imports = [ ../../services/omnisearch ]; + + services.omnisearch = { + enable = true; + port = settings.network.ports.search; + openFirewall = true; + settings = { + server.domain = "http://search.local"; + }; + }; +} diff --git a/services/omnisearch/default.nix b/services/omnisearch/default.nix new file mode 100644 index 0000000..fb877e5 --- /dev/null +++ b/services/omnisearch/default.nix @@ -0,0 +1,77 @@ +{ + config, + pkgs, + lib, + ... +}: +with lib; +let + cfg = config.services.omnisearch; + omnisearch-pkg = import ./pkg.nix { inherit pkgs lib; }; +in +{ + options.services.omnisearch = { + enable = mkEnableOption "omnisearch metasearch engine"; + port = mkOption { + type = types.int; + default = 8087; + }; + host = mkOption { + type = types.str; + default = "0.0.0.0"; + }; + openFirewall = mkOption { + type = types.bool; + default = false; + }; + settings = mkOption { + type = types.attrs; + default = { }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.omnisearch = { + enable = true; + script = "${omnisearch-pkg}/bin/omnisearch"; + serviceConfig = { + Type = "simple"; + User = "omnisearch"; + Group = "omnisearch"; + WorkingDirectory = "/etc/omnisearch"; + Restart = "always"; + RestartSec = 5; + PrivateTmp = true; + NoNewPrivileges = true; + }; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + }; + + users.groups.omnisearch = { }; + users.users.omnisearch = { + isSystemUser = true; + group = "omnisearch"; + shell = null; + }; + + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ + cfg.port + 5000 + ]; + + environment.systemPackages = [ omnisearch-pkg ]; + + environment.etc = { + "omnisearch/config.ini".text = generators.toINI { } ( + recursiveUpdate { + server.host = "0.0.0.0"; + server.port = cfg.port; + cache.dir = "/tmp/omnisearch_cache"; + } cfg.settings + ); + "omnisearch/templates".source = ./templates; + "omnisearch/static".source = ./static; + }; + }; +} diff --git a/services/omnisearch/pkg.nix b/services/omnisearch/pkg.nix new file mode 100644 index 0000000..9c20bd7 --- /dev/null +++ b/services/omnisearch/pkg.nix @@ -0,0 +1,40 @@ +{ lib, pkgs, ... }: +let + libbeaker = pkgs.stdenv.mkDerivation { + pname = "libbeaker"; + version = "0.0.0"; + src = fetchGit { + url = "https://git.bwaaa.monster/beaker"; + rev = "abc598baf15d6f8a4de395a27ba34b1e769558e1"; + }; + buildInputs = with pkgs; [ pkg-config ]; + buildPhase = "make "; + installPhase = '' + mkdir -p "$out" + make INSTALL_PREFIX="$out/" LDCONFIG="ldconfig -C /tmp/ld.so.cache" info install + ''; + }; + + omnisearch-pkg = pkgs.stdenv.mkDerivation { + pname = "omnisearch"; + version = "0.0.0"; + src = fetchGit { + url = "https://git.bwaaa.monster/omnisearch"; + rev = "ddf39b56505a3a83bf888e245068160b4b5f24bd"; + }; + buildInputs = with pkgs; [ + curl + libxml2 + libbeaker + pkg-config + ]; + buildPhase = '' + make CFLAGS="-Wall -Wextra -O2 -Isrc -I${pkgs.libxml2.dev}/include/libxml2" all + ''; + installPhase = '' + mkdir -p $out/bin $out/etc/omnisearch/{templates,static} + cp -rf bin/* "$out/bin" + ''; + }; +in +omnisearch-pkg diff --git a/services/omnisearch/static/calculation.svg b/services/omnisearch/static/calculation.svg new file mode 100644 index 0000000..ba6bab9 --- /dev/null +++ b/services/omnisearch/static/calculation.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100%" height="100%" fill="#3498db"/><path fill="#ffffff" d="M38.925 56.7v-4.6h22.15v4.6zm0-8.83V43.3h22.15v4.57z"/></svg>
\ No newline at end of file diff --git a/services/omnisearch/static/dictionary.jpg b/services/omnisearch/static/dictionary.jpg Binary files differnew file mode 100644 index 0000000..bb47fc5 --- /dev/null +++ b/services/omnisearch/static/dictionary.jpg diff --git a/services/omnisearch/static/favicon.ico b/services/omnisearch/static/favicon.ico Binary files differnew file mode 100644 index 0000000..6433358 --- /dev/null +++ b/services/omnisearch/static/favicon.ico diff --git a/services/omnisearch/static/main.css b/services/omnisearch/static/main.css new file mode 100644 index 0000000..fa15871 --- /dev/null +++ b/services/omnisearch/static/main.css @@ -0,0 +1,676 @@ +:root { + --bg-main: #000000; + --bg-card: #101414; + --border: #222222; + --text-primary: #ffffff; + --text-secondary: #a0a0a0; + --text-muted: #d1d1d1; + --accent: #009090; + --accent-glow: #2dd4bf; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + background-color: var(--bg-main); + color: var(--text-primary); + margin: 0; + padding: 0; + -webkit-tap-highlight-color: transparent; + font-family: monospace; +} + +img[src=""] { + display: none; +} + +.view-home { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + padding: 20px; + background: var(--bg-main); +} + +.view-home .container { + width: 100%; + max-width: 580px; + margin: 0 auto; + text-align: center; + display: flex; + flex-direction: column; +} + +.view-home .hero-logo { + font-size: 4.5rem; + margin-bottom: 30px; + letter-spacing: -3px; + font-weight: 800; +} + +.view-home .search-input-wrapper { + width: 100%; + margin-bottom: 24px; +} + +.view-home .search-box { + font-size: 1.1rem; + padding: 16px 28px; +} + +.view-home .buttons { + display: flex; + gap: 12px; + justify-content: center; +} + +.view-home button { + padding: 10px 24px; + font-weight: 600; + font-size: 0.9rem; + cursor: pointer; + border: 1px solid transparent; + touch-action: manipulation; +} + +.view-home .btn-primary { + background: var(--accent); + color: var(--bg-main); +} + +.view-home .btn-primary:hover { + filter: brightness(1.1); + transform: translateY(-1px); +} + +.view-home .btn-secondary { + background: var(--bg-card); + color: var(--text-primary); + border-color: var(--border); +} + +.view-home .btn-secondary:hover { + background: var(--border); + border-color: var(--text-secondary); +} + +header { + display: flex; + align-items: center; + gap: 20px; + padding: 15px 60px; + border-bottom: 1px solid var(--border); + background: var(--bg-main); +} + +.search-form { + flex-grow: 1; + max-width: 600px; +} + +h1 { + font-size: 1.5rem; + margin: 0; + letter-spacing: -1px; + white-space: nowrap; +} + +h1 span { + color: var(--accent); +} + +.search-box { + width: 100%; + padding: 12px 24px; + border: 1px solid var(--border); + background: var(--bg-card); + color: var(--text-primary); + outline: none; +} + +.search-box:focus { + border-color: var(--accent); + box-shadow: 0 0 1px 0 var(--accent-glow); +} + +.nav-tabs { + padding: 0 60px; + border-bottom: 1px solid var(--border); + background: var(--bg-main); +} + +.nav-container { + display: flex; + gap: 30px; + max-width: 1200px; +} + +.nav-tabs a { + padding: 14px 0; + color: var(--text-secondary); + text-decoration: none; + font-size: 0.9rem; + font-weight: 500; + border-bottom: 2px solid transparent; + transition: color 0.2s; + touch-action: manipulation; +} + +.nav-tabs a:hover { + color: var(--text-primary); +} + +.nav-tabs a.active { + color: var(--accent); + border-bottom-color: var(--accent); +} + +.image-results-container { + padding: 30px 60px; +} + +.image-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 24px; + max-width: 1600px; + margin: 0 auto; +} + +.image-card { + background: var(--bg-card); + overflow: hidden; + border: 1px solid var(--border); + transition: transform 0.2s ease, border-color 0.2s; +} + +.image-card:hover { + transform: translateY(-4px); + border-color: var(--accent); +} + +.image-wrapper { + position: relative; + aspect-ratio: 4/3; + background: #000; + overflow: hidden; +} + +.image-wrapper img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + transition: opacity 0.3s; +} + +.image-overlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.4); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity 0.2s; + pointer-events: none; +} + +.image-card:hover .image-overlay { + opacity: 1; + pointer-events: auto; +} + +.overlay-buttons { + display: flex; + flex-direction: column; + gap: 10px; + width: 70%; +} + +.overlay-btn { + padding: 8px 16px; + font-size: 0.8rem; + font-weight: 700; + text-decoration: none; + text-align: center; + transition: filter 0.2s; + touch-action: manipulation; +} + +.overlay-btn:hover { + filter: brightness(1.1); +} + +.overlay-btn.primary { + background: var(--accent); + color: var(--bg-main); +} + +.overlay-btn.secondary { + background: rgba(255, 255, 255, 0.1); + color: white; + backdrop-filter: blur(4px); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.image-info { + padding: 12px; +} + +.image-caption { + display: block; + font-size: 0.85rem; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 4px; +} + +.image-source { + display: block; + font-size: 0.75rem; + color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.content-layout { + display: grid; + grid-template-columns: 80px minmax(0, 1000px) 450px; + gap: 20px; + padding: 30px 60px; +} + +.results-container { + grid-column: 2; +} + +.engine-warning-list { + display: flex; + flex-direction: column; + gap: 12px; + margin-bottom: 24px; +} + +.engine-warning { + background: var(--bg-card); + border: 1px solid var(--border); + padding: 14px 16px; +} + +.engine-warning-title { + display: block; + font-size: 0.95rem; + font-weight: 700; + margin-bottom: 4px; +} + +.engine-warning-copy { + color: var(--text-muted); + line-height: 1.5; + margin: 0; +} + +.result { + margin-bottom: 32px; +} + +.result>a { + color: var(--accent); + text-decoration: none; + font-size: 1rem; + display: inline-block; + margin-bottom: 4px; +} + +.url { + color: var(--text-secondary); + font-size: 0.75rem; + display: block; + margin-bottom: 4px; +} + +.desc { + color: var(--text-muted); + line-height: 1.6; + margin: 0; +} + +.cached { + color: var(--text-secondary); + font-size: 0.85rem; + display: inline-block; + text-decoration: underline; +} + +.infobox { + grid-column: 3; + background: var(--bg-card); + border: 1px solid var(--border); + align-self: start; + margin-bottom: 10px; +} + +.infobox-header { + padding: 10px; + border-bottom: 1px solid var(--border); +} + +.infobox-title { + font-size: 1rem; + font-weight: 600; + margin: 0; +} + +.infobox-content { + display: flex; + flex-direction: column; + gap: 12px; +} + +.read-more { + color: var(--accent); + font-weight: bold; + text-decoration: none; + font-size: 0.9rem; + align-self: flex-start; +} + +.read-more:hover { + text-decoration: underline; +} + +.infobox-main { + display: flex; + gap: 15px; + padding: 10px; +} + +.infobox-image { + width: 120px; + height: 120px; + min-width: 120px; + border: 1px solid var(--border); + object-fit: cover; +} + +.infobox-summary { + display: block; + font-size: 0.8rem; + line-height: 1.5; + color: var(--text-muted); + margin: 0; +} + +.pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 16px; + margin-top: 40px; + padding-bottom: 40px; +} + +.pagination-btn { + background: var(--bg-card); + color: var(--text-secondary); + border: 1px solid var(--border); + padding: 4px 8px; + text-decoration: none; + font-size: 0.9rem; + font-weight: 600; + transition: all 0.2s; + touch-action: manipulation; +} + +.pagination-btn:hover { + background: var(--border); + border-color: var(--text-secondary); +} + + +.pagination-current { + background: var(--bg-card); + color: var(--text-primary); + border: 1px solid var(--border); + padding: 4px 12px; + text-decoration: none; + font-size: 1.2rem; + font-weight: 600; + transition: all 0.2s; + touch-action: manipulation; +} + +.pagination-current:hover { + background: var(--border); + border-color: var(--text-secondary); +} + + +@media (max-width:1200px) { + .content-layout { + grid-template-columns: 1fr; + padding: 20px 30px; + gap: 10px; + } + + .results-container, + .infobox-sidebar { + grid-column: 1; + max-width: 100%; + } + + .infobox-sidebar { + order: -1; + } + + .nav-tabs, + .image-results-container { + padding: 0 30px; + } + + header { + padding: 15px 30px; + } +} + +@media (max-width:768px) { + header { + flex-direction: column; + gap: 12px; + padding: 12px 16px; + text-align: center; + } + + h1 { + font-size: 1.3rem; + } + + .search-form { + width: 100%; + max-width: 100%; + } + + .search-form .search-box { + width: 100%; + } + + .nav-tabs { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + padding: 0 16px; + } + + .nav-container { + gap: 24px; + min-width: max-content; + } + + .nav-tabs a { + padding: 12px 0; + font-size: 0.95rem; + } + + .content-layout { + padding: 16px; + gap: 10px; + } + + .result { + margin-bottom: 24px; + } + + .engine-warning { + padding: 12px 14px; + } + + .result>a { + font-size: 1.1rem; + word-break: break-word; + } + + .url { + font-size: 0.8rem; + word-break: break-all; + } + + .desc { + font-size: 0.9rem; + } + + .cached { + font-size: 0.8rem; + } + + .infobox { + margin-bottom: 16px; + } + + .infobox-header { + padding: 16px; + } + + .infobox-title { + font-size: 1.2rem; + } + + .infobox-main { + flex-direction: column; + padding: 16px; + gap: 12px; + } + + .infobox-image { + width: 100%; + height: auto; + min-width: unset; + max-width: 200px; + } + + .image-results-container { + padding: 16px; + } + + .pagination { + flex-wrap: wrap; + gap: 8px; + padding: 0 8px; + } + + .pagination-btn { + padding: 10px 14px; + font-size: 0.85rem; + } + + .view-home { + display: flex; + justify-content: center; + align-items: center; + transform: translateY(-5vh); + padding: 20px 16px; + min-height: 100vh; + } + + .view-home .container { + padding: 0; + width: 100%; + max-width: 580px; + } + + .view-home .hero-logo { + font-size: 3rem; + margin-bottom: 24px; + } + + .view-home .search-input-wrapper { + margin-bottom: 16px; + } + + .view-home .search-box { + width: 100%; + font-size: 1rem; + padding: 14px 20px; + } + + .view-home .buttons { + gap: 10px; + } + + .view-home button { + padding: 12px 20px; + } +} + +@media (max-width:600px) { + header { + padding: 12px 12px; + } + + .search-box { + font-size: 0.95rem; + } + + .view-home .search-box { + width: 100%; + } + + .view-home { + padding: 20px 16px; + } + + .image-grid { + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 10px; + } + + .image-card {} + + .image-info { + padding: 8px 10px; + } + + .image-caption { + font-size: 0.8rem; + } + + .image-source { + font-size: 0.7rem; + } + + .overlay-buttons { + width: 80%; + } + + .overlay-btn { + padding: 6px 12px; + font-size: 0.75rem; + } +} diff --git a/services/omnisearch/templates/home.html b/services/omnisearch/templates/home.html new file mode 100644 index 0000000..0c2b528 --- /dev/null +++ b/services/omnisearch/templates/home.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0"> + <title>OmniSearch</title> + <link rel="stylesheet" href="static/main.css"> + <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> + <link rel="search" type="application/opensearchdescription+xml" title="OmniSearch" href="/opensearch.xml"> +</head> + +<body> + <div class="view-home"> + <div class="container"> + <h1 class="hero-logo"> + who<span>farted?</span> + </h1> + <form action="/search" class="home-search-form"> + <div class="search-input-wrapper"> + <input name="q" type="text" class="search-box" placeholder="Search the web..." autofocus autocomplete="off"> + </div> + <div class="buttons"> + <button type="submit" class="btn-primary"> + Search + </button> + </div> + </form> + </div> + </div> +</body> + +</html> diff --git a/services/omnisearch/templates/images.html b/services/omnisearch/templates/images.html new file mode 100644 index 0000000..3aaf1aa --- /dev/null +++ b/services/omnisearch/templates/images.html @@ -0,0 +1,104 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0"> + <title>OmniSearch Images - {{query}}</title> + <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> + <link rel="stylesheet" href="static/main.css"> +</head> + +<body class="images-view"> + <header> + <h1> + Omni<span>Search</span> + </h1> + <form action="/images" method="GET" class="search-form"> + <input name="q" autocomplete="off"="text" class="search-box" placeholder="Search for images..." value="{{query}}"> + </form> + </header> + <nav class="nav-tabs"> + <div class="nav-container"> + <a href="/search?q={{query}}"> + All + </a> + <a href="/images?q={{query}}" class="active"> + Images + </a> + </div> + </nav> + <main class="image-results-container"> + <div class="image-grid"> + {{for img in images}} + <div class="image-card"> + <div class="image-wrapper"> + <img src="{{img[0]}}" alt="{{img[1]}}" loading="lazy"> + <div class="image-overlay"> + <div class="overlay-buttons"> + <a href="{{img[3]}}" target="_blank" class="overlay-btn primary"> + View Image + </a> + <a href="{{img[2]}}" target="_blank" class="overlay-btn secondary"> + Visit Site + </a> + </div> + </div> + </div> + <div class="image-info"> + <span class="image-caption"> + {{img[1]}} + </span> + <span class="image-source"> + {{img[2]}} + </span> + </div> + </div> + {{endfor}} + </div> + <nav class="pagination"> + <a class="pagination-btn prev" href="/images?q={{query}}&p={{prev_page}}"> + ← + </a> + + {{if two_prev_page != 0}} + <a class="pagination-btn prev" href="/images?q={{query}}&p={{two_prev_page}}"> + {{two_prev_page}} + </a> + {{endif}} + + {{if prev_page != 0}} + <a class="pagination-btn prev" href="/images?q={{query}}&p={{prev_page}}"> + {{prev_page}} + </a> + {{endif}} + + <a class="pagination-current" href="/images?q={{query}}&p={{page}}"> + {{page}} + </a> + <a class="pagination-btn next" href="/images?q={{query}}&p={{next_page}}"> + {{next_page}} + </a> + <a class="pagination-btn next" href="/images?q={{query}}&p={{two_next_page}}"> + {{two_next_page}} + </a> + + {{if prev_page == 0}} + <a class="pagination-btn prev" href="/images?q={{query}}&p=4"> + 4 + </a> + {{endif}} + + {{if two_prev_page == 0}} + <a class="pagination-btn prev" href="/images?q={{query}}&p=5"> + 5 + </a> + {{endif}} + <a class="pagination-btn next" href="/images?q={{query}}&p={{next_page}}"> + → + </a> + </nav> + </main> +</body> + +</html> diff --git a/services/omnisearch/templates/opensearch.xml b/services/omnisearch/templates/opensearch.xml new file mode 100644 index 0000000..8544b09 --- /dev/null +++ b/services/omnisearch/templates/opensearch.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription + xmlns="http://a9.com/-/spec/opensearch/1.1/" + xmlns:moz="http://www.mozilla.org/2006/browser/search/"> + <ShortName>OmniSearch</ShortName> + <Description>Lightweight metasearch engine</Description> + <Url type="text/html" method="get" template="{{domain}}/search?q={searchTerms}"/> + <InputEncoding>UTF-8</InputEncoding> + <OutputEncoding>UTF-8</OutputEncoding> + <moz:SearchForm>{{domain}}/</moz:SearchForm> +</OpenSearchDescription> diff --git a/services/omnisearch/templates/results.html b/services/omnisearch/templates/results.html new file mode 100644 index 0000000..846fdc6 --- /dev/null +++ b/services/omnisearch/templates/results.html @@ -0,0 +1,134 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0"> + <title>OmniSearch - {{query}}</title> + <link rel="stylesheet" href="static/main.css"> + <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> + <link rel="search" type="application/opensearchdescription+xml" title="OmniSearch" href="/opensearch.xml"> +</head> + +<body class="results-view"> + <header> + <form action="/search" method="GET" class="search-form"> + <input name="q" type="text" class="search-box" autocomplete="off" placeholder="Search the web..." value="{{query}}"> + </form> + </header> + <nav class="nav-tabs"> + <div class="nav-container"> + <a href="/search?q={{query}}" class="active"> + All + </a> + <a href="/images?q={{query}}"> + Images + </a> + </div> + </nav> + <div class="content-layout"> + <aside class="sidebar-spacer"> + </aside> + <main class="results-container"> + {{if exists engine_warnings}} + <section class="engine-warning-list"> + {{for warning in engine_warnings}} + <article class="engine-warning"> + <strong class="engine-warning-title"> + {{warning[0]}} + </strong> + <p class="engine-warning-copy"> + {{warning[1]}} + </p> + </article> + {{endfor}} + </section> + {{endif}} + + {{for result in results}} + <div class="result"> + <span class="url"> + {{result[1]}} + </span> + <a href="{{result[0]}}"> + {{result[2]}} + </a> + <p class="desc"> + {{result[3]}} + </p> + <span> + <a class="cached" href="https://web.archive.org/web/{{result[0]|safe}}">View Cached</a> + </span> + </div> + {{endfor}} + + <nav class="pagination"> + <a class="pagination-btn prev" href="/search?q={{query}}&p={{prev_page}}"> + ← + </a> + + {{if two_prev_page != 0}} + <a class="pagination-btn prev" href="/search?q={{query}}&p={{two_prev_page}}"> + {{two_prev_page}} + </a> + {{endif}} + + {{if prev_page != 0}} + <a class="pagination-btn prev" href="/search?q={{query}}&p={{prev_page}}"> + {{prev_page}} + </a> + {{endif}} + + <a class="pagination-current" href="/search?q={{query}}&p={{page}}"> + {{page}} + </a> + <a class="pagination-btn next" href="/search?q={{query}}&p={{next_page}}"> + {{next_page}} + </a> + <a class="pagination-btn next" href="/search?q={{query}}&p={{two_next_page}}"> + {{two_next_page}} + </a> + + {{if prev_page == 0}} + <a class="pagination-btn prev" href="/search?q={{query}}&p=4"> + 4 + </a> + {{endif}} + + {{if two_prev_page == 0}} + <a class="pagination-btn prev" href="/search?q={{query}}&p=5"> + 5 + </a> + {{endif}} + <a class="pagination-btn next" href="/search?q={{query}}&p={{next_page}}"> + → + </a> + </nav> + </main> + <aside class="infobox-sidebar"> + {{if exists infoboxes}} + {{for info in infoboxes}} + <section class="infobox"> + <div class="infobox-header"> + <h2 class="infobox-title"> + {{info[0]}} + </h2> + </div> + <div class="infobox-main"> + <img src="{{info[1]}}" alt="{{info[0]}}" class="infobox-image"> + <div class="infobox-content"> + <p class="infobox-summary"> + {{info[2]|safe}} + </p> + <a class="read-more" href="{{info[3]}}"> + Read More + </a> + </div> + </div> + </section> + {{endfor}} + {{endif}} + </aside> + </div> +</body> +</html> diff --git a/settings.nix b/settings.nix index da2ae5d..57c23c0 100644 --- a/settings.nix +++ b/settings.nix @@ -24,6 +24,7 @@ in lib.recursiveUpdate privateSettings rec { lazylibrarian = 5299; paperless = 28981; caldav = 9314; + search = 8087; }; exposeTransmissionRPC = false; }; |
