make hole sidebar/banner uploading and deleting not require page reload

pull/225/head
Aevann 2024-03-06 05:31:45 +02:00
parent f2b26e3cc3
commit 4f57533d2c
3 changed files with 126 additions and 62 deletions

View File

@ -0,0 +1,58 @@
function upload_banner(t, hole_name) {
postToast(t,
`/h/${hole_name}/settings/banners`,
{
"image": t.files[0]
},
(xhr) => {
const list = document.getElementById('hole-banners')
const url = JSON.parse(xhr.response)['url']
const html = `
<section class="mt-5 d-block hole-settings-subsection">
<img class="mr-3" loading="lazy" alt="/h/${hole_name} banner" src="${url}">
<button class="btn btn-danger hole-banner-delete-button mt-2" data-nonce="${nonce}" data-onclick="areyousure(this)" data-areyousure="delete_image(this, '/h/${hole_name}/settings/banners/delete')">Delete</button>
</section>`
list.insertAdjacentHTML('afterbegin', html);
register_new_elements(list);
const nobanners = document.getElementById('hole-banner-no-banners')
if (nobanners) nobanners.remove()
}
);
}
function upload_sidebar(t, hole_name) {
postToast(t,
`/h/${hole_name}/settings/sidebars`,
{
"image": t.files[0]
},
(xhr) => {
const list = document.getElementById('hole-sidebars')
const url = JSON.parse(xhr.response)['url']
const html = `
<section class="mt-5 d-block hole-settings-subsection">
<img class="mr-3" loading="lazy" alt="/h/${hole_name} sidebar" src="${url}">
<button class="btn btn-danger hole-sidebar-delete-button mt-2" data-nonce="${nonce}" data-onclick="areyousure(this)" data-areyousure="delete_image(this, '/h/${hole_name}/settings/sidebars/delete')">Delete</button>
</section>`
list.insertAdjacentHTML('afterbegin', html);
register_new_elements(list);
const nosidebars = document.getElementById('hole-sidebar-no-sidebars')
if (nosidebars) nosidebars.remove()
}
);
}
function delete_image(t, url) {
postToast(t, url,
{
"url": t.previousElementSibling.src
},
() => {
t.parentElement.remove();
}
);
}

View File

