Sérč bar a aktuální počet hlášení

Tento commit je obsažen v:
sneedmaster 2024-03-08 22:05:21 +01:00
rodič 59c1052125
revize e46d858545
19 změnil soubory, kde provedl 290 přidání a 55 odebrání

Zobrazit soubor

@ -30,7 +30,7 @@ fn main() -> Result<(), Error> {
} }
let html = read_to_string(&path)?; let html = read_to_string(&path)?;
let minified = minify(html)?.replace('\n', ""); let minified = minify(html)?.replace('\n', "").replace("&#32;", " ");
File::create(path)?.write_all(minified.as_bytes())?; File::create(path)?.write_all(minified.as_bytes())?;
} }

Zobrazit soubor

@ -6,13 +6,13 @@ use crate::{ctx::Ctx, error::NekrochanError};
impl LocalStats { impl LocalStats {
pub async fn read(ctx: &Ctx) -> Result<Self, NekrochanError> { pub async fn read(ctx: &Ctx) -> Result<Self, NekrochanError> {
let (post_count,) = query_as( let (post_count,) = query_as(
"SELECT coalesce(sum(last_value)::bigint, 0) FROM pg_sequences WHERE sequencename LIKE 'posts_%_id_seq'", "SELECT COALESCE(SUM(last_value)::bigint, 0) FROM pg_sequences WHERE sequencename LIKE 'posts_%_id_seq'",
) )
.fetch_one(ctx.db()) .fetch_one(ctx.db())
.await?; .await?;
let (file_count, file_size) = query_as( let (file_count, file_size) = query_as(
r#"SELECT count(files), coalesce(sum((files->>'size')::bigint)::bigint, 0) FROM ( r#"SELECT COUNT(files), COALESCE(SUM((files->>'size')::bigint)::bigint, 0) FROM (
SELECT jsonb_array_elements(files) AS files FROM overboard SELECT jsonb_array_elements(files) AS files FROM overboard
) flatten"#, ) flatten"#,
) )

Zobrazit soubor

@ -277,6 +277,41 @@ impl Post {
Ok(posts) Ok(posts)
} }
pub async fn read_by_query(
ctx: &Ctx,
board: &Board,
query: String,
page: i64,
) -> Result<Vec<Self>, NekrochanError> {
let posts = query_as(&format!(
"SELECT * FROM posts_{} WHERE LOWER(content_nomarkup) LIKE LOWER($1) LIMIT $2 OFFSET $3",
board.id
))
.bind(format!("%{query}%"))
.bind(board.config.0.page_size)
.bind((page - 1) * board.config.0.page_size)
.fetch_all(ctx.db())
.await?;
Ok(posts)
}
pub async fn read_by_query_overboard(
ctx: &Ctx,
query: String,
page: i64,
) -> Result<Vec<Self>, NekrochanError> {
let posts =
query_as("SELECT * FROM overboard WHERE LOWER(content_nomarkup) LIKE LOWER($1) LIMIT $2 OFFSET $3")
.bind(format!("%{query}%"))
.bind(GENERIC_PAGE_SIZE)
.bind((page - 1) * GENERIC_PAGE_SIZE)
.fetch_all(ctx.db())
.await?;
Ok(posts)
}
pub async fn update_user_id(&self, ctx: &Ctx, user_id: String) -> Result<(), NekrochanError> { pub async fn update_user_id(&self, ctx: &Ctx, user_id: String) -> Result<(), NekrochanError> {
let post = query_as(&format!( let post = query_as(&format!(
"UPDATE posts_{} SET user_id = $1 WHERE id = $2 RETURNING *", "UPDATE posts_{} SET user_id = $1 WHERE id = $2 RETURNING *",

Zobrazit soubor

@ -74,8 +74,9 @@ async fn run() -> Result<(), Error> {
.service(web::overboard::overboard) .service(web::overboard::overboard)
.service(web::overboard_catalog::overboard_catalog) .service(web::overboard_catalog::overboard_catalog)
.service(web::page::page) .service(web::page::page)
.service(web::thread_json::thread_json) .service(web::search::search)
.service(web::thread::thread) .service(web::thread::thread)
.service(web::thread_json::thread_json)
.service(web::actions::appeal_ban::appeal_ban) .service(web::actions::appeal_ban::appeal_ban)
.service(web::actions::create_post::create_post) .service(web::actions::create_post::create_post)
.service(web::actions::edit_posts::edit_posts) .service(web::actions::edit_posts::edit_posts)

Zobrazit soubor

@ -262,9 +262,11 @@ pub async fn create_post(
let name_cookie = Cookie::build("name", name_raw).path("/").finish(); let name_cookie = Cookie::build("name", name_raw).path("/").finish();
let password_cookie = Cookie::build("password", password_raw).path("/").finish(); let password_cookie = Cookie::build("password", password_raw).path("/").finish();
let email_cookie = Cookie::build("email", email_raw).path("/").finish();
res.cookie(name_cookie); res.cookie(name_cookie);
res.cookie(password_cookie); res.cookie(password_cookie);
res.cookie(email_cookie);
let res = if noko { let res = if noko {
res.append_header(("Location", post.post_url().as_str())) res.append_header(("Location", post.post_url().as_str()))

Zobrazit soubor

@ -12,6 +12,7 @@ pub mod news;
pub mod overboard; pub mod overboard;
pub mod overboard_catalog; pub mod overboard_catalog;
pub mod page; pub mod page;
pub mod search;
pub mod staff; pub mod staff;
pub mod tcx; pub mod tcx;
pub mod thread; pub mod thread;

84
src/web/search.rs Normální soubor
Zobrazit soubor

@ -0,0 +1,84 @@
use actix_web::{
get,
web::{Data, Query},
HttpRequest, HttpResponse,
};
use askama::Template;
use serde::Deserialize;
use std::collections::HashMap;
use super::tcx::TemplateCtx;
use crate::{
ctx::Ctx,
db::models::{Board, Post},
error::NekrochanError,
filters, web::template_response,
};
#[derive(Template)]
#[template(path = "search.html")]
struct SearchTemplate {
tcx: TemplateCtx,
board_opt: Option<Board>,
boards: HashMap<String, Board>,
query: String,
posts: Vec<Post>,
page: i64,
}
#[derive(Deserialize)]
pub struct SearchQuery {
board: Option<String>,
query: String,
page: Option<i64>,
}
#[get("/search")]
pub async fn search(
ctx: Data<Ctx>,
req: HttpRequest,
Query(query): Query<SearchQuery>,
) -> Result<HttpResponse, NekrochanError> {
let tcx = TemplateCtx::new(&ctx, &req).await?;
let board_opt = if let Some(board) = query.board {
let board = Board::read(&ctx, board.clone())
.await?
.ok_or(NekrochanError::BoardNotFound(board))?;
Some(board)
} else {
None
};
let boards = if board_opt.is_none() {
Board::read_all_map(&ctx).await?
} else {
HashMap::new()
};
let page = query.page.unwrap_or(1);
if page <= 0 {
return Err(NekrochanError::InvalidPageError);
}
let query = query.query;
let posts = if let Some(board) = &board_opt {
Post::read_by_query(&ctx, board, query.clone(), page).await?
} else {
Post::read_by_query_overboard(&ctx, query.clone(), page).await?
};
let template = SearchTemplate {
tcx,
board_opt,
boards,
query,
posts,
page,
};
template_response(&template)
}

Zobrazit soubor

@ -1,5 +1,6 @@
use actix_web::HttpRequest; use actix_web::HttpRequest;
use redis::{AsyncCommands, Commands, Connection}; use redis::{AsyncCommands, Commands, Connection};
use sqlx::query_as;
use std::{ use std::{
collections::HashSet, collections::HashSet,
net::{IpAddr, Ipv4Addr}, net::{IpAddr, Ipv4Addr},
@ -18,6 +19,7 @@ pub struct TemplateCtx {
pub perms: PermissionWrapper, pub perms: PermissionWrapper,
pub ip: IpAddr, pub ip: IpAddr,
pub yous: HashSet<String>, pub yous: HashSet<String>,
pub report_count: Option<i64>,
} }
impl TemplateCtx { impl TemplateCtx {
@ -37,6 +39,20 @@ impl TemplateCtx {
let account = account.map(|account| account.username); let account = account.map(|account| account.username);
let report_count = if perms.owner() || perms.reports() {
let count: Option<Option<(i64,)>> = query_as("SELECT SUM(jsonb_array_length(reports)) FROM overboard WHERE reports != '[]'::jsonb")
.fetch_optional(ctx.db())
.await
.ok();
match count {
Some(Some((count,))) if count != 0 => Some(count),
_ => None,
}
} else {
None
};
let tcx = Self { let tcx = Self {
cfg, cfg,
boards, boards,
@ -44,6 +60,7 @@ impl TemplateCtx {
ip, ip,
yous, yous,
account, account,
report_count,
}; };
Ok(tcx) Ok(tcx)

Zobrazit soubor

@ -1,6 +1,7 @@
$(function () { $(function () {
let name = get_cookie("name"); let name = get_cookie("name");
let password = get_cookie("password"); let password = get_cookie("password");
let email = get_cookie("email");
if (password === "") { if (password === "") {
password = generate_password(); password = generate_password();
@ -9,42 +10,43 @@ $(function () {
$('input[name="post_name"]').attr("value", name); $('input[name="post_name"]').attr("value", name);
$('input[name="post_password"]').attr("value", password); $('input[name="post_password"]').attr("value", password);
$('input[name="email"]').attr("value", email);
function generate_password() {
let chars =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
let password_length = 8;
let password = "";
for (let i = 0; i <= password_length; i++) {
let random_number = Math.floor(Math.random() * chars.length);
password += chars.substring(random_number, random_number + 1);
}
return password;
}
function get_cookie(cname) {
let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(";");
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == " ") {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
function set_cookie(cname, cvalue) {
document.cookie = `${cname}=${cvalue};path=/`;
}
}); });
function generate_password() {
let chars =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
let password_length = 8;
let password = "";
for (let i = 0; i <= password_length; i++) {
let random_number = Math.floor(Math.random() * chars.length);
password += chars.substring(random_number, random_number + 1);
}
return password;
}
function get_cookie(cname) {
let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(";");
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == " ") {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
function set_cookie(cname, cvalue) {
document.cookie = `${cname}=${cvalue};path=/`;
}

Zobrazit soubor

@ -30,10 +30,10 @@ $(function () {
if (saved_top) { if (saved_top) {
form.css("top", saved_top); form.css("top", saved_top);
} }
if (saved_left) { if (saved_left) {
form.css("left", saved_left); form.css("left", saved_left);
form.css("right", "auto") form.css("right", "auto");
} }
handle.on("mousedown", start); handle.on("mousedown", start);
@ -139,11 +139,12 @@ $(function () {
return; return;
} }
if (rect.right > document.documentElement.clientWidth) { if (Math.floor(rect.right) > document.documentElement.clientWidth) {
form.css("left", 0); form.css("left", 0);
form.css("right", "auto");
} }
if (rect.bottom > document.documentElement.clientHeight) { if (Math.floor(rect.bottom) > document.documentElement.clientHeight) {
form.css("top", 0); form.css("top", 0);
} }

Zobrazit soubor

@ -39,6 +39,9 @@
</span> </span>
{% endfor %} {% endfor %}
<span class="float-r"> <span class="float-r">
{% if let Some(report_count) = tcx.report_count %}
<span class="link-group"><a href="/staff/reports">Hlášení: {{ report_count }}</a></span>
{% endif %}
{% if tcx.account.is_some() %} {% if tcx.account.is_some() %}
<span class="link-group"> <span class="link-group">
<a href="/logout">odhlásit se</a> <a href="/logout">odhlásit se</a>

Zobrazit soubor

@ -16,6 +16,20 @@
</div> </div>
</div> </div>
<hr> <hr>
<form method="get" action="/search">
<input name="board" type="hidden" value="{{ board.id }}">
<table class="form-table">
<tr>
<td>
<input name="query" type="text">
</td>
<td>
<input class="button" type="submit" value="Hledat">
</td>
</tr>
</table>
</form>
<hr>
<form method="post"> <form method="post">
<div class="center"> <div class="center">
{% for thread in threads %} {% for thread in threads %}

Zobrazit soubor

@ -7,7 +7,10 @@
{% block title %}Příspěvky od [{{ ip }}]{% endblock %} {% block title %}Příspěvky od [{{ ip }}]{% endblock %}
{% block content %} {% block content %}
<h1 class="title">Příspěvky od [{{ ip }}]</h1> <div class="center">
<img class="banner" src="/random-banner">
<h1 class="title">Příspěvky od [{{ ip }}]</h1>
</div>
<hr> <hr>
<form method="post"> <form method="post">
<div class="thread"> <div class="thread">
@ -19,7 +22,7 @@
{% endfor %} {% endfor %}
</div> </div>
<hr> <hr>
{% call static_pagination::static_pagination("/ip-posts/{}"|format(ip), page) %} {% call static_pagination::static_pagination("/ip-posts/{}"|format(ip), page, false) %}
<hr> <hr>
{% call post_actions::post_actions() %} {% call post_actions::post_actions() %}
</form> </form>

Zobrazit soubor

@ -24,7 +24,7 @@
<tr> <tr>
<td class="label">Email</td> <td class="label">Email</td>
<td> <td>
<input name="email" type="text" autocomplete="off"> <input name="email" type="text">
</td> </td>
</tr> </tr>
<tr> <tr>

Zobrazit soubor

@ -1,13 +1,22 @@
{% macro static_pagination(base, current) %} {% macro static_pagination(base, current, chain) %}
<div class="pagination box inline-block"> <div class="pagination box inline-block">
{% if current == 1 %} {% if current == 1 %}
[Předchozí] [Předchozí]
{% else %} {% else %}
[<a href="{{ base }}?page={{ current - 1 }}">Předchozí</a>] {% if chain %}
[<a href="{{ base }}&page={{ current - 1 }}">Předchozí</a>]
{% else %}
[<a href="{{ base }}?page={{ current - 1 }}">Předchozí</a>]
{% endif %}
{% endif %} {% endif %}
&#32; &#32;
[<b><a href="{{ base }}?page={{ current }}">{{ current }}</a></b>]&#32; {% if chain %}
[<a href="{{ base }}?page={{ current + 1 }}">Další</a>] [<b><a href="{{ base }}&page={{ current }}">{{ current }}</a></b>]&#32;
[<a href="{{ base }}&page={{ current + 1 }}">Další</a>]
{% else %}
[<b><a href="{{ base }}?page={{ current }}">{{ current }}</a></b>]&#32;
[<a href="{{ base }}?page={{ current + 1 }}">Další</a>]
{% endif %}
</div> </div>
{% endmacro %} {% endmacro %}

Zobrazit soubor

@ -4,7 +4,10 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<h1 class="title">Novinky</h1> <div class="center">
<img class="banner" src="/random-banner">
<h1 class="title">Novinky</h1>
</div>
{% for newspost in news %} {% for newspost in news %}
<div class="news box"> <div class="news box">
<h2 class="headline"> <h2 class="headline">

Zobrazit soubor

@ -15,6 +15,19 @@
</div> </div>
</div> </div>
<hr> <hr>
<form method="get" action="/search">
<table class="form-table">
<tr>
<td>
<input name="query" type="text">
</td>
<td>
<input class="button" type="submit" value="Hledat">
</td>
</tr>
</table>
</form>
<hr>
<form method="post"> <form method="post">
<div class="center"> <div class="center">
{% for thread in threads %} {% for thread in threads %}

47
templates/search.html Normální soubor
Zobrazit soubor

@ -0,0 +1,47 @@
{% import "./macros/post-actions.html" as post_actions %}
{% import "./macros/post.html" as post %}
{% import "./macros/static-pagination.html" as static_pagination %}
{% extends "base.html" %}
{% block title %}
Vyhledávání ({% if let Some(board) = board_opt %}/{{ board.id }}/{% else %}nadnástěnka{% endif %})
{% endblock %}
{% block content %}
<div class="center">
<img class="banner" src="/random-banner">
<h1 class="title">
Výsledky pro "{{ query }}" (
{% if let Some(board) = board_opt %}
<a href="/boards/{{ board.id }}">/{{ board.id }}/</a>
{% else %}
nadnástěnka
{% endif %}
)
</h1>
</div>
<hr>
<form method="post">
<div class="thread">
{% for post in posts %}
{% if let Some(board) = board_opt %}
{% call post::post(board, post, true) %}
{% else %}
<b>Příspěvek z <a href="/boards/{{ post.board }}">/{{ post.board }}/</a></b>
<br>
{% call post::post(boards[post.board.as_str()], post, true) %}
{% endif %}
<br>
{% endfor %}
</div>
<hr>
{% if let Some(board) = board_opt %}
{% call static_pagination::static_pagination("/search?board={}&query={}"|format(board.id, query|urlencode_strict), page, true) %}
{% else %}
{% call static_pagination::static_pagination("/search?query={}"|format(query|urlencode_strict), page, true) %}
{% endif %}
<hr>
{% call post_actions::post_actions() %}
</form>
{% endblock %}

Zobrazit soubor

@ -33,7 +33,7 @@
</table> </table>
<hr> <hr>
{% endfor %} {% endfor %}
{% call static_pagination::static_pagination("/staff/reports", page) %} {% call static_pagination::static_pagination("/staff/reports", page, false) %}
<hr> <hr>
{% call post_actions::post_actions() %} {% call post_actions::post_actions() %}
</form> </form>