add "change hole" button

pull/222/head
Aevann 2024-02-14 14:49:29 +02:00
parent 15fa096105
commit 7bada8f70e
16 changed files with 203 additions and 104 deletions

View File

@ -0,0 +1,36 @@
const hole_to = document.getElementById("hole_to")
const changeHoleButton = document.getElementById("changeHoleButton");
hole_to.addEventListener('keydown', (e) => {
if (!((e.ctrlKey || e.metaKey) && e.key === "Enter")) return;
const targetDOM = document.activeElement;
if (!(targetDOM instanceof HTMLInputElement)) return;
changeHoleButton.click()
bootstrap.Modal.getOrCreateInstance(document.getElementById('changeHoleModal')).hide()
});
function change_holeModal(id) {
changeHoleButton.disabled = false;
changeHoleButton.classList.remove('disabled');
changeHoleButton.innerHTML='Change Hole';
changeHoleButton.dataset.id = id
hole_to.value = ""
setTimeout(() => {
hole_to.focus()
}, 500);
};
changeHoleButton.onclick = function() {
this.disabled = true;
this.classList.add('disabled');
postToast(this, '/change_hole/' + changeHoleButton.dataset.id,
{
"hole_to": hole_to.value
},
() => {}
);
}

View File

@ -24,7 +24,6 @@ function report_postModal(id) {
};
reportPostButton.onclick = function() {
this.innerHTML='Reporting post';
this.disabled = true;
this.classList.add('disabled');

View File

@ -414,3 +414,10 @@ class Post(Base):
@lazy
def is_longpost(self):
return len(self.body) >= 2000
@lazy
def hole_changable(self, v):
if self.hole == 'chudrama':
return v.admin_level >= PERMS['POST_COMMENT_MODERATION']
else:
return v.admin_level >= PERMS['POST_COMMENT_MODERATION'] or (self.hole and v.mods_hole(hole_from)) or self.author_id == v.id

View File

@ -24,7 +24,7 @@ HOLEACTION_TYPES = {
"icon": 'fa-feather-alt',
"color": 'bg-danger'
},
'move_hole': {
'change_hole': {
"str": 'changed hole of {self.target_link}',
"icon": 'fa-manhole',
"color": 'bg-primary'

View File

@ -216,7 +216,7 @@ MODACTION_TYPES = {
"icon": 'fa-memo',
"color": 'bg-danger'
},
'move_hole': {
'change_hole': {
"str": 'changed hole of {self.target_link}',
"icon": 'fa-manhole',
"color": 'bg-primary'

View File

@ -1005,3 +1005,98 @@ def post_hole_snappy_quotes(v, hole):
g.db.add(ma)
return {"message": "Snappy quotes edited successfully!"}
@app.post("/change_hole/<int:pid>")
@limiter.limit('1/second', scope=rpath)
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def change_hole(pid, v):
post = get_post(pid)
if post.ghost:
abort(403, "You can't move ghost posts into holes!")
hole_from = post.hole
hole_to = request.values.get("hole_to", "").strip()
hole_to = get_hole(hole_to, graceful=True)
hole_to = hole_to.name if hole_to else None
if not post.hole_changable(v):
return False
if hole_to == None:
if HOLE_REQUIRED:
abort(403, "All posts are required to be in holes!")
hole_to_in_notif = 'the main feed'
else:
hole_to_in_notif = f'/h/{hole_to}'
if hole_from == hole_to:
abort(409, f"Post is already in {hole_to_in_notif}")
if post.author.exiler_username(hole_to):
abort(403, f"User is exiled from this hole!")
if hole_to == 'changelog':
abort(403, "/h/changelog is archived!")
if hole_to in {'furry','vampire','racist','femboy','edgy'} and not v.client and not post.author.house.lower().startswith(hole_to):
if v.id == post.author_id:
abort(403, f"You need to be a member of House {hole_to.capitalize()} to post in /h/{hole_to}")
else:
abort(403, f"@{post.author_name} needs to be a member of House {hole_to.capitalize()} for their post to be moved to /h/{hole_to}")
post.hole = hole_to
post.hole_pinned = None
if hole_to == 'chudrama':
post.bannedfor = None
post.chuddedfor = None
for c in post.comments:
c.bannedfor = None
c.chuddedfor = None
g.db.add(c)
g.db.add(post)
if v.id != post.author_id:
hole_from_str = 'main feed' if hole_from is None else \
f'<a href="/h/{hole_from}">/h/{hole_from}</a>'
hole_to_str = 'main feed' if hole_to is None else \
f'<a href="/h/{hole_to}">/h/{hole_to}</a>'
if v.admin_level >= PERMS['POST_COMMENT_MODERATION']:
ma = ModAction(
kind='change_hole',
user_id=v.id,
target_post_id=post.id,
_note=f'{hole_from_str}{hole_to_str}',
)
g.db.add(ma)
position = 'a site admin'
else:
ma = HoleAction(
hole=hole_from,
kind='change_hole',
user_id=v.id,
target_post_id=post.id,
_note=f'{hole_from_str}{hole_to_str}',
)
g.db.add(ma)
position = f'a /h/{hole_from} mod'
if hole_from == None:
hole_from_in_notif = 'the main feed'
else:
hole_from_in_notif = f'/h/{hole_from}'
message = f"@{v.username} ({position}) has moved [{post.title}]({post.shortlink}) from {hole_from_in_notif} to {hole_to_in_notif}"
send_repeatable_notification(post.author_id, message)
cache.delete_memoized(frontlist)
return {"message": f"Post moved to {hole_to_in_notif} successfully!"}

View File

@ -150,6 +150,15 @@ def poster_of_the_day():
user = g.db.query(User).filter_by(id=uid).one()
return user
def HOLES():
HOLES = [x[0] for x in g.db.query(Hole.name).order_by(Hole.name)]
if "other" in HOLES:
HOLES.remove("other")
HOLES.append("other")
return HOLES
@app.context_processor
def inject_constants():
return {
@ -177,7 +186,7 @@ def inject_constants():
"SITE_FULL_IMAGES": SITE_FULL_IMAGES,
"IS_EVENT":IS_EVENT, "IS_FISTMAS":IS_FISTMAS, "IS_HOMOWEEN":IS_HOMOWEEN,
"IS_DKD":IS_DKD, "IS_BIRTHGAY":IS_BIRTHGAY, "IS_BIRTHDEAD":IS_BIRTHDEAD,
"CHUD_PHRASES":CHUD_PHRASES, "hasattr":hasattr, "calc_users":calc_users, "HOLE_INACTIVITY_DELETION":HOLE_INACTIVITY_DELETION, "LIGHT_THEMES":LIGHT_THEMES, "NSFW_EMOJIS":NSFW_EMOJIS,
"CHUD_PHRASES":CHUD_PHRASES, "hasattr":hasattr, "calc_users":calc_users, "HOLE_INACTIVITY_DELETION":HOLE_INACTIVITY_DELETION, "LIGHT_THEMES":LIGHT_THEMES, "NSFW_EMOJIS":NSFW_EMOJIS, "HOLES":HOLES,
"MAX_IMAGE_AUDIO_SIZE_MB":MAX_IMAGE_AUDIO_SIZE_MB, "MAX_IMAGE_AUDIO_SIZE_MB_PATRON":MAX_IMAGE_AUDIO_SIZE_MB_PATRON,
"MAX_VIDEO_SIZE_MB":MAX_VIDEO_SIZE_MB, "MAX_VIDEO_SIZE_MB_PATRON":MAX_VIDEO_SIZE_MB_PATRON,
"CURSORMARSEY_DEFAULT":CURSORMARSEY_DEFAULT, "SNAPPY_ID":SNAPPY_ID, "get_running_orgy":get_running_orgy,

View File

@ -96,15 +96,10 @@ def publish(pid, v):
@auth_required
def submit_get(v, hole=None):
hole = get_hole(hole, graceful=True)
if request.path.startswith('/h/') and not hole: abort(404)
HOLES = [x[0] for x in g.db.query(Hole.name).order_by(Hole.name)]
if "other" in HOLES:
HOLES.remove("other")
HOLES.append("other")
return render_template("submit.html", HOLES=HOLES, v=v, hole=hole)
return render_template("submit.html", v=v, hole=hole)
@app.get("/post/<int:pid>")
@app.get("/post/<int:pid>/<anything>")

View File

@ -60,9 +60,6 @@ def report_post(pid, v):
return {"message": "Post flaired successfully!"}
moved = move_post(post, v, reason)
if moved: return {"message": moved}
if v.is_muted: abort(403, "You are forbidden from making reports!")
existing = g.db.query(Report.post_id).filter_by(user_id=v.id, post_id=post.id).one_or_none()
@ -162,91 +159,3 @@ def remove_report_comment(v, cid, uid):
g.db.add(ma)
return {"message": "Report removed successfully!"}
def move_post(post, v, reason):
if not reason.startswith('/h/') and not reason.startswith('h/'):
return False
if post.ghost:
abort(403, "You can't move ghost posts into holes!")
hole_from = post.hole
hole_to = get_hole(reason, graceful=True)
hole_to = hole_to.name if hole_to else None
can_move_post = v.admin_level >= PERMS['POST_COMMENT_MODERATION'] or (post.hole and v.mods_hole(hole_from))
if hole_from != 'chudrama': # posts can only be moved out of /h/chudrama by admins
can_move_post = can_move_post or post.author_id == v.id
if not can_move_post: return False
if hole_to == None:
if HOLE_REQUIRED:
abort(403, "All posts are required to be flaired!")
hole_to_in_notif = 'the main feed'
else:
hole_to_in_notif = f'/h/{hole_to}'
if hole_from == hole_to: abort(409, f"Post is already in {hole_to_in_notif}")
if post.author.exiler_username(hole_to):
abort(403, f"User is exiled from this hole!")
if hole_to == 'changelog':
abort(403, "/h/changelog is archived!")
if hole_to in {'furry','vampire','racist','femboy','edgy'} and not v.client and not post.author.house.lower().startswith(hole_to):
if v.id == post.author_id:
abort(403, f"You need to be a member of House {hole_to.capitalize()} to post in /h/{hole_to}")
else:
abort(403, f"@{post.author_name} needs to be a member of House {hole_to.capitalize()} for their post to be moved to /h/{hole_to}")
post.hole = hole_to
post.hole_pinned = None
if hole_to == 'chudrama':
post.bannedfor = None
post.chuddedfor = None
for c in post.comments:
c.bannedfor = None
c.chuddedfor = None
g.db.add(c)
g.db.add(post)
if v.id != post.author_id:
hole_from_str = 'main feed' if hole_from is None else \
f'<a href="/h/{hole_from}">/h/{hole_from}</a>'
hole_to_str = 'main feed' if hole_to is None else \
f'<a href="/h/{hole_to}">/h/{hole_to}</a>'
if v.admin_level >= PERMS['POST_COMMENT_MODERATION']:
ma = ModAction(
kind='move_hole',
user_id=v.id,
target_post_id=post.id,
_note=f'{hole_from_str}{hole_to_str}',
)
g.db.add(ma)
position = 'a site admin'
else:
ma = HoleAction(
hole=hole_from,
kind='move_hole',
user_id=v.id,
target_post_id=post.id,
_note=f'{hole_from_str}{hole_to_str}',
)
g.db.add(ma)
position = f'a /h/{hole_from} mod'
if hole_from == None:
hole_from_in_notif = 'the main feed'
else:
hole_from_in_notif = f'/h/{hole_from}'
message = f"@{v.username} ({position}) has moved [{post.title}]({post.shortlink}) from {hole_from_in_notif} to {hole_to_in_notif}"
send_repeatable_notification(post.author_id, message)
cache.delete_memoized(frontlist)
return f"Post moved to {hole_to_in_notif} successfully!"

View File

@ -0,0 +1,31 @@
<div class="modal fade" id="changeHoleModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Change Hole</h5>
<button type="button" class="close" data-bs-dismiss="modal">
<span><i class="fas fa-times"></i></span>
</button>
</div>
<div class="modal-body">
<label class='mt-2' for="title">Hole</label>
<div class="input-group">
{%- set hole_placeholder = 'Required' if HOLE_REQUIRED else 'Optional' -%}
<input list="holes" autocomplete="off" id="hole_to" class="form-control" placeholder="{{hole_placeholder}}" {% if HOLE_REQUIRED %}required{% endif %}>
<datalist id="holes">
{% for h in HOLES() %}
<option value="{{h}}"></option>
{% endfor %}
</datalist>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-link text-muted" data-bs-dismiss="modal">Cancel</button>
<button type="button" id="changeHoleButton" class="btn btn-primary btn-danger" data-bs-dismiss="modal">Change Hole</button>
</div>
</div>
</div>
</div>
<script defer src="{{'js/change_hole_modal.js' | asset}}"></script>

View File

@ -339,8 +339,15 @@
</div>
{% if v %}
{% if v.id == p.author_id %}{% include "modals/delete_post.html" %}{% endif %}
{% if v.id == p.author_id %}
{% include "modals/delete_post.html" %}
{% endif %}
{% include "modals/report_post.html" %}
{% if p.hole_changable(v) %}
{% include "modals/change_hole.html" %}
{% endif %}
{% if v.can_edit(p) %}
<script defer src="{{'js/edit_post.js' | asset}}"></script>
{% endif %}

View File

@ -21,6 +21,10 @@
<button type="button" id="unsave-{{p.id}}" class="{% if not p.id in v.saved_idlist %}d-none{% endif %} list-inline-item" data-nonce="{{g.nonce}}" data-onclick="postToastSwitch(this,'/unsave_post/{{p.id}}','save-{{p.id}}','unsave-{{p.id}}','d-none')"><i class="fas fa-save"></i>Unsave {% if p.num_savers %}<span data-bs-toggle="tooltip" data-bs-placement="bottom" title="Number of users who saved this post">[{{p.num_savers}}]</span>{% endif %}</button>
<button type="button" class="list-inline-item" data-bs-toggle="modal" data-bs-dismiss="modal" data-bs-target="#reportPostModal" data-nonce="{{g.nonce}}" data-onclick="report_postModal('{{p.id}}')"><i class="fas fa-flag"></i>Report</button>
{% if p.hole_changable(v) %}
<button type="button" class="list-inline-item" data-bs-toggle="modal" data-bs-dismiss="modal" data-bs-target="#changeHoleModal" data-nonce="{{g.nonce}}" data-onclick="change_holeModal('{{p.id}}')"><i class="fas fa-manhole"></i>Change Hole</button>
{% endif %}
{% endif %}
{% if v and v.id == p.author_id %}

View File

@ -15,6 +15,10 @@
<button type="button" class="nobackground btn btn-link btn-block btn-lg text-left text-muted" data-bs-toggle="modal" data-bs-dismiss="modal" data-bs-target="#reportPostModal" data-nonce="{{g.nonce}}" data-onclick="report_postModal('{{p.id}}')"><i class="fas fa-flag text-center text-muted mr-2"></i>Report</button>
{% if p.hole_changable(v) %}
<button type="button" class="nobackground btn btn-link btn-block btn-lg text-left text-muted" data-bs-toggle="modal" data-bs-dismiss="modal" data-bs-target="#changeHoleModal" data-nonce="{{g.nonce}}" data-onclick="change_holeModal('{{p.id}}')"><i class="fas fa-manhole text-center text-muted mr-2"></i>Change Hole</button>
{% endif %}
{% if FEATURES['AWARDS'] -%}
<button type="button" class="nobackground btn btn-link btn-block btn-lg text-left text-muted" data-bs-toggle="modal" data-bs-dismiss="modal" data-bs-target="#awardModal" data-url='/award/post/{{p.id}}' data-immune="{{p.author.immune_to_negative_awards(v)}}" data-ghost="{{p.ghost}}" data-nonce="{{g.nonce}}"><i class="fas fa-gift text-center text-muted mr-2"></i>Give Award</button>
{%- endif %}

View File

@ -250,6 +250,7 @@
{% if v %}
{% include "modals/delete_post.html" %}
{% include "modals/report_post.html" %}
{% include "modals/change_hole.html" %}
{% if v.admin_level >= PERMS['USER_BAN'] %}
{% include "modals/punish.html" %}
{% endif %}

View File

@ -21,7 +21,7 @@
{%- set hole_placeholder = 'Required' if HOLE_REQUIRED else 'Optional' -%}
<input list="holes" autocomplete="off" id="hole" class="form-control" form="submitform" name="hole" data-nonce="{{g.nonce}}" data-oninput="savetext()" {% if hole %}value="{{hole}}"{% endif %} placeholder="{{hole_placeholder}}" {% if HOLE_REQUIRED %}required{% endif %}>
<datalist id="holes">
{% for h in HOLES %}
{% for h in HOLES() %}
<option value="{{h}}"></option>
{% endfor %}
</datalist>

View File

@ -0,0 +1,2 @@
update modactions set kind='change_hole' where kind='move_hole';
update hole_actions set kind='change_hole' where kind='move_hole';