Compare commits
19 Commits
2d3eef6531
...
bbe57d6e94
| Author | SHA1 | Date | |
|---|---|---|---|
|
bbe57d6e94
|
|||
|
0bed6b58ae
|
|||
|
8164e63b09
|
|||
|
8b5b38e38b
|
|||
|
d0dfd3a4c3
|
|||
|
fca214dfcf
|
|||
|
04fd3f5d20
|
|||
|
1a3c015612
|
|||
|
fc9ae63471
|
|||
|
4d88b5c24c
|
|||
|
fefdbdb493
|
|||
|
d0c82cf9a9
|
|||
|
90fe38497d
|
|||
|
97e2c041c9
|
|||
|
bbbe152ff8
|
|||
|
a3ad36e9a9
|
|||
|
48fcadf61e
|
|||
|
62e1724f6c
|
|||
|
19383a538d
|
@@ -27,7 +27,6 @@ Designers: Paul James Miller
|
|||||||
|
|
||||||
Affected files: [`app/templates/common/icons.html`](./app/templates/common/icons.html)
|
Affected files: [`app/templates/common/icons.html`](./app/templates/common/icons.html)
|
||||||
URL: https://www.figma.com/community/file/1136337054881623512/iconcino-v2-0-0-free-icons-cc0-1-0-license
|
URL: https://www.figma.com/community/file/1136337054881623512/iconcino-v2-0-0-free-icons-cc0-1-0-license
|
||||||
Copyright: Gabriele Malaspina
|
|
||||||
Designers: Gabriele Malaspina
|
Designers: Gabriele Malaspina
|
||||||
License: CC0 1.0
|
License: CC0 1.0
|
||||||
|
|
||||||
@@ -74,8 +73,8 @@ Repo: https://github.com/emcconville/wand
|
|||||||
|
|
||||||
## Bitty
|
## Bitty
|
||||||
|
|
||||||
Affected files: [`data/static/js/vnd/bitty-5.1.0-rc6.min.js`](./data/static/js/vnd/bitty-5.1.0-rc6.min.js)
|
Affected files: [`data/static/js/vnd/bitty-6.0.0-rc3.min.js`](./data/static/js/vnd/bitty-6.0.0-rc3.min.js)
|
||||||
URL: https://bitty.alanwsmith.com/
|
URL: https://bitty.alanwsmith.com/
|
||||||
Copyright: `Copyright (c) 2025 Alan Smith - https://www.alanwsmith.com/`
|
License: CC0 1.0
|
||||||
License: MIT
|
Author: alan w smith https://www.alanwsmith.com/
|
||||||
Repo: https://github.com/alanwsmith/bitty
|
Repo: https://github.com/alanwsmith/bitty
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from flask import Flask, session, request
|
from flask import Flask, session, request
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from .models import Avatars, Users, PostHistory, Posts
|
from .models import Avatars, Users, PostHistory, Posts, MOTD
|
||||||
from .auth import digest
|
from .auth import digest
|
||||||
from .routes.users import is_logged_in, get_active_user, get_prefers_theme
|
from .routes.users import is_logged_in, get_active_user, get_prefers_theme
|
||||||
from .routes.threads import get_post_url
|
from .routes.threads import get_post_url
|
||||||
@@ -51,6 +51,7 @@ def create_deleted_user():
|
|||||||
def reparse_babycode():
|
def reparse_babycode():
|
||||||
print('Re-parsing babycode, this may take a while...')
|
print('Re-parsing babycode, this may take a while...')
|
||||||
from .db import db
|
from .db import db
|
||||||
|
from .constants import MOTD_BANNED_TAGS
|
||||||
post_histories = PostHistory.findall([
|
post_histories = PostHistory.findall([
|
||||||
('markup_language', '=', 'babycode'),
|
('markup_language', '=', 'babycode'),
|
||||||
('format_version', 'IS NOT', BABYCODE_VERSION)
|
('format_version', 'IS NOT', BABYCODE_VERSION)
|
||||||
@@ -80,6 +81,20 @@ def reparse_babycode():
|
|||||||
})
|
})
|
||||||
print(f'Re-parsed {len(users_with_sigs)} user sigs.')
|
print(f'Re-parsed {len(users_with_sigs)} user sigs.')
|
||||||
|
|
||||||
|
stale_motds = MOTD.findall([
|
||||||
|
['markup_language', '=', 'babycode'],
|
||||||
|
['format_version', 'IS NOT', BABYCODE_VERSION]
|
||||||
|
])
|
||||||
|
if stale_motds:
|
||||||
|
print('Re-parsing MOTDs...')
|
||||||
|
with db.transaction():
|
||||||
|
for motd in stale_motds:
|
||||||
|
motd.update({
|
||||||
|
'body_rendered': babycode_to_html(motd['body_original_markup'], banned_tags=MOTD_BANNED_TAGS),
|
||||||
|
'format_version': BABYCODE_VERSION,
|
||||||
|
})
|
||||||
|
print('Re-parsing MOTDs done.')
|
||||||
|
|
||||||
print('Re-parsing done.')
|
print('Re-parsing done.')
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
@@ -171,6 +186,7 @@ def create_app():
|
|||||||
return {
|
return {
|
||||||
'get_post_url': get_post_url,
|
'get_post_url': get_post_url,
|
||||||
'get_prefers_theme': get_prefers_theme,
|
'get_prefers_theme': get_prefers_theme,
|
||||||
|
'get_motds': MOTD.get_all,
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.template_filter("ts_datetime")
|
@app.template_filter("ts_datetime")
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ REACTION_EMOJI = [
|
|||||||
'scissors',
|
'scissors',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
MOTD_BANNED_TAGS = [
|
||||||
|
'img', 'spoiler',
|
||||||
|
]
|
||||||
|
|
||||||
def permission_level_string(perm):
|
def permission_level_string(perm):
|
||||||
return PermissionLevelString[PermissionLevel(int(perm))]
|
return PermissionLevelString[PermissionLevel(int(perm))]
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from pygments.lexers import get_lexer_by_name
|
|||||||
from pygments.util import ClassNotFound as PygmentsClassNotFound
|
from pygments.util import ClassNotFound as PygmentsClassNotFound
|
||||||
import re
|
import re
|
||||||
|
|
||||||
BABYCODE_VERSION = 4
|
BABYCODE_VERSION = 5
|
||||||
|
|
||||||
NAMED_COLORS = [
|
NAMED_COLORS = [
|
||||||
'black', 'silver', 'gray', 'white', 'maroon', 'red',
|
'black', 'silver', 'gray', 'white', 'maroon', 'red',
|
||||||
@@ -61,7 +61,7 @@ def tag_code(children, attr, surrounding):
|
|||||||
return f"<code class=\"inline-code\">{children}</code>"
|
return f"<code class=\"inline-code\">{children}</code>"
|
||||||
else:
|
else:
|
||||||
input_code = children.strip()
|
input_code = children.strip()
|
||||||
button = f"<button type=button class=\"copy-code\" value=\"{input_code}\">Copy</button>"
|
button = f"<button type=button class=\"copy-code\" value=\"{input_code}\" data-send=\"copyCode\" data-receive=\"copyCode\">Copy</button>"
|
||||||
unhighlighted = f"<pre><span class=\"copy-code-container\"><span class=\"code-language-identifier\">code block</span>{button}</span><code>{input_code}</code></pre>"
|
unhighlighted = f"<pre><span class=\"copy-code-container\"><span class=\"code-language-identifier\">code block</span>{button}</span><code>{input_code}</code></pre>"
|
||||||
if not attr:
|
if not attr:
|
||||||
return unhighlighted
|
return unhighlighted
|
||||||
@@ -94,7 +94,7 @@ def tag_color(children, attr, surrounding):
|
|||||||
def tag_spoiler(children, attr, surrounding):
|
def tag_spoiler(children, attr, surrounding):
|
||||||
spoiler_name = attr if attr else "Spoiler"
|
spoiler_name = attr if attr else "Spoiler"
|
||||||
content = f"<div class='accordion-content post-accordion-content hidden'>{children}</div>"
|
content = f"<div class='accordion-content post-accordion-content hidden'>{children}</div>"
|
||||||
container = f"""<div class='accordion hidden'><div class='accordion-header'><button type='button' class='accordion-toggle'>+</button><span>{spoiler_name}</span></div>{content}</div>"""
|
container = f"""<div class='accordion hidden' data-receive='toggleAccordion'><div class='accordion-header'><button type='button' class='accordion-toggle' data-send='toggleAccordion'>+</button><span>{spoiler_name}</span></div>{content}</div>"""
|
||||||
return container
|
return container
|
||||||
|
|
||||||
def tag_image(children, attr, surrounding):
|
def tag_image(children, attr, surrounding):
|
||||||
@@ -218,10 +218,14 @@ def should_collapse(text, surrounding):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def babycode_to_html(s):
|
def babycode_to_html(s, banned_tags=None):
|
||||||
|
allowed_tags = list(TAGS.keys())
|
||||||
|
if banned_tags is not None:
|
||||||
|
for tag in banned_tags:
|
||||||
|
allowed_tags.remove(tag)
|
||||||
subj = escape(s.strip().replace('\r\n', '\n').replace('\r', '\n'))
|
subj = escape(s.strip().replace('\r\n', '\n').replace('\r', '\n'))
|
||||||
parser = Parser(subj)
|
parser = Parser(subj)
|
||||||
parser.valid_bbcode_tags = TAGS.keys()
|
parser.valid_bbcode_tags = allowed_tags
|
||||||
parser.bbcode_tags_only_text_children = TEXT_ONLY
|
parser.bbcode_tags_only_text_children = TEXT_ONLY
|
||||||
parser.valid_emotes = EMOJI.keys()
|
parser.valid_emotes = EMOJI.keys()
|
||||||
|
|
||||||
|
|||||||
@@ -394,3 +394,19 @@ class BookmarkedThreads(Model):
|
|||||||
|
|
||||||
def get_thread(self):
|
def get_thread(self):
|
||||||
return Threads.find({'id': self.thread_id})
|
return Threads.find({'id': self.thread_id})
|
||||||
|
|
||||||
|
|
||||||
|
class MOTD(Model):
|
||||||
|
table = 'motd'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def has_motd(cls):
|
||||||
|
q = 'SELECT EXISTS(SELECT 1 FROM motd) as e'
|
||||||
|
res = db.fetch_one(q)['e']
|
||||||
|
return int(res) == 1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_all(cls):
|
||||||
|
q = 'SELECT id FROM motd'
|
||||||
|
res = db.query(q)
|
||||||
|
return [MOTD.find({'id': i['id']}) for i in res]
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ def babycode_preview():
|
|||||||
markup = request.json.get('markup')
|
markup = request.json.get('markup')
|
||||||
if not markup or not isinstance(markup, str):
|
if not markup or not isinstance(markup, str):
|
||||||
return {'error': 'markup field missing or invalid type'}, 400
|
return {'error': 'markup field missing or invalid type'}, 400
|
||||||
rendered = babycode_to_html(markup)
|
banned_tags = request.json.get('banned_tags', [])
|
||||||
|
rendered = babycode_to_html(markup, banned_tags)
|
||||||
return {'html': rendered}
|
return {'html': rendered}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,33 @@
|
|||||||
from flask import (
|
from flask import (
|
||||||
Blueprint, render_template, request, redirect, url_for
|
Blueprint, render_template, request, redirect, url_for,
|
||||||
|
flash
|
||||||
)
|
)
|
||||||
from .users import login_required, mod_only, get_active_user, admin_only
|
from .users import get_active_user, is_logged_in
|
||||||
from ..models import Users, PasswordResetLinks
|
from ..models import Users, PasswordResetLinks, MOTD
|
||||||
from ..db import db, DB
|
from ..constants import InfoboxKind, MOTD_BANNED_TAGS
|
||||||
|
from ..lib.babycode import babycode_to_html, BABYCODE_VERSION
|
||||||
|
from ..db import db
|
||||||
import secrets
|
import secrets
|
||||||
import time
|
import time
|
||||||
|
|
||||||
bp = Blueprint("mod", __name__, url_prefix = "/mod/")
|
bp = Blueprint("mod", __name__, url_prefix = "/mod/")
|
||||||
|
|
||||||
|
@bp.before_request
|
||||||
|
def _before_request():
|
||||||
|
if not is_logged_in():
|
||||||
|
return redirect(url_for("users.log_in"))
|
||||||
|
|
||||||
|
if not get_active_user().is_mod():
|
||||||
|
return redirect(url_for("topics.all_topics"))
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/sort-topics")
|
@bp.get("/sort-topics")
|
||||||
@login_required
|
|
||||||
@mod_only("topics.all_topics")
|
|
||||||
def sort_topics():
|
def sort_topics():
|
||||||
topics = db.query("SELECT * FROM topics ORDER BY sort_order ASC")
|
topics = db.query("SELECT * FROM topics ORDER BY sort_order ASC")
|
||||||
return render_template("mod/sort-topics.html", topics = topics)
|
return render_template("mod/sort-topics.html", topics = topics)
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/sort-topics")
|
@bp.post("/sort-topics")
|
||||||
@login_required
|
|
||||||
@mod_only("topics.all_topics")
|
|
||||||
def sort_topics_post():
|
def sort_topics_post():
|
||||||
with db.transaction():
|
with db.transaction():
|
||||||
for topic_id, new_order in request.form.items():
|
for topic_id, new_order in request.form.items():
|
||||||
@@ -29,16 +37,12 @@ def sort_topics_post():
|
|||||||
|
|
||||||
|
|
||||||
@bp.get("/user-list")
|
@bp.get("/user-list")
|
||||||
@login_required
|
|
||||||
@mod_only("users.page", username = lambda: get_active_user().username)
|
|
||||||
def user_list():
|
def user_list():
|
||||||
users = Users.select()
|
users = Users.select()
|
||||||
return render_template("mod/user-list.html", users = users)
|
return render_template("mod/user-list.html", users = users)
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/reset-pass/<user_id>")
|
@bp.post("/reset-pass/<user_id>")
|
||||||
@login_required
|
|
||||||
@mod_only("topics.all_topics")
|
|
||||||
def create_reset_pass(user_id):
|
def create_reset_pass(user_id):
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
key = secrets.token_urlsafe(20)
|
key = secrets.token_urlsafe(20)
|
||||||
@@ -49,3 +53,52 @@ def create_reset_pass(user_id):
|
|||||||
})
|
})
|
||||||
|
|
||||||
return redirect(url_for('users.reset_link_login', key=key))
|
return redirect(url_for('users.reset_link_login', key=key))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.get('/panel')
|
||||||
|
def panel():
|
||||||
|
return render_template('mod/panel.html')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.get('/motd')
|
||||||
|
def motd_editor():
|
||||||
|
current = MOTD.get_all()[0] if MOTD.has_motd() else None
|
||||||
|
return render_template('mod/motd.html', current=current)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.post('/motd')
|
||||||
|
def motd_editor_form():
|
||||||
|
orig_body = request.form.get('body', default='')
|
||||||
|
title = request.form.get('title', default='')
|
||||||
|
data = {
|
||||||
|
'title': title,
|
||||||
|
'body_original_markup': orig_body,
|
||||||
|
'body_rendered': babycode_to_html(orig_body, banned_tags=MOTD_BANNED_TAGS),
|
||||||
|
'format_version': BABYCODE_VERSION,
|
||||||
|
'edited_at': int(time.time()),
|
||||||
|
}
|
||||||
|
|
||||||
|
if MOTD.has_motd():
|
||||||
|
motd = MOTD.get_all()[0]
|
||||||
|
motd.update(data)
|
||||||
|
message = 'MOTD updated.'
|
||||||
|
else:
|
||||||
|
data['created_at'] = int(time.time())
|
||||||
|
data['user_id'] = get_active_user().id
|
||||||
|
motd = MOTD.create(data)
|
||||||
|
message = 'MOTD created.'
|
||||||
|
|
||||||
|
flash(message, InfoboxKind.INFO)
|
||||||
|
return redirect(url_for('.motd_editor'))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.post('/motd/delete')
|
||||||
|
def motd_delete():
|
||||||
|
if not MOTD.has_motd():
|
||||||
|
flash('No MOTD to delete.', InfoboxKind.WARN)
|
||||||
|
return redirect(url_for('.motd_editor'))
|
||||||
|
|
||||||
|
current = MOTD.get_all()[0]
|
||||||
|
current.delete()
|
||||||
|
flash('MOTD deleted.', InfoboxKind.INFO)
|
||||||
|
return redirect(url_for('.motd_editor'))
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ bp = Blueprint("topics", __name__, url_prefix = "/topics/")
|
|||||||
|
|
||||||
@bp.get("/")
|
@bp.get("/")
|
||||||
def all_topics():
|
def all_topics():
|
||||||
admin = Users.find({"id": 1})
|
|
||||||
return render_template("topics/topics.html", topic_list = Topics.get_list(), active_threads = Topics.get_active_threads())
|
return render_template("topics/topics.html", topic_list = Topics.get_list(), active_threads = Topics.get_active_threads())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,18 @@ SCHEMA = [
|
|||||||
UNIQUE(collection_id, thread_id)
|
UNIQUE(collection_id, thread_id)
|
||||||
)""",
|
)""",
|
||||||
|
|
||||||
|
"""CREATE TABLE IF NOT EXISTS "motd" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"body_original_markup" TEXT NOT NULL,
|
||||||
|
"body_rendered" TEXT NOT NULL,
|
||||||
|
"markup_language" TEXT NOT NULL DEFAULT 'babycode',
|
||||||
|
"format_version" INTEGER DEFAULT NULL,
|
||||||
|
"created_at" INTEGER DEFAULT (unixepoch(CURRENT_TIMESTAMP)),
|
||||||
|
"edited_at" INTEGER DEFAULT (unixepoch(CURRENT_TIMESTAMP)),
|
||||||
|
"user_id" REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
)""",
|
||||||
|
|
||||||
# INDEXES
|
# INDEXES
|
||||||
"CREATE INDEX IF NOT EXISTS idx_post_history_post_id ON post_history(post_id)",
|
"CREATE INDEX IF NOT EXISTS idx_post_history_post_id ON post_history(post_id)",
|
||||||
"CREATE INDEX IF NOT EXISTS idx_posts_thread ON posts(thread_id, created_at, id)",
|
"CREATE INDEX IF NOT EXISTS idx_posts_thread ON posts(thread_id, created_at, id)",
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<link rel="stylesheet" href="{{ ("/static/css/%s.css" % get_prefers_theme()) | cachebust }}">
|
<link rel="stylesheet" href="{{ ("/static/css/%s.css" % get_prefers_theme()) | cachebust }}">
|
||||||
<link rel="icon" type="image/png" href="/static/favicon.png">
|
<link rel="icon" type="image/png" href="/static/favicon.png">
|
||||||
<script src="/static/js/vnd/bitty-5.1.0-rc6.min.js" type="module"></script>
|
<script src="/static/js/vnd/bitty-6.0.0-rc3.min.js" type="module"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<bitty-5-1 data-connect="/static/js/bitties/pyrom-bitty.js">
|
<bitty-6-0 data-connect="/static/js/bitties/pyrom-bitty.js">
|
||||||
{% include 'common/topnav.html' %}
|
{% include 'common/topnav.html' %}
|
||||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<footer class="darkbg">
|
<footer class="darkbg">
|
||||||
<span>Pyrom commit <a href="{{ "https://git.poto.cafe/yagich/pyrom/commit/" + __commit }}">{{ __commit[:8] }}</a></span>
|
<span>Pyrom commit <a href="{{ "https://git.poto.cafe/yagich/pyrom/commit/" + __commit }}">{{ __commit[:8] }}</a></span>
|
||||||
</footer>
|
</footer>
|
||||||
</bitty-5-1>
|
</bitty-6-0>
|
||||||
<script src="{{ "/static/js/ui.js" | cachebust }}"></script>
|
<script src="{{ "/static/js/ui.js" | cachebust }}"></script>
|
||||||
<script src="{{ "/static/js/date-fmt.js" | cachebust }}"></script>
|
<script src="{{ "/static/js/date-fmt.js" | cachebust }}"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,49 +1,55 @@
|
|||||||
{# https://www.figma.com/community/file/1136337054881623512/iconcino-v2-0-0-free-icons-cc0-1-0-license #}
|
{# https://www.figma.com/community/file/1136337054881623512/iconcino-v2-0-0-free-icons-cc0-1-0-license #}
|
||||||
|
|
||||||
{% macro icn_bookmark(width=24) -%}
|
{% macro icn_bookmark(width=24) -%}
|
||||||
<svg width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="icon" width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 6C6 4.89543 6.89543 4 8 4H16C17.1046 4 18 4.89543 18 6V18.7268C18 19.5969 16.9657 20.0519 16.3243 19.4639L12 15.5L7.67573 19.4639C7.03432 20.0519 6 19.5969 6 18.7268V6Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 6C6 4.89543 6.89543 4 8 4H16C17.1046 4 18 4.89543 18 6V18.7268C18 19.5969 16.9657 20.0519 16.3243 19.4639L12 15.5L7.67573 19.4639C7.03432 20.0519 6 19.5969 6 18.7268V6Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro icn_error(width=60) -%}
|
{% macro icn_error(width=60) -%}
|
||||||
<svg width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="icon" width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M18.364 5.63604C19.9926 7.26472 21 9.51472 21 12C21 16.9706 16.9706 21 12 21C9.51472 21 7.26472 19.9926 5.63604 18.364M18.364 5.63604C16.7353 4.00736 14.4853 3 12 3C7.02944 3 3 7.02944 3 12C3 14.4853 4.00736 16.7353 5.63604 18.364M18.364 5.63604L5.63604 18.364" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M18.364 5.63604C19.9926 7.26472 21 9.51472 21 12C21 16.9706 16.9706 21 12 21C9.51472 21 7.26472 19.9926 5.63604 18.364M18.364 5.63604C16.7353 4.00736 14.4853 3 12 3C7.02944 3 3 7.02944 3 12C3 14.4853 4.00736 16.7353 5.63604 18.364M18.364 5.63604L5.63604 18.364" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro icn_info(width=60) -%}
|
{% macro icn_info(width=60) -%}
|
||||||
<svg width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="icon" width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M12 8V8.5M12 12V16M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M12 8V8.5M12 12V16M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro icn_lock(width=60) -%}
|
{% macro icn_lock(width=60) -%}
|
||||||
<svg width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="icon" width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M12 14V16M8 9V6C8 3.79086 9.79086 2 12 2C14.2091 2 16 3.79086 16 6V9M7 21H17C18.1046 21 19 20.1046 19 19V11C19 9.89543 18.1046 9 17 9H7C5.89543 9 5 9.89543 5 11V19C5 20.1046 5.89543 21 7 21Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M12 14V16M8 9V6C8 3.79086 9.79086 2 12 2C14.2091 2 16 3.79086 16 6V9M7 21H17C18.1046 21 19 20.1046 19 19V11C19 9.89543 18.1046 9 17 9H7C5.89543 9 5 9.89543 5 11V19C5 20.1046 5.89543 21 7 21Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro icn_warn(width=60) -%}
|
{% macro icn_warn(width=60) -%}
|
||||||
<svg width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="icon" width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M12 15H12.01M12 12V9M4.98207 19H19.0179C20.5615 19 21.5233 17.3256 20.7455 15.9923L13.7276 3.96153C12.9558 2.63852 11.0442 2.63852 10.2724 3.96153L3.25452 15.9923C2.47675 17.3256 3.43849 19 4.98207 19Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M12 15H12.01M12 12V9M4.98207 19H19.0179C20.5615 19 21.5233 17.3256 20.7455 15.9923L13.7276 3.96153C12.9558 2.63852 11.0442 2.63852 10.2724 3.96153L3.25452 15.9923C2.47675 17.3256 3.43849 19 4.98207 19Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro icn_image(width=24) -%}
|
{% macro icn_image(width=24) -%}
|
||||||
<svg width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="icon" width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M4 17L7.58959 13.7694C8.38025 13.0578 9.58958 13.0896 10.3417 13.8417L11.5 15L15.0858 11.4142C15.8668 10.6332 17.1332 10.6332 17.9142 11.4142L20 13.5M11 9C11 9.55228 10.5523 10 10 10C9.44772 10 9 9.55228 9 9C9 8.44772 9.44772 8 10 8C10.5523 8 11 8.44772 11 9ZM6 20H18C19.1046 20 20 19.1046 20 18V6C20 4.89543 19.1046 4 18 4H6C4.89543 4 4 4.89543 4 6V18C4 19.1046 4.89543 20 6 20Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M4 17L7.58959 13.7694C8.38025 13.0578 9.58958 13.0896 10.3417 13.8417L11.5 15L15.0858 11.4142C15.8668 10.6332 17.1332 10.6332 17.9142 11.4142L20 13.5M11 9C11 9.55228 10.5523 10 10 10C9.44772 10 9 9.55228 9 9C9 8.44772 9.44772 8 10 8C10.5523 8 11 8.44772 11 9ZM6 20H18C19.1046 20 20 19.1046 20 18V6C20 4.89543 19.1046 4 18 4H6C4.89543 4 4 4.89543 4 6V18C4 19.1046 4.89543 20 6 20Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro icn_spoiler(width=24) -%}
|
{% macro icn_spoiler(width=24) -%}
|
||||||
<svg width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="icon" width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M4 4L9.87868 9.87868M20 20L14.1213 14.1213M9.87868 9.87868C9.33579 10.4216 9 11.1716 9 12C9 13.6569 10.3431 15 12 15C12.8284 15 13.5784 14.6642 14.1213 14.1213M9.87868 9.87868L14.1213 14.1213M6.76821 6.76821C4.72843 8.09899 2.96378 10.026 2 11.9998C3.74646 15.5764 8.12201 19 11.9998 19C13.7376 19 15.5753 18.3124 17.2317 17.2317M9.76138 5.34717C10.5114 5.12316 11.2649 5 12.0005 5C15.8782 5 20.2531 8.42398 22 12.0002C21.448 13.1302 20.6336 14.2449 19.6554 15.2412" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M4 4L9.87868 9.87868M20 20L14.1213 14.1213M9.87868 9.87868C9.33579 10.4216 9 11.1716 9 12C9 13.6569 10.3431 15 12 15C12.8284 15 13.5784 14.6642 14.1213 14.1213M9.87868 9.87868L14.1213 14.1213M6.76821 6.76821C4.72843 8.09899 2.96378 10.026 2 11.9998C3.74646 15.5764 8.12201 19 11.9998 19C13.7376 19 15.5753 18.3124 17.2317 17.2317M9.76138 5.34717C10.5114 5.12316 11.2649 5 12.0005 5C15.8782 5 20.2531 8.42398 22 12.0002C21.448 13.1302 20.6336 14.2449 19.6554 15.2412" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro icn_sticky(width=24) -%}
|
{% macro icn_sticky(width=24) -%}
|
||||||
<svg width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="icon" width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M13 20H6C4.89543 20 4 19.1046 4 18V6C4 4.89543 4.89543 4 6 4H18C19.1046 4 20 4.89543 20 6V13M13 20L20 13M13 20V14C13 13.4477 13.4477 13 14 13H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M13 20H6C4.89543 20 4 19.1046 4 18V6C4 4.89543 4.89543 4 6 4H18C19.1046 4 20 4.89543 20 6V13M13 20L20 13M13 20V14C13 13.4477 13.4477 13 14 13H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro icn_megaphone(width=60) -%}
|
||||||
|
<svg width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 18V14M6 14H8L13 17V7L8 10H5C3.89543 10 3 10.8954 3 12V12C3 13.1046 3.89543 14 5 14H6ZM17 7L19 5M17 17L19 19M19 12H21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
{%- endmacro %}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% from 'common/icons.html' import icn_image, icn_spoiler, icn_info, icn_lock, icn_warn, icn_error, icn_bookmark %}
|
{% from 'common/icons.html' import icn_image, icn_spoiler, icn_info, icn_lock, icn_warn, icn_error, icn_bookmark, icn_megaphone %}
|
||||||
{% 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 %}
|
||||||
@@ -39,49 +39,79 @@
|
|||||||
{% macro infobox(message, kind=InfoboxKind.INFO) %}
|
{% macro infobox(message, kind=InfoboxKind.INFO) %}
|
||||||
<div class="{{ "infobox " + InfoboxHTMLClass[kind] }}">
|
<div class="{{ "infobox " + InfoboxHTMLClass[kind] }}">
|
||||||
<span>
|
<span>
|
||||||
<div class="infobox-icon-container">
|
<div class="infobox-icon-container">
|
||||||
{%- if kind == InfoboxKind.INFO -%}
|
{%- if kind == InfoboxKind.INFO -%}
|
||||||
{{- icn_info() -}}
|
{{- icn_info() -}}
|
||||||
{%- elif kind == InfoboxKind.LOCK -%}
|
{%- elif kind == InfoboxKind.LOCK -%}
|
||||||
{{- icn_lock() -}}
|
{{- icn_lock() -}}
|
||||||
{%- elif kind == InfoboxKind.WARN -%}
|
{%- elif kind == InfoboxKind.WARN -%}
|
||||||
{{- icn_warn() -}}
|
{{- icn_warn() -}}
|
||||||
{%- elif kind == InfoboxKind.ERROR -%}
|
{%- elif kind == InfoboxKind.ERROR -%}
|
||||||
{{- icn_error() -}}
|
{{- icn_error() -}}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</div>
|
</div>
|
||||||
{{ message }}
|
<span>
|
||||||
|
{% set m = message.split(';', maxsplit=1) %}
|
||||||
|
<strong>{{ m[0] }}</strong>
|
||||||
|
{%- if m[1] %}
|
||||||
|
{{ m[1] -}}
|
||||||
|
{%- endif -%}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro motd(motd_obj) %}
|
||||||
|
<div class="motd">
|
||||||
|
<div class="motd-icon-container contain-svg">
|
||||||
|
{{ icn_megaphone(80) }}
|
||||||
|
<i><abbr title="Message of the Day">MOTD</abbr></i>
|
||||||
|
</div>
|
||||||
|
<div class="motd-content-container">
|
||||||
|
<div class="motd-title">{{ motd_obj.title }}</div>
|
||||||
|
<div class="motd-body">{{ motd_obj.body_rendered | safe}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro timestamp(unix_ts) -%}
|
{% macro timestamp(unix_ts) -%}
|
||||||
<span class="timestamp" data-utc="{{ unix_ts }}">{{ unix_ts | ts_datetime('%Y-%m-%d %H:%M')}} <abbr title="Server Time">ST</abbr></span>
|
<span class="timestamp" data-utc="{{ unix_ts }}">{{ unix_ts | ts_datetime('%Y-%m-%d %H:%M')}} <abbr title="Server Time">ST</abbr></span>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro babycode_editor_component(ta_name, ta_placeholder="Post body", optional=False, prefill="") %}
|
{% macro babycode_editor_component(ta_name, ta_placeholder="Post body", optional=False, prefill="", banned_tags=[]) %}
|
||||||
<div class="babycode-editor-container">
|
<div class="babycode-editor-container tab-bar" data-receive="toggleTab">
|
||||||
|
<input type="hidden" id="babycode-banned-tags" value="{{banned_tags | unique | list | tojson | forceescape}}">
|
||||||
<div class="tab-buttons">
|
<div class="tab-buttons">
|
||||||
<button type=button class="tab-button active" data-target-id="tab-edit">Write</button>
|
<button data-send="toggleTab" 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>
|
<button data-send="babycodePreview toggleTab" type=button class="tab-button" data-target-id="tab-preview">Preview</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content active" id="tab-edit">
|
<div class="tab-content active" id="tab-edit">
|
||||||
<span class="babycode-button-container">
|
<span class="babycode-button-container">
|
||||||
<button class="babycode-button" type=button id="post-editor-bold" title="Insert Bold"><strong>B</strong></button>
|
<button data-send="insertBabycodeTag" data-tag="b" class="babycode-button" type=button id="post-editor-bold" title="Insert Bold" {{"disabled" if "b" in banned_tags else ""}}><strong>B</strong></button>
|
||||||
<button class="babycode-button" type=button id="post-editor-italics" title="Insert Italics"><em>I</em></button>
|
<button data-send="insertBabycodeTag" data-tag="i" class="babycode-button" type=button id="post-editor-italics" title="Insert Italics" {{"disabled" if "i" in banned_tags else ""}}><em>I</em></button>
|
||||||
<button class="babycode-button" type=button id="post-editor-strike" title="Insert Strikethrough"><del>S</del></button>
|
<button data-send="insertBabycodeTag" data-tag="s" class="babycode-button" type=button id="post-editor-strike" title="Insert Strikethrough" {{"disabled" if "s" in banned_tags else ""}}><del>S</del></button>
|
||||||
<button class="babycode-button" type=button id="post-editor-underline" title="Insert Underline"><u>U</u></button>
|
<button data-send="insertBabycodeTag" data-tag="u" class="babycode-button" type=button id="post-editor-underline" title="Insert Underline" {{"disabled" if "u" in banned_tags else ""}}><u>U</u></button>
|
||||||
<button class="babycode-button" type=button id="post-editor-url" title="Insert Link"><code>://</code></button>
|
<button data-send="insertBabycodeTag" data-tag="url=" data-prefill="link label" class="babycode-button" type=button id="post-editor-url" title="Insert Link" {{"disabled" if "url" in banned_tags else ""}}><code>://</code></button>
|
||||||
<button class="babycode-button" type=button id="post-editor-code" title="Insert Code block"><code></></code></button>
|
<button data-send="insertBabycodeTag" data-tag="code=" data-break-line="1" class="babycode-button" type=button id="post-editor-code" title="Insert Code block" {{"disabled" if "code" in banned_tags else ""}}><code></></code></button>
|
||||||
<button class="babycode-button contain-svg" type=button id="post-editor-img" title="Insert Image">{{ icn_image() }}</button>
|
<button data-send="insertBabycodeTag" data-tag="img=" data-prefill="alt text" class="babycode-button contain-svg" type=button id="post-editor-img" title="Insert Image" {{"disabled" if "img" in banned_tags else ""}}>{{ icn_image() }}</button>
|
||||||
<button class="babycode-button" type=button id="post-editor-ol" title="Insert Ordered list">1.</button>
|
<button data-send="insertBabycodeTag" data-tag="ol" data-break-line="1" class="babycode-button" type=button id="post-editor-ol" title="Insert Ordered list" {{"disabled" if "ol" in banned_tags else ""}}>1.</button>
|
||||||
<button class="babycode-button" type=button id="post-editor-ul" title="Insert Unordered list">•</button>
|
<button data-send="insertBabycodeTag" data-tag="ul" data-break-line="1" class="babycode-button" type=button id="post-editor-ul" title="Insert Unordered list" {{"disabled" if "u;" in banned_tags else ""}}>•</button>
|
||||||
<button class="babycode-button contain-svg" type=button id="post-editor-spoiler" title="Insert spoiler">{{ icn_spoiler() }}</button>
|
<button data-send="insertBabycodeTag" data-tag="spoiler=" data-break-line="1" data-prefill="hidden content" class="babycode-button contain-svg" type=button id="post-editor-spoiler" title="Insert spoiler" {{"disabled" if "spoiler" in banned_tags else ""}}>{{ icn_spoiler() }}</button>
|
||||||
</span>
|
</span>
|
||||||
<textarea class="babycode-editor" name="{{ ta_name }}" id="babycode-content" placeholder="{{ ta_placeholder }}" {{ "required" if not optional else "" }}>{{ prefill }}</textarea>
|
<textarea class="babycode-editor" name="{{ ta_name }}" id="babycode-content" placeholder="{{ ta_placeholder }}" {{ "required" if not optional else "" }} autocomplete="off" data-receive="insertBabycodeTag addQuote">{{ prefill }}</textarea>
|
||||||
<a href="{{ url_for("app.babycode_guide") }}" target="_blank">babycode guide</a>
|
<a href="{{ url_for("app.babycode_guide") }}" target="_blank">babycode guide</a>
|
||||||
|
{% if banned_tags %}
|
||||||
|
<div>Forbidden tags:</div>
|
||||||
|
<div>
|
||||||
|
<ul class="horizontal">
|
||||||
|
{% for tag in banned_tags | unique %}
|
||||||
|
<li><code class="inline-code">[{{ tag }}]</code></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content" id="tab-preview">
|
<div class="tab-content" id="tab-preview" data-receive="babycodePreview">
|
||||||
<div id="babycode-preview-errors-container">Type something!</div>
|
<div id="babycode-preview-errors-container">Type something!</div>
|
||||||
<div id="babycode-preview-container"></div>
|
<div id="babycode-preview-container"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -177,7 +207,7 @@
|
|||||||
{% if show_reply %}
|
{% if show_reply %}
|
||||||
{% set qtext = "[url=%s]%s said:[/url]" | format(post_permalink, post['username']) %}
|
{% set qtext = "[url=%s]%s said:[/url]" | format(post_permalink, post['username']) %}
|
||||||
{% set reply_text = "%s\n[quote]\n%s\n[/quote]\n" | format(qtext, post['original_markup']) %}
|
{% set reply_text = "%s\n[quote]\n%s\n[/quote]\n" | format(qtext, post['original_markup']) %}
|
||||||
<button value="{{ reply_text }}" class="reply-button">Quote</button>
|
<button data-send="addQuote" value="{{ reply_text }}" class="reply-button">Quote</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% set show_delete = false %}
|
{% set show_delete = false %}
|
||||||
@@ -242,13 +272,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro accordion(hidden=false, style="", disabled=false) %}
|
{% macro accordion(hidden=false, disabled=false) %}
|
||||||
{% if disabled %}
|
{% if disabled %}
|
||||||
{% set hidden = true %}
|
{% set hidden = true %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="accordion {{ "hidden" if hidden else ""}}" style="{{style}}">
|
<div class="accordion {{ "hidden" if hidden else ""}}" data-receive="toggleAccordion">
|
||||||
<div class="accordion-header">
|
<div class="accordion-header">
|
||||||
<button type="button" class="accordion-toggle" {{"disabled" if disabled else ""}}>{{ "+" if hidden else "-" }}</button>
|
<button type="button" class="accordion-toggle" {{"disabled" if disabled else ""}} data-send="toggleAccordion">{{ "+" if hidden else "-" }}</button>
|
||||||
{{ caller('header') }}
|
{{ caller('header') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="accordion-content {{ "hidden" if hidden else "" }}">
|
<div class="accordion-content {{ "hidden" if hidden else "" }}">
|
||||||
|
|||||||
@@ -12,22 +12,19 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{% with user = get_active_user() %}
|
{% with user = get_active_user() %}
|
||||||
Welcome, <a href="{{ url_for("users.page", username = user.username) }}">{{user.username}}</a>
|
Welcome, <a href="{{ url_for("users.page", username = user.username) }}">{{user.username}}</a>
|
||||||
•
|
<ul class="horizontal">
|
||||||
<a href="{{ url_for("users.settings", username = user.username) }}">Settings</a>
|
<li><a href="{{ url_for("users.settings", username = user.username) }}">Settings</a></li>
|
||||||
•
|
<li><a href="{{ url_for("users.inbox", username = user.username) }}">Inbox</a></li>
|
||||||
<a href="{{ url_for("users.inbox", username = user.username) }}">Inbox</a>
|
{% if config.DISABLE_SIGNUP and user.can_invite() %}
|
||||||
{% if config.DISABLE_SIGNUP and user.can_invite() %}
|
<li><a href="{{ url_for('users.invite_links', username=user.username )}}">Invite to {{ config.SITE_NAME }}</a></li>
|
||||||
•
|
{% endif %}
|
||||||
<a href="{{ url_for('users.invite_links', username=user.username )}}">Invite to {{ config.SITE_NAME }}</a>
|
{% if not user.is_guest() %}
|
||||||
{% endif %}
|
<li><a href="{{ url_for('users.bookmarks', username=user.username) }}">Bookmarks</a></li>
|
||||||
{% if not user.is_guest() %}
|
{% endif %}
|
||||||
•
|
{% if user.is_mod() %}
|
||||||
<a href="{{ url_for('users.bookmarks', username=user.username) }}">Bookmarks</a>
|
<li><a href="{{ url_for("mod.panel") }}">Moderation</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.is_mod() %}
|
</ul>
|
||||||
•
|
|
||||||
<a href="{{ url_for("mod.user_list") }}">User list</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{% set bookmark_url = url_for('api.bookmark_thread', thread_id=id) %}
|
{% set bookmark_url = url_for('api.bookmark_thread', thread_id=id) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="bookmarks-dropdown" data-bookmark-type="{{type}}" data-receive="saveBookmarks" data-bookmark-endpoint="{{bookmark_url}}" data-originally-contained-in="{{ selected.id if selected else ""}}" data-require-reload={{require_reload | int}}>
|
<div class="bookmarks-dropdown" data-bookmark-type="{{type}}" data-receive="saveBookmarks" data-bookmark-endpoint="{{bookmark_url}}" data-originally-contained-in="{{ selected.id if selected else ""}}" data-require-reload={{require_reload | int}} popover=auto>
|
||||||
<div class="bookmarks-dropdown-header">
|
<div class="bookmarks-dropdown-header">
|
||||||
<span>Bookmark collections</span>
|
<span>Bookmark collections</span>
|
||||||
{% if not require_reload %}
|
{% if not require_reload %}
|
||||||
@@ -12,9 +12,14 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="bookmark-dropdown-items-container">
|
<div class="bookmark-dropdown-items-container">
|
||||||
{% for collection in collections %}
|
{%- for collection in collections -%}
|
||||||
<div class="bookmark-dropdown-item {{ "selected" if selected and (selected.id | int) == (collection.id | int) else ""}}" data-send="selectBookmarkCollection" data-receive="selectBookmarkCollection" data-collection-id="{{collection.id}}">{{collection.name}} ({{ collection.get_posts_count() }}p, {{ collection.get_threads_count() }}t)</div>
|
{%- set pc = collection.get_posts_count() -%}
|
||||||
{% endfor %}
|
{%- set tc = collection.get_threads_count() -%}
|
||||||
|
<div class="bookmark-dropdown-item {{ "selected" if selected and (selected.id | int) == (collection.id | int) else ""}}" data-send="selectBookmarkCollection" data-receive="selectBookmarkCollection" data-collection-id="{{collection.id}}">
|
||||||
|
<span class="bookmark-dropdown-item-name">{{collection.name}}</span>
|
||||||
|
<span class="bookmark-dropdown-item-stats"><abbr title="{{ pc }} {{('post' | pluralize(pc))}}">{{ pc }}p</abbr>, <abbr title="{{ tc }} {{('thread' | pluralize(tc))}}">{{ tc }}t</abbr></span>
|
||||||
|
</div>
|
||||||
|
{%- endfor -%}
|
||||||
</div>
|
</div>
|
||||||
<span>
|
<span>
|
||||||
<input type="text" placeholder="Memo" class="bookmark-memo-input" value="{{memo}}"></input>
|
<input type="text" placeholder="Memo" class="bookmark-memo-input" value="{{memo}}"></input>
|
||||||
|
|||||||
17
app/templates/mod/motd.html
Normal file
17
app/templates/mod/motd.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{% from 'common/macros.html' import babycode_editor_component %}
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block title %}editing MOTD{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="darkbg settings-container">
|
||||||
|
<h1>Edit Message of the Day</h1>
|
||||||
|
<p>The Message of the Day will show up on the main page and in every topic.</p>
|
||||||
|
<form method="POST">
|
||||||
|
<label for="title">Title</label>
|
||||||
|
<input name="title" id="title" type="text" required autocomplete="off" placeholder="Required" value="{{ current.title }}"><br>
|
||||||
|
<label for="body">Body</label>
|
||||||
|
{{ babycode_editor_component('body', ta_placeholder='MOTD body (required)', banned_tags=['img', 'spoiler'], prefill=current.body_original_markup) }}
|
||||||
|
<input type="submit" value="Save">
|
||||||
|
<input class="critical" type="submit" formaction="{{ url_for('mod.motd_delete') }}" value="Delete MOTD" formnovalidate {{"disabled" if not current else ""}}>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
12
app/templates/mod/panel.html
Normal file
12
app/templates/mod/panel.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}moderation{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="darkbg settings-container">
|
||||||
|
<h1>Moderation actions</h1>
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('mod.user_list') }}">User list</a></li>
|
||||||
|
<li><a href="{{ url_for('mod.sort_topics') }}">Sort topics</a></li>
|
||||||
|
<li><a href="{{ url_for('mod.motd_editor') }}">Message of the Day</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{% from 'common/macros.html' import pager, timestamp %}
|
{% from 'common/macros.html' import pager, timestamp, motd %}
|
||||||
{% from 'common/icons.html' import icn_lock, icn_sticky %}
|
{% from 'common/icons.html' import icn_lock, icn_sticky %}
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}browsing topic {{ topic['name'] }}{% endblock %}
|
{% block title %}browsing topic {{ topic['name'] }}{% endblock %}
|
||||||
@@ -24,9 +24,17 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{% if topic['is_locked'] %}
|
{% if topic['is_locked'] %}
|
||||||
{{ infobox("This topic is locked. Only moderators can create new threads.", InfoboxKind.INFO) }}
|
{{ infobox("This topic is locked.;Only moderators can create new threads.", InfoboxKind.INFO) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{%- with motds = get_motds() -%}
|
||||||
|
{%- if motds -%}
|
||||||
|
{%- for motd_obj in motds -%}
|
||||||
|
{{- motd(motd_obj) -}}
|
||||||
|
{%- endfor -%}
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endwith -%}
|
||||||
|
|
||||||
{% if threads_list | length == 0 %}
|
{% if threads_list | length == 0 %}
|
||||||
<p>There are no threads in this topic.</p>
|
<p>There are no threads in this topic.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{% from 'common/icons.html' import icn_lock %}
|
{% from 'common/icons.html' import icn_lock %}
|
||||||
{% from 'common/macros.html' import timestamp %}
|
{% from 'common/macros.html' import timestamp, motd %}
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<nav class="darkbg">
|
<nav class="darkbg">
|
||||||
@@ -9,6 +9,13 @@
|
|||||||
<a class="linkbutton" href={{ url_for("mod.sort_topics") }}>Sort topics</a>
|
<a class="linkbutton" href={{ url_for("mod.sort_topics") }}>Sort topics</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</nav>
|
</nav>
|
||||||
|
{%- with motds = get_motds() -%}
|
||||||
|
{%- if motds -%}
|
||||||
|
{%- for motd_obj in motds -%}
|
||||||
|
{{- motd(motd_obj) -}}
|
||||||
|
{%- endfor -%}
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endwith -%}
|
||||||
{% if topic_list | length == 0 %}
|
{% if topic_list | length == 0 %}
|
||||||
<p>There are no topics.</p>
|
<p>There are no topics.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -1156,6 +1156,14 @@ ul, ol {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.horizontal, ol.horizontal {
|
||||||
|
display: inline;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
ul.horizontal li, ol.horizontal li {
|
||||||
|
display: inline list-item;
|
||||||
|
}
|
||||||
|
|
||||||
.new-concept-notification.hidden {
|
.new-concept-notification.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -1182,6 +1190,7 @@ ul, ol {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
margin: 10px 5px;
|
margin: 10px 5px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion.hidden {
|
.accordion.hidden {
|
||||||
@@ -1312,14 +1321,17 @@ footer {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
margin: 0;
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
|
max-width: 400px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
color: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmark-dropdown-item {
|
.bookmark-dropdown-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -1332,10 +1344,13 @@ footer {
|
|||||||
background-color: rgb(192.6, 215.8, 214.6);
|
background-color: rgb(192.6, 215.8, 214.6);
|
||||||
}
|
}
|
||||||
.bookmark-dropdown-item::before {
|
.bookmark-dropdown-item::before {
|
||||||
content: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E");
|
content: "";
|
||||||
|
background-color: currentColor;
|
||||||
|
mask: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E") center/contain no-repeat;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.bookmark-dropdown-item.selected {
|
.bookmark-dropdown-item.selected {
|
||||||
background-color: #beb1ce;
|
background-color: #beb1ce;
|
||||||
@@ -1344,7 +1359,7 @@ footer {
|
|||||||
background-color: rgb(203, 192.6, 215.8);
|
background-color: rgb(203, 192.6, 215.8);
|
||||||
}
|
}
|
||||||
.bookmark-dropdown-item.selected::before {
|
.bookmark-dropdown-item.selected::before {
|
||||||
content: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3Crect%20x%3D%225%22%20y%3D%225%22%20width%3D%2214%22%20height%3D%2214%22%20rx%3D%222%22%20stroke%3D%22none%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E");
|
mask: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3Crect%20x%3D%225%22%20y%3D%225%22%20width%3D%2214%22%20height%3D%2214%22%20rx%3D%222%22%20stroke%3D%22none%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E") center/contain no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmarks-dropdown-header {
|
.bookmarks-dropdown-header {
|
||||||
@@ -1352,7 +1367,45 @@ footer {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bookmark-dropdown-item-name {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmark-dropdown-item-stats {
|
||||||
|
padding: 0 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.bookmark-dropdown-items-container {
|
.bookmark-dropdown-items-container {
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.motd {
|
||||||
|
display: flex;
|
||||||
|
background-color: #c1ceb1;
|
||||||
|
border: 2px outset rgb(217.26, 220.38, 213.42);
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.motd-icon-container {
|
||||||
|
display: flex;
|
||||||
|
min-width: 80px;
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.motd-content-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-self: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding-right: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.motd-title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
font-family: "Cadman";
|
font-family: "Cadman";
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
padding: 5px 20px;
|
padding: 5px 20px;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
@@ -574,14 +574,14 @@ pre code { /* Literal.Number.Integer.Long */ }
|
|||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
#delete-dialog, .lightbox-dialog {
|
#delete-dialog, .lightbox-dialog {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
border: 2px solid black;
|
border: 2px solid black;
|
||||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
@@ -618,7 +618,7 @@ pre code { /* Literal.Number.Integer.Long */ }
|
|||||||
blockquote {
|
blockquote {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
border-left: 10px solid #ae6bae;
|
border-left: 10px solid #ae6bae;
|
||||||
background-color: rgba(251, 175, 207, 0.0392156863);
|
background-color: rgba(251, 175, 207, 0.0392156863);
|
||||||
}
|
}
|
||||||
@@ -832,7 +832,7 @@ p {
|
|||||||
|
|
||||||
input[type=text], input[type=password], textarea, select {
|
input[type=text], input[type=password], textarea, select {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
padding: 7px 10px;
|
padding: 7px 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -1146,9 +1146,9 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus
|
|||||||
background-color: #503250;
|
background-color: #503250;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 8px;
|
||||||
border-bottom-right-radius: 4px;
|
border-bottom-right-radius: 8px;
|
||||||
border-bottom-left-radius: 4px;
|
border-bottom-left-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul, ol {
|
ul, ol {
|
||||||
@@ -1156,6 +1156,14 @@ ul, ol {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.horizontal, ol.horizontal {
|
||||||
|
display: inline;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
ul.horizontal li, ol.horizontal li {
|
||||||
|
display: inline list-item;
|
||||||
|
}
|
||||||
|
|
||||||
.new-concept-notification.hidden {
|
.new-concept-notification.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -1167,7 +1175,7 @@ ul, ol {
|
|||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
background-color: #775891;
|
background-color: #775891;
|
||||||
padding: 20px 15px;
|
padding: 20px 15px;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1177,11 +1185,12 @@ ul, ol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.accordion {
|
.accordion {
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 8px;
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 8px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
margin: 10px 5px;
|
margin: 10px 5px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion.hidden {
|
.accordion.hidden {
|
||||||
@@ -1249,7 +1258,7 @@ ul, ol {
|
|||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
background-color: rgba(0, 0, 0, 0.5019607843);
|
background-color: rgba(0, 0, 0, 0.5019607843);
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
}
|
}
|
||||||
@@ -1284,7 +1293,7 @@ footer {
|
|||||||
position: relative;
|
position: relative;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
background-color: rgba(0, 0, 0, 0.5019607843);
|
background-color: rgba(0, 0, 0, 0.5019607843);
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
width: 250px;
|
width: 250px;
|
||||||
@@ -1307,24 +1316,27 @@ footer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bookmarks-dropdown {
|
.bookmarks-dropdown {
|
||||||
background-color: #9b649b;
|
background-color: #503250;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
margin: 0;
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
|
max-width: 400px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
color: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmark-dropdown-item {
|
.bookmark-dropdown-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
color: #e6e6e6;
|
color: #e6e6e6;
|
||||||
background-color: #3c283c;
|
background-color: #3c283c;
|
||||||
}
|
}
|
||||||
@@ -1332,10 +1344,13 @@ footer {
|
|||||||
background-color: rgb(109.2, 72.8, 109.2);
|
background-color: rgb(109.2, 72.8, 109.2);
|
||||||
}
|
}
|
||||||
.bookmark-dropdown-item::before {
|
.bookmark-dropdown-item::before {
|
||||||
content: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E");
|
content: "";
|
||||||
|
background-color: currentColor;
|
||||||
|
mask: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E") center/contain no-repeat;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.bookmark-dropdown-item.selected {
|
.bookmark-dropdown-item.selected {
|
||||||
background-color: #8a5584;
|
background-color: #8a5584;
|
||||||
@@ -1344,7 +1359,7 @@ footer {
|
|||||||
background-color: rgb(167.4843049327, 112.9156950673, 161.3067264574);
|
background-color: rgb(167.4843049327, 112.9156950673, 161.3067264574);
|
||||||
}
|
}
|
||||||
.bookmark-dropdown-item.selected::before {
|
.bookmark-dropdown-item.selected::before {
|
||||||
content: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3Crect%20x%3D%225%22%20y%3D%225%22%20width%3D%2214%22%20height%3D%2214%22%20rx%3D%222%22%20stroke%3D%22none%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E");
|
mask: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3Crect%20x%3D%225%22%20y%3D%225%22%20width%3D%2214%22%20height%3D%2214%22%20rx%3D%222%22%20stroke%3D%22none%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E") center/contain no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmarks-dropdown-header {
|
.bookmarks-dropdown-header {
|
||||||
@@ -1352,16 +1367,73 @@ footer {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bookmark-dropdown-item-name {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmark-dropdown-item-stats {
|
||||||
|
padding: 0 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.bookmark-dropdown-items-container {
|
.bookmark-dropdown-items-container {
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.motd {
|
||||||
|
display: flex;
|
||||||
|
background-color: #503250;
|
||||||
|
border: 2px outset #503250;
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.motd-icon-container {
|
||||||
|
display: flex;
|
||||||
|
min-width: 80px;
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.motd-content-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-self: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding-right: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.motd-title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
#topnav {
|
#topnav {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
border: 10px solid rgb(40, 40, 40);
|
border: 10px solid rgb(40, 40, 40);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#bottomnav {
|
||||||
|
margin-top: 10px;
|
||||||
|
border: 10px solid rgb(40, 40, 40);
|
||||||
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.infobox, .motd {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-sticky-container {
|
||||||
|
border-top-left-radius: 8px;
|
||||||
|
border-bottom-left-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-locked-container {
|
||||||
|
border-top-right-radius: 8px;
|
||||||
|
border-bottom-right-radius: 8px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1156,6 +1156,14 @@ ul, ol {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.horizontal, ol.horizontal {
|
||||||
|
display: inline;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
ul.horizontal li, ol.horizontal li {
|
||||||
|
display: inline list-item;
|
||||||
|
}
|
||||||
|
|
||||||
.new-concept-notification.hidden {
|
.new-concept-notification.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -1182,6 +1190,7 @@ ul, ol {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
margin: 6px 3px;
|
margin: 6px 3px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion.hidden {
|
.accordion.hidden {
|
||||||
@@ -1312,14 +1321,17 @@ footer {
|
|||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
margin: 0;
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
|
max-width: 400px;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
color: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmark-dropdown-item {
|
.bookmark-dropdown-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
padding: 6px 0;
|
padding: 6px 0;
|
||||||
margin: 6px 0;
|
margin: 6px 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -1332,10 +1344,13 @@ footer {
|
|||||||
background-color: rgb(244.6, 148.6, 123);
|
background-color: rgb(244.6, 148.6, 123);
|
||||||
}
|
}
|
||||||
.bookmark-dropdown-item::before {
|
.bookmark-dropdown-item::before {
|
||||||
content: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E");
|
content: "";
|
||||||
|
background-color: currentColor;
|
||||||
|
mask: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E") center/contain no-repeat;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.bookmark-dropdown-item.selected {
|
.bookmark-dropdown-item.selected {
|
||||||
background-color: #b54444;
|
background-color: #b54444;
|
||||||
@@ -1344,7 +1359,7 @@ footer {
|
|||||||
background-color: rgb(197.978313253, 103.221686747, 103.221686747);
|
background-color: rgb(197.978313253, 103.221686747, 103.221686747);
|
||||||
}
|
}
|
||||||
.bookmark-dropdown-item.selected::before {
|
.bookmark-dropdown-item.selected::before {
|
||||||
content: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3Crect%20x%3D%225%22%20y%3D%225%22%20width%3D%2214%22%20height%3D%2214%22%20rx%3D%222%22%20stroke%3D%22none%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E");
|
mask: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3Crect%20x%3D%225%22%20y%3D%225%22%20width%3D%2214%22%20height%3D%2214%22%20rx%3D%222%22%20stroke%3D%22none%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E") center/contain no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmarks-dropdown-header {
|
.bookmarks-dropdown-header {
|
||||||
@@ -1352,11 +1367,49 @@ footer {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bookmark-dropdown-item-name {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmark-dropdown-item-stats {
|
||||||
|
padding: 0 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.bookmark-dropdown-items-container {
|
.bookmark-dropdown-items-container {
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.motd {
|
||||||
|
display: flex;
|
||||||
|
background-color: #f27a5a;
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 6px 8px;
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.motd-icon-container {
|
||||||
|
display: flex;
|
||||||
|
min-width: 80px;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.motd-content-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-self: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding-right: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.motd-title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
#topnav {
|
#topnav {
|
||||||
border-top-left-radius: 16px;
|
border-top-left-radius: 16px;
|
||||||
border-top-right-radius: 16px;
|
border-top-right-radius: 16px;
|
||||||
@@ -1378,10 +1431,3 @@ footer {
|
|||||||
border: none;
|
border: none;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.darkbg {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.darkbg a {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
ta.addEventListener("keydown", (e) => {
|
ta.addEventListener("keydown", (e) => {
|
||||||
if(e.key === "Enter" && e.ctrlKey) {
|
if(e.key === "Enter" && e.ctrlKey) {
|
||||||
// console.log(e.target.form)
|
|
||||||
if (inThread()) {
|
if (inThread()) {
|
||||||
localStorage.removeItem(window.location.pathname);
|
localStorage.removeItem(window.location.pathname);
|
||||||
}
|
}
|
||||||
@@ -38,148 +37,4 @@
|
|||||||
if (!prevContent) return;
|
if (!prevContent) return;
|
||||||
ta.value = prevContent;
|
ta.value = prevContent;
|
||||||
})
|
})
|
||||||
|
|
||||||
const buttonBold = document.getElementById("post-editor-bold");
|
|
||||||
const buttonItalics = document.getElementById("post-editor-italics");
|
|
||||||
const buttonStrike = document.getElementById("post-editor-strike");
|
|
||||||
const buttonUnderline = document.getElementById("post-editor-underline");
|
|
||||||
const buttonUrl = document.getElementById("post-editor-url");
|
|
||||||
const buttonCode = document.getElementById("post-editor-code");
|
|
||||||
const buttonImg = document.getElementById("post-editor-img");
|
|
||||||
const buttonOl = document.getElementById("post-editor-ol");
|
|
||||||
const buttonUl = document.getElementById("post-editor-ul");
|
|
||||||
const buttonSpoiler = document.getElementById("post-editor-spoiler");
|
|
||||||
|
|
||||||
function insertTag(tagStart, newline = false, prefill = "") {
|
|
||||||
const hasAttr = tagStart[tagStart.length - 1] === "=";
|
|
||||||
let tagEnd = tagStart;
|
|
||||||
let tagInsertStart = `[${tagStart}]${newline ? "\n" : ""}`;
|
|
||||||
if (hasAttr) {
|
|
||||||
tagEnd = tagEnd.slice(0, -1);
|
|
||||||
}
|
|
||||||
const tagInsertEnd = `${newline ? "\n" : ""}[/${tagEnd}]`;
|
|
||||||
const hasSelection = ta.selectionStart !== ta.selectionEnd;
|
|
||||||
const text = ta.value;
|
|
||||||
if (hasSelection) {
|
|
||||||
const realStart = Math.min(ta.selectionStart, ta.selectionEnd);
|
|
||||||
const realEnd = Math.max(ta.selectionStart, ta.selectionEnd);
|
|
||||||
const selectionLength = realEnd - realStart;
|
|
||||||
|
|
||||||
const strStart = text.slice(0, realStart);
|
|
||||||
const strEnd = text.substring(realEnd);
|
|
||||||
const frag = `${tagInsertStart}${text.slice(realStart, realEnd)}${tagInsertEnd}`;
|
|
||||||
const reconst = `${strStart}${frag}${strEnd}`;
|
|
||||||
ta.value = reconst;
|
|
||||||
if (!hasAttr){
|
|
||||||
ta.setSelectionRange(realStart + tagInsertStart.length, realStart + tagInsertStart.length + selectionLength);
|
|
||||||
} else {
|
|
||||||
ta.setSelectionRange(realStart + tagInsertEnd.length - 1, realStart + tagInsertEnd.length - 1); // cursor on attr
|
|
||||||
}
|
|
||||||
ta.focus()
|
|
||||||
} else {
|
|
||||||
if (hasAttr) {
|
|
||||||
tagInsertStart += prefill;
|
|
||||||
}
|
|
||||||
const cursor = ta.selectionStart;
|
|
||||||
const strStart = text.slice(0, cursor);
|
|
||||||
const strEnd = text.substr(cursor);
|
|
||||||
|
|
||||||
let newCursor = strStart.length + tagInsertStart.length;
|
|
||||||
if (hasAttr) {
|
|
||||||
newCursor = cursor + tagInsertStart.length - prefill.length - 1;
|
|
||||||
}
|
|
||||||
const reconst = `${strStart}${tagInsertStart}${tagInsertEnd}${strEnd}`;
|
|
||||||
ta.value = reconst;
|
|
||||||
ta.setSelectionRange(newCursor, newCursor);
|
|
||||||
ta.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buttonBold.addEventListener("click", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
insertTag("b")
|
|
||||||
})
|
|
||||||
buttonItalics.addEventListener("click", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
insertTag("i")
|
|
||||||
})
|
|
||||||
buttonStrike.addEventListener("click", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
insertTag("s")
|
|
||||||
})
|
|
||||||
buttonUnderline.addEventListener("click", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
insertTag("u")
|
|
||||||
})
|
|
||||||
buttonUrl.addEventListener("click", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
insertTag("url=", false, "link label");
|
|
||||||
})
|
|
||||||
buttonCode.addEventListener("click", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
insertTag("code", true)
|
|
||||||
})
|
|
||||||
buttonImg.addEventListener("click", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
insertTag("img=", false, "alt text");
|
|
||||||
})
|
|
||||||
buttonOl.addEventListener("click", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
insertTag("ol", true);
|
|
||||||
})
|
|
||||||
buttonUl.addEventListener("click", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
insertTag("ul", true);
|
|
||||||
})
|
|
||||||
buttonSpoiler.addEventListener("click", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
insertTag("spoiler=", true, "hidden content");
|
|
||||||
})
|
|
||||||
|
|
||||||
const previewEndpoint = "/api/babycode-preview";
|
|
||||||
let previousMarkup = "";
|
|
||||||
const previewTab = document.getElementById("tab-preview");
|
|
||||||
previewTab.addEventListener("tab-activated", async () => {
|
|
||||||
const previewContainer = document.getElementById("babycode-preview-container");
|
|
||||||
const previewErrorsContainer = document.getElementById("babycode-preview-errors-container");
|
|
||||||
// previewErrorsContainer.textContent = "";
|
|
||||||
const markup = ta.value.trim();
|
|
||||||
if (markup === "" || markup === previousMarkup) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
previousMarkup = markup;
|
|
||||||
const req = await fetch(previewEndpoint, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({markup: markup})
|
|
||||||
})
|
|
||||||
if (!req.ok) {
|
|
||||||
switch (req.status) {
|
|
||||||
case 429:
|
|
||||||
previewErrorsContainer.textContent = "(Old preview, try again in a few seconds.)"
|
|
||||||
previousMarkup = "";
|
|
||||||
break;
|
|
||||||
case 400:
|
|
||||||
previewErrorsContainer.textContent = "(Request got malformed.)"
|
|
||||||
break;
|
|
||||||
case 401:
|
|
||||||
previewErrorsContainer.textContent = "(You are not logged in.)"
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
previewErrorsContainer.textContent = "(Error. Check console.)"
|
|
||||||
console.error(req.error);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const json_resp = await req.json();
|
|
||||||
previewContainer.innerHTML = json_resp.html;
|
|
||||||
previewErrorsContainer.textContent = "";
|
|
||||||
|
|
||||||
const accordionRefreshEvt = new CustomEvent("refresh_accordions");
|
|
||||||
document.body.dispatchEvent(accordionRefreshEvt);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
const bookmarkMenuHrefTemplate = '/hyperapi/bookmarks-dropdown'
|
const bookmarkMenuHrefTemplate = '/hyperapi/bookmarks-dropdown';
|
||||||
|
const previewEndpoint = '/api/babycode-preview';
|
||||||
|
|
||||||
|
const delay = ms => {return new Promise(resolve => setTimeout(resolve, ms))}
|
||||||
|
|
||||||
export default class {
|
export default class {
|
||||||
async showBookmarkMenu(ev, el) {
|
async showBookmarkMenu(ev, el) {
|
||||||
if ((ev.target.dataset.bookmarkId === el.dataset.bookmarkId) && el.childElementCount === 0) {
|
if ((ev.sender.dataset.bookmarkId === el.getString('bookmarkId')) && el.childElementCount === 0) {
|
||||||
const bookmarkMenuHref = `${bookmarkMenuHrefTemplate}/${ev.target.dataset.bookmarkType}?id=${ev.target.dataset.conceptId}&require_reload=${el.dataset.requireReload}`;
|
const searchParams = new URLSearchParams({
|
||||||
|
'id': ev.sender.dataset.conceptId,
|
||||||
|
'require_reload': el.dataset.requireReload,
|
||||||
|
});
|
||||||
|
const bookmarkMenuHref = `${bookmarkMenuHrefTemplate}/${ev.sender.dataset.bookmarkType}?${searchParams}`;
|
||||||
const res = await this.api.getHTML(bookmarkMenuHref);
|
const res = await this.api.getHTML(bookmarkMenuHref);
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
return;
|
return;
|
||||||
@@ -11,19 +18,39 @@ export default class {
|
|||||||
const frag = res.value;
|
const frag = res.value;
|
||||||
el.appendChild(frag);
|
el.appendChild(frag);
|
||||||
const menu = el.childNodes[0];
|
const menu = el.childNodes[0];
|
||||||
const bRect = el.getBoundingClientRect()
|
menu.showPopover();
|
||||||
if (bRect.left < window.innerWidth - bRect.right) {
|
|
||||||
menu.style.right = 'unset';
|
const bRect = el.getBoundingClientRect();
|
||||||
|
const menuRect = menu.getBoundingClientRect();
|
||||||
|
const preferredLeft = bRect.right - menuRect.width;
|
||||||
|
const preferredRight = bRect.right;
|
||||||
|
const enoughSpace = preferredLeft >= 0;
|
||||||
|
const scrollY = window.scrollY || window.pageYOffset;
|
||||||
|
if (enoughSpace) {
|
||||||
|
menu.style.left = `${preferredLeft}px`;
|
||||||
|
} else {
|
||||||
|
menu.style.left = `${bRect.left}px`;
|
||||||
}
|
}
|
||||||
|
menu.style.top = `${bRect.bottom + scrollY}px`;
|
||||||
|
|
||||||
|
menu.addEventListener('beforetoggle', (e) => {
|
||||||
|
if (e.newState === 'closed') {
|
||||||
|
// if it's still in the tree, remove it
|
||||||
|
// the delay is required to make sure its removed instantly when
|
||||||
|
// clicking the button when the menu is open
|
||||||
|
setTimeout(() => {menu.remove()}, 100);
|
||||||
|
};
|
||||||
|
}, { once: true });
|
||||||
|
|
||||||
} else if (el.childElementCount > 0) {
|
} else if (el.childElementCount > 0) {
|
||||||
el.removeChild(el.childNodes[0]);
|
el.removeChild(el.childNodes[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectBookmarkCollection(ev, el) {
|
selectBookmarkCollection(ev, el) {
|
||||||
const clicked = ev.target;
|
const clicked = ev.sender;
|
||||||
|
|
||||||
if (clicked === el) {
|
if (ev.sender === el) {
|
||||||
if (clicked.classList.contains('selected')) {
|
if (clicked.classList.contains('selected')) {
|
||||||
clicked.classList.remove('selected');
|
clicked.classList.remove('selected');
|
||||||
} else {
|
} else {
|
||||||
@@ -35,7 +62,7 @@ export default class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async saveBookmarks(ev, el) {
|
async saveBookmarks(ev, el) {
|
||||||
const bookmarkHref = el.dataset.bookmarkEndpoint;
|
const bookmarkHref = el.getString('bookmarkEndpoint');
|
||||||
const collection = el.querySelector('.bookmark-dropdown-item.selected');
|
const collection = el.querySelector('.bookmark-dropdown-item.selected');
|
||||||
let data = {};
|
let data = {};
|
||||||
if (collection) {
|
if (collection) {
|
||||||
@@ -44,7 +71,7 @@ export default class {
|
|||||||
data['memo'] = el.querySelector('.bookmark-memo-input').value;
|
data['memo'] = el.querySelector('.bookmark-memo-input').value;
|
||||||
} else {
|
} else {
|
||||||
data['operation'] = 'remove';
|
data['operation'] = 'remove';
|
||||||
data['collection_id'] = el.dataset.originallyContainedIn;
|
data['collection_id'] = el.getString('originallyContainedIn');
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
@@ -54,11 +81,167 @@ export default class {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const requireReload = parseInt(el.dataset.requireReload) !== 0;
|
const requireReload = el.getInt('requireReload') !== 0;
|
||||||
el.remove();
|
el.remove();
|
||||||
await fetch(bookmarkHref, options);
|
await fetch(bookmarkHref, options);
|
||||||
if (requireReload) {
|
if (requireReload) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async copyCode(ev, el) {
|
||||||
|
if (!el.isSender) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await navigator.clipboard.writeText(el.value);
|
||||||
|
el.textContent = 'Copied!'
|
||||||
|
await delay(1000);
|
||||||
|
el.textContent = 'Copy';
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAccordion(ev, el) {
|
||||||
|
const accordion = el;
|
||||||
|
const header = accordion.querySelector('.accordion-header');
|
||||||
|
if (!header.contains(ev.sender)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const btn = ev.sender;
|
||||||
|
const content = el.querySelector('.accordion-content');
|
||||||
|
// these are all meant to be in sync
|
||||||
|
accordion.classList.toggle('hidden');
|
||||||
|
content.classList.toggle('hidden');
|
||||||
|
btn.textContent = accordion.classList.contains('hidden') ? '+' : '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleTab(ev, el) {
|
||||||
|
const tabButtonsContainer = el.querySelector('.tab-buttons');
|
||||||
|
if (!el.contains(ev.sender)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev.sender.classList.contains('active')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetId = ev.sender.getString('targetId');
|
||||||
|
const contents = el.querySelectorAll('.tab-content');
|
||||||
|
for (let content of contents) {
|
||||||
|
if (content.id === targetId) {
|
||||||
|
content.classList.add('active');
|
||||||
|
} else {
|
||||||
|
content.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let button of tabButtonsContainer.children) {
|
||||||
|
if (button.dataset.targetId === targetId) {
|
||||||
|
button.classList.add('active');
|
||||||
|
} else {
|
||||||
|
button.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#previousMarkup = '';
|
||||||
|
async babycodePreview(ev, el) {
|
||||||
|
if (ev.sender.classList.contains('active')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previewErrorsContainer = el.querySelector('#babycode-preview-errors-container');
|
||||||
|
const previewContainer = el.querySelector('#babycode-preview-container');
|
||||||
|
const ta = document.getElementById('babycode-content');
|
||||||
|
const markup = ta.value.trim();
|
||||||
|
if (markup === '' || markup === this.#previousMarkup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bannedTags = JSON.parse(document.getElementById('babycode-banned-tags').value);
|
||||||
|
this.#previousMarkup = markup;
|
||||||
|
|
||||||
|
const res = await this.api.getJSON(previewEndpoint, [], {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
markup: markup,
|
||||||
|
banned_tags: bannedTags,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (res.error) {
|
||||||
|
switch (res.error.status) {
|
||||||
|
case 429:
|
||||||
|
previewErrorsContainer.textContent = '(Old preview, try again in a few seconds.)'
|
||||||
|
this.#previousMarkup = '';
|
||||||
|
break;
|
||||||
|
case 400:
|
||||||
|
previewErrorsContainer.textContent = '(Request got malformed.)'
|
||||||
|
break;
|
||||||
|
case 401:
|
||||||
|
previewErrorsContainer.textContent = '(You are not logged in.)'
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
previewErrorsContainer.textContent = '(Error. Check console.)'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
previewErrorsContainer.textContent = '';
|
||||||
|
previewContainer.innerHTML = res.value.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insertBabycodeTag(ev, el) {
|
||||||
|
const tagStart = ev.sender.getString('tag');
|
||||||
|
const breakLine = 'breakLine' in ev.sender.dataset;
|
||||||
|
const prefill = 'prefill' in ev.sender.dataset ? ev.sender.dataset.prefill : '';
|
||||||
|
|
||||||
|
const hasAttr = tagStart[tagStart.length - 1] === '=';
|
||||||
|
let tagEnd = tagStart;
|
||||||
|
let tagInsertStart = `[${tagStart}]${breakLine ? '\n' : ''}`;
|
||||||
|
if (hasAttr) {
|
||||||
|
tagEnd = tagEnd.slice(0, -1);
|
||||||
|
}
|
||||||
|
const tagInsertEnd = `${breakLine ? '\n' : ''}[/${tagEnd}]`;
|
||||||
|
const hasSelection = el.selectionStart !== el.selectionEnd;
|
||||||
|
const text = el.value;
|
||||||
|
|
||||||
|
if (hasSelection) {
|
||||||
|
const realStart = Math.min(el.selectionStart, el.selectionEnd);
|
||||||
|
const realEnd = Math.max(el.selectionStart, el.selectionEnd);
|
||||||
|
const selectionLength = realEnd - realStart;
|
||||||
|
|
||||||
|
const strStart = text.slice(0, realStart);
|
||||||
|
const strEnd = text.substring(realEnd);
|
||||||
|
const frag = `${tagInsertStart}${text.slice(realStart, realEnd)}${tagInsertEnd}`;
|
||||||
|
const reconst = `${strStart}${frag}${strEnd}`;
|
||||||
|
el.value = reconst;
|
||||||
|
if (!hasAttr) {
|
||||||
|
el.setSelectionRange(realStart + tagInsertStart.length, realStart + tagInsertEnd.length + selectionLength - 1);
|
||||||
|
} else {
|
||||||
|
const attrCursor = realStart + tagInsertEnd.length - (1 + (breakLine ? 1 : 0))
|
||||||
|
el.setSelectionRange(attrCursor, attrCursor); // cursor on attr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hasAttr) {
|
||||||
|
tagInsertStart += prefill;
|
||||||
|
}
|
||||||
|
const cursor = el.selectionStart;
|
||||||
|
const strStart = text.slice(0, cursor);
|
||||||
|
const strEnd = text.substr(cursor);
|
||||||
|
|
||||||
|
let newCursor = strStart.length + tagInsertStart.length;
|
||||||
|
if (hasAttr) {
|
||||||
|
newCursor = cursor + tagInsertStart.length - prefill.length - (1 + (breakLine ? 1 : 0)) //cursor on attr
|
||||||
|
}
|
||||||
|
const reconst = `${strStart}${tagInsertStart}${tagInsertEnd}${strEnd}`;
|
||||||
|
el.value = reconst;
|
||||||
|
el.setSelectionRange(newCursor, newCursor);
|
||||||
|
}
|
||||||
|
el.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
addQuote(ev, el) {
|
||||||
|
el.value += ev.sender.value;
|
||||||
|
el.scrollIntoView();
|
||||||
|
el.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
{
|
{
|
||||||
const ta = document.getElementById("babycode-content");
|
const ta = document.getElementById("babycode-content");
|
||||||
|
|
||||||
for (let button of document.querySelectorAll(".reply-button")) {
|
|
||||||
button.addEventListener("click", (e) => {
|
|
||||||
ta.value += button.value;
|
|
||||||
ta.scrollIntoView()
|
|
||||||
ta.focus();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function supportsPopover() {
|
function supportsPopover() {
|
||||||
return Object.hasOwn(HTMLElement.prototype, "popover");
|
return Object.hasOwn(HTMLElement.prototype, "popover");
|
||||||
|
|||||||
@@ -1,26 +1,3 @@
|
|||||||
function activateSelfDeactivateSibs(button) {
|
|
||||||
if (button.classList.contains("active")) return;
|
|
||||||
|
|
||||||
Array.from(button.parentNode.children).forEach(s => {
|
|
||||||
if (s === button){
|
|
||||||
button.classList.add('active');
|
|
||||||
} else {
|
|
||||||
s.classList.remove('active');
|
|
||||||
}
|
|
||||||
const targetId = s.dataset.targetId;
|
|
||||||
const target = document.getElementById(targetId);
|
|
||||||
|
|
||||||
if (!target) return;
|
|
||||||
|
|
||||||
if (s.classList.contains('active')) {
|
|
||||||
target.classList.add('active');
|
|
||||||
target.dispatchEvent(new CustomEvent("tab-activated", {bubbles: false}))
|
|
||||||
} else {
|
|
||||||
target.classList.remove('active');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function openLightbox(post, idx) {
|
function openLightbox(post, idx) {
|
||||||
lightboxCurrentPost = post;
|
lightboxCurrentPost = post;
|
||||||
lightboxCurrentIdx = idx;
|
lightboxCurrentIdx = idx;
|
||||||
@@ -102,43 +79,6 @@ let lightboxCurrentPost = null;
|
|||||||
let lightboxCurrentIdx = -1;
|
let lightboxCurrentIdx = -1;
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
// tabs
|
|
||||||
document.querySelectorAll(".tab-button").forEach(button => {
|
|
||||||
button.addEventListener("click", () => {
|
|
||||||
activateSelfDeactivateSibs(button);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// accordions
|
|
||||||
const handledAccordions = new Set();
|
|
||||||
function attachAccordionHandlers(accordion){
|
|
||||||
if(handledAccordions.has(accordion)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handledAccordions.add(accordion)
|
|
||||||
const header = accordion.querySelector(".accordion-header");
|
|
||||||
const toggleButton = header.querySelector(".accordion-toggle");
|
|
||||||
const content = accordion.querySelector(".accordion-content");
|
|
||||||
|
|
||||||
const toggle = (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
accordion.classList.toggle("hidden");
|
|
||||||
content.classList.toggle("hidden");
|
|
||||||
toggleButton.textContent = content.classList.contains("hidden") ? "+" : "-"
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleButton.addEventListener("click", toggle);
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshAccordions(){
|
|
||||||
const accordions = document.querySelectorAll(".accordion");
|
|
||||||
accordions.forEach(attachAccordionHandlers);
|
|
||||||
}
|
|
||||||
refreshAccordions()
|
|
||||||
|
|
||||||
document.body.addEventListener('refresh_accordions', refreshAccordions)
|
|
||||||
|
|
||||||
//lightboxes
|
//lightboxes
|
||||||
lightboxObj = constructLightbox();
|
lightboxObj = constructLightbox();
|
||||||
document.body.appendChild(lightboxObj.dialog);
|
document.body.appendChild(lightboxObj.dialog);
|
||||||
@@ -192,13 +132,4 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
image.addEventListener("load", () => setImageMaxSize(image));
|
image.addEventListener("load", () => setImageMaxSize(image));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// copy code blocks
|
|
||||||
for (let button of document.querySelectorAll(".copy-code")) {
|
|
||||||
button.addEventListener("click", async () => {
|
|
||||||
await navigator.clipboard.writeText(button.value)
|
|
||||||
button.textContent = "Copied!"
|
|
||||||
setTimeout(() => {button.textContent = "Copy"}, 1000.0)
|
|
||||||
})
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|||||||
1
data/static/js/vnd/bitty-5.1.0-rc6.min.js
vendored
1
data/static/js/vnd/bitty-5.1.0-rc6.min.js
vendored
File diff suppressed because one or more lines are too long
1
data/static/js/vnd/bitty-6.0.0-rc3.min.js
vendored
Normal file
1
data/static/js/vnd/bitty-6.0.0-rc3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -475,12 +475,12 @@ $inline_code_padding: $SMALL_PADDING $MEDIUM_PADDING !default;
|
|||||||
white-space: pre;
|
white-space: pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
$dialogs-padding: $ZERO_PADDING !default;
|
$dialogs_padding: $ZERO_PADDING !default;
|
||||||
$dialogs-border-radius: $DEFAULT_BORDER_RADIUS !default;
|
$dialogs_border_radius: $DEFAULT_BORDER_RADIUS !default;
|
||||||
$dialog_border: 2px solid black !default;
|
$dialog_border: 2px solid black !default;
|
||||||
#delete-dialog, .lightbox-dialog {
|
#delete-dialog, .lightbox-dialog {
|
||||||
padding: $dialogs-padding;
|
padding: $dialogs_padding;
|
||||||
border-radius: $dialogs-border-radius;
|
border-radius: $dialogs_border_radius;
|
||||||
border: $dialog_border;
|
border: $dialog_border;
|
||||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
@@ -768,6 +768,10 @@ $thread_locked_background: none !default;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg.icon {
|
||||||
|
// pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
$post_img_container_gap: $SMALL_PADDING !default;
|
$post_img_container_gap: $SMALL_PADDING !default;
|
||||||
.post-img-container {
|
.post-img-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1069,6 +1073,15 @@ ul, ol {
|
|||||||
padding: $list_padding;
|
padding: $list_padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$horizontal_list_margin_left: 0 !default;
|
||||||
|
ul.horizontal, ol.horizontal {
|
||||||
|
display: inline;
|
||||||
|
margin-left: $horizontal_list_margin_left;
|
||||||
|
& li {
|
||||||
|
display: inline list-item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.new-concept-notification.hidden {
|
.new-concept-notification.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -1103,7 +1116,7 @@ $accordion_margin: $MEDIUM_PADDING $SMALL_PADDING !default;
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: $accordion_border;
|
border: $accordion_border;
|
||||||
margin: $accordion_margin;
|
margin: $accordion_margin;
|
||||||
// overflow: hidden;
|
overflow: hidden; // for border-radius clipping
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion.hidden {
|
.accordion.hidden {
|
||||||
@@ -1238,7 +1251,7 @@ $bookmarks_dropdown_background_color: $ACCENT_COLOR !default;
|
|||||||
$bookmarks_dropdown_border_radius: $DEFAULT_BORDER_RADIUS !default;
|
$bookmarks_dropdown_border_radius: $DEFAULT_BORDER_RADIUS !default;
|
||||||
$bookmarks_dropdown_border: $button_border !default;
|
$bookmarks_dropdown_border: $button_border !default;
|
||||||
$bookmarks_dropdown_shadow: 0 0 30px rgba(0, 0, 0, 0.25) !default;
|
$bookmarks_dropdown_shadow: 0 0 30px rgba(0, 0, 0, 0.25) !default;
|
||||||
$bookmarks_dropdown_min_width: 400px !default;
|
$bookmarks_dropdown_width: 400px !default;
|
||||||
$bookmarks_dropdown_padding: $MEDIUM_PADDING !default;
|
$bookmarks_dropdown_padding: $MEDIUM_PADDING !default;
|
||||||
.bookmarks-dropdown {
|
.bookmarks-dropdown {
|
||||||
background-color: $bookmarks_dropdown_background_color;
|
background-color: $bookmarks_dropdown_background_color;
|
||||||
@@ -1246,10 +1259,12 @@ $bookmarks_dropdown_padding: $MEDIUM_PADDING !default;
|
|||||||
border-radius: $bookmarks_dropdown_border_radius;
|
border-radius: $bookmarks_dropdown_border_radius;
|
||||||
box-shadow: $bookmarks_dropdown_shadow;
|
box-shadow: $bookmarks_dropdown_shadow;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
margin: 0;
|
||||||
min-width: $bookmarks_dropdown_min_width;
|
min-width: $bookmarks_dropdown_width;
|
||||||
|
max-width: $bookmarks_dropdown_width;
|
||||||
padding: $bookmarks_dropdown_padding;
|
padding: $bookmarks_dropdown_padding;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
color: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
$bookmark_dropdown_item_padding: $MEDIUM_PADDING 0 !default;
|
$bookmark_dropdown_item_padding: $MEDIUM_PADDING 0 !default;
|
||||||
@@ -1263,6 +1278,7 @@ $bookmark_dropdown_item_icon_size: 24px !default;
|
|||||||
$bookmark_dropdown_item_icon_padding: 0 $MEDIUM_PADDING !default;
|
$bookmark_dropdown_item_icon_padding: 0 $MEDIUM_PADDING !default;
|
||||||
.bookmark-dropdown-item {
|
.bookmark-dropdown-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
padding: $bookmark_dropdown_item_padding;
|
padding: $bookmark_dropdown_item_padding;
|
||||||
margin: $bookmark_dropdown_item_margin;
|
margin: $bookmark_dropdown_item_margin;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -1276,11 +1292,13 @@ $bookmark_dropdown_item_icon_padding: 0 $MEDIUM_PADDING !default;
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
// TODO: un-inline this once the bitty bug is fixed
|
content: '';
|
||||||
content: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E');
|
background-color: currentColor;
|
||||||
|
mask: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E') center/contain no-repeat;
|
||||||
width: $bookmark_dropdown_item_icon_size;
|
width: $bookmark_dropdown_item_icon_size;
|
||||||
height: $bookmark_dropdown_item_icon_size;
|
height: $bookmark_dropdown_item_icon_size;
|
||||||
padding: $bookmark_dropdown_item_icon_padding;
|
padding: $bookmark_dropdown_item_icon_padding;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
@@ -1290,7 +1308,7 @@ $bookmark_dropdown_item_icon_padding: 0 $MEDIUM_PADDING !default;
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::before{
|
&::before{
|
||||||
content: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3Crect%20x%3D%225%22%20y%3D%225%22%20width%3D%2214%22%20height%3D%2214%22%20rx%3D%222%22%20stroke%3D%22none%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E');
|
mask: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20x%3D%221.5%22%20y%3D%221.5%22%20width%3D%2221%22%20height%3D%2221%22%20rx%3D%223%22%20stroke%3D%22currentColor%22%20stroke-width%3D%223%22%20fill%3D%22none%22%2F%3E%3Crect%20x%3D%225%22%20y%3D%225%22%20width%3D%2214%22%20height%3D%2214%22%20rx%3D%222%22%20stroke%3D%22none%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E') center/contain no-repeat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1300,8 +1318,54 @@ $bookmark_dropdown_item_icon_padding: 0 $MEDIUM_PADDING !default;
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bookmark-dropdown-item-name {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bookmark_dropdown_item_stats_padding: 0 $MEDIUM_PADDING !default;
|
||||||
|
.bookmark-dropdown-item-stats {
|
||||||
|
padding: $bookmark_dropdown_item_stats_padding;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
$bookmark_dropdown_items_container_max_height: 300px !default;
|
$bookmark_dropdown_items_container_max_height: 300px !default;
|
||||||
.bookmark-dropdown-items-container {
|
.bookmark-dropdown-items-container {
|
||||||
max-height: $bookmark_dropdown_items_container_max_height;
|
max-height: $bookmark_dropdown_items_container_max_height;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$motd_background_color: $ACCENT_COLOR !default;
|
||||||
|
$motd_border: 2px outset $LIGHT !default;
|
||||||
|
$motd_padding: $MEDIUM_PADDING $MEDIUM_BIG_PADDING !default;
|
||||||
|
$motd_margin: $MEDIUM_PADDING 0 !default;
|
||||||
|
.motd {
|
||||||
|
display: flex;
|
||||||
|
background-color: $motd_background_color;
|
||||||
|
border: $motd_border;
|
||||||
|
padding: $motd_padding;
|
||||||
|
margin: $motd_margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
$motd_icon_min_width: 80px !default;
|
||||||
|
$motd_icon_padding_right: $MEDIUM_BIG_PADDING !default;
|
||||||
|
.motd-icon-container {
|
||||||
|
display: flex;
|
||||||
|
min-width: $motd_icon_min_width;
|
||||||
|
padding-right: $motd_icon_padding_right;
|
||||||
|
}
|
||||||
|
|
||||||
|
$motd_content_padding_right: 25% !default;
|
||||||
|
.motd-content-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-self: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding-right: $motd_content_padding_right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.motd-title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ $dark_accent: #231c23;
|
|||||||
$warn: #eaea6a;
|
$warn: #eaea6a;
|
||||||
$crit: #d53232;
|
$crit: #d53232;
|
||||||
|
|
||||||
|
$br: 8px;
|
||||||
|
|
||||||
@use 'default' with (
|
@use 'default' with (
|
||||||
$ACCENT_COLOR: #9b649b,
|
$ACCENT_COLOR: #9b649b,
|
||||||
|
|
||||||
@@ -30,6 +32,8 @@ $crit: #d53232;
|
|||||||
$BUTTON_CRITICAL_FONT_COLOR: $fc,
|
$BUTTON_CRITICAL_FONT_COLOR: $fc,
|
||||||
$ACCORDION_COLOR: #7d467d,
|
$ACCORDION_COLOR: #7d467d,
|
||||||
|
|
||||||
|
$DEFAULT_BORDER_RADIUS: $br,
|
||||||
|
|
||||||
$bottomnav_color: $dark_accent,
|
$bottomnav_color: $dark_accent,
|
||||||
|
|
||||||
$topic_info_background: $dark_accent,
|
$topic_info_background: $dark_accent,
|
||||||
@@ -49,6 +53,7 @@ $crit: #d53232;
|
|||||||
$post_content_background: $dark_accent,
|
$post_content_background: $dark_accent,
|
||||||
|
|
||||||
$thread_info_background_color: $dark_accent,
|
$thread_info_background_color: $dark_accent,
|
||||||
|
$motd_background_color: $lightish_accent,
|
||||||
|
|
||||||
$post_reactions_background: $lightish_accent,
|
$post_reactions_background: $lightish_accent,
|
||||||
|
|
||||||
@@ -73,6 +78,8 @@ $crit: #d53232;
|
|||||||
|
|
||||||
$tab_content_background: $lightish_accent,
|
$tab_content_background: $lightish_accent,
|
||||||
$tab_button_active_color: #8a5584,
|
$tab_button_active_color: #8a5584,
|
||||||
|
|
||||||
|
$bookmarks_dropdown_background_color: $lightish_accent,
|
||||||
);
|
);
|
||||||
|
|
||||||
#topnav {
|
#topnav {
|
||||||
@@ -80,6 +87,25 @@ $crit: #d53232;
|
|||||||
border: 10px solid rgb(40, 40, 40);
|
border: 10px solid rgb(40, 40, 40);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#bottomnav {
|
||||||
|
margin-top: 10px;
|
||||||
|
border: 10px solid rgb(40, 40, 40);
|
||||||
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.infobox, .motd {
|
||||||
|
border-radius: $br;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-sticky-container {
|
||||||
|
border-top-left-radius: $br;
|
||||||
|
border-bottom-left-radius: $br;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-locked-container {
|
||||||
|
border-top-right-radius: $br;
|
||||||
|
border-bottom-right-radius: $br;
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ $br: 16px;
|
|||||||
$usercard_border: none,
|
$usercard_border: none,
|
||||||
$usercard_border_right: none,
|
$usercard_border_right: none,
|
||||||
$thread_locked_border: 1px solid black,
|
$thread_locked_border: 1px solid black,
|
||||||
|
$motd_border: 1px solid black,
|
||||||
|
|
||||||
$SETTINGS_WIDTH: 60%,
|
$SETTINGS_WIDTH: 60%,
|
||||||
$PAGE_SIDE_MARGIN: 50px,
|
$PAGE_SIDE_MARGIN: 50px,
|
||||||
@@ -87,11 +88,3 @@ footer {
|
|||||||
border: none;
|
border: none;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.darkbg {
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
& a {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user