Compare commits

..

No commits in common. "7f7367a34d62ac7aefc33ef39fa01f264df82ba1" and "2ca9990e54ff610af5441dc8e86ead128027fec7" have entirely different histories.

41 changed files with 1120 additions and 271 deletions

14
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,14 @@
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "monthly"

40
Cargo.lock generated
View file

@ -865,9 +865,9 @@ dependencies = [
[[package]] [[package]]
name = "error-stack" name = "error-stack"
version = "0.5.0" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe413319145d1063f080f27556fd30b1d70b01e2ba10c2a6e40d4be982ffc5d1" checksum = "27a72baa257b5e0e2de241967bc5ee8f855d6072351042688621081d66b2a76b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"rustc_version 0.4.0", "rustc_version 0.4.0",
@ -1188,16 +1188,16 @@ dependencies = [
[[package]] [[package]]
name = "html5ever" name = "html5ever"
version = "0.27.0" version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
dependencies = [ dependencies = [
"log", "log",
"mac", "mac",
"markup5ever 0.12.1", "markup5ever 0.11.0",
"proc-macro2 1.0.86", "proc-macro2 1.0.86",
"quote 1.0.36", "quote 1.0.36",
"syn 2.0.74", "syn 1.0.109",
] ]
[[package]] [[package]]
@ -1598,13 +1598,13 @@ dependencies = [
[[package]] [[package]]
name = "markup5ever" name = "markup5ever"
version = "0.12.1" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
dependencies = [ dependencies = [
"log", "log",
"phf 0.11.2", "phf 0.10.1",
"phf_codegen 0.11.2", "phf_codegen 0.10.0",
"string_cache 0.8.7", "string_cache 0.8.7",
"string_cache_codegen 0.5.2", "string_cache_codegen 0.5.2",
"tendril", "tendril",
@ -1992,16 +1992,6 @@ dependencies = [
"phf_shared 0.10.0", "phf_shared 0.10.0",
] ]
[[package]]
name = "phf_codegen"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
dependencies = [
"phf_generator 0.11.2",
"phf_shared 0.11.2",
]
[[package]] [[package]]
name = "phf_generator" name = "phf_generator"
version = "0.7.24" version = "0.7.24"
@ -2677,14 +2667,14 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "scraper" name = "scraper"
version = "0.20.0" version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b90460b31bfe1fc07be8262e42c665ad97118d4585869de9345a84d501a9eaf0" checksum = "585480e3719b311b78a573db1c9d9c4c1f8010c2dee4cc59c2efe58ea4dbc3e1"
dependencies = [ dependencies = [
"ahash", "ahash",
"cssparser", "cssparser",
"ego-tree", "ego-tree",
"html5ever 0.27.0", "html5ever 0.26.0",
"once_cell", "once_cell",
"selectors", "selectors",
"tendril", "tendril",
@ -2788,9 +2778,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.125" version = "1.0.124"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d"
dependencies = [ dependencies = [
"itoa 1.0.11", "itoa 1.0.11",
"memchr", "memchr",

View file

@ -20,11 +20,11 @@ tokio = { version = "1.32.0", features = [
"io-util", "io-util",
], default-features = false } ], default-features = false }
serde = { version = "1.0.196", default-features = false, features = ["derive"] } serde = { version = "1.0.196", default-features = false, features = ["derive"] }
serde_json = { version = "1.0.125", default-features = false } serde_json = { version = "1.0.116", default-features = false }
maud = { version = "0.26.0", default-features = false, features = [ maud = { version = "0.26.0", default-features = false, features = [
"actix-web", "actix-web",
] } ] }
scraper = { version = "0.20.0", default-features = false } scraper = { version = "0.18.1", default-features = false }
actix-web = { version = "4.4.0", features = [ actix-web = { version = "4.4.0", features = [
"cookies", "cookies",
"macros", "macros",
@ -35,7 +35,7 @@ actix-cors = { version = "0.7.0", default-features = false }
fake-useragent = { version = "0.1.3", default-features = false } fake-useragent = { version = "0.1.3", default-features = false }
env_logger = { version = "0.11.1", default-features = false } env_logger = { version = "0.11.1", default-features = false }
log = { version = "0.4.21", default-features = false } log = { version = "0.4.21", default-features = false }
error-stack = { version = "0.5.0", default-features = false, features = [ error-stack = { version = "0.4.0", default-features = false, features = [
"std", "std",
] } ] }
async-trait = { version = "0.1.80", default-features = false } async-trait = { version = "0.1.80", default-features = false }

94
public/static/cookies.js Normal file
View file

@ -0,0 +1,94 @@
/**
* This functions gets the saved cookies if it is present on the user's machine If it
* is available then it is parsed and converted to an object which is then used to
* retrieve the preferences that the user had selected previously and is then loaded
* and used for displaying the user provided settings by setting them as the selected
* options in the settings page.
*
* @function
* @param {string} cookie - It takes the client settings cookie as a string.
* @returns {void}
*/
function setClientSettingsOnPage(cookie) {
let cookie_value = cookie
.split(';')
.map((item) => item.split('='))
.reduce((acc, [_, v]) => (acc = JSON.parse(v)) && acc, {})
// Loop through all select tags and add their values to the cookie dictionary
document.querySelectorAll('select').forEach((select_tag) => {
switch (select_tag.name) {
case 'themes':
select_tag.value = cookie_value['theme']
break
case 'colorschemes':
select_tag.value = cookie_value['colorscheme']
break
case 'animations':
select_tag.value = cookie_value['animation']
break
case 'safe_search_levels':
select_tag.value = cookie_value['safe_search_level']
break
}
})
let engines = document.querySelectorAll('.engine')
let engines_cookie = cookie_value['engines']
if (engines_cookie.length === engines.length) {
document.querySelector('.select_all').checked = true
engines.forEach((engine_checkbox) => {
engine_checkbox.checked = true
})
} else {
engines.forEach((engines_checkbox) => {
engines_checkbox.checked = false
})
engines_cookie.forEach((engine_name) => {
engines.forEach((engine_checkbox) => {
if (
engine_checkbox.parentNode.parentNode.innerText.trim() ===
engine_name.trim()
) {
engine_checkbox.checked = true
}
})
})
}
}
/**
* This function is executed when any page on the website finishes loading and
* this function retrieves the cookies if it is present on the user's machine.
* If it is available then the saved cookies is display in the cookies tab
* otherwise an appropriate message is displayed if it is not available.
*
* @function
* @listens DOMContentLoaded
* @returns {void}
*/
document.addEventListener(
'DOMContentLoaded',
() => {
try {
// Decode the cookie value
let cookie = decodeURIComponent(document.cookie)
// Set the value of the input field to the decoded cookie value if it is not empty
// Otherwise, display a message indicating that no cookies have been saved on the user's system
if (cookie.length) {
document.querySelector('.cookies input').value = cookie
// This function displays the user provided settings on the settings page.
setClientSettingsOnPage(cookie)
} else {
document.querySelector('.cookies input').value =
'No cookies have been saved on your system'
}
} catch (error) {
// If there is an error decoding the cookie, log the error to the console
// and display an error message in the input field
console.error('Error decoding cookie:', error)
document.querySelector('.cookies input').value = 'Error decoding cookie'
}
},
false,
)

View file

@ -0,0 +1,7 @@
/**
* This function provides the ability for the button to toggle the dropdown error-box
* in the search page.
*/
function toggleErrorBox() {
document.querySelector('.dropdown_error_box').classList.toggle('show')
}

34
public/static/index.js Normal file
View file

@ -0,0 +1,34 @@
/**
* Selects the input element for the search box
* @type {HTMLInputElement}
*/
const searchBox = document.querySelector('input')
/**
* Redirects the user to the search results page with the query parameter
*/
function searchWeb() {
const query = searchBox.value.trim()
try {
let safeSearchLevel = document.querySelector('.search_options select').value
if (query) {
window.location.href = `search?q=${encodeURIComponent(
query,
)}&safesearch=${encodeURIComponent(safeSearchLevel)}`
}
} catch (error) {
if (query) {
window.location.href = `search?q=${encodeURIComponent(query)}`
}
}
}
/**
* Listens for the 'Enter' key press event on the search box and calls the searchWeb function
* @param {KeyboardEvent} e - The keyboard event object
*/
searchBox.addEventListener('keyup', (e) => {
if (e.key === 'Enter') {
searchWeb()
}
})

View file

@ -0,0 +1,39 @@
/**
* Navigates to the next page by incrementing the current page number in the URL query string.
* @returns {void}
*/
function navigate_forward() {
let url = new URL(window.location);
let searchParams = url.searchParams;
let q = searchParams.get('q');
let page = parseInt(searchParams.get('page'));
if (isNaN(page)) {
page = 1;
} else {
page++;
}
window.location.href = `${url.origin}${url.pathname}?q=${encodeURIComponent(q)}&page=${page}`;
}
/**
* Navigates to the previous page by decrementing the current page number in the URL query string.
* @returns {void}
*/
function navigate_backward() {
let url = new URL(window.location);
let searchParams = url.searchParams;
let q = searchParams.get('q');
let page = parseInt(searchParams.get('page'));
if (isNaN(page)) {
page = 0;
} else if (page > 0) {
page--;
}
window.location.href = `${url.origin}${url.pathname}?q=${encodeURIComponent(q)}&page=${page}`;
}

View file

@ -0,0 +1,18 @@
document.addEventListener(
'DOMContentLoaded',
() => {
let url = new URL(window.location)
let searchParams = url.searchParams
let safeSearchLevel = searchParams.get('safesearch')
if (
safeSearchLevel >= 0 &&
safeSearchLevel <= 2 &&
safeSearchLevel !== null
) {
document.querySelector('.search_options select').value = safeSearchLevel
}
},
false,
)

155
public/static/settings.js Normal file
View file

@ -0,0 +1,155 @@
/**
* This function handles the toggling of selections of all upstream search engines
* options in the settings page under the tab engines.
*/
function toggleAllSelection() {
document
.querySelectorAll('.engine')
.forEach(
(engine_checkbox) =>
(engine_checkbox.checked =
document.querySelector('.select_all').checked),
)
}
/**
* This function adds the functionality to sidebar buttons to only show settings
* related to that tab.
* @param {HTMLElement} current_tab - The current tab that was clicked.
*/
function setActiveTab(current_tab) {
// Remove the active class from all tabs and buttons
document
.querySelectorAll('.tab')
.forEach((tab) => tab.classList.remove('active'))
document
.querySelectorAll('.btn')
.forEach((tab) => tab.classList.remove('active'))
// Add the active class to the current tab and its corresponding settings
current_tab.classList.add('active')
document
.querySelector(`.${current_tab.innerText.toLowerCase().replace(' ', '_')}`)
.classList.add('active')
}
/**
* This function adds the functionality to save all the user selected preferences
* to be saved in a cookie on the users machine.
*/
function setClientSettings() {
// Create an object to store the user's preferences
let cookie_dictionary = new Object()
// Loop through all select tags and add their values to the cookie dictionary
document.querySelectorAll('select').forEach((select_tag) => {
switch (select_tag.name) {
case 'themes':
cookie_dictionary['theme'] = select_tag.value
break
case 'colorschemes':
cookie_dictionary['colorscheme'] = select_tag.value
break
case 'animations':
cookie_dictionary['animation'] = select_tag.value || null
break
case 'safe_search_levels':
cookie_dictionary['safe_search_level'] = Number(select_tag.value)
break
}
})
// Loop through all engine checkboxes and add their values to the cookie dictionary
let engines = []
document.querySelectorAll('.engine').forEach((engine_checkbox) => {
if (engine_checkbox.checked) {
engines.push(engine_checkbox.parentNode.parentNode.innerText.trim())
}
})
cookie_dictionary['engines'] = engines
// Set the expiration date for the cookie to 1 year from the current date
let expiration_date = new Date()
expiration_date.setFullYear(expiration_date.getFullYear() + 1)
// Save the cookie to the user's machine
document.cookie = `appCookie=${JSON.stringify(
cookie_dictionary,
)}; expires=${expiration_date.toUTCString()}`
// Display a success message to the user
document.querySelector('.message').innerText =
'✅ The settings have been saved sucessfully!!'
// Clear the success message after 10 seconds
setTimeout(() => {
document.querySelector('.message').innerText = ''
}, 10000)
}
/**
* This functions gets the saved cookies if it is present on the user's machine If it
* is available then it is parsed and converted to an object which is then used to
* retrieve the preferences that the user had selected previously and is then loaded in the
* website otherwise the function does nothing and the default server side settings are loaded.
*/
function getClientSettings() {
// Get the appCookie from the user's machine
let cookie = decodeURIComponent(document.cookie)
// If the cookie is not empty, parse it and use it to set the user's preferences
if (cookie.length) {
let cookie_value = cookie
.split(';')
.map((item) => item.split('='))
.reduce((acc, [_, v]) => (acc = JSON.parse(v)) && acc, {})
let links = Array.from(document.querySelectorAll('link'))
// A check to determine whether the animation link exists under the head tag or not.
// If it does not exists then create and add a new animation link under the head tag
// and update the other link tags href according to the settings provided by the user
// via the UI. On the other hand if it does exist then just update all the link tags
// href according to the settings provided by the user via the UI.
if (!links.some((item) => item.href.includes('static/animations'))) {
if (cookie_value['animation']) {
let animation_link = document.createElement('link')
animation_link.href = `static/animations/${cookie_value['animation']}.css`
animation_link.rel = 'stylesheet'
animation_link.type = 'text/css'
document.querySelector('head').appendChild(animation_link)
}
// Loop through all link tags and update their href values to match the user's preferences
links.forEach((item) => {
if (item.href.includes('static/themes')) {
item.href = `static/themes/${cookie_value['theme']}.css`
} else if (item.href.includes('static/colorschemes')) {
item.href = `static/colorschemes/${cookie_value['colorscheme']}.css`
}
})
} else {
// Loop through all link tags and update their href values to match the user's preferences
links.forEach((item) => {
if (item.href.includes('static/themes')) {
item.href = `static/themes/${cookie_value['theme']}.css`
} else if (item.href.includes('static/colorschemes')) {
item.href = `static/colorschemes/${cookie_value['colorscheme']}.css`
} else if (
item.href.includes('static/animations') &&
cookie_value['animation']
) {
item.href = `static/colorschemes/${cookie_value['animation']}.css`
}
})
if (!cookie_value['animation']) {
document
.querySelector('head')
.removeChild(
links.filter((item) => item.href.includes('static/animations')),
)
}
}
}
}

View file

@ -35,19 +35,19 @@ button {
/* styles for the index page */ /* styles for the index page */
.search_container { .search-container {
display: flex; display: flex;
flex-direction: row; flex-direction: column;
gap: 5rem; gap: 5rem;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.search_container svg { .search-container svg {
color: var(--logo-color); color: var(--logo-color);
} }
.search_container div { .search-container div {
display: flex; display: flex;
} }
@ -102,10 +102,10 @@ button {
} }
.search_bar button img { .search_bar button img {
position: absolute; position:absolute;
left: 50%; left:50%;
top: 50%; top:50%;
transform: translate(-50%, -50%); transform:translate(-50%, -50%);
} }
.search_bar button:active { .search_bar button:active {
@ -593,7 +593,7 @@ footer div {
font-size: 2.5rem; font-size: 2.5rem;
} }
.settings>h1 { .settings > h1 {
margin-bottom: 4rem; margin-bottom: 4rem;
margin-left: 2rem; margin-left: 2rem;
} }
@ -603,7 +603,7 @@ footer div {
margin: 0.3rem 0 1rem; margin: 0.3rem 0 1rem;
} }
.settings>hr { .settings > hr {
margin-left: 2rem; margin-left: 2rem;
} }
@ -796,15 +796,15 @@ footer div {
transition: .2s; transition: .2s;
} }
input:checked+.slider { input:checked + .slider {
background-color: var(--color-three); background-color: var(--color-three);
} }
input:focus+.slider { input:focus + .slider {
box-shadow: 0 0 1px var(--color-three); box-shadow: 0 0 1px var(--color-three);
} }
input:checked+.slider::before { input:checked + .slider::before {
transform: translateX(2.6rem); transform: translateX(2.6rem);
} }
@ -817,7 +817,7 @@ input:checked+.slider::before {
border-radius: 50%; border-radius: 50%;
} }
@media screen and (width <=1136px) { @media screen and (width <= 1136px) {
.hero-text-container { .hero-text-container {
width: unset; width: unset;
} }
@ -827,7 +827,7 @@ input:checked+.slider::before {
} }
} }
@media screen and (width <=706px) { @media screen and (width <= 706px) {
.about-container article .logo-container svg { .about-container article .logo-container svg {
width: clamp(200px, 290px, 815px); width: clamp(200px, 290px, 815px);
} }

View file

@ -4,12 +4,14 @@ use figment::{providers::Serialized, Figment};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Struct holding config Options /// Struct holding config Options
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Config { pub struct Config {
/// It stores the parsed port number option on which the server should launch. /// It stores the parsed port number option on which the server should launch.
pub port: u16, pub port: u16,
/// It stores the parsed ip address option on which the server should launch /// It stores the parsed ip address option on which the server should launch
pub binding_ip: String, pub binding_ip: String,
/// It stores the theming options for the website.
pub style: Style,
/// Memory cache invalidation time /// Memory cache invalidation time
pub cache_expiry_time: u64, pub cache_expiry_time: u64,
/// It stores the option to whether enable or disable logs. /// It stores the option to whether enable or disable logs.
@ -19,7 +21,7 @@ pub struct Config {
/// It toggles whether to use adaptive HTTP windows /// It toggles whether to use adaptive HTTP windows
pub adaptive_window: bool, pub adaptive_window: bool,
/// It stores all the engine names that were enabled by the user. /// It stores all the engine names that were enabled by the user.
pub upstream_search_engines: crate::engines::Engines, pub upstream_search_engines: Vec<String>,
/// It stores the time (secs) which controls the server request timeout. /// It stores the time (secs) which controls the server request timeout.
pub request_timeout: u8, pub request_timeout: u8,
/// Set the keep-alive time for client connections to the HTTP server /// Set the keep-alive time for client connections to the HTTP server
@ -30,6 +32,19 @@ pub struct Config {
pub pool_idle_connection_timeout: u8, pub pool_idle_connection_timeout: u8,
} }
/// A struct holding style config
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct Style {
/// It stores the parsed theme option used to set a theme for the website.
pub theme: String,
/// It stores the parsed colorscheme option used to set a colorscheme for the
/// theme being used.
pub colorscheme: String,
/// It stores the parsed animation option used to set an animation for the
/// theme being used.
pub animation: Option<String>,
}
/// Configuration options for the rate limiter middleware. /// Configuration options for the rate limiter middleware.
pub struct RateLimiter { pub struct RateLimiter {
/// The number of request that are allowed within a provided time limit. /// The number of request that are allowed within a provided time limit.
@ -43,11 +58,24 @@ impl Default for Config {
Self { Self {
port: 8080, port: 8080,
binding_ip: "127.0.0.1".into(), binding_ip: "127.0.0.1".into(),
style: Style {
theme: "simple".into(),
colorscheme: "catppuccin-mocha".into(),
animation: Some("simple-frosted-glow".into()),
},
cache_expiry_time: 600, cache_expiry_time: 600,
logging: true, logging: true,
debug: false, debug: false,
adaptive_window: false, adaptive_window: false,
upstream_search_engines: Default::default(), upstream_search_engines: vec![
"bing".into(),
"brave".into(),
"duckduckgo".into(),
"librex".into(),
"mojeek".into(),
"searx".into(),
"startpage".into(),
],
request_timeout: 2, request_timeout: 2,
tcp_connection_keep_alive: 10, tcp_connection_keep_alive: 10,
pool_idle_connection_timeout: 30, pool_idle_connection_timeout: 30,

View file

@ -24,19 +24,18 @@ pub struct Bing {
parser: SearchResultParser, parser: SearchResultParser,
} }
impl Default for Bing { impl Bing {
/// Creates the Bing parser. /// Creates the Bing parser.
fn default() -> Self { pub fn new() -> Result<Self, EngineError> {
Self { Ok(Self {
parser: SearchResultParser::new( parser: SearchResultParser::new(
".b_results", ".b_results",
".b_algo", ".b_algo",
"h2 a", "h2 a",
".tpcn a.tilk", ".tpcn a.tilk",
".b_caption p", ".b_caption p",
) )?,
.expect("somehow you changed the static stings in the binary i guess"), })
}
} }
} }

View file

@ -20,19 +20,18 @@ pub struct Brave {
parser: SearchResultParser, parser: SearchResultParser,
} }
impl Default for Brave { impl Brave {
/// Creates the Brave parser. /// Creates the Brave parser.
fn default() -> Self { pub fn new() -> Result<Brave, EngineError> {
Self { Ok(Self {
parser: SearchResultParser::new( parser: SearchResultParser::new(
"#results h4", "#results h4",
"#results [data-pos]", "#results [data-pos]",
"a > .url", "a > .url",
"a", "a",
".snippet-description", ".snippet-description",
) )?,
.expect("somehow you changed the static stings in the binary i guess"), })
}
} }
} }

View file

@ -23,19 +23,18 @@ pub struct DuckDuckGo {
parser: SearchResultParser, parser: SearchResultParser,
} }
impl Default for DuckDuckGo { impl DuckDuckGo {
/// Creates the DuckDuckGo parser. /// Creates the DuckDuckGo parser.
fn default() -> Self { pub fn new() -> Result<Self, EngineError> {
Self { Ok(Self {
parser: SearchResultParser::new( parser: SearchResultParser::new(
".no-results", ".no-results",
".results>.result", ".results>.result",
".result__title>.result__a", ".result__title>.result__a",
".result__url", ".result__url",
".result__snippet", ".result__snippet",
) )?,
.expect("somehow you changed the static stings in the binary i guess"), })
}
} }
} }

View file

@ -20,23 +20,22 @@ pub struct LibreX {
parser: SearchResultParser, parser: SearchResultParser,
} }
impl Default for LibreX { impl LibreX {
/// Creates a new instance of LibreX with a default configuration. /// Creates a new instance of LibreX with a default configuration.
/// ///
/// # Returns /// # Returns
/// ///
/// Returns a `Result` containing `LibreX` if successful, otherwise an `EngineError`. /// Returns a `Result` containing `LibreX` if successful, otherwise an `EngineError`.
fn default() -> Self { pub fn new() -> Result<Self, EngineError> {
Self { Ok(Self {
parser: SearchResultParser::new( parser: SearchResultParser::new(
".text-result-container>p", ".text-result-container>p",
".text-result-container", ".text-result-container",
".text-result-wrapper>a>h2", ".text-result-wrapper>a>h2",
".text-result-wrapper>a", ".text-result-wrapper>a",
".text-result-wrapper>span", ".text-result-wrapper>span",
) )?,
.expect("somehow you changed the static stings in the binary i guess"), })
}
} }
} }

View file

@ -3,12 +3,6 @@
//! provide a standard functions to be implemented for all the upstream search engine handling //! provide a standard functions to be implemented for all the upstream search engine handling
//! code. Moreover, it also provides a custom error for the upstream search engine handling code. //! code. Moreover, it also provides a custom error for the upstream search engine handling code.
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::models::engine_models::EngineHandler;
pub mod bing; pub mod bing;
pub mod brave; pub mod brave;
pub mod duckduckgo; pub mod duckduckgo;
@ -17,79 +11,3 @@ pub mod mojeek;
pub mod search_result_parser; pub mod search_result_parser;
pub mod searx; pub mod searx;
pub mod startpage; pub mod startpage;
/// Struct that keeps track of search engines
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
pub struct Engines {
bing: bool,
brave: bool,
duckduckgo: bool,
librex: bool,
mojeek: bool,
searx: bool,
startpage: bool,
}
impl Default for Engines {
fn default() -> Self {
Self {
bing: true,
brave: true,
duckduckgo: true,
librex: true,
mojeek: true,
searx: true,
startpage: true,
}
}
}
impl From<&Engines> for Vec<EngineHandler> {
fn from(value: &Engines) -> Self {
let mut v = vec![];
if value.duckduckgo {
let engine = crate::engines::duckduckgo::DuckDuckGo::default();
v.push(EngineHandler::new("duckduckgo", Arc::new(engine)));
}
if value.searx {
let engine = crate::engines::searx::Searx::default();
v.push(EngineHandler::new("searx", Arc::new(engine)));
}
if value.brave {
let engine = crate::engines::brave::Brave::default();
v.push(EngineHandler::new("brave", Arc::new(engine)));
}
if value.startpage {
let engine = crate::engines::startpage::Startpage::default();
v.push(EngineHandler::new("startpage", Arc::new(engine)));
}
if value.librex {
let engine = crate::engines::librex::LibreX::default();
v.push(EngineHandler::new("librex", Arc::new(engine)));
}
if value.mojeek {
let engine = crate::engines::mojeek::Mojeek::default();
v.push(EngineHandler::new("mojeek", Arc::new(engine)));
}
if value.bing {
let engine = crate::engines::bing::Bing::default();
v.push(EngineHandler::new("bing", Arc::new(engine)));
}
v
}
}
impl Engines {
/// Returns a list of all engines
pub fn list(&self) -> Box<[&'static str]> {
Box::new([
"duckduckgo",
"searx",
"brave",
"startpage",
"librex",
"mojeek",
"bing",
])
}
}

View file

@ -23,19 +23,18 @@ pub struct Mojeek {
parser: SearchResultParser, parser: SearchResultParser,
} }
impl Default for Mojeek { impl Mojeek {
/// Creates the Mojeek parser. /// Creates the Mojeek parser.
fn default() -> Self { pub fn new() -> Result<Self, EngineError> {
Self { Ok(Self {
parser: SearchResultParser::new( parser: SearchResultParser::new(
".result-col", ".result-col",
".results-standard li", ".results-standard li",
"a span.url", "a span.url",
"h2 a.title", "h2 a.title",
"p.s", "p.s",
) )?,
.expect("somehow you changed the static stings in the binary i guess"), })
}
} }
} }

View file

@ -19,19 +19,18 @@ pub struct Searx {
parser: SearchResultParser, parser: SearchResultParser,
} }
impl Default for Searx { impl Searx {
/// creates a Searx parser /// creates a Searx parser
fn default() -> Self { pub fn new() -> Result<Searx, EngineError> {
Self { Ok(Self {
parser: SearchResultParser::new( parser: SearchResultParser::new(
"#urls>.dialog-error>p", "#urls>.dialog-error>p",
".result", ".result",
"h3>a", "h3>a",
"h3>a", "h3>a",
".content", ".content",
) )?,
.expect("somehow you changed the static stings in the binary i guess"), })
}
} }
} }

View file

@ -23,19 +23,18 @@ pub struct Startpage {
parser: SearchResultParser, parser: SearchResultParser,
} }
impl Default for Startpage { impl Startpage {
/// Creates the Startpage parser. /// Creates the Startpage parser.
fn default() -> Self { pub fn new() -> Result<Self, EngineError> {
Self { Ok(Self {
parser: SearchResultParser::new( parser: SearchResultParser::new(
".no-results", ".no-results",
".w-gl__result__main", ".w-gl__result__main",
".w-gl__result-second-line-container>.w-gl__result-title>h3", ".w-gl__result-second-line-container>.w-gl__result-title>h3",
".w-gl__result-url", ".w-gl__result-url",
".w-gl__description", ".w-gl__description",
) )?,
.expect("somehow you changed the static stings in the binary i guess"), })
}
} }
} }

View file

@ -2,7 +2,7 @@
//! and register all the routes for the `crabbysearch` meta search engine website. //! and register all the routes for the `crabbysearch` meta search engine website.
#![forbid(unsafe_code, clippy::panic)] #![forbid(unsafe_code, clippy::panic)]
#![deny(missing_docs, clippy::perf)] #![deny(missing_docs, clippy::missing_docs_in_private_items, clippy::perf)]
#![warn(clippy::cognitive_complexity, rust_2018_idioms)] #![warn(clippy::cognitive_complexity, rust_2018_idioms)]
pub mod cache; pub mod cache;
@ -50,7 +50,7 @@ async fn main() {
config.port, config.port,
); );
let listener = TcpListener::bind((config.binding_ip.clone(), config.port)) let listener = TcpListener::bind((config.binding_ip.as_str(), config.port))
.expect("could not create TcpListener"); .expect("could not create TcpListener");
let public_folder_path: &str = file_path(FileType::Theme).unwrap(); let public_folder_path: &str = file_path(FileType::Theme).unwrap();
@ -86,6 +86,7 @@ async fn main() {
.service(router::index) // index page .service(router::index) // index page
.service(server::routes::search::search) // search page .service(server::routes::search::search) // search page
.service(router::about) // about page .service(router::about) // about page
.service(router::settings) // settings page
.default_service(web::route().to(router::not_found)) // error page .default_service(web::route().to(router::not_found)) // error page
}) })
// Start server on 127.0.0.1 with the user provided port number. for example 127.0.0.1:8080. // Start server on 127.0.0.1 with the user provided port number. for example 127.0.0.1:8080.

View file

@ -113,6 +113,14 @@ pub struct SearchResults {
/// Stores the information on which engines failed with their engine name /// Stores the information on which engines failed with their engine name
/// and the type of error that caused it. /// and the type of error that caused it.
pub engine_errors_info: Vec<EngineErrorInfo>, pub engine_errors_info: Vec<EngineErrorInfo>,
/// Stores the flag option which holds the check value that the following
/// search query was disallowed when the safe search level set to 4 and it
/// was present in the `Blocklist` file.
pub disallowed: bool,
/// Stores the flag option which holds the check value that the following
/// search query was filtered when the safe search level set to 3 and it
/// was present in the `Blocklist` file.
pub filtered: bool,
/// Stores the safe search level `safesearch` provided in the search url. /// Stores the safe search level `safesearch` provided in the search url.
pub safe_search_level: u8, pub safe_search_level: u8,
/// Stores the flag option which holds the check value that whether any search engines were /// Stores the flag option which holds the check value that whether any search engines were
@ -135,12 +143,23 @@ impl SearchResults {
Self { Self {
results, results,
engine_errors_info: engine_errors_info.to_owned(), engine_errors_info: engine_errors_info.to_owned(),
disallowed: Default::default(),
filtered: Default::default(),
safe_search_level: Default::default(), safe_search_level: Default::default(),
no_engines_selected: Default::default(), no_engines_selected: Default::default(),
} }
} }
/// A setter function that sets disallowed to true.
pub fn set_disallowed(&mut self) {
self.disallowed = true;
}
/// A setter function that sets the filtered to true.
pub fn set_filtered(&mut self, filtered: bool) {
self.filtered = filtered;
}
/// A getter function that gets the value of `engine_errors_info`. /// A getter function that gets the value of `engine_errors_info`.
pub fn engine_errors_info(&mut self) -> Vec<EngineErrorInfo> { pub fn engine_errors_info(&mut self) -> Vec<EngineErrorInfo> {
std::mem::take(&mut self.engine_errors_info) std::mem::take(&mut self.engine_errors_info)

View file

@ -2,9 +2,9 @@
//! the upstream search engines with the search query provided by the user. //! the upstream search engines with the search query provided by the user.
use super::aggregation_models::SearchResult; use super::aggregation_models::SearchResult;
use error_stack::{Result, ResultExt}; use error_stack::{Report, Result, ResultExt};
use reqwest::Client; use reqwest::Client;
use std::{fmt, sync::Arc}; use std::fmt;
/// A custom error type used for handle engine associated errors. /// A custom error type used for handle engine associated errors.
#[derive(Debug)] #[derive(Debug)]
@ -150,15 +150,20 @@ pub trait SearchEngine: Sync + Send {
} }
/// A named struct which stores the engine struct with the name of the associated engine. /// A named struct which stores the engine struct with the name of the associated engine.
#[derive(Clone)]
pub struct EngineHandler { pub struct EngineHandler {
/// It stores the engine struct wrapped in a box smart pointer as the engine struct implements /// It stores the engine struct wrapped in a box smart pointer as the engine struct implements
/// the `SearchEngine` trait. /// the `SearchEngine` trait.
engine: Arc<dyn SearchEngine>, engine: Box<dyn SearchEngine>,
/// It stores the name of the engine to which the struct is associated to. /// It stores the name of the engine to which the struct is associated to.
name: &'static str, name: &'static str,
} }
impl Clone for EngineHandler {
fn clone(&self) -> Self {
Self::new(self.name).unwrap()
}
}
impl EngineHandler { impl EngineHandler {
/// Parses an engine name into an engine handler. /// Parses an engine name into an engine handler.
/// ///
@ -169,13 +174,53 @@ impl EngineHandler {
/// # Returns /// # Returns
/// ///
/// It returns an option either containing the value or a none if the engine is unknown /// It returns an option either containing the value or a none if the engine is unknown
pub fn new(name: &'static str, engine: Arc<dyn SearchEngine>) -> Self { pub fn new(engine_name: &str) -> Result<Self, EngineError> {
Self { name, engine } let engine: (&'static str, Box<dyn SearchEngine>) =
match engine_name.to_lowercase().as_str() {
"duckduckgo" => {
let engine = crate::engines::duckduckgo::DuckDuckGo::new()?;
("duckduckgo", Box::new(engine))
}
"searx" => {
let engine = crate::engines::searx::Searx::new()?;
("searx", Box::new(engine))
}
"brave" => {
let engine = crate::engines::brave::Brave::new()?;
("brave", Box::new(engine))
}
"startpage" => {
let engine = crate::engines::startpage::Startpage::new()?;
("startpage", Box::new(engine))
}
"librex" => {
let engine = crate::engines::librex::LibreX::new()?;
("librex", Box::new(engine))
}
"mojeek" => {
let engine = crate::engines::mojeek::Mojeek::new()?;
("mojeek", Box::new(engine))
}
"bing" => {
let engine = crate::engines::bing::Bing::new()?;
("bing", Box::new(engine))
}
_ => {
return Err(Report::from(EngineError::NoSuchEngineFound(
engine_name.to_string(),
)))
}
};
Ok(Self {
engine: engine.1,
name: engine.0,
})
} }
/// This function converts the EngineHandler type into a tuple containing the engine name and /// This function converts the EngineHandler type into a tuple containing the engine name and
/// the associated engine struct. /// the associated engine struct.
pub fn into_name_engine(self) -> (&'static str, Arc<dyn SearchEngine>) { pub fn into_name_engine(self) -> (&'static str, Box<dyn SearchEngine>) {
(self.name, self.engine) (self.name, self.engine)
} }
} }

View file

@ -1,13 +1,17 @@
//! This module provides the models to parse cookies and search parameters from the search //! This module provides the models to parse cookies and search parameters from the search
//! engine website. //! engine website.
use std::borrow::Cow;
use serde::Deserialize; use serde::Deserialize;
use crate::config::Style;
/// A named struct which deserializes all the user provided search parameters and stores them. /// A named struct which deserializes all the user provided search parameters and stores them.
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct SearchParams { pub struct SearchParams {
/// It stores the search parameter option `q` (or query in simple words) /// It stores the search parameter option `q` (or query in simple words)
/// of the search url. /// of the search url.
pub query: Option<String>, pub q: Option<String>,
/// It stores the search parameter `page` (or pageno in simple words) /// It stores the search parameter `page` (or pageno in simple words)
/// of the search url. /// of the search url.
pub page: Option<u32>, pub page: Option<u32>,
@ -15,3 +19,27 @@ pub struct SearchParams {
/// search url. /// search url.
pub safesearch: Option<u8>, pub safesearch: Option<u8>,
} }
/// A named struct which is used to deserialize the cookies fetched from the client side.
#[allow(dead_code)]
#[derive(Deserialize)]
pub struct Cookie<'a> {
/// It stores the theme name used in the website.
pub theme: Cow<'a, str>,
/// It stores the colorscheme name used for the website theme.
pub colorscheme: Cow<'a, str>,
/// It stores the user selected upstream search engines selected from the UI.
pub engines: Cow<'a, Vec<Cow<'a, str>>>,
}
impl<'a> Cookie<'a> {
/// server_models::Cookie contructor function
pub fn build(style: &'a Style, mut engines: Vec<Cow<'a, str>>) -> Self {
engines.sort();
Self {
theme: Cow::Borrowed(&style.theme),
colorscheme: Cow::Borrowed(&style.colorscheme),
engines: Cow::Owned(engines),
}
}
}

View file

@ -147,7 +147,9 @@ pub async fn aggregate(
}; };
} }
let results: Vec<SearchResult> = result_map.iter().map(|(_, value)| value.clone()).collect(); let mut results: Vec<SearchResult> =
result_map.iter().map(|(_, value)| value.clone()).collect();
results.sort_by(|a, b| a.description.len().cmp(&b.description.len()));
Ok(SearchResults::new(results, &engine_errors_info)) Ok(SearchResults::new(results, &engine_errors_info))
} }

View file

@ -2,24 +2,39 @@
//! meta search engine website and provide appropriate response to each route/page //! meta search engine website and provide appropriate response to each route/page
//! when requested. //! when requested.
use crate::handler::{file_path, FileType}; use crate::{
use actix_web::{get, http::header::ContentType, HttpRequest, HttpResponse}; config::Config,
handler::{file_path, FileType},
};
use actix_web::{get, http::header::ContentType, web, HttpRequest, HttpResponse};
use tokio::fs::read_to_string; use tokio::fs::read_to_string;
/// Handles the route of index page or main page of the `crabbysearch` meta search engine website. /// Handles the route of index page or main page of the `crabbysearch` meta search engine website.
#[get("/")] #[get("/")]
pub async fn index() -> Result<HttpResponse, Box<dyn std::error::Error>> { pub async fn index(config: web::Data<Config>) -> Result<HttpResponse, Box<dyn std::error::Error>> {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok().content_type(ContentType::html()).body(
.content_type(ContentType::html()) crate::templates::views::index::index(
.body(crate::templates::views::index::index().0)) &config.style.colorscheme,
&config.style.theme,
&config.style.animation,
)
.0,
))
} }
/// Handles the route of any other accessed route/page which is not provided by the /// Handles the route of any other accessed route/page which is not provided by the
/// website essentially the 404 error page. /// website essentially the 404 error page.
pub async fn not_found() -> Result<HttpResponse, Box<dyn std::error::Error>> { pub async fn not_found(
Ok(HttpResponse::Ok() config: web::Data<Config>,
.content_type(ContentType::html()) ) -> Result<HttpResponse, Box<dyn std::error::Error>> {
.body(crate::templates::views::not_found::not_found().0)) Ok(HttpResponse::Ok().content_type(ContentType::html()).body(
crate::templates::views::not_found::not_found(
&config.style.colorscheme,
&config.style.theme,
&config.style.animation,
)
.0,
))
} }
/// Handles the route of robots.txt page of the `crabbysearch` meta search engine website. /// Handles the route of robots.txt page of the `crabbysearch` meta search engine website.
@ -34,8 +49,29 @@ pub async fn robots_data(_req: HttpRequest) -> Result<HttpResponse, Box<dyn std:
/// Handles the route of about page of the `crabbysearch` meta search engine website. /// Handles the route of about page of the `crabbysearch` meta search engine website.
#[get("/about")] #[get("/about")]
pub async fn about() -> Result<HttpResponse, Box<dyn std::error::Error>> { pub async fn about(config: web::Data<Config>) -> Result<HttpResponse, Box<dyn std::error::Error>> {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok().content_type(ContentType::html()).body(
.content_type(ContentType::html()) crate::templates::views::about::about(
.body(crate::templates::views::about::about().0)) &config.style.colorscheme,
&config.style.theme,
&config.style.animation,
)
.0,
))
}
/// Handles the route of settings page of the `crabbysearch` meta search engine website.
#[get("/settings")]
pub async fn settings(
config: web::Data<Config>,
) -> Result<HttpResponse, Box<dyn std::error::Error>> {
Ok(HttpResponse::Ok().content_type(ContentType::html()).body(
crate::templates::views::settings::settings(
&config.style.colorscheme,
&config.style.theme,
&config.style.animation,
//&config.upstream_search_engines,
)?
.0,
))
} }

