Compare commits
No commits in common. "77b81726763679a50b213657f74ef51a43520a04" and "320b898b296a5606500ff4a4f701dddee4d81f6d" have entirely different histories.
77b8172676
...
320b898b29
@ -70,12 +70,10 @@ def create_app():
|
|||||||
|
|
||||||
from app.routes.app import bp as app_bp
|
from app.routes.app import bp as app_bp
|
||||||
from app.routes.topics import bp as topics_bp
|
from app.routes.topics import bp as topics_bp
|
||||||
from app.routes.threads import bp as threads_bp
|
|
||||||
from app.routes.users import bp as users_bp
|
from app.routes.users import bp as users_bp
|
||||||
from app.routes.mod import bp as mod_bp
|
from app.routes.mod import bp as mod_bp
|
||||||
app.register_blueprint(app_bp)
|
app.register_blueprint(app_bp)
|
||||||
app.register_blueprint(topics_bp)
|
app.register_blueprint(topics_bp)
|
||||||
app.register_blueprint(threads_bp)
|
|
||||||
app.register_blueprint(users_bp)
|
app.register_blueprint(users_bp)
|
||||||
app.register_blueprint(mod_bp)
|
app.register_blueprint(mod_bp)
|
||||||
|
|
||||||
|
83
app/db.py
83
app/db.py
@ -5,11 +5,10 @@ from flask import current_app
|
|||||||
class DB:
|
class DB:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._connection = None
|
self._connection = None
|
||||||
self._transaction_depth = 0
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def connection(self, in_transaction = False):
|
def _get_connection(self):
|
||||||
if self._connection:
|
if self._connection:
|
||||||
yield self._connection
|
yield self._connection
|
||||||
return
|
return
|
||||||
@ -19,36 +18,29 @@ class DB:
|
|||||||
conn.execute("PRAGMA FOREIGN_KEYS = 1")
|
conn.execute("PRAGMA FOREIGN_KEYS = 1")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if in_transaction:
|
|
||||||
self._connection = conn
|
|
||||||
self._transaction_depth += 1
|
|
||||||
conn.execute("BEGIN")
|
|
||||||
|
|
||||||
yield conn
|
yield conn
|
||||||
|
|
||||||
if in_transaction:
|
|
||||||
conn.commit()
|
|
||||||
except Exception:
|
|
||||||
if in_transaction and self._connection:
|
|
||||||
conn.rollback()
|
|
||||||
finally:
|
finally:
|
||||||
if in_transaction:
|
|
||||||
self._transaction_depth -= 1
|
|
||||||
if self._transaction_depth == 0:
|
|
||||||
self._connection = None
|
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def transaction(self):
|
def transaction(self):
|
||||||
"""Transaction context."""
|
"""Transaction context."""
|
||||||
with self.connection(in_transaction=True) as conn:
|
tr_connection = sqlite3.connect(current_app.config["DB_PATH"])
|
||||||
yield conn
|
tr_connection.row_factory = sqlite3.Row
|
||||||
|
tr_connection.execute("PRAGMA FOREIGN_KEYS = 1")
|
||||||
|
tr_connection.execute("BEGIN")
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
tr_connection.execute("COMMIT")
|
||||||
|
except Exception:
|
||||||
|
tr_connection.execute("ROLLBACK")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def query(self, sql, *args):
|
def query(self, sql, *args):
|
||||||
"""Executes a query and returns a list of dictionaries."""
|
"""Executes a query and returns a list of dictionaries."""
|
||||||
with self.connection() as conn:
|
with self._get_connection() as conn:
|
||||||
rows = conn.execute(sql, args).fetchall()
|
rows = conn.execute(sql, args).fetchall()
|
||||||
return [dict(row) for row in rows]
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
@ -64,7 +56,7 @@ class DB:
|
|||||||
RETURNING *
|
RETURNING *
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with self.connection() as conn:
|
with self._get_connection() as conn:
|
||||||
result = conn.execute(sql, values).fetchone()
|
result = conn.execute(sql, values).fetchone()
|
||||||
conn.commit()
|
conn.commit()
|
||||||
return dict(result) if result else None
|
return dict(result) if result else None
|
||||||
@ -72,14 +64,14 @@ class DB:
|
|||||||
|
|
||||||
def execute(self, sql, *args):
|
def execute(self, sql, *args):
|
||||||
"""Executes a query without returning."""
|
"""Executes a query without returning."""
|
||||||
with self.connection() as conn:
|
with self._get_connection() as conn:
|
||||||
conn.execute(sql, args)
|
conn.execute(sql, args)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
def fetch_one(self, sql, *args):
|
def fetch_one(self, sql, *args):
|
||||||
"""Grabs the first row of a query."""
|
"""Grabs the first row of a query."""
|
||||||
with self.connection() as conn:
|
with self._get_connection() as conn:
|
||||||
row = conn.execute(sql, args).fetchone()
|
row = conn.execute(sql, args).fetchone()
|
||||||
return dict(row) if row else None
|
return dict(row) if row else None
|
||||||
|
|
||||||
@ -87,21 +79,9 @@ class DB:
|
|||||||
class QueryBuilder:
|
class QueryBuilder:
|
||||||
def __init__(self, table):
|
def __init__(self, table):
|
||||||
self.table = table
|
self.table = table
|
||||||
self._where = [] # list of tuples
|
self._where = {}
|
||||||
self._select = "*"
|
self._select = "*"
|
||||||
|
self._params = []
|
||||||
|
|
||||||
def _build_where(self):
|
|
||||||
if not self._where:
|
|
||||||
return "", []
|
|
||||||
|
|
||||||
conditions = []
|
|
||||||
params = []
|
|
||||||
for col, op, val in self._where:
|
|
||||||
conditions.append(f"{col} {op} ?")
|
|
||||||
params.append(val)
|
|
||||||
|
|
||||||
return " WHERE " + " AND ".join(conditions), params
|
|
||||||
|
|
||||||
|
|
||||||
def select(self, columns = "*"):
|
def select(self, columns = "*"):
|
||||||
@ -109,34 +89,35 @@ class DB:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
def where(self, condition, operator = "="):
|
def where(self, condition):
|
||||||
if isinstance(condition, dict):
|
self._where.update(condition)
|
||||||
for key, value in condition.items():
|
|
||||||
self._where.append((key, "=", value))
|
|
||||||
elif isinstance(condition, list):
|
|
||||||
for c in condition:
|
|
||||||
self._where.append(c)
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
def build_select(self):
|
def build_select(self):
|
||||||
sql = f"SELECT {self._select} FROM {self.table}"
|
sql = f"SELECT {self._select} FROM {self.table}"
|
||||||
where_clause, params = self._build_where()
|
if self._where:
|
||||||
return sql + where_clause, params
|
conditions = " AND ".join(f"{k} = ?" for k in self._where.keys())
|
||||||
|
sql += f" WHERE {conditions}"
|
||||||
|
return sql, list(self._where.values())
|
||||||
|
|
||||||
|
|
||||||
def build_update(self, data):
|
def build_update(self, data):
|
||||||
columns = ", ".join(f"{k} = ?" for k in data.keys())
|
columns = ", ".join(f"{k} = ?" for k in data.keys())
|
||||||
sql = f"UPDATE {self.table} SET {columns}"
|
sql = f"UPDATE {self.table} SET {columns}"
|
||||||
where_clause, where_params = self._build_where()
|
if self._where:
|
||||||
params = list(data.values()) + list(where_params)
|
conditions = " AND ".join(f"{k} = ?" for k in self._where.keys())
|
||||||
return sql + where_clause, params
|
sql += f" WHERE {conditions}"
|
||||||
|
params = list(data.values()) + list(self._where.values())
|
||||||
|
return sql, params
|
||||||
|
|
||||||
|
|
||||||
def build_delete(self):
|
def build_delete(self):
|
||||||
sql = f"DELETE FROM {self.table}"
|
sql = f"DELETE FROM {self.table}"
|
||||||
where_clause, params = self._build_where()
|
if self._where:
|
||||||
return sql + where_clause, params
|
conditions = " AND ".join(f"{k} = ?" for k in self._where.keys())
|
||||||
|
sql += f" WHERE {conditions}"
|
||||||
|
return sql, list(self._where.values())
|
||||||
|
|
||||||
|
|
||||||
def first(self):
|
def first(self):
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
from .babycode_parser import Parser
|
|
||||||
from markupsafe import escape
|
|
||||||
import re
|
|
||||||
|
|
||||||
def tag_code(children, attr):
|
|
||||||
is_inline = children.find('\n') == -1
|
|
||||||
if is_inline:
|
|
||||||
return f"<code class=\"inline_code\">{children}</code>"
|
|
||||||
else:
|
|
||||||
t = children.strip()
|
|
||||||
button = f"<button type=button class=\"copy-code\" value={t}>Copy</button>"
|
|
||||||
return f"<pre><span class=\"copy-code-container\">{button}</span><code>{t}</code></pre>"
|
|
||||||
|
|
||||||
def tag_list(children):
|
|
||||||
list_body = re.sub(r" +\n", "<br>", children.strip())
|
|
||||||
list_body = re.sub(r"\n\n+", "\1", list_body)
|
|
||||||
return " ".join([f"<li>{x}</li>" for x in list_body.split("\1") if x])
|
|
||||||
|
|
||||||
TAGS = {
|
|
||||||
"b": lambda children, attr: f"<strong>{children}</strong>",
|
|
||||||
"i": lambda children, attr: f"<em>{children}</em>",
|
|
||||||
"s": lambda children, attr: f"<del>{children}</del>",
|
|
||||||
"img": lambda children, attr: f"<div class=\"post-img-container\"><img class=\"block-img\" src={attr} alt={children}></div>",
|
|
||||||
"url": lambda children, attr: f"<a href={attr}>{children}</a>",
|
|
||||||
"quote": lambda children, attr: f"<blockquote>{children}</blockquote>",
|
|
||||||
"code": tag_code,
|
|
||||||
"ul": lambda children, attr: f"<ul>{tag_list(children)}</ul>",
|
|
||||||
"ol": lambda children, attr: f"<ol>{tag_list(children)}</ol>",
|
|
||||||
}
|
|
||||||
|
|
||||||
TEXT_ONLY = ["code"]
|
|
||||||
|
|
||||||
def break_lines(text):
|
|
||||||
text = re.sub(r" +\n", "<br>", text)
|
|
||||||
text = re.sub(r"\n\n+", "<br><br>", text)
|
|
||||||
return text
|
|
||||||
|
|
||||||
def babycode_to_html(s):
|
|
||||||
subj = escape(s.strip().replace('\r\n', '\n').replace('\r', '\n'))
|
|
||||||
parser = Parser(subj)
|
|
||||||
parser.valid_bbcode_tags = TAGS.keys()
|
|
||||||
parser.bbcode_tags_only_text_children = TEXT_ONLY
|
|
||||||
|
|
||||||
elements = parser.parse()
|
|
||||||
out = ""
|
|
||||||
def fold(element, nobr):
|
|
||||||
if isinstance(element, str):
|
|
||||||
if nobr:
|
|
||||||
return element
|
|
||||||
return break_lines(element)
|
|
||||||
|
|
||||||
match element['type']:
|
|
||||||
case "bbcode":
|
|
||||||
c = ""
|
|
||||||
for child in element['children']:
|
|
||||||
_nobr = element['name'] == "code" or element['name'] == "ul" or element['name'] == "ol"
|
|
||||||
c = c + fold(child, _nobr)
|
|
||||||
res = TAGS[element['name']](c, element['attr'])
|
|
||||||
return res
|
|
||||||
case "link":
|
|
||||||
return f"<a href=\"{element['url']}\">{element['url']}</a>"
|
|
||||||
case "rule":
|
|
||||||
return "<hr>"
|
|
||||||
for e in elements:
|
|
||||||
out = out + fold(e, False)
|
|
||||||
return out
|
|
@ -161,7 +161,7 @@ class Parser:
|
|||||||
if len(children) == 0:
|
if len(children) == 0:
|
||||||
children.append(ch)
|
children.append(ch)
|
||||||
else:
|
else:
|
||||||
children[0] = children[0] + ch
|
children[1] = children[1] + ch
|
||||||
else:
|
else:
|
||||||
element = self.parse_element(children)
|
element = self.parse_element(children)
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ class Users(Model):
|
|||||||
|
|
||||||
def get_post_stats(self):
|
def get_post_stats(self):
|
||||||
q = """SELECT
|
q = """SELECT
|
||||||
COUNT(DISTINCT posts.id) AS post_count,
|
COUNT(posts.id) AS post_count,
|
||||||
COUNT(DISTINCT threads.id) AS thread_count,
|
COUNT(DISTINCT threads.id) AS thread_count,
|
||||||
MAX(threads.title) FILTER (WHERE threads.created_at = latest.created_at) AS latest_thread_title,
|
MAX(threads.title) FILTER (WHERE threads.created_at = latest.created_at) AS latest_thread_title,
|
||||||
MAX(threads.slug) FILTER (WHERE threads.created_at = latest.created_at) AS latest_thread_slug
|
MAX(threads.slug) FILTER (WHERE threads.created_at = latest.created_at) AS latest_thread_slug
|
||||||
|
@ -5,8 +5,3 @@ bp = Blueprint("app", __name__, url_prefix = "/")
|
|||||||
@bp.route("/")
|
@bp.route("/")
|
||||||
def index():
|
def index():
|
||||||
return redirect(url_for("topics.all_topics"))
|
return redirect(url_for("topics.all_topics"))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/babycode")
|
|
||||||
def babycode_guide():
|
|
||||||
return "not yet"
|
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
from flask import Blueprint, redirect, url_for
|
|
||||||
from ..lib.babycode import babycode_to_html
|
|
||||||
from ..db import db
|
|
||||||
from ..models import Posts, PostHistory
|
|
||||||
|
|
||||||
bp = Blueprint("posts", __name__, url_prefix = "/posts")
|
|
||||||
|
|
||||||
def create_post(thread_id, user_id, content, markup_language="babycode"):
|
|
||||||
with db.transaction():
|
|
||||||
post = Posts.create({
|
|
||||||
"thread_id": thread_id,
|
|
||||||
"user_id": user_id,
|
|
||||||
"current_revision_id": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
revision = PostHistory.create({
|
|
||||||
"post_id": post.id,
|
|
||||||
"content": babycode_to_html(content),
|
|
||||||
"is_initial_revision": True,
|
|
||||||
"original_markup": content,
|
|
||||||
"markup_language": markup_language,
|
|
||||||
})
|
|
||||||
|
|
||||||
post.update({"current_revision_id": revision.id})
|
|
||||||
return post
|
|
||||||
|
|
@ -1,49 +1,7 @@
|
|||||||
from flask import (
|
from flask import Blueprint, render_template
|
||||||
Blueprint, render_template, request, redirect, url_for
|
|
||||||
)
|
|
||||||
from .users import login_required, get_active_user
|
|
||||||
from ..models import Threads, Topics
|
|
||||||
from .posts import create_post
|
|
||||||
from slugify import slugify
|
|
||||||
import time
|
|
||||||
|
|
||||||
bp = Blueprint("threads", __name__, url_prefix = "/threads/")
|
|
||||||
|
|
||||||
|
bp = Blueprint("topics", __name__, url_prefix = "/threads/")
|
||||||
|
|
||||||
@bp.get("/<slug>")
|
@bp.get("/<slug>")
|
||||||
def thread(slug):
|
def thread(slug):
|
||||||
return slug
|
return slug
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/create")
|
|
||||||
@login_required
|
|
||||||
def create():
|
|
||||||
all_topics = Topics.select()
|
|
||||||
return render_template("threads/create.html", all_topics = all_topics)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/create")
|
|
||||||
@login_required
|
|
||||||
def create_form():
|
|
||||||
topic = Topics.find({"id": request.form['topic_id']})
|
|
||||||
user = get_active_user()
|
|
||||||
if not topic:
|
|
||||||
return "no"
|
|
||||||
|
|
||||||
if topic.is_locked and not get_active_user().is_mod():
|
|
||||||
return "no"
|
|
||||||
|
|
||||||
title = request.form['title'].strip()
|
|
||||||
now = int(time.time())
|
|
||||||
slug = f"{slugify(title)}-{now}"
|
|
||||||
|
|
||||||
post_content = request.form['initial_post']
|
|
||||||
thread = Threads.create({
|
|
||||||
"topic_id": topic.id,
|
|
||||||
"user_id": user.id,
|
|
||||||
"title": title,
|
|
||||||
"slug": slug,
|
|
||||||
"created_at": now,
|
|
||||||
})
|
|
||||||
post = create_post(thread.id, user.id, post_content)
|
|
||||||
return redirect(url_for(".thread", slug = thread.slug))
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{% macro pager(current_page, page_count) %}
|
{% macro pager(current_page, page_count) %}
|
||||||
{% set left_start = (1, current_page - 5) | max %}
|
{% set left_start = (1, current_page - 5) | max %}
|
||||||
{% set right_end = (page_count, current_page + 5) | min %}
|
{% set right_end = (page_count, current_page + 5) | min %}
|
||||||
|
|
||||||
<div class="pager">
|
<div class="pager">
|
||||||
<span>Page:</span>
|
<span>Page:</span>
|
||||||
{% if current_page > 5 %}
|
{% if current_page > 5 %}
|
||||||
@ -41,50 +42,3 @@
|
|||||||
{% macro timestamp(unix_ts) %}
|
{% macro timestamp(unix_ts) %}
|
||||||
<span class="timestamp" data-utc="{{ unix_ts }}">{{ unix_ts | ts_datetime('%Y-%m-%d %H:%M')}} ST</span>
|
<span class="timestamp" data-utc="{{ unix_ts }}">{{ unix_ts | ts_datetime('%Y-%m-%d %H:%M')}} ST</span>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro babycode_editor_component(ta_name, ta_placeholder="Post body", optional=False, prefill="") %}
|
|
||||||
<div class="babycode-editor-container">
|
|
||||||
<div class="tab-buttons">
|
|
||||||
<button type=button class="tab-button active" data-target-id="tab-edit">Write</button>
|
|
||||||
<button type=button class="tab-button" data-target-id="tab-preview">Preview</button>
|
|
||||||
</div>
|
|
||||||
<div class="tab-content active" id="tab-edit">
|
|
||||||
<span class="babycode-button-container">
|
|
||||||
<button class="babycode-button" type=button id="post-editor-bold" title="Insert Bold"><strong>B</strong></button>
|
|
||||||
<button class="babycode-button" type=button id="post-editor-italics" title="Insert Italics"><em>I</em></button>
|
|
||||||
<button class="babycode-button" type=button id="post-editor-strike" title="Insert Strikethrough"><del>S</del></button>
|
|
||||||
<button class="babycode-button" type=button id="post-editor-url" title="Insert Link"><code>://</code></button>
|
|
||||||
<button class="babycode-button" type=button id="post-editor-code" title="Insert Code block"><code></></code></button>
|
|
||||||
<button class="babycode-button contain-svg full" type=button id="post-editor-img" title="Insert Image"><img src="/static/misc/image.svg"></button>
|
|
||||||
<button class="babycode-button" type=button id="post-editor-ol" title="Insert Ordered list">1.</button>
|
|
||||||
<button class="babycode-button" type=button id="post-editor-ul" title="Insert Unordered list">•</button>
|
|
||||||
</span>
|
|
||||||
<textarea class="babycode-editor" name="{{ ta_name }}" id="babycode-content" placeholder="{{ ta_placeholder }}" {{ "required" if not optional else "" }}>{{ prefill }}</textarea>
|
|
||||||
<a href="{{ url_for("app.babycode_guide") }}" target="_blank">babycode guide</a>
|
|
||||||
</div>
|
|
||||||
<div class="tab-content" id="tab-preview">
|
|
||||||
<div id="babycode-preview-errors-container">Type something!</div>
|
|
||||||
<div id="babycode-preview-container"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="/static/js/babycode-editor.js?v=1"></script>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro babycode_editor_form(ta_name, prefill = "", cancel_url="", endpoint="") %}
|
|
||||||
{% set save_button_text = "Post reply" if not cancel_url else "Save" %}
|
|
||||||
<form class="post-edit-form" method="post" action={{ endpoint }}>
|
|
||||||
{{babycode_editor_component(ta_name, prefill = prefill)}}
|
|
||||||
{% if not cancel_url %}
|
|
||||||
<span>
|
|
||||||
<input type="checkbox" id="subscribe" name="subscribe" {{ "checked" if session['subscribe_by_default'] else "" }}>
|
|
||||||
<label for="subscribe">Subscribe to thread</label>
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
<span>
|
|
||||||
<input type="submit" value="{{ save_button_text }}">
|
|
||||||
{% if cancel_url %}
|
|
||||||
<a class="linkbutton warn" href="{{ cancel_url }}">Cancel</a>
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
</form>
|
|
||||||
{% endmacro %}
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
{% from "common/macros.html" import babycode_editor_component %}
|
|
||||||
{% extends "base.html" %}
|
|
||||||
{% block title %}drafting a thread{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<div class="darkbg settings-container">
|
|
||||||
<h1>New thread</h1>
|
|
||||||
<form method="post">
|
|
||||||
<label for="topic_id">Topic</label>
|
|
||||||
<select name="topic_id" id="topic_id" autocomplete="off">
|
|
||||||
{% for topic in all_topics %}
|
|
||||||
<option value="{{ topic['id'] }}" {{"selected" if (request.args.get('topic_id')) == (topic['id'] | string) else ""}}>{{ topic['name'] }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select><br>
|
|
||||||
<label for="title">Thread title</label>
|
|
||||||
<input type="text" id="title" name="title" placeholder="Required" required>
|
|
||||||
<label for="initial_post">Post body</label><br>
|
|
||||||
{{ babycode_editor_component("initial_post") }}
|
|
||||||
<input type="submit" value="Create thread">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -6,11 +6,7 @@
|
|||||||
<h1 class="thread-title">All threads in "{{topic['name']}}"</h1>
|
<h1 class="thread-title">All threads in "{{topic['name']}}"</h1>
|
||||||
<span>{{topic['description']}}</span>
|
<span>{{topic['description']}}</span>
|
||||||
<div>
|
<div>
|
||||||
{% if active_user %}
|
{% if active_user and active_user.is_mod() %}
|
||||||
{% if not (topic['is_locked']) | int or active_user.is_mod() %}
|
|
||||||
<a class="linkbutton" href="{{ url_for("threads.create", topic_id=topic['id']) }}">New thread</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if active_user.is_mod() %}
|
|
||||||
<a class="linkbutton" href="{{url_for("topics.edit", slug=topic['slug'])}}">Edit topic</a>
|
<a class="linkbutton" href="{{url_for("topics.edit", slug=topic['slug'])}}">Edit topic</a>
|
||||||
<form class="modform" method="post" action="{{url_for("topics.edit", slug=topic['slug']) }}">
|
<form class="modform" method="post" action="{{url_for("topics.edit", slug=topic['slug']) }}">
|
||||||
<input type="hidden" name="is_locked" value="{{ (not topic.is_locked) | int }}">
|
<input type="hidden" name="is_locked" value="{{ (not topic.is_locked) | int }}">
|
||||||
@ -18,7 +14,6 @@
|
|||||||
</form>
|
</form>
|
||||||
<button type="button" class="critical" id="topic-delete-dialog-open">Delete</button>
|
<button type="button" class="critical" id="topic-delete-dialog-open">Delete</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
@ -467,10 +467,6 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus
|
|||||||
height: 50%;
|
height: 50%;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
.contain-svg.full > svg, .contain-svg img {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-img {
|
.block-img {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
@ -476,10 +476,6 @@ input[type="text"], input[type="password"], textarea, select {
|
|||||||
height: 50%;
|
height: 50%;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
&.full > svg, img {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-img {
|
.block-img {
|
||||||
|
Loading…
Reference in New Issue
Block a user