remove holes (#492)

* remove holes

* don't import class that doesn't exist

* remove subs in template code

* remove subs_created

* fix sticky bug

* uh... i think this will fix it????

* fix stupid nearly ungrokkable nesting error

* ...

* ....

* ....

* *sigh*

* .............................i think i got it.

* Revert ".............................i think i got it."

This reverts commit 419a545875.

* Revert "*sigh*"

This reverts commit fcfc9d7995.

* Revert "...."

This reverts commit e200c8f6f0.

* Revert "...."

This reverts commit 681db8cb02.

* Revert "..."

This reverts commit c54372b9ff.

* Revert "fix stupid nearly ungrokkable nesting error"

This reverts commit e202fd774c.

* Revert "uh... i think this will fix it????"

This reverts commit e4d9366093.

* readd missing endif

* fix email templates.

* fix frontpage listing

* some minor fixes wrt saving

* fix some listing errors

* Remove more references to holes system

A couple of these came from the merge of #554. A few others were just
left in the templates and presumably hard to catch on the first
removal pass.

* remove unnecessary lazy

* Add migration

Tested. Very sensitive to order. I wound up borrowing the statement
order we used for deleting sub rows on upstream (manually, of course).

---------

Co-authored-by: TLSM <duolsm@outlook.com>
This commit is contained in:
justcool393 2023-03-31 22:13:30 -07:00 committed by GitHub
parent 1647b4cf20
commit 77af24a5b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 226 additions and 1260 deletions

View file

@ -94,8 +94,6 @@ function savetext() {
localStorage.setItem("post_title", document.getElementById('post-title').value) localStorage.setItem("post_title", document.getElementById('post-title').value)
localStorage.setItem("post_text", document.getElementById('post-text').value) localStorage.setItem("post_text", document.getElementById('post-text').value)
localStorage.setItem("post_url", document.getElementById('post-url').value) localStorage.setItem("post_url", document.getElementById('post-url').value)
let sub = document.getElementById('sub')
if (sub) localStorage.setItem("sub", sub.value)
} }

View file

@ -59,16 +59,12 @@ from .badges import BadgeDef, Badge
from .clients import OauthApp, ClientAuth from .clients import OauthApp, ClientAuth
from .comment import Comment from .comment import Comment
from .domains import BannedDomain from .domains import BannedDomain
from .exiles import Exile
from .flags import Flag, CommentFlag from .flags import Flag, CommentFlag
from .follows import Follow from .follows import Follow
from .marsey import Marsey from .marsey import Marsey
from .mod import Mod
from .mod_logs import ModAction from .mod_logs import ModAction
from .notifications import Notification from .notifications import Notification
from .saves import SaveRelationship, CommentSaveRelationship from .saves import SaveRelationship, CommentSaveRelationship
from .sub import Sub
from .sub_block import SubBlock
from .submission import Submission from .submission import Submission
from .subscriptions import Subscription from .subscriptions import Subscription
from .user import User from .user import User

View file

@ -402,8 +402,8 @@ class Comment(CreatedBase):
if v.id == self.author_id: return 1 if v.id == self.author_id: return 1
return getattr(self, 'voted', 0) return getattr(self, 'voted', 0)
@lazy
def sticky_api_url(self, v) -> Optional[str]: def sticky_api_url(self, v) -> str | None:
''' '''
Returns the API URL used to sticky this comment. Returns the API URL used to sticky this comment.
:returns: Currently `None` always. Stickying comments was disabled :returns: Currently `None` always. Stickying comments was disabled
@ -414,12 +414,11 @@ class Comment(CreatedBase):
if not v: return None if not v: return None
if v.admin_level >= 2: if v.admin_level >= 2:
return 'sticky_comment' return 'sticky_comment'
if v.id == self.post.author_id: elif v.id == self.post.author_id:
return 'pin_comment' return 'pin_comment'
if self.post.sub and v.mods(self.post.sub):
return 'mod_pin'
return None return None
@lazy @lazy
def active_flags(self, v): def active_flags(self, v):
return len(self.flags(v)) return len(self.flags(v))

View file

@ -1,18 +0,0 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.classes.base import Base
class Exile(Base):
__tablename__ = "exiles"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
sub = Column(String, ForeignKey("subs.name"), primary_key=True)
exiler_id = Column(Integer, ForeignKey("users.id"), nullable=False)
Index('fki_exile_exiler_fkey', exiler_id)
Index('fki_exile_sub_fkey', sub)
exiler = relationship("User", primaryjoin="User.id==Exile.exiler_id", viewonly=True)
def __repr__(self):
return f"<{self.__class__.__name__}(user_id={self.user_id}, sub={self.sub})>"

View file

@ -1,14 +0,0 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.classes.base import CreatedBase
from files.helpers.lazy import *
class Mod(CreatedBase):
__tablename__ = "mods"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
sub = Column(String, ForeignKey("subs.name"), primary_key=True)
Index('fki_mod_sub_fkey', sub)
def __repr__(self):
return f"<{self.__class__.__name__}(user_id={self.user_id}, sub={self.sub})>"

View file

