Implemented calendar
This commit is contained in:
parent
d2cef14c38
commit
6f53cd3836
15 changed files with 145 additions and 182 deletions
|
|
@ -8,7 +8,12 @@
|
||||||
+ Added `User` table.
|
+ Added `User` table.
|
||||||
+ Added `Flask-Login` and `Flask-WTF` dependencies in order to implement user logins.
|
+ Added `Flask-Login` and `Flask-WTF` dependencies in order to implement user logins.
|
||||||
+ Added `python-i18n` as a dependency. Therefore, i18n changed format, using JSON files now.
|
+ Added `python-i18n` as a dependency. Therefore, i18n changed format, using JSON files now.
|
||||||
+ Now you can export pages in a JSON format. Coming soon: importing.
|
+ Login is now required for creating and editing.
|
||||||
|
+ Now you can leave a comment while changing a page’s text. Moreover, a new revision is created now
|
||||||
|
only in case of an effective text change.
|
||||||
|
+ Now a page can be dated in the calendar.
|
||||||
|
+ Now you can export and import pages in a JSON format. Importing can be done by admin users only.
|
||||||
|
+ Improved page history view, and added user contributions page.
|
||||||
+ Like it or not, now gzip library is required.
|
+ Like it or not, now gzip library is required.
|
||||||
+ Added CSS variables in the site style.
|
+ Added CSS variables in the site style.
|
||||||
|
|
||||||
|
|
|
||||||
33
app.py
33
app.py
|
|
@ -21,7 +21,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from werkzeug.routing import BaseConverter
|
from werkzeug.routing import BaseConverter
|
||||||
from peewee import *
|
from peewee import *
|
||||||
from playhouse.db_url import connect as dbconnect
|
from playhouse.db_url import connect as dbconnect
|
||||||
import datetime, hashlib, html, importlib, json, markdown, os, random, \
|
import calendar, datetime, hashlib, html, importlib, json, markdown, os, random, \
|
||||||
re, sys, warnings
|
re, sys, warnings
|
||||||
from functools import lru_cache, partial
|
from functools import lru_cache, partial
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
@ -436,10 +436,10 @@ def is_url_available(url):
|
||||||
return url not in forbidden_urls and not Page.select().where(Page.url == url).exists()
|
return url not in forbidden_urls and not Page.select().where(Page.url == url).exists()
|
||||||
|
|
||||||
forbidden_urls = [
|
forbidden_urls = [
|
||||||
'create', 'edit', 'p', 'ajax', 'history', 'manage', 'static', 'media',
|
'about', 'accounts', 'ajax', 'backlinks', 'calendar', 'circles', 'create',
|
||||||
'accounts', 'tags', 'init-config', 'upload', 'upload-info', 'about',
|
'easter', 'edit', 'embed', 'help', 'history', 'init-config', 'kt',
|
||||||
'stats', 'terms', 'privacy', 'easter', 'search', 'help', 'circles',
|
'manage', 'media', 'p', 'privacy', 'protect', 'search', 'static', 'stats',
|
||||||
'protect', 'kt', 'embed', 'backlinks', 'u'
|
'tags', 'terms', 'u', 'upload', 'upload-info'
|
||||||
]
|
]
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
@ -547,6 +547,7 @@ def savepoint(form, is_preview=False, pageobj=None):
|
||||||
pl_owner_is_current_user=pageobj.is_owned_by(current_user) if pageobj else True,
|
pl_owner_is_current_user=pageobj.is_owned_by(current_user) if pageobj else True,
|
||||||
preview=preview,
|
preview=preview,
|
||||||
pl_js_info=pl_js_info,
|
pl_js_info=pl_js_info,
|
||||||
|
pl_calendar=('usecalendar' in form) and form['calendar'],
|
||||||
pl_readonly=not pageobj.can_edit(current_user) if pageobj else False
|
pl_readonly=not pageobj.can_edit(current_user) if pageobj else False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -575,7 +576,8 @@ def create():
|
||||||
title=request.form['title'],
|
title=request.form['title'],
|
||||||
is_redirect=False,
|
is_redirect=False,
|
||||||
touched=datetime.datetime.now(),
|
touched=datetime.datetime.now(),
|
||||||
owner=current_user,
|
owner_id=current_user.id,
|
||||||
|
calendar=datetime.date.fromisoformat(request.form["calendar"]) if 'usecalendar' in request.form else None,
|
||||||
is_math_enabled='enablemath' in request.form,
|
is_math_enabled='enablemath' in request.form,
|
||||||
is_locked = 'lockpage' in request.form
|
is_locked = 'lockpage' in request.form
|
||||||
)
|
)
|
||||||
|
|
@ -632,6 +634,7 @@ def edit(id):
|
||||||
p.touched = datetime.datetime.now()
|
p.touched = datetime.datetime.now()
|
||||||
p.is_math_enabled = 'enablemath' in request.form
|
p.is_math_enabled = 'enablemath' in request.form
|
||||||
p.is_locked = 'lockpage' in request.form
|
p.is_locked = 'lockpage' in request.form
|
||||||
|
p.calendar = datetime.date.fromisoformat(request.form["calendar"]) if 'usecalendar' in request.form else None
|
||||||
p.save()
|
p.save()
|
||||||
p.change_tags(p_tags)
|
p.change_tags(p_tags)
|
||||||
if request.form['text'] != p.latest.text:
|
if request.form['text'] != p.latest.text:
|
||||||
|
|
@ -657,6 +660,12 @@ def edit(id):
|
||||||
form["enablemath"] = "1"
|
form["enablemath"] = "1"
|
||||||
if p.is_locked:
|
if p.is_locked:
|
||||||
form["lockpage"] = "1"
|
form["lockpage"] = "1"
|
||||||
|
if p.calendar:
|
||||||
|
form["usecalendar"] = "1"
|
||||||
|
try:
|
||||||
|
form["calendar"] = p.calendar.isoformat().split("T")[0]
|
||||||
|
except Exception:
|
||||||
|
form["calendar"] = p.calendar
|
||||||
|
|
||||||
return savepoint(form, pageobj=p)
|
return savepoint(form, pageobj=p)
|
||||||
|
|
||||||
|
|
@ -778,6 +787,18 @@ def contributions(username):
|
||||||
abort(404)
|
abort(404)
|
||||||
return render_template('contributions.html', u=user, contributions=user.contributions.order_by(PageRevision.pub_date.desc()))
|
return render_template('contributions.html', u=user, contributions=user.contributions.order_by(PageRevision.pub_date.desc()))
|
||||||
|
|
||||||
|
@app.route('/calendar/')
|
||||||
|
def calendar_view():
|
||||||
|
return render_template('calendar.html')
|
||||||
|
|
||||||
|
@app.route('/calendar/<int:y>/<int:m>')
|
||||||
|
def calendar_month(y, m):
|
||||||
|
notes = Page.select().where(
|
||||||
|
(datetime.date(y, m, 1) <= Page.calendar) &
|
||||||
|
(Page.calendar < datetime.date(y+1 if m==12 else y, 1 if m==12 else m+1, 1))
|
||||||
|
).order_by(Page.calendar)
|
||||||
|
|
||||||
|
return render_template('month.html', d=datetime.date(y, m, 1), notes=notes)
|
||||||
|
|
||||||
@app.route('/history/revision/<int:revisionid>/')
|
@app.route('/history/revision/<int:revisionid>/')
|
||||||
def view_old(revisionid):
|
def view_old(revisionid):
|
||||||
|
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
from flask import Blueprint, render_template
|
|
||||||
from peewee import *
|
|
||||||
import instagram_private_api, json, os, sys, random, codecs
|
|
||||||
|
|
||||||
database = SqliteDatabase('instagram.sqlite')
|
|
||||||
|
|
||||||
class BaseModel(Model):
|
|
||||||
class Meta:
|
|
||||||
database = database
|
|
||||||
|
|
||||||
class InstagramProfile(BaseModel):
|
|
||||||
p_id = IntegerField()
|
|
||||||
p_username = CharField(30)
|
|
||||||
p_full_name = CharField(30)
|
|
||||||
p_biography = CharField(150)
|
|
||||||
posts_count = IntegerField()
|
|
||||||
followers_count = IntegerField()
|
|
||||||
following_count = IntegerField()
|
|
||||||
flags = BitField()
|
|
||||||
pub_date = DateTimeField()
|
|
||||||
is_verified = flags.flag(1)
|
|
||||||
is_private = flags.flag(2)
|
|
||||||
|
|
||||||
class InstagramMedia(BaseModel):
|
|
||||||
user = IntegerField()
|
|
||||||
pub_date = DateTimeField()
|
|
||||||
media_url = TextField()
|
|
||||||
description = CharField(2200)
|
|
||||||
|
|
||||||
def init_db():
|
|
||||||
database.create_tables([InstagramProfile, InstagramMedia])
|
|
||||||
|
|
||||||
def bytes_to_json(python_object):
|
|
||||||
if isinstance(python_object, bytes):
|
|
||||||
return {'__class__': 'bytes',
|
|
||||||
'__value__': codecs.encode(python_object, 'base64').decode()}
|
|
||||||
raise TypeError(repr(python_object) + ' is not JSON serializable')
|
|
||||||
|
|
||||||
def bytes_from_json(json_object):
|
|
||||||
if '__class__' in json_object and json_object['__class__'] == 'bytes':
|
|
||||||
return codecs.decode(json_object['__value__'].encode(), 'base64')
|
|
||||||
return json_object
|
|
||||||
|
|
||||||
SETTINGS_PATH = 'ig_api_settings'
|
|
||||||
|
|
||||||
def load_settings(username):
|
|
||||||
with open(os.path.join(SETTINGS_PATH, username + '.json')) as f:
|
|
||||||
settings = json.load(f, object_hook=bytes_from_json)
|
|
||||||
return settings
|
|
||||||
|
|
||||||
def save_settings(username, settings):
|
|
||||||
with open(os.path.join(SETTINGS_PATH, username + '.json'), 'w') as f:
|
|
||||||
json.dump(settings, f, default=bytes_to_json)
|
|
||||||
|
|
||||||
CLIENTS = []
|
|
||||||
|
|
||||||
def load_clients():
|
|
||||||
try:
|
|
||||||
with open(os.path.join(SETTINGS_PATH, 'config.txt')) as f:
|
|
||||||
conf = f.read()
|
|
||||||
except OSError:
|
|
||||||
print('Config file not found.')
|
|
||||||
return
|
|
||||||
for up in conf.split('\n'):
|
|
||||||
try:
|
|
||||||
up = up.split('#')[0].strip()
|
|
||||||
if not up:
|
|
||||||
continue
|
|
||||||
username, password = up.split(':')
|
|
||||||
try:
|
|
||||||
settings = load_settings(username)
|
|
||||||
except Exception:
|
|
||||||
settings = None
|
|
||||||
try:
|
|
||||||
if settings:
|
|
||||||
device_id = settings.get('device_id')
|
|
||||||
api = instagram_private_api.Client(
|
|
||||||
username, password,
|
|
||||||
settings=settings
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
api = instagram_private_api.Client(
|
|
||||||
username, password,
|
|
||||||
on_login=lambda x: save_settings(username, x.settings)
|
|
||||||
)
|
|
||||||
except (instagram_private_api.ClientCookieExpiredError,
|
|
||||||
instagram_private_api.ClientLoginRequiredError) as e:
|
|
||||||
api = instagram_private_api.Client(
|
|
||||||
username, password,
|
|
||||||
device_id=device_id,
|
|
||||||
on_login=lambda x: save_settings(username, x.settings)
|
|
||||||
)
|
|
||||||
CLIENTS.append(api)
|
|
||||||
except Exception:
|
|
||||||
sys.excepthook(*sys.exc_info())
|
|
||||||
continue
|
|
||||||
|
|
||||||
def make_request(method_name, *args, **kwargs):
|
|
||||||
exc = None
|
|
||||||
usable_clients = list(range(len(CLIENTS)))
|
|
||||||
while usable_clients:
|
|
||||||
ci = random.choice(usable_clients)
|
|
||||||
client = CLIENTS[ci]
|
|
||||||
usable_clients.remove(ci)
|
|
||||||
try:
|
|
||||||
method = getattr(client, method_name)
|
|
||||||
except AttributeError:
|
|
||||||
raise ValueError('client has no method called {!r}'.format(method_name))
|
|
||||||
if not callable(method):
|
|
||||||
raise ValueError('client has no method called {!r}'.format(method_name))
|
|
||||||
try:
|
|
||||||
return method(*args, **kwargs)
|
|
||||||
except Exception as e:
|
|
||||||
exc = e
|
|
||||||
if exc:
|
|
||||||
raise exc
|
|
||||||
else:
|
|
||||||
raise RuntimeError('no active clients')
|
|
||||||
|
|
||||||
N_FORCE = 0
|
|
||||||
N_FALLBACK_CACHE = 1
|
|
||||||
N_PREFER_CACHE = 2
|
|
||||||
N_OFFLINE = 3
|
|
||||||
|
|
||||||
def choose_method(online, offline, network):
|
|
||||||
if network == N_FORCE:
|
|
||||||
return online()
|
|
||||||
elif network == N_FALLBACK_CACHE:
|
|
||||||
try:
|
|
||||||
return online()
|
|
||||||
except Exception:
|
|
||||||
return offline()
|
|
||||||
elif network == N_PREFER_CACHE:
|
|
||||||
try:
|
|
||||||
return offline()
|
|
||||||
except Exception:
|
|
||||||
return online()
|
|
||||||
elif network == N_OFFLINE:
|
|
||||||
return offline()
|
|
||||||
|
|
||||||
def get_profile_info(username_or_id, network=N_FALLBACK_CACHE):
|
|
||||||
if isinstance(username_or_id, str):
|
|
||||||
username, userid = username_or_id, None
|
|
||||||
elif isinstance(username_or_id, int):
|
|
||||||
username, userid = None, username_or_id
|
|
||||||
else:
|
|
||||||
raise TypeError('invalid username or id')
|
|
||||||
def online():
|
|
||||||
if userid:
|
|
||||||
data = make_request('user_info', userid)
|
|
||||||
else:
|
|
||||||
data = make_request('username_info', username)
|
|
||||||
return InstagramProfile.create(
|
|
||||||
p_id = data['user']['pk'],
|
|
||||||
p_username = data['user']['username'],
|
|
||||||
p_full_name = data['user']['full_name'],
|
|
||||||
p_biography = data['user']['biography'],
|
|
||||||
posts_count = data['user']['media_count'],
|
|
||||||
followers_count = data['user']['follower_count'],
|
|
||||||
following_count = data['user']['following_count'],
|
|
||||||
is_verified = data['user']['is_verified'],
|
|
||||||
is_private = data['user']['is_private'],
|
|
||||||
pub_date = datetime.datetime.now()
|
|
||||||
)
|
|
||||||
def offline():
|
|
||||||
if userid:
|
|
||||||
q = InstagramProfile.select().where(InstagramProfile.p_id == userid)
|
|
||||||
else:
|
|
||||||
q = InstagramProfile.select().where(InstagramProfile.p_username == username)
|
|
||||||
return q.order_by(InstagramProfile.pub_date.desc())[0]
|
|
||||||
return choose_method(online, offline, network)
|
|
||||||
|
|
||||||
load_clients()
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
"random-page": "Random page",
|
"random-page": "Random page",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"year": "Year",
|
"year": "Year",
|
||||||
|
"month": "Month",
|
||||||
"calculate": "Calculate",
|
"calculate": "Calculate",
|
||||||
"show-all": "Show all",
|
"show-all": "Show all",
|
||||||
"just-now": "just now",
|
"just-now": "just now",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
"random-page": "Pagina casuale",
|
"random-page": "Pagina casuale",
|
||||||
"search": "Cerca",
|
"search": "Cerca",
|
||||||
"year": "Anno",
|
"year": "Anno",
|
||||||
|
"month": "Mese",
|
||||||
"calculate": "Calcola",
|
"calculate": "Calcola",
|
||||||
"show-all": "Mostra tutto",
|
"show-all": "Mostra tutto",
|
||||||
"just-now": "poco fa",
|
"just-now": "poco fa",
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,13 @@ input[type="submit"],input[type="reset"],input[type="button"],button{font-family
|
||||||
.page-tags .tag-count{color: var(--btn-success);font-size:smaller;font-weight:600}
|
.page-tags .tag-count{color: var(--btn-success);font-size:smaller;font-weight:600}
|
||||||
.search-wrapper {display:flex;width:90%;margin:auto}
|
.search-wrapper {display:flex;width:90%;margin:auto}
|
||||||
.search-wrapper > input {flex:1}
|
.search-wrapper > input {flex:1}
|
||||||
|
.calendar-subtitle {text-align: center; margin-top: -1em}
|
||||||
.preview-subtitle {text-align: center; margin-top: -1em}
|
.preview-subtitle {text-align: center; margin-top: -1em}
|
||||||
textarea {background-color: var(--bg-sharp); color: var(--fg-sharp)}
|
textarea {background-color: var(--bg-sharp); color: var(--fg-sharp)}
|
||||||
|
ul.inline {margin:0; padding:0; display: inline}
|
||||||
|
ul.inline > li {display: inline-block;}
|
||||||
|
ul.inline > li::after {content: "·"}
|
||||||
|
ul.inline > li:last-child::after {content: ""}
|
||||||
|
|
||||||
/* Circles extension */
|
/* Circles extension */
|
||||||
.circles-red{color: #e14}
|
.circles-red{color: #e14}
|
||||||
|
|
|
||||||
33
templates/calendar.html
Normal file
33
templates/calendar.html
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Calendar – {{ app_name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Calendar</h1>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<fieldset>
|
||||||
|
<legend>Calendar view</legend>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label><strong>Show as</strong>:</label>
|
||||||
|
<ul class="inline">
|
||||||
|
{% set view_choices = ["month"] %}
|
||||||
|
{% for vch in view_choices %}
|
||||||
|
<li>
|
||||||
|
{% if vch == viewas %}
|
||||||
|
<strong>{{ T(vch) }}</strong>
|
||||||
|
{% else %}
|
||||||
|
<a href="/calendar/{{ vch }}">{{ T(vch) }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" >
|
||||||
|
<div>
|
||||||
|
<label><strong>Show month</strong></label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -77,6 +77,11 @@
|
||||||
<label for="CB__lockpage">Lock page for editing by other users</label>
|
<label for="CB__lockpage">Lock page for editing by other users</label>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" id="CB__usecalendar" name="usecalendar" {% if pl_calendar %}checked=""{% endif %}>
|
||||||
|
<label for="CB__usecalendar">Use calendar:</label>
|
||||||
|
<input type="date" name="calendar" {% if pl_calendar %}value="{{ pl_calendar }}"{% endif %}>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,12 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if n.calendar %}
|
||||||
|
<p class="nl-calendar">
|
||||||
|
<span class="material-icons">calendar_today</span>
|
||||||
|
<time datetime="{{ n.calendar.isoformat() }}">{{ n.calendar.strftime('%B %-d, %Y') }}</time>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<li><a href="/p/most_recent/">{{ T('show-all') }}</a></li>
|
<li><a href="/p/most_recent/">{{ T('show-all') }}</a></li>
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,9 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</p>
|
</p>
|
||||||
|
{% if n.calendar %}
|
||||||
|
<p class="nl-calendar">
|
||||||
|
<span class="material-icons">calendar_today</span>
|
||||||
|
<time datetime="{{ n.calendar.isoformat() }}">{{ n.calendar.strftime('%B %-d, %Y') }}</time>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,20 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ n.get_url() }}" class="nl-title">{{ n.title }}</a>
|
<a href="{{ n.get_url() }}" class="nl-title">{{ n.title }}</a>
|
||||||
<p class="nl-desc">{{ n.short_desc() }}</p>
|
<p class="nl-desc">{{ n.short_desc() }}</p>
|
||||||
<p class="nl-tags">Tags:
|
{% if n.tags %}
|
||||||
|
<p class="nl-tags">{{ T('tags') }}:
|
||||||
{% for tag in n.tags %}
|
{% for tag in n.tags %}
|
||||||
<a href="/tags/{{ tag.name }}/" class="nl-tag">#{{ tag.name }}</a>
|
{% set tn = tag.name %}
|
||||||
|
<a href="/tags/{{ tn }}/" class="nl-tag">#{{ tn }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</p>
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if n.calendar %}
|
||||||
|
<p class="nl-calendar">
|
||||||
|
<span class="material-icons">calendar_today</span>
|
||||||
|
<time datetime="{{ n.calendar.isoformat() }}">{{ n.calendar.strftime('%B %-d, %Y') }}</time>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if page_n <= total_count // 20 %}
|
{% if page_n <= total_count // 20 %}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,12 @@
|
||||||
<a href="/tags/{{ tn }}/" class="nl-tag">#{{ tn }}</a>
|
<a href="/tags/{{ tn }}/" class="nl-tag">#{{ tn }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if n.calendar %}
|
||||||
|
<p class="nl-calendar">
|
||||||
|
<span class="material-icons">calendar_today</span>
|
||||||
|
<time datetime="{{ n.calendar.isoformat() }}">{{ n.calendar.strftime('%B %-d, %Y') }}</time>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
||||||
32
templates/month.html
Normal file
32
templates/month.html
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ d.strftime("%B %Y") }} – {{ app_name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{ d.strftime("%B %Y") }}</h1>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for n in notes %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ n.get_url() }}" class="nl-title">{{ n.title }}</a>
|
||||||
|
<p class="nl-desc">{{ n.short_desc() }}</p>
|
||||||
|
{% if n.tags %}
|
||||||
|
<p class="nl-tags">{{ T('tags') }}:
|
||||||
|
{% for tag in n.tags %}
|
||||||
|
{% set tn = tag.name %}
|
||||||
|
<a href="/tags/{{ tn }}/" class="nl-tag">#{{ tn }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if n.calendar %}
|
||||||
|
<p class="nl-calendar">
|
||||||
|
<span class="material-icons">calendar_today</span>
|
||||||
|
<time datetime="{{ n.calendar.isoformat() }}">{{ n.calendar.strftime('%B %-d, %Y') }}</time>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -27,6 +27,8 @@
|
||||||
</ul>
|
</ul>
|
||||||
{% elif q %}
|
{% elif q %}
|
||||||
<h2>{{ T('search-no-results') }} <em>{{ q }}</em></h2>
|
<h2>{{ T('search-no-results') }} <em>{{ q }}</em></h2>
|
||||||
|
{% else %}
|
||||||
|
<p>Please note that search queries do not search for page text.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@
|
||||||
<article>
|
<article>
|
||||||
<h1 id="firstHeading">{{ p.title }}</h1>
|
<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>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="jump-to-actions">
|
<div class="jump-to-actions">
|
||||||
<span>{{ T('last-changed') }} {{ rev.human_pub_date() }}</span> ·
|
<span>{{ T('last-changed') }} {{ rev.human_pub_date() }}</span> ·
|
||||||
<a href="#page-actions">{{ T('jump-to-actions') }}</a>
|
<a href="#page-actions">{{ T('jump-to-actions') }}</a>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue