add custom css for chats

master
Aevann 2024-08-08 01:52:53 +03:00
parent dede32f545
commit ed3a1b94d0
7 changed files with 102 additions and 7 deletions

View File

@ -2,7 +2,7 @@ import time
from flask import g from flask import g
from sqlalchemy import Column, ForeignKey from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship from sqlalchemy.orm import deferred, relationship
from sqlalchemy.sql.sqltypes import * from sqlalchemy.sql.sqltypes import *
from files.classes import Base from files.classes import Base
@ -14,6 +14,7 @@ class Chat(Base):
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
name = Column(String) name = Column(String)
created_utc = Column(Integer) created_utc = Column(Integer)
css = deferred(Column(String))
@property @property
@lazy @lazy

View File

@ -112,9 +112,9 @@ def chat(v, chat_id):
orgy = get_running_orgy(v, chat_id) orgy = get_running_orgy(v, chat_id)
if orgy: if orgy:
orgies = g.db.query(Orgy).filter_by(chat_id=chat_id).order_by(Orgy.start_utc).all() orgies = g.db.query(Orgy).filter_by(chat_id=chat_id).order_by(Orgy.start_utc).all()
return render_template("orgy.html", v=v, messages=displayed_messages, chat=chat, sorted_memberships=sorted_memberships, muting_chat=muting_chat, orgy=orgy, orgies=orgies, membership=membership) return render_template("orgy.html", v=v, messages=displayed_messages, chat=chat, sorted_memberships=sorted_memberships, muting_chat=muting_chat, orgy=orgy, orgies=orgies, membership=membership, chat_css=chat.css)
return render_template("chat.html", v=v, messages=displayed_messages, chat=chat, sorted_memberships=sorted_memberships, muting_chat=muting_chat, membership=membership) return render_template("chat.html", v=v, messages=displayed_messages, chat=chat, sorted_memberships=sorted_memberships, muting_chat=muting_chat, membership=membership, chat_css=chat.css)
@app.post("/chat/<int:chat_id>/name") @app.post("/chat/<int:chat_id>/name")
@ -188,6 +188,63 @@ def mute_chat(v, chat_id):
return {"message": msg} return {"message": msg}
@app.get("/chat/<int:chat_id>/custom_css")
@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 chat_custom_css_get(v, chat_id):
chat = g.db.get(Chat, chat_id)
if not chat:
abort(404, "Chat not found!")
if v.id != chat.owner_id:
abort(403, "Only the chat owner can change its custom css!")
return render_template("chat_css.html", v=v, chat=chat)
@app.post("/chat/<int:chat_id>/custom_css")
@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 chat_custom_css_post(v, chat_id):
chat = g.db.get(Chat, chat_id)
if not chat:
abort(404, "Chat not found!")
if v.id != chat.owner_id:
abort(403, "Only the chat owner can change its custom css!")
if v.shadowbanned: abort(400)
css = request.values.get('css', '').strip()
if len(css) > CSS_LENGTH_LIMIT:
abort(400, f"Chat CSS is too long (max {CSS_LENGTH_LIMIT} characters)")
valid, error = validate_css(css)
if not valid:
abort(400, error)
chat.css = css
g.db.add(chat)
return {"message": "Chat Custom CSS successfully updated!"}
@app.get("/chat/<int:chat_id>/css")
@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 chat_css(v, chat_id):
chat = g.db.query(Chat.css).filter_by(id=chat_id).one_or_none()
if not chat:
abort(404, "Chat not found!")
resp = make_response(chat.css or "")
resp.headers.add("Content-Type", "text/css")
return resp
@app.get("/chat/<int:chat_id>/orgies") @app.get("/chat/<int:chat_id>/orgies")
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400) @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) @limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)

View File

@ -17,8 +17,7 @@
<b class="mt-2 mb-2 ml-1 text-small toggleable d-md-none" style="display:inline-block">{{chat.name}}</b> <b class="mt-2 mb-2 ml-1 text-small toggleable d-md-none" style="display:inline-block">{{chat.name}}</b>
{% if v.id == chat.owner_id %} {% if v.id == chat.owner_id %}
<button class="px-2 toggleable" type="button" data-nonce="{{g.nonce}}" data-onclick="toggleElement('chat-name-form', 'chat-name')"> <button class="chat-control fas fa-pen text-small text-muted px-2 toggleable" type="button" data-nonce="{{g.nonce}}" data-onclick="toggleElement('chat-name-form', 'chat-name')">
<i class="fas fa-pen text-small text-muted"></i>
</button> </button>
<form id="chat-name-form" class="d-none mt-2" action="/chat/{{chat.id}}/name" method="post"> <form id="chat-name-form" class="d-none mt-2" action="/chat/{{chat.id}}/name" method="post">
@ -27,7 +26,9 @@
<button type="submit" class="btn btn-primary" style="margin-top:-5px">Save</button> <button type="submit" class="btn btn-primary" style="margin-top:-5px">Save</button>
</form> </form>
<a href="/chat/{{chat.id}}/orgies" class="orgy-control fas fa-tv text-muted ml-2"></a> <a href="/chat/{{chat.id}}/orgies" class="chat-control fas fa-tv text-muted mx-2"></a>
<a href="/chat/{{chat.id}}/custom_css" class="chat-control fas fa-palette text-muted ml-2"></a>
{% else %} {% else %}
<button class="px-2" type="button" data-nonce="{{g.nonce}}" data-bs-toggle="tooltip" title="{% if muting_chat %}Unmute (do it){% else %}Mute (don't do it){% endif %}" data-nonce="{{g.nonce}}" data-onclick="postToastReload(this, '/chat/{{chat.id}}/toggle_mute')"> <button class="px-2" type="button" data-nonce="{{g.nonce}}" data-bs-toggle="tooltip" title="{% if muting_chat %}Unmute (do it){% else %}Mute (don't do it){% endif %}" data-nonce="{{g.nonce}}" data-onclick="postToastReload(this, '/chat/{{chat.id}}/toggle_mute')">
<i class="fas fa-bell-slash {% if muting_chat %}text-danger{% else %}text-muted{% endif %}"></i> <i class="fas fa-bell-slash {% if muting_chat %}text-danger{% else %}text-muted{% endif %}"></i>