@ -240,11 +240,6 @@ ACTIONTYPES = {
"icon": 'fa-sack-dollar', "icon": 'fa-sack-dollar',
"color": 'bg-success' "color": 'bg-success'
}, },
'move_hole': {
"str": 'moved {self.target_link} to <a href="/h/{self.target_post.sub}">/h/{self.target_post.sub}</a>',
"icon": 'fa-manhole',
"color": 'bg-primary'
},
'nuke_user': { 'nuke_user': {
"str": 'removed all content of {self.target_link}', "str": 'removed all content of {self.target_link}',
"icon": 'fa-radiation-alt', "icon": 'fa-radiation-alt',

View file

@ -1,45 +0,0 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.helpers.config.environment import SITE_FULL
from files.classes.base import Base
from files.helpers.lazy import lazy
from .sub_block import *
class Sub(Base):
__tablename__ = "subs"
name = Column(String, primary_key=True)
sidebar = Column(String)
sidebar_html = Column(String)
sidebarurl = Column(String)
bannerurl = Column(String)
css = Column(String)
Index('subs_idx', name)
blocks = relationship("SubBlock", lazy="dynamic", primaryjoin="SubBlock.sub==Sub.name", viewonly=True)
def __repr__(self):
return f"<{self.__class__.__name__}(name={self.name})>"
@property
@lazy
def sidebar_url(self):
if self.sidebarurl: return SITE_FULL + self.sidebarurl
return None # Add default sidebar for subs if subs are used again
@property
@lazy
def banner_url(self):
if self.bannerurl: return SITE_FULL + self.bannerurl
return None # Add default banner for subs if subs are used again
@property
@lazy
def subscription_num(self):
return self.subscriptions.count()
@property
@lazy
def block_num(self):
return self.blocks.count()

View file

@ -1,14 +0,0 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.classes.base import Base
class SubBlock(Base):
__tablename__ = "sub_blocks"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
sub = Column(String, ForeignKey("subs.name"), primary_key=True)
Index('fki_sub_blocks_sub_fkey', sub)
def __repr__(self):
return f"<{self.__class__.__name__}(user_id={self.user_id}, sub={self.sub})>"

View file

@ -1,12 +1,11 @@
import time
from urllib.parse import urlparse from urllib.parse import urlparse
from flask import g from flask import g
from sqlalchemy import * from sqlalchemy import *
from sqlalchemy.orm import (declared_attr, deferred, relationship, from sqlalchemy.orm import Session, declared_attr, deferred, relationship
Session)
from files.classes.base import CreatedBase from files.classes.base import CreatedBase
from files.classes.flags import Flag
from files.classes.votes import Vote from files.classes.votes import Vote
from files.helpers.assetcache import assetcache_path from files.helpers.assetcache import assetcache_path
from files.helpers.config.const import * from files.helpers.config.const import *
@ -16,8 +15,6 @@ from files.helpers.content import body_displayed
from files.helpers.lazy import lazy from files.helpers.lazy import lazy
from files.helpers.time import format_age, format_datetime from files.helpers.time import format_age, format_datetime
from .flags import Flag
class Submission(CreatedBase): class Submission(CreatedBase):
__tablename__ = "submissions" __tablename__ = "submissions"
@ -34,7 +31,6 @@ class Submission(CreatedBase):
distinguish_level = Column(Integer, default=0, nullable=False) distinguish_level = Column(Integer, default=0, nullable=False)
stickied = Column(String) stickied = Column(String)
stickied_utc = Column(Integer) stickied_utc = Column(Integer)
sub = Column(String, ForeignKey("subs.name"))
is_pinned = Column(Boolean, default=False, nullable=False) is_pinned = Column(Boolean, default=False, nullable=False)
private = Column(Boolean, default=False, nullable=False) private = Column(Boolean, default=False, nullable=False)
club = Column(Boolean, default=False, nullable=False) club = Column(Boolean, default=False, nullable=False)
@ -86,7 +82,6 @@ class Submission(CreatedBase):
awards = relationship("AwardRelationship", viewonly=True) awards = relationship("AwardRelationship", viewonly=True)
reports = relationship("Flag", viewonly=True) reports = relationship("Flag", viewonly=True)
comments = relationship("Comment", primaryjoin="Comment.parent_submission==Submission.id") comments = relationship("Comment", primaryjoin="Comment.parent_submission==Submission.id")
subr = relationship("Sub", primaryjoin="foreign(Submission.sub)==remote(Sub.name)", viewonly=True)
notes = relationship("UserNote", back_populates="post") notes = relationship("UserNote", back_populates="post")
task = relationship("ScheduledSubmissionTask", back_populates="submissions") task = relationship("ScheduledSubmissionTask", back_populates="submissions")
@ -175,8 +170,6 @@ class Submission(CreatedBase):
@lazy @lazy
def shortlink(self): def shortlink(self):
link = f"/post/{self.id}" link = f"/post/{self.id}"
if self.sub: link = f"/h/{self.sub}{link}"
if self.club: return link + '/-' if self.club: return link + '/-'
output = title_regex.sub('', self.title.lower()) output = title_regex.sub('', self.title.lower())

View file

@ -12,13 +12,10 @@ from files.classes.award import AwardRelationship
from files.classes.badges import Badge from files.classes.badges import Badge
from files.classes.base import CreatedBase from files.classes.base import CreatedBase
from files.classes.clients import * # note: imports Comment and Submission from files.classes.clients import * # note: imports Comment and Submission
from files.classes.exiles import *
from files.classes.follows import Follow from files.classes.follows import Follow
from files.classes.mod import Mod
from files.classes.mod_logs import ModAction from files.classes.mod_logs import ModAction
from files.classes.notifications import Notification from files.classes.notifications import Notification
from files.classes.saves import CommentSaveRelationship, SaveRelationship from files.classes.saves import CommentSaveRelationship, SaveRelationship
from files.classes.sub_block import SubBlock
from files.classes.subscriptions import Subscription from files.classes.subscriptions import Subscription
from files.classes.userblock import UserBlock from files.classes.userblock import UserBlock
from files.helpers.assetcache import assetcache_path from files.helpers.assetcache import assetcache_path
@ -113,7 +110,6 @@ class User(CreatedBase):
ban_evade = Column(Integer, default=0, nullable=False) ban_evade = Column(Integer, default=0, nullable=False)
original_username = deferred(Column(String)) original_username = deferred(Column(String))
referred_by = Column(Integer, ForeignKey("users.id")) referred_by = Column(Integer, ForeignKey("users.id"))
subs_created = Column(Integer, default=0, nullable=False)
volunteer_last_started_utc = Column(DateTime, nullable=True) volunteer_last_started_utc = Column(DateTime, nullable=True)
Index( Index(
@ -173,30 +169,6 @@ class User(CreatedBase):
accountAgeDays = self.age_timedelta.days accountAgeDays = self.age_timedelta.days
return self.comment_count < minComments or accountAgeDays < minAge or self.truecoins < minKarma return self.comment_count < minComments or accountAgeDays < minAge or self.truecoins < minKarma
@lazy
def mods(self, sub):
return self.admin_level == 3 or bool(g.db.query(Mod.user_id).filter_by(user_id=self.id, sub=sub).one_or_none())
@lazy
def exiled_from(self, sub):
return self.admin_level < 2 and bool(g.db.query(Exile.user_id).filter_by(user_id=self.id, sub=sub).one_or_none())
@property
@lazy
def all_blocks(self):
return [x[0] for x in g.db.query(SubBlock.sub).filter_by(user_id=self.id).all()]
@lazy
def blocks(self, sub):
return g.db.query(SubBlock).filter_by(user_id=self.id, sub=sub).one_or_none()
@lazy
def mod_date(self, sub):
if self.id == OWNER_ID: return 1
mod = g.db.query(Mod).filter_by(user_id=self.id, sub=sub).one_or_none()
if not mod: return None
return mod.created_utc
@property @property
@lazy @lazy
def csslazy(self): def csslazy(self):
@ -428,12 +400,6 @@ class User(CreatedBase):
return output return output
@property
@lazy
def moderated_subs(self):
modded_subs = g.db.query(Mod.sub).filter_by(user_id=self.id).all()
return modded_subs
def has_follower(self, user): def has_follower(self, user):
return g.db.query(Follow).filter_by(target_id=self.id, user_id=user.id).one_or_none() return g.db.query(Follow).filter_by(target_id=self.id, user_id=user.id).one_or_none()

View file

@ -103,7 +103,6 @@ def seed_db_worker(num_users = 900, num_posts = 40, num_toplevel_comments = 1000
embed_url=None, embed_url=None,
title=f'Clever unique post title number {i}', title=f'Clever unique post title number {i}',
title_html=f'Clever unique post title number {i}', title_html=f'Clever unique post title number {i}',
sub=None,
ghost=False, ghost=False,
filter_state='normal' filter_state='normal'
) )

View file

@ -66,7 +66,6 @@ def notif_comment2(p):
if existing: return existing[0] if existing: return existing[0]
else: else:
text = f"@{p.author.username} has mentioned you: [{p.title}](/post/{p.id})" text = f"@{p.author.username} has mentioned you: [{p.title}](/post/{p.id})"
if p.sub: text += f" in <a href='/h/{p.sub}'>/h/{p.sub}"
text_html = sanitize(text, alert=True) text_html = sanitize(text, alert=True)
return create_comment(text_html) return create_comment(text_html)
@ -113,9 +112,6 @@ def notify_submission_publish(target: Submission):
f"@{target.author.username} has made a new post: " f"@{target.author.username} has made a new post: "
f"[{target.title}]({target.shortlink})" f"[{target.title}]({target.shortlink})"
) )
if target.sub:
message += f" in <a href='/h/{target.sub}'>/h/{target.sub}"
cid = notif_comment(message, autojanny=True) cid = notif_comment(message, autojanny=True)
for follow in target.author.followers: for follow in target.author.followers:
add_notif(cid, follow.user_id) add_notif(cid, follow.user_id)

View file

@ -21,7 +21,7 @@ USERPAGELISTING_TIMEOUT_SECS: Final[int] = 86400
CHANGELOGLIST_TIMEOUT_SECS: Final[int] = 86400 CHANGELOGLIST_TIMEOUT_SECS: Final[int] = 86400
@cache.memoize(timeout=FRONTLIST_TIMEOUT_SECS) @cache.memoize(timeout=FRONTLIST_TIMEOUT_SECS)
def frontlist(v=None, sort='new', page=1, t="all", ids_only=True, ccmode="false", filter_words='', gt=0, lt=0, sub=None, site=None): def frontlist(v=None, sort='new', page=1, t="all", ids_only=True, ccmode="false", filter_words='', gt=0, lt=0):
posts = g.db.query(Submission) posts = g.db.query(Submission)
if v and v.hidevotedon: if v and v.hidevotedon:
@ -34,9 +34,6 @@ def frontlist(v=None, sort='new', page=1, t="all", ids_only=True, ccmode="false"
filter_clause = filter_clause | (Submission.author_id == v.id) filter_clause = filter_clause | (Submission.author_id == v.id)
posts = posts.filter(filter_clause) posts = posts.filter(filter_clause)
if sub: posts = posts.filter_by(sub=sub.name)
elif v: posts = posts.filter((Submission.sub == None) | Submission.sub.notin_(v.all_blocks))
if gt: posts = posts.filter(Submission.created_utc > gt) if gt: posts = posts.filter(Submission.created_utc > gt)
if lt: posts = posts.filter(Submission.created_utc < lt) if lt: posts = posts.filter(Submission.created_utc < lt)
@ -80,9 +77,7 @@ def frontlist(v=None, sort='new', page=1, t="all", ids_only=True, ccmode="false"
if page == 1 and ccmode == "false" and not gt and not lt: if page == 1 and ccmode == "false" and not gt and not lt:
pins = g.db.query(Submission).filter(Submission.stickied != None, Submission.is_banned == False) pins = g.db.query(Submission).filter(Submission.stickied != None, Submission.is_banned == False)
if sub: pins = pins.filter_by(sub=sub.name) if v:
elif v:
pins = pins.filter((Submission.sub == None) | Submission.sub.notin_(v.all_blocks))
if v.admin_level < 2: if v.admin_level < 2:
pins = pins.filter(Submission.author_id.notin_(v.userblocks)) pins = pins.filter(Submission.author_id.notin_(v.userblocks))
@ -105,7 +100,7 @@ def frontlist(v=None, sort='new', page=1, t="all", ids_only=True, ccmode="false"
@cache.memoize(timeout=USERPAGELISTING_TIMEOUT_SECS) @cache.memoize(timeout=USERPAGELISTING_TIMEOUT_SECS)
def userpagelisting(u:User, site=None, v=None, page=1, sort="new", t="all"): def userpagelisting(u:User, v=None, page=1, sort="new", t="all"):
if u.shadowbanned and not (v and (v.admin_level > 1 or v.id == u.id)): return [] if u.shadowbanned and not (v and (v.admin_level > 1 or v.id == u.id)): return []
posts = g.db.query(Submission.id).filter_by(author_id=u.id, is_pinned=False) posts = g.db.query(Submission.id).filter_by(author_id=u.id, is_pinned=False)
@ -122,7 +117,7 @@ def userpagelisting(u:User, site=None, v=None, page=1, sort="new", t="all"):
@cache.memoize(timeout=CHANGELOGLIST_TIMEOUT_SECS) @cache.memoize(timeout=CHANGELOGLIST_TIMEOUT_SECS)
def changeloglist(v=None, sort="new", page=1, t="all", site=None): def changeloglist(v=None, sort="new", page=1, t="all"):
posts = g.db.query(Submission.id).filter_by(is_banned=False, private=False,).filter(Submission.deleted_utc == 0) posts = g.db.query(Submission.id).filter_by(is_banned=False, private=False,).filter(Submission.deleted_utc == 0)
if v.admin_level < 2: if v.admin_level < 2:

View file

@ -20,4 +20,3 @@ if FEATURES['AWARDS']:
from .volunteer import * from .volunteer import *
if app.debug: if app.debug:
from .dev import * from .dev import *
# from .subs import *

View file

@ -10,10 +10,8 @@ from files.routes.importstar import *
@app.get("/comment/<cid>") @app.get("/comment/<cid>")
@app.get("/post/<pid>/<anything>/<cid>") @app.get("/post/<pid>/<anything>/<cid>")
# @app.get("/h/<sub>/comment/<cid>")
# @app.get("/h/<sub>/post/<pid>/<anything>/<cid>")
@auth_desired @auth_desired
def post_pid_comment_cid(cid, pid=None, anything=None, v=None, sub=None): def post_pid_comment_cid(cid, pid=None, anything=None, v=None):
comment = get_comment(cid, v=v) comment = get_comment(cid, v=v)
if v and request.values.get("read"): if v and request.values.get("read"):
@ -100,7 +98,7 @@ def post_pid_comment_cid(cid, pid=None, anything=None, v=None, sub=None):
else: else:
if post.is_banned and not (v and (v.admin_level > 1 or post.author_id == v.id)): template = "submission_banned.html" if post.is_banned and not (v and (v.admin_level > 1 or post.author_id == v.id)): template = "submission_banned.html"
else: template = "submission.html" else: template = "submission.html"
return render_template(template, v=v, p=post, sort=sort, comment_info=comment_info, render_replies=True, sub=post.subr) return render_template(template, v=v, p=post, sort=sort, comment_info=comment_info, render_replies=True)
@app.post("/comment") @app.post("/comment")
@limiter.limit("1/second;20/minute;200/hour;1000/day") @limiter.limit("1/second;20/minute;200/hour;1000/day")
@ -115,7 +113,6 @@ def api_comment(v):
parent = None parent = None
parent_post = None parent_post = None
parent_comment_id = None parent_comment_id = None
sub = None
if parent_fullname.startswith("t2_"): if parent_fullname.startswith("t2_"):
parent = get_post(id, v=v) parent = get_post(id, v=v)
@ -127,8 +124,6 @@ def api_comment(v):
else: abort(400) else: abort(400)
if not parent_post: abort(404) # don't allow sending comments to the ether if not parent_post: abort(404) # don't allow sending comments to the ether
level = 1 if isinstance(parent, Submission) else parent.level + 1 level = 1 if isinstance(parent, Submission) else parent.level + 1
sub = parent_post.sub
if sub and v.exiled_from(sub): abort(403, f"You're exiled from /h/{sub}")
body = sanitize_raw(request.values.get("body"), allow_newlines=True, length_limit=COMMENT_BODY_LENGTH_MAXIMUM) body = sanitize_raw(request.values.get("body"), allow_newlines=True, length_limit=COMMENT_BODY_LENGTH_MAXIMUM)
if not body and not request.files.get('file'): if not body and not request.files.get('file'):
@ -413,46 +408,6 @@ def unpin_comment(cid, v):
return {"message": "Comment unpinned!"} return {"message": "Comment unpinned!"}
@app.post("/mod_pin/<cid>")
@auth_required
def mod_pin(cid, v):
comment = get_comment(cid, v=v)
if not comment.is_pinned:
if not (comment.post.sub and v.mods(comment.post.sub)): abort(403)
comment.is_pinned = v.username + " (Mod)"
g.db.add(comment)
if v.id != comment.author_id:
message = f"@{v.username} (Mod) has pinned your [comment]({comment.shortlink})!"
send_repeatable_notification(comment.author_id, message)
g.db.commit()
return {"message": "Comment pinned!"}
@app.post("/unmod_pin/<cid>")
@auth_required
def mod_unpin(cid, v):
comment = get_comment(cid, v=v)
if comment.is_pinned:
if not (comment.post.sub and v.mods(comment.post.sub)): abort(403)
comment.is_pinned = None
g.db.add(comment)
if v.id != comment.author_id:
message = f"@{v.username} (Mod) has unpinned your [comment]({comment.shortlink})!"
send_repeatable_notification(comment.author_id, message)
g.db.commit()
return {"message": "Comment unpinned!"}
@app.post("/save_comment/<cid>") @app.post("/save_comment/<cid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day") @limiter.limit("1/second;30/minute;200/hour;1000/day")
@auth_required @auth_required

View file

@ -163,15 +163,9 @@ def notifications(v):
@app.get("/") @app.get("/")
@app.get("/catalog") @app.get("/catalog")
# @app.get("/h/<sub>")
# @app.get("/s/<sub>")
@limiter.limit("3/second;30/minute;1000/hour;5000/day") @limiter.limit("3/second;30/minute;1000/hour;5000/day")
@auth_desired @auth_desired
def front_all(v, sub=None, subdomain=None): def front_all(v, subdomain=None):
if sub: sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if (request.path.startswith('/h/') or request.path.startswith('/s/')) and not sub: abort(404)
if g.webview and not session.get("session_id"): if g.webview and not session.get("session_id"):
session["session_id"] = secrets.token_hex(49) session["session_id"] = secrets.token_hex(49)
@ -205,8 +199,6 @@ def front_all(v, sub=None, subdomain=None):
filter_words=v.filter_words if v else [], filter_words=v.filter_words if v else [],
gt=gt, gt=gt,
lt=lt, lt=lt,
sub=sub,
site=SITE
) )
posts = get_posts(ids, v=v, eager=True) posts = get_posts(ids, v=v, eager=True)
@ -229,7 +221,7 @@ def front_all(v, sub=None, subdomain=None):
g.db.commit() g.db.commit()
if request.headers.get("Authorization"): return {"data": [x.json for x in posts], "next_exists": next_exists} if request.headers.get("Authorization"): return {"data": [x.json for x in posts], "next_exists": next_exists}
return render_template("home.html", v=v, listing=posts, next_exists=next_exists, sort=sort, t=t, page=page, ccmode=ccmode, sub=sub, home=True) return render_template("home.html", v=v, listing=posts, next_exists=next_exists, sort=sort, t=t, page=page, ccmode=ccmode, home=True)
@app.get("/changelog") @app.get("/changelog")
@ -245,7 +237,6 @@ def changelog(v):
page=page, page=page,
t=t, t=t,
v=v, v=v,
site=SITE
) )
next_exists = (len(ids) > 25) next_exists = (len(ids) > 25)

