Víc džavaskriptu
Tento commit je obsažen v:
rodič
dc07e1f650
revize
3ed82b79a9
@ -1,11 +1,9 @@
|
||||
use captcha::{gen, Difficulty};
|
||||
use redis::{cmd, AsyncCommands, JsonAsyncCommands};
|
||||
use sha256::digest;
|
||||
use sqlx::{query, query_as, types::Json};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::models::{Board, File};
|
||||
use crate::{cfg::BoardCfg, ctx::Ctx, error::NekrochanError, CAPTCHA};
|
||||
use crate::{cfg::BoardCfg, ctx::Ctx, error::NekrochanError};
|
||||
|
||||
impl Board {
|
||||
pub async fn create(
|
||||
@ -216,52 +214,6 @@ impl Board {
|
||||
}
|
||||
}
|
||||
|
||||
impl Board {
|
||||
pub fn thread_captcha(&self) -> Option<(String, String)> {
|
||||
let captcha = match self.config.thread_captcha.as_str() {
|
||||
"easy" => gen(Difficulty::Easy),
|
||||
"medium" => gen(Difficulty::Medium),
|
||||
"hard" => gen(Difficulty::Hard),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let base64 = captcha.as_base64()?;
|
||||
|
||||
let board = self.id.clone();
|
||||
let difficulty = self.config.thread_captcha.clone();
|
||||
let id = digest(base64.as_bytes());
|
||||
|
||||
let key = (board, difficulty, id.clone());
|
||||
let solution = captcha.chars_as_string();
|
||||
|
||||
CAPTCHA.write().ok()?.insert(key, solution);
|
||||
|
||||
Some((id, base64))
|
||||
}
|
||||
|
||||
pub fn reply_captcha(&self) -> Option<(String, String)> {
|
||||
let captcha = match self.config.reply_captcha.as_str() {
|
||||
"easy" => gen(Difficulty::Easy),
|
||||
"medium" => gen(Difficulty::Medium),
|
||||
"hard" => gen(Difficulty::Hard),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let base64 = captcha.as_base64()?;
|
||||
|
||||
let board = self.id.clone();
|
||||
let difficulty = self.config.thread_captcha.clone();
|
||||
let id = digest(base64.as_bytes());
|
||||
|
||||
let key = (board, difficulty, id.clone());
|
||||
let solution = captcha.chars_as_string();
|
||||
|
||||
CAPTCHA.write().ok()?.insert(key, solution);
|
||||
|
||||
Some((id, base64))
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_overboard(ctx: &Ctx, boards: Vec<Board>) -> Result<(), NekrochanError> {
|
||||
query("DROP VIEW IF EXISTS overboard")
|
||||
.execute(ctx.db())
|
||||
|
@ -54,12 +54,16 @@ pub enum NekrochanError {
|
||||
InternalError,
|
||||
#[error("Neplatný autentizační token. Vymaž soubory cookie.")]
|
||||
InvalidAuthError,
|
||||
#[error("Tato CAPTCHA vypršela nebo neexistuje.")]
|
||||
InvalidCaptchaError,
|
||||
#[error("Neplatná strana.")]
|
||||
InvalidPageError,
|
||||
#[error("Obsah musí mít 1-20000 znaků.")]
|
||||
NewsContentFormatError,
|
||||
#[error("Titulek musí mít 1-100 znaků.")]
|
||||
NewsTitleFormatError,
|
||||
#[error("Tato nástěnka nevyžaduje CAPTCHA.")]
|
||||
NoCaptchaError,
|
||||
#[error("Příspěvek musí mít obsah.")]
|
||||
NoContentError,
|
||||
#[error("Příspěvek musí mít soubor.")]
|
||||
@ -86,8 +90,6 @@ pub enum NekrochanError {
|
||||
ReplyReplyError,
|
||||
#[error("Na této nástěnce se musí vyplnit CAPTCHA.")]
|
||||
RequiredCaptchaError,
|
||||
#[error("Tato CAPTCHA neexistuje nebo už byla vyřešena.")]
|
||||
SolvedCaptchaError,
|
||||
#[error("Toto vlákno je uzamčené.")]
|
||||
ThreadLockError,
|
||||
#[error("Tento ban nelze odvolat.")]
|
||||
@ -237,9 +239,11 @@ impl ResponseError for NekrochanError {
|
||||
NekrochanError::InsufficientPermissionError => StatusCode::FORBIDDEN,
|
||||
NekrochanError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
NekrochanError::InvalidAuthError => StatusCode::UNAUTHORIZED,
|
||||
NekrochanError::InvalidCaptchaError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::InvalidPageError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::NewsContentFormatError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::NewsTitleFormatError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::NoCaptchaError => StatusCode::NOT_FOUND,
|
||||
NekrochanError::NoContentError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::NoFileError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::NoPostsError => StatusCode::BAD_REQUEST,
|
||||
@ -253,7 +257,6 @@ impl ResponseError for NekrochanError {
|
||||
NekrochanError::ReplyReplyError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::ReportFormatError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::RequiredCaptchaError => StatusCode::UNAUTHORIZED,
|
||||
NekrochanError::SolvedCaptchaError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::ThreadLockError => StatusCode::FORBIDDEN,
|
||||
NekrochanError::UnappealableError => StatusCode::BAD_REQUEST,
|
||||
NekrochanError::UsernameFormatError => StatusCode::BAD_REQUEST,
|
||||
|
@ -1,11 +1,4 @@
|
||||
use error::NekrochanError;
|
||||
use lazy_static::lazy_static;
|
||||
use std::{collections::HashMap, sync::RwLock};
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CAPTCHA: RwLock<HashMap<(String, String, String), String>> =
|
||||
RwLock::new(HashMap::new());
|
||||
}
|
||||
|
||||
const GENERIC_PAGE_SIZE: i64 = 15;
|
||||
|
||||
|
@ -63,8 +63,9 @@ async fn run() -> Result<(), Error> {
|
||||
.service(web::board::board)
|
||||
.service(web::board_catalog::board_catalog)
|
||||
.service(web::index::index)
|
||||
.service(web::ip_posts::ip_posts)
|
||||
.service(web::captcha::captcha)
|
||||
.service(web::edit_posts::edit_posts)
|
||||
.service(web::ip_posts::ip_posts)
|
||||
.service(web::login::login_get)
|
||||
.service(web::login::login_post)
|
||||
.service(web::logout::logout)
|
||||
|
@ -19,7 +19,6 @@ use crate::{
|
||||
ban_response,
|
||||
tcx::{account_from_auth_opt, ip_from_req},
|
||||
},
|
||||
CAPTCHA,
|
||||
};
|
||||
|
||||
#[derive(MultipartForm)]
|
||||
@ -95,36 +94,23 @@ pub async fn create_post(
|
||||
None => None,
|
||||
};
|
||||
|
||||
let difficulties = ["easy", "medium", "hard"];
|
||||
|
||||
let difficulty = if thread.is_none() {
|
||||
if difficulties.contains(&board.config.0.thread_captcha.as_str()) {
|
||||
Some(board.config.0.thread_captcha.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if difficulties.contains(&board.config.0.reply_captcha.as_str()) {
|
||||
Some(board.config.0.reply_captcha.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(difficulty) = difficulty {
|
||||
if (thread.is_none() && board.config.0.thread_captcha != "off")
|
||||
|| (thread.is_some() && board.config.0.reply_captcha != "off")
|
||||
{
|
||||
let board = board.id.clone();
|
||||
|
||||
let id = form
|
||||
.captcha_id
|
||||
.ok_or(NekrochanError::RequiredCaptchaError)?
|
||||
.0;
|
||||
|
||||
let key = (board, difficulty, id);
|
||||
let key = format!("captcha:{board}:{id}");
|
||||
|
||||
let solution = form
|
||||
.captcha_solution
|
||||
.ok_or(NekrochanError::RequiredCaptchaError)?;
|
||||
|
||||
let actual_solution = CAPTCHA
|
||||
.write()?
|
||||
.remove(&key)
|
||||
.ok_or(NekrochanError::SolvedCaptchaError)?;
|
||||
let actual_solution: String = ctx.cache().get_del(key).await?;
|
||||
|
||||
if solution.trim() != actual_solution {
|
||||
return Err(NekrochanError::IncorrectCaptchaError);
|
||||
|
55
src/web/captcha.rs
Normální soubor
55
src/web/captcha.rs
Normální soubor
@ -0,0 +1,55 @@
|
||||
use ::captcha::{gen, Difficulty};
|
||||
use actix_web::{
|
||||
get,
|
||||
web::{Data, Json, Query},
|
||||
};
|
||||
use redis::AsyncCommands;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha256::digest;
|
||||
|
||||
use crate::{ctx::Ctx, db::models::Board, error::NekrochanError};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CaptchaQuery {
|
||||
pub board: String,
|
||||
pub reply: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct CaptchaResponse {
|
||||
pub png: String,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[get("/captcha")]
|
||||
pub async fn captcha(
|
||||
ctx: Data<Ctx>,
|
||||
Query(query): Query<CaptchaQuery>,
|
||||
) -> Result<Json<CaptchaResponse>, NekrochanError> {
|
||||
let board = Board::read(&ctx, query.board.clone())
|
||||
.await?
|
||||
.ok_or(NekrochanError::BoardNotFound(query.board))?;
|
||||
|
||||
let captcha = match board.config.thread_captcha.as_str() {
|
||||
"easy" => gen(Difficulty::Easy),
|
||||
"medium" => gen(Difficulty::Medium),
|
||||
"hard" => gen(Difficulty::Hard),
|
||||
_ => return Err(NekrochanError::NoCaptchaError),
|
||||
};
|
||||
|
||||
// >NOOOOOOOO YOU NEED TO MAKE A NEW ERROR TYPE FOR THIS ERROR THAT CAN ONLY HAPPEN ONCE IN THE CODE NOOOOOOOOOO
|
||||
let png = captcha.as_base64().ok_or(NekrochanError::NoCaptchaError)?;
|
||||
|
||||
let board = board.id;
|
||||
let id = digest(png.as_bytes());
|
||||
|
||||
let key = format!("captcha:{board}:{id}");
|
||||
let solution = captcha.chars_as_string();
|
||||
|
||||
ctx.cache().set(&key, solution).await?;
|
||||
ctx.cache().expire(&key, 600).await?;
|
||||
|
||||
let res = CaptchaResponse { png, id };
|
||||
|
||||
Ok(Json(res))
|
||||
}
|
@ -32,6 +32,7 @@ pub async fn login_get(ctx: Data<Ctx>, req: HttpRequest) -> Result<HttpResponse,
|
||||
#[derive(Deserialize)]
|
||||
pub struct LogInForm {
|
||||
username: String,
|
||||
#[serde(rename = "account_password")]
|
||||
password: String,
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
use actix_web::{http::StatusCode, HttpRequest, HttpResponse, HttpResponseBuilder};
|
||||
use askama::Template;
|
||||
|
||||
pub mod actions;
|
||||
pub mod board;
|
||||
pub mod board_catalog;
|
||||
pub mod captcha;
|
||||
pub mod edit_posts;
|
||||
pub mod index;
|
||||
pub mod ip_posts;
|
||||
pub mod login;
|
||||
pub mod logout;
|
||||
pub mod news;
|
||||
@ -14,7 +13,9 @@ pub mod overboard_catalog;
|
||||
pub mod staff;
|
||||
pub mod tcx;
|
||||
pub mod thread;
|
||||
pub mod ip_posts;
|
||||
|
||||
use actix_web::{http::StatusCode, HttpRequest, HttpResponse, HttpResponseBuilder};
|
||||
use askama::Template;
|
||||
|
||||
use self::tcx::TemplateCtx;
|
||||
use crate::{ctx::Ctx, db::models::Ban, error::NekrochanError, filters};
|
||||
|
@ -1,5 +1,4 @@
|
||||
use actix_web::HttpRequest;
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use redis::AsyncCommands;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
@ -17,7 +16,6 @@ pub struct TemplateCtx {
|
||||
pub account: Option<String>,
|
||||
pub perms: PermissionWrapper,
|
||||
pub name: Option<String>,
|
||||
pub password: String,
|
||||
pub ip: IpAddr,
|
||||
pub yous: HashSet<String>,
|
||||
}
|
||||
@ -35,16 +33,6 @@ impl TemplateCtx {
|
||||
};
|
||||
|
||||
let name = req.cookie("name").map(|cookie| cookie.value().into());
|
||||
let password_cookie = req.cookie("password").map(|cookie| cookie.value().into());
|
||||
|
||||
let password: String = match password_cookie {
|
||||
Some(password) => password,
|
||||
None => thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(8)
|
||||
.map(char::from)
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let (ip, _) = ip_from_req(req)?;
|
||||
let yous = ctx.cache().zrange(format!("by_ip:{ip}"), 0, -1).await?;
|
||||
@ -56,7 +44,6 @@ impl TemplateCtx {
|
||||
boards,
|
||||
perms,
|
||||
name,
|
||||
password,
|
||||
ip,
|
||||
yous,
|
||||
account,
|
||||
|
25
static/js/captcha.js
Normální soubor
25
static/js/captcha.js
Normální soubor
@ -0,0 +1,25 @@
|
||||
$(function () {
|
||||
$("#get-captcha").click(function () {
|
||||
let btn = $(this);
|
||||
|
||||
let board = btn.attr("data-board");
|
||||
let reply = btn.attr("data-reply");
|
||||
let req_url = `/captcha?board=${board}&reply=${reply}`;
|
||||
|
||||
btn.text("Získat CAPTCHA");
|
||||
btn.prop("disabled", true);
|
||||
btn.addClass("loading");
|
||||
|
||||
$.get(req_url, function (data, _) {
|
||||
try {
|
||||
$("#captcha").replaceWith(`<img id="captcha" src="data:image/png;base64,${data.png}">`);
|
||||
$("#captcha-id").prop("value", data.id);
|
||||
} catch {
|
||||
btn.append(" [Chyba]");
|
||||
}
|
||||
|
||||
btn.prop("disabled", false);
|
||||
btn.removeClass("loading");
|
||||
});
|
||||
});
|
||||
});
|
48
static/js/password.js
Normální soubor
48
static/js/password.js
Normální soubor
@ -0,0 +1,48 @@
|
||||
$(function () {
|
||||
let password = get_cookie("password");
|
||||
|
||||
if (password === "") {
|
||||
password = generate_password();
|
||||
set_cookie("password", password);
|
||||
}
|
||||
|
||||
$('input[name="password"]').prop("value", password);
|
||||
});
|
||||
|
||||
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=/`;
|
||||
}
|
@ -160,12 +160,6 @@ summary {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.captcha {
|
||||
width: 100%;
|
||||
image-rendering: pixelated;
|
||||
border: 1px solid var(--input-border);
|
||||
}
|
||||
|
||||
.main {
|
||||
margin: 8px;
|
||||
}
|
||||
@ -223,7 +217,6 @@ summary {
|
||||
|
||||
.form-table .button,
|
||||
.full-width {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -479,4 +472,17 @@ summary {
|
||||
|
||||
.loading {
|
||||
opacity: 0.5;
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
img#captcha {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
||||
display: block;
|
||||
width: 100%;
|
||||
image-rendering: pixelated;
|
||||
border: 1px solid var(--input-border);
|
||||
margin: 0px auto;
|
||||
}
|
||||
|
@ -11,9 +11,11 @@
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<script src="/static/js/jquery.min.js"></script>
|
||||
<script src="/static/js/expand-image.js"></script>
|
||||
<script src="/static/js/password.js"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</head>
|
||||
<body id="top">
|
||||
<body>
|
||||
<div id="top"></div>
|
||||
<div class="board-links header">
|
||||
<span class="link-group"><a href="/">domov</a></span>
|
||||
{% call board_links::board_links() %}
|
||||
@ -36,7 +38,7 @@
|
||||
</div>
|
||||
<div class="main">
|
||||
{% block content %}{% endblock %}
|
||||
<div id="bottom" class="footer">
|
||||
<div class="footer">
|
||||
<div class="box inline-block">
|
||||
<a href="https://git.nekrofilie.com/sneedmaster/nekrochan">nekrochan</a> - Projekt <a href="https://nekrofilie.com/">Nekrofilie</a>
|
||||
<br>
|
||||
@ -44,5 +46,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="bottom"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
{% block theme %}{{ board.config.0.board_theme }}{% endblock %}
|
||||
{% block title %}/{{ board.id }}/ - {{ board.name }}{% endblock %}
|
||||
{% block scripts %}<script src="/static/js/captcha.js"></script>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
|
@ -13,7 +13,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Heslo</td>
|
||||
<td><input name="password" type="password" required=""></td>
|
||||
<td><input name="account_password" type="password" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><input class="button" type="submit" value="Přihlásit se"></td>
|
||||
|
@ -29,14 +29,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Heslo</td>
|
||||
<td>
|
||||
<input
|
||||
name="password"
|
||||
type="text"
|
||||
autocomplete="new-password"
|
||||
value="{{ tcx.password }}"
|
||||
>
|
||||
</td>
|
||||
<td><input name="password" type="password"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
|
@ -52,33 +52,40 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Heslo <span class="small">(na odstranění)</span></td>
|
||||
<td>
|
||||
<input name="password" type="text" autocomplete="new-password" value="{{ tcx.password }}" required="">
|
||||
</td>
|
||||
<td><input name="password" type="password" required=""></td>
|
||||
</tr>
|
||||
{% if !(tcx.perms.bypass_captcha() || tcx.perms.owner()) %}
|
||||
{% let difficulty %}
|
||||
{% if reply %}
|
||||
{% if let Some((id, base64)) = board.reply_captcha() %}
|
||||
<tr>
|
||||
<td class="label">CAPTCHA</td>
|
||||
<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="">
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% let difficulty = board.config.0.reply_captcha.as_str() %}
|
||||
{% else %}
|
||||
{% if let Some((id, base64)) = board.thread_captcha() %}
|
||||
{% let difficulty = board.config.0.thread_captcha.as_str() %}
|
||||
{% endif %}
|
||||
|
||||
{% if (!reply && board.config.0.thread_captcha != "off") || (reply && board.config.0.reply_captcha != "off") %}
|
||||
<noscript>
|
||||
<tr>
|
||||
<td class="label">CAPTCHA</td>
|
||||
<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="">
|
||||
<td colspan="2">
|
||||
<div class="reply-mode">CAPTCHA vyžaduje JavaScript</div>
|
||||
</td>
|
||||
</tr>
|
||||
</noscript>
|
||||
<tr>
|
||||
<td class="label">
|
||||
CAPTCHA<br>
|
||||
<span class="small">(vyprší za 10 min.)</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="input-wrapper">
|
||||
<table class="full-width">
|
||||
<tr><td><button id="get-captcha" type="button" class="button" data-board="{{ board.id }}" data-reply="{{ reply }}">Získat CAPTCHA</button></td></tr>
|
||||
<tr><td><div id="captcha"></div></tr>
|
||||
<input id="captcha-id" name="captcha_id" type="hidden" required="">
|
||||
<tr><td><input name="captcha_solution" type="text" placeholder="Řešení" required=""></tr></td>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
|
@ -45,7 +45,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Heslo</td>
|
||||
<td><input name="password" type="password" required=""></td>
|
||||
<td><input name="account_password" type="password" required=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><input class="button" type="submit" value="Vytvořit účet"></td>
|
||||
|
@ -76,10 +76,10 @@
|
||||
<option value="off" {% if board.config.0.thread_captcha == "off" %}selected="selected"{% endif %}>
|
||||
Žádná
|
||||
</option>
|
||||
<option value="medium" {% if board.config.0.thread_captcha == "medium" %}selected="selected"{% endif %}>
|
||||
<option value="easy" {% if board.config.0.thread_captcha == "easy" %}selected="selected"{% endif %}>
|
||||
Lehká
|
||||
</option>
|
||||
<option value="easy" {% if board.config.0.thread_captcha == "easy" %}selected="selected"{% endif %}>
|
||||
<option value="medium" {% if board.config.0.thread_captcha == "medium" %}selected="selected"{% endif %}>
|
||||
Střední
|
||||
</option>
|
||||
<option value="hard" {% if board.config.0.thread_captcha == "hard" %}selected="selected"{% endif %}>
|
||||
@ -96,10 +96,10 @@
|
||||
<option value="off" {% if board.config.0.reply_captcha == "off" %}selected="selected"{% endif %}>
|
||||
Žádná
|
||||
</option>
|
||||
<option value="medium" {% if board.config.0.reply_captcha == "medium" %}selected="selected"{% endif %}>
|
||||
<option value="easy" {% if board.config.0.reply_captcha == "easy" %}selected="selected"{% endif %}>
|
||||
Lehká
|
||||
</option>
|
||||
<option value="easy" {% if board.config.0.reply_captcha == "easy" %}selected="selected"{% endif %}>
|
||||
<option value="medium" {% if board.config.0.reply_captcha == "medium" %}selected="selected"{% endif %}>
|
||||
Střední
|
||||
</option>
|
||||
<option value="hard" {% if board.config.0.reply_captcha == "hard" %}selected="selected"{% endif %}>
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
{% block theme %}{{ board.config.0.board_theme }}{% endblock %}
|
||||
{% block title %}/{{ board.id }}/ - {{ thread.content_nomarkup|inline_post }}{% endblock %}
|
||||
{% block scripts %}<script src="/static/js/update-thread.js"></script>{% endblock %}
|
||||
{% block scripts %}<script src="/static/js/captcha.js"></script>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
|
Načítá se…
Odkázat v novém úkolu
Zablokovat Uživatele