Merge remote-tracking branch 'origin/main'

This commit is contained in:
rimu 2025-01-22 09:00:31 +13:00
commit bb9889cb63
21 changed files with 24 additions and 676 deletions

View file

@ -420,6 +420,7 @@ def shared_inbox():
object = request_json['object']
if not 'actor' in object:
missing_actor_in_announce_object = True
log_incoming_ap(id, APLOG_ANNOUNCE, APLOG_MONITOR, request_json, 'Actor is missing in Announce object')
if not 'id' in object or not 'type' in object or not 'object' in object:
if 'type' in object and (object['type'] == 'Page' or object['type'] == 'Note'):
log_incoming_ap(id, APLOG_ANNOUNCE, APLOG_IGNORED, saved_json, 'Intended for Mastodon')
@ -541,6 +542,7 @@ def replay_inbox_request(request_json):
object = request_json['object']
if not 'actor' in object:
missing_actor_in_announce_object = True
log_incoming_ap(id, APLOG_ANNOUNCE, APLOG_MONITOR, request_json, 'REPLAY: Actor is missing in Announce object')
if not 'id' in object or not 'type' in object or not 'object' in object:
if 'type' in object and (object['type'] == 'Page' or object['type'] == 'Note'):
log_incoming_ap(id, APLOG_ANNOUNCE, APLOG_IGNORED, request_json, 'REPLAY: Intended for Mastodon')

View file