View file

@ -1,8 +1,7 @@
import sys
import time import time
import urllib.parse import urllib.parse
from io import BytesIO from io import BytesIO
from os import path
from sys import stdout
from urllib.parse import ParseResult, urlparse from urllib.parse import ParseResult, urlparse
import gevent import gevent
@ -12,7 +11,7 @@ from PIL import Image as PILimage
from sqlalchemy.orm import Query from sqlalchemy.orm import Query
import files.helpers.validators as validators import files.helpers.validators as validators
from files.__main__ import app, cache, db_session, limiter from files.__main__ import app, db_session, limiter
from files.classes import * from files.classes import *
from files.helpers.alerts import * from files.helpers.alerts import *
from files.helpers.caching import invalidate_cache from files.helpers.caching import invalidate_cache
@ -68,23 +67,14 @@ def publish(pid, v):
return redirect(post.permalink) return redirect(post.permalink)
@app.get("/submit") @app.get("/submit")
# @app.get("/h/<sub>/submit")
@auth_required @auth_required
def submit_get(v, sub=None): def submit_get(v):
if sub: sub = g.db.query(Sub.name).filter_by(name=sub.strip().lower()).one_or_none() return render_template("submit.html", v=v)
if request.path.startswith('/h/') and not sub: abort(404)
SUBS = [x[0] for x in g.db.query(Sub.name).order_by(Sub.name).all()]
return render_template("submit.html", SUBS=SUBS, v=v, sub=sub)
@app.get("/post/<pid>") @app.get("/post/<pid>")
@app.get("/post/<pid>/<anything>") @app.get("/post/<pid>/<anything>")
# @app.get("/h/<sub>/post/<pid>")
# @app.get("/h/<sub>/post/<pid>/<anything>")
@auth_desired @auth_desired
def post_id(pid, anything=None, v=None, sub=None): def post_id(pid, anything=None, v=None):
post = get_post(pid, v=v) post = get_post(pid, v=v)
if post.over_18 and not (v and v.over_18) and session.get('over_18', 0) < int(time.time()): if post.over_18 and not (v and v.over_18) and session.get('over_18', 0) < int(time.time()):
@ -135,7 +125,7 @@ def post_id(pid, anything=None, v=None, sub=None):
else: else:
if post.is_banned and not (v and (v.admin_level > 1 or post.author_id == v.id)): template = "submission_banned.html" if post.is_banned and not (v and (v.admin_level > 1 or post.author_id == v.id)): template = "submission_banned.html"
else: template = "submission.html" else: template = "submission.html"
return render_template(template, v=v, p=post, ids=list(ids), sort=sort, render_replies=True, offset=offset, sub=post.subr) return render_template(template, v=v, p=post, ids=list(ids), sort=sort, render_replies=True, offset=offset)
@app.get("/viewmore/<pid>/<sort>/<offset>") @app.get("/viewmore/<pid>/<sort>/<offset>")
@limiter.limit("1/second;30/minute;200/hour;1000/day") @limiter.limit("1/second;30/minute;200/hour;1000/day")
@ -430,7 +420,7 @@ def thumbnail_thread(pid):
db.commit() db.commit()
db.close() db.close()
stdout.flush() sys.stdout.flush()
return return
@ -439,7 +429,7 @@ def api_is_repost():
url = request.values.get('url') url = request.values.get('url')
if not url: abort(400) if not url: abort(400)
url = urllib.parse.unparse(canonicalize_url2(url, httpsify=True)) url = canonicalize_url2(url, httpsify=True).geturl()
if url.endswith('/'): url = url[:-1] if url.endswith('/'): url = url[:-1]
search_url = sql_ilike_clean(url) search_url = sql_ilike_clean(url)
@ -536,19 +526,16 @@ def _duplicate_check2(
@app.post("/submit") @app.post("/submit")
# @app.post("/h/<sub>/submit")
@limiter.limit("1/second;2/minute;10/hour;50/day") @limiter.limit("1/second;2/minute;10/hour;50/day")
@auth_required @auth_required
def submit_post(v, sub=None): def submit_post(v):
def error(error): def error(error):
title:str = request.values.get("title", "") title:str = request.values.get("title", "")
body:str = request.values.get("body", "") body:str = request.values.get("body", "")
url:str = request.values.get("url", "") url:str = request.values.get("url", "")
if request.headers.get("Authorization") or request.headers.get("xhr"): abort(400, error) if request.headers.get("Authorization") or request.headers.get("xhr"): abort(400, error)
return render_template("submit.html", v=v, error=error, title=title, url=url, body=body), 400
SUBS = [x[0] for x in g.db.query(Sub.name).order_by(Sub.name).all()]
return render_template("submit.html", SUBS=SUBS, v=v, error=error, title=title, url=url, body=body), 400
if v.is_suspended: return error("You can't perform this action while banned.") if v.is_suspended: return error("You can't perform this action while banned.")
@ -560,17 +547,6 @@ def submit_post(v, sub=None):
except ValueError as e: except ValueError as e:
return error(str(e)) return error(str(e))
sub = request.values.get("sub")
if sub: sub = sub.replace('/h/','').replace('s/','')
if sub and sub != 'none':
sname = sub.strip().lower()
sub = g.db.query(Sub.name).filter_by(name=sname).one_or_none()
if not sub: return error(f"/h/{sname} not found!")
sub = sub[0]
if v.exiled_from(sub): return error(f"You're exiled from /h/{sub}")
else: sub = None
duplicate:Optional[werkzeug.wrappers.Response] = \ duplicate:Optional[werkzeug.wrappers.Response] = \
_duplicate_check(validated_post.repost_search_url) _duplicate_check(validated_post.repost_search_url)
if duplicate: return duplicate if duplicate: return duplicate
@ -605,7 +581,6 @@ def submit_post(v, sub=None):
embed_url=validated_post.embed_slow, embed_url=validated_post.embed_slow,
title=validated_post.title, title=validated_post.title,
title_html=validated_post.title_html, title_html=validated_post.title_html,
sub=sub,
ghost=False, ghost=False,
filter_state='filtered' if v.admin_level == 0 and app.config['SETTINGS']['FilterNewPosts'] else 'normal', filter_state='filtered' if v.admin_level == 0 and app.config['SETTINGS']['FilterNewPosts'] else 'normal',
thumburl=validated_post.thumburl thumburl=validated_post.thumburl
@ -624,7 +599,7 @@ def submit_post(v, sub=None):
post.voted = 1 post.voted = 1
if 'megathread' in post.title.lower(): sort = 'new' if 'megathread' in post.title.lower(): sort = 'new'
else: sort = v.defaultsortingcomments else: sort = v.defaultsortingcomments
return render_template('submission.html', v=v, p=post, sort=sort, render_replies=True, offset=0, success=True, sub=post.subr) return render_template('submission.html', v=v, p=post, sort=sort, render_replies=True, offset=0, success=True)
@app.post("/delete_post/<pid>") @app.post("/delete_post/<pid>")
@ -667,7 +642,6 @@ def undelete_post_pid(pid, v):
@app.post("/toggle_comment_nsfw/<cid>") @app.post("/toggle_comment_nsfw/<cid>")
@auth_required @auth_required
def toggle_comment_nsfw(cid, v): def toggle_comment_nsfw(cid, v):
comment = g.db.query(Comment).filter_by(id=cid).one_or_none() comment = g.db.query(Comment).filter_by(id=cid).one_or_none()
if comment.author_id != v.id and not v.admin_level > 1: abort(403) if comment.author_id != v.id and not v.admin_level > 1: abort(403)
comment.over_18 = not comment.over_18 comment.over_18 = not comment.over_18
@ -681,7 +655,6 @@ def toggle_comment_nsfw(cid, v):
@app.post("/toggle_post_nsfw/<pid>") @app.post("/toggle_post_nsfw/<pid>")
@auth_required @auth_required
def toggle_post_nsfw(pid, v): def toggle_post_nsfw(pid, v):
post = get_post(pid) post = get_post(pid)
if post.author_id != v.id and not v.admin_level > 1: if post.author_id != v.id and not v.admin_level > 1:
@ -707,7 +680,6 @@ def toggle_post_nsfw(pid, v):
@limiter.limit("1/second;30/minute;200/hour;1000/day") @limiter.limit("1/second;30/minute;200/hour;1000/day")
@auth_required @auth_required
def save_post(pid, v): def save_post(pid, v):
post=get_post(pid) post=get_post(pid)
save = g.db.query(SaveRelationship).filter_by(user_id=v.id, submission_id=post.id).one_or_none() save = g.db.query(SaveRelationship).filter_by(user_id=v.id, submission_id=post.id).one_or_none()
@ -723,7 +695,6 @@ def save_post(pid, v):
@limiter.limit("1/second;30/minute;200/hour;1000/day") @limiter.limit("1/second;30/minute;200/hour;1000/day")
@auth_required @auth_required
def unsave_post(pid, v): def unsave_post(pid, v):
post=get_post(pid) post=get_post(pid)
save = g.db.query(SaveRelationship).filter_by(user_id=v.id, submission_id=post.id).one_or_none() save = g.db.query(SaveRelationship).filter_by(user_id=v.id, submission_id=post.id).one_or_none()
@ -742,7 +713,7 @@ def api_pin_post(post_id, v):
post.is_pinned = not post.is_pinned post.is_pinned = not post.is_pinned
g.db.add(post) g.db.add(post)
cache.delete_memoized(User.userpagelisting) invalidate_cache(userpagelisting=True)
g.db.commit() g.db.commit()
if post.is_pinned: return {"message": "Post pinned!"} if post.is_pinned: return {"message": "Post pinned!"}

View file

@ -1,387 +0,0 @@
from files.__main__ import app, limiter, mail
from files.helpers.alerts import *
from files.helpers.wrappers import *
from files.classes import *
from .front import frontlist
@app.post("/exile/post/<pid>")
@is_not_permabanned
def exile_post(v, pid):
p = get_post(pid)
sub = p.sub
if not sub: abort(400)
if not v.mods(sub): abort(403)
u = p.author
if u.mods(sub): abort(403)
if u.admin_level < 2 and not u.exiled_from(sub):
exile = Exile(user_id=u.id, sub=sub, exiler_id=v.id)
g.db.add(exile)
send_notification(u.id, f"@{v.username} has exiled you from /h/{sub} for [{p.title}]({p.shortlink})")
g.db.commit()
return {"message": "User exiled successfully!"}
@app.post("/exile/comment/<cid>")
@is_not_permabanned
def exile_comment(v, cid):
c = get_comment(cid)
sub = c.post.sub
if not sub: abort(400)
if not v.mods(sub): abort(403)
u = c.author
if u.mods(sub): abort(403)
if u.admin_level < 2 and not u.exiled_from(sub):
exile = Exile(user_id=u.id, sub=sub, exiler_id=v.id)
g.db.add(exile)
send_notification(u.id, f"@{v.username} has exiled you from /h/{sub} for [{c.permalink}]({c.shortlink})")
g.db.commit()
return {"message": "User exiled successfully!"}
@app.post("/h/<sub>/unexile/<uid>")
@is_not_permabanned
def unexile(v, sub, uid):
u = get_account(uid)
if not v.mods(sub): abort(403)
if u.exiled_from(sub):
exile = g.db.query(Exile).filter_by(user_id=u.id, sub=sub).one_or_none()
g.db.delete(exile)
send_notification(u.id, f"@{v.username} has revoked your exile from /h/{sub}")
g.db.commit()
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"message": "User unexiled successfully!"}
return redirect(f'/h/{sub}/exilees')
@app.post("/h/<sub>/block")
@auth_required
def block_sub(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
sub = sub.name
if v.mods(sub): abort(409, "You can't block subs you mod!")
existing = g.db.query(SubBlock).filter_by(user_id=v.id, sub=sub).one_or_none()
if not existing:
block = SubBlock(user_id=v.id, sub=sub)
g.db.add(block)
g.db.commit()
cache.delete_memoized(frontlist)
return {"message": "Sub blocked successfully!"}
@app.post("/h/<sub>/unblock")
@auth_required
def unblock_sub(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
sub = sub.name
block = g.db.query(SubBlock).filter_by(user_id=v.id, sub=sub).one_or_none()
if block:
g.db.delete(block)
g.db.commit()
cache.delete_memoized(frontlist)
return {"message": "Sub unblocked successfully!"}
@app.get("/h/<sub>/mods")
@auth_required
def mods(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
users = g.db.query(User, Mod).join(Mod, Mod.user_id==User.id).filter_by(sub=sub.name).order_by(Mod.created_utc).all()
return render_template("sub/mods.html", v=v, sub=sub, users=users)
@app.get("/h/<sub>/exilees")
@auth_required
def exilees(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
users = g.db.query(User, Exile).join(Exile, Exile.user_id==User.id).filter_by(sub=sub.name).all()
return render_template("sub/exilees.html", v=v, sub=sub, users=users)
@app.get("/h/<sub>/blockers")
@auth_required
def blockers(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
users = g.db.query(User).join(SubBlock, SubBlock.user_id==User.id).filter_by(sub=sub.name).all()
return render_template("sub/blockers.html", v=v, sub=sub, users=users)
@app.post("/h/<sub>/add_mod")
@limiter.limit("1/second;5/day")
@is_not_permabanned
def add_mod(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
sub = sub.name
if not v.mods(sub): abort(403)
user = request.values.get('user')
if not user: abort(400)
user = get_user(user)
existing = g.db.query(Mod).filter_by(user_id=user.id, sub=sub).one_or_none()
if not existing:
mod = Mod(user_id=user.id, sub=sub)
g.db.add(mod)
if v.id != user.id:
send_repeatable_notification(user.id, f"@{v.username} has added you as a mod to /h/{sub}")
g.db.commit()
return redirect(f'/h/{sub}/mods')
@app.post("/h/<sub>/remove_mod")
@is_not_permabanned
def remove_mod(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
sub = sub.name
if not v.mods(sub): abort(403)
uid = request.values.get('uid')
if not uid: abort(400)
try: uid = int(uid)
except: abort(400)
user = g.db.query(User).filter_by(id=uid).one_or_none()
if not user: abort(404)
mod = g.db.query(Mod).filter_by(user_id=user.id, sub=sub).one_or_none()
if not mod: abort(400)
if not (v.id == user.id or v.mod_date(sub) and v.mod_date(sub) < mod.created_utc): abort(403)
g.db.delete(mod)
if v.id != user.id:
send_repeatable_notification(user.id, f"@{v.username} has removed you as a mod from /h/{sub}")
g.db.commit()
return redirect(f'/h/{sub}/mods')
@app.get("/create_sub")
@is_not_permabanned
def create_sub(v):
num = v.subs_created + 1
for a in v.alts:
num += a.subs_created
cost = num * 100
return render_template("sub/create_sub.html", v=v, cost=cost)
@app.post("/create_sub")
@is_not_permabanned
def create_sub2(v):
name = request.values.get('name')
if not name: abort(400)
name = name.strip().lower()
num = v.subs_created + 1
for a in v.alts:
num += a.subs_created
cost = num * 100
if not valid_sub_regex.fullmatch(name):
return render_template("sub/create_sub.html", v=v, cost=cost, error="Sub name not allowed."), 400
sub = g.db.query(Sub).filter_by(name=name).one_or_none()
if not sub:
if v.coins < cost:
return render_template("sub/create_sub.html", v=v, cost=cost, error="You don't have enough coins!"), 403
v.coins -= cost
v.subs_created += 1
g.db.add(v)
sub = Sub(name=name)
g.db.add(sub)
g.db.flush()
mod = Mod(user_id=v.id, sub=sub.name)
g.db.add(mod)
g.db.commit()
return redirect(f'/h/{sub.name}')
@app.post("/kick/<pid>")
@is_not_permabanned
def kick(v, pid):
post = get_post(pid)
if not post.sub: abort(403)
if not v.mods(post.sub): abort(403)
post.sub = None
g.db.add(post)
g.db.commit()
cache.delete_memoized(frontlist)
return {"message": "Post kicked successfully!"}
@app.get('/h/<sub>/settings')
@is_not_permabanned
def sub_settings(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
return render_template('sub/settings.html', v=v, sidebar=sub.sidebar, sub=sub)
@app.post('/h/<sub>/sidebar')
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@is_not_permabanned
def post_sub_sidebar(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
sub.sidebar = request.values.get('sidebar', '').strip()[:500]
sub.sidebar_html = sanitize(sub.sidebar)
if len(sub.sidebar_html) > 1000: return "Sidebar is too big!"
g.db.add(sub)
g.db.commit()
return redirect(f'/h/{sub.name}/settings')
@app.post('/h/<sub>/css')
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@is_not_permabanned
def post_sub_css(v, sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
sub.css = request.values.get('css', '').strip()
g.db.add(sub)
g.db.commit()
return redirect(f'/h/{sub.name}/settings')
@app.get("/h/<sub>/css")
def get_sub_css(sub):
sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
resp=make_response(sub.css or "")
resp.headers.add("Content-Type", "text/css")
return resp
@app.post("/h/<sub>/banner")
@limiter.limit("1/second;10/day")
@is_not_permabanned
def sub_banner(v, sub):
if request.headers.get("cf-ipcountry") == "T1": abort(403, "Image uploads are not allowed through TOR.")
sub = g.db.query(Sub).filter_by(name=sub.lower().strip()).one_or_none()
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
file = request.files["banner"]
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
bannerurl = process_image(name)
if bannerurl:
if sub.bannerurl and '/images/' in sub.bannerurl:
fpath = '/images/' + sub.bannerurl.split('/images/')[1]
if path.isfile(fpath): os.remove(fpath)
sub.bannerurl = bannerurl
g.db.add(sub)
g.db.commit()
return redirect(f'/h/{sub.name}/settings')
@app.post("/h/<sub>/sidebar_image")
@limiter.limit("1/second;10/day")
@is_not_permabanned
def sub_sidebar(v, sub):
if request.headers.get("cf-ipcountry") == "T1": abort(403, "Image uploads are not allowed through TOR.")
sub = g.db.query(Sub).filter_by(name=sub.lower().strip()).one_or_none()
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
file = request.files["sidebar"]
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
sidebarurl = process_image(name)
if sidebarurl:
if sub.sidebarurl and '/images/' in sub.sidebarurl:
fpath = '/images/' + sub.sidebarurl.split('/images/')[1]
if path.isfile(fpath): os.remove(fpath)
sub.sidebarurl = sidebarurl
g.db.add(sub)
g.db.commit()
return redirect(f'/h/{sub.name}/settings')
@app.get("/holes")
@auth_desired
def subs(v):
subs = g.db.query(Sub, func.count(Submission.sub)).outerjoin(Submission, Sub.name == Submission.sub).group_by(Sub.name).order_by(func.count(Submission.sub).desc()).all()
return render_template('sub/subs.html', v=v, subs=subs)

View file

@ -681,7 +681,7 @@ def u_username(username, v=None):
try: page = max(int(request.values.get("page", 1)), 1) try: page = max(int(request.values.get("page", 1)), 1)
except: page = 1 except: page = 1
ids = listings.userpagelisting(u, site=SITE, v=v, page=page, sort=sort, t=t) ids = listings.userpagelisting(u, v=v, page=page, sort=sort, t=t)
next_exists = (len(ids) > 25) next_exists = (len(ids) > 25)
ids = ids[:25] ids = ids[:25]

View file

@ -156,7 +156,6 @@
{% if v and v.admin_level >= 2 %} {% if v and v.admin_level >= 2 %}
{%- include 'component/comment/actions_mobile_admin.html' -%} {%- include 'component/comment/actions_mobile_admin.html' -%}
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}

View file

@ -55,14 +55,6 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if c.post %}
{% set sub = c.post.sub %}
{% if sub and v.mods(sub) and not c.author.mods(sub) %}
<button id="exile-{{c.id}}" class="d-none {% if not c.author.exiled_from(sub) %}d-md-block{% endif %} dropdown-item list-inline-item text-danger" onclick="post_toast2(this,'/exile/comment/{{c.id}}','exile-{{c.id}}','unexile-{{c.id}}')"><i class="fas fa-campfire text-danger fa-fw"></i>Exile user</button>
<button id="unexile-{{c.id}}" class="d-none {% if c.author.exiled_from(sub) %}d-md-block{% endif %} dropdown-item list-inline-item text-success" onclick="post_toast2(this,'/h/{{sub}}/unexile/{{c.author_id}}','exile-{{c.id}}','unexile-{{c.id}}')"><i class="fas fa-campfire text-success fa-fw"></i>Unexile user</button>
{% endif %}
{% endif %}
{% if c.parent_submission and (c.author_id==v.id or v.admin_level >= 2) %} {% if c.parent_submission and (c.author_id==v.id or v.admin_level >= 2) %}
<button id="unmark-{{c.id}}" class="dropdown-item list-inline-item d-none {% if c.over_18 %}d-md-block{% endif %} text-danger" onclick="post_toast3(this,'/toggle_comment_nsfw/{{c.id}}','mark-{{c.id}}','unmark-{{c.id}}')"><i class="fas fa-eye-evil text-danger fa-fw"></i>Unmark +18</button> <button id="unmark-{{c.id}}" class="dropdown-item list-inline-item d-none {% if c.over_18 %}d-md-block{% endif %} text-danger" onclick="post_toast3(this,'/toggle_comment_nsfw/{{c.id}}','mark-{{c.id}}','unmark-{{c.id}}')"><i class="fas fa-eye-evil text-danger fa-fw"></i>Unmark +18</button>
<button id="mark-{{c.id}}" class="dropdown-item list-inline-item d-none {% if not c.over_18 %}d-md-block{% endif %} text-danger" onclick="post_toast3(this,'/toggle_comment_nsfw/{{c.id}}','mark-{{c.id}}','unmark-{{c.id}}')"><i class="fas fa-eye-evil text-danger fa-fw"></i>Mark +18</button> <button id="mark-{{c.id}}" class="dropdown-item list-inline-item d-none {% if not c.over_18 %}d-md-block{% endif %} text-danger" onclick="post_toast3(this,'/toggle_comment_nsfw/{{c.id}}','mark-{{c.id}}','unmark-{{c.id}}')"><i class="fas fa-eye-evil text-danger fa-fw"></i>Mark +18</button>

View file

@ -50,17 +50,6 @@
{% if c.post and v.id == c.post.author_id %} {% if c.post and v.id == c.post.author_id %}
<a id="pin2-{{c.id}}" class="list-group-item {% if c.is_pinned %}d-none{% endif %} text-info" role="button" data-bs-target="#actionsModal-{{c.id}}" onclick="post_toast2(this,'/pin_comment/{{c.id}}','pin2-{{c.id}}','unpin2-{{c.id}}')" data-bs-dismiss="modal"><i class="fas fa-thumbtack fa-rotate--45 text-info mr-2"></i>Pin</a> <a id="pin2-{{c.id}}" class="list-group-item {% if c.is_pinned %}d-none{% endif %} text-info" role="button" data-bs-target="#actionsModal-{{c.id}}" onclick="post_toast2(this,'/pin_comment/{{c.id}}','pin2-{{c.id}}','unpin2-{{c.id}}')" data-bs-dismiss="modal"><i class="fas fa-thumbtack fa-rotate--45 text-info mr-2"></i>Pin</a>
<a id="unpin2-{{c.id}}" class="list-group-item {% if not c.is_pinned %}d-none{% endif %} text-info" role="button" data-bs-target="#actionsModal-{{c.id}}" onclick="post_toast2(this,'/unpin_comment/{{c.id}}','pin2-{{c.id}}','unpin2-{{c.id}}')" data-bs-dismiss="modal"><i class="fas fa-thumbtack fa-rotate--45 text-info mr-2"></i>Unpin</a> <a id="unpin2-{{c.id}}" class="list-group-item {% if not c.is_pinned %}d-none{% endif %} text-info" role="button" data-bs-target="#actionsModal-{{c.id}}" onclick="post_toast2(this,'/unpin_comment/{{c.id}}','pin2-{{c.id}}','unpin2-{{c.id}}')" data-bs-dismiss="modal"><i class="fas fa-thumbtack fa-rotate--45 text-info mr-2"></i>Unpin</a>
{% elif c.post.sub and v.mods(c.post.sub) %}
<a id="pin2-{{c.id}}" class="list-group-item {% if c.is_pinned %}d-none{% endif %} text-info" role="button" data-bs-target="#actionsModal-{{c.id}}" onclick="post_toast2(this,'/mod_pin/{{c.id}}','pin2-{{c.id}}','unpin2-{{c.id}}')" data-bs-dismiss="modal"><i class="fas fa-thumbtack fa-rotate--45 text-info mr-2"></i>Pin</a>
<a id="unpin2-{{c.id}}" class="list-group-item {% if not c.is_pinned %}d-none{% endif %} text-info" role="button" data-bs-target="#actionsModal-{{c.id}}" onclick="post_toast2(this,'/mod_unpin/{{c.id}}','pin2-{{c.id}}','unpin2-{{c.id}}')" data-bs-dismiss="modal"><i class="fas fa-thumbtack fa-rotate--45 text-info mr-2"></i>Unpin</a>
{% endif %}
{% endif %}
{% if c.post %}
{% set sub = c.post.sub %}
{% if sub and v.mods(sub) and not c.author.mods(sub) %}
<a data-bs-dismiss="modal" id="exile2-{{c.id}}" class="{% if c.author.exiled_from(sub) %}d-none{% endif %} list-group-item text-danger" onclick="post_toast2(this,'/exile/comment/{{c.id}}','exile2-{{c.id}}','unexile2-{{c.id}}')"><i class="fas fa-campfire text-danger mr-2"></i>Exile user</a>
<a data-bs-dismiss="modal" id="unexile2-{{c.id}}" class="{% if not c.author.exiled_from(sub) %}d-none{% endif %} list-group-item text-success" onclick="post_toast2(this,'/h/{{sub}}/unexile/{{c.author_id}}','exile2-{{c.id}}','unexile2-{{c.id}}')"><i class="fas fa-campfire text-success mr-2"></i>Unexile user</a>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}

View file

@ -24,13 +24,6 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if c.post %}
{% set sub = c.post.sub %}
{% if sub and c.author.exiled_from(sub) %}
<a role="button"><i class="fas fa-campfire text-danger" data-bs-toggle="tooltip" data-bs-placement="bottom" title="User has been exiled from /h/{{sub}}"></i></a>
{% endif %}
{% endif %}
{% if c.bannedfor %} {% if c.bannedfor %}
<a role="button"><i class="fas fa-hammer-crash text-danger" data-bs-toggle="tooltip" data-bs-placement="bottom" title="User was banned for this comment{% if c.author.banned_by %} by @{{c.author.banned_by.username}}{% endif %}"></i></a> <a role="button"><i class="fas fa-hammer-crash text-danger" data-bs-toggle="tooltip" data-bs-placement="bottom" title="User was banned for this comment{% if c.author.banned_by %} by @{{c.author.banned_by.username}}{% endif %}"></i></a>
{% endif %} {% endif %}

View file

@ -72,25 +72,12 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if v.id != p.author_id and not p.ghost %} {% if v.id != p.author_id and not p.ghost %}
<a id="unblock-{{p.id}}" class="dropdown-item text-success list-inline-item {% if not p.is_blocking %}d-none{% endif %}" role="button" onclick="post_toast2(this,'/settings/unblock?username={{p.author_name}}','block-{{p.id}}','unblock-{{p.id}}')"><i class="fas fa-eye text-success"></i>Unblock user</a> <a id="unblock-{{p.id}}" class="dropdown-item text-success list-inline-item {% if not p.is_blocking %}d-none{% endif %}" role="button" onclick="post_toast2(this,'/settings/unblock?username={{p.author_name}}','block-{{p.id}}','unblock-{{p.id}}')"><i class="fas fa-eye text-success"></i>Unblock user</a>
<a id="block-{{p.id}}" class="dropdown-item list-inline-item text-danger {% if p.is_blocking %}d-none{% endif %}" role="button" onclick="post_toast2(this,'/settings/block?username={{p.author_name}}','block-{{p.id}}','unblock-{{p.id}}')"><i class="fas fa-eye-slash text-danger"></i>Block user</a> <a id="block-{{p.id}}" class="dropdown-item list-inline-item text-danger {% if p.is_blocking %}d-none{% endif %}" role="button" onclick="post_toast2(this,'/settings/block?username={{p.author_name}}','block-{{p.id}}','unblock-{{p.id}}')"><i class="fas fa-eye-slash text-danger"></i>Block user</a>
{% endif %} {% endif %}
{% if p.sub and v.mods(p.sub) %}
<a class="dropdown-item list-inline-item text-danger" role="button" onclick="post_toast(this,'/kick/{{p.id}}')"><i class="fas fa-sign-out text-danger"></i>Remove</a>
{% if not p.author.mods(p.sub) %}
<a id="exile-{{p.id}}" class="{% if p.author.exiled_from(p.sub) %}d-none{% endif %} dropdown-item list-inline-item text-danger" role="button" onclick="post_toast2(this,'/exile/post/{{p.id}}','exile-{{p.id}}','unexile-{{p.id}}')"><i class="fas fa-campfire text-danger"></i>Exile user</a>
<a id="unexile-{{p.id}}" class="{% if not p.author.exiled_from(p.sub) %}d-none{% endif %} dropdown-item list-inline-item text-success" role="button" onclick="post_toast2(this,'/h/{{sub}}/unexile/{{p.author_id}}','exile-{{p.id}}','unexile-{{p.id}}')"><i class="fas fa-campfire text-success"></i>Unexile user</a>
{% endif %}
{% endif %}
{% if v.id==p.author_id or v.admin_level > 1 %} {% if v.id==p.author_id or v.admin_level > 1 %}
<a id="mark-{{p.id}}" class="dropdown-item {% if p.over_18 %}d-none{% endif %} list-inline-item text-danger" role="button" onclick="post_toast2(this,'/toggle_post_nsfw/{{p.id}}','mark-{{p.id}}','unmark-{{p.id}}')"><i class="fas fa-eye-evil"></i>Mark +18</a> <a id="mark-{{p.id}}" class="dropdown-item {% if p.over_18 %}d-none{% endif %} list-inline-item text-danger" role="button" onclick="post_toast2(this,'/toggle_post_nsfw/{{p.id}}','mark-{{p.id}}','unmark-{{p.id}}')"><i class="fas fa-eye-evil"></i>Mark +18</a>
<a id="unmark-{{p.id}}" class="dropdown-item {% if not p.over_18 %}d-none{% endif %} list-inline-item text-success" role="button" onclick="post_toast2(this,'/toggle_post_nsfw/{{p.id}}','mark-{{p.id}}','unmark-{{p.id}}')"><i class="fas fa-eye-evil"></i>Unmark +18</a> <a id="unmark-{{p.id}}" class="dropdown-item {% if not p.over_18 %}d-none{% endif %} list-inline-item text-success" role="button" onclick="post_toast2(this,'/toggle_post_nsfw/{{p.id}}','mark-{{p.id}}','unmark-{{p.id}}')"><i class="fas fa-eye-evil"></i>Unmark +18</a>

View file

@ -50,13 +50,4 @@
<button id="block2-{{p.id}}" class="blockuser nobackground btn btn-link btn-block btn-lg text-danger text-left{% if p.is_blocking %} d-none{% endif %}" onclick="document.getElementById('block2-{{p.id}}').classList.toggle('d-none');document.getElementById('prompt2-{{p.id}}').classList.toggle('d-none');"><i class="fas fa-eye-slash mr-3 text-danger"></i>Block user</button> <button id="block2-{{p.id}}" class="blockuser nobackground btn btn-link btn-block btn-lg text-danger text-left{% if p.is_blocking %} d-none{% endif %}" onclick="document.getElementById('block2-{{p.id}}').classList.toggle('d-none');document.getElementById('prompt2-{{p.id}}').classList.toggle('d-none');"><i class="fas fa-eye-slash mr-3 text-danger"></i>Block user</button>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if p.sub and v.mods(p.sub) %}
<button data-bs-dismiss="modal" class="nobackground btn btn-link btn-block btn-lg text-left text-danger" onclick="post_toast(this,'/kick/{{p.id}}')"><i class="fas fa-sign-out text-danger text-center mr-3"></i>Remove</button>
{% if not p.author.mods(p.sub) %}
<button data-bs-dismiss="modal" id="exile2" class="{% if p.author.exiled_from(p.sub) %}d-none{% endif %} nobackground btn btn-link btn-block btn-lg text-left text-danger" onclick="post_toast2(this,'/exile/post/{{p.id}}','exile2','unexile2')"><i class="fas fa-campfire mr-3 text-danger"></i>Exile user</button>
<button data-bs-dismiss="modal" id="unexile2" class="{% if not p.author.exiled_from(p.sub) %}d-none{% endif %} nobackground btn btn-link btn-block btn-lg text-left text-success" onclick="post_toast2(this,'/h/{{sub}}/unexile/{{p.author_id}}','exile2','unexile2')"><i class="fas fa-campfire mr-3 text-success"></i>Unexile user</button>
{% endif %}
{% endif %}
{%- endif -%} {# {%- if p.is_real_submission -%} #} {%- endif -%} {# {%- if p.is_real_submission -%} #}

View file

@ -27,10 +27,6 @@
<link rel="stylesheet" href="{{ 'css/catalog.css' | asset }}"> <link rel="stylesheet" href="{{ 'css/catalog.css' | asset }}">
{% endif %} {% endif %}
{% if sub and sub.css and not request.path.endswith('settings') %}
<link rel="stylesheet" href="/h/{{sub.name}}/css" type="text/css">
{% endif %}
{% if v and v.themecolor == '30409f' %} {% if v and v.themecolor == '30409f' %}
<style> <style>
p a { p a {
@ -219,14 +215,10 @@
{% block Banner %} {% block Banner %}
{% if '@' not in request.path %} {% if '@' not in request.path %}
{% if sub %}
<img alt="/h/{{sub.name}} banner" class="primary_banner" role="button" data-bs-toggle="modal" data-bs-target="#expandImageModal" onclick="expandDesktopImage('{{sub.banner_url}}')" loading="lazy" src="{{sub.banner_url}}" width=100% style="object-fit:cover;max-height:25vw">
{% else %}
<a href="/"> <a href="/">
<img alt="site banner" class="primary_banner" src="{{ ('images/'~SITE_ID~'/banner.webp') | asset }}" width="100%"> <img alt="site banner" class="primary_banner" src="{{ ('images/'~SITE_ID~'/banner.webp') | asset }}" width="100%">
</a> </a>
{% endif %} {% endif %}
{% endif %}
{% endblock %} {% endblock %}
{% block mobileUserBanner %} {% block mobileUserBanner %}

View file

@ -6,13 +6,9 @@
<img alt="header icon" height=33 width=80 src="{{ ('images/'~SITE_ID~'/headericon.webp') | asset }}"> <img alt="header icon" height=33 width=80 src="{{ ('images/'~SITE_ID~'/headericon.webp') | asset }}">
</a> </a>
{% if sub %}
<a href="/h/{{sub.name}}" class="font-weight-bold ml-1 flex-grow-1 mt-1" style="font-size:max(14px,1.2vw)">/h/{{sub.name}}</a>
{% else %}
<a href="/" class="flex-grow-1"> <a href="/" class="flex-grow-1">
<img id="logo" alt="logo" src="{{ ('images/'~SITE_ID~'/logo.webp') | asset }}" width="100" height="20"> <img id="logo" alt="logo" src="{{ ('images/'~SITE_ID~'/logo.webp') | asset }}" width="100" height="20">
</a> </a>
{% endif %}
<div class="flex-grow-1 d-fl d-none d-md-block {% if not v %}pad{% endif %}"> <div class="flex-grow-1 d-fl d-none d-md-block {% if not v %}pad{% endif %}">
<form id="searchform" class="form-inline search flex-nowrap mx-0 mx-lg-auto" {% if err %}style="margin-right:40rem!important"{% endif %} action="{% if request.path.startswith('/search') %}{{request.path}}{% else %}/search/posts/{% endif %}" method="get"> <form id="searchform" class="form-inline search flex-nowrap mx-0 mx-lg-auto" {% if err %}style="margin-right:40rem!important"{% endif %} action="{% if request.path.startswith('/search') %}{{request.path}}{% else %}/search/posts/{% endif %}" method="get">
@ -44,7 +40,7 @@
{% endif %} {% endif %}
{% if v %} {% if v %}
<a class="mobile-nav-icon d-md-none" href="{% if sub %}/h/{{sub.name}}{% endif %}/submit" data-bs-toggle="tooltip" data-bs-placement="bottom" title="New Post"><i class="fas fa-edit align-middle text-gray-500 black"></i></a> <a class="mobile-nav-icon d-md-none" href="/submit" data-bs-toggle="tooltip" data-bs-placement="bottom" title="New Post"><i class="fas fa-edit align-middle text-gray-500 black"></i></a>
{% else %} {% else %}
<a class="mobile-nav-icon d-md-none" href="/login" data-bs-toggle="tooltip" data-bs-placement="bottom" title="New Post"><i class="fas fa-edit align-middle text-gray-500 black"></i></a> <a class="mobile-nav-icon d-md-none" href="/login" data-bs-toggle="tooltip" data-bs-placement="bottom" title="New Post"><i class="fas fa-edit align-middle text-gray-500 black"></i></a>
{% endif %} {% endif %}
@ -91,7 +87,7 @@
{% if v %} {% if v %}
<li class="nav-item d-flex align-items-center justify-content-center text-center mx-1"> <li class="nav-item d-flex align-items-center justify-content-center text-center mx-1">
<a class="nav-link" href="{% if sub %}/h/{{sub.name}}{% endif %}/submit" data-bs-toggle="tooltip" data-bs-placement="bottom" title="New Post"><i class="fas fa-edit"></i></a> <a class="nav-link" href="/submit" data-bs-toggle="tooltip" data-bs-placement="bottom" title="New Post"><i class="fas fa-edit"></i></a>
</li> </li>
<li class="nav-item d-flex align-items-center justify-content-center text-center"> <li class="nav-item d-flex align-items-center justify-content-center text-center">

View file

@ -33,22 +33,11 @@
<div class="row" style="overflow: visible;padding-top:5px;"> <div class="row" style="overflow: visible;padding-top:5px;">
<div class="col"> <div class="col">
{% if sub %}
<div class="mt-3">
{% if v %}
<a class="btn btn-primary btn-block {% if v.blocks(sub.name) %}d-none{% endif %}" onclick="post_toast(this,'/h/{{sub.name}}/block','block-sub','unblock-sub');this.classList.toggle('d-none');nextElementSibling.classList.toggle('d-none')">Block /h/{{sub.name}}</a>
<a class="btn btn-primary btn-block {% if not v.blocks(sub.name) %}d-none{% endif %}" onclick="post_toast(this,'/h/{{sub.name}}/unblock','block-sub','unblock-sub');this.classList.toggle('d-none');previousElementSibling.classList.toggle('d-none')">Unblock /h/{{sub.name}}</a>
{% else %}
<a class="btn btn-primary btn-block" href="/login">Block /h/{{sub.name}}</a>
{% endif %}
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-center pt-3 pb-2 sorting" style="float:right"> <div class="d-flex justify-content-between align-items-center pt-3 pb-2 sorting" style="float:right">
{% block navbar %} {% block navbar %}
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<a href="{% if sub %}/h/{{sub.name}}{% endif %}/submit" class="btn btn-secondary ml-2 mr-2 d-nowrap"> <a href="/submit" class="btn btn-secondary ml-2 mr-2 d-nowrap">
<i class="fas fa-edit mr-2 "></i> <i class="fas fa-edit mr-2 "></i>
Post Post
</a> </a>

View file

@ -4,22 +4,6 @@
{% include "nav_secondary.html" %} {% include "nav_secondary.html" %}
</div> </div>
{% if sub %}
{% if sub.sidebar_html %}
<div class="mb-4">{{sub.sidebar_html|safe}}</div>
{% endif %}
{% if v %}
{% if v.admin_level > 2 %}
<a class="btn btn-primary btn-block mb-3" href="/create_sub">CREATE HOLE</a>
{% endif %}
{% if v.mods(sub.name) %}
<a class="btn btn-primary btn-block mb-3" href="/h/{{sub.name}}/settings">HOLE SETTINGS</a>
{% endif %}
{% endif %}
<a class="btn btn-primary btn-block mb-3" href="/h/{{sub.name}}/mods">HOLE MODS</a>
<a class="btn btn-primary btn-block mb-3" href="/h/{{sub.name}}/exilees">HOLE EXILEES</a>
<a class="btn btn-primary btn-block mb-3" href="/h/{{sub.name}}/blockers">HOLE BLOCKERS</a>
{% else %}
<div class="rules mt-4"> <div class="rules mt-4">
<h4>What is this place?</h4> <h4>What is this place?</h4>
@ -129,5 +113,4 @@
]|shuffle|join('\n')|safe}} ]|shuffle|join('\n')|safe}}
</ul> </ul>
</div> </div>
{% endif %}
</div> </div>

View file

@ -1,24 +0,0 @@
{% extends "default.html" %}
{% block content %}
<pre>
</pre>
<h5>Users blocking /h/{{sub.name}}</h5>
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
</tr>
</thead>
{% for user in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -1,62 +0,0 @@
{% extends "submit.html" %}
{% block title %}
<title>Create a hole</title>
{% endblock %}
{% block form %}
<pre>
</pre>
<form id="submitform" action="/create_sub" method="post">
<div class="container">
<div class="row justify-content-center mb-4 pb-7">
<div class="col col-md-6 p-3 py-md-0">
<h1 class="d-none d-md-block">Create a hole</h1>
<h2 class="h3 d-md-none">Create a hole</h2>
<div class="body">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<label for="title">Sub Name</label>
<input minlength="3" maxlength="20" pattern='[a-zA-Z0-9_\-]*' class="form-control" id="title-register" aria-describedby="titleHelpRegister" type="text" name="name" required>
<small class="form-text text-muted">3-20 characters, including letters, numbers, _ , and -</small>
</div>
<div class="footer">
<div class="d-flex">
{% if error %}
<p class="mb-0">
<span class="text-danger text-small" style="vertical-align: sub;">{{ error }}</span>
</p>
{% endif %}
<button class="btn btn-primary ml-auto" id="create_button" {% if cost > v.coins %}disabled{% endif %}>Create Hole</button>
</div>
<p class="mt-2 mr-1" style="float: right"><b>Cost</b>: {{cost}} coins</p>
</div>
</div>
</div>
</div>
</form>
{% endblock %}

View file

@ -1,36 +0,0 @@
{% extends "default.html" %}
{% block content %}
<pre>
</pre>
<h5>Users exiled from /h/{{sub.name}}</h5>
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
<th>Exiled by</th>
<th></th>
</tr>
</thead>
{% for user, exile in users %}
<tr>
{% set exiler=exile.exiler %}
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
<td><a style="color:#{{exiler.namecolor}}" href="/@{{exiler.username}}"><img loading="lazy" src="{{exiler.profile_url}}" class="pp20"><span {% if exiler.patron %}class="patron" style="background-color:#{{exiler.namecolor}}"{% endif %}>{{exiler.username}}</span></a></td>
<td>
{% if v.mods(sub.name) %}
<form action="/h/{{sub.name}}/unexile/{{user.id}}" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input class="btn btn-primary" style="margin-top:-5px" autocomplete="off" class="btn btn-primary ml-auto" type="submit" value="Unexile">
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -1,45 +0,0 @@
{% extends "default.html" %}
{% block content %}
<pre>
</pre>
<h5>/h/{{sub.name}} Mods</h5>
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
<th>Mod since</th>
<th></th>
</tr>
</thead>
{% for user, mod in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
<td>{{mod.created_datetime}}</td>
<td>
{% if v.id == user.id or v.mod_date(sub.name) and v.mod_date(sub.name) < mod.created_utc %}
<form action="/h/{{sub.name}}/remove_mod" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input type="hidden" name="uid" value="{{user.id}}">
<input class="btn btn-primary" style="margin-top:-5px" autocomplete="off" class="btn btn-primary ml-auto" type="submit" value="{% if v.id == user.id %}Resign{% else %}Remove Mod{% endif %}">
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% if v.mods(sub.name) %}
<form action="/h/{{sub.name}}/add_mod" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input class="form-control" style="display:inline;width:250px" autocomplete="off" type="text" name="user" class="form-control" placeholder="Enter username..">
<input class="btn btn-primary" style="margin-top:-5px" autocomplete="off" class="btn btn-primary ml-auto" type="submit" value="Add Mod">
</form>
{% endif %}
{% endblock %}

View file

@ -1,139 +0,0 @@
{% extends "default.html" %}
{% block pagetitle %}Edit {{SITE_TITLE}} sidebar{% endblock %}
{% block content %}
{% if msg %}
<div class="alert alert-success alert-dismissible fade show my-3" role="alert">
<i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
<span>
{{msg}}
</span>
<button class="close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
{% endif %}
<h2 class="h5 mt-5">Sidebar Picture</h2>
<div class="settings-section rounded">
<div class="d-flex">
<div class="title w-lg-25 text-md-center">
<img loading="lazy" alt="sub sidebar picture" src="{{sub.sidebar_url}}" class="profile-pic-75">
</div>
<div class="body w-lg-100 my-auto">
<div class="d-flex">
<div>
<form action="/h/{{sub.name}}/sidebar_image" method="post" enctype="multipart/form-data">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<label class="btn btn-secondary text-capitalize mr-2 mb-0">
Update<input autocomplete="off" type="file" accept="image/*" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} hidden name="sidebar" onchange="form.submit()">
</label>
</form>
</div>
</div>
<div class="text-small-extra text-muted mt-3">JPG, PNG, GIF files are supported. Max file size is {% if v and v.patron %}8{% else %}4{% endif %} MB.</div>
</div>
</div>
</div>
<h2 class="h5 mt-5">Banner</h2>
<div class="settings-section rounded">
<div class="d-flex">
<div class="title w-lg-75 text-md-center">
<img loading="lazy" alt="/h/{[sub.name]} banner" src="{{sub.banner_url}}" class="banner-pic-135">
</div>
<div class="body w-lg-100 my-auto">
<div class="d-flex">
<div>
<form action="/h/{{sub.name}}/banner" method="post" enctype="multipart/form-data">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<label class="btn btn-secondary text-capitalize mr-2 mb-0">
Update<input autocomplete="off" type="file" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} accept="image/*" hidden name="banner" onchange="form.submit()">
</label>
</form>
</div>
</div>
<div class="text-small-extra text-muted mt-3">JPG, PNG, GIF files are supported. Max file size is {% if v and v.patron %}8{% else %}4{% endif %} MB.</div>
</div>
</div>
</div>
<div class="row my-5 pt-5">
<div class="col col-md-8">
<div class="settings">
<div id="description">
<h2>Edit Sidebar</h2>
<br>
</div>
<div class="body d-lg-flex">
<div class="w-lg-100">
<form id="sidebar" action="/h/{{sub.name}}/sidebar" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<textarea autocomplete="off" maxlength="500" class="form-control rounded" id="bio-text" aria-label="With textarea" placeholder="Enter sidebar here..." rows="10" name="sidebar" form="sidebar">{% if sub.sidebar %}{{sub.sidebar}}{% endif %}</textarea>
<div class="d-flex mt-2">
<input autocomplete="off" class="btn btn-primary ml-auto" type="submit" value="Save">
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col col-md-8">
<div class="settings">
<div id="description">
<h2>Edit CSS</h2>
<br>
</div>
<div class="body d-lg-flex">
<div class="w-lg-100">
<form id="css" action="/h/{{sub.name}}/css" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<textarea autocomplete="off" maxlength="4000" class="form-control rounded" id="bio-text" aria-label="With textarea" placeholder="Enter css here..." rows="10" name="css" form="css">{% if sub.css %}{{sub.css}}{% endif %}</textarea>
<div class="d-flex mt-2">
<input autocomplete="off" class="btn btn-primary ml-auto" type="submit" value="Save">
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,32 +0,0 @@
{% extends "default.html" %}
{% block content %}
<script src="{{ 'js/sort_table.js' | asset }}"></script>
<pre>
</pre>
<h5>List of holes</h5>
<pre></pre>
<div class="overflow-x-auto">
<table id="sortable_table" class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
<th role="button" onclick="sort_table(2)">Posts</th>
<th role="button" onclick="sort_table(3)">Blockers</th>
</tr>
</thead>
{% for sub, count in subs %}
<tr>
<td>{{loop.index}}</td>
<td><a href="/h/{{sub.name}}">{{sub.name}}</a></td>
<td><a href="/h/{{sub.name}}">{{count}}</a></td>
<td><a href="/h/{{sub.name}}/blockers">{{sub.block_num}}</a></td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -1,24 +0,0 @@
{% extends "default.html" %}
{% block content %}
<pre>
</pre>
<h5>Users subscribed to /h/{{sub.name}}</h5>
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
</tr>
</thead>
{% for user in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -32,7 +32,6 @@
localStorage.setItem("post_title", "") localStorage.setItem("post_title", "")
localStorage.setItem("post_text", "") localStorage.setItem("post_text", "")
localStorage.setItem("post_url", "") localStorage.setItem("post_url", "")
localStorage.setItem("sub", "")
</script> </script>
{% endif %} {% endif %}
@ -139,14 +138,6 @@
<div id="post-content" class="{% if p.deleted_utc %}deleted {% endif %}card-block w-100 my-md-auto"> <div id="post-content" class="{% if p.deleted_utc %}deleted {% endif %}card-block w-100 my-md-auto">
<div class="post-meta text-left mb-2"> <div class="post-meta text-left mb-2">
{% if p.sub %}
<a href='/h/{{p.sub}}'>/h/{{p.sub}}</a>&nbsp;&nbsp;
{% endif %}
{% if p.sub and p.author.exiled_from(p.sub) %}
<a role="button"><i class="fas fa-campfire text-danger" data-bs-toggle="tooltip" data-bs-placement="bottom" title="User has been exiled from /h/{{p.sub}}"></i></a>
{% endif %}
{% if p.bannedfor %} {% if p.bannedfor %}
<a role="button"><i class="fas fa-hammer-crash text-danger" data-bs-toggle="tooltip" data-bs-placement="bottom" title="User was banned for this post{% if p.author.banned_by %} by @{{p.author.banned_by.username}}{% endif %}"></i></a> <a role="button"><i class="fas fa-hammer-crash text-danger" data-bs-toggle="tooltip" data-bs-placement="bottom" title="User was banned for this post{% if p.author.banned_by %} by @{{p.author.banned_by.username}}{% endif %}"></i></a>
{% endif %} {% endif %}

View file

@ -152,14 +152,6 @@
{% if p.flair %}<span class="patron font-weight-bolder mr-1" style="background-color:var(--primary); font-size:12px; line-height:2;">{{p.flair | safe}}</span>{% endif %} {% if p.flair %}<span class="patron font-weight-bolder mr-1" style="background-color:var(--primary); font-size:12px; line-height:2;">{{p.flair | safe}}</span>{% endif %}
{{p.realtitle(v) | safe}} {{p.realtitle(v) | safe}}
</a></h5> </a></h5>
{% if p.sub %}
<a href='/h/{{p.sub}}'>/h/{{p.sub}}</a>&nbsp;&nbsp;
{% endif %}
{% if p.sub and p.author.exiled_from(p.sub) %}
<a role="button"><i class="fas fa-campfire text-danger" data-bs-toggle="tooltip" data-bs-placement="bottom" title="User has been exiled from /h/{{p.sub}}"></i></a>
{% endif %}
</div> </div>
<div class="post-info text-left"> <div class="post-info text-left">
@ -366,7 +358,7 @@
</span> </span>
<h2 class="h5">You haven't {% if "saved" in request.full_path %}saved{% else %}made{% endif %} a post yet</h2> <h2 class="h5">You haven't {% if "saved" in request.full_path %}saved{% else %}made{% endif %} a post yet</h2>
<p class="text-muted mb-md-5">Your {% if "saved" in request.full_path %}saved posts{% else %}posting history{% endif %} will show here.</p> <p class="text-muted mb-md-5">Your {% if "saved" in request.full_path %}saved posts{% else %}posting history{% endif %} will show here.</p>
{% if "saved" not in request.full_path %}<a href="{% if sub %}/h/{{sub.name}}{% endif %}/submit" class="btn btn-primary">Create a post</a>{% endif %} {% if "saved" not in request.full_path %}<a href="/submit" class="btn btn-primary">Create a post</a>{% endif %}
</div> </div>
</div> </div>
</div> </div>

View file

@ -39,7 +39,7 @@
{% include "header.html" %} {% include "header.html" %}
{% block form %} {% block form %}
<div class="submit-grid-view"> <div class="submit-grid-view">
<form id="submitform" action="{% if sub %}/h/{{sub.name}}{% endif %}/submit" method="post" enctype="multipart/form-data" style="grid-column: 2"> <form id="submitform" action="/submit" method="post" enctype="multipart/form-data" style="grid-column: 2">
<div class="container"> <div class="container">
<div class="row justify-content-center mb-5"> <div class="row justify-content-center mb-5">
<div class="col p-3 py-md-0"> <div class="col p-3 py-md-0">
@ -146,13 +146,6 @@
</div> </div>
{% endblock %} {% endblock %}
{% if request.path == '/submit' %}
<script>
let sub = document.getElementById('sub')
if (sub) sub.value = localStorage.getItem("sub")
</script>
{% endif %}
<script src="{{ 'js/vendor/purify.min.js' | asset }}"></script> <script src="{{ 'js/vendor/purify.min.js' | asset }}"></script>
<script src="{{ 'js/vendor/marked.min.js' | asset }}"></script> <script src="{{ 'js/vendor/marked.min.js' | asset }}"></script>
<script src="{{ 'js/marked.custom.js' | asset }}"></script> <script src="{{ 'js/marked.custom.js' | asset }}"></script>

View file

@ -142,17 +142,6 @@
</div> </div>
{% endif %} {% endif %}
{% if u.moderated_subs %}
<div class="text-white rounded p-2 mb-3" style="background-color: rgba(50, 50, 50, 0.6); width: 30%;">
<p class="text-uppercase my-0 pb-1" style="font-weight: bold; font-size: 12px;">Moderator of</p>
{% for a in u.moderated_subs %}
<span class="d-inline-block mx-1">
<a href="/h/{{a['sub']}}">/h/{{a['sub']}}</a>
</span>
{% endfor %}
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<div> <div>
{% if v and v.id != u.id %} {% if v and v.id != u.id %}
@ -387,17 +376,6 @@
</div> </div>
{% endif %} {% endif %}
{% if u.moderated_subs %}
<div class="text-white rounded p-2 mb-3" style="background-color: rgba(50, 50, 50, 0.6);">
<p class="text-uppercase my-0 pb-1" style="font-weight: bold; font-size: 12px;">Moderator of</p>
{% for a in u.moderated_subs %}
<span class="d-inline-block mx-1">
<a href="/h/{{a['sub']}}">/h/{{a['sub']}}</a>
</span>
{% endfor %}
</div>
{% endif %}
<div class="mb-3"> <div class="mb-3">
{% for b in u.badges %} {% for b in u.badges %}
{% if b.url %} {% if b.url %}

View file

@ -202,7 +202,6 @@ def test_bulk_update_descendant_count_quick(accounts, submissions, comments):
'embed_url': None, 'embed_url': None,
'title': f'Clever unique post title number {i}', 'title': f'Clever unique post title number {i}',
'title_html': f'Clever unique post title number {i}', 'title_html': f'Clever unique post title number {i}',
'sub': None,
'ghost': False, 'ghost': False,
'filter_state': 'normal' 'filter_state': 'normal'
}) })

