JS na rozbalování souborů + IP akce

Tento commit je obsažen v:
sneedmaster 2024-02-18 20:25:38 +01:00
rodič a453130797
revize eecc1a191c
23 změnil soubory, kde provedl 565 přidání a 221 odebrání

Zobrazit soubor

@ -195,6 +195,11 @@ impl Board {
update_overboard(ctx, boards).await?; 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)) query(&format!("DROP TABLE posts_{}", self.id))
.execute(ctx.db()) .execute(ctx.db())
.await?; .await?;

Zobrazit soubor

@ -197,6 +197,27 @@ impl Post {
Ok(posts) 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> { pub async fn read_replies(&self, ctx: &Ctx) -> Result<Vec<Self>, NekrochanError> {
let replies = query_as(&format!( let replies = query_as(&format!(
"SELECT * FROM posts_{} WHERE thread = $1 ORDER BY sticky DESC, created ASC", "SELECT * FROM posts_{} WHERE thread = $1 ORDER BY sticky DESC, created ASC",

Zobrazit soubor

@ -63,6 +63,7 @@ async fn run() -> Result<(), Error> {
.service(web::board::board) .service(web::board::board)
.service(web::board_catalog::board_catalog) .service(web::board_catalog::board_catalog)
.service(web::index::index) .service(web::index::index)
.service(web::ip_posts::ip_posts)
.service(web::edit_posts::edit_posts) .service(web::edit_posts::edit_posts)
.service(web::login::login_get) .service(web::login::login_get)
.service(web::login::login_post) .service(web::login::login_post)

Zobrazit soubor

@ -15,6 +15,7 @@ pub enum Permissions {
BoardConfig, BoardConfig,
News, News,
Jannytext, Jannytext,
ViewIPs,
BypassBans, BypassBans,
BypassBoardLock, BypassBoardLock,
BypassThreadLock, BypassThreadLock,
@ -83,6 +84,10 @@ impl PermissionWrapper {
self.0.contains(Permissions::Jannytext) self.0.contains(Permissions::Jannytext)
} }
pub fn view_ips(&self) -> bool {
self.0.contains(Permissions::ViewIPs)
}
pub fn bypass_bans(&self) -> bool { pub fn bypass_bans(&self) -> bool {
self.0.contains(Permissions::BypassBans) self.0.contains(Permissions::BypassBans)
} }

Zobrazit soubor

@ -1,6 +1,7 @@
use actix_web::{post, web::Data, HttpRequest, HttpResponse}; use actix_web::{post, web::Data, HttpRequest, HttpResponse};
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
use ipnetwork::IpNetwork; use ipnetwork::IpNetwork;
use redis::AsyncCommands;
use serde::Deserialize; use serde::Deserialize;
use std::{collections::HashSet, fmt::Write, net::IpAddr}; use std::{collections::HashSet, fmt::Write, net::IpAddr};
@ -26,6 +27,8 @@ pub struct StaffPostActionsForm {
pub remove_files: Option<String>, pub remove_files: Option<String>,
#[serde(rename = "staff_toggle_spoiler")] #[serde(rename = "staff_toggle_spoiler")]
pub toggle_spoiler: Option<String>, 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_sticky: Option<String>,
pub toggle_lock: Option<String>, pub toggle_lock: Option<String>,
pub remove_reports: Option<String>, pub remove_reports: Option<String>,
@ -36,6 +39,7 @@ pub struct StaffPostActionsForm {
pub ban_reason: Option<String>, pub ban_reason: Option<String>,
pub ban_duration: Option<u64>, pub ban_duration: Option<u64>,
pub ban_range: Option<String>, pub ban_range: Option<String>,
pub troll_user: Option<String>,
} }
#[post("/actions/staff-post-actions")] #[post("/actions/staff-post-actions")]
@ -57,6 +61,7 @@ pub async fn staff_post_actions(
let mut locks_toggled = 0; let mut locks_toggled = 0;
let mut reports_removed = 0; let mut reports_removed = 0;
let mut bans_issued = 0; let mut bans_issued = 0;
let mut users_trolled = 0;
for post in &posts { for post in &posts {
if (form.remove_posts.is_some() if (form.remove_posts.is_some()
@ -90,6 +95,30 @@ pub async fn staff_post_actions(
spoilers_toggled += post.files.0.len(); 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() { if form.toggle_sticky.is_some() {
post.update_sticky(&ctx).await?; post.update_sticky(&ctx).await?;
stickies_toggled += 1; stickies_toggled += 1;
@ -99,8 +128,15 @@ pub async fn staff_post_actions(
post.update_lock(&ctx).await?; post.update_lock(&ctx).await?;
locks_toggled += 1; locks_toggled += 1;
} }
}
for post in &posts {
if form.remove_reports.is_some() { 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?; post.delete_reports(&ctx).await?;
reports_removed += post.reports.0.len(); reports_removed += post.reports.0.len();
} }
@ -108,7 +144,7 @@ pub async fn staff_post_actions(
let mut already_banned = HashSet::new(); let mut already_banned = HashSet::new();
for post in posts { for post in &posts {
if let ( if let (
(Some(_), None) | (None, Some(_)) | (Some(_), Some(_)), (Some(_), None) | (None, Some(_)) | (Some(_), Some(_)),
Some(reason), Some(reason),
@ -132,6 +168,11 @@ pub async fn staff_post_actions(
} }
if form.ban_reporters.is_some() { 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( ips_to_ban.extend(
post.reports post.reports
.0 .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 { if posts_removed != 0 {
writeln!( writeln!(
&mut response, &mut response,
@ -226,6 +295,14 @@ pub async fn staff_post_actions(
.ok(); .ok();
} }
if users_trolled != 0 {
writeln!(
&mut response,
"[Úspěch] Vytroleni uživatelé: {users_trolled}"
)
.ok();
}
if bans_issued != 0 { if bans_issued != 0 {
writeln!(&mut response, "[Úspěch] Uděleny bany: {bans_issued}").ok(); writeln!(&mut response, "[Úspěch] Uděleny bany: {bans_issued}").ok();
} }

65
src/web/ip_posts.rs Normální soubor
Zobrazit 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)
}

Zobrazit soubor

@ -14,6 +14,7 @@ pub mod overboard_catalog;
pub mod staff; pub mod staff;
pub mod tcx; pub mod tcx;
pub mod thread; pub mod thread;
pub mod ip_posts;
use self::tcx::TemplateCtx; use self::tcx::TemplateCtx;
use crate::{ctx::Ctx, db::models::Ban, error::NekrochanError, filters}; use crate::{ctx::Ctx, db::models::Ban, error::NekrochanError, filters};

Zobrazit soubor

@ -18,9 +18,10 @@ pub struct UpdatePermissionsForm {
reports: Option<String>, reports: Option<String>,
bans: Option<String>, bans: Option<String>,
banners: Option<String>, banners: Option<String>,
board_config: Option<String>,
news: Option<String>, news: Option<String>,
jannytext: Option<String>, jannytext: Option<String>,
board_config: Option<String>, view_ips: Option<String>,
bypass_bans: Option<String>, bypass_bans: Option<String>,
bypass_board_lock: Option<String>, bypass_board_lock: Option<String>,
bypass_thread_lock: Option<String>, bypass_thread_lock: Option<String>,
@ -91,6 +92,10 @@ pub async fn update_permissions(
permissions |= Permissions::Jannytext; permissions |= Permissions::Jannytext;
} }
if form.view_ips.is_some() {
permissions |= Permissions::ViewIPs;
}
if form.bypass_bans.is_some() { if form.bypass_bans.is_some() {
permissions |= Permissions::BypassBans; permissions |= Permissions::BypassBans;
} }

86
static/js/expand-image.js Normální soubor
Zobrazit 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

Rozdílový obsah nebyl zobrazen, protože některé řádky jsou příliš dlouhá

Zobrazit soubor

@ -1,62 +1,63 @@
:root { :root {
font-size: 10pt; font-size: 10pt;
font-family: var(--font); font-family: var(--font);
color: var(--text); color: var(--text);
} }
body { body {
min-height: 100vh; min-height: 100vh;
background: var(--bg); background: var(--bg);
margin: 0; margin: 0;
} }
a { a {
color: var(--link-color); color: var(--link-color);
} }
a:hover { a:hover {
color: var(--link-hover); color: var(--link-hover);
} }
details { details {
display: inline-block; display: inline-block;
margin-bottom: 8px; margin-bottom: 8px;
} }
details:last-of-type { details:last-of-type {
margin-bottom: 0; margin-bottom: 0;
} }
img, img,
video { video {
max-height: 90vh; max-width: 100%;
max-height: 90vh;
} }
hr { hr {
border-top: 1px solid var(--hr-color); border-top: 1px solid var(--hr-color);
border-left: none; border-left: none;
border-right: none; border-right: none;
border-bottom: none; border-bottom: none;
} }
summary { summary {
cursor: pointer; cursor: pointer;
} }
.form-table .label { .form-table .label {
font-weight: bold; font-weight: bold;
background-color: var(--table-head); background-color: var(--table-head);
border: 1px solid var(--table-border); border: 1px solid var(--table-border);
padding: 4px; padding: 4px;
} }
.form-table td { .form-table td {
padding: 0; padding: 0;
} }
.edit-box { .edit-box {
display: block; display: block;
width: 100%; width: 100%;
} }
.form-table input[type="text"], .form-table input[type="text"],
@ -66,407 +67,408 @@ summary {
.form-table select, .form-table select,
.input-wrapper, .input-wrapper,
.edit-box { .edit-box {
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
display: block; display: block;
width: 100%; width: 100%;
color: var(--text); color: var(--text);
background-color: var(--input-color); background-color: var(--input-color);
border-radius: 0; border-radius: 0;
border: 1px solid var(--input-border); border: 1px solid var(--input-border);
padding: 4px; padding: 4px;
} }
.form-table input[type="checkbox"] { .form-table input[type="checkbox"] {
display: block; display: block;
margin: 0 auto; margin: 0 auto;
} }
.form-table textarea, .form-table textarea,
.edit-box { .edit-box {
height: 8rem; height: 8rem;
resize: none; resize: none;
} }
.reply-mode { .reply-mode {
font-weight: bold; font-weight: bold;
font-size: 18px; font-size: 18px;
text-align: center; text-align: center;
background-color: var(--table-head); background-color: var(--table-head);
border: 1px solid var(--table-border); border: 1px solid var(--table-border);
padding: 8px; padding: 8px;
} }
.container > form > .form-table { .container > form > .form-table {
margin: 8px auto; margin: 8px auto;
} }
.table-wrap { .table-wrap {
overflow: scroll; overflow: scroll;
} }
.data-table { .data-table {
width: 100%; width: 100%;
border-spacing: 0; border-spacing: 0;
border-collapse: collapse; border-collapse: collapse;
margin: 8px 0; margin: 8px 0;
} }
.data-table th { .data-table th {
background-color: var(--table-head); background-color: var(--table-head);
} }
.data-table td { .data-table td {
background-color: var(--table-background); background-color: var(--table-background);
} }
.data-table td:not(.form-table td), .data-table td:not(.form-table td),
.data-table th { .data-table th {
border: 1px solid var(--table-border); border: 1px solid var(--table-border);
padding: 4px; padding: 4px;
} }
.data-table .banner { .data-table .banner {
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
} }
.news { .news {
margin: 8px 0; margin: 8px 0;
} }
.box { .box {
background-color: var(--box-color); background-color: var(--box-color);
border-right: 1px solid var(--box-border); border-right: 1px solid var(--box-border);
border-bottom: 1px solid var(--box-border); border-bottom: 1px solid var(--box-border);
padding: 8px; padding: 8px;
} }
.box:target { .box:target {
background-color: var(--hl-box-color); background-color: var(--hl-box-color);
border-right: 1px solid var(--hl-box-border); border-right: 1px solid var(--hl-box-border);
border-bottom: 1px solid var(--hl-box-border); border-bottom: 1px solid var(--hl-box-border);
} }
.button { .button {
cursor: pointer; cursor: pointer;
border-radius: 0; border-radius: 0;
color: var(--text); color: var(--text);
background-color: var(--input-color); background-color: var(--input-color);
border: 1px solid var(--input-border); border: 1px solid var(--input-border);
padding: 8px; padding: 8px;
} }
.captcha { .captcha {
width: 100%; width: 100%;
image-rendering: pixelated; image-rendering: pixelated;
border: 1px solid var(--input-border); border: 1px solid var(--input-border);
} }
.main { .main {
margin: 8px; margin: 8px;
} }
.container { .container {
margin: 0 auto; margin: 0 auto;
max-width: 720px; max-width: 720px;
} }
.title { .title {
color: var(--title-color); color: var(--title-color);
font-family: var(--title-font); font-family: var(--title-font);
text-align: center; text-align: center;
letter-spacing: -2px; letter-spacing: -2px;
margin: 0; margin: 0;
} }
.description { .description {
font-weight: bold; font-weight: bold;
margin: 0; margin: 0;
} }
.big { .big {
font-size: 1.2rem; font-size: 1.2rem;
} }
.small { .small {
font-size: 0.8rem; font-size: 0.8rem;
} }
.center { .center {
text-align: center; text-align: center;
} }
.inline-block { .inline-block {
display: inline-block; display: inline-block;
} }
.float-r { .float-r {
float: right; float: right;
} }
.fixed-table { .fixed-table {
table-layout: fixed; table-layout: fixed;
} }
.m-0 { .m-0 {
margin: 0; margin: 0;
} }
.form-table .button, .form-table .button,
.full-width { .full-width {
display: block; display: block;
width: 100%; width: 100%;
} }
.banner { .banner {
display: block; display: block;
width: 100%; width: 100%;
max-width: 300px; max-width: 300px;
margin: 8px auto; margin: 8px auto;
border: 1px solid var(--box-border); border: 1px solid var(--box-border);
} }
.headline { .headline {
font-size: 1rem; font-size: 1rem;
margin: 0; margin: 0;
} }
.headline::after { .headline::after {
content: ""; content: "";
display: block; display: block;
clear: both; clear: both;
} }
.board-links, .board-links,
.pagination { .pagination {
color: var(--link-list-color); color: var(--link-list-color);
} }
.link-separator::after { .link-separator::after {
content: " / "; content: " / ";
} }
.link-group::before { .link-group::before {
content: " [ "; content: " [ ";
} }
.link-group::after { .link-group::after {
content: " ] "; content: " ] ";
} }
.header { .header {
padding: 2px; padding: 2px;
} }
.header::after { .header::after {
content: ""; content: "";
display: block; display: block;
clear: both; clear: both;
} }
.footer { .footer {
text-align: center; text-align: center;
font-size: 8pt; font-size: 8pt;
margin-top: 8px; margin-top: 8px;
} }
.post { .post {
margin-bottom: 8px; margin-bottom: 8px;
padding: 8px; padding: 8px;
} }
.post.box { .post.box {
display: inline-block; display: inline-block;
min-width: 400px; min-width: 400px;
} }
.post:last-of-type { .post:last-of-type {
margin-bottom: 0; margin-bottom: 0;
} }
.post::after { .post::after {
content: ""; content: "";
display: block; display: block;
clear: both; clear: both;
} }
.board-links a, .board-links a,
.pagination a, .pagination a,
.post-number { .post-number {
text-decoration: none; text-decoration: none;
} }
.post-header input[type="checkbox"] { .post-header input[type="checkbox"] {
margin: 0; vertical-align: middle;
margin: 0;
} }
.catalog-entry { .catalog-entry {
display: inline-block; display: inline-block;
width: 200px; width: 200px;
height: 250px; height: 250px;
overflow: scroll; overflow: scroll;
margin: 4px; margin: 4px;
padding: 8px; padding: 8px;
} }
.catalog-entry .thumb { .catalog-entry .thumb {
display: block; display: block;
max-width: 100%; max-width: 100%;
max-height: 50%; max-height: 50%;
box-shadow: 0 0 3px #000; box-shadow: 0 0 3px #000;
margin: 4px auto; margin: 4px auto;
padding: 2px; padding: 2px;
} }
.catalog-entry .post-content { .catalog-entry .post-content {
margin: 8px; margin: 8px;
} }
.name { .name {
font-weight: bold; font-weight: bold;
color: var(--name-color); color: var(--name-color);
} }
.tripcode { .tripcode {
color: var(--trip-color); color: var(--trip-color);
} }
.capcode { .capcode {
color: var(--capcode-color); color: var(--capcode-color);
font-weight: bold; font-weight: bold;
} }
.user-id { .user-id {
text-shadow: #000 0 0 1px, #000 0 0 1px, #000 0 0 1px, #000 0 0 1px, 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; #000 0 0 1px, #000 0 0 1px;
color: #ffffff; color: #ffffff;
border: 1px solid var(--box-border); border: 1px solid var(--box-border);
padding: 0 2px; padding: 0 2px;
} }
.post-files { .post-files {
float: left; float: left;
margin: 0 8px 8px 8px; margin: 0 8px 8px 8px;
} }
.multi-files { .multi-files {
float: none; float: none;
} }
.post-file { .post-file {
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
text-align: center; text-align: center;
font-size: 8pt; font-size: 8pt;
padding: 4px; padding: 4px;
} }
.thumb { .thumb {
max-width: 200px; max-width: 200px;
max-height: 200px; max-height: 200px;
} }
.post-content { .post-content {
font-family: inherit; font-family: inherit;
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-word; word-break: break-word;
margin: 0; margin: 0;
} }
.post .post-content { .post .post-content {
margin: 1rem 2rem; margin: 1rem 2rem;
} }
.post-content a { .post-content a {
color: var(--post-link-color); color: var(--post-link-color);
} }
.post-content a:hover { .post-content a:hover {
color: var(--post-link-hover); color: var(--post-link-hover);
} }
.dead-quote { .dead-quote {
color: var(--dead-quote-color); color: var(--dead-quote-color);
text-decoration: line-through; text-decoration: line-through;
} }
.greentext { .greentext {
color: var(--greentext-color); color: var(--greentext-color);
} }
.orangetext { .orangetext {
color: var(--orangetext-color); color: var(--orangetext-color);
} }
.redtext { .redtext {
color: var(--redtext-color); color: var(--redtext-color);
font-weight: bold; font-weight: bold;
} }
.bluetext { .bluetext {
color: var(--bluetext-color); color: var(--bluetext-color);
font-weight: bold; font-weight: bold;
} }
.glowtext { .glowtext {
text-shadow: 0 0 40px #00fe20, 0 0 2px #00fe20; text-shadow: 0 0 40px #00fe20, 0 0 2px #00fe20;
} }
.uh-oh-text { .uh-oh-text {
color: var(--uh-oh-text); color: var(--uh-oh-text);
background-color: var(--uh-oh-color); background-color: var(--uh-oh-color);
} }
.spoiler { .spoiler {
color: var(--text); color: var(--text);
background-color: var(--text); background-color: var(--text);
} }
.spoiler:hover { .spoiler:hover {
background-color: transparent; background-color: transparent;
} }
.jannytext { .jannytext {
color: var(--jannytext-color); color: var(--jannytext-color);
font-weight: bold; font-weight: bold;
} }
.icon { .icon {
height: 0.8em; height: 0.8em;
vertical-align: middle; vertical-align: middle;
} }
.posts-omitted { .posts-omitted {
margin-top: 0; margin-top: 0;
margin-bottom: 8px; margin-bottom: 8px;
} }
.board-list { .board-list {
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
.thumb { .thumb {
max-width: 100px; max-width: 100px;
max-height: 100px; max-height: 100px;
} }
.post.box { .post.box {
display: block; display: block;
min-width: unset; min-width: unset;
} }
.thread > br { .thread > br {
display: none; display: none;
} }
.catalog-entry { .catalog-entry {
width: 140px; width: 140px;
height: 220px; height: 220px;
} }
} }

Zobrazit soubor

@ -9,10 +9,10 @@
<meta name="description" content="{{ tcx.cfg.site.description }}"> <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/themes/{% block theme %}{% include "../theme.txt" %}{% endblock %}'>
<link rel="stylesheet" href="/static/style.css"> <link rel="stylesheet" href="/static/style.css">
<!-- Muh flash of unstyled content --> <script src="/static/js/jquery.min.js"></script>
<script>0</script> <script src="/static/js/expand-image.js"></script>
</head> </head>
<body> <body id="top">
<div class="board-links header"> <div class="board-links header">
<span class="link-group"><a href="/">domov</a></span> <span class="link-group"><a href="/">domov</a></span>
{% call board_links::board_links() %} {% call board_links::board_links() %}
@ -35,7 +35,7 @@
</div> </div>
<div class="main"> <div class="main">
{% block content %}{% endblock %} {% block content %}{% endblock %}
<div class="footer"> <div id="bottom" class="footer">
<div class="box inline-block"> <div class="box inline-block">
<a href="https://git.nekrofilie.com/sneedmaster/nekrochan">nekrochan</a> - Projekt <a href="https://nekrofilie.com/">Nekrofilie</a> <a href="https://git.nekrofilie.com/sneedmaster/nekrochan">nekrochan</a> - Projekt <a href="https://nekrofilie.com/">Nekrofilie</a>
<br> <br>

Zobrazit soubor

@ -21,7 +21,9 @@
<div class="box"> <div class="box">
<h2 class="headline"> <h2 class="headline">
<span>{{ news.title }}</span> <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> </h2>
<hr> <hr>
<div class="post-content">{{ news.content|safe }}</div> <div class="post-content">{{ news.content|safe }}</div>

26
templates/ip-posts.html Normální soubor
Zobrazit 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 %}

Zobrazit soubor

@ -67,7 +67,7 @@
</table> </table>
</details> </details>
<br> <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> <details>
<summary class="box">Uklízečské akce</summary> <summary class="box">Uklízečské akce</summary>
<table class="form-table"> <table class="form-table">
@ -96,6 +96,22 @@
</div> </div>
</td> </td>
</tr> </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> <tr>
<td class="label">Připnout/odepnout</td> <td class="label">Připnout/odepnout</td>
<td> <td>
@ -177,6 +193,14 @@
</td> </td>
</tr> </tr>
{% endif %} {% 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> <tr>
<td colspan="2"> <td colspan="2">
<input <input

Zobrazit soubor

@ -2,6 +2,9 @@
<div id="{{ post.id }}" class="post{% if boxed %} box{% endif %}"> <div id="{{ post.id }}" class="post{% if boxed %} box{% endif %}">
<div class="post-header"> <div class="post-header">
<input name="posts[]" type="checkbox" value="{{ post.board }}/{{ post.id }}">&#32; <input name="posts[]" type="checkbox" value="{{ post.board }}/{{ post.id }}">&#32;
{% 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>&#32;
{% endif %}
{% if let Some(email) = post.email %} {% if let Some(email) = post.email %}
<a class="name" rel="nofollow" href="mailto:{{ email }}">{{ post.name }}</a>&#32; <a class="name" rel="nofollow" href="mailto:{{ email }}">{{ post.name }}</a>&#32;
{% else %} {% else %}
@ -11,14 +14,14 @@
<span class="tripcode">{{ tripcode }}</span>&#32; <span class="tripcode">{{ tripcode }}</span>&#32;
{% endif %} {% endif %}
{% if let Some(capcode) = post.capcode %} {% if let Some(capcode) = post.capcode %}
<span class="capcode">## {{ capcode }} <img class="icon" src="/favicon.ico"></span>&#32; <span title="Tento uživatel to dělá ZDARMA!" class="capcode">## {{ capcode }} <img class="icon" src="/favicon.ico"></span>&#32;
{% endif %} {% endif %}
{% if tcx.ip == post.ip %} {% if tcx.ip == post.ip %}
{# Technically not a tripcode or something but same styling #} {# Technically not a tripcode or something but same styling #}
<i class="tripcode">(Ty)</i>&#32; <i class="tripcode">(Ty)</i>&#32;
{% endif %} {% endif %}
{% if board.config.0.flags %} {% if board.config.0.flags %}
<img class="icon" src="/static/flags/{{ post.country }}.png">&#32; <img title="Země: {{ post.country }}" class="icon" src="/static/flags/{{ post.country }}.png">&#32;
{% endif %} {% endif %}
<span title="{{ post.created|czech_datetime }}">{{ post.created|czech_humantime }}</span>&#32; <span title="{{ post.created|czech_datetime }}">{{ post.created|czech_humantime }}</span>&#32;
{% if board.config.0.user_ids %} {% if board.config.0.user_ids %}
@ -26,10 +29,10 @@
{% endif %} {% endif %}
<a class="post-number" href="{{ post.post_url() }}">Č.{{ post.id }}</a>&#32; <a class="post-number" href="{{ post.post_url() }}">Č.{{ post.id }}</a>&#32;
{% if post.sticky %} {% if post.sticky %}
<img class="icon" src="/static/icons/sticky.png">&#32; <img title="Připnuto" class="icon" src="/static/icons/sticky.png">&#32;
{% endif %} {% endif %}
{% if post.locked %} {% if post.locked %}
<img class="icon" src="/static/icons/locked.png">&#32; <img title="Uzamčeno" class="icon" src="/static/icons/locked.png">&#32;
{% endif %} {% endif %}
{% if !boxed %} {% if !boxed %}
@ -40,7 +43,7 @@
<div class="post-files{% if post.files.0.len() > 1 %} multi-files{% endif %}"> <div class="post-files{% if post.files.0.len() > 1 %} multi-files{% endif %}">
{% for file in post.files.0 %} {% for file in post.files.0 %}
<div class="post-file"> <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 %} {% if file.spoiler %}
[Spoiler] [Spoiler]
{% else %} {% else %}
@ -50,7 +53,7 @@
<br> <br>
({{ file.size|filesizeformat }}, {{ file.width }}x{{ file.height }}) ({{ file.size|filesizeformat }}, {{ file.width }}x{{ file.height }})
<br> <br>
<a target="_blank" href="{{ file.file_url() }}"> <a class="expandable" target="_blank" href="{{ file.file_url() }}">
<img class="thumb" src="{{ file.thumb_url() }}"> <img class="thumb" src="{{ file.thumb_url() }}">
</a> </a>
</div> </div>

Zobrazit soubor

@ -9,7 +9,9 @@
<div class="news box"> <div class="news box">
<h2 class="headline"> <h2 class="headline">
<span>{{ newspost.title }}</span> <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> </h2>
<hr> <hr>
<div class="post-content">{{ newspost.content|safe }}</div> <div class="post-content">{{ newspost.content|safe }}</div>

Zobrazit soubor

@ -24,7 +24,7 @@
<td><input name="accounts[]" type="checkbox" value="{{ account.username }}" {% if !tcx.perms.owner() %}disabled=""{% endif %}></td> <td><input name="accounts[]" type="checkbox" value="{{ account.username }}" {% if !tcx.perms.owner() %}disabled=""{% endif %}></td>
<td>{{ account.username }}</td> <td>{{ account.username }}</td>
<td>{% if account.owner %}Ano{% else %}Ne{% endif %}</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> <td>{{ account.permissions.0 }} <a href="/staff/permissions/{{ account.username }}">[Zobrazit]</a></td>
</tr> </tr>
{% endfor %} {% endfor %}

Zobrazit soubor

@ -38,8 +38,12 @@
<td>{{ ban.issued_by }}</td> <td>{{ ban.issued_by }}</td>
<td>{% if ban.appealable %}Ano{% else %}Ne{% endif %}</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>{% if let Some(appeal) = ban.appeal %}<div class="post-content">{{ appeal }}</div>{% else %}-{% endif %}</td>
<td>{{ ban.created|czech_datetime }}</td> <td title="ban.{{ ban.created|czech_humantime }}">{{ 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> {% if let Some(expires) = ban.expires %}
<td title="{{ expires|czech_humantime }}">{{ expires|czech_datetime }}</td>
{% else %}
<td>Nikdy</td>
{% endif %}
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

Zobrazit soubor

@ -26,7 +26,7 @@
<td>/{{ board.id }}/</td> <td>/{{ board.id }}/</td>
<td>{{ board.name }}</td> <td>{{ board.name }}</td>
<td>{{ board.description }}</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> <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

@ -11,7 +11,9 @@
<div class="box"> <div class="box">
<h2 class="headline"> <h2 class="headline">
<span>{{ newspost.title }}</span> <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> </h2>
<hr> <hr>
<textarea class="edit-box" name="{{ newspost.id }}">{{ newspost.content_nomarkup }}</textarea> <textarea class="edit-box" name="{{ newspost.id }}">{{ newspost.content_nomarkup }}</textarea>

Zobrazit soubor

@ -115,6 +115,15 @@
</td> </td>
</tr> </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> <tr>
<td class="label">Obejít ban</td> <td class="label">Obejít ban</td>
<td> <td>

Zobrazit soubor

@ -16,6 +16,8 @@
<hr> <hr>
<form method="post"> <form method="post">
{% for post in posts %} {% 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) %} {% call post::post(boards[post.board.as_str()], post, true) %}
<table class="data-table inline-block m-0"> <table class="data-table inline-block m-0">
<tr> <tr>