Merge branch 'main' of https://git.nekrofilie.com/sneedmaster/nekrochan
Tento commit je obsažen v:
revize
6b1f4addd5
98
README.md
98
README.md
@ -3,101 +3,3 @@
|
|||||||
100% český imidžbórdový skript
|
100% český imidžbórdový skript
|
||||||
|
|
||||||
> 100% český přestože je kód anglicky...
|
> 100% český přestože je kód anglicky...
|
||||||
|
|
||||||
Brzy dostupný na https://czchan.org/.
|
|
||||||
|
|
||||||
## Tutoriál nebo něco
|
|
||||||
|
|
||||||
Pravděpodobně to běží jenom na Linuxu, ale nikdo na serverech Windows stejně nepoužívá. Tutoriál počítá se systémem Ubuntu a je možné, že je nekompletní.
|
|
||||||
|
|
||||||
### Nainstaluj Rust
|
|
||||||
|
|
||||||
Ne, nejsem transka (zatím).
|
|
||||||
|
|
||||||
```
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nainstaluj ostatní požadavky
|
|
||||||
|
|
||||||
```
|
|
||||||
# Potřebné ke kompilaci
|
|
||||||
sudo apt install binutils build-essential libssl-dev libpq-dev postgresql
|
|
||||||
# Potřebné k funkci
|
|
||||||
sudo apt install imagemagick ffmpeg
|
|
||||||
```
|
|
||||||
|
|
||||||
### Vytvoř databázi
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo adduser nekrochan --system --disabled-login --no-create-home --group
|
|
||||||
sudo passwd nekrochan # Nastavíme heslo pro systémového uživatele
|
|
||||||
sudo -iu postgres psql -c "CREATE USER nekrochan WITH PASSWORD 'password';"
|
|
||||||
sudo -iu postgres psql -c "CREATE DATABASE nekrochan WITH OWNER nekrochan;"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Automatická konfigurace
|
|
||||||
|
|
||||||
```
|
|
||||||
chmod +x ./configure.sh
|
|
||||||
./configure.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nastartuj server
|
|
||||||
|
|
||||||
```
|
|
||||||
cargo run --release
|
|
||||||
```
|
|
||||||
|
|
||||||
### Vytvoř nástěnku
|
|
||||||
|
|
||||||
Po kompilaci by se měl spustit server na https://localhost:7000/. Stránka ti pravděpodobně řekne, že ještě nebyla inicializována domovní stránka. Je potřeba vytvořit nástěnku. Nejdříve je ale potřeba vytvořit administátorský účet.
|
|
||||||
|
|
||||||
Heslo v příkladu je "password", můžeš použít příklad a heslo změnit potom v administrátorském rozhraní.
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo -iu postgres psql -d nekrochan -c "INSERT INTO accounts (username, password, owner, permissions) VALUES ('admin', '$2y$10$jHl27pbYNvgpQxksmF.N/O0IHrfFBDY1Tg/qBX/UwrMa3j7owkiQm', true, '131072'::jsonb);"
|
|
||||||
```
|
|
||||||
|
|
||||||
Po příkazu budeš muset restartovat server, aby se změna projevila v mezipaměti.
|
|
||||||
|
|
||||||
Nástěnka lze vytvořit po přihlášení na https://localhost:7000/login na stránce https://localhost:7000/staff/boards
|
|
||||||
|
|
||||||
### Automatický start
|
|
||||||
|
|
||||||
Nejprve vytvoříme složku pro nekrochan a zkopírujeme tam potřebné soubory.
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo mkdir -p /srv/nekrochan
|
|
||||||
sudo chown nekrochan:nekrochan /srv/nekrochan
|
|
||||||
sudo cp -r ./target/release/nekrochan Nekrochan.toml ./pages ./static ./uploads /srv/nekrochan/
|
|
||||||
```
|
|
||||||
|
|
||||||
Nyní vytvoříme skript pro systemd, aby server automaticky nastartovat po zapnutí počítače. Uložíme ho jako `/etc/systemd/system/nekrochan.service`.
|
|
||||||
|
|
||||||
```
|
|
||||||
[Unit]
|
|
||||||
Description=Nekrochan
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
User=nekrochan
|
|
||||||
ExecStart=/srv/nekrochan/nekrochan
|
|
||||||
WorkingDirectory=/srv/nekrochan
|
|
||||||
Environment=RUST_LOG="info"
|
|
||||||
Restart=on-failure
|
|
||||||
|
|
||||||
ProtectSystem=yes
|
|
||||||
PrivateTmp=true
|
|
||||||
MemoryDenyWriteExecute=true
|
|
||||||
NoNewPrivileges=true
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
```
|
|
||||||
|
|
||||||
### Další konfigurace
|
|
||||||
|
|
||||||
Většinu možností najdeš v souboru `Nekrochan.toml`. Vlastní stránky (např. pravidla, faq apod.) můžeš nahrávat do složky `pages`.
|
|
||||||
|
|
||||||
Také budeš pravděpodobně chtít nastavit reverzní proxy, např. NGINX. IP adresu posílej serveru v hlavičce `X-Forwarded-For` a kód země (potřebný pro vlajky) v hlavičce `X-Country-Code`.
|
|
||||||
|
2
build.rs
2
build.rs
@ -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(" ", " ");
|
||||||
|
|
||||||
File::create(path)?.write_all(minified.as_bytes())?;
|
File::create(path)?.write_all(minified.as_bytes())?;
|
||||||
}
|
}
|
||||||
|
@ -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"#,
|
||||||
)
|
)
|
||||||
|
@ -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) ORDER BY created DESC 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) ORDER BY created DESC 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 *",
|
||||||
|
@ -84,6 +84,8 @@ pub enum NekrochanError {
|
|||||||
PostNameFormatError,
|
PostNameFormatError,
|
||||||
#[error("Příspěvek /{}/{} neexistuje.", .0, .1)]
|
#[error("Příspěvek /{}/{} neexistuje.", .0, .1)]
|
||||||
PostNotFound(String, i64),
|
PostNotFound(String, i64),
|
||||||
|
#[error("Hledaný termín musí mít 1-256 znaků.")]
|
||||||
|
QueryFormatError,
|
||||||
#[error("Vlákno dosáhlo limitu odpovědí.")]
|
#[error("Vlákno dosáhlo limitu odpovědí.")]
|
||||||
ReplyLimitError,
|
ReplyLimitError,
|
||||||
#[error("Hlášení můsí mít 1-200 znaků.")]
|
#[error("Hlášení můsí mít 1-200 znaků.")]
|
||||||
@ -255,6 +257,7 @@ impl ResponseError for NekrochanError {
|
|||||||
NekrochanError::PasswordFormatError => StatusCode::BAD_REQUEST,
|
NekrochanError::PasswordFormatError => StatusCode::BAD_REQUEST,
|
||||||
NekrochanError::PostNameFormatError => StatusCode::BAD_REQUEST,
|
NekrochanError::PostNameFormatError => StatusCode::BAD_REQUEST,
|
||||||
NekrochanError::PostNotFound(_, _) => StatusCode::NOT_FOUND,
|
NekrochanError::PostNotFound(_, _) => StatusCode::NOT_FOUND,
|
||||||
|
NekrochanError::QueryFormatError => StatusCode::BAD_REQUEST,
|
||||||
NekrochanError::ReplyLimitError => StatusCode::FORBIDDEN,
|
NekrochanError::ReplyLimitError => StatusCode::FORBIDDEN,
|
||||||
NekrochanError::ReportFormatError => StatusCode::BAD_REQUEST,
|
NekrochanError::ReportFormatError => StatusCode::BAD_REQUEST,
|
||||||
NekrochanError::RequiredCaptchaError => StatusCode::UNAUTHORIZED,
|
NekrochanError::RequiredCaptchaError => StatusCode::UNAUTHORIZED,
|
||||||
|
@ -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)
|
||||||
|
@ -25,12 +25,14 @@ use crate::{
|
|||||||
pub struct PostForm {
|
pub struct PostForm {
|
||||||
pub board: Text<String>,
|
pub board: Text<String>,
|
||||||
pub thread: Option<Text<i64>>,
|
pub thread: Option<Text<i64>>,
|
||||||
|
#[multipart(rename = "post_name")]
|
||||||
pub name: Text<String>,
|
pub name: Text<String>,
|
||||||
pub email: Text<String>,
|
pub email: Text<String>,
|
||||||
pub content: Text<String>,
|
pub content: Text<String>,
|
||||||
#[multipart(rename = "files[]")]
|
#[multipart(rename = "files[]")]
|
||||||
pub files: Vec<TempFile>,
|
pub files: Vec<TempFile>,
|
||||||
pub spoiler_files: Option<Text<String>>,
|
pub spoiler_files: Option<Text<String>>,
|
||||||
|
#[multipart(rename = "post_password")]
|
||||||
pub password: Text<String>,
|
pub password: Text<String>,
|
||||||
pub captcha_id: Option<Text<String>>,
|
pub captcha_id: Option<Text<String>>,
|
||||||
pub captcha_solution: Option<Text<String>>,
|
pub captcha_solution: Option<Text<String>>,
|
||||||
@ -260,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()))
|
||||||
|
@ -23,6 +23,7 @@ pub struct UserPostActionsForm {
|
|||||||
pub remove_posts: Option<String>,
|
pub remove_posts: Option<String>,
|
||||||
pub remove_files: Option<String>,
|
pub remove_files: Option<String>,
|
||||||
pub toggle_spoiler: Option<String>,
|
pub toggle_spoiler: Option<String>,
|
||||||
|
#[serde(rename = "post_password")]
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,6 @@ pub async fn login_get(ctx: Data<Ctx>, req: HttpRequest) -> Result<HttpResponse,
|
|||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct LogInForm {
|
pub struct LogInForm {
|
||||||
username: String,
|
username: String,
|
||||||
#[serde(rename = "account_password")]
|
|
||||||
password: String,
|
password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
88
src/web/search.rs
Normální soubor
88
src/web/search.rs
Normální soubor
@ -0,0 +1,88 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
if query.is_empty() || query.len() > 256 {
|
||||||
|
return Err(NekrochanError::QueryFormatError);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
@ -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)
|
||||||
|
binární
static/favicon.ico
binární
static/favicon.ico
Binární soubor nebyl zobrazen.
Před Šířka: | Výška: | Velikost: 766 B Za Šířka: | Výška: | Velikost: 4.2 KiB |
@ -1,50 +1,52 @@
|
|||||||
$(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();
|
||||||
set_cookie("password", password);
|
set_cookie("password", password);
|
||||||
}
|
}
|
||||||
|
|
||||||
$('input[name="name"]').attr("value", name);
|
$('input[name="post_name"]').attr("value", name);
|
||||||
$('input[name="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=/`;
|
|
||||||
}
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,14 +9,6 @@
|
|||||||
<meta name="description" content="{{ tcx.cfg.site.description }}">
|
<meta name="description" content="{{ tcx.cfg.site.description }}">
|
||||||
<link rel="stylesheet" href="/static/themes/{% block theme %}{{ tcx.cfg.site.theme }}{% endblock %}">
|
<link rel="stylesheet" href="/static/themes/{% block theme %}{{ tcx.cfg.site.theme }}{% endblock %}">
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
<script src="/static/js/jquery.min.js"></script>
|
|
||||||
<!-- UX scripts -->
|
|
||||||
<script src="/static/js/autofill.js"></script>
|
|
||||||
<script src="/static/js/expand.js"></script>
|
|
||||||
<script src="/static/js/hover.js"></script>
|
|
||||||
<script src="/static/js/quote.js"></script>
|
|
||||||
<script src="/static/js/time.js"></script>
|
|
||||||
{% block scripts %}{% endblock %}
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="top"></div>
|
<div id="top"></div>
|
||||||
@ -39,6 +31,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>
|
||||||
@ -57,5 +52,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="bottom"></div>
|
<div id="bottom"></div>
|
||||||
|
<script src="/static/js/jquery.min.js" defer=""></script>
|
||||||
|
<script src="/static/js/autofill.js" defer=""></script>
|
||||||
|
<script src="/static/js/expand.js" defer=""></script>
|
||||||
|
<script src="/static/js/hover.js" defer=""></script>
|
||||||
|
<script src="/static/js/quote.js" defer=""></script>
|
||||||
|
<script src="/static/js/time.js" defer=""></script>
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -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 %}
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
{% block theme %}{{ board.config.0.board_theme }}{% endblock %}
|
{% block theme %}{{ board.config.0.board_theme }}{% endblock %}
|
||||||
{% block title %}/{{ board.id }}/ - {{ board.name }}{% endblock %}
|
{% block title %}/{{ board.id }}/ - {{ board.name }}{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="/static/js/captcha.js"></script>
|
<script src="/static/js/captcha.js" defer=""></script>
|
||||||
<script src="/static/js/post-form.js"></script>
|
<script src="/static/js/post-form.js" defer=""></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
@ -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>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="label">Heslo</td>
|
<td class="label">Heslo</td>
|
||||||
<td><input name="account_password" type="password" required=""></td>
|
<td><input name="password" type="password" required=""></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2"><input class="button" type="submit" value="Přihlásit se"></td>
|
<td colspan="2"><input class="button" type="submit" value="Přihlásit se"></td>
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="label">Heslo</td>
|
<td class="label">Heslo</td>
|
||||||
<td><input name="password" type="password"></td>
|
<td><input name="post_password" type="password"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{% macro post_form(board, reply, reply_to) %}
|
{% macro post_form(board, reply, reply_to) %}
|
||||||
<form id="post-form" method="post" enctype="multipart/form-data" action="/actions/create-post" data-visible="false">
|
<form id="post-form" method="post" enctype="multipart/form-data" action="/actions/create-post" autocomplete="off" data-visible="false">
|
||||||
<input name="board" type="hidden" value="{{ board.id }}">
|
<input name="board" type="hidden" value="{{ board.id }}">
|
||||||
{% if reply %}
|
{% if reply %}
|
||||||
<input name="thread" type="hidden" value="{{ reply_to }}">
|
<input name="thread" type="hidden" value="{{ reply_to }}">
|
||||||
@ -18,13 +18,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="label">Jméno</td>
|
<td class="label">Jméno</td>
|
||||||
<td>
|
<td>
|
||||||
<input name="name" type="text" placeholder="{{ board.config.0.anon_name }}">
|
<input name="post_name" type="text" placeholder="{{ board.config.0.anon_name }}">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<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>
|
||||||
@ -91,7 +91,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="label">Heslo</td>
|
<td class="label">Heslo</td>
|
||||||
<td><input name="password" type="password" required=""></td>
|
<td><input name="post_password" type="password" required=""></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
{% if reply %}
|
{% if reply %}
|
||||||
|
@ -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 %}
|
||||||
 
|
 
|
||||||
|
|
||||||
[<b><a href="{{ base }}?page={{ current }}">{{ current }}</a></b>] 
|
{% if chain %}
|
||||||
[<a href="{{ base }}?page={{ current + 1 }}">Další</a>]
|
[<b><a href="{{ base }}&page={{ current }}">{{ current }}</a></b>] 
|
||||||
|
[<a href="{{ base }}&page={{ current + 1 }}">Další</a>]
|
||||||
|
{% else %}
|
||||||
|
[<b><a href="{{ base }}?page={{ current }}">{{ current }}</a></b>] 
|
||||||
|
[<a href="{{ base }}?page={{ current + 1 }}">Další</a>]
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
@ -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">
|
||||||
|
@ -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
47
templates/search.html
Normální 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 %}
|
@ -45,7 +45,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="label">Heslo</td>
|
<td class="label">Heslo</td>
|
||||||
<td><input name="account_password" type="password" required=""></td>
|
<td><input name="password" type="password" required=""></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2"><input class="button" type="submit" value="Vytvořit účet"></td>
|
<td colspan="2"><input class="button" type="submit" value="Vytvořit účet"></td>
|
||||||
|
@ -35,11 +35,7 @@
|
|||||||
|
|
||||||
{% if tcx.perms.owner() %}
|
{% if tcx.perms.owner() %}
|
||||||
<input class="button" type="submit" formaction="/staff/actions/remove-boards" value="Odstranit vybrané" formnovalidate>
|
<input class="button" type="submit" formaction="/staff/actions/remove-boards" value="Odstranit vybrané" formnovalidate>
|
||||||
{% endif %}
|
<hr>
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
{% if tcx.perms.owner() || tcx.perms.board_config() %}
|
|
||||||
<table class="form-table">
|
<table class="form-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="label">Jméno</td>
|
<td class="label">Jméno</td>
|
||||||
|
@ -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>
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
{% block title %}/{{ board.id }}/ - {{ thread.content_nomarkup|inline_post }}{% endblock %}
|
{% block title %}/{{ board.id }}/ - {{ thread.content_nomarkup|inline_post }}{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="/static/js/captcha.js"></script>
|
<script src="/static/js/captcha.js" defer=""></script>
|
||||||
<script src="/static/js/live.js"></script>
|
<script src="/static/js/live.js" defer=""></script>
|
||||||
<script src="/static/js/post-form.js"></script>
|
<script src="/static/js/post-form.js" defer=""></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
Načítá se…
Odkázat v novém úkolu
Zablokovat Uživatele