JS na rozbalování souborů + IP akce
Tento commit je obsažen v:
rodič
a453130797
revize
eecc1a191c
@ -195,6 +195,11 @@ impl Board {
|
||||
|
||||
update_overboard(ctx, boards).await?;
|
||||
|
||||
query("DELETE FROM bans WHERE board = $1")
|
||||
.bind(&self.id)
|
||||
.execute(ctx.db())
|
||||
.await?;
|
||||
|
||||
query(&format!("DROP TABLE posts_{}", self.id))
|
||||
.execute(ctx.db())
|
||||
.await?;
|
||||
|
@ -197,6 +197,27 @@ impl Post {
|
||||
Ok(posts)
|
||||
}
|
||||
|
||||
pub async fn read_ip_page(
|
||||
ctx: &Ctx,
|
||||
ip: IpAddr,
|
||||
page: i64,
|
||||
) -> Result<Vec<Self>, NekrochanError> {
|
||||
let posts = query_as(
|
||||
r#"SELECT * FROM overboard
|
||||
WHERE ip = $1
|
||||
ORDER BY created DESC
|
||||
LIMIT $2
|
||||
OFFSET $3"#,
|
||||
)
|
||||
.bind(ip)
|
||||
.bind(GENERIC_PAGE_SIZE)
|
||||
.bind((page - 1) * GENERIC_PAGE_SIZE)
|
||||
.fetch_all(ctx.db())
|
||||
.await?;
|
||||
|
||||
Ok(posts)
|
||||
}
|
||||
|
||||
pub async fn read_replies(&self, ctx: &Ctx) -> Result<Vec<Self>, NekrochanError> {
|
||||
let replies = query_as(&format!(
|
||||
"SELECT * FROM posts_{} WHERE thread = $1 ORDER BY sticky DESC, created ASC",
|
||||
|
@ -63,6 +63,7 @@ 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::edit_posts::edit_posts)
|
||||
.service(web::login::login_get)
|
||||
.service(web::login::login_post)
|
||||
|
@ -15,6 +15,7 @@ pub enum Permissions {
|
||||
BoardConfig,
|
||||
News,
|
||||
Jannytext,
|
||||
ViewIPs,
|
||||
BypassBans,
|
||||
BypassBoardLock,
|
||||
BypassThreadLock,
|
||||
@ -83,6 +84,10 @@ impl PermissionWrapper {
|
||||
self.0.contains(Permissions::Jannytext)
|
||||
}
|
||||
|
||||
pub fn view_ips(&self) -> bool {
|
||||
self.0.contains(Permissions::ViewIPs)
|
||||
}
|
||||
|
||||
pub fn bypass_bans(&self) -> bool {
|
||||
self.0.contains(Permissions::BypassBans)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use actix_web::{post, web::Data, HttpRequest, HttpResponse};
|
||||
use chrono::{Duration, Utc};
|
||||
use ipnetwork::IpNetwork;
|
||||
use redis::AsyncCommands;
|
||||
use serde::Deserialize;
|
||||
use std::{collections::HashSet, fmt::Write, net::IpAddr};
|
||||
|
||||
@ -26,6 +27,8 @@ pub struct StaffPostActionsForm {
|
||||
pub remove_files: Option<String>,
|
||||
#[serde(rename = "staff_toggle_spoiler")]
|
||||
pub toggle_spoiler: Option<String>,
|
||||
pub remove_by_ip_board: Option<String>,
|
||||
pub remove_by_ip_global: Option<String>,
|
||||
pub toggle_sticky: Option<String>,
|
||||
pub toggle_lock: Option<String>,
|
||||
pub remove_reports: Option<String>,
|
||||
@ -36,6 +39,7 @@ pub struct StaffPostActionsForm {
|
||||
pub ban_reason: Option<String>,
|
||||
pub ban_duration: Option<u64>,
|
||||
pub ban_range: Option<String>,
|
||||
pub troll_user: Option<String>,
|
||||
}
|
||||
|
||||
#[post("/actions/staff-post-actions")]
|
||||
@ -57,6 +61,7 @@ pub async fn staff_post_actions(
|
||||
let mut locks_toggled = 0;
|
||||
let mut reports_removed = 0;
|
||||
let mut bans_issued = 0;
|
||||
let mut users_trolled = 0;
|
||||
|
||||
for post in &posts {
|
||||
if (form.remove_posts.is_some()
|
||||
@ -90,6 +95,30 @@ pub async fn staff_post_actions(
|
||||
spoilers_toggled += post.files.0.len();
|
||||
}
|
||||
|
||||
if form.remove_by_ip_board.is_some() {
|
||||
let key = format!("by_ip:{}", post.ip);
|
||||
let ip_posts: Vec<String> = ctx.cache().zrange(key, 0, -1).await?;
|
||||
let board_ip_posts = ip_posts
|
||||
.into_iter()
|
||||
.filter(|p| p.starts_with(&format!("{}/", post.board)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for post in get_posts_from_ids(&ctx, &board_ip_posts).await {
|
||||
post.delete(&ctx).await?;
|
||||
posts_removed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if form.remove_by_ip_global.is_some() {
|
||||
let key = format!("by_ip:{}", post.ip);
|
||||
let ip_posts: Vec<String> = ctx.cache().zrange(key, 0, -1).await?;
|
||||
|
||||
for post in get_posts_from_ids(&ctx, &ip_posts).await {
|
||||
post.delete(&ctx).await?;
|
||||
posts_removed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if form.toggle_sticky.is_some() {
|
||||
post.update_sticky(&ctx).await?;
|
||||
stickies_toggled += 1;
|
||||
@ -99,8 +128,15 @@ pub async fn staff_post_actions(
|
||||
post.update_lock(&ctx).await?;
|
||||
locks_toggled += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for post in &posts {
|
||||
if form.remove_reports.is_some() {
|
||||
if !(tcx.perms.owner() || tcx.perms.reports()) {
|
||||
writeln!(&mut response, "[Chyba] Nemáš přístup k hlášením.").ok();
|
||||
continue;
|
||||
}
|
||||
|
||||
post.delete_reports(&ctx).await?;
|
||||
reports_removed += post.reports.0.len();
|
||||
}
|
||||
@ -108,7 +144,7 @@ pub async fn staff_post_actions(
|
||||
|
||||
let mut already_banned = HashSet::new();
|
||||
|
||||
for post in posts {
|
||||
for post in &posts {
|
||||
if let (
|
||||
(Some(_), None) | (None, Some(_)) | (Some(_), Some(_)),
|
||||
Some(reason),
|
||||
@ -132,6 +168,11 @@ pub async fn staff_post_actions(
|
||||
}
|
||||
|
||||
if form.ban_reporters.is_some() {
|
||||
if !(tcx.perms.owner() || tcx.perms.reports()) {
|
||||
writeln!(&mut response, "[Chyba] Nemáš přístup k hlášením.").ok();
|
||||
continue;
|
||||
}
|
||||
|
||||
ips_to_ban.extend(
|
||||
post.reports
|
||||
.0
|
||||
@ -178,6 +219,34 @@ pub async fn staff_post_actions(
|
||||
}
|
||||
}
|
||||
|
||||
for post in &posts {
|
||||
if form.troll_user.is_some() {
|
||||
if !(tcx.perms.owner() || tcx.perms.edit_posts()) {
|
||||
writeln!(&mut response, "[Chyba] Nemáš oprávnění upravovat příspěvky.").ok();
|
||||
continue;
|
||||
}
|
||||
|
||||
if !(tcx.perms.owner() || tcx.perms.view_ips()) {
|
||||
writeln!(&mut response, "[Chyba] Nemáš oprávnění zobrazovat IP adresy.").ok();
|
||||
continue;
|
||||
}
|
||||
|
||||
let content_nomarkup = format!(
|
||||
"{}\n\n##({})##",
|
||||
post.content_nomarkup, post.ip
|
||||
);
|
||||
|
||||
let content = format!(
|
||||
"{}\n\n<span class=\"jannytext\">({})</span>",
|
||||
post.content, post.ip
|
||||
);
|
||||
|
||||
post.update_content(&ctx, content, content_nomarkup).await?;
|
||||
users_trolled += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if posts_removed != 0 {
|
||||
writeln!(
|
||||
&mut response,
|
||||
@ -226,6 +295,14 @@ pub async fn staff_post_actions(
|
||||
.ok();
|
||||
}
|
||||
|
||||
if users_trolled != 0 {
|
||||
writeln!(
|
||||
&mut response,
|
||||
"[Úspěch] Vytroleni uživatelé: {users_trolled}"
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
|
||||
if bans_issued != 0 {
|
||||
writeln!(&mut response, "[Úspěch] Uděleny bany: {bans_issued}").ok();
|
||||
}
|
||||
|
65
src/web/ip_posts.rs
Normální soubor
65
src/web/ip_posts.rs
Normální soubor
@ -0,0 +1,65 @@
|
||||
use actix_web::{
|
||||
get,
|
||||
web::{Data, Path, Query},
|
||||
HttpRequest, HttpResponse,
|
||||
};
|
||||
use askama::Template;
|
||||
use serde::Deserialize;
|
||||
use std::{collections::HashMap, net::IpAddr};
|
||||
|
||||
use crate::{
|
||||
ctx::Ctx,
|
||||
db::models::{Board, Post},
|
||||
error::NekrochanError,
|
||||
filters,
|
||||
web::{tcx::TemplateCtx, template_response},
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct IpPostsQuery {
|
||||
page: i64,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "ip-posts.html")]
|
||||
struct IpPostsTemplate {
|
||||
tcx: TemplateCtx,
|
||||
ip: IpAddr,
|
||||
boards: HashMap<String, Board>,
|
||||
posts: Vec<Post>,
|
||||
page: i64,
|
||||
}
|
||||
|
||||
#[get("/ip-posts/{ip}")]
|
||||
pub async fn ip_posts(
|
||||
ctx: Data<Ctx>,
|
||||
req: HttpRequest,
|
||||
path: Path<IpAddr>,
|
||||
query: Option<Query<IpPostsQuery>>,
|
||||
) -> Result<HttpResponse, NekrochanError> {
|
||||
let tcx = TemplateCtx::new(&ctx, &req).await?;
|
||||
|
||||
if !(tcx.perms.owner() || tcx.perms.view_ips()) {
|
||||
return Err(NekrochanError::InsufficientPermissionError);
|
||||
}
|
||||
|
||||
let ip = path.into_inner();
|
||||
let boards = Board::read_all_map(&ctx).await?;
|
||||
let page = query.map_or(1, |q| q.page);
|
||||
|
||||
if page <= 0 {
|
||||
return Err(NekrochanError::InvalidPageError);
|
||||
}
|
||||
|
||||
let posts = Post::read_ip_page(&ctx, ip, page).await?;
|
||||
|
||||
let template = IpPostsTemplate {
|
||||
tcx,
|
||||
ip,
|
||||
boards,
|
||||
posts,
|
||||
page,
|
||||
};
|
||||
|
||||
template_response(&template)
|
||||
}
|
@ -14,6 +14,7 @@ pub mod overboard_catalog;
|
||||
pub mod staff;
|
||||
pub mod tcx;
|
||||
pub mod thread;
|
||||
pub mod ip_posts;
|
||||
|
||||
use self::tcx::TemplateCtx;
|
||||
use crate::{ctx::Ctx, db::models::Ban, error::NekrochanError, filters};
|
||||
|
@ -18,9 +18,10 @@ pub struct UpdatePermissionsForm {
|
||||
reports: Option<String>,
|
||||
bans: Option<String>,
|
||||
banners: Option<String>,
|
||||
board_config: Option<String>,
|
||||
news: Option<String>,
|
||||
jannytext: Option<String>,
|
||||
board_config: Option<String>,
|
||||
view_ips: Option<String>,
|
||||
bypass_bans: Option<String>,
|
||||
bypass_board_lock: Option<String>,
|
||||
bypass_thread_lock: Option<String>,
|
||||
@ -91,6 +92,10 @@ pub async fn update_permissions(
|
||||
permissions |= Permissions::Jannytext;
|
||||
}
|
||||
|
||||
if form.view_ips.is_some() {
|
||||
permissions |= Permissions::ViewIPs;
|
||||
}
|
||||
|
||||
if form.bypass_bans.is_some() {
|
||||
permissions |= Permissions::BypassBans;
|
||||
}
|
||||
|
86
static/js/expand-image.js
Normální soubor
86
static/js/expand-image.js
Normální soubor
@ -0,0 +1,86 @@
|
||||
$(function () {
|
||||
$(".expandable").click(function () {
|
||||
let src_link = $(this).attr("href");
|
||||
|
||||
let is_video = [".mpeg", ".mov", ".mp4", ".webm", ".mkv", ".ogg"].some(
|
||||
(ext) => src_link.endsWith(ext)
|
||||
);
|
||||
|
||||
if (!is_video) {
|
||||
toggle_image($(this), src_link);
|
||||
} else {
|
||||
toggle_video($(this), src_link);
|
||||
}
|
||||
|
||||
$(this).toggleClass("expanded");
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
function toggle_image(parent, src_link) {
|
||||
let expanded = parent.hasClass("expanded");
|
||||
let thumb = parent.find(".thumb");
|
||||
let src = parent.find(".src");
|
||||
|
||||
if (!expanded) {
|
||||
if (src.length === 0) {
|
||||
thumb.css("opacity", 0.5);
|
||||
parent.append(`<img class="src" src="${src_link}">`);
|
||||
|
||||
let src = parent.find(".src");
|
||||
|
||||
src.hide();
|
||||
|
||||
src.on("load", function () {
|
||||
thumb.hide();
|
||||
thumb.css("opacity", "");
|
||||
src.show();
|
||||
});
|
||||
} else {
|
||||
thumb.hide();
|
||||
src.show();
|
||||
}
|
||||
} else {
|
||||
src.hide();
|
||||
thumb.show();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle_video(parent, src_link) {
|
||||
let expanded = parent.hasClass("expanded");
|
||||
let thumb = parent.find(".thumb");
|
||||
let src = parent.parent().find(".src");
|
||||
|
||||
if (!expanded) {
|
||||
if (src.length === 0) {
|
||||
thumb.css("opacity", 0.5);
|
||||
|
||||
parent.append('<span class="closer">[Zavřít]<br></span>')
|
||||
|
||||
parent.parent().append(
|
||||
`<video class="src" src="${src_link}" autoplay="" controls="" loop=""></video>`
|
||||
);
|
||||
|
||||
let src = parent.parent().find(".src");
|
||||
|
||||
src.hide();
|
||||
|
||||
src.on("loadstart", function () {
|
||||
thumb.hide();
|
||||
thumb.css("opacity", "");
|
||||
src.show();
|
||||
});
|
||||
} else {
|
||||
thumb.hide();
|
||||
src.show();
|
||||
src.get(0).play();
|
||||
parent.find(".closer").show();
|
||||
}
|
||||
} else {
|
||||
src.get(0).pause();
|
||||
src.hide();
|
||||
thumb.show();
|
||||
parent.find(".closer").hide();
|
||||
}
|
||||
}
|
2
static/js/jquery.min.js
vendorováno
Normální soubor
2
static/js/jquery.min.js
vendorováno
Normální soubor
Rozdílový obsah nebyl zobrazen, protože některé řádky jsou příliš dlouhá
404
static/style.css
404
static/style.css
@ -1,62 +1,63 @@
|
||||
:root {
|
||||
font-size: 10pt;
|
||||
font-family: var(--font);
|
||||
color: var(--text);
|
||||
font-size: 10pt;
|
||||
font-family: var(--font);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
background: var(--bg);
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
background: var(--bg);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-color);
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--link-hover);
|
||||
color: var(--link-hover);
|
||||
}
|
||||
|
||||
details {
|
||||
display: inline-block;
|
||||
margin-bottom: 8px;
|
||||
display: inline-block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
details:last-of-type {
|
||||
margin-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
img,
|
||||
video {
|
||||
max-height: 90vh;
|
||||
max-width: 100%;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px solid var(--hr-color);
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
border-top: 1px solid var(--hr-color);
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
summary {
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-table .label {
|
||||
font-weight: bold;
|
||||
background-color: var(--table-head);
|
||||
border: 1px solid var(--table-border);
|
||||
padding: 4px;
|
||||
font-weight: bold;
|
||||
background-color: var(--table-head);
|
||||
border: 1px solid var(--table-border);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.form-table td {
|
||||
padding: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.edit-box {
|
||||
display: block;
|
||||
width: 100%;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-table input[type="text"],
|
||||
@ -66,407 +67,408 @@ summary {
|
||||
.form-table select,
|
||||
.input-wrapper,
|
||||
.edit-box {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
||||
display: block;
|
||||
width: 100%;
|
||||
color: var(--text);
|
||||
background-color: var(--input-color);
|
||||
border-radius: 0;
|
||||
border: 1px solid var(--input-border);
|
||||
padding: 4px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
color: var(--text);
|
||||
background-color: var(--input-color);
|
||||
border-radius: 0;
|
||||
border: 1px solid var(--input-border);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.form-table input[type="checkbox"] {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.form-table textarea,
|
||||
.edit-box {
|
||||
height: 8rem;
|
||||
resize: none;
|
||||
height: 8rem;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.reply-mode {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
background-color: var(--table-head);
|
||||
border: 1px solid var(--table-border);
|
||||
padding: 8px;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
background-color: var(--table-head);
|
||||
border: 1px solid var(--table-border);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.container > form > .form-table {
|
||||
margin: 8px auto;
|
||||
margin: 8px auto;
|
||||
}
|
||||
|
||||
.table-wrap {
|
||||
overflow: scroll;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
margin: 8px 0;
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
background-color: var(--table-head);
|
||||
background-color: var(--table-head);
|
||||
}
|
||||
|
||||
.data-table td {
|
||||
background-color: var(--table-background);
|
||||
background-color: var(--table-background);
|
||||
}
|
||||
|
||||
.data-table td:not(.form-table td),
|
||||
.data-table th {
|
||||
border: 1px solid var(--table-border);
|
||||
padding: 4px;
|
||||
border: 1px solid var(--table-border);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.data-table .banner {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.news {
|
||||
margin: 8px 0;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.box {
|
||||
background-color: var(--box-color);
|
||||
border-right: 1px solid var(--box-border);
|
||||
border-bottom: 1px solid var(--box-border);
|
||||
padding: 8px;
|
||||
background-color: var(--box-color);
|
||||
border-right: 1px solid var(--box-border);
|
||||
border-bottom: 1px solid var(--box-border);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.box:target {
|
||||
background-color: var(--hl-box-color);
|
||||
border-right: 1px solid var(--hl-box-border);
|
||||
border-bottom: 1px solid var(--hl-box-border);
|
||||
background-color: var(--hl-box-color);
|
||||
border-right: 1px solid var(--hl-box-border);
|
||||
border-bottom: 1px solid var(--hl-box-border);
|
||||
}
|
||||
|
||||
.button {
|
||||
cursor: pointer;
|
||||
border-radius: 0;
|
||||
color: var(--text);
|
||||
background-color: var(--input-color);
|
||||
border: 1px solid var(--input-border);
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
border-radius: 0;
|
||||
color: var(--text);
|
||||
background-color: var(--input-color);
|
||||
border: 1px solid var(--input-border);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.captcha {
|
||||
width: 100%;
|
||||
image-rendering: pixelated;
|
||||
border: 1px solid var(--input-border);
|
||||
width: 100%;
|
||||
image-rendering: pixelated;
|
||||
border: 1px solid var(--input-border);
|
||||
}
|
||||
|
||||
.main {
|
||||
margin: 8px;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--title-color);
|
||||
font-family: var(--title-font);
|
||||
text-align: center;
|
||||
letter-spacing: -2px;
|
||||
margin: 0;
|
||||
color: var(--title-color);
|
||||
font-family: var(--title-font);
|
||||
text-align: center;
|
||||
letter-spacing: -2px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.big {
|
||||
font-size: 1.2rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.float-r {
|
||||
float: right;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.fixed-table {
|
||||
table-layout: fixed;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.m-0 {
|
||||
margin: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.form-table .button,
|
||||
.full-width {
|
||||
display: block;
|
||||
width: 100%;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.banner {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin: 8px auto;
|
||||
border: 1px solid var(--box-border);
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin: 8px auto;
|
||||
border: 1px solid var(--box-border);
|
||||
}
|
||||
|
||||
.headline {
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.headline::after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.board-links,
|
||||
.pagination {
|
||||
color: var(--link-list-color);
|
||||
color: var(--link-list-color);
|
||||
}
|
||||
|
||||
.link-separator::after {
|
||||
content: " / ";
|
||||
content: " / ";
|
||||
}
|
||||
|
||||
.link-group::before {
|
||||
content: " [ ";
|
||||
content: " [ ";
|
||||
}
|
||||
|
||||
.link-group::after {
|
||||
content: " ] ";
|
||||
content: " ] ";
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 2px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.header::after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
font-size: 8pt;
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
font-size: 8pt;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.post {
|
||||
margin-bottom: 8px;
|
||||
padding: 8px;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.post.box {
|
||||
display: inline-block;
|
||||
min-width: 400px;
|
||||
display: inline-block;
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.post:last-of-type {
|
||||
margin-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.post::after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.board-links a,
|
||||
.pagination a,
|
||||
.post-number {
|
||||
text-decoration: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.post-header input[type="checkbox"] {
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.catalog-entry {
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
height: 250px;
|
||||
overflow: scroll;
|
||||
margin: 4px;
|
||||
padding: 8px;
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
height: 250px;
|
||||
overflow: scroll;
|
||||
margin: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.catalog-entry .thumb {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-height: 50%;
|
||||
box-shadow: 0 0 3px #000;
|
||||
margin: 4px auto;
|
||||
padding: 2px;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-height: 50%;
|
||||
box-shadow: 0 0 3px #000;
|
||||
margin: 4px auto;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.catalog-entry .post-content {
|
||||
margin: 8px;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: bold;
|
||||
color: var(--name-color);
|
||||
font-weight: bold;
|
||||
color: var(--name-color);
|
||||
}
|
||||
|
||||
.tripcode {
|
||||
color: var(--trip-color);
|
||||
color: var(--trip-color);
|
||||
}
|
||||
|
||||
.capcode {
|
||||
color: var(--capcode-color);
|
||||
font-weight: bold;
|
||||
color: var(--capcode-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
text-shadow: #000 0 0 1px, #000 0 0 1px, #000 0 0 1px, #000 0 0 1px,
|
||||
#000 0 0 1px, #000 0 0 1px;
|
||||
color: #ffffff;
|
||||
border: 1px solid var(--box-border);
|
||||
padding: 0 2px;
|
||||
text-shadow: #000 0 0 1px, #000 0 0 1px, #000 0 0 1px, #000 0 0 1px,
|
||||
#000 0 0 1px, #000 0 0 1px;
|
||||
color: #ffffff;
|
||||
border: 1px solid var(--box-border);
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.post-files {
|
||||
float: left;
|
||||
margin: 0 8px 8px 8px;
|
||||
float: left;
|
||||
margin: 0 8px 8px 8px;
|
||||
}
|
||||
|
||||
.multi-files {
|
||||
float: none;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.post-file {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
text-align: center;
|
||||
font-size: 8pt;
|
||||
padding: 4px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
text-align: center;
|
||||
font-size: 8pt;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.thumb {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.post-content {
|
||||
font-family: inherit;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.post .post-content {
|
||||
margin: 1rem 2rem;
|
||||
margin: 1rem 2rem;
|
||||
}
|
||||
|
||||
.post-content a {
|
||||
color: var(--post-link-color);
|
||||
color: var(--post-link-color);
|
||||
}
|
||||
|
||||
.post-content a:hover {
|
||||
color: var(--post-link-hover);
|
||||
color: var(--post-link-hover);
|
||||
}
|
||||
|
||||
.dead-quote {
|
||||
color: var(--dead-quote-color);
|
||||
text-decoration: line-through;
|
||||
color: var(--dead-quote-color);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.greentext {
|
||||
color: var(--greentext-color);
|
||||
color: var(--greentext-color);
|
||||
}
|
||||
|
||||
.orangetext {
|
||||
color: var(--orangetext-color);
|
||||
color: var(--orangetext-color);
|
||||
}
|
||||
|
||||
.redtext {
|
||||
color: var(--redtext-color);
|
||||
font-weight: bold;
|
||||
color: var(--redtext-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.bluetext {
|
||||
color: var(--bluetext-color);
|
||||
font-weight: bold;
|
||||
color: var(--bluetext-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.glowtext {
|
||||
text-shadow: 0 0 40px #00fe20, 0 0 2px #00fe20;
|
||||
text-shadow: 0 0 40px #00fe20, 0 0 2px #00fe20;
|
||||
}
|
||||
|
||||
.uh-oh-text {
|
||||
color: var(--uh-oh-text);
|
||||
background-color: var(--uh-oh-color);
|
||||
color: var(--uh-oh-text);
|
||||
background-color: var(--uh-oh-color);
|
||||
}
|
||||
|
||||
.spoiler {
|
||||
color: var(--text);
|
||||
background-color: var(--text);
|
||||
color: var(--text);
|
||||
background-color: var(--text);
|
||||
}
|
||||
|
||||
.spoiler:hover {
|
||||
background-color: transparent;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.jannytext {
|
||||
color: var(--jannytext-color);
|
||||
font-weight: bold;
|
||||
color: var(--jannytext-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 0.8em;
|
||||
vertical-align: middle;
|
||||
height: 0.8em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.posts-omitted {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.board-list {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.thumb {
|
||||
max-width: 100px;
|
||||
max-height: 100px;
|
||||
}
|
||||
.thumb {
|
||||
max-width: 100px;
|
||||
max-height: 100px;
|
||||
}
|
||||
|
||||
.post.box {
|
||||
display: block;
|
||||
min-width: unset;
|
||||
}
|
||||
.post.box {
|
||||
display: block;
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.thread > br {
|
||||
display: none;
|
||||
}
|
||||
.thread > br {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.catalog-entry {
|
||||
width: 140px;
|
||||
height: 220px;
|
||||
}
|
||||
.catalog-entry {
|
||||
width: 140px;
|
||||
height: 220px;
|
||||
}
|
||||
}
|
||||
|
@ -9,10 +9,10 @@
|
||||
<meta name="description" content="{{ tcx.cfg.site.description }}">
|
||||
<link rel="stylesheet" href='/static/themes/{% block theme %}{% include "../theme.txt" %}{% endblock %}'>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<!-- Muh flash of unstyled content -->
|
||||
<script>0</script>
|
||||
<script src="/static/js/jquery.min.js"></script>
|
||||
<script src="/static/js/expand-image.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body id="top">
|
||||
<div class="board-links header">
|
||||
<span class="link-group"><a href="/">domov</a></span>
|
||||
{% call board_links::board_links() %}
|
||||
@ -35,7 +35,7 @@
|
||||
</div>
|
||||
<div class="main">
|
||||
{% block content %}{% endblock %}
|
||||
<div class="footer">
|
||||
<div id="bottom" 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>
|
||||
|
@ -21,7 +21,9 @@
|
||||
<div class="box">
|
||||
<h2 class="headline">
|
||||
<span>{{ news.title }}</span>
|
||||
<span class="float-r">{{ news.author }} - {{ news.created|czech_datetime }}</span>
|
||||
<span class="float-r">
|
||||
{{ news.author }} - <span title="{{ news.created|czech_humantime }}">{{ news.created|czech_datetime }}</span>
|
||||
</span>
|
||||
</h2>
|
||||
<hr>
|
||||
<div class="post-content">{{ news.content|safe }}</div>
|
||||
|
26
templates/ip-posts.html
Normální soubor
26
templates/ip-posts.html
Normální soubor
@ -0,0 +1,26 @@
|
||||
{% import "./macros/post-actions.html" as post_actions %}
|
||||
{% import "./macros/post.html" as post %}
|
||||
{% import "./macros/static-pagination.html" as static_pagination %}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Příspěvky od [{{ ip }}]{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title">Příspěvky od [{{ ip }}]</h1>
|
||||
<hr>
|
||||
<form method="post">
|
||||
<div class="thread">
|
||||
{% for post in posts %}
|
||||
<b>Příspěvek z <a href="/boards/{{ post.board }}">/{{ post.board }}/</a></b>
|
||||
<br>
|
||||
{% call post::post(boards[post.board.as_str()], post, true) %}
|
||||
<br>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<hr>
|
||||
{% call static_pagination::static_pagination("/ip-posts/{}"|format(ip), page) %}
|
||||
<hr>
|
||||
{% call post_actions::post_actions() %}
|
||||
</form>
|
||||
{% endblock %}
|
@ -67,7 +67,7 @@
|
||||
</table>
|
||||
</details>
|
||||
<br>
|
||||
{% if tcx.perms.owner() || tcx.perms.manage_posts() || tcx.perms.bans() || tcx.perms.edit_posts() %}
|
||||
{% if tcx.perms.owner() || tcx.perms.edit_posts() || tcx.perms.manage_posts() || tcx.perms.reports() || tcx.perms.bans() %}
|
||||
<details>
|
||||
<summary class="box">Uklízečské akce</summary>
|
||||
<table class="form-table">
|
||||
@ -96,6 +96,22 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Odstranit od IP na nástěnce</td>
|
||||
<td>
|
||||
<div class="input-wrapper">
|
||||
<input name="remove_by_ip_board" type="checkbox">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Odstranit od IP globálně</td>
|
||||
<td>
|
||||
<div class="input-wrapper">
|
||||
<input name="remove_by_ip_global" type="checkbox">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Připnout/odepnout</td>
|
||||
<td>
|
||||
@ -177,6 +193,14 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if tcx.perms.owner() || (tcx.perms.edit_posts() && tcx.perms.view_ips()) %}
|
||||
<td class="label">Vytrolit uživatele</td>
|
||||
<td>
|
||||
<div class="input-wrapper">
|
||||
<input name="troll_user" type="checkbox">
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input
|
||||
|
@ -2,6 +2,9 @@
|
||||
<div id="{{ post.id }}" class="post{% if boxed %} box{% endif %}">
|
||||
<div class="post-header">
|
||||
<input name="posts[]" type="checkbox" value="{{ post.board }}/{{ post.id }}"> 
|
||||
{% if tcx.perms.owner() || tcx.perms.view_ips() %}
|
||||
<b><a title="Více příspěvků od uživatele [{{ post.ip }}]" href="/ip-posts/{{ post.ip }}">[+]</a></b> 
|
||||
{% endif %}
|
||||
{% if let Some(email) = post.email %}
|
||||
<a class="name" rel="nofollow" href="mailto:{{ email }}">{{ post.name }}</a> 
|
||||
{% else %}
|
||||
@ -11,14 +14,14 @@
|
||||
<span class="tripcode">{{ tripcode }}</span> 
|
||||
{% endif %}
|
||||
{% if let Some(capcode) = post.capcode %}
|
||||
<span class="capcode">## {{ capcode }} <img class="icon" src="/favicon.ico"></span> 
|
||||
<span title="Tento uživatel to dělá ZDARMA!" class="capcode">## {{ capcode }} <img class="icon" src="/favicon.ico"></span> 
|
||||
{% endif %}
|
||||
{% if tcx.ip == post.ip %}
|
||||
{# Technically not a tripcode or something but same styling #}
|
||||
<i class="tripcode">(Ty)</i> 
|
||||
{% endif %}
|
||||
{% if board.config.0.flags %}
|
||||
<img class="icon" src="/static/flags/{{ post.country }}.png"> 
|
||||
<img title="Země: {{ post.country }}" class="icon" src="/static/flags/{{ post.country }}.png"> 
|
||||
{% endif %}
|
||||
<span title="{{ post.created|czech_datetime }}">{{ post.created|czech_humantime }}</span> 
|
||||
{% if board.config.0.user_ids %}
|
||||
@ -26,10 +29,10 @@
|
||||
{% endif %}
|
||||
<a class="post-number" href="{{ post.post_url() }}">Č.{{ post.id }}</a> 
|
||||
{% if post.sticky %}
|
||||
<img class="icon" src="/static/icons/sticky.png"> 
|
||||
<img title="Připnuto" class="icon" src="/static/icons/sticky.png"> 
|
||||
{% endif %}
|
||||
{% if post.locked %}
|
||||
<img class="icon" src="/static/icons/locked.png"> 
|
||||
<img title="Uzamčeno" class="icon" src="/static/icons/locked.png"> 
|
||||
{% endif %}
|
||||
|
||||
{% if !boxed %}
|
||||
@ -40,7 +43,7 @@
|
||||
<div class="post-files{% if post.files.0.len() > 1 %} multi-files{% endif %}">
|
||||
{% for file in post.files.0 %}
|
||||
<div class="post-file">
|
||||
<a download="{{ file.original_name }}" href="{{ file.file_url() }}">
|
||||
<a title="Stáhnout ({{ file.original_name }})" download="{{ file.original_name }}" href="{{ file.file_url() }}">
|
||||
{% if file.spoiler %}
|
||||
[Spoiler]
|
||||
{% else %}
|
||||
@ -50,7 +53,7 @@
|
||||
<br>
|
||||
({{ file.size|filesizeformat }}, {{ file.width }}x{{ file.height }})
|
||||
<br>
|
||||
<a target="_blank" href="{{ file.file_url() }}">
|
||||
<a class="expandable" target="_blank" href="{{ file.file_url() }}">
|
||||
<img class="thumb" src="{{ file.thumb_url() }}">
|
||||
</a>
|
||||
</div>
|
||||
|
@ -9,7 +9,9 @@
|
||||
<div class="news box">
|
||||
<h2 class="headline">
|
||||
<span>{{ newspost.title }}</span>
|
||||
<span class="float-r">{{ newspost.author }} - {{ newspost.created|czech_datetime }}</span>
|
||||
<span class="float-r">
|
||||
{{ newspost.author }} - <span title="{{ newspost.created|czech_humantime }}">{{ newspost.created|czech_datetime }}</span>
|
||||
</span>
|
||||
</h2>
|
||||
<hr>
|
||||
<div class="post-content">{{ newspost.content|safe }}</div>
|
||||
|
@ -24,7 +24,7 @@
|
||||
<td><input name="accounts[]" type="checkbox" value="{{ account.username }}" {% if !tcx.perms.owner() %}disabled=""{% endif %}></td>
|
||||
<td>{{ account.username }}</td>
|
||||
<td>{% if account.owner %}Ano{% else %}Ne{% endif %}</td>
|
||||
<td>{{ account.created|czech_datetime }}</td>
|
||||
<td title="{{ account.created|czech_humantime }}">{{ account.created|czech_datetime }}</td>
|
||||
<td>{{ account.permissions.0 }} <a href="/staff/permissions/{{ account.username }}">[Zobrazit]</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
@ -38,8 +38,12 @@
|
||||
<td>{{ ban.issued_by }}</td>
|
||||
<td>{% if ban.appealable %}Ano{% else %}Ne{% endif %}</td>
|
||||
<td>{% if let Some(appeal) = ban.appeal %}<div class="post-content">{{ appeal }}</div>{% else %}-{% endif %}</td>
|
||||
<td>{{ ban.created|czech_datetime }}</td>
|
||||
<td>{% if let Some(expires) = ban.expires %}<span title="{{ expires|czech_datetime }}">{{ expires|czech_humantime }}</span>{% else %}<i>Nikdy</i>{% endif %}</td>
|
||||
<td title="ban.{{ ban.created|czech_humantime }}">{{ ban.created|czech_datetime }}</td>
|
||||
{% if let Some(expires) = ban.expires %}
|
||||
<td title="{{ expires|czech_humantime }}">{{ expires|czech_datetime }}</td>
|
||||
{% else %}
|
||||
<td>Nikdy</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
@ -26,7 +26,7 @@
|
||||
<td>/{{ board.id }}/</td>
|
||||
<td>{{ board.name }}</td>
|
||||
<td>{{ board.description }}</td>
|
||||
<td>{{ board.created|czech_datetime }}</td>
|
||||
<td title="{{ board.created|czech_humantime }}">{{ board.created|czech_datetime }}</td>
|
||||
<td>{% if tcx.perms.owner() || tcx.perms.board_config() %}<a href="/staff/board-config/{{ board.id }}">[Zobrazit]</a>{% else %}-{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
@ -11,7 +11,9 @@
|
||||
<div class="box">
|
||||
<h2 class="headline">
|
||||
<span>{{ newspost.title }}</span>
|
||||
<span class="float-r">{{ newspost.author }} - {{ newspost.created|czech_datetime }}</span>
|
||||
<span class="float-r">
|
||||
{{ newspost.author }} - <span title="{{ newspost.created|czech_humantime }}">{{ newspost.created|czech_datetime }}</span>
|
||||
</span>
|
||||
</h2>
|
||||
<hr>
|
||||
<textarea class="edit-box" name="{{ newspost.id }}">{{ newspost.content_nomarkup }}</textarea>
|
||||
|
@ -115,6 +115,15 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Zobrazit IP adresy</td>
|
||||
<td>
|
||||
<div class="input-wrapper">
|
||||
<input name="view_ips" type="checkbox"{% if account.perms().view_ips() %} checked="checked"{% endif %}{% if !tcx.perms.owner() %} disabled=""{% endif %}>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="label">Obejít ban</td>
|
||||
<td>
|
||||
|
@ -16,6 +16,8 @@
|
||||
<hr>
|
||||
<form method="post">
|
||||
{% for post in posts %}
|
||||
<b>Příspěvek z <a href="/boards/{{ post.board }}">/{{ post.board }}/</a></b>
|
||||
<br>
|
||||
{% call post::post(boards[post.board.as_str()], post, true) %}
|
||||
<table class="data-table inline-block m-0">
|
||||
<tr>
|
||||
|
Načítá se…
Odkázat v novém úkolu
Zablokovat Uživatele