added /manage/ and /manage/accounts/ views, added TOC to template
This commit is contained in:
parent
4536f7fbd9
commit
191e235137
9 changed files with 162 additions and 21 deletions
|
|
@ -10,6 +10,9 @@
|
|||
caution message when viewing them.
|
||||
+ Added Terms and Privacy Policy.
|
||||
+ Changed user page URLs (contributions page) from `/u/user` to `/@user`.
|
||||
+ `/manage/` is now a list of all managing options, including export/import and the brand new
|
||||
`/manage/accounts`.
|
||||
+ TOC is now shown in pages where screen width is greater than 960 pixels.
|
||||
+ Style changes: added a top bar with the site title. It replaces the floating menu on the top right.
|
||||
+ Added a built-in installer (`app_init.py`). You still need to manually create `site.conf`.
|
||||
|
||||
|
|
|
|||
62
app.py
62
app.py
|
|
@ -161,6 +161,12 @@ class User(BaseModel):
|
|||
def is_authenticated(self):
|
||||
return True
|
||||
|
||||
def groups(self):
|
||||
return (
|
||||
UserGroup.select().join(UserGroupMembership, on=UserGroupMembership.group)
|
||||
.where(UserGroupMembership.user == self)
|
||||
)
|
||||
|
||||
class UserGroup(BaseModel):
|
||||
name = CharField(32, unique=True)
|
||||
permissions = BitField()
|
||||
|
|
@ -295,6 +301,8 @@ class PageRevision(BaseModel):
|
|||
return self.textref.get_content()
|
||||
def html(self, *, math=True):
|
||||
return md(self.text, math=self.page.is_math_enabled and math)
|
||||
def html_and_toc(self, *, math=True):
|
||||
return md_and_toc(self.text, math=self.page.is_math_enabled and math)
|
||||
def human_pub_date(self):
|
||||
delta = datetime.datetime.now() - self.pub_date
|
||||
T = partial(get_string, g.lang)
|
||||
|
|
@ -484,7 +492,7 @@ def init_db_and_create_first_user():
|
|||
|
||||
#### WIKI SYNTAX ####
|
||||
|
||||
def md(text, expand_magic=False, toc=True, math=True):
|
||||
def md_and_toc(text, expand_magic=False, toc=True, math=True):
|
||||
if expand_magic:
|
||||
# DEPRECATED seeking for a better solution.
|
||||
warnings.warn('Magic words are no more supported.', DeprecationWarning)
|
||||
|
|
@ -502,9 +510,16 @@ def md(text, expand_magic=False, toc=True, math=True):
|
|||
'insert_fonts_css': not _getconf('site', 'katex_url')
|
||||
}
|
||||
try:
|
||||
return markdown.Markdown(extensions=extensions, extension_configs=extension_configs).convert(text)
|
||||
converter = markdown.Markdown(extensions=extensions, extension_configs=extension_configs)
|
||||
if toc:
|
||||
return converter.convert(text), converter.toc
|
||||
else:
|
||||
return converter.convert(text), ''
|
||||
except Exception as e:
|
||||
return '<p class="error">There was an error during rendering: {e.__class__.__name__}: {e}</p>'.format(e=e)
|
||||
return '<p class="error">There was an error during rendering: {e.__class__.__name__}: {e}</p>'.format(e=e), ''
|
||||
|
||||
def md(text, expand_magic=False, toc=True, math=True):
|
||||
return md_and_toc(text, expand_magic=expand_magic, toc=toc, math=math)[0]
|
||||
|
||||
def remove_tags(text, convert=True, headings=True):
|
||||
if headings:
|
||||
|
|
@ -538,7 +553,7 @@ def is_url_available(url):
|
|||
|
||||
forbidden_urls = [
|
||||
'about', 'accounts', 'ajax', 'backlinks', 'calendar', 'circles', 'create',
|
||||
'easter', 'edit', 'embed', 'help', 'history', 'init-config', 'kt',
|
||||
'easter', 'edit', 'embed', 'group', 'help', 'history', 'init-config', 'kt',
|
||||
'manage', 'media', 'p', 'privacy', 'protect', 'search', 'static', 'stats',
|
||||
'tags', 'terms', 'u', 'upload', 'upload-info'
|
||||
]
|
||||
|
|
@ -783,11 +798,7 @@ def edit(id):
|
|||
|
||||
@app.route("/__sync_start")
|
||||
def __sync_start():
|
||||
if _getconf("sync", "master", "this") == "this":
|
||||
abort(403)
|
||||
from app_sync import main
|
||||
main()
|
||||
flash("Successfully synced messages.")
|
||||
flash("Sync is unavailable. Please import and export pages manually.")
|
||||
return redirect("/")
|
||||
|
||||
@app.route('/_jsoninfo/<int:id>', methods=['GET', 'POST'])
|
||||
|
|
@ -895,7 +906,15 @@ def contributions(username):
|
|||
user = User.get(User.username == username)
|
||||
except User.DoesNotExist:
|
||||
abort(404)
|
||||
return render_template('contributions.jinja2', u=user, contributions=user.contributions.order_by(PageRevision.pub_date.desc()))
|
||||
contributions = user.contributions.order_by(PageRevision.pub_date.desc())
|
||||
page = int(request.args.get('page', 1))
|
||||
return render_template('contributions.jinja2',
|
||||
u=user,
|
||||
contributions=contributions.paginate(page),
|
||||
page_n=page,
|
||||
total_count=contributions.count(),
|
||||
min=min
|
||||
)
|
||||
|
||||
@app.route('/calendar/')
|
||||
def calendar_view():
|
||||
|
|
@ -1083,6 +1102,12 @@ def easter_y(y=None):
|
|||
else:
|
||||
return render_template('easter.jinja2')
|
||||
|
||||
## administration ##
|
||||
|
||||
@app.route('/manage/')
|
||||
def manage_main():
|
||||
return render_template('administration.jinja2')
|
||||
|
||||
## import / export ##
|
||||
|
||||
class Exporter(object):
|
||||
|
|
@ -1214,6 +1239,23 @@ def importpages():
|
|||
flash('Pages can be imported by Administrators only!')
|
||||
return render_template('importpages.jinja2')
|
||||
|
||||
@app.route('/manage/accounts/', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def manage_accounts():
|
||||
users = User.select().order_by(User.join_date.desc())
|
||||
page = int(request.args.get('page', 1))
|
||||
if request.method == 'POST':
|
||||
if current_user.is_admin:
|
||||
pass
|
||||
else:
|
||||
flash('Operation not permitted!')
|
||||
return render_template('manageaccounts.jinja2',
|
||||
users=users.paginate(page),
|
||||
page_n=page,
|
||||
total_count=users.count(),
|
||||
min=min
|
||||
)
|
||||
|
||||
## terms / privacy ##
|
||||
|
||||
@app.route('/terms/')
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
/* basic styles */
|
||||
* {box-sizing: border-box;}
|
||||
body{font-family:sans-serif;background-color:var(--bg-main); color: var(--fg-main);margin:0;position:relative}
|
||||
.content{margin: 3em 1.3em}
|
||||
.content{margin: 3em 1.3em; position: relative}
|
||||
.footer{text-align:center;}
|
||||
|
||||
/* header styles */
|
||||
|
|
@ -112,9 +112,10 @@ ul.inline > li:last-child::after {content: ""}
|
|||
|
||||
|
||||
/* floating elements */
|
||||
.toc{float:right}
|
||||
@media (max-width:639px){
|
||||
.toc{display:none}
|
||||
nav.toc{display:none}
|
||||
@media only screen and (min-width:960px){
|
||||
nav.toc{display:block;position:absolute; top: 0; right: 0; width: 320px;}
|
||||
.inner-content {margin-right: 320px}
|
||||
}
|
||||
.backontop{position:fixed;bottom:0;right:0}
|
||||
@media print{
|
||||
|
|
|
|||
23
templates/administration.jinja2
Normal file
23
templates/administration.jinja2
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{% extends "base.jinja2" %}
|
||||
|
||||
{% block title %}Administrative tools — {{ app_name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Administrative tools</h1>
|
||||
|
||||
{% if current_user and current_user.is_admin %}
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/manage/export">Export pages</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/manage/import">Import pages</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/manage/accounts">Manage accounts</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>Administrative tools can be accessed by administrator users only.</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
@ -52,6 +52,7 @@
|
|||
<div class="flash">{{ msg }}</div>
|
||||
{% endfor %}
|
||||
{% block content %}{% endblock %}
|
||||
{% block toc %}{% endblock %}
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<div class="footer-copyright">© 2020–2023 Sakuragasaki46.</div>
|
||||
|
|
|
|||
|
|
@ -7,9 +7,16 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Contributions of {{ u.username }}</div>
|
||||
<h1>Contributions of {{ u.username }}</h1>
|
||||
|
||||
<p class="nl-pagination">Showing results <strong>{{ page_n * 20 - 19 }}</strong> to <strong>{{ min(page_n * 20, total_count) }}</strong> of <strong>{{ total_count }}</strong> total.</p>
|
||||
|
||||
<ul>
|
||||
{% if page_n > 1 %}
|
||||
<li class="nl-prev"><a href="?page={{ page_n - 1}}">« Previous page</a></li>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% for rev in contributions %}
|
||||
<li>
|
||||
<a href="/history/revision/{{ rev.id }}/">
|
||||
|
|
@ -26,5 +33,9 @@
|
|||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% if page_n < total_count // 20 %}
|
||||
<li class="nl-next"><a href="?page={{ page_n + 1 }}">Next page »</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
|
|
|||
52
templates/manageaccounts.jinja2
Normal file
52
templates/manageaccounts.jinja2
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{% extends "base.jinja2" %}
|
||||
|
||||
{% block title %}Manage accounts - {{ app_name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Manage accounts</h1>
|
||||
|
||||
{% if current_user.is_admin %}
|
||||
<p>
|
||||
Here is the list of users registered on {{ app_name }}, in reverse chronological order.
|
||||
<strong>Beware: you are managing sensitive informations.</strong>
|
||||
</p>
|
||||
|
||||
<p class="nl-pagination">Showing results <strong>{{ page_n * 20 - 19 }}</strong> to <strong>{{ min(page_n * 20, total_count) }}</strong> of <strong>{{ total_count }}</strong> total.</p>
|
||||
|
||||
<form enctype="multipart/form-data" method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
|
||||
<ul>
|
||||
{% if page_n > 1 %}
|
||||
<li class="nl-prev"><a href="?page={{ page_n - 1}}">« Previous page</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% for u in users %}
|
||||
<li>
|
||||
<input type="checkbox" name="u{{ u.id }}">
|
||||
<a href="/@{{ u.username }}">{{ u.username }}</a>
|
||||
{% if u == current_user %}<strong>(you)</strong>{% endif %}
|
||||
-
|
||||
Groups:
|
||||
<ul class="inline">
|
||||
{% for ug in u.groups() %}
|
||||
<li>{{ ug.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
-
|
||||
Registered on:
|
||||
{{ u.join_date }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% if page_n < total_count // 20 %}
|
||||
<li class="nl-next"><a href="?page={{ page_n + 1 }}">Next page »</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
|
||||
</form>
|
||||
{% else %}
|
||||
<p>Managing accounts can be done by users with Admin permissions only.</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "base.jinja2" %}
|
||||
|
||||
{% block title %}Not found - {{ app_name }}{% endblock %}
|
||||
{% block title %}{{ T('not-found') }} - {{ app_name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ T('not-found') }}</h1>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@
|
|||
|
||||
{% block json_info %}<script>window.page_info={{ p.js_info()|tojson|safe }};</script>{% endblock %}
|
||||
|
||||
{% set html_and_toc = rev.html_and_toc(math = request.args.get('math') not in ['0', 'false', 'no', 'off']) %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h1 id="firstHeading">{{ p.title }}</h1>
|
||||
|
||||
{% if p.calendar %}
|
||||
<p class="calendar-subtitle"><span class="material-icons">calendar_today</span>{{ p.calendar.strftime('%B %-d, %Y') }}</div>
|
||||
<p class="calendar-subtitle"><span class="material-icons">calendar_today</span>{{ p.calendar.strftime('%B %-d, %Y') }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="jump-to-actions">
|
||||
|
|
@ -28,7 +30,7 @@
|
|||
{% endif %}
|
||||
|
||||
<div class="inner-content">
|
||||
{{ rev.html(math = request.args.get('math') not in ['0', 'false', 'no', 'off'])|safe }}
|
||||
{{ html_and_toc[0]|safe }}
|
||||
</div>
|
||||
|
||||
{% if p.tags %}
|
||||
|
|
@ -54,6 +56,12 @@
|
|||
{{ T('owner') }}: {{ p.owner.username }}
|
||||
{% endblock %}
|
||||
|
||||
{% block toc %}
|
||||
<nav class="toc">
|
||||
{{ html_and_toc[1] }}
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="/static/content.js"></script>
|
||||
{% endblock %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue