Globální bannery

Tento commit je obsažen v:
sneedmaster 2023-12-16 13:51:50 +01:00
rodič 1eeffc71c6
revize bde9a95bd6
30 změnil soubory, kde provedl 172 přidání a 125 odebrání

Zobrazit soubor

@ -1 +0,0 @@
DROP TABLE accounts, boards, bans;

Zobrazit soubor

@ -0,0 +1,6 @@
ALTER TABLE boards DROP COLUMN banners;
CREATE TABLE banners (
id SERIAL NOT NULL PRIMARY KEY,
banner JSONB NOT NULL
);

Zobrazit soubor

@ -31,7 +31,6 @@ pub struct ServerCfg {
pub struct SiteCfg { pub struct SiteCfg {
pub name: String, pub name: String,
pub description: String, pub description: String,
pub site_banner: Option<String>,
} }
#[derive(Deserialize, Clone)] #[derive(Deserialize, Clone)]

64
src/db/banner.rs Normální soubor
Zobrazit soubor

@ -0,0 +1,64 @@
use redis::AsyncCommands;
use sqlx::{query, query_as, types::Json};
use super::models::{Banner, File};
use crate::{ctx::Ctx, NekrochanError};
impl Banner {
pub async fn create(ctx: &Ctx, banner: File) -> Result<Self, NekrochanError> {
let banner: Self = query_as("INSERT INTO banners (banner) VALUES ($1) RETURNING *")
.bind(Json(banner))
.fetch_one(ctx.db())
.await?;
ctx.cache()
.zadd("banners", serde_json::to_string(&banner)?, banner.id)
.await?;
Ok(banner)
}
pub async fn read(ctx: &Ctx, id: i32) -> Result<Option<Self>, NekrochanError> {
let banners: Vec<String> = ctx.cache().zrangebyscore("banners", id, id).await?;
let json = banners.get(0);
let banner = match json {
Some(json) => Some(serde_json::from_str(json)?),
None => None,
};
Ok(banner)
}
pub async fn read_all(ctx: &Ctx) -> Result<Vec<Self>, NekrochanError> {
let banners_str: Vec<String> = ctx.cache().zrange("banners", 0, -1).await?;
let banners_json = format!("[{}]", banners_str.join(",")); // If it works, it works
let banners = serde_json::from_str(&banners_json)?;
Ok(banners)
}
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 {
Some(json) => Some(serde_json::from_str(&json)?),
None => None,
};
Ok(banner)
}
pub async fn remove(&self, ctx: &Ctx) -> Result<(), NekrochanError> {
self.banner.delete().await;
query("DELETE FROM banners WHERE id = $1")
.bind(self.id)
.execute(ctx.db())
.await?;
ctx.cache().zrembyscore("banners", self.id, self.id).await?;
Ok(())
}
}

Zobrazit soubor