View file

@ -3,14 +3,15 @@
use crate::{ use crate::{
cache::Cache, cache::Cache,
config::Config, config::Config,
engines::Engines,
models::{ models::{
aggregation_models::SearchResults, engine_models::EngineHandler, aggregation_models::SearchResults,
server_models::SearchParams, engine_models::EngineHandler,
server_models::{self, SearchParams},
}, },
results::aggregator::aggregate, results::aggregator::aggregate,
}; };
use actix_web::{get, http::header::ContentType, web, HttpRequest, HttpResponse}; use actix_web::{get, http::header::ContentType, web, HttpRequest, HttpResponse};
use std::borrow::Cow;
use tokio::join; use tokio::join;
/// Handles the route of search page of the `crabbysearch` meta search engine website and it takes /// Handles the route of search page of the `crabbysearch` meta search engine website and it takes
@ -35,23 +36,29 @@ pub async fn search(
) -> Result<HttpResponse, Box<dyn std::error::Error>> { ) -> Result<HttpResponse, Box<dyn std::error::Error>> {
let params = web::Query::<SearchParams>::from_query(req.query_string())?; let params = web::Query::<SearchParams>::from_query(req.query_string())?;
if params.query.as_ref().is_some_and(|q| q.trim().is_empty()) || params.query.is_none() { if params.q.as_ref().is_some_and(|q| q.trim().is_empty()) || params.q.is_none() {
log::info!("wha!");
return Ok(HttpResponse::TemporaryRedirect() return Ok(HttpResponse::TemporaryRedirect()
.insert_header(("location", "/")) .insert_header(("location", "/"))
.finish()); .finish());
} }
let query = params.query.as_ref().unwrap().trim(); let query = params.q.as_ref().unwrap().trim();
let cookie = req.cookie("appCookie"); let cookie = req.cookie("appCookie");
log::info!("{cookie:?}");
// Get search settings using the user's cookie or from the server's config // Get search settings using the user's cookie or from the server's config
let search_settings: crate::engines::Engines = cookie let search_settings: server_models::Cookie<'_> = cookie
.and_then(|cookie_value| serde_json::from_str(&cookie_value.value().to_lowercase()).ok()) .and_then(|cookie_value| serde_json::from_str(cookie_value.value()).ok())
.unwrap_or_default(); .unwrap_or_else(|| {
server_models::Cookie::build(
&config.style,
config
.upstream_search_engines
.iter()
.map(|e| Cow::Borrowed(e.as_str()))
.collect(),
)
});
// Closure wrapping the results function capturing local references // Closure wrapping the results function capturing local references
let get_results = |page| results(config.clone(), cache.clone(), query, page, &search_settings); let get_results = |page| results(config.clone(), cache.clone(), query, page, &search_settings);
@ -101,9 +108,16 @@ pub async fn search(
cache.cache_results(&results_list, &cache_keys); cache.cache_results(&results_list, &cache_keys);
} }
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok().content_type(ContentType::html()).body(
.content_type(ContentType::html()) crate::templates::views::search::search(
.body(crate::templates::views::search::search(query, &results.0).0)) &config.style.colorscheme,
&config.style.theme,
&config.style.animation,
query,
&results.0,
)
.0,
))
} }
/// Fetches the results for a query and page. It First checks the redis cache, if that /// Fetches the results for a query and page. It First checks the redis cache, if that
@ -126,11 +140,16 @@ async fn results(
cache: web::Data<crate::cache::Cache>, cache: web::Data<crate::cache::Cache>,
query: &str, query: &str,
page: u32, page: u32,
upstream: &Engines, search_settings: &server_models::Cookie<'_>,
) -> Result<(SearchResults, String), Box<dyn std::error::Error>> { ) -> Result<(SearchResults, String), Box<dyn std::error::Error>> {
// eagerly parse cookie value to evaluate safe search level // eagerly parse cookie value to evaluate safe search level
let cache_key = format!("search?q={}&page={}&engines={:?}", query, page, upstream); let cache_key = format!(
"search?q={}&page={}&engines={}",
query,
page,
search_settings.engines.join(",")
);
// fetch the cached results json. // fetch the cached results json.
let response = cache.cached_results(&cache_key); let response = cache.cached_results(&cache_key);
@ -143,15 +162,32 @@ async fn results(
// default selected upstream search engines from the config file otherwise // default selected upstream search engines from the config file otherwise
// parse the non-empty cookie and grab the user selected engines from the // parse the non-empty cookie and grab the user selected engines from the
// UI and use that. // UI and use that.
let results: SearchResults = match false { let mut results: SearchResults = match search_settings.engines.is_empty() {
false => aggregate(query, page, config, &Vec::<EngineHandler>::from(upstream)).await?, false => {
aggregate(
query,
page,
config,
&search_settings
.engines
.iter()
.filter_map(|engine| EngineHandler::new(engine).ok())
.collect::<Vec<EngineHandler>>(),
)
.await?
}
true => { true => {
let mut search_results = SearchResults::default(); let mut search_results = SearchResults::default();
search_results.set_no_engines_selected(); search_results.set_no_engines_selected();
search_results search_results
} }
}; };
let (engine_errors_info, results_empty_check, no_engines_selected) = (
results.engine_errors_info().is_empty(),
results.results().is_empty(),
results.no_engines_selected(),
);
results.set_filtered(engine_errors_info & results_empty_check & !no_engines_selected);
cache.cache_results(&[results.clone()], &[cache_key.clone()]); cache.cache_results(&[results.clone()], &[cache_key.clone()]);
Ok((results, cache_key)) Ok((results, cache_key))
} }