@ -1001,35 +1001,34 @@ def make_image_sizes_async(file_id, thumbnail_width, medium_width, directory, to
def find_reply_parent(in_reply_to: str) -> Tuple[int, int, int]:
parent_comment = post = None
post_id = parent_comment_id = root_id = None
# 'comment' is hint that in_reply_to was another comment
if 'comment' in in_reply_to:
parent_comment = PostReply.get_by_ap_id(in_reply_to)
if not parent_comment:
return (None, None, None)
parent_comment_id = parent_comment.id
post_id = parent_comment.post_id
root_id = parent_comment.root_id
elif 'post' in in_reply_to:
parent_comment_id = None
post = Post.get_by_ap_id(in_reply_to)
if not post:
return (None, None, None)
post_id = post.id
root_id = None
else:
parent_comment_id = None
root_id = None
post_id = None
if parent_comment:
parent_comment_id = parent_comment.id
post_id = parent_comment.post_id
root_id = parent_comment.root_id
# 'post' is hint that in_reply_to was a post
if not parent_comment and 'post' in in_reply_to:
post = Post.get_by_ap_id(in_reply_to)
if post:
post_id = post.id
# no hint in in_reply_to, or it was misleading (e.g. replies to nodebb comments have '/post/' in them)
if not parent_comment and not post:
parent_comment = PostReply.get_by_ap_id(in_reply_to)
if parent_comment:
parent_comment_id = parent_comment.id
post_id = parent_comment.post_id
root_id = parent_comment.root_id
else:
parent_comment = PostReply.get_by_ap_id(in_reply_to)
if parent_comment:
parent_comment_id = parent_comment.id
post_id = parent_comment.post_id
root_id = parent_comment.root_id
else:
return (None, None, None)
post = Post.get_by_ap_id(in_reply_to)
if post:
post_id = post.id
return post_id, parent_comment_id, root_id

View file

@ -496,79 +496,3 @@ def alpha_emoji():
return jsonify({"error": "not_yet_implemented"}), 400
# HTML routes
from flask import abort, render_template
from app.models import Community
from app.utils import current_theme
import os
@bp.route('/api/alpha/', methods=['GET'])
def get_alpha():
if not current_app.debug:
abort(404)
template_name = "index.html"
theme = current_theme()
if theme != '' and os.path.exists(f'app/templates/themes/{theme}/{template_name}'):
return render_template(f'themes/{theme}/{template_name}')
else:
return render_template(template_name)
@bp.route('/api/alpha/auth/login', methods=['GET'])
def get_alpha_auth_login():
if not current_app.debug:
abort(404)
template_name = "auth/login.html"
theme = current_theme()
if theme != '' and os.path.exists(f'app/templates/themes/{theme}/{template_name}'):
return render_template(f'themes/{theme}/{template_name}')
else:
return render_template(template_name)
@bp.route('/api/alpha/auth/logout', methods=['GET'])
def get_alpha_auth_logout():
if not current_app.debug:
abort(404)
template_name = "auth/logout.html"
theme = current_theme()
if theme != '' and os.path.exists(f'app/templates/themes/{theme}/{template_name}'):
return render_template(f'themes/{theme}/{template_name}')
else:
return render_template(template_name)
@bp.route('/api/alpha/communities', methods=['GET'])
def get_alpha_communities():
if not current_app.debug:
abort(404)
template_name = "list_communities.html"
theme = current_theme()
if theme != '' and os.path.exists(f'app/templates/themes/{theme}/{template_name}'):
return render_template(f'themes/{theme}/{template_name}')
else:
return render_template(template_name)
@bp.route('/api/alpha/c/<actor>', methods=['GET'])
def community_profile(actor):
if '@' in actor:
community = Community.query.filter_by(ap_id=actor.lower(), banned=False).first()
else:
community = Community.query.filter_by(name=actor, ap_id=None).first()
template_name = "community.html"
theme = current_theme()
if theme != '' and os.path.exists(f'app/templates/themes/{theme}/{template_name}'):
return render_template(f'themes/{theme}/{template_name}', community_id=community.id)
else:
return render_template(template_name, community_id=community.id)

View file

@ -1,38 +0,0 @@
{% extends 'themes/' + theme() + '/base.html' %}
{% block app_content %}
<p class="mb-0" id="site_request"></p>
<details><summary>JSON</summary><pre id="site_json"></pre></details>
<p class="mb-0">POST <code>/api/alpha/user/login</code></p>
<details><summary>JSON</summary><pre id="login_json"></pre></details>
<hr />
<form id="login_form">
<div class="form-floating">
<input type="text" class="form-control" id="username" placeholder="Username">
<label for="username">Username</label>
</div>
<div class="form-floating my-2">
<input type="password" class="form-control" id="password" placeholder="Password">
<label for="password">Password</label>
</div>
<div class="form-check text-start my-3">
<input class="form-check-input" type="checkbox" id="remember_me">
<label class="form-check-label" for="remember_me">Remember me</label>
</div>
<button class="btn btn-primary w-100 py-2" type="submit">Sign in</button>
</form>
<p class="mt-3">
{{ _('New User?') }} <a href="{{ url_for('auth.register') }}">{{ _('Register new account') }}</a>
</p>
<p class="mt-0">
{{ _('Forgot Your Password?') }} <a href="{{ url_for('auth.reset_password_request') }}">{{ _('Reset it') }}</a>
</p>
<script src="{{ '/static/themes/' + theme() + '/js/login.js' }}"></script>
{% endblock %}

View file

@ -1,5 +0,0 @@
{% extends 'themes/' + theme() + '/base.html' %}
{% block app_content %}
<script src="{{ '/static/themes/' + theme() + '/js/logout.js' }}"></script>
{% endblock %}

View file

@ -1,114 +0,0 @@
{% macro render_username(user, add_domain=True) -%}
<span class="render_username">
{% if user.deleted -%}
[deleted]
{% else -%}
<a href="/u/{{ user.link() }}" title="{{ user.ap_id if user.ap_id != none else user.user_name }}" aria-label="{{ _('Author') }}">
{{ user.display_name() }}{% if not user.is_local() %}<span class="text-muted">@{{ user.ap_domain }}</span>{% endif %}
</a>
{% if user.bot -%}
<span class="fe fe-bot-account" title="Bot account"> </span>
{% endif -%}
{% endif -%}
</span>
{% endmacro -%}
{% macro render_communityname(community, add_domain=True) -%}
<span class="render_community">
<a href="/c/{{ community.link() }}" aria-label="{{ _('Go to community %(name)s', name=community.name) }}">
{{ community.title }}{% if not community.is_local() %}<span class="text-muted">@{{ community.ap_domain }}</span>{% endif %}
</a>
</span>
{% endmacro -%}
<!doctype html>
<html lang="en" data-bs-theme="auto">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title id="head_title"></title>
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<link rel="manifest" href="/static/manifest.json">
<link id="icon_152" rel="apple-touch-icon" sizes="152x152" href="">
<link id="icon_32" rel="icon" type="image/png" sizes="32x32" href="">
<link id="icon_16" rel="icon" type="image/png" sizes="16x16" href="">
<link id="icon_shortcut" rel="shortcut icon" type="image/png" href="">
<link rel='icon' type="image/x-icon" href="/static/images/favicon.ico">
{{ bootstrap.load_css() }}
<link href="{{ '/static/themes/' + theme() + '/css/navbars.css' }}" rel="stylesheet">
<link href="{{ '/static/themes/' + theme() + '/css/color-modes.css' }}" rel="stylesheet">
<script src="{{ '/static/themes/' + theme() + '/js/color-modes.js' }}"></script>
</head>
<body>
<div class="dropdown position-fixed bottom-0 end-0 mb-3 me-3 bd-mode-toggle">
<button class="btn btn-bd-primary py-2 dropdown-toggle d-flex align-items-center" id="bd-theme" type="button" aria-expanded="false" data-bs-toggle="dropdown" aria-label="Toggle theme (auto)">
<svg class="bi my-1 theme-icon-active" width="1em" height="1em"><use href="{{ '/static/themes/' + theme() + '/svg/color-modes.svg#circle-half' }}"></use></svg>
<span class="visually-hidden" id="bd-theme-text">Toggle theme</span>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow" aria-labelledby="bd-theme-text">
<li>
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light" aria-pressed="false">
<svg class="bi me-2 opacity-50" width="1em" height="1em"><use href="{{ '/static/themes/' + theme() + '/svg/color-modes.svg#sun-fill' }}"></use></svg>
Light
<svg class="bi ms-auto d-none" width="1em" height="1em"><use href="{{ '/static/themes/' + theme() + '/svg/color-modes.svg#check2' }}"></use></svg>
</button>
</li>
<li>
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="dark" aria-pressed="false">
<svg class="bi me-2 opacity-50" width="1em" height="1em"><use href="{{ '/static/themes/' + theme() + '/svg/color-modes.svg#moon-stars-fill' }}"></use></svg>
Dark
<svg class="bi ms-auto d-none" width="1em" height="1em"><use href="{{ '/static/themes/' + theme() + '/svg/color-modes.svg#check2' }}"></use></svg>
</button>
</li>
<li>
<button type="button" class="dropdown-item d-flex align-items-center active" data-bs-theme-value="auto" aria-pressed="true">
<svg class="bi me-2 opacity-50" width="1em" height="1em"><use href="{{ '/static/themes/' + theme() + '/svg/color-modes.svg#circle-half' }}"></use></svg>
Auto
<svg class="bi ms-auto d-none" width="1em" height="1em"><use href="{{ '/static/themes/' + theme() + '/svg/color-modes.svg#check2' }}"></use></svg>
</button>
</li>
</ul>
</div>
<nav class="navbar navbar-expand-lg sticky-top bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="/api/alpha" id="navbar_title"></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="navbar_items">
</ul>
</div>
</div>
</nav>
<main class="container">
{% block app_content %}{% endblock %}
<hr />
</main>
<footer class="text-center">
<small>
<a href="/donate">{{ _('Donate') }}</a><br />
<a href="/about">{{ _('About') }}</a><br />
<a href="/keyboard_shortcuts">{{ _('Keyboard shortcuts') }}</a><br/>
<a href="https://codeberg.org/rimu/pyfedi">PieFed</a> is free and open source.<br />Please <a href="https://codeberg.org/rimu/pyfedi/issues">report bugs</a> or <a href="https://join.piefed.social/get-involved/">get involved</a>.<br />
<a href="/privacy">Privacy policy</a>
</small>
</footer>
{{ bootstrap.load_js() }}
<script src="{{ '/static/themes/' + theme() + '/js/site.js' }}" type="module"></script>
</body>
</html>

View file

@ -1,12 +0,0 @@
{% extends 'themes/' + theme() + '/base.html' %}
{% block app_content %}
<p class="mb-0" id="site_request"></p>
<details><summary>JSON</summary><pre id="site_json"></pre></details>
<p class="mb-0" id="community_request" data-value="{{ community_id }}">GET <code>/api/alpha/community?id={{ community_id }}</code></p>
<details><summary>JSON</summary><pre id="community_json"></pre></details>
<p class="mb-0" id="community_post_list_request">GET <code>/api/alpha/post/list?sort=Hot&page=1&community_id={{ community_id }}</code></p>
<details><summary>JSON</summary><pre id="community_post_list_json"></pre></details>
<script src="{{ '/static/themes/' + theme() + '/js/community.js' }}" type="module" data-param1="{{ community_id }}"></script>
{% endblock %}

View file

@ -1,29 +0,0 @@
.bi {
vertical-align: -.125em;
fill: currentColor;
}
.btn-bd-primary {
--bd-violet-bg: #712cf9;
--bd-violet-rgb: 112.520718, 44.062154, 249.437846;
--bs-btn-font-weight: 600;
--bs-btn-color: var(--bs-white);
--bs-btn-bg: var(--bd-violet-bg);
--bs-btn-border-color: var(--bd-violet-bg);
--bs-btn-hover-color: var(--bs-white);
--bs-btn-hover-bg: #6528e0;
--bs-btn-hover-border-color: #6528e0;
--bs-btn-focus-shadow-rgb: var(--bd-violet-rgb);
--bs-btn-active-color: var(--bs-btn-hover-color);
--bs-btn-active-bg: #5a23c8;
--bs-btn-active-border-color: #5a23c8;
}
.bd-mode-toggle {
z-index: 1500;
}
.bd-mode-toggle .dropdown-menu .active .bi {
display: block !important;
}

View file

@ -1,8 +0,0 @@
body {
padding-bottom: 20px;
}
.navbar {
margin-bottom: 20px;
}

View file

@ -1,12 +0,0 @@
{% extends 'themes/' + theme() + '/base.html' %}
{% block app_content %}
<h1>{{ _('Donate') }}</h1>
<p>PieFed is free and open-source software while operating without any advertising, monetization, or reliance on
venture capital. Your contributions are vital in supporting the PieFed development effort,
allowing us to expand and enhance PieFed with new features.</p>
<div class="btn-group btn-group-lg" role="group" aria-label="Donation options">
<a type="button" class="btn btn-outline-primary" href="https://www.patreon.com/PieFed">Donate using Patreon</a>
</div>
{% endblock %}

View file

@ -1,12 +0,0 @@
{% extends 'themes/' + theme() + '/base.html' %}
{% block app_content %}
<p class="mb-0" id="site_request"></p>
<details><summary>JSON</summary><pre id="site_json"></pre></details>
<hr />
<p class="mb-0" id="post_list_request"></p>
<details><summary>JSON</summary><pre id="post_list_json"></pre></details>
{% endblock%}

View file

@ -1,81 +0,0 @@
/*!
* Color mode toggler for Bootstrap's docs (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under the Creative Commons Attribution 3.0 Unported License.
*/
(() => {
'use strict'
const getStoredTheme = () => localStorage.getItem('theme')
const setStoredTheme = theme => localStorage.setItem('theme', theme)
const getPreferredTheme = () => {
const storedTheme = getStoredTheme()
if (storedTheme) {
return storedTheme
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
const setTheme = theme => {
if (theme === 'auto') {
document.documentElement.setAttribute('data-bs-theme', (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'))
} else {
document.documentElement.setAttribute('data-bs-theme', theme)
}
}
setTheme(getPreferredTheme())
const showActiveTheme = (theme, focus = false) => {
const themeSwitcher = document.querySelector('#bd-theme')
if (!themeSwitcher) {
return
}
const themeSwitcherText = document.querySelector('#bd-theme-text')
const activeThemeIcon = document.querySelector('.theme-icon-active use')
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
const svgOfActiveBtn = btnToActive.querySelector('svg use').getAttribute('href')
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
element.classList.remove('active')
element.setAttribute('aria-pressed', 'false')
})
btnToActive.classList.add('active')
btnToActive.setAttribute('aria-pressed', 'true')
activeThemeIcon.setAttribute('href', svgOfActiveBtn)
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`
themeSwitcher.setAttribute('aria-label', themeSwitcherLabel)
if (focus) {
themeSwitcher.focus()
}
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
const storedTheme = getStoredTheme()
if (storedTheme !== 'light' && storedTheme !== 'dark') {
setTheme(getPreferredTheme())
}
})
window.addEventListener('DOMContentLoaded', () => {
showActiveTheme(getPreferredTheme())
document.querySelectorAll('[data-bs-theme-value]')
.forEach(toggle => {
toggle.addEventListener('click', () => {
const theme = toggle.getAttribute('data-bs-theme-value')
setStoredTheme(theme)
setTheme(theme)
showActiveTheme(theme, true)
})
})
})
})()

View file

@ -1,26 +0,0 @@
const element = document.getElementById('community_request');
const community_id = element.getAttribute('data-value');
import { baseUrl } from './site.js';
const community_api = baseUrl + '/api/alpha/community?id=' + community_id;
const community_post_list_api = baseUrl + '/api/alpha/post/list?sort=Hot&page=1&community_id=' + community_id;
import { jwt } from './site.js';
if (jwt != null) {
var request = {method: "GET", headers: {Authorization: `Bearer ${jwt}`}};
} else {
var request = {method: "GET"};
}
fetch(community_api, request)
.then(response => response.json())
.then(data => {
document.querySelector('#community_json').textContent = JSON.stringify(data, null, 2);
})
fetch(community_post_list_api, request)
.then(response => response.json())
.then(data => {
document.querySelector('#community_post_list_json').textContent = JSON.stringify(data, null, 2);
})

View file

@ -1,15 +0,0 @@
import { baseUrl } from './site.js';
const api = baseUrl + '/api/alpha/community/list';
import { jwt } from './site.js';
if (jwt != null) {
var request = {method: "GET", headers: {Authorization: `Bearer ${jwt}`}};
} else {
var request = {method: "GET"};
}
fetch(api, request)
.then(response => response.json())
.then(data => {
document.querySelector('#community_list_json').textContent = JSON.stringify(data, null, 2);
})

View file

@ -1,38 +0,0 @@
document.querySelector('#login_json').textContent = '{"username_or_email": "", "password": ""}'
const login_form = document.getElementById('login_form');
const username = document.getElementById('username');
const password = document.getElementById('password');
const remember_me = document.getElementById('remember_me');
login_form.addEventListener('submit', async event => {
event.preventDefault();
json_string = JSON.stringify({ username_or_email: username.value, password: password.value })
const url = new URL(window.location.href);
const baseUrl = `${url.protocol}//${url.host}`;
const api = baseUrl + '/api/alpha/user/login';
try {
const response = await fetch(api, {method: 'POST', body: json_string});
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
const response_json = await response.json();
if (remember_me.checked == true) {
localStorage.setItem('jwt', response_json['jwt']);
} else {
sessionStorage.setItem('jwt', response_json['jwt']);
}
window.location.href = baseUrl;
} catch (error) {
console.error(error.message);
}
});

View file

@ -1,6 +0,0 @@
localStorage.removeItem('jwt');
sessionStorage.removeItem('jwt');
const url = new URL(window.location.href);
const baseUrl = `${url.protocol}//${url.host}`;
window.location.href = baseUrl;

View file

@ -1,150 +0,0 @@
const url = new URL(window.location.href);
export const baseUrl = `${url.protocol}//${url.host}`;
const api_site = baseUrl + '/api/alpha/site';
let jwt = null;
let session_jwt = sessionStorage.getItem('jwt');
if (session_jwt != null) {
jwt = session_jwt;
} else {
let local_jwt = localStorage.getItem('jwt');
if (local_jwt != null) {
jwt = local_jwt;
}
}
export { jwt };
const navbar = document.getElementById('navbar_items');
if (jwt != null) {
var request = {method: "GET", headers: {Authorization: `Bearer ${jwt}`}};
} else {
var request = {method: "GET"};
navbar.innerHTML = '<li class="nav-item"><a class="nav-link" href="/api/alpha/auth/login">Log in (via API)</a></li>' +
'<li class="nav-item dropdown">' +
'<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">' +
'Communities' +
'</a>' +
'<ul class="dropdown-menu">' +
'<li><a class="dropdown-item" href="/api/alpha/communities">All communities</a></li>' +
'</ul>' +
'</li>' +
'<li class="nav-item"><a class="nav-link" href="/user/settings">User settings</a></li>' +
'<li class="nav-item"><a class="nav-link" href="/donate">Donate</a></li>';
}
fetch(api_site, request)
.then(response => response.json())
.then(data => {
// head
document.querySelector('#head_title').textContent = data.site.name;
document.querySelector('#icon_152').href = data.site.icon_152;
document.querySelector('#icon_32').href = data.site.icon_32;
document.querySelector('#icon_16').href = data.site.icon_16;
document.querySelector('#icon_shortcut').href = data.site.icon_32;
// navbar
document.querySelector('#navbar_title').innerHTML = '<img src="' + data.site.icon + '" alt="Logo" width="36" height="36" />' + ' ' + data.site.name;
if (jwt != null) {
const all_communities_item = document.createElement('li');
all_communities_item.innerHTML = '<a class="dropdown-item" href="/api/alpha/communities">All communities</a>'
const communities_menu = document.createElement('ul');
communities_menu.className = 'dropdown-menu'
communities_menu.appendChild(all_communities_item)
if (data.my_user.moderates.length > 0) {
const dropdown_divider = document.createElement('li');
dropdown_divider.innerHTML = '<hr class="dropdown-divider">'
communities_menu.appendChild(dropdown_divider)
const dropdown_header = document.createElement('li');
dropdown_header.innerHTML = '<h6 class="dropdown-header">Moderating</h6>'
communities_menu.appendChild(dropdown_header)
for (let mods of data.my_user.moderates) {
let moderated_community_item = document.createElement('li');
if (mods.community.local) {
moderated_community_item.innerHTML = '<a class="dropdown-item" href="' + baseUrl + '/api/alpha/c/' + mods.community.name + '">' +
mods.community.title + '<span class="text-body-secondary">' + ' (' + mods.community.ap_domain + ')</span>' +
'</a>'
} else {
moderated_community_item.innerHTML = '<a class="dropdown-item" href="' + baseUrl + '/api/alpha/c/' + mods.community.name + '@' + mods.community.ap_domain + '">' +
mods.community.title + '<span class="text-body-secondary">' + ' (' + mods.community.ap_domain + ')</span>' +
'</a>'
}
communities_menu.appendChild(moderated_community_item)
}
}
if (data.my_user.follows.length > 0) {
const dropdown_divider = document.createElement('li');
dropdown_divider.innerHTML = '<hr class="dropdown-divider">'
communities_menu.appendChild(dropdown_divider)
const dropdown_header = document.createElement('li');
dropdown_header.innerHTML = '<h6 class="dropdown-header">Joined Communities</h6>'
communities_menu.appendChild(dropdown_header)
for (let follows of data.my_user.follows) {
let followed_community_item = document.createElement('li');
if (follows.community.local) {
followed_community_item.innerHTML = '<a class="dropdown-item" href="' + baseUrl + '/api/alpha/c/' + follows.community.name + '">' +
follows.community.title + '<span class="text-body-secondary">' + ' (' + follows.community.ap_domain + ')</span>' +
'</a>'
} else {
followed_community_item.innerHTML = '<a class="dropdown-item" href="' + baseUrl + '/api/alpha/c/' + follows.community.name + '@' + follows.community.ap_domain + '">' +
follows.community.title + '<span class="text-body-secondary">' + ' (' + follows.community.ap_domain + ')</span>' +
'</a>'
}
communities_menu.appendChild(followed_community_item)
}
}
const communities_item = document.createElement('li')
communities_item.className = 'nav-item dropdown'
communities_item.innerHTML = '<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">Communities</a>'
communities_item.appendChild(communities_menu)
navbar.appendChild(communities_item)
const user_settings_item = document.createElement('li')
user_settings_item.className = 'nav-item'
user_settings_item.innerHTML = '<a class="nav-link" href="/user/settings">User settings</a>';
navbar.appendChild(user_settings_item)
const logout_item = document.createElement('li')
logout_item.className = 'nav-item'
logout_item.innerHTML = '<a class="nav-link" href="/api/alpha/auth/logout">Log out (via API)</a>';
navbar.appendChild(logout_item)
}
// site info
let postlist = document.querySelector('#post_list_request')
if (jwt != null) {
document.querySelector('#site_request').innerHTML = 'GET <code>/api/alpha/site</code> [LOGGED IN]'
if (postlist) {
postlist.innerHTML = 'GET <code>/api/alpha/post/list?type_=Subscribed&sort=New&page=1</code></p>'
}
} else {
document.querySelector('#site_request').innerHTML = 'GET <code>/api/alpha/site</code> [LOGGED OUT]'
if (postlist) {
postlist.innerHTML = 'GET <code>/api/alpha/post/list?type_=Popular&sort=Hot&page=1</code></p>'
}
}
document.querySelector('#site_json').textContent = JSON.stringify(data, null, 2);
})
let postlist = document.querySelector('#post_list_request');
if (postlist) {
if (jwt != null) {
var api_postlist = baseUrl + '/api/alpha/post/list?type_=Subscribed&sort=New&page=1';
} else {
var api_postlist = baseUrl + '/api/alpha/post/list?type_=Popular&sort=Hot&page=1';
}
fetch(api_postlist, request)
.then(response => response.json())
.then(data => {
document.querySelector('#post_list_json').textContent = JSON.stringify(data, null, 2);
})
}

View file

@ -1,10 +0,0 @@
{% extends 'themes/' + theme() + '/base.html' %}
{% block app_content %}
<p class="mb-0" id="site_request"></p>
<details><summary>JSON</summary><pre id="site_json"></pre></details>
<p class="mb-0">GET <code>/api/alpha/community/list</code></p>
<details><summary>JSON</summary><pre id="community_list_json"></pre></details>
<script src="{{ '/static/themes/' + theme() + '/js/list_communities.js' }}" type="module"></script>
{% endblock %}

View file

@ -1,15 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" class="d-none">
<symbol id="check2" viewBox="0 0 16 16">
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
</symbol>
<symbol id="circle-half" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 0 8 1v14zm0 1A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"/>
</symbol>
<symbol id="moon-stars-fill" viewBox="0 0 16 16">
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
<path d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z"/>
</symbol>
<symbol id="sun-fill" viewBox="0 0 16 16">
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -1,4 +0,0 @@
{
"name": "X API",
"debug": true
}

View file

@ -1106,8 +1106,6 @@ def theme_list():
for dir in dirs:
if os.path.exists(f'app/templates/themes/{dir}/{dir}.json'):
theme_settings = json.loads(file_get_contents(f'app/templates/themes/{dir}/{dir}.json'))
if 'debug' in theme_settings and theme_settings['debug'] == True and not current_app.debug:
continue
result.append((dir, theme_settings['name']))
return result