novinky + ostatní menší změny
Tento commit je obsažen v:
rodič
2432fcba66
revize
01e0c8aeee
76
Cargo.lock
vygenerováno
76
Cargo.lock
vygenerováno
@ -586,6 +586,28 @@ dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91d7b79e99bfaa0d47da0687c43aa3b7381938a62ad3a6498599039321f660b7"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz-build",
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz-build"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f"
|
||||
dependencies = [
|
||||
"parse-zoneinfo",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.2.5"
|
||||
@ -1786,6 +1808,7 @@ dependencies = [
|
||||
"askama",
|
||||
"captcha",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"dotenv",
|
||||
"encoding",
|
||||
"enumflags2",
|
||||
@ -1953,6 +1976,15 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "944553dd59c802559559161f9816429058b869003836120e262e8caec061b7ae"
|
||||
|
||||
[[package]]
|
||||
name = "parse-zoneinfo"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.13"
|
||||
@ -1984,6 +2016,44 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.2"
|
||||
@ -2487,6 +2557,12 @@ dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.8"
|
||||
|
@ -11,6 +11,7 @@ askama = "0.12.0"
|
||||
anyhow = "1.0.71"
|
||||
captcha = "0.0.9"
|
||||
chrono = { version = "0.4.31", features = ["serde", "unstable-locales"] }
|
||||
chrono-tz = "0.8.5"
|
||||
dotenv = "0.15.0"
|
||||
enumflags2 = "0.7.7"
|
||||
encoding = "0.2.33"
|
||||
|
@ -38,7 +38,7 @@ impl Banner {
|
||||
Ok(banners)
|
||||
}
|
||||
|
||||
pub async fn read_random(ctx: &Ctx) -> Result<Option<Self>, NekrochanError> {
|
||||
pub async fn read_random(ctx: &Ctx) -> Result<Option<Self>, NekrochanError> {
|
||||
let banner: Option<String> = ctx.cache().zrandmember("banners", None).await?;
|
||||
|
||||
let banner = match banner {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use sqlx::query_as;
|
||||
|
||||
use crate::{error::NekrochanError, ctx::Ctx};
|
||||
use super::models::LocalStats;
|
||||
use crate::{ctx::Ctx, error::NekrochanError};
|
||||
|
||||
impl LocalStats {
|
||||
pub async fn read(ctx: &Ctx) -> Result<Self, NekrochanError> {
|
||||
@ -27,4 +27,4 @@ impl LocalStats {
|
||||
|
||||
Ok(stats)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,11 +8,13 @@ impl NewsPost {
|
||||
ctx: &Ctx,
|
||||
title: String,
|
||||
content: String,
|
||||
content_nomarkup: String,
|
||||
author: String,
|
||||
) -> Result<Self, NekrochanError> {
|
||||
let newspost = query_as("INSERT INTO news (title, content, author) VALUES ($1, $2, $3)")
|
||||
let newspost = query_as("INSERT INTO news (title, content, content_nomarkup, author) VALUES ($1, $2, $3, $4) RETURNING *")
|
||||
.bind(title)
|
||||
.bind(content)
|
||||
.bind(content_nomarkup)
|
||||
.bind(author)
|
||||
.fetch_one(ctx.db())
|
||||
.await?;
|
||||
@ -48,12 +50,12 @@ impl NewsPost {
|
||||
pub async fn update(
|
||||
&self,
|
||||
ctx: &Ctx,
|
||||
title: String,
|
||||
content: String,
|
||||
content_nomarkup: String,
|
||||
) -> Result<(), NekrochanError> {
|
||||
query("UPDATE news SET title = $1, content = $2 WHERE id = $3")
|
||||
.bind(title)
|
||||
query("UPDATE news SET content = $1, content_nomarkup = $2 WHERE id = $3")
|
||||
.bind(content)
|
||||
.bind(content_nomarkup)
|
||||
.bind(self.id)
|
||||
.execute(ctx.db())
|
||||
.await?;
|
||||
|
15
src/error.rs
15
src/error.rs
@ -8,8 +8,12 @@ pub enum NekrochanError {
|
||||
AccountNotFound(String),
|
||||
#[error("Tento ban už byl odvolán.")]
|
||||
AlreadyAppealedError,
|
||||
#[error("Odvolání můsí mít 1-1000 znaků.")]
|
||||
BanAppealFormatError,
|
||||
#[error("Žádný takový ban pro tuto IP adresu neexistuje.")]
|
||||
BanNotFound,
|
||||
#[error("Důvod banu musí mít 1-200 znaků.")]
|
||||
BanReasonFormatError,
|
||||
#[error("Nástěnka /{}/ je uzamčená.", .0)]
|
||||
BoardLockError(String),
|
||||
#[error("Jméno nástěnky musí mít 1-32 znaků.")]
|
||||
@ -52,6 +56,10 @@ pub enum NekrochanError {
|
||||
InvalidAuthError,
|
||||
#[error("Neplatná strana.")]
|
||||
InvalidPageError,
|
||||
#[error("Obsah musí mít 1-20000 znaků.")]
|
||||
NewsContentFormatError,
|
||||
#[error("Titulek musí mít 1-100 znaků.")]
|
||||
NewsTitleFormatError,
|
||||
#[error("Příspěvek musí mít obsah.")]
|
||||
NoContentError,
|
||||
#[error("Příspěvek musí mít soubor.")]
|
||||
@ -72,6 +80,8 @@ pub enum NekrochanError {
|
||||
PostNotFound(String, i64),
|
||||
#[error("Vlákno dosáhlo limitu odpovědí.")]
|
||||
ReplyLimitError,
|
||||
#[error("Hlášení můsí mít 1-200 znaků.")]
|
||||
ReportFormatError,
|
||||
#[error("Nelze vytvořit odpověď na odpověď.")]
|
||||
ReplyReplyError,
|
||||
#[error("Na této nástěnce se musí vyplnit CAPTCHA.")]
|
||||
@ -204,7 +214,9 @@ impl ResponseError for NekrochanError {
|
||||
match self {
|
||||
NekrochanError::AccountNotFound(_) => StatusCode::NOT_FOUND,
|
||||
NekrochanError::AlreadyAppealedError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::BanAppealFormatError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::BanNotFound => StatusCode::NOT_FOUND,
|
||||
NekrochanError::BanReasonFormatError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::BoardLockError(_) => StatusCode::FORBIDDEN,
|
||||
NekrochanError::BoardNameFormatError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::BoardNotFound(_) => StatusCode::NOT_FOUND,
|
||||
@ -226,6 +238,8 @@ impl ResponseError for NekrochanError {
|
||||
NekrochanError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
NekrochanError::InvalidAuthError => StatusCode::UNAUTHORIZED,
|
||||
NekrochanError::InvalidPageError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::NewsContentFormatError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::NewsTitleFormatError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::NoContentError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::NoFileError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::NoPostsError => StatusCode::BAD_REQUEST,
|
||||
@ -237,6 +251,7 @@ impl ResponseError for NekrochanError {
|
||||
NekrochanError::PostNotFound(_, _) => StatusCode::NOT_FOUND,
|
||||
NekrochanError::ReplyLimitError => StatusCode::FORBIDDEN,
|
||||
NekrochanError::ReplyReplyError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::ReportFormatError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::RequiredCaptchaError => StatusCode::UNAUTHORIZED,
|
||||
NekrochanError::SolvedCaptchaError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::ThreadLockError => StatusCode::FORBIDDEN,
|
||||
|
@ -1,4 +1,5 @@
|
||||
use chrono::{DateTime, Locale, Utc};
|
||||
use chrono::{DateTime, Locale, TimeZone, Utc};
|
||||
use chrono_tz::Europe::Prague;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::{Captures, Regex};
|
||||
use std::{collections::HashSet, fmt::Display};
|
||||
@ -66,7 +67,9 @@ pub fn czech_humantime(time: &DateTime<Utc>) -> askama::Result<String> {
|
||||
Ok(time)
|
||||
}
|
||||
|
||||
pub fn czech_datetime(time: &DateTime<Utc>) -> askama::Result<String> {
|
||||
pub fn czech_datetime(utc: &DateTime<Utc>) -> askama::Result<String> {
|
||||
let time = Prague.from_utc_datetime(&utc.naive_utc());
|
||||
|
||||
let time = time
|
||||
.format_localized("%d.%m.%Y (%a) %H:%M:%S", Locale::cs_CZ)
|
||||
.to_string();
|
||||
|
@ -13,11 +13,11 @@ pub mod ctx;
|
||||
pub mod db;
|
||||
pub mod error;
|
||||
pub mod files;
|
||||
pub mod schedule;
|
||||
pub mod filters;
|
||||
pub mod markup;
|
||||
pub mod perms;
|
||||
pub mod qsform;
|
||||
pub mod schedule;
|
||||
pub mod trip;
|
||||
pub mod web;
|
||||
|
||||
|
@ -67,6 +67,7 @@ async fn run() -> Result<(), Error> {
|
||||
.service(web::login::login_get)
|
||||
.service(web::login::login_post)
|
||||
.service(web::logout::logout)
|
||||
.service(web::news::news)
|
||||
.service(web::overboard::overboard)
|
||||
.service(web::overboard_catalog::overboard_catalog)
|
||||
.service(web::thread::thread)
|
||||
@ -82,17 +83,22 @@ async fn run() -> Result<(), Error> {
|
||||
.service(web::staff::banners::banners)
|
||||
.service(web::staff::board_config::board_config)
|
||||
.service(web::staff::boards::boards)
|
||||
.service(web::staff::edit_news::edit_news)
|
||||
.service(web::staff::news::news)
|
||||
.service(web::staff::permissions::permissions)
|
||||
.service(web::staff::reports::reports)
|
||||
.service(web::staff::actions::add_banners::add_banners)
|
||||
.service(web::staff::actions::change_password::change_password)
|
||||
.service(web::staff::actions::create_account::create_account)
|
||||
.service(web::staff::actions::create_board::create_board)
|
||||
.service(web::staff::actions::create_news::create_news)
|
||||
.service(web::staff::actions::delete_account::delete_account)
|
||||
.service(web::staff::actions::edit_news::edit_news)
|
||||
.service(web::staff::actions::remove_accounts::remove_accounts)
|
||||
.service(web::staff::actions::remove_banners::remove_banners)
|
||||
.service(web::staff::actions::remove_bans::remove_bans)
|
||||
.service(web::staff::actions::remove_boards::remove_boards)
|
||||
.service(web::staff::actions::remove_news::remove_news)
|
||||
.service(web::staff::actions::transfer_ownership::transfer_ownership)
|
||||
.service(web::staff::actions::update_board_config::update_board_config)
|
||||
.service(web::staff::actions::update_boards::update_boards)
|
||||
|
@ -78,7 +78,7 @@ pub fn parse_name(
|
||||
Some(capcode) => {
|
||||
let capcode: String = capcode.as_str().trim().into();
|
||||
|
||||
if capcode.is_empty() {
|
||||
if capcode.is_empty() || !(perms.owner() || perms.custom_capcodes()) {
|
||||
Some(capcode_fallback(perms.owner()))
|
||||
} else {
|
||||
if capcode.len() > 32 {
|
||||
@ -106,38 +106,44 @@ fn capcode_fallback(owner: bool) -> String {
|
||||
|
||||
pub async fn markup(
|
||||
ctx: &Ctx,
|
||||
board: &String,
|
||||
board: Option<String>,
|
||||
op: Option<i64>,
|
||||
text: &str,
|
||||
) -> Result<String, NekrochanError> {
|
||||
let text = escape_html(&text);
|
||||
let text = escape_html(text);
|
||||
|
||||
let quoted_posts = get_quoted_posts(ctx, board, &text).await?;
|
||||
let text = if let Some(board) = board {
|
||||
let quoted_posts = get_quoted_posts(ctx, &board, &text).await?;
|
||||
|
||||
let text = QUOTE_REGEX.replace_all(&text, |captures: &Captures| {
|
||||
let id_raw = &captures[1];
|
||||
let text = QUOTE_REGEX.replace_all(&text, |captures: &Captures| {
|
||||
let id_raw = &captures[1];
|
||||
|
||||
let Ok(id) = id_raw.parse() else {
|
||||
let Ok(id) = id_raw.parse() else {
|
||||
return format!("<span class=\"dead-quote\">>>{id_raw}</span>");
|
||||
};
|
||||
|
||||
let post = quoted_posts.get(&id);
|
||||
let post = quoted_posts.get(&id);
|
||||
|
||||
if let Some(post) = post {
|
||||
format!(
|
||||
"<a class=\"quote\" href=\"{}\">>>{}</a>{}",
|
||||
post.post_url(),
|
||||
post.id,
|
||||
if op == Some(post.id) {
|
||||
" <span class=\"small\">(OP)</span>"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)
|
||||
} else {
|
||||
format!("<span class=\"dead-quote\">>>{id}</span>")
|
||||
}
|
||||
});
|
||||
if let Some(post) = post {
|
||||
format!(
|
||||
"<a class=\"quote\" href=\"{}\">>>{}</a>{}",
|
||||
post.post_url(),
|
||||
post.id,
|
||||
if op == Some(post.id) {
|
||||
" <span class=\"small\">(OP)</span>"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)
|
||||
} else {
|
||||
format!("<span class=\"dead-quote\">>>{id}</span>")
|
||||
}
|
||||
});
|
||||
|
||||
text.to_string()
|
||||
} else {
|
||||
text
|
||||
};
|
||||
|
||||
let text = GREENTEXT_REGEX.replace_all(&text, "<span class=\"greentext\">>$1</span>");
|
||||
let text = ORANGETEXT_REGEX.replace_all(&text, "<span class=\"orangetext\"><$1</span>");
|
||||
|
10
src/perms.rs
10
src/perms.rs
@ -7,6 +7,7 @@ pub enum Permissions {
|
||||
EditPosts,
|
||||
ManagePosts,
|
||||
Capcodes,
|
||||
CustomCapcodes,
|
||||
StaffLog,
|
||||
Reports,
|
||||
Bans,
|
||||
@ -17,6 +18,7 @@ pub enum Permissions {
|
||||
BypassBoardLock,
|
||||
BypassThreadLock,
|
||||
BypassCaptcha,
|
||||
BypassAntispam,
|
||||
}
|
||||
|
||||
pub struct PermissionWrapper(BitFlags<Permissions>, bool);
|
||||
@ -48,6 +50,10 @@ impl PermissionWrapper {
|
||||
self.0.contains(Permissions::Capcodes)
|
||||
}
|
||||
|
||||
pub fn custom_capcodes(&self) -> bool {
|
||||
self.0.contains(Permissions::CustomCapcodes)
|
||||
}
|
||||
|
||||
pub fn staff_log(&self) -> bool {
|
||||
self.0.contains(Permissions::StaffLog)
|
||||
}
|
||||
@ -87,4 +93,8 @@ impl PermissionWrapper {
|
||||
pub fn bypass_captcha(&self) -> bool {
|
||||
self.0.contains(Permissions::BypassCaptcha)
|
||||
}
|
||||
|
||||
pub fn bypass_antispam(&self) -> bool {
|
||||
self.0.contains(Permissions::BypassAntispam)
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,12 @@ pub async fn appeal_ban(
|
||||
return Err(NekrochanError::UnappealableError);
|
||||
}
|
||||
|
||||
let appeal = form.appeal.trim().into();
|
||||
let appeal: String = form.appeal.trim().into();
|
||||
|
||||
if appeal.is_empty() || appeal.len() > 1000 {
|
||||
return Err(NekrochanError::BanAppealFormatError);
|
||||
}
|
||||
|
||||
ban.update_appeal(&ctx, appeal).await?;
|
||||
|
||||
template_response(&ActionTemplate {
|
||||
|
@ -168,7 +168,7 @@ pub async fn create_post(
|
||||
|
||||
let content = markup(
|
||||
&ctx,
|
||||
&board.id,
|
||||
Some(board.id.clone()),
|
||||
thread.as_ref().map(|t| t.id),
|
||||
&content_nomarkup,
|
||||
)
|
||||
|
@ -38,7 +38,13 @@ pub async fn edit_posts(
|
||||
for (key, content_nomarkup) in edits {
|
||||
let post = &posts[&key];
|
||||
let content_nomarkup = content_nomarkup.trim();
|
||||
let content = markup(&ctx, &post.board, post.thread, content_nomarkup).await?;
|
||||
let content = markup(
|
||||
&ctx,
|
||||
Some(post.board.clone()),
|
||||
post.thread,
|
||||
content_nomarkup,
|
||||
)
|
||||
.await?;
|
||||
|
||||
post.update_content(&ctx, content, content_nomarkup.into())
|
||||
.await?;
|
||||
|
@ -46,6 +46,10 @@ pub async fn report_posts(
|
||||
|
||||
let reason = form.report_reason.trim();
|
||||
|
||||
if reason.is_empty() || reason.len() > 200 {
|
||||
return Err(NekrochanError::ReportFormatError);
|
||||
}
|
||||
|
||||
for post in &posts {
|
||||
let board = &boards[&post.board];
|
||||
|
||||
|
@ -139,7 +139,12 @@ pub async fn staff_post_actions(
|
||||
};
|
||||
|
||||
let ip_range = IpNetwork::new(post.ip, prefix)?;
|
||||
let reason = ban_reason.trim().into();
|
||||
let reason: String = ban_reason.trim().into();
|
||||
|
||||
if reason.is_empty() || reason.len() > 200 {
|
||||
return Err(NekrochanError::BanReasonFormatError);
|
||||
}
|
||||
|
||||
let appealable = form.unappealable_ban.is_none();
|
||||
|
||||
let expires = if ban_duration == 0 {
|
||||
|
@ -12,6 +12,7 @@ use crate::{
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EditPostsForm {
|
||||
#[serde(default)]
|
||||
pub posts: Vec<String>,
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,12 @@ pub async fn index(ctx: Data<Ctx>, req: HttpRequest) -> Result<HttpResponse, Nek
|
||||
let boards = Board::read_all(&ctx).await?;
|
||||
let stats = LocalStats::read(&ctx).await?;
|
||||
|
||||
let template = IndexTemplate { tcx, boards, stats, news };
|
||||
let template = IndexTemplate {
|
||||
tcx,
|
||||
boards,
|
||||
stats,
|
||||
news,
|
||||
};
|
||||
|
||||
template_response(&template)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ pub mod edit_posts;
|
||||
pub mod index;
|
||||
pub mod login;
|
||||
pub mod logout;
|
||||
pub mod news;
|
||||
pub mod overboard;
|
||||
pub mod overboard_catalog;
|
||||
pub mod staff;
|
||||
|
21
src/web/news.rs
Normální soubor
21
src/web/news.rs
Normální soubor
@ -0,0 +1,21 @@
|
||||
use actix_web::{get, web::Data, HttpRequest, HttpResponse};
|
||||
use askama::Template;
|
||||
|
||||
use super::{tcx::TemplateCtx, template_response};
|
||||
use crate::{ctx::Ctx, db::models::NewsPost, error::NekrochanError, filters};
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "news.html")]
|
||||
struct NewsTemplate {
|
||||
tcx: TemplateCtx,
|
||||
news: Vec<NewsPost>,
|
||||
}
|
||||
|
||||
#[get("/news")]
|
||||
pub async fn news(ctx: Data<Ctx>, req: HttpRequest) -> Result<HttpResponse, NekrochanError> {
|
||||
let tcx = TemplateCtx::new(&ctx, &req).await?;
|
||||
let news = NewsPost::read_all(&ctx).await?;
|
||||
let template = NewsTemplate { tcx, news };
|
||||
|
||||
template_response(&template)
|
||||
}
|
48
src/web/staff/actions/create_news.rs
Normální soubor
48
src/web/staff/actions/create_news.rs
Normální soubor
@ -0,0 +1,48 @@
|
||||
use actix_web::{post, web::Data, HttpRequest, HttpResponse};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
ctx::Ctx, db::models::NewsPost, error::NekrochanError, markup::markup, qsform::QsForm,
|
||||
web::tcx::account_from_auth,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateNewsForm {
|
||||
title: String,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[post("/staff/actions/create-news")]
|
||||
pub async fn create_news(
|
||||
ctx: Data<Ctx>,
|
||||
req: HttpRequest,
|
||||
QsForm(form): QsForm<CreateNewsForm>,
|
||||
) -> Result<HttpResponse, NekrochanError> {
|
||||
let account = account_from_auth(&ctx, &req).await?;
|
||||
|
||||
if !(account.perms().owner() || account.perms().news()) {
|
||||
return Err(NekrochanError::InsufficientPermissionError);
|
||||
}
|
||||
|
||||
let title = form.title.trim().to_owned();
|
||||
let content = form.content.trim().to_owned();
|
||||
|
||||
if title.is_empty() || title.len() > 100 {
|
||||
return Err(NekrochanError::NewsTitleFormatError);
|
||||
}
|
||||
|
||||
if content.is_empty() || content.len() > 10000 {
|
||||
return Err(NekrochanError::NewsContentFormatError);
|
||||
}
|
||||
|
||||
let content_nomarkup = content;
|
||||
let content = markup(&ctx, None, None, &content_nomarkup).await?;
|
||||
|
||||
NewsPost::create(&ctx, title, content, content_nomarkup, account.username).await?;
|
||||
|
||||
let res = HttpResponse::SeeOther()
|
||||
.append_header(("Location", "/staff/news"))
|
||||
.finish();
|
||||
|
||||
Ok(res)
|
||||
}
|
70
src/web/staff/actions/edit_news.rs
Normální soubor
70
src/web/staff/actions/edit_news.rs
Normální soubor
@ -0,0 +1,70 @@
|
||||
use actix_web::{post, web::Data, HttpRequest, HttpResponse};
|
||||
use std::{collections::HashMap, fmt::Write};
|
||||
|
||||
use crate::{
|
||||
ctx::Ctx,
|
||||
db::models::NewsPost,
|
||||
error::NekrochanError,
|
||||
markup::markup,
|
||||
qsform::QsForm,
|
||||
web::{actions::ActionTemplate, tcx::TemplateCtx, template_response},
|
||||
};
|
||||
|
||||
#[post("/staff/actions/edit-news")]
|
||||
pub async fn edit_news(
|
||||
ctx: Data<Ctx>,
|
||||
req: HttpRequest,
|
||||
QsForm(edits): QsForm<HashMap<i32, String>>,
|
||||
) -> Result<HttpResponse, NekrochanError> {
|
||||
let tcx = TemplateCtx::new(&ctx, &req).await?;
|
||||
|
||||
if !(tcx.perms.owner() || tcx.perms.news()) {
|
||||
return Err(NekrochanError::InsufficientPermissionError);
|
||||
}
|
||||
|
||||
let mut news = Vec::new();
|
||||
|
||||
for id in edits.keys() {
|
||||
if let Some(newspost) = NewsPost::read(&ctx, *id).await? {
|
||||
news.push(newspost);
|
||||
}
|
||||
}
|
||||
|
||||
let news = news
|
||||
.into_iter()
|
||||
.map(|newspost| (newspost.id, newspost))
|
||||
.collect::<HashMap<i32, NewsPost>>();
|
||||
|
||||
let mut response = String::new();
|
||||
let mut news_edited = 0;
|
||||
|
||||
for (id, content_nomarkup) in edits {
|
||||
let newspost = &news[&id];
|
||||
|
||||
if !tcx.perms.owner() && tcx.account != Some(newspost.author.clone()) {
|
||||
writeln!(
|
||||
&mut response,
|
||||
"[Chyba] pouze vlastník nebo autor může upravit novinky."
|
||||
)
|
||||
.ok();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let content_nomarkup = content_nomarkup.trim();
|
||||
let content = markup(&ctx, None, None, content_nomarkup).await?;
|
||||
|
||||
newspost
|
||||
.update(&ctx, content, content_nomarkup.into())
|
||||
.await?;
|
||||
news_edited += 1;
|
||||
}
|
||||
|
||||
if news_edited != 0 {
|
||||
writeln!(&mut response, "[Úspěch] Upraveny novinky: {news_edited}").ok();
|
||||
}
|
||||
|
||||
let template = ActionTemplate { tcx, response };
|
||||
|
||||
template_response(&template)
|
||||
}
|
@ -2,11 +2,14 @@ pub mod add_banners;
|
||||
pub mod change_password;
|
||||
pub mod create_account;
|
||||
pub mod create_board;
|
||||
pub mod create_news;
|
||||
pub mod delete_account;
|
||||
pub mod edit_news;
|
||||
pub mod remove_accounts;
|
||||
pub mod remove_banners;
|
||||
pub mod remove_bans;
|
||||
pub mod remove_boards;
|
||||
pub mod remove_news;
|
||||
pub mod transfer_ownership;
|
||||
pub mod update_board_config;
|
||||
pub mod update_boards;
|
||||
|
65
src/web/staff/actions/remove_news.rs
Normální soubor
65
src/web/staff/actions/remove_news.rs
Normální soubor
@ -0,0 +1,65 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use actix_web::{post, web::Data, HttpRequest, HttpResponse};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
ctx::Ctx,
|
||||
db::models::NewsPost,
|
||||
error::NekrochanError,
|
||||
qsform::QsForm,
|
||||
web::{actions::ActionTemplate, template_response, TemplateCtx},
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RemoveNewsForm {
|
||||
#[serde(default)]
|
||||
pub news: Vec<i32>,
|
||||
}
|
||||
|
||||
#[post("/staff/actions/remove-news")]
|
||||
pub async fn remove_news(
|
||||
ctx: Data<Ctx>,
|
||||
req: HttpRequest,
|
||||
QsForm(form): QsForm<RemoveNewsForm>,
|
||||
) -> Result<HttpResponse, NekrochanError> {
|
||||
let tcx = TemplateCtx::new(&ctx, &req).await?;
|
||||
|
||||
if !(tcx.perms.owner() || tcx.perms.news()) {
|
||||
return Err(NekrochanError::InsufficientPermissionError);
|
||||
}
|
||||
|
||||
let mut news = Vec::new();
|
||||
|
||||
for id in form.news {
|
||||
if let Some(newspost) = NewsPost::read(&ctx, id).await? {
|
||||
news.push(newspost);
|
||||
}
|
||||
}
|
||||
|
||||
let mut response = String::new();
|
||||
let mut news_removed = 0;
|
||||
|
||||
for newspost in news {
|
||||
if !tcx.perms.owner() && tcx.account != Some(newspost.author.clone()) {
|
||||
writeln!(
|
||||
&mut response,
|
||||
"[Chyba] pouze vlastník nebo autor může odstranit novinky."
|
||||
)
|
||||
.ok();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
newspost.delete(&ctx).await?;
|
||||
news_removed += 1;
|
||||
}
|
||||
|
||||
if news_removed != 0 {
|
||||
writeln!(&mut response, "[Úspěch] Odstraněny novinky: {news_removed}").ok();
|
||||
}
|
||||
|
||||
let template = ActionTemplate { tcx, response };
|
||||
|
||||
template_response(&template)
|
||||
}
|
@ -13,15 +13,18 @@ pub struct UpdatePermissionsForm {
|
||||
edit_posts: Option<String>,
|
||||
manage_posts: Option<String>,
|
||||
capcodes: Option<String>,
|
||||
custom_capcodes: Option<String>,
|
||||
staff_log: Option<String>,
|
||||
reports: Option<String>,
|
||||
bans: Option<String>,
|
||||
banners: Option<String>,
|
||||
news: Option<String>,
|
||||
board_config: Option<String>,
|
||||
bypass_bans: Option<String>,
|
||||
bypass_board_lock: Option<String>,
|
||||
bypass_thread_lock: Option<String>,
|
||||
bypass_captcha: Option<String>,
|
||||
bypass_antispam: Option<String>,
|
||||
}
|
||||
|
||||
#[post("/staff/actions/update-permissions")]
|
||||
@ -55,6 +58,10 @@ pub async fn update_permissions(
|
||||
permissions |= Permissions::Capcodes;
|
||||
}
|
||||
|
||||
if form.custom_capcodes.is_some() {
|
||||
permissions |= Permissions::CustomCapcodes;
|
||||
}
|
||||
|
||||
if form.staff_log.is_some() {
|
||||
permissions |= Permissions::StaffLog;
|
||||
}
|
||||
@ -75,6 +82,10 @@ pub async fn update_permissions(
|
||||
permissions |= Permissions::BoardConfig;
|
||||
}
|
||||
|
||||
if form.news.is_some() {
|
||||
permissions |= Permissions::News;
|
||||
}
|
||||
|
||||
if form.bypass_bans.is_some() {
|
||||
permissions |= Permissions::BypassBans;
|
||||
}
|
||||
@ -91,6 +102,10 @@ pub async fn update_permissions(
|
||||
permissions |= Permissions::BypassCaptcha;
|
||||
}
|
||||
|
||||
if form.bypass_antispam.is_some() {
|
||||
permissions |= Permissions::BypassAntispam;
|
||||
}
|
||||
|
||||
updated_account
|
||||
.update_permissions(&ctx, permissions.bits())
|
||||
.await?;
|
||||
|
49
src/web/staff/edit_news.rs
Normální soubor
49
src/web/staff/edit_news.rs
Normální soubor
@ -0,0 +1,49 @@
|
||||
use actix_web::{post, web::Data, HttpRequest, HttpResponse};
|
||||
use askama::Template;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
ctx::Ctx,
|
||||
db::models::NewsPost,
|
||||
error::NekrochanError,
|
||||
filters,
|
||||
qsform::QsForm,
|
||||
web::{template_response, TemplateCtx},
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EditNewsForm {
|
||||
pub news: Vec<i32>,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "staff/edit-news.html")]
|
||||
struct EditNewsTemplate {
|
||||
tcx: TemplateCtx,
|
||||
news: Vec<NewsPost>,
|
||||
}
|
||||
|
||||
#[post("/staff/edit-news")]
|
||||
pub async fn edit_news(
|
||||
ctx: Data<Ctx>,
|
||||
req: HttpRequest,
|
||||
QsForm(form): QsForm<EditNewsForm>,
|
||||
) -> Result<HttpResponse, NekrochanError> {
|
||||
let tcx = TemplateCtx::new(&ctx, &req).await?;
|
||||
|
||||
if !(tcx.perms.owner() || tcx.perms.news()) {
|
||||
return Err(NekrochanError::InsufficientPermissionError);
|
||||
}
|
||||
|
||||
let mut news = Vec::new();
|
||||
|
||||
for id in form.news {
|
||||
if let Some(newspost) = NewsPost::read(&ctx, id).await? {
|
||||
news.push(newspost);
|
||||
}
|
||||
}
|
||||
|
||||
let template = EditNewsTemplate { tcx, news };
|
||||
|
||||
template_response(&template)
|
||||
}
|
@ -5,5 +5,7 @@ pub mod banners;
|
||||
pub mod bans;
|
||||
pub mod board_config;
|
||||
pub mod boards;
|
||||
pub mod edit_news;
|
||||
pub mod news;
|
||||
pub mod permissions;
|
||||
pub mod reports;
|
||||
|
25
src/web/staff/news.rs
Normální soubor
25
src/web/staff/news.rs
Normální soubor
@ -0,0 +1,25 @@
|
||||
use actix_web::{get, web::Data, HttpRequest, HttpResponse};
|
||||
use askama::Template;
|
||||
|
||||
use crate::{
|
||||
ctx::Ctx,
|
||||
db::models::NewsPost,
|
||||
error::NekrochanError,
|
||||
web::{tcx::TemplateCtx, template_response},
|
||||
};
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "staff/news.html")]
|
||||
struct NewsTemplate {
|
||||
tcx: TemplateCtx,
|
||||
news: Vec<NewsPost>,
|
||||
}
|
||||
|
||||
#[get("/staff/news")]
|
||||
pub async fn news(ctx: Data<Ctx>, req: HttpRequest) -> Result<HttpResponse, NekrochanError> {
|
||||
let tcx = TemplateCtx::new(&ctx, &req).await?;
|
||||
let news = NewsPost::read_all(&ctx).await?;
|
||||
let template = NewsTemplate { tcx, news };
|
||||
|
||||
template_response(&template)
|
||||
}
|
@ -14,7 +14,7 @@ use crate::{
|
||||
pub struct TemplateCtx {
|
||||
pub cfg: Cfg,
|
||||
pub boards: Vec<String>,
|
||||
pub logged_in: bool,
|
||||
pub account: Option<String>,
|
||||
pub perms: PermissionWrapper,
|
||||
pub name: Option<String>,
|
||||
pub password: String,
|
||||
@ -28,7 +28,6 @@ impl TemplateCtx {
|
||||
let boards = ctx.cache().lrange("board_ids", 0, -1).await?;
|
||||
|
||||
let account = account_from_auth_opt(ctx, req).await?;
|
||||
let logged_in = account.is_some();
|
||||
|
||||
let perms = match &account {
|
||||
Some(account) => account.perms(),
|
||||
@ -50,15 +49,17 @@ impl TemplateCtx {
|
||||
let (ip, _) = ip_from_req(req)?;
|
||||
let yous = ctx.cache().zrange(format!("by_ip:{ip}"), 0, -1).await?;
|
||||
|
||||
let account = account.map(|account| account.username);
|
||||
|
||||
let tcx = Self {
|
||||
cfg,
|
||||
boards,
|
||||
logged_in,
|
||||
perms,
|
||||
name,
|
||||
password,
|
||||
ip,
|
||||
yous,
|
||||
account,
|
||||
};
|
||||
|
||||
Ok(tcx)
|
||||
|
@ -54,7 +54,7 @@ summary {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.edit-post {
|
||||
.edit-box {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
@ -65,7 +65,7 @@ summary {
|
||||
.form-table textarea,
|
||||
.form-table select,
|
||||
.input-wrapper,
|
||||
.edit-post {
|
||||
.edit-box {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
@ -85,15 +85,11 @@ summary {
|
||||
}
|
||||
|
||||
.form-table textarea,
|
||||
.edit-post {
|
||||
.edit-box {
|
||||
height: 8rem;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.form-table .submit .button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.reply-mode {
|
||||
font-weight: bold;
|
||||
@ -139,6 +135,10 @@ summary {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.news {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.box {
|
||||
background-color: var(--box-color);
|
||||
border-right: 1px solid var(--box-border);
|
||||
@ -213,6 +213,12 @@ summary {
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.form-table .button,
|
||||
.full-width {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.banner {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h1 class="title">Výsledek</h1>
|
||||
<table class="data-table">
|
||||
<tr><th>Výsledek</th></tr>
|
||||
<tr>
|
||||
|
@ -52,7 +52,7 @@
|
||||
<td><textarea name="appeal"></textarea></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="submit" colspan="2">
|
||||
<td colspan="2">
|
||||
<input class="button" type="submit" value="Odeslat">
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -16,11 +16,18 @@
|
||||
<div class="board-links header">
|
||||
<span class="link-group"><a href="/">domov</a></span>
|
||||
{% call board_links::board_links() %}
|
||||
<span class="link-group"><a href="/overboard">nadnástěnka</a></span>
|
||||
<span class="link-group">
|
||||
<a href="/overboard">nadnástěnka</a>
|
||||
<span class="link-separator"></span>
|
||||
<a href="/news">novinky</a></span>
|
||||
</span>
|
||||
<span class="float-r">
|
||||
{% if tcx.logged_in %}
|
||||
<span class="link-group"><a href="/logout">odhlásit se</a></span>
|
||||
<span class="link-group"><a href="/staff/account">účet</a></span>
|
||||
{% if tcx.account.is_some() %}
|
||||
<span class="link-group">
|
||||
<a href="/logout">odhlásit se</a>
|
||||
<span class="link-separator"></span>
|
||||
<a href="/staff/account">účet</a>
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="link-group"><a href="/login">přihlásit se</a></span>
|
||||
{% endif %}
|
||||
|
@ -10,11 +10,11 @@
|
||||
{% for post in posts %}
|
||||
<div class="box">
|
||||
<b>Příspěvek #{{ post.id }} na /{{ post.board }}/</b>
|
||||
<textarea class="edit-post" name="{{ post.board }}/{{ post.id }}">{{ post.content_nomarkup }}</textarea>
|
||||
<textarea class="edit-box" name="{{ post.board }}/{{ post.id }}">{{ post.content_nomarkup }}</textarea>
|
||||
</div>
|
||||
<hr>
|
||||
{% endfor %}
|
||||
<input class="button" type="submit" value="Upravit">
|
||||
<input class="button full-width" type="submit" value="Upravit">
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -18,12 +18,14 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h2 class="headline">
|
||||
<span>{{ news.title }}</span>
|
||||
<span class="float-r">{{ news.author }} - {{ news.created|czech_datetime }}</span>
|
||||
</h2>
|
||||
<hr>
|
||||
<pre class="post-content">{{ news.content|safe }}</pre>
|
||||
<div class="box">
|
||||
<h2 class="headline">
|
||||
<span>{{ news.title }}</span>
|
||||
<span class="float-r">{{ news.author }} - {{ news.created|czech_datetime }}</span>
|
||||
</h2>
|
||||
<hr>
|
||||
<div class="post-content">{{ news.content|safe }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -9,14 +9,14 @@
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<td class="label">Jméno</td>
|
||||
<td><input name="username" type="text" required="required"></td>
|
||||
<td><input name="username" type="text" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Heslo</td>
|
||||
<td><input name="password" type="password" required="required"></td>
|
||||
<td><input name="password" type="password" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="submit" 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>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
@ -18,6 +18,6 @@
|
||||
<img class="icon" src="/static/icons/locked.png"> 
|
||||
{% endif %}
|
||||
</b>
|
||||
<pre class="post-content">{{ post.content|add_yous(post.board, tcx.yous)|safe }}</pre>
|
||||
<div class="post-content">{{ post.content|add_yous(post.board, tcx.yous)|safe }}</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
@ -40,7 +40,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="submit" colspan="2">
|
||||
<td colspan="2">
|
||||
<input
|
||||
class="button"
|
||||
type="submit"
|
||||
@ -56,7 +56,7 @@
|
||||
<td><textarea name="report_reason"></textarea></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="submit" colspan="2">
|
||||
<td colspan="2">
|
||||
<input
|
||||
class="button"
|
||||
type="submit"
|
||||
@ -159,7 +159,7 @@
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="submit" colspan="2">
|
||||
<td colspan="2">
|
||||
<input
|
||||
class="button"
|
||||
type="submit"
|
||||
@ -172,7 +172,7 @@
|
||||
{% if tcx.perms.owner() || tcx.perms.edit_posts() %}
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<td class="submit">
|
||||
<td>
|
||||
<input
|
||||
class="button"
|
||||
type="submit"
|
||||
|
@ -25,14 +25,14 @@
|
||||
<tr>
|
||||
<td class="label">Obsah</td>
|
||||
<td>
|
||||
<textarea name="content" {% if (!reply && board.config.0.require_thread_content) || (reply && board.config.0.require_reply_content) %}required="required"{% endif %}></textarea>
|
||||
<textarea name="content" {% if (!reply && board.config.0.require_thread_content) || (reply && board.config.0.require_reply_content) %}required=""{% endif %}></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Soubory</td>
|
||||
<td>
|
||||
<div class="input-wrapper">
|
||||
<input name="files[]" type="file"{% if board.config.0.file_limit > 1 %} multiple="multiple"{% endif %}{% if (!reply && board.config.0.require_thread_file) || (reply && board.config.0.require_reply_file) %} required="required"{% endif %}>
|
||||
<input name="files[]" type="file"{% if board.config.0.file_limit > 1 %} multiple="multiple"{% endif %}{% if (!reply && board.config.0.require_thread_file) || (reply && board.config.0.require_reply_file) %} required=""{% endif %}>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -58,7 +58,7 @@
|
||||
<td>
|
||||
<img class="captcha" src="data:image/png;base64,{{ base64 }}">
|
||||
<input name="captcha_id" type="hidden" value="{{ id }}">
|
||||
<input name="captcha_solution" type="text" placeholder="Řešení" required="required">
|
||||
<input name="captcha_solution" type="text" placeholder="Řešení" required="">
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@ -69,7 +69,7 @@
|
||||
<td>
|
||||
<img class="captcha" src="data:image/png;base64,{{ base64 }}">
|
||||
<input name="captcha_id" type="hidden" value="{{ id }}">
|
||||
<input name="captcha_solution" type="text" placeholder="Řešení" required="required">
|
||||
<input name="captcha_solution" type="text" placeholder="Řešení" required="">
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@ -77,9 +77,9 @@
|
||||
{% endif %}
|
||||
<tr>
|
||||
{% if reply %}
|
||||
<td class="submit" colspan="2"><input class="button" type="submit" value="Nová odpověď"></td>
|
||||
<td colspan="2"><input class="button" type="submit" value="Nová odpověď"></td>
|
||||
{% else %}
|
||||
<td class="submit" colspan="2"><input class="button" type="submit" value="Nové vlákno"></td>
|
||||
<td colspan="2"><input class="button" type="submit" value="Nové vlákno"></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -57,6 +57,6 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<pre class="post-content">{{ post.content|add_yous(post.board, tcx.yous)|safe }}</pre>
|
||||
<div class="post-content">{{ post.content|add_yous(post.board, tcx.yous)|safe }}</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
@ -18,5 +18,9 @@
|
||||
{% if tcx.perms.owner() || tcx.perms.reports() %}
|
||||
<a href="/staff/reports">[Hlášení]</a> 
|
||||
{% endif %}
|
||||
|
||||
{% if tcx.perms.owner() || tcx.perms.news() %}
|
||||
<a href="/staff/news">[Novinky]</a> 
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
19
templates/news.html
Normální soubor
19
templates/news.html
Normální soubor
@ -0,0 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Novinky{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h1 class="title">Novinky</h1>
|
||||
{% for newspost in news %}
|
||||
<div class="news box">
|
||||
<h2 class="headline">
|
||||
<span>{{ newspost.title }}</span>
|
||||
<span class="float-r">{{ newspost.author }} - {{ newspost.created|czech_datetime }}</span>
|
||||
</h2>
|
||||
<hr>
|
||||
<div class="post-content">{{ newspost.content|safe }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
@ -13,14 +13,14 @@
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<td class="label">Staré heslo</td>
|
||||
<td><input name="old_password" type="password" required="required"></td>
|
||||
<td><input name="old_password" type="password" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Nové heslo</td>
|
||||
<td><input name="new_password" type="password" required="required"></td>
|
||||
<td><input name="new_password" type="password" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="submit" colspan="2"><input class="button" type="submit" value="Změnit heslo"></td>
|
||||
<td colspan="2"><input class="button" type="submit" value="Změnit heslo"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
@ -31,18 +31,18 @@
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<td class="label">Účet</td>
|
||||
<td><input name="account" type="text" required="required"></td>
|
||||
<td><input name="account" type="text" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Potvrdit</td>
|
||||
<td>
|
||||
<div class="input-wrapper">
|
||||
<input name="confirm" type="checkbox" required="required">
|
||||
<input name="confirm" type="checkbox" required="">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="submit" colspan="2"><input class="button" type="submit" value="Předat vlastnictví"></td>
|
||||
<td colspan="2"><input class="button" type="submit" value="Předat vlastnictví"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
@ -55,12 +55,12 @@
|
||||
<td class="label">Potvrdit</td>
|
||||
<td>
|
||||
<div class="input-wrapper">
|
||||
<input name="confirm" type="checkbox" required="required">
|
||||
<input name="confirm" type="checkbox" required="">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="submit" colspan="2"><input class="button" type="submit" value="Vymazat účet"></td>
|
||||
<td colspan="2"><input class="button" type="submit" value="Vymazat účet"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
@ -41,14 +41,14 @@
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<td class="label">Jméno</td>
|
||||
<td><input name="username" type="text" required="required"></td>
|
||||
<td><input name="username" type="text" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Heslo</td>
|
||||
<td><input name="password" type="password" required="required"></td>
|
||||
<td><input name="password" type="password" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="submit" 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>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
@ -35,10 +35,10 @@
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<td class="label">Bannery</td>
|
||||
<td><div class="input-wrapper"><input name="files[]" type="file" multiple="multiple" required="required"></div></td>
|
||||
<td><div class="input-wrapper"><input name="files[]" type="file" multiple="multiple" required=""></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="submit" colspan="2"><input class="button" type="submit" value="Přidat bannery"></td>
|
||||
<td colspan="2"><input class="button" type="submit" value="Přidat bannery"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
@ -26,7 +26,13 @@
|
||||
{% for ban in bans %}
|
||||
<tr>
|
||||
<td><input name="bans[]" type="checkbox" value="{{ ban.id }}"></td>
|
||||
<td title="{{ ban.ip_range }}">{{ ban.ip_range.network() }}-{{ ban.ip_range.broadcast() }}</td>
|
||||
<td title="{{ ban.ip_range }}">
|
||||
{% if ban.ip_range.network() == ban.ip_range.broadcast() %}
|
||||
{{ ban.ip_range.ip() }}
|
||||
{% else %}
|
||||
{{ ban.ip_range.network() }}-{{ ban.ip_range.broadcast() }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% if let Some(board) = ban.board %}/{{ board }}/{% else %}<i>Všechny</i>{% endif %}</td>
|
||||
<td><div class="post-content">{{ ban.reason }}</div></td>
|
||||
<td>{{ ban.issued_by }}</td>
|
||||
|
@ -23,32 +23,32 @@
|
||||
|
||||
<tr>
|
||||
<td class="label">Výchozí jméno</td>
|
||||
<td><input name="anon_name" type="text" value="{{ board.config.0.anon_name }}" required="required"></td>
|
||||
<td><input name="anon_name" type="text" value="{{ board.config.0.anon_name }}" required=""></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Velikost stránky</td>
|
||||
<td><input name="page_size" type="number" min="1" value="{{ board.config.0.page_size }}" required="required"></td>
|
||||
<td><input name="page_size" type="number" min="1" value="{{ board.config.0.page_size }}" required=""></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Počet stránek</td>
|
||||
<td><input name="page_count" type="number" min="1" value="{{ board.config.0.page_count }}" required="required"></td>
|
||||
<td><input name="page_count" type="number" min="1" value="{{ board.config.0.page_count }}" required=""></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Limit souborů</td>
|
||||
<td><input name="file_limit" type="number" min="1" value="{{ board.config.0.file_limit }}" required="required"></td>
|
||||
<td><input name="file_limit" type="number" min="1" value="{{ board.config.0.file_limit }}" required=""></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Limit naťuknutí</td>
|
||||
<td><input name="bump_limit" type="number" min="0" value="{{ board.config.0.bump_limit }}" required="required"></td>
|
||||
<td><input name="bump_limit" type="number" min="0" value="{{ board.config.0.bump_limit }}" required=""></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Limit odpovědí</td>
|
||||
<td><input name="reply_limit" type="number" min="0" value="{{ board.config.0.reply_limit }}" required="required"></td>
|
||||
<td><input name="reply_limit" type="number" min="0" value="{{ board.config.0.reply_limit }}" required=""></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
@ -156,21 +156,21 @@
|
||||
|
||||
<tr>
|
||||
<td class="label">Interval antispamu (IP)</td>
|
||||
<td><input name="antispam_ip" type="number" min="0" value="{{ board.config.0.antispam_ip }}" required="required"></td>
|
||||
<td><input name="antispam_ip" type="number" min="0" value="{{ board.config.0.antispam_ip }}" required=""></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Interval antispamu (Obsah)</td>
|
||||
<td><input name="antispam_content" type="number" min="0" value="{{ board.config.0.antispam_content }}" required="required"></td>
|
||||
<td><input name="antispam_content" type="number" min="0" value="{{ board.config.0.antispam_content }}" required=""></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Interval antispamu (IP+Obsah)</td>
|
||||
<td><input name="antispam_both" type="number" min="0" value="{{ board.config.0.antispam_both }}" required="required"></td>
|
||||
<td><input name="antispam_both" type="number" min="0" value="{{ board.config.0.antispam_both }}" required=""></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="submit" colspan="2"><input class="button" type="submit" value="Uložit"></td>
|
||||
<td colspan="2"><input class="button" type="submit" value="Uložit"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
@ -43,14 +43,14 @@
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<td class="label">Jméno</td>
|
||||
<td><input name="name" type="text" required="required"></td>
|
||||
<td><input name="name" type="text" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Popis</td>
|
||||
<td><input name="description" type="text" required="required"></td>
|
||||
<td><input name="description" type="text" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="submit" colspan="2"><input class="button" type="submit" formaction="/staff/actions/update-boards" value="Upravit vybrané"></td>
|
||||
<td colspan="2"><input class="button" type="submit" formaction="/staff/actions/update-boards" value="Upravit vybrané"></td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
@ -61,18 +61,18 @@
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<td class="label">ID</td>
|
||||
<td><input name="id" type="text" required="required"></td>
|
||||
<td><input name="id" type="text" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Jméno</td>
|
||||
<td><input name="name" type="text" required="required"></td>
|
||||
<td><input name="name" type="text" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Popis</td>
|
||||
<td><input name="description" type="text" required="required"></td>
|
||||
<td><input name="description" type="text" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="submit" colspan="2"><input class="button" type="submit" value="Vytvořit nástěnku"></td>
|
||||
<td colspan="2"><input class="button" type="submit" value="Vytvořit nástěnku"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
24
templates/staff/edit-news.html
Normální soubor
24
templates/staff/edit-news.html
Normální soubor
@ -0,0 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Upravit příspěvky{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h1 class="title">Upravit novinky</h1>
|
||||
<hr>
|
||||
<form method="post" action="/staff/actions/edit-news">
|
||||
{% for newspost in news %}
|
||||
<div class="box">
|
||||
<h2 class="headline">
|
||||
<span>{{ newspost.title }}</span>
|
||||
<span class="float-r">{{ newspost.author }} - {{ newspost.created|czech_datetime }}</span>
|
||||
</h2>
|
||||
<hr>
|
||||
<textarea class="edit-box" name="{{ newspost.id }}">{{ newspost.content_nomarkup }}</textarea>
|
||||
</div>
|
||||
<hr>
|
||||
{% endfor %}
|
||||
<input class="button full-width" type="submit" value="Upravit">
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,29 +0,0 @@
|
||||
{% import "../macros/pagination.html" as pagination %}
|
||||
{% import "../macros/staff-nav.html" as staff_nav %}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Záznamy{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title">Záznamy</h1>
|
||||
{% call staff_nav::staff_nav() %}
|
||||
<hr>
|
||||
<h2>Záznamy</h2>
|
||||
<div class="table-wrap">
|
||||
<table class="data-table center">
|
||||
<tr>
|
||||
<th>Zpráva</th>
|
||||
<th>Datum</th>
|
||||
</tr>
|
||||
{% for record in records %}
|
||||
<tr>
|
||||
<td>{{ record.message }}</td>
|
||||
<td>{{ record.created|czech_datetime }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
<hr>
|
||||
{% call pagination::pagination("/staff/logs", pages, page) %}
|
||||
{% endblock %}
|
51
templates/staff/news.html
Normální soubor
51
templates/staff/news.html
Normální soubor
@ -0,0 +1,51 @@
|
||||
{% import "../macros/staff-nav.html" as staff_nav %}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Novinky{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title">Novinky</h1>
|
||||
{% call staff_nav::staff_nav() %}
|
||||
<hr>
|
||||
<h2>Novinky</h2>
|
||||
<form method="post">
|
||||
<div class="table-wrap">
|
||||
<table class="data-table center">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Titulek</th>
|
||||
<th>Autor</th>
|
||||
<th>Datum</th>
|
||||
</tr>
|
||||
{% for newspost in news %}
|
||||
<tr>
|
||||
<td><input name="news[]" type="checkbox" value="{{ newspost.id }}"></td>
|
||||
<td>{{ newspost.title }}</td>
|
||||
<td>{{ newspost.author }}</td>
|
||||
<td>{{ newspost.created }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
<input class="button" type="submit" formaction="/staff/actions/remove-news" value="Odstranit vybrané"> 
|
||||
<input class="button" type="submit" formaction="/staff/edit-news" value="Upravit vybrané">
|
||||
</form>
|
||||
<hr>
|
||||
<h2>Vytvořit novinky</h2>
|
||||
<form method="post" action="/staff/actions/create-news">
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<td class="label">Titulek</td>
|
||||
<td><input name="title" type="text" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Obsah</td>
|
||||
<td><textarea name="content" required=""></textarea></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><input class="button" type="submit" value="Vytvořit novinky"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
{% endblock %}
|
@ -43,6 +43,15 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Vlastní capcode</td>
|
||||
<td>
|
||||
<div class="input-wrapper">
|
||||
<input name="custom_capcodes" type="checkbox"{% if account.perms().custom_capcodes() %} checked="checked"{% endif %}{% if !tcx.perms.owner() %} disabled=""{% endif %}>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Záznamy</td>
|
||||
<td>
|
||||
@ -88,6 +97,15 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Novinky</td>
|
||||
<td>
|
||||
<div class="input-wrapper">
|
||||
<input name="news" type="checkbox"{% if account.perms().news() %} checked="checked"{% endif %}{% if !tcx.perms.owner() %} disabled=""{% endif %}>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Obejít ban</td>
|
||||
<td>
|
||||
@ -124,9 +142,18 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Obejít antispam</td>
|
||||
<td>
|
||||
<div class="input-wrapper">
|
||||
<input name="bypass_antispam" type="checkbox"{% if account.perms().bypass_antispam() %} checked="checked"{% endif %}{% if !tcx.perms.owner() %} disabled=""{% endif %}>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% if tcx.perms.owner() %}
|
||||
<tr>
|
||||
<td class="submit" colspan="2"><input class="button" type="submit" value="Uložit"></td>
|
||||
<td colspan="2"><input class="button" type="submit" value="Uložit"></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
Načítá se…
Odkázat v novém úkolu
Zablokovat Uživatele