View file

@ -1,6 +1,6 @@
//! A module that handles `bar` partial for the `search_bar` partial and the home/index/main page in the `crabbysearch` frontend. //! A module that handles `bar` partial for the `search_bar` partial and the home/index/main page in the `crabbysearch` frontend.
use maud::{html, Markup}; use maud::{html, Markup, PreEscaped};
/// A functions that handles the html code for the bar for the `search_bar` partial and the /// A functions that handles the html code for the bar for the `search_bar` partial and the
/// home/index/main page in the search engine frontend. /// home/index/main page in the search engine frontend.
@ -12,42 +12,12 @@ use maud::{html, Markup};
/// # Returns /// # Returns
/// ///
/// It returns the compiled html code for the search bar as a result. /// It returns the compiled html code for the search bar as a result.
pub fn bar(query: &str, engines: Vec<(&'static str, bool)>) -> Markup { pub fn bar(query: &str) -> Markup {
html!( html!(
.search_container { (PreEscaped("<div class=\"search_bar\">"))
form action="/search" method="get" { input type="search" name="search-box" value=(query) placeholder="Type to search";
div class="search_bar" { button type="submit" onclick="searchWeb()" {
input type="search" name="query" value=(query) placeholder="Type to search";
input type="hidden" name="page" value="1";
button type="submit" {
img src="./images/magnifying_glass.svg" alt="Info icon for error box"; img src="./images/magnifying_glass.svg" alt="Info icon for error box";
} }
}
div class="search_bar" {
.engine_selection {
@for (name, selected) in engines{
@if selected {
.toggle_btn{
span {(name)}
label class="switch"{
input type="checkbox" class="engine" checked;
span class="slider round"{}
}
}
}
@else {
.toggle_btn {
(name)
label class="switch"{
input type="checkbox" class="engine";
span class="slider round"{}
}
}
}
}
}
}
}
}
) )
} }

