forked from MarseyWorld/MarseyWorld
make hole sidebar/banner uploading and deleting not require page reload
parent
f2b26e3cc3
commit
4f57533d2c
|
@ -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();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
|
@ -537,7 +537,7 @@ def get_hole_css(hole):
|
||||||
resp.headers.add("Content-Type", "text/css")
|
resp.headers.add("Content-Type", "text/css")
|
||||||
return resp
|
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)
|
||||||
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
|
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
|
||||||
@limiter.limit("50/day", deduct_when=lambda response: response.status_code < 400)
|
@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 not v.mods_hole(hole.name): abort(403)
|
||||||
if v.shadowbanned: abort(500)
|
if v.shadowbanned: abort(500)
|
||||||
|
|
||||||
file = request.files["sidebar"]
|
file = request.files["image"]
|
||||||
|
|
||||||
name = f'/images/{time.time()}'.replace('.','') + '.webp'
|
name = f'/images/{time.time()}'.replace('.','') + '.webp'
|
||||||
file.save(name)
|
file.save(name)
|
||||||
|
@ -568,26 +568,28 @@ def upload_hole_sidebar(v, hole):
|
||||||
)
|
)
|
||||||
g.db.add(ma)
|
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)
|
||||||
@limiter.limit("1/second", deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
|
@limiter.limit("1/second", deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
|
||||||
@auth_required
|
@auth_required
|
||||||
def delete_hole_sidebar(v, hole, index):
|
def delete_hole_sidebar(v, hole):
|
||||||
hole = get_hole(hole)
|
hole = get_hole(hole)
|
||||||
if not v.mods_hole(hole.name): abort(403)
|
if not v.mods_hole(hole.name): abort(403)
|
||||||
|
|
||||||
if not hole.sidebarurls:
|
if not hole.sidebarurls:
|
||||||
abort(404, f"Sidebar image not found (/h/{hole.name} has no sidebar images)")
|
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 = request.values["url"]
|
||||||
sidebar = hole.sidebarurls[index]
|
|
||||||
try:
|
if sidebar not in hole.sidebarurls:
|
||||||
remove_media_using_link(sidebar)
|
abort(404, "Sidebar image not found!")
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
try: remove_media_using_link(sidebar)
|
||||||
del hole.sidebarurls[index]
|
except FileNotFoundError: pass
|
||||||
|
|
||||||
|
hole.sidebarurls.remove(sidebar)
|
||||||
g.db.add(hole)
|
g.db.add(hole)
|
||||||
|
|
||||||
ma = HoleAction(
|
ma = HoleAction(
|
||||||
|
@ -598,7 +600,7 @@ def delete_hole_sidebar(v, hole, index):
|
||||||
)
|
)
|
||||||
g.db.add(ma)
|
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")
|
@app.post("/h/<hole>/settings/sidebars/delete_all")
|
||||||
@limiter.limit("1/10 second;30/day", deduct_when=lambda response: response.status_code < 400)
|
@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"}
|
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)
|
||||||
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
|
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
|
||||||
@limiter.limit("50/day", deduct_when=lambda response: response.status_code < 400)
|
@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 not v.mods_hole(hole.name): abort(403)
|
||||||
if v.shadowbanned: abort(500)
|
if v.shadowbanned: abort(500)
|
||||||
|
|
||||||
file = request.files["banner"]
|
file = request.files["image"]
|
||||||
|
|
||||||
name = f'/images/{time.time()}'.replace('.','') + '.webp'
|
name = f'/images/{time.time()}'.replace('.','') + '.webp'
|
||||||
file.save(name)
|
file.save(name)
|
||||||
|
@ -657,37 +659,39 @@ def upload_hole_banner(v, hole):
|
||||||
)
|
)
|
||||||
g.db.add(ma)
|
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)
|
||||||
@limiter.limit("1/second", deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
|
@limiter.limit("1/second", deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
|
||||||
@auth_required
|
@auth_required
|
||||||
def delete_hole_banner(v, hole, index):
|
def delete_hole_banner(v, hole):
|
||||||
hole = get_hole(hole)
|
hole = get_hole(hole)
|
||||||
if not v.mods_hole(hole.name): abort(403)
|
if not v.mods_hole(hole.name): abort(403)
|
||||||
|
|
||||||
if not hole.bannerurls:
|
if not hole.bannerurls:
|
||||||
abort(404, f"Banner not found (/h/{hole.name} has no banners)")
|
abort(404, f"Banner not found (/h/{hole.name} has no banner images)")
|
||||||
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 = request.values["url"]
|
||||||
banner = hole.bannerurls[index]
|
|
||||||
try:
|
if banner not in hole.bannerurls:
|
||||||
remove_media_using_link(banner)
|
abort(404, "Banner not found!")
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
try: remove_media_using_link(banner)
|
||||||
del hole.bannerurls[index]
|
except FileNotFoundError: pass
|
||||||
|
|
||||||
|
hole.bannerurls.remove(banner)
|
||||||
g.db.add(hole)
|
g.db.add(hole)
|
||||||
|
|
||||||
ma = HoleAction(
|
ma = HoleAction(
|
||||||
hole=hole.name,
|
hole=hole.name,
|
||||||
kind='delete_banner',
|
kind='delete_banner_image',
|
||||||
_note=f'<a href="{banner}">{banner}</a>',
|
_note=f'<a href="{banner}">{banner}</a>',
|
||||||
user_id=v.id
|
user_id=v.id
|
||||||
)
|
)
|
||||||
g.db.add(ma)
|
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")
|
@app.post("/h/<hole>/settings/banners/delete_all")
|
||||||
@limiter.limit("1/10 second;30/day", deduct_when=lambda response: response.status_code < 400)
|
@limiter.limit("1/10 second;30/day", deduct_when=lambda response: response.status_code < 400)
|
||||||
|
|
|
@ -99,56 +99,58 @@
|
||||||
<h4 class="mb-4 pb-2">Banners</h4>
|
<h4 class="mb-4 pb-2">Banners</h4>
|
||||||
{% if not g.is_tor %}
|
{% if not g.is_tor %}
|
||||||
<section id="hole-banner-upload-new" class="mb-5 hole-settings-subsection">
|
<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">
|
||||||
<input hidden name="formkey" value="{{v|formkey}}" class="notranslate" translate="no">
|
<label class="btn btn-secondary text-capitalize mr-2 mb-0">
|
||||||
<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}}')">
|
||||||
Upload New Banner<input autocomplete="off" type="file" accept="image/*" hidden name="banner" data-nonce="{{g.nonce}}" onchange_submit>
|
</label>
|
||||||
</label>
|
|
||||||
</form>
|
|
||||||
<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>
|
<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">
|
<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.
|
All image files are supported. Max file size is {% if v and v.patron %}16{% else %}8{% endif %} MB.
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for banner in hole.bannerurls %}
|
<div id="hole-banners">
|
||||||
<section id="hole-banner-update-{{loop.index - 1}}" class="mt-5 d-block hole-settings-subsection">
|
{% for banner in hole.bannerurls %}
|
||||||
<img class="mr-3" loading="lazy" alt="/h/{{hole.name}} banner" src="{{banner}}">
|
<section class="mt-5 d-block hole-settings-subsection">
|
||||||
<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>
|
<img class="mr-3" loading="lazy" alt="/h/{{hole.name}} banner" src="{{banner}}">
|
||||||
</section>
|
<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>
|
||||||
{% else %}
|
</section>
|
||||||
<section id="hole-banner-no-banners" class="d-block hole-settings-subsection">
|
{% else %}
|
||||||
{{macros.ghost_box("No banners uploaded", "", 2, "flex:1")}}
|
<section id="hole-banner-no-banners" class="d-block hole-settings-subsection">
|
||||||
</section>
|
{{macros.ghost_box("No banners uploaded", "", 2, "flex:1")}}
|
||||||
{% endfor %}
|
</section>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hole-settings-section hole-sidebar-update-section">
|
<div class="hole-settings-section hole-sidebar-update-section">
|
||||||
<h4 class="mb-4 pb-2">Sidebar Images</h4>
|
<h4 class="mb-4 pb-2">Sidebar Images</h4>
|
||||||
{% if not g.is_tor %}
|
{% if not g.is_tor %}
|
||||||
<section id="hole-sidebar-upload-new" class="mb-5 hole-settings-subsection">
|
<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">
|
||||||
<input hidden name="formkey" value="{{v|formkey}}" class="notranslate" translate="no">
|
<label class="btn btn-secondary text-capitalize mr-2 mb-0">
|
||||||
<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}}')">
|
||||||
Upload New Sidebar Image<input autocomplete="off" type="file" accept="image/*" hidden name="sidebar" data-nonce="{{g.nonce}}" onchange_submit>
|
</label>
|
||||||
</label>
|
|
||||||
</form>
|
|
||||||
<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>
|
<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">
|
<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.
|
All image files are supported. Max file size is {% if v and v.patron %}16{% else %}8{% endif %} MB.
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for sidebar in hole.sidebarurls %}
|
<div id="hole-sidebars">
|
||||||
<section id="hole-sidebar-update-{{loop.index - 1}}" class="mt-5 d-block hole-settings-subsection">
|
{% for sidebar in hole.sidebarurls %}
|
||||||
<img class="mr-3" loading="lazy" alt="/h/{{hole.name}} sidebar" src="{{sidebar}}">
|
<section class="mt-5 d-block hole-settings-subsection">
|
||||||
<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>
|
<img class="mr-3" loading="lazy" alt="/h/{{hole.name}} sidebar" src="{{sidebar}}">
|
||||||
</section>
|
<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>
|
||||||
{% else %}
|
</section>
|
||||||
<section id="hole-sidebar-no-sidebars" class="d-block hole-settings-subsection">
|
{% else %}
|
||||||
{{macros.ghost_box("No sidebar images uploaded", "", 2, "flex:1")}}
|
<section id="hole-sidebar-no-sidebars" class="d-block hole-settings-subsection">
|
||||||
</section>
|
{{macros.ghost_box("No sidebar images uploaded", "", 2, "flex:1")}}
|
||||||
{% endfor %}
|
</section>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script defer src="{{'js/hole_settings.js' | asset}}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in New Issue