View file

@ -0,0 +1,89 @@
"""remove holes
Revision ID: 6b6c1b21a487
Revises: 8b97d5de5050
Create Date: 2023-03-30 05:36:17.697225+00:00
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '6b6c1b21a487'
down_revision = '8b97d5de5050'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! (adjusted -@TLSM) ###
op.drop_index('fki_exile_exiler_fkey', table_name='exiles')
op.drop_index('fki_exile_sub_fkey', table_name='exiles')
op.drop_table('exiles')
op.drop_index('fki_mod_sub_fkey', table_name='mods')
op.drop_table('mods')
op.drop_index('fki_sub_blocks_sub_fkey', table_name='sub_blocks')
op.drop_table('sub_blocks')
op.drop_constraint('sub_fkey', 'submissions', type_='foreignkey')
op.drop_column('submissions', 'sub')
op.drop_column('users', 'subs_created')
op.drop_index('subs_idx', table_name='subs')
op.drop_table('subs')
op.execute("DELETE FROM modactions WHERE kind = 'move_hole'")
def downgrade():
# ### commands auto generated by Alembic - please adjust! (adjusted -@TLSM) ###
op.create_table('subs',
sa.Column('name', sa.VARCHAR(length=20), autoincrement=False, nullable=False),
sa.Column('sidebar', sa.VARCHAR(length=500), autoincrement=False, nullable=True),
sa.Column('sidebar_html', sa.VARCHAR(length=1000), autoincrement=False, nullable=True),
sa.Column('sidebarurl', sa.VARCHAR(length=60), autoincrement=False, nullable=True),
sa.Column('bannerurl', sa.VARCHAR(length=60), autoincrement=False, nullable=True),
sa.Column('css', sa.VARCHAR(length=4000), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('name', name='subs_pkey'),
postgresql_ignore_search_path=False
)
op.create_index('subs_idx', 'subs', ['name'], unique=False)
op.add_column('users', sa.Column('subs_created', sa.INTEGER(), server_default=sa.text('0'), autoincrement=False, nullable=False))
op.add_column('submissions', sa.Column('sub', sa.VARCHAR(length=20), autoincrement=False, nullable=True))
op.create_foreign_key('sub_fkey', 'submissions', 'subs', ['sub'], ['name'])
op.create_table('sub_blocks',
sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('sub', sa.VARCHAR(length=20), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['sub'], ['subs.name'], name='sub_blocks_sub_fkey'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name='sub_blocks_user_fkey'),
sa.PrimaryKeyConstraint('user_id', 'sub', name='sub_blocks_pkey')
)
op.create_index('fki_sub_blocks_sub_fkey', 'sub_blocks', ['sub'], unique=False)
op.create_table('mods',
sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('sub', sa.VARCHAR(length=20), autoincrement=False, nullable=False),
sa.Column('created_utc', sa.INTEGER(), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['sub'], ['subs.name'], name='mod_sub_fkey'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name='user_mod_fkey'),
sa.PrimaryKeyConstraint('user_id', 'sub', name='mods_pkey')
)
op.create_index('fki_mod_sub_fkey', 'mods', ['sub'], unique=False)
op.create_table('exiles',
sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('sub', sa.VARCHAR(length=20), autoincrement=False, nullable=False),
sa.Column('exiler_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['exiler_id'], ['users.id'], name='exile_exiler_fkey'),
sa.ForeignKeyConstraint(['sub'], ['subs.name'], name='exile_sub_fkey'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name='exile_user_fkey'),
sa.PrimaryKeyConstraint('user_id', 'sub', name='exiles_pkey')
)
op.create_index('fki_exile_sub_fkey', 'exiles', ['sub'], unique=False)
op.create_index('fki_exile_exiler_fkey', 'exiles', ['exiler_id'], unique=False)