View file

@ -1,6 +1,7 @@
//! A module that handles the header for all the pages in the `crabbysearch` frontend. //! A module that handles the header for all the pages in the `crabbysearch` frontend.
use maud::{html, Markup, DOCTYPE}; use crate::templates::partials::navbar::navbar;
use maud::{html, Markup, PreEscaped, DOCTYPE};
/// A function that handles the html code for the header for all the pages in the search engine frontend. /// A function that handles the html code for the header for all the pages in the search engine frontend.
/// ///
@ -12,7 +13,7 @@ use maud::{html, Markup, DOCTYPE};
/// # Returns /// # Returns
/// ///
/// It returns the compiled html markup code for the header as a result. /// It returns the compiled html markup code for the header as a result.
pub fn header() -> Markup { pub fn header(colorscheme: &str, theme: &str, animation: &Option<String>) -> Markup {
html!( html!(
(DOCTYPE) (DOCTYPE)
html lang="en" html lang="en"
@ -21,12 +22,17 @@ pub fn header() -> Markup {
title{"crabbysearch"} title{"crabbysearch"}
meta charset="UTF-8"; meta charset="UTF-8";
meta name="viewport" content="width=device-width, initial-scale=1"; meta name="viewport" content="width=device-width, initial-scale=1";
link href=("static/colorschemes/monokai.css") rel="stylesheet" type="text/css"; link href=(format!("static/colorschemes/{colorscheme}.css")) rel="stylesheet" type="text/css";
link href=("static/themes/simple.css") rel="stylesheet" type="text/css"; link href=(format!("static/themes/{theme}.css")) rel="stylesheet" type="text/css";
@if animation.is_some() {
link href=(format!("static/animations/{}.css", animation.as_ref().unwrap())) rel="stylesheet" type="text/css";
}
} }
(PreEscaped("<body onload=\"getClientSettings()\">"))
header{ header{
h1{a href="/"{"crabbysearch"}} h1{a href="/"{"crabbysearch"}}
(navbar())
} }
) )
} }

