add settings page
This commit is contained in:
parent
cd2827b9fc
commit
6da140f34b
124
Cargo.lock
generated
124
Cargo.lock
generated
@ -205,6 +205,30 @@ dependencies = [
|
|||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-extra"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"axum-core",
|
||||||
|
"bytes",
|
||||||
|
"cookie",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.0.0",
|
||||||
|
"http-body 1.0.0",
|
||||||
|
"http-body-util",
|
||||||
|
"mime",
|
||||||
|
"pin-project-lite",
|
||||||
|
"serde",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -354,6 +378,17 @@ dependencies = [
|
|||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
"time",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@ -421,6 +456,15 @@ dependencies = [
|
|||||||
"syn 2.0.52",
|
"syn 2.0.52",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.3.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.17"
|
version = "0.99.17"
|
||||||
@ -743,6 +787,15 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "html-escape"
|
||||||
|
version = "0.2.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476"
|
||||||
|
dependencies = [
|
||||||
|
"utf8-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "html5ever"
|
name = "html5ever"
|
||||||
version = "0.26.0"
|
version = "0.26.0"
|
||||||
@ -1083,6 +1136,7 @@ dependencies = [
|
|||||||
"ammonia",
|
"ammonia",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
"axum",
|
"axum",
|
||||||
|
"axum-extra",
|
||||||
"base64 0.22.0",
|
"base64 0.22.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -1090,6 +1144,7 @@ dependencies = [
|
|||||||
"eyre",
|
"eyre",
|
||||||
"fend-core",
|
"fend-core",
|
||||||
"futures",
|
"futures",
|
||||||
|
"html-escape",
|
||||||
"maud",
|
"maud",
|
||||||
"numbat",
|
"numbat",
|
||||||
"rand",
|
"rand",
|
||||||
@ -1101,6 +1156,8 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"toml",
|
"toml",
|
||||||
|
"tower",
|
||||||
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"url",
|
"url",
|
||||||
@ -1160,6 +1217,12 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-format"
|
name = "num-format"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
@ -1425,6 +1488,12 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@ -2095,6 +2164,37 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"itoa",
|
||||||
|
"num-conv",
|
||||||
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
|
||||||
|
dependencies = [
|
||||||
|
"num-conv",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@ -2219,6 +2319,23 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.2",
|
||||||
|
"bytes",
|
||||||
|
"http 1.0.0",
|
||||||
|
"http-body 1.0.0",
|
||||||
|
"http-body-util",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2239,6 +2356,7 @@ version = "0.1.40"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"log",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tracing-attributes",
|
"tracing-attributes",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
@ -2373,6 +2491,12 @@ version = "0.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8-width"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
12
Cargo.toml
12
Cargo.toml
@ -9,13 +9,8 @@ build = "src/build.rs"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
ammonia = "3.3.0"
|
ammonia = "3.3.0"
|
||||||
async-stream = "0.3.5"
|
async-stream = "0.3.5"
|
||||||
axum = { version = "0.7.4", default-features = false, features = [
|
axum = { version = "0.7.4", default-features = false, features = ["tokio", "http1", "http2", "query", "json", "form"] }
|
||||||
"tokio",
|
axum-extra = { version = "0.9.3", features = ["cookie"] }
|
||||||
"http1",
|
|
||||||
"http2",
|
|
||||||
"query",
|
|
||||||
"json",
|
|
||||||
] }
|
|
||||||
base64 = "0.22.0"
|
base64 = "0.22.0"
|
||||||
bytes = "1.5.0"
|
bytes = "1.5.0"
|
||||||
chrono = "0.4.35"
|
chrono = "0.4.35"
|
||||||
@ -23,6 +18,7 @@ chrono-tz = { version = "0.8.6", features = ["case-insensitive"] }
|
|||||||
eyre = "0.6.12"
|
eyre = "0.6.12"
|
||||||
fend-core = "1.4.5"
|
fend-core = "1.4.5"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
|
html-escape = "0.2.13"
|
||||||
maud = "0.26.0"
|
maud = "0.26.0"
|
||||||
numbat = "1.11.0"
|
numbat = "1.11.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
@ -40,6 +36,8 @@ serde_json = { version = "1.0.114", features = ["preserve_order"] }
|
|||||||
tokio = { version = "1.36.0", features = ["rt", "macros"] }
|
tokio = { version = "1.36.0", features = ["rt", "macros"] }
|
||||||
tokio-stream = "0.1.15"
|
tokio-stream = "0.1.15"
|
||||||
toml = { version = "0.8.12", default-features = false, features = ["parse"] }
|
toml = { version = "0.8.12", default-features = false, features = ["parse"] }
|
||||||
|
tower = "0.4.13"
|
||||||
|
tower-http = "0.5.2"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = "0.3.18"
|
tracing-subscriber = "0.3.18"
|
||||||
url = "2.5.0"
|
url = "2.5.0"
|
||||||
|
@ -1,18 +1,25 @@
|
|||||||
use std::{collections::HashMap, fs, net::SocketAddr, path::Path, sync::LazyLock};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fs,
|
||||||
|
net::SocketAddr,
|
||||||
|
path::Path,
|
||||||
|
sync::{Arc, LazyLock},
|
||||||
|
};
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::engines::Engine;
|
use crate::engines::Engine;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub bind: SocketAddr,
|
pub bind: SocketAddr,
|
||||||
/// Whether the JSON API should be accessible.
|
/// Whether the JSON API should be accessible.
|
||||||
pub api: bool,
|
pub api: bool,
|
||||||
pub ui: UiConfig,
|
pub ui: UiConfig,
|
||||||
pub image_search: ImageSearchConfig,
|
pub image_search: ImageSearchConfig,
|
||||||
pub engines: EnginesConfig,
|
// wrapped in an arc to make Config cheaper to clone
|
||||||
|
pub engines: Arc<EnginesConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
@ -31,23 +38,31 @@ impl Config {
|
|||||||
self.ui.overlay(partial.ui.unwrap_or_default());
|
self.ui.overlay(partial.ui.unwrap_or_default());
|
||||||
self.image_search
|
self.image_search
|
||||||
.overlay(partial.image_search.unwrap_or_default());
|
.overlay(partial.image_search.unwrap_or_default());
|
||||||
self.engines.overlay(partial.engines.unwrap_or_default());
|
if let Some(partial_engines) = partial.engines {
|
||||||
|
let mut engines = self.engines.as_ref().clone();
|
||||||
|
engines.overlay(partial_engines);
|
||||||
|
self.engines = Arc::new(engines);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UiConfig {
|
pub struct UiConfig {
|
||||||
pub show_engine_list_separator: bool,
|
pub show_engine_list_separator: bool,
|
||||||
pub show_version_info: bool,
|
pub show_version_info: bool,
|
||||||
|
/// Settings are always accessible anyways, this just controls whether the
|
||||||
|
/// link to them in the index page is visible.
|
||||||
|
pub show_settings_link: bool,
|
||||||
pub site_name: String,
|
pub site_name: String,
|
||||||
pub stylesheet_url: Option<String>,
|
pub stylesheet_url: String,
|
||||||
pub stylesheet_str: Option<String>,
|
pub stylesheet_str: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Default)]
|
#[derive(Deserialize, Debug, Default)]
|
||||||
pub struct PartialUiConfig {
|
pub struct PartialUiConfig {
|
||||||
pub show_engine_list_separator: Option<bool>,
|
pub show_engine_list_separator: Option<bool>,
|
||||||
pub show_version_info: Option<bool>,
|
pub show_version_info: Option<bool>,
|
||||||
|
pub show_settings_link: Option<bool>,
|
||||||
pub site_name: Option<String>,
|
pub site_name: Option<String>,
|
||||||
pub stylesheet_url: Option<String>,
|
pub stylesheet_url: Option<String>,
|
||||||
pub stylesheet_str: Option<String>,
|
pub stylesheet_str: Option<String>,
|
||||||
@ -59,13 +74,20 @@ impl UiConfig {
|
|||||||
.show_engine_list_separator
|
.show_engine_list_separator
|
||||||
.unwrap_or(self.show_engine_list_separator);
|
.unwrap_or(self.show_engine_list_separator);
|
||||||
self.show_version_info = partial.show_version_info.unwrap_or(self.show_version_info);
|
self.show_version_info = partial.show_version_info.unwrap_or(self.show_version_info);
|
||||||
|
self.show_settings_link = partial
|
||||||
|
.show_settings_link
|
||||||
|
.unwrap_or(self.show_settings_link);
|
||||||
self.site_name = partial.site_name.unwrap_or(self.site_name.clone());
|
self.site_name = partial.site_name.unwrap_or(self.site_name.clone());
|
||||||
self.stylesheet_url = partial.stylesheet_url.or(self.stylesheet_url.clone());
|
self.stylesheet_url = partial
|
||||||
self.stylesheet_str = partial.stylesheet_str.or(self.stylesheet_str.clone());
|
.stylesheet_url
|
||||||
|
.unwrap_or(self.stylesheet_url.clone());
|
||||||
|
self.stylesheet_str = partial
|
||||||
|
.stylesheet_str
|
||||||
|
.unwrap_or(self.stylesheet_str.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ImageSearchConfig {
|
pub struct ImageSearchConfig {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub show_engines: bool,
|
pub show_engines: bool,
|
||||||
@ -87,7 +109,7 @@ impl ImageSearchConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ImageProxyConfig {
|
pub struct ImageProxyConfig {
|
||||||
/// Whether we should proxy remote images through our server. This is mostly
|
/// Whether we should proxy remote images through our server. This is mostly
|
||||||
/// a privacy feature.
|
/// a privacy feature.
|
||||||
@ -109,7 +131,7 @@ impl ImageProxyConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EnginesConfig {
|
pub struct EnginesConfig {
|
||||||
pub map: HashMap<Engine, EngineConfig>,
|
pub map: HashMap<Engine, EngineConfig>,
|
||||||
}
|
}
|
||||||
@ -152,7 +174,7 @@ impl EnginesConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EngineConfig {
|
pub struct EngineConfig {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
/// The priority of this engine relative to the other engines.
|
/// The priority of this engine relative to the other engines.
|
||||||
@ -206,8 +228,9 @@ impl Default for Config {
|
|||||||
show_engine_list_separator: false,
|
show_engine_list_separator: false,
|
||||||
show_version_info: false,
|
show_version_info: false,
|
||||||
site_name: "metasearch".to_string(),
|
site_name: "metasearch".to_string(),
|
||||||
stylesheet_url: None,
|
show_settings_link: true,
|
||||||
stylesheet_str: None,
|
stylesheet_url: "".to_string(),
|
||||||
|
stylesheet_str: "".to_string(),
|
||||||
},
|
},
|
||||||
image_search: ImageSearchConfig {
|
image_search: ImageSearchConfig {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@ -217,7 +240,7 @@ impl Default for Config {
|
|||||||
max_download_size: 10_000_000,
|
max_download_size: 10_000_000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
engines: EnginesConfig::default(),
|
engines: Arc::new(EnginesConfig::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const searchInputEl = document.getElementById("search-input");
|
const searchInputEl = document.getElementById("search-input");
|
||||||
|
|
||||||
|
if (searchInputEl) {
|
||||||
// add an element with search suggestions after the search input
|
// add an element with search suggestions after the search input
|
||||||
const suggestionsEl = document.createElement("div");
|
const suggestionsEl = document.createElement("div");
|
||||||
suggestionsEl.id = "search-input-suggestions";
|
suggestionsEl.id = "search-input-suggestions";
|
||||||
@ -28,9 +29,9 @@ async function updateSuggestions() {
|
|||||||
const thisQueryId = nextQueryId;
|
const thisQueryId = nextQueryId;
|
||||||
nextQueryId++;
|
nextQueryId++;
|
||||||
|
|
||||||
const res = await fetch(`/autocomplete?q=${encodeURIComponent(value)}`).then(
|
const res = await fetch(
|
||||||
(res) => res.json()
|
`/autocomplete?q=${encodeURIComponent(value)}`
|
||||||
);
|
).then((res) => res.json());
|
||||||
const options = res[1];
|
const options = res[1];
|
||||||
|
|
||||||
// this makes sure we don't load suggestions out of order
|
// this makes sure we don't load suggestions out of order
|
||||||
@ -163,3 +164,40 @@ searchInputEl.addEventListener("click", updateSuggestions);
|
|||||||
searchInputEl.addEventListener("blur", (e) => {
|
searchInputEl.addEventListener("blur", (e) => {
|
||||||
suggestionsEl.style.visibility = "hidden";
|
suggestionsEl.style.visibility = "hidden";
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const customCssEl = document.getElementById("custom-css");
|
||||||
|
if (customCssEl) {
|
||||||
|
// tab to indent
|
||||||
|
// https://stackoverflow.com/a/6637396
|
||||||
|
customCssEl.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key == "Tab") {
|
||||||
|
e.preventDefault();
|
||||||
|
var start = customCssEl.selectionStart;
|
||||||
|
var end = customCssEl.selectionEnd;
|
||||||
|
customCssEl.value =
|
||||||
|
customCssEl.value.substring(0, start) +
|
||||||
|
"\t" +
|
||||||
|
customCssEl.value.substring(end);
|
||||||
|
customCssEl.selectionStart = customCssEl.selectionEnd = start + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ctrl+enter anywhere on the page to submit
|
||||||
|
const saveEl = document.getElementById("save-settings-button");
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key == "Enter" && (e.ctrlKey || e.metaKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log("click");
|
||||||
|
saveEl.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// save whether the details are open or not
|
||||||
|
const customCssDetailsEl = document.getElementById("custom-css-details");
|
||||||
|
const customCssDetailsOpen = localStorage.getItem("custom-css-details-open");
|
||||||
|
if (customCssDetailsOpen === "true") customCssDetailsEl.open = true;
|
||||||
|
customCssDetailsEl.addEventListener("toggle", () => {
|
||||||
|
localStorage.setItem("custom-css-details-open", customCssDetailsEl.open);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -40,12 +40,19 @@ body {
|
|||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-link {
|
||||||
|
position: absolute;
|
||||||
|
top: 1em;
|
||||||
|
right: 1em;
|
||||||
|
}
|
||||||
.version-info {
|
.version-info {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 16px;
|
bottom: 1em;
|
||||||
right: 16px;
|
right: 1em;
|
||||||
}
|
}
|
||||||
.results-container {
|
|
||||||
|
.main-container {
|
||||||
/* enough space for the infobox */
|
/* enough space for the infobox */
|
||||||
max-width: 73.5rem;
|
max-width: 73.5rem;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@ -62,17 +69,19 @@ main {
|
|||||||
/* image search uses 100% width */
|
/* image search uses 100% width */
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
.results-container.search-images {
|
.main-container.search-images {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 74rem) {
|
@media screen and (max-width: 74rem) {
|
||||||
/* small screens */
|
/* small screens */
|
||||||
.results-container {
|
.main-container {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 40rem;
|
max-width: 40rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input {
|
input,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
background-color: var(--bg-2);
|
background-color: var(--bg-2);
|
||||||
color: var(--fg-1);
|
color: var(--fg-1);
|
||||||
@ -107,7 +116,7 @@ blockquote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* index page */
|
/* index page */
|
||||||
.main-container {
|
.main-container.index-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
@ -122,6 +131,22 @@ h1 {
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* settings page */
|
||||||
|
.settings-page .back-to-index-button {
|
||||||
|
bottom: 0.5em;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.settings-form select {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#save-settings-button {
|
||||||
|
margin-top: 1em;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#custom-css {
|
||||||
|
tab-size: 2;
|
||||||
|
}
|
||||||
|
|
||||||
/* header */
|
/* header */
|
||||||
.search-form {
|
.search-form {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
@ -1,18 +1,13 @@
|
|||||||
use std::{collections::HashMap, sync::Arc};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use axum::{
|
use axum::{extract::Query, http::StatusCode, response::IntoResponse, Extension, Json};
|
||||||
extract::{Query, State},
|
|
||||||
http::StatusCode,
|
|
||||||
response::IntoResponse,
|
|
||||||
Json,
|
|
||||||
};
|
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{config::Config, engines};
|
use crate::{config::Config, engines};
|
||||||
|
|
||||||
pub async fn route(
|
pub async fn route(
|
||||||
Query(params): Query<HashMap<String, String>>,
|
Query(params): Query<HashMap<String, String>>,
|
||||||
State(config): State<Arc<Config>>,
|
Extension(config): Extension<Config>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let query = params
|
let query = params
|
||||||
.get("q")
|
.get("q")
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use std::{collections::HashMap, sync::Arc};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Query, State},
|
extract::Query,
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
|
Extension,
|
||||||
};
|
};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
@ -11,7 +12,7 @@ use crate::{config::Config, engines};
|
|||||||
|
|
||||||
pub async fn route(
|
pub async fn route(
|
||||||
Query(params): Query<HashMap<String, String>>,
|
Query(params): Query<HashMap<String, String>>,
|
||||||
State(config): State<Arc<Config>>,
|
Extension(config): Extension<Config>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let image_search_config = &config.image_search;
|
let image_search_config = &config.image_search;
|
||||||
let proxy_config = &image_search_config.proxy;
|
let proxy_config = &image_search_config.proxy;
|
||||||
|
@ -1,46 +1,32 @@
|
|||||||
use std::sync::Arc;
|
use axum::{http::header, response::IntoResponse, Extension};
|
||||||
|
|
||||||
use axum::{extract::State, http::header, response::IntoResponse};
|
|
||||||
use maud::{html, PreEscaped, DOCTYPE};
|
use maud::{html, PreEscaped, DOCTYPE};
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::{config::Config, web::head_html};
|
||||||
|
|
||||||
const BASE_COMMIT_URL: &str = "https://github.com/mat-1/metasearch2/commit/";
|
const BASE_COMMIT_URL: &str = "https://github.com/mat-1/metasearch2/commit/";
|
||||||
const VERSION: &str = std::env!("CARGO_PKG_VERSION");
|
const VERSION: &str = std::env!("CARGO_PKG_VERSION");
|
||||||
const COMMIT_HASH: &str = std::env!("GIT_HASH");
|
const COMMIT_HASH: &str = std::env!("GIT_HASH");
|
||||||
const COMMIT_HASH_SHORT: &str = std::env!("GIT_HASH_SHORT");
|
const COMMIT_HASH_SHORT: &str = std::env!("GIT_HASH_SHORT");
|
||||||
|
|
||||||
pub async fn index(State(config): State<Arc<Config>>) -> impl IntoResponse {
|
pub async fn get(Extension(config): Extension<Config>) -> impl IntoResponse {
|
||||||
let mut html = String::new();
|
let html = html! {
|
||||||
html.push_str(
|
|
||||||
&html! {
|
|
||||||
(PreEscaped("<!-- source code: https://github.com/mat-1/metasearch2 -->\n"))
|
(PreEscaped("<!-- source code: https://github.com/mat-1/metasearch2 -->\n"))
|
||||||
(DOCTYPE)
|
(DOCTYPE)
|
||||||
html lang="en" {
|
html lang="en" {
|
||||||
head {
|
{(head_html(None, &config))}
|
||||||
meta charset="UTF-8";
|
|
||||||
meta name="viewport" content="width=device-width, initial-scale=1.0";
|
|
||||||
title { {(config.ui.site_name)} }
|
|
||||||
link rel="stylesheet" href="/style.css";
|
|
||||||
@if let Some(stylesheet_url) = &config.ui.stylesheet_url {
|
|
||||||
link rel="stylesheet" href=(stylesheet_url);
|
|
||||||
}
|
|
||||||
@if let Some(stylesheet_str) = &config.ui.stylesheet_str {
|
|
||||||
link rel="stylesheet" href=(stylesheet_str);
|
|
||||||
}
|
|
||||||
script src="/script.js" defer {}
|
|
||||||
link rel="search" type="application/opensearchdescription+xml" title="metasearch" href="/opensearch.xml";
|
|
||||||
}
|
|
||||||
body {
|
body {
|
||||||
div."main-container" {
|
@if config.ui.show_settings_link {
|
||||||
|
a.settings-link href="/settings" { "Settings" }
|
||||||
|
}
|
||||||
|
div.main-container.index-page {
|
||||||
h1 { {(config.ui.site_name)} }
|
h1 { {(config.ui.site_name)} }
|
||||||
form."search-form" action="/search" method="get" {
|
form.search-form action="/search" method="get" {
|
||||||
input type="text" name="q" placeholder="Search" id="search-input" autofocus onfocus="this.select()" autocomplete="off";
|
input type="text" name="q" placeholder="Search" id="search-input" autofocus onfocus="this.select()" autocomplete="off";
|
||||||
input type="submit" value="Search";
|
input type="submit" value="Search";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@if config.ui.show_version_info {
|
@if config.ui.show_version_info {
|
||||||
span."version-info" {
|
span.version-info {
|
||||||
@if COMMIT_HASH == "unknown" || COMMIT_HASH_SHORT == "unknown" {
|
@if COMMIT_HASH == "unknown" || COMMIT_HASH_SHORT == "unknown" {
|
||||||
"Version "
|
"Version "
|
||||||
(VERSION)
|
(VERSION)
|
||||||
@ -57,8 +43,7 @@ pub async fn index(State(config): State<Arc<Config>>) -> impl IntoResponse {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.into_string(),
|
.into_string();
|
||||||
);
|
|
||||||
|
|
||||||
([(header::CONTENT_TYPE, "text/html; charset=utf-8")], html)
|
([(header::CONTENT_TYPE, "text/html; charset=utf-8")], html)
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
pub mod autocomplete;
|
mod autocomplete;
|
||||||
mod image_proxy;
|
mod image_proxy;
|
||||||
pub mod index;
|
mod index;
|
||||||
pub mod opensearch;
|
mod opensearch;
|
||||||
pub mod search;
|
mod search;
|
||||||
|
mod settings;
|
||||||
|
|
||||||
use std::{convert::Infallible, net::SocketAddr, sync::Arc};
|
use std::{convert::Infallible, net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
http::header,
|
extract::{Request, State},
|
||||||
routing::{get, MethodRouter},
|
http::{header, StatusCode},
|
||||||
|
middleware::{self, Next},
|
||||||
|
response::Response,
|
||||||
|
routing::{get, post, MethodRouter},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
use axum_extra::extract::CookieJar;
|
||||||
|
use maud::{html, Markup, PreEscaped};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
@ -18,6 +24,8 @@ use crate::config::Config;
|
|||||||
pub async fn run(config: Config) {
|
pub async fn run(config: Config) {
|
||||||
let bind_addr = config.bind;
|
let bind_addr = config.bind;
|
||||||
|
|
||||||
|
let config = Arc::new(config);
|
||||||
|
|
||||||
fn static_route<S>(
|
fn static_route<S>(
|
||||||
content: &'static str,
|
content: &'static str,
|
||||||
content_type: &'static str,
|
content_type: &'static str,
|
||||||
@ -30,7 +38,10 @@ pub async fn run(config: Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(index::index))
|
.route("/", get(index::get))
|
||||||
|
.route("/search", get(search::get))
|
||||||
|
.route("/settings", get(settings::get))
|
||||||
|
.route("/settings", post(settings::post))
|
||||||
.route(
|
.route(
|
||||||
"/style.css",
|
"/style.css",
|
||||||
static_route(include_str!("assets/style.css"), "text/css; charset=utf-8"),
|
static_route(include_str!("assets/style.css"), "text/css; charset=utf-8"),
|
||||||
@ -57,10 +68,13 @@ pub async fn run(config: Config) {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.route("/opensearch.xml", get(opensearch::route))
|
.route("/opensearch.xml", get(opensearch::route))
|
||||||
.route("/search", get(search::route))
|
|
||||||
.route("/autocomplete", get(autocomplete::route))
|
.route("/autocomplete", get(autocomplete::route))
|
||||||
.route("/image-proxy", get(image_proxy::route))
|
.route("/image-proxy", get(image_proxy::route))
|
||||||
.with_state(Arc::new(config));
|
.layer(middleware::from_fn_with_state(
|
||||||
|
config.clone(),
|
||||||
|
config_middleware,
|
||||||
|
))
|
||||||
|
.with_state(config);
|
||||||
|
|
||||||
info!("Listening on http://{bind_addr}");
|
info!("Listening on http://{bind_addr}");
|
||||||
|
|
||||||
@ -72,3 +86,52 @@ pub async fn run(config: Config) {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn config_middleware(
|
||||||
|
State(config): State<Arc<Config>>,
|
||||||
|
cookies: CookieJar,
|
||||||
|
mut req: Request,
|
||||||
|
next: Next,
|
||||||
|
) -> Result<Response, StatusCode> {
|
||||||
|
let mut config = config.clone().as_ref().clone();
|
||||||
|
|
||||||
|
fn set_from_cookie(config: &mut String, cookies: &CookieJar, name: &str) {
|
||||||
|
if let Some(cookie) = cookies.get(name) {
|
||||||
|
let value = cookie.value();
|
||||||
|
*config = value.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_from_cookie(&mut config.ui.stylesheet_url, &cookies, "stylesheet-url");
|
||||||
|
set_from_cookie(&mut config.ui.stylesheet_str, &cookies, "stylesheet-str");
|
||||||
|
|
||||||
|
// modify the state
|
||||||
|
req.extensions_mut().insert(config);
|
||||||
|
|
||||||
|
Ok(next.run(req).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn head_html(title: Option<&str>, config: &Config) -> Markup {
|
||||||
|
html! {
|
||||||
|
head {
|
||||||
|
meta charset="UTF-8";
|
||||||
|
meta name="viewport" content="width=device-width, initial-scale=1.0";
|
||||||
|
title {
|
||||||
|
@if let Some(title) = title {
|
||||||
|
{ (title) }
|
||||||
|
{ " - " }
|
||||||
|
}
|
||||||
|
{(config.ui.site_name)}
|
||||||
|
}
|
||||||
|
link rel="stylesheet" href="/style.css";
|
||||||
|
@if !config.ui.stylesheet_url.is_empty() {
|
||||||
|
link rel="stylesheet" href=(config.ui.stylesheet_url);
|
||||||
|
}
|
||||||
|
@if !config.ui.stylesheet_str.is_empty() {
|
||||||
|
style { (PreEscaped(html_escape::encode_style(&config.ui.stylesheet_str))) }
|
||||||
|
}
|
||||||
|
script src="/script.js" defer {}
|
||||||
|
link rel="search" type="application/opensearchdescription+xml" title="metasearch" href="/opensearch.xml";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
mod all;
|
mod all;
|
||||||
mod images;
|
mod images;
|
||||||
|
|
||||||
use std::{collections::HashMap, net::SocketAddr, str::FromStr, sync::Arc};
|
use std::{collections::HashMap, net::SocketAddr, str::FromStr};
|
||||||
|
|
||||||
use async_stream::stream;
|
use async_stream::stream;
|
||||||
use axum::{
|
use axum::{
|
||||||
body::Body,
|
body::Body,
|
||||||
extract::{ConnectInfo, Query, State},
|
extract::{ConnectInfo, Query},
|
||||||
http::{header, HeaderMap, StatusCode},
|
http::{header, HeaderMap, StatusCode},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
Json,
|
Extension, Json,
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use maud::{html, PreEscaped, DOCTYPE};
|
use maud::{html, PreEscaped, DOCTYPE};
|
||||||
@ -20,29 +20,10 @@ use crate::{
|
|||||||
self, Engine, EngineProgressUpdate, ProgressUpdateData, ResponseForTab, SearchQuery,
|
self, Engine, EngineProgressUpdate, ProgressUpdateData, ResponseForTab, SearchQuery,
|
||||||
SearchTab,
|
SearchTab,
|
||||||
},
|
},
|
||||||
|
web::head_html,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn render_beginning_of_html(search: &SearchQuery) -> String {
|
fn render_beginning_of_html(search: &SearchQuery) -> String {
|
||||||
let head_html = html! {
|
|
||||||
head {
|
|
||||||
meta charset="UTF-8";
|
|
||||||
meta name="viewport" content="width=device-width, initial-scale=1.0";
|
|
||||||
title {
|
|
||||||
(search.query)
|
|
||||||
" - "
|
|
||||||
(search.config.ui.site_name)
|
|
||||||
}
|
|
||||||
link rel="stylesheet" href="/style.css";
|
|
||||||
@if let Some(stylesheet_url) = &search.config.ui.stylesheet_url {
|
|
||||||
link rel="stylesheet" href=(stylesheet_url);
|
|
||||||
}
|
|
||||||
@if let Some(stylesheet_str) = &search.config.ui.stylesheet_str {
|
|
||||||
link rel="stylesheet" href=(stylesheet_str);
|
|
||||||
}
|
|
||||||
script src="/script.js" defer {}
|
|
||||||
link rel="search" type="application/opensearchdescription+xml" title="metasearch" href="/opensearch.xml";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let form_html = html! {
|
let form_html = html! {
|
||||||
form."search-form" action="/search" method="get" {
|
form."search-form" action="/search" method="get" {
|
||||||
input #"search-input" type="text" name="q" placeholder="Search" value=(search.query) autofocus onfocus="this.select()" autocomplete="off";
|
input #"search-input" type="text" name="q" placeholder="Search" value=(search.query) autofocus onfocus="this.select()" autocomplete="off";
|
||||||
@ -62,9 +43,9 @@ fn render_beginning_of_html(search: &SearchQuery) -> String {
|
|||||||
html! {
|
html! {
|
||||||
(DOCTYPE)
|
(DOCTYPE)
|
||||||
html lang="en";
|
html lang="en";
|
||||||
(head_html)
|
{(head_html(Some(&search.query), &search.config))}
|
||||||
body;
|
body;
|
||||||
div.results-container.{"search-" (search.tab.to_string())};
|
div.main-container.{"search-" (search.tab.to_string())};
|
||||||
main;
|
main;
|
||||||
(form_html)
|
(form_html)
|
||||||
div.progress-updates;
|
div.progress-updates;
|
||||||
@ -131,9 +112,9 @@ pub fn render_engine_list(engines: &[engines::Engine], config: &Config) -> PreEs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn route(
|
pub async fn get(
|
||||||
Query(params): Query<HashMap<String, String>>,
|
Query(params): Query<HashMap<String, String>>,
|
||||||
State(config): State<Arc<Config>>,
|
Extension(config): Extension<Config>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||||
) -> axum::response::Response {
|
) -> axum::response::Response {
|
||||||
@ -182,7 +163,7 @@ pub async fn route(
|
|||||||
|| addr.ip().to_string(),
|
|| addr.ip().to_string(),
|
||||||
|ip| ip.to_str().unwrap_or_default().to_string(),
|
|ip| ip.to_str().unwrap_or_default().to_string(),
|
||||||
),
|
),
|
||||||
config: config.clone(),
|
config: config.clone().into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let trying_to_use_api =
|
let trying_to_use_api =
|
||||||
|
79
src/web/settings.rs
Normal file
79
src/web/settings.rs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
use axum::{ http::{header, StatusCode}, response::IntoResponse, Extension, Form};
|
||||||
|
use axum_extra::extract::{cookie::Cookie, CookieJar};
|
||||||
|
use maud::{html, Markup, PreEscaped, DOCTYPE};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::{config::Config, web::head_html};
|
||||||
|
|
||||||
|
pub async fn get(
|
||||||
|
Extension(config): Extension<Config>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let theme_option = |value: &str, name: &str| -> Markup {
|
||||||
|
let selected = config.ui.stylesheet_url == value;
|
||||||
|
html! {
|
||||||
|
option value=(value) selected[selected] {
|
||||||
|
{ (name) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = html! {
|
||||||
|
(PreEscaped("<!-- source code: https://github.com/mat-1/metasearch2 -->\n"))
|
||||||
|
(DOCTYPE)
|
||||||
|
html lang="en" {
|
||||||
|
{(head_html(Some("settings"), &config))}
|
||||||
|
body {
|
||||||
|
div.main-container.settings-page {
|
||||||
|
main {
|
||||||
|
a.back-to-index-button href="/" { "Back" }
|
||||||
|
h1 { "Settings" }
|
||||||
|
form.settings-form method="post" {
|
||||||
|
label for="theme" { "Theme" }
|
||||||
|
select name="stylesheet-url" selected=(config.ui.stylesheet_url) {
|
||||||
|
{ (theme_option("", "Ayu Dark")) }
|
||||||
|
{ (theme_option("/themes/catppuccin-mocha.css", "Catppuccin Mocha")) }
|
||||||
|
}
|
||||||
|
|
||||||
|
br;
|
||||||
|
|
||||||
|
// custom css textarea
|
||||||
|
details #custom-css-details {
|
||||||
|
summary { "Custom CSS" }
|
||||||
|
textarea name="stylesheet-str" id="custom-css" rows="10" cols="50" {
|
||||||
|
{ (config.ui.stylesheet_str) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input #save-settings-button type="submit" value="Save";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into_string();
|
||||||
|
|
||||||
|
( [(header::CONTENT_TYPE, "text/html; charset=utf-8")], html)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct Settings {
|
||||||
|
stylesheet_url: String,
|
||||||
|
stylesheet_str: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn post(
|
||||||
|
mut jar: CookieJar,
|
||||||
|
Form(settings): Form<Settings>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
jar = jar.add(Cookie::new("stylesheet-url", settings.stylesheet_url));
|
||||||
|
jar = jar.add(Cookie::new("stylesheet-str", settings.stylesheet_str));
|
||||||
|
|
||||||
|
(
|
||||||
|
StatusCode::FOUND,
|
||||||
|
[(header::LOCATION, "/settings")],
|
||||||
|
jar
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user