@ -537,7 +537,7 @@ def get_hole_css(hole):
resp.headers.add("Content-Type", "text/css")
return resp
@app.post("/h/<hole>/settings/sidebars/")
@app.post("/h/<hole>/settings/sidebars")
@limiter.limit('1/second', scope=rpath)
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
@limiter.limit("50/day", deduct_when=lambda response: response.status_code < 400)
@ -550,7 +550,7 @@ def upload_hole_sidebar(v, hole):
if not v.mods_hole(hole.name): abort(403)
if v.shadowbanned: abort(500)
file = request.files["sidebar"]
file = request.files["image"]
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
@ -568,26 +568,28 @@ def upload_hole_sidebar(v, hole):
)
g.db.add(ma)
return redirect(f'/h/{hole}/settings')
return {"message": "Sidebar image uploaded successfully!", "url": sidebarurl}
@app.post("/h/<hole>/settings/sidebars/delete/<int:index>")
@app.post("/h/<hole>/settings/sidebars/delete")
@limiter.limit("1/second", deduct_when=lambda response: response.status_code < 400)
@limiter.limit("1/second", deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def delete_hole_sidebar(v, hole, index):
def delete_hole_sidebar(v, hole):
hole = get_hole(hole)
if not v.mods_hole(hole.name): abort(403)
if not hole.sidebarurls:
abort(404, f"Sidebar image not found (/h/{hole.name} has no sidebar images)")
if index < 0 or index >= len(hole.sidebarurls):
abort(404, f'Sidebar image not found (sidebar index {index} is not between 0 and {len(hole.sidebarurls)})')
sidebar = hole.sidebarurls[index]
try:
remove_media_using_link(sidebar)
except FileNotFoundError:
pass
del hole.sidebarurls[index]
sidebar = request.values["url"]
if sidebar not in hole.sidebarurls:
abort(404, "Sidebar image not found!")
try: remove_media_using_link(sidebar)
except FileNotFoundError: pass
hole.sidebarurls.remove(sidebar)
g.db.add(hole)
ma = HoleAction(
@ -598,7 +600,7 @@ def delete_hole_sidebar(v, hole, index):
)
g.db.add(ma)
return {"message": f"Deleted sidebar {index} from /h/{hole} successfully"}
return {"message": "Sidebar image deleted successfully!"}
@app.post("/h/<hole>/settings/sidebars/delete_all")
@limiter.limit("1/10 second;30/day", deduct_when=lambda response: response.status_code < 400)
@ -626,7 +628,7 @@ def delete_all_hole_sidebars(v, hole):
return {"message": f"Deleted all sidebar images from /h/{hole} successfully"}
@app.post("/h/<hole>/settings/banners/")
@app.post("/h/<hole>/settings/banners")
@limiter.limit('1/second', scope=rpath)
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
@limiter.limit("50/day", deduct_when=lambda response: response.status_code < 400)
@ -639,7 +641,7 @@ def upload_hole_banner(v, hole):
if not v.mods_hole(hole.name): abort(403)
if v.shadowbanned: abort(500)
file = request.files["banner"]
file = request.files["image"]
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
@ -657,37 +659,39 @@ def upload_hole_banner(v, hole):
)
g.db.add(ma)
return redirect(f'/h/{hole}/settings')
return {"message": "Banner uploaded successfully!", "url": bannerurl}
@app.post("/h/<hole>/settings/banners/delete/<int:index>")
@app.post("/h/<hole>/settings/banners/delete")
@limiter.limit("1/second", deduct_when=lambda response: response.status_code < 400)
@limiter.limit("1/second", deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def delete_hole_banner(v, hole, index):
def delete_hole_banner(v, hole):
hole = get_hole(hole)
if not v.mods_hole(hole.name): abort(403)
if not hole.bannerurls:
abort(404, f"Banner not found (/h/{hole.name} has no banners)")
if index < 0 or index >= len(hole.bannerurls):
abort(404, f'Banner not found (banner index {index} is not between 0 and {len(hole.bannerurls)})')
banner = hole.bannerurls[index]
try:
remove_media_using_link(banner)
except FileNotFoundError:
pass
del hole.bannerurls[index]
abort(404, f"Banner not found (/h/{hole.name} has no banner images)")
banner = request.values["url"]
if banner not in hole.bannerurls:
abort(404, "Banner not found!")
try: remove_media_using_link(banner)
except FileNotFoundError: pass
hole.bannerurls.remove(banner)
g.db.add(hole)
ma = HoleAction(
hole=hole.name,
kind='delete_banner',
kind='delete_banner_image',
_note=f'<a href="{banner}">{banner}</a>',
user_id=v.id
)
g.db.add(ma)
return {"message": f"Deleted banner {index} from /h/{hole} successfully"}
return {"message": "Banner deleted successfully!"}
@app.post("/h/<hole>/settings/banners/delete_all")
@limiter.limit("1/10 second;30/day", deduct_when=lambda response: response.status_code < 400)

View File

@ -99,56 +99,58 @@
<h4 class="mb-4 pb-2">Banners</h4>
{% if not g.is_tor %}
<section id="hole-banner-upload-new" class="mb-5 hole-settings-subsection">
<form class="d-inline-block" action="/h/{{hole.name}}/settings/banners/" method="post" enctype="multipart/form-data">
<input hidden name="formkey" value="{{v|formkey}}" class="notranslate" translate="no">
<label class="btn btn-secondary text-capitalize mr-2 mb-0">
Upload New Banner<input autocomplete="off" type="file" accept="image/*" hidden name="banner" data-nonce="{{g.nonce}}" onchange_submit>
</label>
</form>
<input hidden name="formkey" value="{{v|formkey}}" class="notranslate" translate="no">
<label class="btn btn-secondary text-capitalize mr-2 mb-0">
Upload New Banner<input id="upload_banner" autocomplete="off" type="file" accept="image/*" hidden name="banner" data-nonce="{{g.nonce}}" data-onchange="upload_banner(this, '{{hole.name}}')">
</label>
<button type="button" class="btn btn-danger hole-banner-delete-button" id="hole-banner-delete-all" data-nonce="{{g.nonce}}" data-onclick="areyousure(this)" data-areyousure="postToastReload(this, '/h/{{hole.name}}/settings/banners/delete_all')">Delete All Banners</button>
<div class="text-small text-muted mt-3">
All image files are supported. Max file size is {% if v and v.patron %}16{% else %}8{% endif %} MB.
</div>
</section>
{% endif %}
{% for banner in hole.bannerurls %}
<section id="hole-banner-update-{{loop.index - 1}}" class="mt-5 d-block hole-settings-subsection">
<img class="mr-3" loading="lazy" alt="/h/{{hole.name}} banner" src="{{banner}}">
<button class="btn btn-danger hole-banner-delete-button mt-2" id="hole-banner-delete-{{loop.index}}" data-nonce="{{g.nonce}}" data-onclick="areyousure(this)" data-areyousure="postToastReload(this, '/h/{{hole.name}}/settings/banners/delete/{{loop.index - 1}}')">Delete</button>
</section>
{% else %}
<section id="hole-banner-no-banners" class="d-block hole-settings-subsection">
{{macros.ghost_box("No banners uploaded", "", 2, "flex:1")}}
</section>
{% endfor %}
<div id="hole-banners">
{% for banner in hole.bannerurls %}
<section class="mt-5 d-block hole-settings-subsection">
<img class="mr-3" loading="lazy" alt="/h/{{hole.name}} banner" src="{{banner}}">
<button class="btn btn-danger hole-banner-delete-button mt-2" data-nonce="{{g.nonce}}" data-onclick="areyousure(this)" data-areyousure="delete_image(this, '/h/{{hole.name}}/settings/banners/delete')">Delete</button>
</section>
{% else %}
<section id="hole-banner-no-banners" class="d-block hole-settings-subsection">
{{macros.ghost_box("No banners uploaded", "", 2, "flex:1")}}
</section>
{% endfor %}
</div>
</div>
<div class="hole-settings-section hole-sidebar-update-section">
<h4 class="mb-4 pb-2">Sidebar Images</h4>
{% if not g.is_tor %}
<section id="hole-sidebar-upload-new" class="mb-5 hole-settings-subsection">
<form class="d-inline-block" action="/h/{{hole.name}}/settings/sidebars/" method="post" enctype="multipart/form-data">
<input hidden name="formkey" value="{{v|formkey}}" class="notranslate" translate="no">
<label class="btn btn-secondary text-capitalize mr-2 mb-0">
Upload New Sidebar Image<input autocomplete="off" type="file" accept="image/*" hidden name="sidebar" data-nonce="{{g.nonce}}" onchange_submit>
</label>
</form>
<input hidden name="formkey" value="{{v|formkey}}" class="notranslate" translate="no">
<label class="btn btn-secondary text-capitalize mr-2 mb-0">
Upload New Sidebar Image<input id="upload_sidebar" autocomplete="off" type="file" accept="image/*" hidden name="sidebar" data-nonce="{{g.nonce}}" data-onchange="upload_sidebar(this, '{{hole.name}}')">
</label>
<button type="button" class="btn btn-danger hole-sidebar-delete-button" id="hole-sidebar-delete-all" data-nonce="{{g.nonce}}" data-onclick="areyousure(this)" data-areyousure="postToastReload(this, '/h/{{hole.name}}/settings/sidebars/delete_all')">Delete All Sidebar Images</button>
<div class="text-small text-muted mt-3">
All image files are supported. Max file size is {% if v and v.patron %}16{% else %}8{% endif %} MB.
</div>
</section>
{% endif %}
{% for sidebar in hole.sidebarurls %}
<section id="hole-sidebar-update-{{loop.index - 1}}" class="mt-5 d-block hole-settings-subsection">
<img class="mr-3" loading="lazy" alt="/h/{{hole.name}} sidebar" src="{{sidebar}}">
<button class="btn btn-danger hole-sidebar-delete-button mt-2" id="hole-sidebar-delete-{{loop.index}}" data-nonce="{{g.nonce}}" data-onclick="areyousure(this)" data-areyousure="postToastReload(this, '/h/{{hole.name}}/settings/sidebars/delete/{{loop.index - 1}}')">Delete</button>
</section>
{% else %}
<section id="hole-sidebar-no-sidebars" class="d-block hole-settings-subsection">
{{macros.ghost_box("No sidebar images uploaded", "", 2, "flex:1")}}
</section>
{% endfor %}
<div id="hole-sidebars">
{% for sidebar in hole.sidebarurls %}
<section class="mt-5 d-block hole-settings-subsection">
<img class="mr-3" loading="lazy" alt="/h/{{hole.name}} sidebar" src="{{sidebar}}">
<button class="btn btn-danger hole-sidebar-delete-button mt-2" data-nonce="{{g.nonce}}" data-onclick="areyousure(this)" data-areyousure="delete_image(this, '/h/{{hole.name}}/settings/sidebars/delete')">Delete</button>
</section>
{% else %}
<section id="hole-sidebar-no-sidebars" class="d-block hole-settings-subsection">
{{macros.ghost_box("No sidebar images uploaded", "", 2, "flex:1")}}
</section>
{% endfor %}
</div>
</div>
</div>
<script defer src="{{'js/hole_settings.js' | asset}}"></script>
{% endblock %}