View file

@ -3,3 +3,6 @@
pub mod bar; pub mod bar;
pub mod footer; pub mod footer;
pub mod header; pub mod header;
pub mod navbar;
pub mod search_bar;
pub mod settings_tabs;

View file

@ -0,0 +1,19 @@
//! A module that handles `navbar` partial for the header partial in the `crabbysearch` frontend.
use maud::{html, Markup};
/// A functions that handles the html code for the header partial.
///
/// # Returns
///
/// It returns the compiled html code for the navbar as a result.
pub fn navbar() -> Markup {
html!(
nav{
ul{
li{a href="about"{"about"}}
li{a href="settings"{"settings"}}
}
}
)
}

View file

@ -0,0 +1,76 @@
//! A module that handles `search bar` partial for the search page in the `crabbysearch` frontend.
use maud::{html, Markup, PreEscaped};
use crate::{models::aggregation_models::EngineErrorInfo, templates::partials::bar::bar};
/// A constant holding the named safe search level options for the corresponding values 0, 1 and 2.
const SAFE_SEARCH_LEVELS_NAME: [&str; 3] = ["None", "Low", "Moderate"];
/// A functions that handles the html code for the search bar for the search page.
///
/// # Arguments
///
/// * `engine_errors_info` - It takes the engine errors list containing errors for each upstream
/// search engine which failed to provide results as an argument.
/// * `safe_search_level` - It takes the safe search level with values from 0-2 as an argument.
/// * `query` - It takes the current search query provided by user as an argument.
///
/// # Returns
///
/// It returns the compiled html code for the search bar as a result.
pub fn search_bar(
engine_errors_info: &[EngineErrorInfo],
safe_search_level: u8,
query: &str,
) -> Markup {
html!(
.search_area{
(bar(query))
.error_box {
@if !engine_errors_info.is_empty(){
button onclick="toggleErrorBox()" class="error_box_toggle_button"{
img src="./images/warning.svg" alt="Info icon for error box";
}
.dropdown_error_box{
@for errors in engine_errors_info{
.error_item{
span class="engine_name"{(errors.engine)}
span class="engine_name"{(errors.error)}
span class="severity_color" style="background: {{{this.severity_color}}};"{}
}
}
}
}
@else {
button onclick="toggleErrorBox()" class="error_box_toggle_button"{
img src="./images/info.svg" alt="Warning icon for error box";
}
.dropdown_error_box {
.no_errors{
"Everything looks good 🙂!!"
}
}
}
}
(PreEscaped("</div>"))
.search_options {
@if safe_search_level >= 3 {
(PreEscaped("<select name=\"safe_search_levels\" disabled>"))
}
@else{
(PreEscaped("<select name=\"safe_search_levels\">"))
}
@for (idx, name) in SAFE_SEARCH_LEVELS_NAME.iter().enumerate() {
@if (safe_search_level as usize) == idx {
option value=(idx) selected {(format!("SafeSearch: {name}"))}
}
@else{
option value=(idx) {(format!("SafeSearch: {name}"))}
}
}
(PreEscaped("</select>"))
}
}
)
}

