code cleanup + aesthetic changes
This commit is contained in:
parent
3c6d52ed28
commit
b5112c6565
16 changed files with 46 additions and 245 deletions
|
|
@ -1,5 +1,11 @@
|
||||||
# What’s New
|
# What’s New
|
||||||
|
|
||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
+ **Deprecated** several configuration values ~
|
||||||
|
+ Removed permanently the remains of extensions.
|
||||||
|
+ I18n improvements.
|
||||||
|
|
||||||
## 1.0.0
|
## 1.0.0
|
||||||
|
|
||||||
+ **BREAKING CHANGES AHEAD**!
|
+ **BREAKING CHANGES AHEAD**!
|
||||||
|
|
@ -11,7 +17,7 @@
|
||||||
+ Switched to `pyproject.toml`. `requirements.txt` has been sunset.
|
+ Switched to `pyproject.toml`. `requirements.txt` has been sunset.
|
||||||
+ Switched to the Apache License; the old license text is moved to `LICENSE.0_9`
|
+ Switched to the Apache License; the old license text is moved to `LICENSE.0_9`
|
||||||
+ Added color themes! This is a breaking (but trivial) aesthetic change. Default theme is 'Miku' (aquamarine green).
|
+ Added color themes! This is a breaking (but trivial) aesthetic change. Default theme is 'Miku' (aquamarine green).
|
||||||
+ Extensions have been removed. They never had a clear, usable, public API in the first place.
|
+ Extensions **have been removed**. They never had a clear, usable, public API in the first place.
|
||||||
|
|
||||||
## 0.9
|
## 0.9
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
"back-to": "Back to",
|
"back-to": "Back to",
|
||||||
"backlinks": "Backlinks",
|
"backlinks": "Backlinks",
|
||||||
"backlinks-empty": "No other pages linking here. Is this page orphan?",
|
"backlinks-empty": "No other pages linking here. Is this page orphan?",
|
||||||
|
"bad-request": "Bad request",
|
||||||
"calculate": "Calculate",
|
"calculate": "Calculate",
|
||||||
"calendar": "Calendar",
|
"calendar": "Calendar",
|
||||||
"confirm-password": "Confirm password",
|
"confirm-password": "Confirm password",
|
||||||
|
|
@ -18,6 +19,8 @@
|
||||||
"groups-count": "User group count",
|
"groups-count": "User group count",
|
||||||
"have-read-terms": "I have read {0} and {1}",
|
"have-read-terms": "I have read {0} and {1}",
|
||||||
"homepage": "Homepage",
|
"homepage": "Homepage",
|
||||||
|
"in-the-future": "in the future",
|
||||||
|
"in-the-past": "in the past",
|
||||||
"include-tags": "Include tags",
|
"include-tags": "Include tags",
|
||||||
"input-tags": "Tags (comma separated)",
|
"input-tags": "Tags (comma separated)",
|
||||||
"jump-to-actions": "Jump to actions",
|
"jump-to-actions": "Jump to actions",
|
||||||
|
|
@ -27,6 +30,7 @@
|
||||||
"latest-uploads": "Latest uploads",
|
"latest-uploads": "Latest uploads",
|
||||||
"logged-in-as": "Logged in as",
|
"logged-in-as": "Logged in as",
|
||||||
"login": "Log in",
|
"login": "Log in",
|
||||||
|
"manage-accounts": "Manage accounts",
|
||||||
"month": "Month",
|
"month": "Month",
|
||||||
"n-days-ago": "{0} days ago",
|
"n-days-ago": "{0} days ago",
|
||||||
"n-hours-ago": "{0} hours ago",
|
"n-hours-ago": "{0} hours ago",
|
||||||
|
|
@ -40,6 +44,7 @@
|
||||||
"not-found-text-2": "does not exist.",
|
"not-found-text-2": "does not exist.",
|
||||||
"not-logged-in": "Not logged in",
|
"not-logged-in": "Not logged in",
|
||||||
"note-history": "Page history",
|
"note-history": "Page history",
|
||||||
|
"notes-by-date": "Pages by date",
|
||||||
"notes-count": "Number of pages",
|
"notes-count": "Number of pages",
|
||||||
"notes-count-with-url": "Number of pages with URL set",
|
"notes-count-with-url": "Number of pages with URL set",
|
||||||
"notes-month-empty": "None found :(",
|
"notes-month-empty": "None found :(",
|
||||||
|
|
@ -62,10 +67,13 @@
|
||||||
"search-results": "Search results for",
|
"search-results": "Search results for",
|
||||||
"search-no-results": "No results for",
|
"search-no-results": "No results for",
|
||||||
"show-all": "Show all",
|
"show-all": "Show all",
|
||||||
|
"show-more-years": "Show more years",
|
||||||
"sign-up": "Sign up",
|
"sign-up": "Sign up",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
"terms-of-service": "Terms of Service",
|
"terms-of-service": "Terms of Service",
|
||||||
"upload-file": "Upload file",
|
"upload-file": "Upload file",
|
||||||
|
"user-generated-warning": "Any content in this site is user-generated and",
|
||||||
|
"user-liability-warning": "each user is responsible for what they publish",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"users-count": "Number of users",
|
"users-count": "Number of users",
|
||||||
"welcome": "Welcome to {0}!",
|
"welcome": "Welcome to {0}!",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
"back-to": "Torna a",
|
"back-to": "Torna a",
|
||||||
"backlinks": "Collegamenti in entrata",
|
"backlinks": "Collegamenti in entrata",
|
||||||
"backlinks-empty": "Nessuna altra pagina punta qui. Questa pagina è orfana?",
|
"backlinks-empty": "Nessuna altra pagina punta qui. Questa pagina è orfana?",
|
||||||
|
"bad-request": "Richiesta non conforme",
|
||||||
"calculate": "Calcola",
|
"calculate": "Calcola",
|
||||||
"calendar": "Calendario",
|
"calendar": "Calendario",
|
||||||
"confirm-password": "Conferma password",
|
"confirm-password": "Conferma password",
|
||||||
|
|
|
||||||
|
|
@ -8,27 +8,21 @@ Pages are stored in SQLite/MySQL databases.
|
||||||
Markdown is used for text formatting.
|
Markdown is used for text formatting.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
__version__ = '1.0.0'
|
__version__ = '1.1.0-dev36'
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
Flask, abort, flash, g, jsonify, make_response, redirect,
|
Flask, g, make_response, redirect,
|
||||||
request, render_template, send_from_directory
|
request, render_template, send_from_directory
|
||||||
)
|
)
|
||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
from flask_login import LoginManager, login_user, logout_user, current_user, login_required
|
from flask_login import LoginManager
|
||||||
from flask_wtf import CSRFProtect
|
from flask_wtf import CSRFProtect
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from suou.configparse import ConfigOptions, ConfigParserConfigSource, ConfigValue
|
from suou.configparse import ConfigOptions, ConfigParserConfigSource, ConfigValue
|
||||||
from suou.flask import add_context_from_config
|
from suou.flask import add_context_from_config
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
|
||||||
from werkzeug.routing import BaseConverter
|
from werkzeug.routing import BaseConverter
|
||||||
import datetime, hashlib, html, importlib, json, markdown, os, random, \
|
import html, os
|
||||||
re, sys, warnings
|
from functools import partial
|
||||||
from functools import lru_cache, partial
|
|
||||||
from urllib.parse import quote
|
|
||||||
import gzip
|
|
||||||
from getpass import getpass
|
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
import dotenv
|
import dotenv
|
||||||
|
|
||||||
|
|
@ -40,11 +34,13 @@ dotenv.load_dotenv(os.path.join(APP_BASE_DIR, '.env'))
|
||||||
|
|
||||||
class SiteConfig(ConfigOptions):
|
class SiteConfig(ConfigOptions):
|
||||||
app_name = ConfigValue(default='Salvi', legacy_src='site.title', public=True)
|
app_name = ConfigValue(default='Salvi', legacy_src='site.title', public=True)
|
||||||
|
## v--- will change to server_name in 2.0.0
|
||||||
domain_name = ConfigValue(public=True)
|
domain_name = ConfigValue(public=True)
|
||||||
database_url = ConfigValue(legacy_src='database.url', required=True)
|
database_url = ConfigValue(legacy_src='database.url', required=True)
|
||||||
secret_key = ConfigValue(required=True)
|
secret_key = ConfigValue(required=True)
|
||||||
default_group = ConfigValue(prefix='salvi_', cast=int, default=1)
|
default_group = ConfigValue(prefix='salvi_', cast=int, default=1)
|
||||||
material_icons_url = ConfigValue(default='https://fonts.googleapis.com/icon?family=Material+Icons', public=True)
|
material_icons_url = ConfigValue(default='https://fonts.googleapis.com/icon?family=Material+Icons', public=True)
|
||||||
|
# v--- pending deprecation
|
||||||
default_items_per_page = ConfigValue(prefix='salvi_', legacy_src='appearance.items_per_page', default=20, cast=int)
|
default_items_per_page = ConfigValue(prefix='salvi_', legacy_src='appearance.items_per_page', default=20, cast=int)
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ PING_RE = r'(?<!\w)@(' + USERNAME_RE + r')'
|
||||||
|
|
||||||
FORBIDDEN_URLS = [
|
FORBIDDEN_URLS = [
|
||||||
'about', 'accounts', 'ajax', 'backlinks', 'calendar', 'circles', 'create',
|
'about', 'accounts', 'ajax', 'backlinks', 'calendar', 'circles', 'create',
|
||||||
'easter', 'edit', 'embed', 'group', 'help', 'history', 'init-config', 'kt',
|
'easter', 'edit', 'embed', 'group', 'help', 'history', 'init-config',
|
||||||
'manage', 'media', 'p', 'privacy', 'protect', 'rules', 'search', 'static',
|
'manage', 'media', 'p', 'privacy', 'protect', 'rules', 'search', 'static',
|
||||||
'stats', 'tags', 'terms', 'theme-switch', 'u', 'upload', 'upload-info', 'v1'
|
'stats', 'tags', 'terms', 'theme-switch', 'timeline', 'u', 'upload', 'upload-info', 'v1'
|
||||||
]
|
]
|
||||||
|
|
@ -17,7 +17,7 @@ from flask import abort, flash
|
||||||
from flask_login import AnonymousUserMixin, current_user, login_required
|
from flask_login import AnonymousUserMixin, current_user, login_required
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
from sqlalchemy.orm import Relationship, declarative_base, deferred, relationship
|
from sqlalchemy.orm import Mapped, Relationship, declarative_base, deferred, relationship
|
||||||
from sqlalchemy import Column, Integer, String, DateTime, BigInteger, ForeignKey, UniqueConstraint, create_engine, Index, BLOB as Blob, delete, func, insert, or_, select, update
|
from sqlalchemy import Column, Integer, String, DateTime, BigInteger, ForeignKey, UniqueConstraint, create_engine, Index, BLOB as Blob, delete, func, insert, or_, select, update
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
|
|
@ -33,59 +33,8 @@ from .i18n import human_elapsed_time
|
||||||
|
|
||||||
current_user: User
|
current_user: User
|
||||||
|
|
||||||
## TODO consider moving it to suou.
|
|
||||||
## TODO consider sqlalchemy.ext.hybrid.hybrid_property works
|
|
||||||
from sqlalchemy.ext.hybrid import Comparator
|
|
||||||
|
|
||||||
class _BitComparator(Comparator):
|
|
||||||
_column: Column
|
|
||||||
_flag: int
|
|
||||||
def __init__(self, col, flag):
|
|
||||||
self._column = col
|
|
||||||
self._flag = flag
|
|
||||||
def _bulk_update_tuples(self, value):
|
|
||||||
return [ (self._column, self._upd_exp(value)) ]
|
|
||||||
def operate(self, op, other, **kwargs):
|
|
||||||
return op(self._sel_exp(), self._flag if other else 0, **kwargs)
|
|
||||||
def __clause_element__(self):
|
|
||||||
return self._column
|
|
||||||
def __str__(self):
|
|
||||||
return self._column
|
|
||||||
def _sel_exp(self):
|
|
||||||
return self._column.op('&')(self._flag)
|
|
||||||
def _upd_exp(self, value):
|
|
||||||
return self._column.op('|')(self._flag) if value else self._column.op('&')(~self._flag)
|
|
||||||
|
|
||||||
class BitSelector:
|
|
||||||
_column: Column
|
|
||||||
_flag: int
|
|
||||||
_name: str
|
|
||||||
def __init__(self, column, flag: int):
|
|
||||||
if bin(flag := int(flag))[2:].rstrip('0') != '1':
|
|
||||||
warnings.warn('using non-powers of 2 as flags may cause errors or undefined behavior', FutureWarning)
|
|
||||||
self._column = column
|
|
||||||
self._flag = flag
|
|
||||||
def __set_name__(self, name, owner=None):
|
|
||||||
self._name = name
|
|
||||||
def __get__(self, obj, objtype=None):
|
|
||||||
if obj:
|
|
||||||
return getattr(obj, self._column.name) & self._flag > 0
|
|
||||||
else:
|
|
||||||
return _BitComparator(self._column, self._flag)
|
|
||||||
def __set__(self, obj, val):
|
|
||||||
if obj:
|
|
||||||
orig = getattr(obj, self._column.name)
|
|
||||||
if val:
|
|
||||||
orig |= self._flag
|
|
||||||
else:
|
|
||||||
orig &= ~(self._flag)
|
|
||||||
setattr(obj, self._column.name, orig)
|
|
||||||
else:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
# Helper for interactive session management
|
# Helper for interactive session management
|
||||||
from suou.sqlalchemy import create_session, declarative_base
|
from suou.sqlalchemy import create_session, declarative_base, BitSelector
|
||||||
|
|
||||||
CSI = create_session_interactively = partial(create_session, app_config.database_url)
|
CSI = create_session_interactively = partial(create_session, app_config.database_url)
|
||||||
|
|
||||||
|
|
@ -221,11 +170,11 @@ class Page(db.Model):
|
||||||
calendar = Column(DateTime, index=True, nullable=True)
|
calendar = Column(DateTime, index=True, nullable=True)
|
||||||
owner_id = Column(Integer, ForeignKey('user.id'), nullable=True)
|
owner_id = Column(Integer, ForeignKey('user.id'), nullable=True)
|
||||||
flags = Column(BigInteger, default=0)
|
flags = Column(BigInteger, default=0)
|
||||||
is_redirect = BitSelector(flags, 1)
|
is_redirect: Mapped[bool] = BitSelector(flags, 1)
|
||||||
is_sync = BitSelector(flags, 2)
|
is_sync: Mapped[bool] = BitSelector(flags, 2)
|
||||||
is_math_enabled = BitSelector(flags, 4)
|
is_math_enabled: Mapped[bool] = BitSelector(flags, 4)
|
||||||
is_locked = BitSelector(flags, 8)
|
is_locked: Mapped[bool] = BitSelector(flags, 8)
|
||||||
is_cw = BitSelector(flags, 16)
|
is_cw: Mapped[bool] = BitSelector(flags, 16)
|
||||||
|
|
||||||
revisions: Relationship[List[PageRevision]] = relationship("PageRevision", back_populates = 'page')
|
revisions: Relationship[List[PageRevision]] = relationship("PageRevision", back_populates = 'page')
|
||||||
owner: Relationship[User] = relationship('User', back_populates = 'owned_pages')
|
owner: Relationship[User] = relationship('User', back_populates = 'owned_pages')
|
||||||
|
|
@ -237,7 +186,7 @@ class Page(db.Model):
|
||||||
|
|
||||||
def latest(self) -> PageRevision:
|
def latest(self) -> PageRevision:
|
||||||
return db.session.execute(
|
return db.session.execute(
|
||||||
db.select(PageRevision).where(PageRevision.page_id == self.id).order_by(PageRevision.pub_date.desc()).limit(1)
|
select(PageRevision).where(PageRevision.page_id == self.id).order_by(PageRevision.pub_date.desc()).limit(1)
|
||||||
).scalar()
|
).scalar()
|
||||||
|
|
||||||
def get_url(self):
|
def get_url(self):
|
||||||
|
|
@ -247,6 +196,7 @@ class Page(db.Model):
|
||||||
def by_url(cls, url: str):
|
def by_url(cls, url: str):
|
||||||
return db.session.execute(db.select(Page).where(Page.url == url)).scalar()
|
return db.session.execute(db.select(Page).where(Page.url == url)).scalar()
|
||||||
|
|
||||||
|
@deprecated('usage of inefficient and deprecated remove_tags()')
|
||||||
def short_desc(self) -> str:
|
def short_desc(self) -> str:
|
||||||
if self.is_cw:
|
if self.is_cw:
|
||||||
return '(Content Warning: we are not allowed to show a description.)'
|
return '(Content Warning: we are not allowed to show a description.)'
|
||||||
|
|
@ -298,10 +248,6 @@ class Page(db.Model):
|
||||||
tags = [x.name for x in self.tags]
|
tags = [x.name for x in self.tags]
|
||||||
)
|
)
|
||||||
|
|
||||||
@not_implemented
|
|
||||||
def ldjson():
|
|
||||||
...
|
|
||||||
|
|
||||||
@deprecated('meta name="keywords" is nowadays ignored by search engines')
|
@deprecated('meta name="keywords" is nowadays ignored by search engines')
|
||||||
def seo_keywords(self):
|
def seo_keywords(self):
|
||||||
kw = []
|
kw = []
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ def md_and_toc(text) -> tuple[Markup, Any | None]:
|
||||||
try:
|
try:
|
||||||
converter: markdown.Markdown = markdown.Markdown(extensions=extensions, extension_configs=extension_configs)
|
converter: markdown.Markdown = markdown.Markdown(extensions=extensions, extension_configs=extension_configs)
|
||||||
markup = Markup(converter.convert(text))
|
markup = Markup(converter.convert(text))
|
||||||
toc = converter.toc
|
toc = Markup(converter.toc)
|
||||||
return markup, toc
|
return markup, toc
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return error_p('Error during rendering: {0}: {1}')
|
return error_p('Error during rendering: {0}: {1}')
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% from "macros/title.html" import title_tag with context %}
|
{% from "macros/title.html" import title_tag with context %}
|
||||||
|
|
||||||
{% block title %}{{ title_tag(T('Bad Request'), false) }}{% endblock %}
|
{% block title %}{{ title_tag(T('bad-request'), false) }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="error-page">
|
<main class="error-page">
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@
|
||||||
}</style>
|
}</style>
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
{% block json_info %}{% endblock %}
|
{% block json_info %}{% endblock %}
|
||||||
{% block ldjson %}{% endblock %}
|
|
||||||
</head>
|
</head>
|
||||||
<body {% if color_theme %} class="{{ theme_classes(color_theme) }}"{% endif %}>
|
<body {% if color_theme %} class="{{ theme_classes(color_theme) }}"{% endif %}>
|
||||||
<div id="__top"></div>
|
<div id="__top"></div>
|
||||||
|
|
@ -63,7 +62,7 @@
|
||||||
</div>
|
</div>
|
||||||
<footer class="site-footer">
|
<footer class="site-footer">
|
||||||
<div class="footer-copyright">© 2020-2025 Sakuragasaki46.</div>
|
<div class="footer-copyright">© 2020-2025 Sakuragasaki46.</div>
|
||||||
<div class="footer-liability">Any content in this site is user-generated and <u>each user is responsible for what they publish</u>.</div>
|
<div class="footer-liability">{{ T('user-generated-warning')}} <u>{{ T('user-liability-warning') }}</u>.</div>
|
||||||
{% if not g.no_user %}
|
{% if not g.no_user %}
|
||||||
<div class="footer-loggedinas">
|
<div class="footer-loggedinas">
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,13 @@
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Show more years:
|
{{ T('show-more-years') }}:
|
||||||
<ul class="inline">
|
<ul class="inline">
|
||||||
<li>
|
<li>
|
||||||
<a href="?till_year={{ till_year + 15 }}">in the future</a>
|
<a href="?till_year={{ till_year + 15 }}">{{ T('in-the-future') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="?from_year={{ from_year - 15 }}">in the past</a>
|
<a href="?from_year={{ from_year - 15 }}">{{ T('in-the-past') }}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block title %}List of contacts – {{ app_name }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<p>Showing: <strong>{{ cat }}</strong> · <a href="/kt/new">New contact</a></p>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend>Show by:</legend>
|
|
||||||
<p><strong>Letter</strong>:
|
|
||||||
{% set typ_list = 'ABCDEFGHIJKLMNOPQRSTUVWYZ' %}
|
|
||||||
{% for t in typ_list %}
|
|
||||||
<a href="/kt/{{ t }}">{{ t }}</a> ·
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="/kt/">All</a> ·
|
|
||||||
<a href="/kt/expired">Expired</a> ·
|
|
||||||
<a href="/kt/ok">Sane</a>
|
|
||||||
</p>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
{% if count > people.count() %}
|
|
||||||
<p>Showing <strong>{{ people.count() }}</strong> people of <strong>{{ count }}</strong> total.</p>
|
|
||||||
{% if count > pageno * 50 %}
|
|
||||||
<p><a href="?page={{ pageno + 1 }}" rel="nofollow">Next page</a>{% if pageno > 1 %} · <a href="?page={{ pageno - 1 }}" rel="nofollow">Prev page</a>{% endif %}</p>
|
|
||||||
{% elif pageno > 1 %}
|
|
||||||
<a href="?page={{ pageno - 1 }}" rel="nofollow">Prev page</a></p>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<p><strong>{{ count }}</strong> people.</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<table class="contactnova-list">
|
|
||||||
<thead>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for p in people %}
|
|
||||||
<tr class="contactnova-status_{{ p.status }}">
|
|
||||||
<td><span class="material-icons">{{ p.status_str() }}</span></td>
|
|
||||||
<td class="contactnova-col-code"><a href="/kt/{{ p.code }}">{{ p.code }}</a></td>
|
|
||||||
<td>{{ p.display_name }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{% if count > people.count() %}
|
|
||||||
<p>Showing <strong>{{ people.count() }}</strong> people of <strong>{{ count }}</strong> total.</p>
|
|
||||||
{% if count > pageno * 50 %}
|
|
||||||
<p><a href="?page={{ pageno + 1 }}" rel="nofollow">Next page</a>{% if pageno > 1 %} · <a href="?page={{ pageno - 1 }}" rel="nofollow">Prev page</a>{% endif %}</p>
|
|
||||||
{% elif pageno > 1 %}
|
|
||||||
<a href="?page={{ pageno - 1 }}" rel="nofollow">Prev page</a></p>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<p><strong>{{ count }}</strong> people.</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block title %}Create new contact – {{ app_name }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<form method="POST" class="circles-add-form">
|
|
||||||
{% if returnto %}<input type="hidden" name="returnto" value="{{ returnto }}">{% endif %}
|
|
||||||
{% if not pl %}
|
|
||||||
<div>
|
|
||||||
<label>Letter</label>
|
|
||||||
<select id="ktCodeLetter" name="letter">
|
|
||||||
<option value="-" disabled="" selected="">(Choose)</option>
|
|
||||||
{% set typ_list = 'ABCDEFGHIJKLMNOPQRSTUVWYZ' %}
|
|
||||||
{% for t in typ_list %}
|
|
||||||
<option value="{{ t }}">{{ t }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div>
|
|
||||||
<label>Code</label>
|
|
||||||
<strong id="ktNewCode">---</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Display name</label>
|
|
||||||
<input name="display_name" maxlength="50"{% if pl %} value="{{ pl.display_name }}"{% endif %}>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Status</label>
|
|
||||||
<select name="status">
|
|
||||||
{% set statuses = {
|
|
||||||
0: 'Variable',
|
|
||||||
1: 'OK',
|
|
||||||
2: 'Issues',
|
|
||||||
} %}
|
|
||||||
{% for k, v in statuses.items() %}
|
|
||||||
<option value="{{ k }}"{% if pl and pl.status == k %} selected=""{% endif %}>{{ v }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Issues</label>
|
|
||||||
<textarea maxlength="500" name="issues">{{ pl and pl.issues }}</textarea>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Description</label>
|
|
||||||
<textarea maxlength="5000" name="description">{{ pl and pl.description }}</textarea>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Due</label>
|
|
||||||
<input name="due" required="" type="date" value="{{ pl.due if pl else pl_date }}" min="2020-01-01" />
|
|
||||||
</div>
|
|
||||||
<input type="submit" id="ktSubmit" value="Save">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
{% if not pl %}
|
|
||||||
ktSubmit.disabled = true;
|
|
||||||
{% endif %}
|
|
||||||
ktCodeLetter.onchange = function(){
|
|
||||||
let x = new XMLHttpRequest;
|
|
||||||
x.open('GET', '/kt/_newcode/' + ktCodeLetter.value);
|
|
||||||
x.onreadystatechange = () => {
|
|
||||||
if (x.readyState === XMLHttpRequest.DONE && x.status == 200) {
|
|
||||||
ktNewCode.textContent = x.responseText;
|
|
||||||
ktSubmit.disabled = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
x.send();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block title %}Contact {{ p.code }} – {{ app_name }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{{ p.code }}</h1>
|
|
||||||
|
|
||||||
<p>aka: <strong>{{ p.display_name }}</strong></p>
|
|
||||||
|
|
||||||
{% if p.issues %}
|
|
||||||
<p class="contactnova-issues">{{ p.issues }}</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p class="contactnova-description">
|
|
||||||
{{ p.description or "No description available." |linebreaks }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p> </p>
|
|
||||||
|
|
||||||
<p>Expires: {{ p.due.strftime('%B %-d, %Y') }}</p>
|
|
||||||
|
|
||||||
<p><a href="/kt/">Back</a> · <a href="/kt/edit/{{ p.code }}?returnto=/kt/{{ p.code }}">Edit contact</a></p>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
{% from "macros/title.html" import title_tag with context %}
|
{% from "macros/title.html" import title_tag with context %}
|
||||||
{% from "macros/nl.html" import nl_list with context %}
|
{% from "macros/nl.html" import nl_list with context %}
|
||||||
|
|
||||||
{% block title %}{{ title_tag('Notes by date') }}{% endblock %}
|
{% block title %}{{ title_tag(T('notes-by-date')) }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main>
|
<main>
|
||||||
<h1 id="firstHeading">Notes by date</h1>
|
<h1 id="firstHeading">{{ T('notes-by-date') }}</h1>
|
||||||
|
|
||||||
<div class="inner-content">
|
<div class="inner-content">
|
||||||
{{ nl_list(notes) }}
|
{{ nl_list(notes) }}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% from "macros/title.html" import title_tag with context %}
|
{% from "macros/title.html" import title_tag with context %}
|
||||||
|
|
||||||
{% block title %}{{ title_tag('Manage accounts', False) }}{% endblock %}
|
{% block title %}{{ title_tag(T('manage-accounts'), False) }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Manage accounts</h1>
|
<h1>{{ T('manage-accounts') }}</h1>
|
||||||
|
|
||||||
<div class="inner-content">
|
<div class="inner-content">
|
||||||
{% if current_user.is_admin %}
|
{% if current_user.is_admin %}
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
<strong>Beware: you are managing sensitive informations.</strong>
|
<strong>Beware: you are managing sensitive informations.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="nl-pagination">Showing results <strong>{{ users.page * 20 - 19 }}</strong> to <strong>{{ min(users.page * 20, users.total) }}</strong> of <strong>{{ users.total }}</strong> total.</p>
|
<p class="nl-pagination">{{ T('search-results') }} <strong>{{ users.page * 20 - 19 }}</strong> to <strong>{{ min(users.page * 20, users.total) }}</strong> of <strong>{{ users.total }}</strong> total.</p>
|
||||||
|
|
||||||
<form enctype="multipart/form-data" method="POST">
|
<form enctype="multipart/form-data" method="POST">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
{% elif q %}
|
{% elif q %}
|
||||||
<h2>{{ T('search-no-results') }} <em>{{ q }}</em></h2>
|
<h2>{{ T('search-no-results') }} <em>{{ q }}</em></h2>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p><u>Please note that search queries do not search for page text.</u></p>
|
<p><u>Please note that search queries <u>do not search for page text</u>.</u></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue