Compare commits

...

19 Commits

Author SHA1 Message Date
bbe57d6e94 move over a bunch of ui functionality to bitty 2025-11-29 06:58:41 +03:00
0bed6b58ae make otomotone theme a bit rounder 2025-11-29 04:02:15 +03:00
8164e63b09 theme edits 2025-11-27 22:56:12 +03:00
8b5b38e38b render motds in topic and topics views 2025-11-27 22:25:03 +03:00
d0dfd3a4c3 re-parse MOTDs if required on init 2025-11-27 19:56:17 +03:00
fca214dfcf add motd schema and motd editor 2025-11-27 19:47:26 +03:00
04fd3f5d20 add css class for horizontal lists 2025-11-27 17:11:57 +03:00
1a3c015612 use before_request in mod app 2025-11-27 15:56:29 +03:00
fc9ae63471 add mod panel view 2025-11-27 15:28:17 +03:00
4d88b5c24c update bitty to 6.0.0-rc3 2025-11-27 15:01:06 +03:00
fefdbdb493 make top navlinks into <ul> 2025-11-27 03:04:05 +03:00
d0c82cf9a9 use sender in show bookmark bitty 2025-11-27 03:03:21 +03:00
90fe38497d make bookmark dropdown items better looking 2025-11-26 21:02:59 +03:00
97e2c041c9 use bitty 6 api's 2025-11-26 16:11:46 +03:00
bbbe152ff8 fix bookmark menu bg in otomotone 2025-11-26 16:06:35 +03:00
a3ad36e9a9 fix click on svg; keep dropdown svg's inlined and make them respect currentColor 2025-11-26 15:46:38 +03:00
48fcadf61e more accurate public domain attribution 2025-11-26 15:37:01 +03:00
62e1724f6c use URLSearchParams in hyperapi url constructor 2025-11-26 15:19:11 +03:00
19383a538d update bitty to 6.0.0-rc1 2025-11-26 15:18:44 +03:00
30 changed files with 778 additions and 377 deletions

View File

@@ -27,7 +27,6 @@ Designers: Paul James Miller
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
Copyright: Gabriele Malaspina
Designers: Gabriele Malaspina
License: CC0 1.0
@@ -74,8 +73,8 @@ Repo: https://github.com/emcconville/wand
## 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/
Copyright: `Copyright (c) 2025 Alan Smith - https://www.alanwsmith.com/`
License: MIT
Repo: https://github.com/alanwsmith/bitty
License: CC0 1.0
Author: alan w smith https://www.alanwsmith.com/
Repo: https://github.com/alanwsmith/bitty

View File

@@ -1,6 +1,6 @@
from flask import Flask, session, request
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 .routes.users import is_logged_in, get_active_user, get_prefers_theme
from .routes.threads import get_post_url
@@ -51,6 +51,7 @@ def create_deleted_user():
def reparse_babycode():
print('Re-parsing babycode, this may take a while...')
from .db import db
from .constants import MOTD_BANNED_TAGS
post_histories = PostHistory.findall([
('markup_language', '=', 'babycode'),
('format_version', 'IS NOT', BABYCODE_VERSION)
@@ -80,6 +81,20 @@ def reparse_babycode():
})
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.')
def create_app():
@@ -171,6 +186,7 @@ def create_app():
return {
'get_post_url': get_post_url,
'get_prefers_theme': get_prefers_theme,
'get_motds': MOTD.get_all,
}
@app.template_filter("ts_datetime")

View File

@@ -47,6 +47,10 @@ REACTION_EMOJI = [
'scissors',
]
MOTD_BANNED_TAGS = [
'img', 'spoiler',
]
def permission_level_string(perm):
return PermissionLevelString[PermissionLevel(int(perm))]

View File