View file

@ -0,0 +1,25 @@
//! A module that handles the engines tab for setting page view in the `crabbysearch` frontend.
use maud::{html, Markup};
/// A functions that handles the html code for the cookies tab for the settings page for the search page.
///
/// # Returns
///
/// It returns the compiled html markup code for the cookies tab.
pub fn cookies() -> Markup {
html!(
div class="cookies tab"{
h1{"Cookies"}
p class="description"{
"This is the cookies are saved on your system and it contains the preferences
you chose in the settings page"
}
input type="text" name="cookie_field" value="" readonly;
p class="description"{
"The cookies stored are not used by us for any malicious intend or for
tracking you in any way."
}
}
)
}

View file

@ -0,0 +1,74 @@
//! A module that handles the engines tab for setting page view in the `crabbysearch` frontend.
use std::collections::HashMap;
use maud::{html, Markup};
/// A functions that handles the html code for the engines tab for the settings page for the search page.
///
/// # Arguments
///
/// * `engine_names` - It takes the key value pair list of all available engine names and there corresponding
/// selected (enabled/disabled) value as an argument.
///
/// # Returns
///
/// It returns the compiled html markup code for the engines tab.
pub fn engines(engine_names: &HashMap<String, bool>) -> Markup {
html!(
div class="engines tab"{
h1{"Engines"}
h3{"select search engines"}
p class="description"{
"Select the search engines from the list of engines that you want results from"
}
.engine_selection{
// Checks whether all the engines are selected or not if they are then the
// checked `select_all` button is rendered otherwise the unchecked version
// is rendered.
@if engine_names.values().all(|selected| *selected){
.toggle_btn{
label class="switch"{
input type="checkbox" class="select_all" onchange="toggleAllSelection()" checked;
span class="slider round"{}
}
"Select All"
}
}
@else{
.toggle_btn {
label class="switch"{
input type="checkbox" class="select_all" onchange="toggleAllSelection()";
span class="slider round"{}
}
"Select All"
}
}
hr;
@for (engine_name, selected) in engine_names{
// Checks whether the `engine_name` is selected or not if they are then the
// checked `engine` button is rendered otherwise the unchecked version is
// rendered.
@if *selected {
.toggle_btn{
label class="switch"{
input type="checkbox" class="engine" checked;
span class="slider round"{}
}
(format!("{}{}",&engine_name[..1].to_uppercase(), &engine_name[1..]))
}
}
@else {
.toggle_btn {
label class="switch"{
input type="checkbox" class="engine";
span class="slider round"{}
}
(format!("{}{}",&engine_name[..1], &engine_name[1..]))
}
}
}
}
}
)
}

View file

@ -0,0 +1,6 @@
//! This module provides other modules to handle the partials for the tabs for the settings page
//! view in the `crabbysearch` frontend.
pub mod cookies;
pub mod engines;
pub mod user_interface;

View file

@ -0,0 +1,96 @@
//! A module that handles the user interface tab for setting page view in the `crabbysearch` frontend.
use crate::handler::{file_path, FileType};
use maud::{html, Markup};
use std::fs::read_dir;
/// A helper function that helps in building the list of all available colorscheme/theme/animation
/// names present in the colorschemes, animations and themes folder respectively by excluding the
/// ones that have already been selected via the config file.
///
/// # Arguments
///
/// * `style_type` - It takes the style type of the values `theme` and `colorscheme` as an
/// argument.
/// * `selected_style` - It takes the currently selected style value provided via the config file
/// as an argument.
///
/// # Error
///
/// Returns a list of colorscheme/theme names as a vector of tuple strings on success otherwise
/// returns a standard error message.
fn style_option_list(
style_type: &str,
selected_style: &str,
) -> Result<Vec<(String, String)>, Box<dyn std::error::Error>> {
let mut style_option_names: Vec<(String, String)> = Vec::new();
for file in read_dir(format!(
"{}static/{}/",
file_path(FileType::Theme)?,
style_type,
))? {
let style_name = file?.file_name().to_str().unwrap().replace(".css", "");
if selected_style != style_name {
style_option_names.push((style_name.clone(), style_name.replace('-', " ")));
}
}
if style_type == "animations" {
style_option_names.push((String::default(), "none".to_owned()))
}
Ok(style_option_names)
}
/// A functions that handles the html code for the user interface tab for the settings page for the search page.
///
/// # Error
///
/// It returns the compiled html markup code for the user interface tab on success otherwise
/// returns a standard error message.
pub fn user_interface(
theme: &str,
colorscheme: &str,
animation: &Option<String>,
) -> Result<Markup, Box<dyn std::error::Error>> {
Ok(html!(
div class="user_interface tab"{
h1{"User Interface"}
h3{"select theme"}
p class="description"{
"Select the theme from the available themes to be used in user interface"
}
select name="themes"{
// Sets the user selected theme name from the config file as the first option in the selection list.
option value=(theme){(theme.replace('-', " "))}
@for (k,v) in style_option_list("themes", theme)?{
option value=(k){(v)}
}
}
h3{"select color scheme"}
p class="description"{
"Select the color scheme for your theme to be used in user interface"
}
select name="colorschemes"{
// Sets the user selected colorscheme name from the config file as the first option in the selection list.
option value=(colorscheme){(colorscheme.replace('-', " "))}
@for (k,v) in style_option_list("colorschemes", colorscheme)?{
option value=(k){(v)}
}
}
h3{"select animation"}
p class="description"{
"Select the animation for your theme to be used in user interface"
}
select name="animations"{
@let default_animation = &String::default();
@let animation = animation.as_ref().unwrap_or(default_animation);
// Sets the user selected animation name from the config file as the first option in the selection list.
option value=(animation){(animation.replace('-'," "))}
@for (k,v) in style_option_list("animations", animation)?{
option value=(k){(v)}
}
}
}
))
}

