Compare commits
9 Commits
6b7a0e7a17
...
cd3fce17ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
cd3fce17ae
|
|||
|
572e6e86c4
|
|||
|
0224e2e390
|
|||
|
27314f34a5
|
|||
|
daf205f200
|
|||
|
687d72e5ab
|
|||
|
ed395a0175
|
|||
|
160629fca7
|
|||
|
d2ea0bbd51
|
@@ -149,6 +149,13 @@ def clear_stale_sessions():
|
|||||||
for sess in stale_sessions:
|
for sess in stale_sessions:
|
||||||
sess.delete()
|
sess.delete()
|
||||||
|
|
||||||
|
def clear_api_limits():
|
||||||
|
from .db import db
|
||||||
|
from .models import APIRateLimits
|
||||||
|
with db.transaction():
|
||||||
|
limits = APIRateLimits.select()
|
||||||
|
for l in limits:
|
||||||
|
l.delete()
|
||||||
|
|
||||||
cache = Cache()
|
cache = Cache()
|
||||||
|
|
||||||
@@ -203,12 +210,14 @@ def create_app():
|
|||||||
from app.routes.guides import bp as guides_bp
|
from app.routes.guides import bp as guides_bp
|
||||||
from app.routes.mod import bp as mod_bp
|
from app.routes.mod import bp as mod_bp
|
||||||
from app.routes.posts import bp as posts_bp
|
from app.routes.posts import bp as posts_bp
|
||||||
|
from app.routes.api import bp as api_bp
|
||||||
app.register_blueprint(topics_bp)
|
app.register_blueprint(topics_bp)
|
||||||
app.register_blueprint(threads_bp)
|
app.register_blueprint(threads_bp)
|
||||||
app.register_blueprint(users_bp)
|
app.register_blueprint(users_bp)
|
||||||
app.register_blueprint(guides_bp)
|
app.register_blueprint(guides_bp)
|
||||||
app.register_blueprint(mod_bp)
|
app.register_blueprint(mod_bp)
|
||||||
app.register_blueprint(posts_bp)
|
app.register_blueprint(posts_bp)
|
||||||
|
app.register_blueprint(api_bp)
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
from .schema import create as create_tables
|
from .schema import create as create_tables
|
||||||
@@ -221,6 +230,7 @@ def create_app():
|
|||||||
create_deleted_user()
|
create_deleted_user()
|
||||||
|
|
||||||
clear_stale_sessions()
|
clear_stale_sessions()
|
||||||
|
clear_api_limits()
|
||||||
|
|
||||||
reparse_babycode()
|
reparse_babycode()
|
||||||
|
|
||||||
@@ -318,6 +328,15 @@ def create_app():
|
|||||||
else:
|
else:
|
||||||
return render_template('common/404.html'), e.code
|
return render_template('common/404.html'), e.code
|
||||||
|
|
||||||
|
@app.errorhandler(405)
|
||||||
|
def _handle_405(e):
|
||||||
|
if request.path.startswith('/hyperapi/'):
|
||||||
|
return '<h1>method not allowed</h1>', e.code
|
||||||
|
elif request.path.startswith('/api/'):
|
||||||
|
return {'error': 'method not allowed'}, e.code
|
||||||
|
else:
|
||||||
|
return render_template('common/404.html'), e.code
|
||||||
|
|
||||||
@app.errorhandler(403)
|
@app.errorhandler(403)
|
||||||
def _handle_403(e):
|
def _handle_403(e):
|
||||||
if request.path.startswith('/hyperapi/'):
|
if request.path.startswith('/hyperapi/'):
|
||||||
|
|||||||
@@ -105,6 +105,14 @@ def login_required(view_func):
|
|||||||
return view_func(*args, **kwargs)
|
return view_func(*args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
def hard_login_required(view_func):
|
||||||
|
@wraps(view_func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
if not is_logged_in():
|
||||||
|
abort(403)
|
||||||
|
return view_func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
def mod_only(view_func):
|
def mod_only(view_func):
|
||||||
@wraps(view_func)
|
@wraps(view_func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
|
|||||||
21
app/routes/api.py
Normal file
21
app/routes/api.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from flask import Blueprint, request
|
||||||
|
from ..auth import is_logged_in, hard_login_required, get_active_user
|
||||||
|
from ..lib.babycode import babycode_to_html
|
||||||
|
from ..models import APIRateLimits
|
||||||
|
|
||||||
|
bp = Blueprint('api', __name__, url_prefix='/api/')
|
||||||
|
|
||||||
|
@bp.post('/babycode-preview/')
|
||||||
|
@hard_login_required
|
||||||
|
def babycode_preview():
|
||||||
|
user = get_active_user()
|
||||||
|
if not APIRateLimits.is_allowed(user.id, 'babycode_preview', 5):
|
||||||
|
return {'error': 'too many requests'}, 429
|
||||||
|
markup = str(request.json.get('markup', ''))
|
||||||
|
if not markup:
|
||||||
|
return {'error': 'markup field missing or invalid type'}, 400
|
||||||
|
banned_tags = request.json.get('banned_tags', [])
|
||||||
|
if not isinstance(banned_tags, list):
|
||||||
|
return {'error': 'banned_tags field is invalid type'}, 400
|
||||||
|
rendered = babycode_to_html(markup, banned_tags).result
|
||||||
|
return {'html': rendered}
|
||||||
@@ -62,7 +62,7 @@ def thread(thread_id, slug):
|
|||||||
user = get_active_user()
|
user = get_active_user()
|
||||||
if user:
|
if user:
|
||||||
subscription = Subscriptions.find({'user_id': user.id, 'thread_id': thread.id})
|
subscription = Subscriptions.find({'user_id': user.id, 'thread_id': thread.id})
|
||||||
if subscription:
|
if subscription and last_post['created_at'] > int(subscription.last_seen):
|
||||||
subscription.update({'last_seen': last_post['created_at']})
|
subscription.update({'last_seen': last_post['created_at']})
|
||||||
return render_template(
|
return render_template(
|
||||||
'threads/thread.html', thread=thread,
|
'threads/thread.html', thread=thread,
|
||||||
@@ -82,13 +82,16 @@ def reply(thread_id):
|
|||||||
if not user.can_post_to_thread_or_topic(thread):
|
if not user.can_post_to_thread_or_topic(thread):
|
||||||
return redirect(url_for('.thread_by_id', thread_id=thread_id))
|
return redirect(url_for('.thread_by_id', thread_id=thread_id))
|
||||||
post = Posts.new(user.id, thread.id, request.form.get('babycode_content'))
|
post = Posts.new(user.id, thread.id, request.form.get('babycode_content'))
|
||||||
|
subscription = Subscriptions.find({'user_id': user.id, 'thread_id': thread.id})
|
||||||
if get_form_checkbox('subscribe'):
|
if get_form_checkbox('subscribe'):
|
||||||
if not Subscriptions.find({'user_id': user.id, 'thread_id': thread.id}):
|
if not subscription:
|
||||||
Subscriptions.create({
|
subscription = Subscriptions.create({
|
||||||
'user_id': user.id,
|
'user_id': user.id,
|
||||||
'thread_id': thread.id,
|
'thread_id': thread.id,
|
||||||
'last_seen': time_now(),
|
'last_seen': time_now(),
|
||||||
})
|
})
|
||||||
|
if subscription and subscription.last_seen < time_now():
|
||||||
|
subscription.update({'last_seen': time_now()})
|
||||||
return redirect(url_for('.thread_by_id', thread_id=thread_id, after=post.id, _anchor=f'post-{post.id}'))
|
return redirect(url_for('.thread_by_id', thread_id=thread_id, after=post.id, _anchor=f'post-{post.id}'))
|
||||||
|
|
||||||
@bp.get('/<int:thread_id>/edit/')
|
@bp.get('/<int:thread_id>/edit/')
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<bitty-8 data-connect="/static/js/bits/progressive-enhancement.js"></bitty-8>
|
||||||
|
<bitty-8 data-connect="/static/js/bits/ui.js"></bitty-8>
|
||||||
{%- 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 -%}
|
||||||
@@ -23,6 +25,7 @@
|
|||||||
{%- endwith -%}
|
{%- endwith -%}
|
||||||
{%- block content -%}{%- endblock -%}
|
{%- block content -%}{%- endblock -%}
|
||||||
{%- include 'common/footer.html' -%}
|
{%- include 'common/footer.html' -%}
|
||||||
|
<script type="module" src="/static/js/vnd/bitty-8.0.0.js"></script>
|
||||||
<script src="{{'/static/js/ui.js' | cachebust}}"></script>
|
<script src="{{'/static/js/ui.js' | cachebust}}"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -72,15 +72,15 @@
|
|||||||
</span>
|
</span>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
{% macro tabs(prefix='', labels = []) -%}
|
{% macro tabs(prefix='', labels=[], signal_ss=[], signal_rs=[]) -%}
|
||||||
<div class="tab-container">
|
<div class="tab-container" data-r="setTab">
|
||||||
<div class="tab-bar" role="tablist">
|
<div class="tab-bar" role="tablist">
|
||||||
{%- for tab_label in labels -%}
|
{%- for tab_label in labels -%}
|
||||||
<button type="button" class="tab-button" role="tab" aria-selected="{{'true' if loop.index0==0 else 'false'}}" id="{{prefix+'-'+(tab_label | lower)+'-tab'}}" aria-controls="{{prefix+'-'+(tab_label | lower)+'-content'}}" disabled>{{tab_label}}</button>
|
<button type="button" class="tab-button" role="tab" aria-selected="{{'true' if loop.index0==0 else 'false'}}" id="{{prefix+'-'+(tab_label | lower)+'-tab'}}" aria-controls="{{prefix+'-'+(tab_label | lower)+'-content'}}" disabled data-r="enhance" data-s="setTab {{signal_ss[loop.index0] if signal_ss[loop.index0] else ''}}" data-tab-index="{{loop.index0}}">{{tab_label}}</button>
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
</div>
|
</div>
|
||||||
{%- for tab_label in labels -%}
|
{%- for tab_label in labels -%}
|
||||||
<div class="plank secondary-bg even no-shadow tab-content {{'hidden' if loop.index0!=0 else ''}}" role="tabpanel" aria-labelledby="{{prefix+'-'+(tab_label | lower)+'-tab'}}" id="{{prefix+'-'+(tab_label | lower)+'-content'}}">
|
<div class="plank secondary-bg even no-shadow tab-content {{'hidden' if loop.index0!=0 else ''}}" role="tabpanel" aria-labelledby="{{prefix+'-'+(tab_label | lower)+'-tab'}}" id="{{prefix+'-'+(tab_label | lower)+'-content'}}" data-r="{{signal_rs[loop.index0] if signal_rs[loop.index0] else ''}}">
|
||||||
{{- caller(loop.index0) -}}
|
{{- caller(loop.index0) -}}
|
||||||
</div>
|
</div>
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
@@ -94,24 +94,25 @@
|
|||||||
id='babycode-content',
|
id='babycode-content',
|
||||||
banned_tags=[]
|
banned_tags=[]
|
||||||
) -%}
|
) -%}
|
||||||
{%- call(idx) tabs(prefix='babycode', labels=['Write', 'Preview']) -%}
|
{%- call(idx) tabs(prefix='babycode', labels=['Write', 'Preview'], signal_ss=[none, 'babycodePreviewInit'], signal_rs=[none, 'babycodePreview']) -%}
|
||||||
{%- if idx == 0 -%}
|
{%- if idx == 0 -%}
|
||||||
<span class="babycode-editor-controls">
|
<span class="babycode-editor-controls">
|
||||||
<span class="button-row">
|
<span class="button-row js-only" data-r="enhance">
|
||||||
<button type="button" class="minimal" disabled><b>B</b></button>
|
<button type="button" title="insert bold" class="minimal" data-babycode-tag="b" data-s="insertBabycode"><b>B</b></button>
|
||||||
<button type="button" class="minimal" disabled><i>i</i></button>
|
<button type="button" title="insert italic" class="minimal" data-babycode-tag="i" data-s="insertBabycode"><i>i</i></button>
|
||||||
<button type="button" class="minimal" disabled><s>S</s></button>
|
<button type="button" title="insert strikethrough" class="minimal" data-babycode-tag="s" data-s="insertBabycode"><s>S</s></button>
|
||||||
<button type="button" class="minimal" disabled><u>U</u></button>
|
<button type="button" title="insert underline" class="minimal" data-babycode-tag="u" data-s="insertBabycode"><u>U</u></button>
|
||||||
<button type="button" class="minimal" disabled><code>://</code></button>
|
<button type="button" title="insert link" class="minimal" data-babycode-tag="url" data-prefill="link label" data-s="insertBabycode"><code>://</code></button>
|
||||||
<button type="button" class="minimal" disabled><code></></code></button>
|
<button type="button" title="insert code block" class="minimal" data-babycode-tag="code" data-break-line data-s="insertBabycode"><code></></code></button>
|
||||||
<button type="button" class="minimal" disabled>1.</button>
|
<button type="button" title="insert ordered list" class="minimal" data-babycode-tag="ol" data-break-line data-s="insertBabycode">1.</button>
|
||||||
<button type="button" class="minimal" disabled>•</button>
|
<button type="button" title="insert unordered list" class="minimal" data-babycode-tag="ul" data-break-line data-s="insertBabycode">•</button>
|
||||||
<button type="button" class="minimal" disabled><img src="/static/emoji/angry.png" class="emoji"></button>
|
<button type="button" title="insert spoiler" class="minimal" data-babycode-tag="spoiler=" data-break-line data-prefill="spoiler content" data-s="insertBabycode">s</button>
|
||||||
|
<button type="button" title="insert emoji…" class="minimal"><img src="/static/emoji/angry.png" class="emoji"></button>
|
||||||
</span>
|
</span>
|
||||||
<span class="flex-last">{# stub: char count #}</span>
|
<span class="flex-last js-only" data-r="enhance babycodeEditorCharCount">stub: char count</span>
|
||||||
</span>
|
</span>
|
||||||
<input type="hidden" name="babycode_banned_tags" id="{{id}}-banned-tags" value="{{banned_tags | unique | list | tojson | forceescape}}">
|
<input type="hidden" name="babycode_banned_tags" id="{{id}}-banned-tags" value="{{banned_tags | unique | list | tojson | forceescape}}">
|
||||||
<textarea name="babycode_content" id="{{id}}" class="babycode-editor" placeholder="{{placeholder}}" {{'required' if required else ''}} autocomplete="off" maxlength="5000">{{ prefill }}</textarea>
|
<textarea name="babycode_content" id="{{id}}" class="babycode-editor" placeholder="{{placeholder}}" {{'required' if required else ''}} autocomplete="off" maxlength="5000" data-r="insertBabycode babycodePreviewInit babycodeEditorCharCountInit" data-listeners="input" data-s="babycodeEditorCharCount" data-banned-tags="{{banned_tags | unique | list | tojson | forceescape}}">{{ prefill }}</textarea>
|
||||||
{%- if banned_tags -%}
|
{%- if banned_tags -%}
|
||||||
<div>
|
<div>
|
||||||
<span>Forbidden tags:</span>
|
<span>Forbidden tags:</span>
|
||||||
@@ -123,6 +124,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
<a href="##">babycode help</a>
|
<a href="##">babycode help</a>
|
||||||
|
{%- else -%}
|
||||||
|
<div data-r="showBabycodePreview"></div>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- endcall -%}
|
{%- endcall -%}
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
@@ -183,12 +186,12 @@
|
|||||||
<a class="linkbutton" href="{{url_for('posts.edit', post_id=post.id, _anchor='babycode-content')}}">Edit</a>
|
<a class="linkbutton" href="{{url_for('posts.edit', post_id=post.id, _anchor='babycode-content')}}">Edit</a>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- if can_reply -%}
|
{%- if can_reply -%}
|
||||||
<button disabled title="This feature requires JavaScript to be enabled.">Quote</button>
|
<button data-r="enhance" disabled title="This feature requires JavaScript to be enabled.">Quote</button>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- if can_delete -%}
|
{%- if can_delete -%}
|
||||||
<a class="linkbutton critical" href="{{url_for('posts.delete', post_id=post.id)}}">Delete</a>
|
<a class="linkbutton critical" href="{{url_for('posts.delete', post_id=post.id)}}">Delete</a>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
<button disabled title="This feature requires JavaScript to be enabled.">{{icn_bookmark(24)}}Bookmark…</button>
|
<button data-r="enhance" disabled title="This feature requires JavaScript to be enabled.">{{icn_bookmark(24)}}Bookmark…</button>
|
||||||
</span>
|
</span>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</div>
|
</div>
|
||||||
@@ -213,10 +216,10 @@
|
|||||||
{% set reactors_str = reactors_str + '\n...and many others' %}
|
{% set reactors_str = reactors_str + '\n...and many others' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% set has_reacted = get_active_user() is not none and get_active_user().username in reactors %}
|
{% set has_reacted = get_active_user() is not none and get_active_user().username in reactors %}
|
||||||
<button type="button" disabled title="{{reactors_str}}" class="minimal {{'alt' if has_reacted else ''}}"><img src="/static/emoji/{{reaction.reaction_text}}.png">{{reaction.c}}</button>
|
<button data-r="enhance" type="button" disabled title="{{reactors_str}}" class="minimal {{'alt' if has_reacted else ''}}"><img src="/static/emoji/{{reaction.reaction_text}}.png">{{reaction.c}}</button>
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
</span>
|
</span>
|
||||||
{%- if is_logged_in() and allow_reacting -%}<button disabled title="This feature requires JavaScript to be enabled.">Add reaction</button>{%- endif -%}
|
{%- if is_logged_in() and allow_reacting -%}<button data-r="enhance" disabled title="This feature requires JavaScript to be enabled.">Add reaction</button>{%- endif -%}
|
||||||
{%- elif is_editing -%}
|
{%- elif is_editing -%}
|
||||||
<input type="submit" value="Save">
|
<input type="submit" value="Save">
|
||||||
<a href="{{get_post_url(post.id, _anchor=true)}}" class="linkbutton warn">Cancel</a>
|
<a href="{{get_post_url(post.id, _anchor=true)}}" class="linkbutton warn">Cancel</a>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<input type="submit" value="Clear MOTD" class="warn">
|
<input type="submit" value="Clear MOTD" class="warn">
|
||||||
</form>
|
</form>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
{{babycode_editor_component(placeholder='test', id='test-content')}}
|
||||||
<fieldset class="plank" id="sort-topics">
|
<fieldset class="plank" id="sort-topics">
|
||||||
<legend>Sort topics</legend>
|
<legend>Sort topics</legend>
|
||||||
<p>Drag topics around to reorder them. Press "Save order" when done.</p>
|
<p>Drag topics around to reorder them. Press "Save order" when done.</p>
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ a.site-title {
|
|||||||
--rotation: 180deg;
|
--rotation: 180deg;
|
||||||
padding: var(--medium-padding) var(--huge-padding);
|
padding: var(--medium-padding) var(--huge-padding);
|
||||||
|
|
||||||
background: linear-gradient(var(--rotation), var(--lighter-color) 0%, var(--main-color) 30%, var(--main-color) 70%, var(--darker-color) 100%);
|
background: linear-gradient(var(--rotation), var(--lighter-color) 0%, var(--main-color) 40px, var(--main-color) calc(100% - 40px), var(--darker-color) 100%);
|
||||||
background-color: var(--main-color);
|
background-color: var(--main-color);
|
||||||
|
|
||||||
border: 2px groove var(--border-color);
|
border: 2px groove var(--border-color);
|
||||||
@@ -905,6 +905,9 @@ ol.sortable-list {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.js-only {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
body {
|
body {
|
||||||
@@ -928,9 +931,9 @@ ol.sortable-list {
|
|||||||
min-height: 140px;
|
min-height: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.usercard-inner {
|
.usercard-inner:has(.usercard-rest) {
|
||||||
flex-direction: row;
|
display: grid;
|
||||||
justify-content: space-between;
|
grid-template-columns: min-content 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-title-counter {
|
.thread-title-counter {
|
||||||
|
|||||||
16
data/static/js/bits/progressive-enhancement.js
Normal file
16
data/static/js/bits/progressive-enhancement.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export const b = {
|
||||||
|
init: 'enhance',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enhance(_, __, el) {
|
||||||
|
if (el.classList.contains('js-only')) {
|
||||||
|
el.classList.remove('js-only');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.disabled) {
|
||||||
|
el.disabled = false;
|
||||||
|
if (el.title.search('JavaScript') !== -1) {
|
||||||
|
el.title = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
164
data/static/js/bits/ui.js
Normal file
164
data/static/js/bits/ui.js
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
export const b = {
|
||||||
|
babycodePreviewEndpoint: '/api/babycode-preview/',
|
||||||
|
init: 'babycodeEditorCharCountInit',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTab(_, sender, el) {
|
||||||
|
if (sender.ariaSelected === 'true') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!el.contains(sender)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tabIndex = parseInt(sender.dataset.tabIndex);
|
||||||
|
const tabPanels = el.querySelectorAll('.tab-content');
|
||||||
|
const tabButtons = el.querySelectorAll('.tab-bar button');
|
||||||
|
|
||||||
|
for (let i = 0; i < tabPanels.length; i++) {
|
||||||
|
const tabPanel = tabPanels[i];
|
||||||
|
const tabButton = tabButtons[i];
|
||||||
|
if (i === tabIndex) {
|
||||||
|
tabPanel.classList.remove('hidden');
|
||||||
|
tabButton.ariaSelected = 'true';
|
||||||
|
} else if (!tabPanel.classList.contains('hidden')) {
|
||||||
|
tabPanel.classList.add('hidden');
|
||||||
|
tabButton.ariaSelected = 'false';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function insertBabycode(_, sender, el) {
|
||||||
|
if (!el.parentNode.contains(sender)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagStart = sender.dataset.babycodeTag;
|
||||||
|
const breakLine = 'breakLine' in sender.dataset;
|
||||||
|
const prefill = 'prefill' in sender.dataset ? 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.substring(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();
|
||||||
|
b.send({ sender: el }, 'babycodeEditorCharCount');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function babycodeEditorCharCount(evOrPayload, sender, el) {
|
||||||
|
if (!sender) { // sent from bitty, not input
|
||||||
|
sender = evOrPayload.sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sender.parentNode.contains(el)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxLength = sender.maxLength;
|
||||||
|
const currentLength = sender.value.length;
|
||||||
|
|
||||||
|
el.innerText = `${currentLength}/${maxLength}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function babycodeEditorCharCountInit(_, __, el) {
|
||||||
|
b.send({ sender: el }, 'babycodeEditorCharCount');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function babycodePreviewInit(ev, sender, el) {
|
||||||
|
if (!sender.parentNode.parentNode.contains(el)) { // tab container > tab bar > button
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
b.send({ text: el.value, sender: sender, bannedTags: JSON.parse(el.dataset.bannedTags) }, 'babycodePreview');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function babycodePreview(payload, _, el) {
|
||||||
|
if (!payload.sender.parentNode.parentNode.contains(el)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!payload.text.trim()) {
|
||||||
|
b.send({ plain: 'Type something to get a preview.', sender: el }, 'showBabycodePreview');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
markup: payload.text,
|
||||||
|
banned_tags: payload.bannedTags,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const f = await fetch(b.babycodePreviewEndpoint, options);
|
||||||
|
try {
|
||||||
|
if (!f.ok) {
|
||||||
|
console.error(f);
|
||||||
|
let msg = '';
|
||||||
|
switch (f.status) {
|
||||||
|
case 429:
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
msg = '(Something went wrong. Try again later.)'
|
||||||
|
}
|
||||||
|
b.send({ plain: msg, sender: el }, 'showBabycodePreview');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
b.send({ ...(await f.json()), sender: el }, 'showBabycodePreview');
|
||||||
|
} catch (error) {
|
||||||
|
b.send({ plain: '(Something went wrong. Try again later.)', sender: el }, 'showBabycodePreview');
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showBabycodePreview(payload, _, el) {
|
||||||
|
if (!payload.sender.parentNode.contains(el)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (payload.plain) {
|
||||||
|
el.innerHTML = `<p>${payload.plain}</p>`;
|
||||||
|
} else {
|
||||||
|
el.innerHTML = payload.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
data/static/js/vnd/bitty-8.0.0.js
Normal file
1
data/static/js/vnd/bitty-8.0.0.js
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user