@@ -6,7 +6,7 @@ from pygments.lexers import get_lexer_by_name
from pygments.util import ClassNotFound as PygmentsClassNotFound
import re
BABYCODE_VERSION = 4
BABYCODE_VERSION = 5
NAMED_COLORS = [
'black', 'silver', 'gray', 'white', 'maroon', 'red',
@@ -61,7 +61,7 @@ def tag_code(children, attr, surrounding):
return f"<code class=\"inline-code\">{children}</code>"
else:
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>"
if not attr:
return unhighlighted
@@ -94,7 +94,7 @@ def tag_color(children, attr, surrounding):
def tag_spoiler(children, attr, surrounding):
spoiler_name = attr if attr else "Spoiler"
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
def tag_image(children, attr, surrounding):
@@ -218,10 +218,14 @@ def should_collapse(text, surrounding):
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'))
parser = Parser(subj)
parser.valid_bbcode_tags = TAGS.keys()
parser.valid_bbcode_tags = allowed_tags
parser.bbcode_tags_only_text_children = TEXT_ONLY
parser.valid_emotes = EMOJI.keys()

View File

@@ -394,3 +394,19 @@ class BookmarkedThreads(Model):
def get_thread(self):
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]

View File

@@ -40,7 +40,8 @@ def babycode_preview():
markup = request.json.get('markup')
if not markup or not isinstance(markup, str):
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}

View File

@@ -1,25 +1,33 @@
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 ..models import Users, PasswordResetLinks
from ..db import db, DB
from .users import get_active_user, is_logged_in
from ..models import Users, PasswordResetLinks, MOTD
from ..constants import InfoboxKind, MOTD_BANNED_TAGS
from ..lib.babycode import babycode_to_html, BABYCODE_VERSION
from ..db import db
import secrets
import time
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")
@login_required
@mod_only("topics.all_topics")
def sort_topics():
topics = db.query("SELECT * FROM topics ORDER BY sort_order ASC")
return render_template("mod/sort-topics.html", topics = topics)
@bp.post("/sort-topics")
@login_required
@mod_only("topics.all_topics")
def sort_topics_post():
with db.transaction():
for topic_id, new_order in request.form.items():
@@ -29,16 +37,12 @@ def sort_topics_post():
@bp.get("/user-list")
@login_required
@mod_only("users.page", username = lambda: get_active_user().username)
def user_list():
users = Users.select()
return render_template("mod/user-list.html", users = users)
@bp.post("/reset-pass/<user_id>")
@login_required
@mod_only("topics.all_topics")
def create_reset_pass(user_id):
now = int(time.time())
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))
@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'))

View File

@@ -13,7 +13,6 @@ bp = Blueprint("topics", __name__, url_prefix = "/topics/")
@bp.get("/")
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())

View File