View file

@ -14,7 +14,16 @@ use crate::templates::partials::{footer::footer, header::header};
/// # Returns /// # Returns
/// ///
/// It returns the compiled html markup code as a result. /// It returns the compiled html markup code as a result.
pub fn about() -> Markup { pub fn about(colorscheme: &str, theme: &str, animation: &Option<String>) -> Markup {
let logo_svg = r#"
<svg viewBox="0 0 173 57" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M77.8201 21.4277L73.4513 35.5049H70.3855L67.5496 25.1067L64.7137 35.5049H61.6479L57.2536 21.4277H60.2172L63.1553 32.7457L66.1444 21.4277H69.1847L72.0461 32.6946L74.9586 21.4277H77.8201ZM92.8986 28.1214C92.8986 28.6494 92.8645 29.1263 92.7964 29.5521H82.0405C82.1257 30.6762 82.543 31.5789 83.2924 32.2602C84.0418 32.9415 84.9616 33.2822 86.0516 33.2822C87.6186 33.2822 88.7257 32.6264 89.3729 31.3149H92.5154C92.0896 32.6094 91.3146 33.6739 90.1905 34.5085C89.0834 35.326 87.7038 35.7348 86.0516 35.7348C84.7061 35.7348 83.4968 35.4368 82.4238 34.8406C81.3678 34.2275 80.5332 33.3758 79.92 32.2858C79.3239 31.1787 79.0258 29.9013 79.0258 28.4535C79.0258 27.0058 79.3154 25.7369 79.8945 24.6468C80.4906 23.5397 81.3167 22.6881 82.3727 22.092C83.4457 21.4958 84.672 21.1978 86.0516 21.1978C87.3801 21.1978 88.5639 21.4873 89.6029 22.0664C90.6418 22.6455 91.4509 23.4631 92.03 24.5191C92.6091 25.558 92.8986 26.7588 92.8986 28.1214ZM89.8583 27.2016C89.8413 26.1286 89.4581 25.2685 88.7087 24.6213C87.9592 23.974 87.031 23.6504 85.9239 23.6504C84.919 23.6504 84.0589 23.974 83.3435 24.6213C82.6281 25.2515 82.2023 26.1116 82.0661 27.2016H89.8583ZM98.6773 23.5227C99.1713 22.8414 99.844 22.2878 100.696 21.862C101.564 21.4192 102.527 21.1978 103.583 21.1978C104.826 21.1978 105.95 21.4958 106.955 22.092C107.96 22.6881 108.752 23.5397 109.331 24.6468C109.91 25.7369 110.2 26.9887 110.2 28.4024C110.2 29.8161 109.91 31.085 109.331 32.2091C108.752 33.3162 107.951 34.1849 106.929 34.8151C105.925 35.4282 104.809 35.7348 103.583 35.7348C102.493 35.7348 101.522 35.5219 100.67 35.0961C99.8355 34.6703 99.1713 34.1253 98.6773 33.461V35.5049H95.7648V16.5991H98.6773V23.5227ZM107.236 28.4024C107.236 27.4316 107.032 26.597 106.623 25.8987C106.231 25.1833 105.703 24.6468 105.039 24.2891C104.392 23.9144 103.693 23.7271 102.944 23.7271C102.212 23.7271 101.513 23.9144 100.849 24.2891C100.202 24.6638 99.6737 25.2089 99.265 25.9242C98.8732 26.6396 98.6773 27.4827 98.6773 28.4535C98.6773 29.4244 98.8732 30.276 99.265 31.0084C99.6737 31.7237 100.202 32.2688 100.849 32.6435C101.513 33.0182 102.212 33.2055 102.944 33.2055C103.693 33.2055 104.392 33.0182 105.039 32.6435C105.703 32.2517 106.231 31.6897 106.623 30.9573C107.032 30.2249 107.236 29.3733 107.236 28.4024ZM118.19 35.7348C117.082 35.7348 116.086 35.5389 115.2 35.1472C114.332 34.7384 113.642 34.1934 113.131 33.5121C112.62 32.8138 112.347 32.0388 112.313 31.1872H115.328C115.379 31.7833 115.66 32.2858 116.171 32.6946C116.699 33.0863 117.355 33.2822 118.138 33.2822C118.956 33.2822 119.586 33.1289 120.029 32.8223C120.489 32.4987 120.719 32.0899 120.719 31.596C120.719 31.068 120.463 30.6762 119.952 30.4207C119.458 30.1653 118.666 29.8842 117.576 29.5777C116.52 29.2881 115.66 29.0071 114.996 28.7346C114.332 28.462 113.753 28.0447 113.259 27.4827C112.782 26.9206 112.543 26.1797 112.543 25.26C112.543 24.5105 112.765 23.8293 113.208 23.2161C113.65 22.5859 114.281 22.092 115.098 21.7343C115.933 21.3766 116.887 21.1978 117.96 21.1978C119.561 21.1978 120.847 21.6065 121.817 22.4241C122.805 23.2246 123.333 24.3232 123.401 25.7198H120.489C120.438 25.0896 120.182 24.5872 119.722 24.2125C119.263 23.8378 118.641 23.6504 117.857 23.6504C117.091 23.6504 116.503 23.7952 116.095 24.0847C115.686 24.3743 115.481 24.7575 115.481 25.2344C115.481 25.6091 115.618 25.9242 115.89 26.1797C116.163 26.4352 116.495 26.6396 116.887 26.7929C117.278 26.9291 117.857 27.108 118.624 27.3294C119.646 27.6019 120.48 27.8829 121.128 28.1725C121.792 28.445 122.362 28.8538 122.839 29.3988C123.316 29.9438 123.563 30.6677 123.58 31.5704C123.58 32.3709 123.359 33.0863 122.916 33.7165C122.473 34.3467 121.843 34.8406 121.025 35.1983C120.225 35.556 119.28 35.7348 118.19 35.7348ZM139.476 21.4277V35.5049H136.563V33.8442C136.104 34.4233 135.499 34.8832 134.75 35.2239C134.017 35.5475 133.234 35.7093 132.399 35.7093C131.292 35.7093 130.296 35.4793 129.41 35.0195C128.541 34.5596 127.851 33.8783 127.34 32.9756C126.847 32.0729 126.6 30.9828 126.6 29.7054V21.4277H129.487V29.2711C129.487 30.5315 129.802 31.5023 130.432 32.1836C131.062 32.8478 131.922 33.18 133.012 33.18C134.102 33.18 134.962 32.8478 135.593 32.1836C136.24 31.5023 136.563 30.5315 136.563 29.2711V21.4277H139.476ZM146.231 23.4716C146.657 22.7562 147.219 22.2027 147.918 21.8109C148.633 21.4022 149.476 21.1978 150.447 21.1978V24.2125H149.706C148.565 24.2125 147.696 24.502 147.1 25.0811C146.521 25.6602 146.231 26.6651 146.231 28.0958V35.5049H143.319V21.4277H146.231V23.4716ZM159.026 23.8037H156.42V35.5049H153.482V23.8037H151.821V21.4277H153.482V20.4313C153.482 18.8133 153.907 17.638 154.759 16.9056C155.628 16.1562 156.982 15.7815 158.821 15.7815V18.2086C157.936 18.2086 157.314 18.3789 156.956 18.7196C156.599 19.0432 156.42 19.6138 156.42 20.4313V21.4277H159.026V23.8037ZM167.636 28.3769L172.184 35.5049H168.888L165.848 30.7273L162.986 35.5049H159.946L164.494 28.5813L159.946 21.4277H163.242L166.282 26.2053L169.144 21.4277H172.184L167.636 28.3769Z" fill="currentColor"/>
<path d="M2.21486 42.7894C1.15271 43.0507 0.550463 44.1151 1.00616 45.1192C4.17619 52.1035 11.5005 54.9673 23.3646 52.0493C35.2399 49.1285 47.5128 41.4358 47.2254 33.7293C47.1854 32.6562 46.0226 32.0146 44.9605 32.2759L2.21486 42.7894Z" fill="currentColor"/>
<path d="M20.1227 10.0027C21.9192 10.8048 23.7313 11.7606 25.4259 12.8819C28.7827 15.1031 31.9178 18.1341 33.329 22.1366C34.1626 24.5009 34.0742 26.7513 33.2144 28.7679C32.4048 30.6666 30.9903 32.178 29.4212 33.3664C37.6699 31.0439 47.0335 26.0679 44.0686 17.608C40.9417 8.68557 29.3768 3.38405 21.266 1.04683C19.3981 0.508566 17.8191 2.37853 18.4252 4.22557C18.9773 5.90835 19.5596 7.85665 20.1227 10.0027Z" fill="currentColor"/>
<path d="M8.27125 34.3558C8.02834 30.0503 7.01551 25.8501 5.987 22.6653C5.38101 20.7888 6.95924 18.8318 8.8458 19.4057C13.6444 20.8655 19.4581 23.6235 21.1736 27.928C23.1268 32.8287 16.4467 35.584 11.1405 36.7375C9.66674 37.0579 8.35621 35.8616 8.27125 34.3558Z" fill="currentColor"/>
<path d="M12.5601 18.017C14.2332 18.6725 15.9372 19.4786 17.5019 20.4515C19.9805 21.9927 22.38 24.1208 23.5241 26.9914C24.2516 28.8168 24.2152 30.6223 23.4834 32.2482C23.1213 33.0529 22.6157 33.7553 22.0354 34.3669C27.6731 32.3348 32.9532 28.6804 30.9428 22.9781C28.6334 16.428 20.4047 12.4027 14.1807 10.4674C12.3234 9.88988 10.7357 11.7563 11.3182 13.6121C11.7303 14.9248 12.1549 16.4076 12.5601 18.017Z" fill="currentColor" fill-opacity="0.89"/>
</svg>
"#;
let feature_lightning = r#" let feature_lightning = r#"
<svg xmlns="http://www.w3.org/2000/svg" width="60" viewBox="0 0 256 256"><path fill="currentColor" d="m213.85 125.46l-112 120a8 8 0 0 1-13.69-7l14.66-73.33l-57.63-21.64a8 8 0 0 1-3-13l112-120a8 8 0 0 1 13.69 7l-14.7 73.41l57.63 21.61a8 8 0 0 1 3 12.95Z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="60" viewBox="0 0 256 256"><path fill="currentColor" d="m213.85 125.46l-112 120a8 8 0 0 1-13.69-7l14.66-73.33l-57.63-21.64a8 8 0 0 1-3-13l112-120a8 8 0 0 1 13.69 7l-14.7 73.41l57.63 21.61a8 8 0 0 1 3 12.95Z"/></svg>
"#; "#;
@ -34,9 +43,12 @@ pub fn about() -> Markup {
<svg xmlns="http://www.w3.org/2000/svg" width="60" viewBox="0 0 20 20"><path fill="currentColor" d="M18.33 3.57s.27-.8-.31-1.36c-.53-.52-1.22-.24-1.22-.24c-.61.3-5.76 3.47-7.67 5.57c-.86.96-2.06 3.79-1.09 4.82c.92.98 3.96-.17 4.79-1c2.06-2.06 5.21-7.17 5.5-7.79M1.4 17.65c2.37-1.56 1.46-3.41 3.23-4.64c.93-.65 2.22-.62 3.08.29c.63.67.8 2.57-.16 3.46c-1.57 1.45-4 1.55-6.15.89"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="60" viewBox="0 0 20 20"><path fill="currentColor" d="M18.33 3.57s.27-.8-.31-1.36c-.53-.52-1.22-.24-1.22-.24c-.61.3-5.76 3.47-7.67 5.57c-.86.96-2.06 3.79-1.09 4.82c.92.98 3.96-.17 4.79-1c2.06-2.06 5.21-7.17 5.5-7.79M1.4 17.65c2.37-1.56 1.46-3.41 3.23-4.64c.93-.65 2.22-.62 3.08.29c.63.67.8 2.57-.16 3.46c-1.57 1.45-4 1.55-6.15.89"/></svg>
"#; "#;
html!( html!(
(header()) (header(colorscheme, theme, animation))
main class="about-container"{ main class="about-container"{
article { article {
div class="logo-container" {
(PreEscaped(logo_svg))
}
div class="text-block" { div class="text-block" {
h3 class="text-block-title" {"Why crabbysearch?"} h3 class="text-block-title" {"Why crabbysearch?"}

View file

@ -1,6 +1,6 @@
//! A module that handles the view for the index/home/main page in the `crabbysearch` frontend. //! A module that handles the view for the index/home/main page in the `crabbysearch` frontend.
use maud::{html, Markup}; use maud::{html, Markup, PreEscaped};
use crate::templates::partials::{bar::bar, footer::footer, header::header}; use crate::templates::partials::{bar::bar, footer::footer, header::header};
@ -14,8 +14,14 @@ use crate::templates::partials::{bar::bar, footer::footer, header::header};
/// # Returns /// # Returns
/// ///
/// It returns the compiled html markup code as a result. /// It returns the compiled html markup code as a result.
pub fn index() -> Markup { pub fn index(colorscheme: &str, theme: &str, animation: &Option<String>) -> Markup {
html!((header())(bar(&String::default(), vec![("bing", true)]))( html!(
footer() (header(colorscheme, theme, animation))
)) main class="search-container"{
(bar(&String::default()))
(PreEscaped("</div>"))
}
script src="static/index.js"{}
(footer())
)
} }

View file

@ -5,3 +5,4 @@ pub mod about;
pub mod index; pub mod index;
pub mod not_found; pub mod not_found;
pub mod search; pub mod search;
pub mod settings;

View file

@ -13,9 +13,9 @@ use maud::{html, Markup};
/// # Returns /// # Returns
/// ///
/// It returns the compiled html markup code as a result. /// It returns the compiled html markup code as a result.
pub fn not_found() -> Markup { pub fn not_found(colorscheme: &str, theme: &str, animation: &Option<String>) -> Markup {
html!( html!(
(header()) (header(colorscheme, theme, animation))
main class="error_container"{ main class="error_container"{
img src="images/robot-404.svg" alt="Image of broken robot."; img src="images/robot-404.svg" alt="Image of broken robot.";
.error_content{ .error_content{

View file

@ -4,7 +4,7 @@ use maud::{html, Markup, PreEscaped};
use crate::{ use crate::{
models::aggregation_models::SearchResults, models::aggregation_models::SearchResults,
templates::partials::{bar::bar, footer::footer, header::header}, templates::partials::{footer::footer, header::header, search_bar::search_bar},
}; };
/// A function that handles the html code for the search page view in the search engine frontend. /// A function that handles the html code for the search page view in the search engine frontend.
@ -19,11 +19,17 @@ use crate::{
/// # Returns /// # Returns
/// ///
/// It returns the compiled html markup code as a result. /// It returns the compiled html markup code as a result.
pub fn search(query: &str, search_results: &SearchResults) -> Markup { pub fn search(
colorscheme: &str,
theme: &str,
animation: &Option<String>,
query: &str,
search_results: &SearchResults,
) -> Markup {
html!( html!(
(header()) (header(colorscheme, theme, animation))
main class="results"{ main class="results"{
(bar(query, vec![("Bing", true)])) (search_bar(&search_results.engine_errors_info, search_results.safe_search_level, query))
.results_aggregated{ .results_aggregated{
@if !search_results.results.is_empty() { @if !search_results.results.is_empty() {
@for result in search_results.results.iter(){ @for result in search_results.results.iter(){
@ -39,6 +45,40 @@ pub fn search(query: &str, search_results: &SearchResults) -> Markup {
} }
} }
} }
@else if search_results.disallowed{
.result_disallowed{
.description{
p{
"Your search - "{span class="user_query"{(query)}}" -
has been disallowed."
}
p class="description_paragraph"{"Dear user,"}
p class="description_paragraph"{
"The query - "{span class="user_query"{(query)}}" - has
been blacklisted via server configuration and hence disallowed by the
server. Henceforth no results could be displayed for your query."
}
}
img src="./images/barricade.png" alt="Image of a Barricade";
}
}
@else if search_results.filtered {
.result_filtered{
.description{
p{
"Your search - "{span class="user_query"{(query)}}" -
has been filtered."
}
p class="description_paragraph"{"Dear user,"}
p class="description_paragraph"{
"All the search results contain results that has been configured to be
filtered out via server configuration and henceforth has been
completely filtered out."
}
}
img src="./images/filter.png" alt="Image of a paper inside a funnel";
}
}
@else if search_results.no_engines_selected { @else if search_results.no_engines_selected {
.result_engine_not_selected{ .result_engine_not_selected{
.description{ .description{

View file

@ -0,0 +1,58 @@
//! A module that handles the view for the settings page in the `crabbysearch` frontend.
use std::collections::HashMap;
use maud::{html, Markup};
use crate::templates::partials::{
footer::footer,
header::header,
settings_tabs::{cookies::cookies, engines::engines, user_interface::user_interface},
};
/// A function that handles the html code for the settings page view in the search engine frontend.
///
/// # Arguments
///
/// * `safe_search_level` - It takes the safe search level as an argument.
/// * `colorscheme` - It takes the colorscheme name as an argument.
/// * `theme` - It takes the theme name as an argument.
/// * `animation` - It takes the animation name as an argument.
/// * `engine_names` - It takes a list of engine names as an argument.
///
/// # Error
///
/// This function returns a compiled html markup code on success otherwise returns a standard error
/// message.
pub fn settings(
colorscheme: &str,
theme: &str,
animation: &Option<String>,
//engine_names: &HashMap<String, bool>,
) -> Result<Markup, Box<dyn std::error::Error>> {
Ok(html!(
(header(colorscheme, theme, animation))
main class="settings"{
h1{"Settings"}
hr;
.settings_container{
.sidebar{
div class="btn active" onclick="setActiveTab(this)"{"general"}
.btn onclick="setActiveTab(this)"{"user interface"}
.btn onclick="setActiveTab(this)"{"engines"}
.btn onclick="setActiveTab(this)"{"cookies"}
}
.main_container{
(user_interface(theme, colorscheme, animation)?)
//(engines(engine_names))
(cookies())
p class="message"{}
button type="submit" onclick="setClientSettings()"{"Save"}
}
}
}
script src="static/settings.js"{}
script src="static/cookies.js"{}
(footer())
))
}