@ -1,5 +1,4 @@
use captcha::{gen, Difficulty}; use captcha::{gen, Difficulty};
use rand::{seq::SliceRandom, thread_rng};
use redis::{cmd, AsyncCommands, JsonAsyncCommands}; use redis::{cmd, AsyncCommands, JsonAsyncCommands};
use sha256::digest; use sha256::digest;
use sqlx::{query, query_as, types::Json}; use sqlx::{query, query_as, types::Json};
@ -207,12 +206,6 @@ impl Board {
} }
impl Board { impl Board {
pub fn random_banner(&self) -> Option<File> {
self.banners
.choose(&mut thread_rng())
.map(std::clone::Clone::clone)
}
pub fn thread_captcha(&self) -> Option<(String, String)> { pub fn thread_captcha(&self) -> Option<(String, String)> {
let captcha = match self.config.thread_captcha.as_str() { let captcha = match self.config.thread_captcha.as_str() {
"easy" => gen(Difficulty::Easy), "easy" => gen(Difficulty::Easy),

Zobrazit soubor

@ -3,7 +3,7 @@ use redis::{cmd, AsyncCommands, JsonAsyncCommands};
use sha256::digest; use sha256::digest;
use sqlx::query_as; use sqlx::query_as;
use super::models::{Account, Board, Post}; use super::models::{Account, Banner, Board, Post};
use crate::ctx::Ctx; use crate::ctx::Ctx;
pub async fn init_cache(ctx: &Ctx) -> Result<(), Error> { pub async fn init_cache(ctx: &Ctx) -> Result<(), Error> {
@ -29,6 +29,16 @@ pub async fn init_cache(ctx: &Ctx) -> Result<(), Error> {
ctx.cache().lpush("board_ids", &board.id).await?; ctx.cache().lpush("board_ids", &board.id).await?;
} }
let banners: Vec<Banner> = query_as("SELECT * FROM banners")
.fetch_all(ctx.db())
.await?;
for banner in &banners {
ctx.cache()
.zadd("banners", serde_json::to_string(banner)?, banner.id)
.await?;
}
cmd("SORT") cmd("SORT")
.arg("board_ids") .arg("board_ids")
.arg("ALPHA") .arg("ALPHA")

Zobrazit soubor

@ -3,5 +3,6 @@ pub mod models;
mod account; mod account;
mod ban; mod ban;
mod banner;
mod board; mod board;
mod post; mod post;

Zobrazit soubor

@ -7,7 +7,7 @@ use sqlx::{types::Json, FromRow};
use crate::cfg::BoardCfg; use crate::cfg::BoardCfg;
#[derive(FromRow, Clone, Serialize, Deserialize)] #[derive(FromRow, Serialize, Deserialize, Clone)]
pub struct Account { pub struct Account {
pub username: String, pub username: String,
pub password: String, pub password: String,
@ -21,7 +21,6 @@ pub struct Board {
pub id: String, pub id: String,
pub name: String, pub name: String,
pub description: String, pub description: String,
pub banners: Json<Vec<File>>,
pub config: Json<BoardCfg>, pub config: Json<BoardCfg>,
pub created: DateTime<Utc>, pub created: DateTime<Utc>,
} }
@ -90,3 +89,9 @@ pub struct File {
pub timestamp: i64, pub timestamp: i64,
pub size: usize, pub size: usize,
} }
#[derive(FromRow, Serialize, Deserialize)]
pub struct Banner {
pub id: i32,
pub banner: Json<File>,
}

Zobrazit soubor

@ -333,6 +333,10 @@ impl Post {
.await?; .await?;
for post in &to_be_deleted { for post in &to_be_deleted {
for file in post.files.iter() {
file.delete().await;
}
let id = post.id; let id = post.id;
let url = post.post_url(); let url = post.post_url();
@ -381,7 +385,7 @@ impl Post {
} }
pub async fn delete_files(&self, ctx: &Ctx) -> Result<(), NekrochanError> { pub async fn delete_files(&self, ctx: &Ctx) -> Result<(), NekrochanError> {
for file in self.files.iter() { for file in &self.files.0 {
file.delete().await; file.delete().await;
} }

Zobrazit soubor

@ -11,7 +11,7 @@ use tokio::{
use crate::{ use crate::{
cfg::Cfg, cfg::Cfg,
ctx::Ctx, ctx::Ctx,
db::models::{Board, File, Post}, db::models::{Banner, Board, File, Post},
error::NekrochanError, error::NekrochanError,
}; };
@ -310,13 +310,18 @@ pub async fn cleanup_files(ctx: &Ctx) -> Result<(), Error> {
let mut keep = HashSet::new(); let mut keep = HashSet::new();
let mut keep_thumbs = HashSet::new(); let mut keep_thumbs = HashSet::new();
let banners = Banner::read_all(ctx).await?;
for banner in banners {
keep.insert(format!(
"{}.{}",
banner.banner.timestamp, banner.banner.format
));
}
let boards = Board::read_all(ctx).await?; let boards = Board::read_all(ctx).await?;
for board in boards { for board in boards {
for file in board.banners.0 {
keep.insert(format!("{}.{}", file.timestamp, file.format));
}
let posts = Post::read_all(ctx, board.id.clone()).await?; let posts = Post::read_all(ctx, board.id.clone()).await?;
for post in posts { for post in posts {

Zobrazit soubor

@ -3,11 +3,10 @@ use actix_web::{
body::MessageBody, body::MessageBody,
dev::ServiceResponse, dev::ServiceResponse,
get, get,
http::StatusCode, http::header::{HeaderValue, CACHE_CONTROL, PRAGMA},
middleware::{ErrorHandlerResponse, ErrorHandlers}, middleware::{ErrorHandlerResponse, ErrorHandlers},
post,
web::Data, web::Data,
App, HttpResponse, HttpServer, ResponseError, App, HttpRequest, HttpResponse, HttpServer, ResponseError,
}; };
use anyhow::Error; use anyhow::Error;
use askama::Template; use askama::Template;
@ -15,7 +14,7 @@ use log::{error, info};
use nekrochan::{ use nekrochan::{
cfg::Cfg, cfg::Cfg,
ctx::Ctx, ctx::Ctx,
db::cache::init_cache, db::{cache::init_cache, models::Banner},
error::NekrochanError, error::NekrochanError,
files::cleanup_files, files::cleanup_files,
web::{self, template_response}, web::{self, template_response},
@ -98,8 +97,8 @@ async fn run() -> Result<(), Error> {
.service(web::staff::actions::update_board_config::update_board_config) .service(web::staff::actions::update_board_config::update_board_config)
.service(web::staff::actions::update_boards::update_boards) .service(web::staff::actions::update_boards::update_boards)
.service(web::staff::actions::update_permissions::update_permissions) .service(web::staff::actions::update_permissions::update_permissions)
.service(debug)
.service(favicon) .service(favicon)
.service(random_banner)
.service(Files::new("/static", "./static")) .service(Files::new("/static", "./static"))
.service(Files::new("/uploads", "./uploads").disable_content_disposition()) .service(Files::new("/uploads", "./uploads").disable_content_disposition())
.wrap(ErrorHandlers::new().default_handler(error_handler)) .wrap(ErrorHandlers::new().default_handler(error_handler))
@ -118,11 +117,28 @@ async fn favicon() -> Result<NamedFile, NekrochanError> {
Ok(favicon) Ok(favicon)
} }
#[post("/debug")] #[get("/random-banner")]
async fn debug(req: String) -> HttpResponse { async fn random_banner(ctx: Data<Ctx>, req: HttpRequest) -> Result<HttpResponse, NekrochanError> {
println!("{req}"); let file = if let Some(banner) = Banner::read_random(&ctx).await? {
let timestamp = banner.banner.timestamp;
let format = &banner.banner.format;
HttpResponse::new(StatusCode::OK) NamedFile::open(format!("./uploads/{timestamp}.{format}"))?
} else {
NamedFile::open("./static/default-banner.png")?
};
let mut res = file.into_response(&req);
res.headers_mut().append(
CACHE_CONTROL,
HeaderValue::from_static("no-cache, no-store, must-revalidate"),
);
res.headers_mut()
.append(PRAGMA, HeaderValue::from_static("no-cache"));
Ok(res)
} }
#[derive(Template)] #[derive(Template)]

Zobrazit soubor

@ -59,7 +59,7 @@ impl PermissionWrapper {
self.0.contains(Permissions::Bans) self.0.contains(Permissions::Bans)
} }
pub fn board_banners(&self) -> bool { pub fn banners(&self) -> bool {
self.0.contains(Permissions::BoardBanners) self.0.contains(Permissions::BoardBanners)
} }

Zobrazit soubor

@ -1,16 +1,15 @@
use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm}; use actix_multipart::form::{tempfile::TempFile, MultipartForm};
use actix_web::{post, web::Data, HttpRequest, HttpResponse}; use actix_web::{post, web::Data, HttpRequest, HttpResponse};
use crate::{ use crate::{
ctx::Ctx, ctx::Ctx,
db::models::{Board, File}, db::models::{Banner, File},
error::NekrochanError, error::NekrochanError,
web::tcx::account_from_auth, web::tcx::account_from_auth,
}; };
#[derive(MultipartForm)] #[derive(MultipartForm)]
pub struct AddBannersForm { pub struct AddBannersForm {
board: Text<String>,
#[multipart(rename = "files[]")] #[multipart(rename = "files[]")]
files: Vec<TempFile>, files: Vec<TempFile>,
} }
@ -23,32 +22,20 @@ pub async fn add_banners(
) -> Result<HttpResponse, NekrochanError> { ) -> Result<HttpResponse, NekrochanError> {
let account = account_from_auth(&ctx, &req).await?; let account = account_from_auth(&ctx, &req).await?;
if !(account.perms().owner() || account.perms().board_banners()) { if !(account.perms().owner() || account.perms().banners()) {
return Err(NekrochanError::InsufficientPermissionError); return Err(NekrochanError::InsufficientPermissionError);
} }
let board = form.board.0;
let board = Board::read(&ctx, board.clone())
.await?
.ok_or(NekrochanError::BoardNotFound(board))?;
let mut new_banners = board.banners.0.clone();
let added_banners = form.files;
let mut cfg = ctx.cfg.clone(); let mut cfg = ctx.cfg.clone();
cfg.files.videos = false; cfg.files.videos = false;
for banner in added_banners { for file in form.files {
let file = File::new(&cfg, banner, false, false).await?; Banner::create(&ctx, File::new(&cfg, file, false, false).await?).await?;
new_banners.push(file);
} }
board.update_banners(&ctx, new_banners).await?;
let res = HttpResponse::SeeOther() let res = HttpResponse::SeeOther()
.append_header(("Location", format!("/staff/banners/{}", board.id).as_str())) .append_header(("Location", "/staff/banners"))
.finish(); .finish();
Ok(res) Ok(res)

Zobrazit soubor

@ -2,14 +2,14 @@ use actix_web::{post, web::Data, HttpRequest, HttpResponse};
use serde::Deserialize; use serde::Deserialize;
use crate::{ use crate::{
ctx::Ctx, db::models::Board, error::NekrochanError, qsform::QsForm, web::tcx::account_from_auth, ctx::Ctx, db::models::Banner, error::NekrochanError, qsform::QsForm,
web::tcx::account_from_auth,
}; };
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct RemoveBannersForm { pub struct RemoveBannersForm {
board: String,
#[serde(default)] #[serde(default)]
banners: Vec<usize>, banners: Vec<i32>,
} }
#[post("/staff/actions/remove-banners")] #[post("/staff/actions/remove-banners")]
@ -20,30 +20,18 @@ pub async fn remove_banners(
) -> Result<HttpResponse, NekrochanError> { ) -> Result<HttpResponse, NekrochanError> {
let account = account_from_auth(&ctx, &req).await?; let account = account_from_auth(&ctx, &req).await?;
if !(account.perms().owner() || account.perms().board_banners()) { if !(account.perms().owner() || account.perms().banners()) {
return Err(NekrochanError::InsufficientPermissionError); return Err(NekrochanError::InsufficientPermissionError);
} }
let board = form.board; for id in form.banners {
let board = Board::read(&ctx, board.clone()) if let Some(banner) = Banner::read(&ctx, id).await? {
.await? banner.remove(&ctx).await?;
.ok_or(NekrochanError::BoardNotFound(board))?;
let old_banners = board.banners.0.clone();
let mut new_banners = Vec::new();
for (i, banner) in old_banners.into_iter().enumerate() {
if form.banners.contains(&i) {
banner.delete().await;
} else {
new_banners.push(banner);
} }
} }
board.update_banners(&ctx, new_banners).await?;
let res = HttpResponse::SeeOther() let res = HttpResponse::SeeOther()
.append_header(("Location", format!("/staff/banners/{}", board.id).as_str())) .append_header(("Location", "/staff/banners"))
.finish(); .finish();
Ok(res) Ok(res)

Zobrazit soubor

@ -16,7 +16,7 @@ pub struct UpdatePermissionsForm {
staff_log: Option<String>, staff_log: Option<String>,
reports: Option<String>, reports: Option<String>,
bans: Option<String>, bans: Option<String>,
board_banners: Option<String>, banners: Option<String>,
board_config: Option<String>, board_config: Option<String>,
bypass_bans: Option<String>, bypass_bans: Option<String>,
bypass_board_lock: Option<String>, bypass_board_lock: Option<String>,
@ -67,7 +67,7 @@ pub async fn update_permissions(
permissions |= Permissions::Bans; permissions |= Permissions::Bans;
} }
if form.board_banners.is_some() { if form.banners.is_some() {
permissions |= Permissions::BoardBanners; permissions |= Permissions::BoardBanners;
} }

Zobrazit soubor

@ -1,13 +1,9 @@
use actix_web::{ use actix_web::{get, web::Data, HttpRequest, HttpResponse};
get,
web::{Data, Path},
HttpRequest, HttpResponse,
};
use askama::Template; use askama::Template;
use crate::{ use crate::{
ctx::Ctx, ctx::Ctx,
db::models::Board, db::models::Banner,
error::NekrochanError, error::NekrochanError,
web::{ web::{
tcx::{account_from_auth, TemplateCtx}, tcx::{account_from_auth, TemplateCtx},
@ -19,28 +15,20 @@ use crate::{
#[template(path = "staff/banners.html")] #[template(path = "staff/banners.html")]
struct BannersTemplate { struct BannersTemplate {
tcx: TemplateCtx, tcx: TemplateCtx,
board: Board, banners: Vec<Banner>,
} }
#[get("/staff/banners/{board}")] #[get("/staff/banners")]
pub async fn banners( pub async fn banners(ctx: Data<Ctx>, req: HttpRequest) -> Result<HttpResponse, NekrochanError> {
ctx: Data<Ctx>,
req: HttpRequest,
board: Path<String>,
) -> Result<HttpResponse, NekrochanError> {
let tcx = TemplateCtx::new(&ctx, &req).await?; let tcx = TemplateCtx::new(&ctx, &req).await?;
let account = account_from_auth(&ctx, &req).await?; let account = account_from_auth(&ctx, &req).await?;
if !(account.perms().owner() || account.perms().board_banners()) { if !(account.perms().owner() || account.perms().banners()) {
return Err(NekrochanError::InsufficientPermissionError); return Err(NekrochanError::InsufficientPermissionError);
} }
let board = board.into_inner(); let banners = Banner::read_all(&ctx).await?;
let board = Board::read(&ctx, board.clone()) let template = BannersTemplate { tcx, banners };
.await?
.ok_or(NekrochanError::BoardNotFound(board))?;
let template = BannersTemplate { tcx, board };
template_response(&template) template_response(&template)
} }

Zobrazit soubor

@ -24,10 +24,7 @@ pub async fn boards(ctx: Data<Ctx>, req: HttpRequest) -> Result<HttpResponse, Ne
let tcx = TemplateCtx::new(&ctx, &req).await?; let tcx = TemplateCtx::new(&ctx, &req).await?;
let account = account_from_auth(&ctx, &req).await?; let account = account_from_auth(&ctx, &req).await?;
if !(account.perms().owner() if !(account.perms().owner() || account.perms().board_config() || account.perms().banners()) {
|| account.perms().board_config()
|| account.perms().board_banners())
{
return Err(NekrochanError::InsufficientPermissionError); return Err(NekrochanError::InsufficientPermissionError);
} }

binární
static/banner.gif

Binární soubor nebyl zobrazen.

Před

Šířka:  |  Výška:  |  Velikost: 206 KiB

binární
static/default-banner.png Normální soubor

Binární soubor nebyl zobrazen.

Za

Šířka:  |  Výška:  |  Velikost: 19 KiB

Zobrazit soubor

@ -8,9 +8,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="center"> <div class="center">
{% if let Some(banner) = board.random_banner() %} <img class="banner" src="/random-banner">
<img class="banner" src="{{ banner.file_url() }}">
{% endif %}
<h1 class="title">Katalog (<a href="/boards/{{ board.id }}">/{{ board.id }}/</a>)</h1> <h1 class="title">Katalog (<a href="/boards/{{ board.id }}">/{{ board.id }}/</a>)</h1>
<p class="description">{{ board.description }}</p> <p class="description">{{ board.description }}</p>
<a href="/boards/{{ board.id }}">Index</a> <a href="/boards/{{ board.id }}">Index</a>

Zobrazit soubor

@ -10,9 +10,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="center"> <div class="center">
{% if let Some(banner) = board.random_banner() %} <img class="banner" src="/random-banner">
<img class="banner" src="{{ banner.file_url() }}">
{% endif %}
<h1 class="title">/{{ board.id }}/ - {{ board.name }}</h1> <h1 class="title">/{{ board.id }}/ - {{ board.name }}</h1>
<p class="description">{{ board.description }}</p> <p class="description">{{ board.description }}</p>
<a href="/boards/{{ board.id }}/catalog">Katalog</a> <a href="/boards/{{ board.id }}/catalog">Katalog</a>

Zobrazit soubor

@ -5,9 +5,6 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="center"> <div class="center">
{% if let Some(banner) = tcx.cfg.site.site_banner %}
<img class="banner" src="{{ banner }}">
{% endif %}
<h1 class="title">{{ tcx.cfg.site.name }}</h1> <h1 class="title">{{ tcx.cfg.site.name }}</h1>
<p class="description">{{ tcx.cfg.site.description }}</p> <p class="description">{{ tcx.cfg.site.description }}</p>
</div> </div>

Zobrazit soubor

@ -3,7 +3,7 @@
<a href="/staff/account">[Účet]</a>&#32; <a href="/staff/account">[Účet]</a>&#32;
<a href="/staff/accounts">[Účty]</a>&#32; <a href="/staff/accounts">[Účty]</a>&#32;
{% if perms.owner() || perms.board_config() || perms.board_banners() %} {% if perms.owner() || perms.board_config() || perms.banners() %}
<a href="/staff/boards">[Nástěnky]</a>&#32; <a href="/staff/boards">[Nástěnky]</a>&#32;
{% endif %} {% endif %}
@ -11,6 +11,10 @@
<a href="/staff/bans">[Bany]</a>&#32; <a href="/staff/bans">[Bany]</a>&#32;
{% endif %} {% endif %}
{% if perms.owner() || perms.banners() %}
<a href="/staff/banners">[Bannery]</a>&#32;
{% endif %}
{% if perms.owner() || perms.reports() %} {% if perms.owner() || perms.reports() %}
<a href="/staff/reports">[Hlášení]</a>&#32; <a href="/staff/reports">[Hlášení]</a>&#32;
{% endif %} {% endif %}

Zobrazit soubor

@ -8,9 +8,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="center"> <div class="center">
{% if let Some(banner) = tcx.cfg.site.site_banner %} <img class="banner" src="/random-banner">
<img class="banner" src="{{ banner }}">
{% endif %}
<h1 class="title">Katalog nadnástěnky</h1> <h1 class="title">Katalog nadnástěnky</h1>
<p class="description">Nově naťuknutá vlákna ze všech nástěnek</p> <p class="description">Nově naťuknutá vlákna ze všech nástěnek</p>
<a href="/overboard">Index</a> <a href="/overboard">Index</a>

Zobrazit soubor

@ -9,9 +9,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="center"> <div class="center">
{% if let Some(banner) = tcx.cfg.site.site_banner %} <img class="banner" src="/random-banner">
<img class="banner" src="{{ banner }}">
{% endif %}
<h1 class="title">Index nadnástěnky</h1> <h1 class="title">Index nadnástěnky</h1>
<p class="description">Nově naťuknutá vlákna ze všech nástěnek</p> <p class="description">Nově naťuknutá vlákna ze všech nástěnek</p>
<a href="/overboard/catalog">Katalog</a> <a href="/overboard/catalog">Katalog</a>

Zobrazit soubor

@ -2,27 +2,25 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Bannery (/{{ board.id }}/){% endblock %} {% block title %}Bannery{% endblock %}
{% block content %} {% block content %}
<h1 class="title">Bannery (/{{ board.id }}/)</h1> <h1 class="title">Bannery</h1>
{% call staff_nav::staff_nav(tcx.perms) %} {% call staff_nav::staff_nav(tcx.perms) %}
<hr> <hr>
<h2>Bannery</h2> <h2>Bannery</h2>
<form method="post" action="/staff/actions/remove-banners"> <form method="post" action="/staff/actions/remove-banners">
<input name="board" type="hidden" value="{{ board.id }}">
<div class="table-wrap"> <div class="table-wrap">
<table class="data-table"> <table class="data-table">
<tr> <tr>
<th></th> <th></th>
<th>Banner</th> <th>Banner</th>
</tr> </tr>
{% for banner in board.banners.0 %} {% for banner in banners %}
<tr> <tr>
<td><input name="banners[]" type="checkbox" value="{{ loop.index0 }}"></td> <td><input name="banners[]" type="checkbox" value="{{ banner.id }}"></td>
<td> <td>
<img class="banner" src="{{ banner.file_url() }}"> <img class="banner" src="{{ banner.banner.file_url() }}">
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -34,8 +32,6 @@
<hr> <hr>
<h2>Přidat bannery</h2> <h2>Přidat bannery</h2>
<form method="post" enctype="multipart/form-data" action="/staff/actions/add-banners"> <form method="post" enctype="multipart/form-data" action="/staff/actions/add-banners">
<input name="board" type="hidden" value="{{ board.id }}">
<table class="form-table"> <table class="form-table">
<tr> <tr>
<td class="label">Bannery</td> <td class="label">Bannery</td>

Zobrazit soubor

@ -18,7 +18,6 @@
<th>Jméno</th> <th>Jméno</th>
<th>Popis</th> <th>Popis</th>
<th>Vytvořena</th> <th>Vytvořena</th>
<th>Bannery</th>
<th>Nastavení</th> <th>Nastavení</th>
</tr> </tr>
{% for board in boards %} {% for board in boards %}
@ -28,7 +27,6 @@
<td>{{ board.name }}</td> <td>{{ board.name }}</td>
<td>{{ board.description }}</td> <td>{{ board.description }}</td>
<td>{{ board.created|czech_datetime }}</td> <td>{{ board.created|czech_datetime }}</td>
<td>{% if tcx.perms.owner() || tcx.perms.board_banners() %}<a href="/staff/banners/{{ board.id }}">[Zobrazit]</a>{% else %}-{% endif %}</td>
<td>{% if tcx.perms.owner() || tcx.perms.board_config() %}<a href="/staff/board-config/{{ board.id }}">[Zobrazit]</a>{% else %}-{% endif %}</td> <td>{% if tcx.perms.owner() || tcx.perms.board_config() %}<a href="/staff/board-config/{{ board.id }}">[Zobrazit]</a>{% else %}-{% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}

Zobrazit soubor

@ -71,10 +71,10 @@
</tr> </tr>
<tr> <tr>
<td class="label">Bannery nástěnek</td> <td class="label">Bannery</td>
<td> <td>
<div class="input-wrapper"> <div class="input-wrapper">
<input name="board_banners" type="checkbox"{% if account.perms().board_banners() %} checked="checked"{% endif %}{% if !tcx.perms.owner() %} disabled=""{% endif %}> <input name="banners" type="checkbox"{% if account.perms().banners() %} checked="checked"{% endif %}{% if !tcx.perms.owner() %} disabled=""{% endif %}>
</div> </div>
</td> </td>
</tr> </tr>

Zobrazit soubor

@ -9,9 +9,7 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="center"> <div class="center">
{% if let Some(banner) = board.random_banner() %} <img class="banner" src="/random-banner">
<img class="banner" src="{{ banner.file_url() }}">
{% endif %}
<h1 class="title">/{{ board.id }}/ - {{ board.name }}</h1> <h1 class="title">/{{ board.id }}/ - {{ board.name }}</h1>
<p class="description">{{ board.description }}</p> <p class="description">{{ board.description }}</p>
<a href="/boards/{{ board.id }}/catalog">Katalog</a> <a href="/boards/{{ board.id }}/catalog">Katalog</a>