@@ -120,6 +120,18 @@ SCHEMA = [
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
"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)",

View File

@@ -10,10 +10,10 @@
{% endif %}
<link rel="stylesheet" href="{{ ("/static/css/%s.css" % get_prefers_theme()) | cachebust }}">
<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>
<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' %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
@@ -26,7 +26,7 @@
<footer class="darkbg">
<span>Pyrom commit <a href="{{ "https://git.poto.cafe/yagich/pyrom/commit/" + __commit }}">{{ __commit[:8] }}</a></span>
</footer>
</bitty-5-1>
</bitty-6-0>
<script src="{{ "/static/js/ui.js" | cachebust }}"></script>
<script src="{{ "/static/js/date-fmt.js" | cachebust }}"></script>
</body>

View File

@@ -1,49 +1,55 @@
{# https://www.figma.com/community/file/1136337054881623512/iconcino-v2-0-0-free-icons-cc0-1-0-license #}
{% 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"/>
</svg>
{%- endmacro %}
{% 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"/>
</svg>
{%- endmacro %}
{% 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"/>
</svg>
{%- endmacro %}
{% 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"/>
</svg>
{%- endmacro %}
{% 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"/>
</svg>
{%- endmacro %}
{% 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"/>
</svg>
{%- endmacro %}
{% 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"/>
</svg>
{%- endmacro %}
{% 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"/>
</svg>
{%- 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 %}

View File

@@ -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) %}
{% set left_start = [1, current_page - 5] | max %}
{% set right_end = [page_count, current_page + 5] | min %}
@@ -39,49 +39,79 @@
{% macro infobox(message, kind=InfoboxKind.INFO) %}
<div class="{{ "infobox " + InfoboxHTMLClass[kind] }}">
<span>
<div class="infobox-icon-container">
{%- if kind == InfoboxKind.INFO -%}
{{- icn_info() -}}
{%- elif kind == InfoboxKind.LOCK -%}
{{- icn_lock() -}}
{%- elif kind == InfoboxKind.WARN -%}
{{- icn_warn() -}}
{%- elif kind == InfoboxKind.ERROR -%}
{{- icn_error() -}}
{%- endif -%}
</div>
{{ message }}
<div class="infobox-icon-container">
{%- if kind == InfoboxKind.INFO -%}
{{- icn_info() -}}
{%- elif kind == InfoboxKind.LOCK -%}
{{- icn_lock() -}}
{%- elif kind == InfoboxKind.WARN -%}
{{- icn_warn() -}}
{%- elif kind == InfoboxKind.ERROR -%}
{{- icn_error() -}}
{%- endif -%}
</div>
<span>
{% set m = message.split(';', maxsplit=1) %}
<strong>{{ m[0] }}</strong>
{%- if m[1] %}
{{ m[1] -}}
{%- endif -%}
</span>
</span>
</div>
{% 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) -%}
<span class="timestamp" data-utc="{{ unix_ts }}">{{ unix_ts | ts_datetime('%Y-%m-%d %H:%M')}} <abbr title="Server Time">ST</abbr></span>
{%- endmacro %}
{% macro babycode_editor_component(ta_name, ta_placeholder="Post body", optional=False, prefill="") %}
<div class="babycode-editor-container">
{% macro babycode_editor_component(ta_name, ta_placeholder="Post body", optional=False, prefill="", banned_tags=[]) %}
<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">
<button type=button class="tab-button active" data-target-id="tab-edit">Write</button>
<button type=button class="tab-button" data-target-id="tab-preview">Preview</button>
<button data-send="toggleTab" type=button class="tab-button active" data-target-id="tab-edit">Write</button>
<button data-send="babycodePreview toggleTab" type=button class="tab-button" data-target-id="tab-preview">Preview</button>
</div>
<div class="tab-content active" id="tab-edit">
<span class="babycode-button-container">
<button class="babycode-button" type=button id="post-editor-bold" title="Insert Bold"><strong>B</strong></button>
<button class="babycode-button" type=button id="post-editor-italics" title="Insert Italics"><em>I</em></button>
<button class="babycode-button" type=button id="post-editor-strike" title="Insert Strikethrough"><del>S</del></button>
<button class="babycode-button" type=button id="post-editor-underline" title="Insert Underline"><u>U</u></button>
<button class="babycode-button" type=button id="post-editor-url" title="Insert Link"><code>://</code></button>
<button class="babycode-button" type=button id="post-editor-code" title="Insert Code block"><code>&lt;/&gt;</code></button>
<button class="babycode-button contain-svg" type=button id="post-editor-img" title="Insert Image">{{ icn_image() }}</button>
<button class="babycode-button" type=button id="post-editor-ol" title="Insert Ordered list">1.</button>
<button class="babycode-button" type=button id="post-editor-ul" title="Insert Unordered list">&bullet;</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="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 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 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 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 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 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>&lt;/&gt;</code></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 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 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 ""}}>&bullet;</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>
<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>
{% 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 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-container"></div>
</div>
@@ -177,7 +207,7 @@
{% if show_reply %}
{% 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']) %}
<button value="{{ reply_text }}" class="reply-button">Quote</button>
<button data-send="addQuote" value="{{ reply_text }}" class="reply-button">Quote</button>
{% endif %}
{% set show_delete = false %}
@@ -242,13 +272,13 @@
</div>
{% endmacro %}
{% macro accordion(hidden=false, style="", disabled=false) %}
{% macro accordion(hidden=false, disabled=false) %}
{% if disabled %}
{% set hidden = true %}
{% endif %}
<div class="accordion {{ "hidden" if hidden else ""}}" style="{{style}}">
<div class="accordion {{ "hidden" if hidden else ""}}" data-receive="toggleAccordion">
<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') }}
</div>
<div class="accordion-content {{ "hidden" if hidden else "" }}">

View File

@@ -12,22 +12,19 @@
{% else %}
{% with user = get_active_user() %}
Welcome, <a href="{{ url_for("users.page", username = user.username) }}">{{user.username}}</a>
&bullet;
<a href="{{ url_for("users.settings", username = user.username) }}">Settings</a>
&bullet;
<a href="{{ url_for("users.inbox", username = user.username) }}">Inbox</a>
{% if config.DISABLE_SIGNUP and user.can_invite() %}
&bullet;
<a href="{{ url_for('users.invite_links', username=user.username )}}">Invite to {{ config.SITE_NAME }}</a>
{% endif %}
{% if not user.is_guest() %}
&bullet;
<a href="{{ url_for('users.bookmarks', username=user.username) }}">Bookmarks</a>
{% endif %}
{% if user.is_mod() %}
&bullet;
<a href="{{ url_for("mod.user_list") }}">User list</a>
{% endif %}
<ul class="horizontal">
<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>
{% 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 %}
{% if not user.is_guest() %}
<li><a href="{{ url_for('users.bookmarks', username=user.username) }}">Bookmarks</a></li>
{% endif %}
{% if user.is_mod() %}
<li><a href="{{ url_for("mod.panel") }}">Moderation</a></li>
{% endif %}
</ul>
{% endwith %}
{% endif %}
</span>

View File

@@ -4,7 +4,7 @@
{% else %}
{% set bookmark_url = url_for('api.bookmark_thread', thread_id=id) %}
{% 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">
<span>Bookmark collections</span>
{% if not require_reload %}
@@ -12,9 +12,14 @@
{% endif %}
</div>
<div class="bookmark-dropdown-items-container">
{% 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>
{% endfor %}
{%- for collection in collections -%}
{%- set pc = collection.get_posts_count() -%}
{%- 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>
<span>
<input type="text" placeholder="Memo" class="bookmark-memo-input" value="{{memo}}"></input>

View 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 %}

View 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 %}

View File

@@ -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 %}
{% extends "base.html" %}
{% block title %}browsing topic {{ topic['name'] }}{% endblock %}
@@ -24,9 +24,17 @@
</nav>
{% 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 %}
{%- with motds = get_motds() -%}
{%- if motds -%}
{%- for motd_obj in motds -%}
{{- motd(motd_obj) -}}
{%- endfor -%}
{%- endif -%}
{%- endwith -%}
{% if threads_list | length == 0 %}
<p>There are no threads in this topic.</p>
{% else %}

View File

@@ -1,5 +1,5 @@
{% from 'common/icons.html' import icn_lock %}
{% from 'common/macros.html' import timestamp %}
{% from 'common/macros.html' import timestamp, motd %}
{% extends "base.html" %}
{% block content %}
<nav class="darkbg">
@@ -9,6 +9,13 @@
<a class="linkbutton" href={{ url_for("mod.sort_topics") }}>Sort topics</a>
{% endif %}
</nav>
{%- with motds = get_motds() -%}
{%- if motds -%}
{%- for motd_obj in motds -%}
{{- motd(motd_obj) -}}
{%- endfor -%}
{%- endif -%}
{%- endwith -%}
{% if topic_list | length == 0 %}
<p>There are no topics.</p>
{% else %}

View File

@@ -1156,6 +1156,14 @@ ul, ol {
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 {
display: none;
}
@@ -1182,6 +1190,7 @@ ul, ol {
box-sizing: border-box;
border: 1px solid black;
margin: 10px 5px;
overflow: hidden;
}
.accordion.hidden {
@@ -1312,14 +1321,17 @@ footer {
border-radius: 4px;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
position: absolute;
right: 0;
margin: 0;
min-width: 400px;
max-width: 400px;
padding: 10px;
z-index: 100;
color: unset;
}
.bookmark-dropdown-item {
display: flex;
justify-content: space-between;
padding: 10px 0;
margin: 10px 0;
cursor: pointer;
@@ -1332,10 +1344,13 @@ footer {
background-color: rgb(192.6, 215.8, 214.6);
}
.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;
height: 24px;
padding: 0 10px;
flex-shrink: 0;
}
.bookmark-dropdown-item.selected {
background-color: #beb1ce;
@@ -1344,7 +1359,7 @@ footer {
background-color: rgb(203, 192.6, 215.8);
}
.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 {
@@ -1352,7 +1367,45 @@ footer {
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 {
max-height: 300px;
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;
}

View File

@@ -32,7 +32,7 @@
font-family: "Cadman";
text-decoration: none;
border: 1px solid black;
border-radius: 4px;
border-radius: 8px;
padding: 5px 20px;
margin: 10px 0;
}
@@ -574,14 +574,14 @@ pre code { /* Literal.Number.Integer.Long */ }
padding: 5px 10px;
display: inline-block;
margin: 4px;
border-radius: 4px;
border-radius: 8px;
font-size: 1rem;
white-space: pre;
}
#delete-dialog, .lightbox-dialog {
padding: 0;
border-radius: 4px;
border-radius: 8px;
border: 2px solid black;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
}
@@ -618,7 +618,7 @@ pre code { /* Literal.Number.Integer.Long */ }
blockquote {
padding: 10px 20px;
margin: 10px;
border-radius: 4px;
border-radius: 8px;
border-left: 10px solid #ae6bae;
background-color: rgba(251, 175, 207, 0.0392156863);
}
@@ -832,7 +832,7 @@ p {
input[type=text], input[type=password], textarea, select {
border: 1px solid black;
border-radius: 4px;
border-radius: 8px;
padding: 7px 10px;
width: 100%;
box-sizing: border-box;
@@ -1146,9 +1146,9 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus
background-color: #503250;
border: 1px solid black;
padding: 10px;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
}
ul, ol {
@@ -1156,6 +1156,14 @@ ul, ol {
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 {
display: none;
}
@@ -1167,7 +1175,7 @@ ul, ol {
border: 1px solid black;
background-color: #775891;
padding: 20px 15px;
border-radius: 4px;
border-radius: 8px;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
}
@@ -1177,11 +1185,12 @@ ul, ol {
}
.accordion {
border-top-right-radius: 4px;
border-top-left-radius: 4px;
border-top-right-radius: 8px;
border-top-left-radius: 8px;
box-sizing: border-box;
border: 1px solid black;
margin: 10px 5px;
overflow: hidden;
}
.accordion.hidden {
@@ -1249,7 +1258,7 @@ ul, ol {
transform: translateX(-50%);
margin: 0;
border: none;
border-radius: 4px;
border-radius: 8px;
background-color: rgba(0, 0, 0, 0.5019607843);
padding: 5px 10px;
}
@@ -1284,7 +1293,7 @@ footer {
position: relative;
margin: 0;
border: none;
border-radius: 4px;
border-radius: 8px;
background-color: rgba(0, 0, 0, 0.5019607843);
padding: 5px 10px;
width: 250px;
@@ -1307,24 +1316,27 @@ footer {
}
.bookmarks-dropdown {
background-color: #9b649b;
background-color: #503250;
border: 1px solid black;
border-radius: 4px;
border-radius: 8px;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
position: absolute;
right: 0;
margin: 0;
min-width: 400px;
max-width: 400px;
padding: 10px;
z-index: 100;
color: unset;
}
.bookmark-dropdown-item {
display: flex;
justify-content: space-between;
padding: 10px 0;
margin: 10px 0;
cursor: pointer;
border: 1px solid black;
border-radius: 4px;
border-radius: 8px;
color: #e6e6e6;
background-color: #3c283c;
}
@@ -1332,10 +1344,13 @@ footer {
background-color: rgb(109.2, 72.8, 109.2);
}
.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;
height: 24px;
padding: 0 10px;
flex-shrink: 0;
}
.bookmark-dropdown-item.selected {
background-color: #8a5584;
@@ -1344,7 +1359,7 @@ footer {
background-color: rgb(167.4843049327, 112.9156950673, 161.3067264574);
}
.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 {
@@ -1352,16 +1367,73 @@ footer {
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 {
max-height: 300px;
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 {
margin-bottom: 10px;
border: 10px solid rgb(40, 40, 40);
}
#bottomnav {
margin-top: 10px;
border: 10px solid rgb(40, 40, 40);
}
footer {
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;
}

View File

@@ -1156,6 +1156,14 @@ ul, ol {
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 {
display: none;
}
@@ -1182,6 +1190,7 @@ ul, ol {
box-sizing: border-box;
border: 1px solid black;
margin: 6px 3px;
overflow: hidden;
}
.accordion.hidden {
@@ -1312,14 +1321,17 @@ footer {
border-radius: 16px;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.25);
position: absolute;
right: 0;
margin: 0;
min-width: 400px;
max-width: 400px;
padding: 6px;
z-index: 100;
color: unset;
}
.bookmark-dropdown-item {
display: flex;
justify-content: space-between;
padding: 6px 0;
margin: 6px 0;
cursor: pointer;
@@ -1332,10 +1344,13 @@ footer {
background-color: rgb(244.6, 148.6, 123);
}
.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;
height: 24px;
padding: 0 6px;
flex-shrink: 0;
}
.bookmark-dropdown-item.selected {
background-color: #b54444;
@@ -1344,7 +1359,7 @@ footer {
background-color: rgb(197.978313253, 103.221686747, 103.221686747);
}
.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 {
@@ -1352,11 +1367,49 @@ footer {
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 {
max-height: 300px;
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 {
border-top-left-radius: 16px;
border-top-right-radius: 16px;
@@ -1378,10 +1431,3 @@ footer {
border: none;
text-align: center;
}
.darkbg {
color: white;
}
.darkbg a {
color: white;
}

View File

@@ -3,7 +3,6 @@
ta.addEventListener("keydown", (e) => {
if(e.key === "Enter" && e.ctrlKey) {
// console.log(e.target.form)
if (inThread()) {
localStorage.removeItem(window.location.pathname);
}
@@ -38,148 +37,4 @@
if (!prevContent) return;
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);
});
}

View File

@@ -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 {
async showBookmarkMenu(ev, el) {
if ((ev.target.dataset.bookmarkId === el.dataset.bookmarkId) && el.childElementCount === 0) {
const bookmarkMenuHref = `${bookmarkMenuHrefTemplate}/${ev.target.dataset.bookmarkType}?id=${ev.target.dataset.conceptId}&require_reload=${el.dataset.requireReload}`;
if ((ev.sender.dataset.bookmarkId === el.getString('bookmarkId')) && el.childElementCount === 0) {
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);
if (res.error) {
return;
@@ -11,19 +18,39 @@ export default class {
const frag = res.value;
el.appendChild(frag);
const menu = el.childNodes[0];
const bRect = el.getBoundingClientRect()
if (bRect.left < window.innerWidth - bRect.right) {
menu.style.right = 'unset';
menu.showPopover();
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) {
el.removeChild(el.childNodes[0]);
}
}
selectBookmarkCollection(ev, el) {
const clicked = ev.target;
const clicked = ev.sender;
if (clicked === el) {
if (ev.sender === el) {
if (clicked.classList.contains('selected')) {
clicked.classList.remove('selected');
} else {
@@ -35,7 +62,7 @@ export default class {
}
async saveBookmarks(ev, el) {
const bookmarkHref = el.dataset.bookmarkEndpoint;
const bookmarkHref = el.getString('bookmarkEndpoint');
const collection = el.querySelector('.bookmark-dropdown-item.selected');
let data = {};
if (collection) {
@@ -44,7 +71,7 @@ export default class {
data['memo'] = el.querySelector('.bookmark-memo-input').value;
} else {
data['operation'] = 'remove';
data['collection_id'] = el.dataset.originallyContainedIn;
data['collection_id'] = el.getString('originallyContainedIn');
}
const options = {
@@ -54,11 +81,167 @@ export default class {
'Content-Type': 'application/json',
},
}
const requireReload = parseInt(el.dataset.requireReload) !== 0;
const requireReload = el.getInt('requireReload') !== 0;
el.remove();
await fetch(bookmarkHref, options);
if (requireReload) {
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();
}
}

View File

@@ -1,13 +1,5 @@
{
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() {
return Object.hasOwn(HTMLElement.prototype, "popover");

View File

@@ -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) {
lightboxCurrentPost = post;
lightboxCurrentIdx = idx;
@@ -102,43 +79,6 @@ let lightboxCurrentPost = null;
let lightboxCurrentIdx = -1;
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
lightboxObj = constructLightbox();
document.body.appendChild(lightboxObj.dialog);
@@ -192,13 +132,4 @@ document.addEventListener("DOMContentLoaded", () => {
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)
})
};
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -475,12 +475,12 @@ $inline_code_padding: $SMALL_PADDING $MEDIUM_PADDING !default;
white-space: pre;
}
$dialogs-padding: $ZERO_PADDING !default;
$dialogs-border-radius: $DEFAULT_BORDER_RADIUS !default;
$dialogs_padding: $ZERO_PADDING !default;
$dialogs_border_radius: $DEFAULT_BORDER_RADIUS !default;
$dialog_border: 2px solid black !default;
#delete-dialog, .lightbox-dialog {
padding: $dialogs-padding;
border-radius: $dialogs-border-radius;
padding: $dialogs_padding;
border-radius: $dialogs_border_radius;
border: $dialog_border;
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 {
display: flex;
@@ -1069,6 +1073,15 @@ ul, ol {
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 {
display: none;
}
@@ -1103,7 +1116,7 @@ $accordion_margin: $MEDIUM_PADDING $SMALL_PADDING !default;
box-sizing: border-box;
border: $accordion_border;
margin: $accordion_margin;
// overflow: hidden;
overflow: hidden; // for border-radius clipping
}
.accordion.hidden {
@@ -1238,7 +1251,7 @@ $bookmarks_dropdown_background_color: $ACCENT_COLOR !default;
$bookmarks_dropdown_border_radius: $DEFAULT_BORDER_RADIUS !default;
$bookmarks_dropdown_border: $button_border !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 {
background-color: $bookmarks_dropdown_background_color;
@@ -1246,10 +1259,12 @@ $bookmarks_dropdown_padding: $MEDIUM_PADDING !default;
border-radius: $bookmarks_dropdown_border_radius;
box-shadow: $bookmarks_dropdown_shadow;
position: absolute;
right: 0;
min-width: $bookmarks_dropdown_min_width;
margin: 0;
min-width: $bookmarks_dropdown_width;
max-width: $bookmarks_dropdown_width;
padding: $bookmarks_dropdown_padding;
z-index: 100;
color: unset;
}
$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 {
display: flex;
justify-content: space-between;
padding: $bookmark_dropdown_item_padding;
margin: $bookmark_dropdown_item_margin;
cursor: pointer;
@@ -1276,11 +1292,13 @@ $bookmark_dropdown_item_icon_padding: 0 $MEDIUM_PADDING !default;
}
&::before {
// TODO: un-inline this once the bitty bug is fixed
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: $bookmark_dropdown_item_icon_size;
height: $bookmark_dropdown_item_icon_size;
padding: $bookmark_dropdown_item_icon_padding;
flex-shrink: 0;
}
&.selected {
@@ -1290,7 +1308,7 @@ $bookmark_dropdown_item_icon_padding: 0 $MEDIUM_PADDING !default;
}
&::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;
}
.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: $bookmark_dropdown_items_container_max_height;
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;
}

View File

@@ -8,6 +8,8 @@ $dark_accent: #231c23;
$warn: #eaea6a;
$crit: #d53232;
$br: 8px;
@use 'default' with (
$ACCENT_COLOR: #9b649b,
@@ -30,6 +32,8 @@ $crit: #d53232;
$BUTTON_CRITICAL_FONT_COLOR: $fc,
$ACCORDION_COLOR: #7d467d,
$DEFAULT_BORDER_RADIUS: $br,
$bottomnav_color: $dark_accent,
$topic_info_background: $dark_accent,
@@ -49,6 +53,7 @@ $crit: #d53232;
$post_content_background: $dark_accent,
$thread_info_background_color: $dark_accent,
$motd_background_color: $lightish_accent,
$post_reactions_background: $lightish_accent,
@@ -73,6 +78,8 @@ $crit: #d53232;
$tab_content_background: $lightish_accent,
$tab_button_active_color: #8a5584,
$bookmarks_dropdown_background_color: $lightish_accent,
);
#topnav {
@@ -80,6 +87,25 @@ $crit: #d53232;
border: 10px solid rgb(40, 40, 40);
}
#bottomnav {
margin-top: 10px;
border: 10px solid rgb(40, 40, 40);
}
footer {
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;
}

View File

@@ -15,6 +15,7 @@ $br: 16px;
$usercard_border: none,
$usercard_border_right: none,
$thread_locked_border: 1px solid black,
$motd_border: 1px solid black,
$SETTINGS_WIDTH: 60%,
$PAGE_SIDE_MARGIN: 50px,
@@ -87,11 +88,3 @@ footer {
border: none;
text-align: center;
}
.darkbg {
color: white;
& a {
color: white;
}
}