View File

@ -0,0 +1,29 @@
{% extends "default.html" %}
{% block pagetitle %}Chat CSS{% endblock %}
{% block content %}
<div class="settings my-5 mx-3">
<section>
<a href="/chat/{{chat.id}}">
<i class="fas fa-long-arrow-left mr-2"></i>Go back to chat
</a>
<h2 class="mt-4">Chat Custom CSS</h2>
<div class="settings-section rounded mb-0">
<p class="text-small text-muted pt-2 pl-3">Edit your chat's custom CSS</p>
<div class="body d-lg-flex border-bottom">
<div class="w-lg-100">
<form id="chat-custom-css" action="/chat/{{chat.id}}/custom_css" method="post" data-nonce="{{g.nonce}}" data-onsubmit="sendFormXHR(this)">
<input hidden name="formkey" value="{{v|formkey}}" class="notranslate" translate="no">
<textarea autocomplete="off" class="form-control rounded" id="css-textarea" placeholder="Custom CSS" rows="3" name="css" form="chat-custom-css" maxlength="{{CSS_LENGTH_LIMIT}}">{% if chat.css %}{{chat.css}}{% endif %}</textarea>
<small>Limit of {{CSS_LENGTH_LIMIT}} characters</small>
<div class="d-flex mt-2">
<input autocomplete="off" id="submit-btn" class="btn btn-primary ml-auto" type="submit" value="Save">
</div>
</form>
</div>
</div>
</div>
</section>
</div>
{% endblock %}

View File

@ -15,7 +15,9 @@
<b class="mt-2 mb-2 ml-1 text-small d-md-none" style="display:inline-block">{{orgy.title}}</b> <b class="mt-2 mb-2 ml-1 text-small d-md-none" style="display:inline-block">{{orgy.title}}</b>
{% if v.id == chat.owner_id %} {% if v.id == chat.owner_id %}
<a href="/chat/{{chat.id}}/orgies" class="orgy-control fas fa-tv text-muted ml-2"></a> <a href="/chat/{{chat.id}}/orgies" class="chat-control fas fa-tv text-muted mx-2"></a>
<a href="/chat/{{chat.id}}/custom_css" class="chat-control fas fa-palette text-muted ml-2"></a>
{% else %} {% else %}
<button class="px-2" type="button" data-nonce="{{g.nonce}}" data-bs-toggle="tooltip" title="{% if muting_chat %}Unmute (do it){% else %}Mute (don't do it){% endif %}" data-nonce="{{g.nonce}}" data-onclick="postToastReload(this, '/chat/{{chat.id}}/toggle_mute')"> <button class="px-2" type="button" data-nonce="{{g.nonce}}" data-bs-toggle="tooltip" title="{% if muting_chat %}Unmute (do it){% else %}Mute (don't do it){% endif %}" data-nonce="{{g.nonce}}" data-onclick="postToastReload(this, '/chat/{{chat.id}}/toggle_mute')">
<i class="fas fa-bell-slash {% if muting_chat %}text-danger{% else %}text-muted{% endif %}"></i> <i class="fas fa-bell-slash {% if muting_chat %}text-danger{% else %}text-muted{% endif %}"></i>

View File

@ -151,6 +151,10 @@
{% if v and (v.css or v.background) and not request.path.startswith('/settings') %} {% if v and (v.css or v.background) and not request.path.startswith('/settings') %}
<link rel="stylesheet" href="/@{{v.username}}/css"> <link rel="stylesheet" href="/@{{v.username}}/css">
{% endif %} {% endif %}
{% if chat_css %}
<link rel="stylesheet" href="/chat/{{chat.id}}/css">
{% endif %}
{% endmacro %} {% endmacro %}
{% macro seo() %} {% macro seo() %}

View File

@ -0,0 +1 @@
alter table chats add column css character varying(20000);