Remove magic words and uploads, deprecate upload endpoints, style changes, version advance
This commit is contained in:
parent
5d18cee75e
commit
733ea8cce1
8 changed files with 87 additions and 156 deletions
141
app.py
141
app.py
|
|
@ -31,12 +31,8 @@ try:
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
except ImportError:
|
except ImportError:
|
||||||
slugify = None
|
slugify = None
|
||||||
try:
|
|
||||||
import markdown_strikethrough
|
|
||||||
except Exception:
|
|
||||||
markdown_strikethrough = None
|
|
||||||
|
|
||||||
__version__ = '0.4'
|
__version__ = '0.5-dev'
|
||||||
|
|
||||||
#### CONSTANTS ####
|
#### CONSTANTS ####
|
||||||
|
|
||||||
|
|
@ -93,6 +89,28 @@ def _makelist(l):
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
#### MARKDOWN EXTENSIONS ####
|
||||||
|
|
||||||
|
class StrikethroughExtension(markdown.extensions.Extension):
|
||||||
|
def extendMarkdown(self, md, md_globals):
|
||||||
|
postprocessor = StrikethroughPostprocessor(md)
|
||||||
|
md.postprocessors.add('strikethrough', postprocessor, '>raw_html')
|
||||||
|
|
||||||
|
class StrikethroughPostprocessor(markdown.postprocessors.Postprocessor):
|
||||||
|
pattern = re.compile(r"~~(((?!~~).)+)~~", re.DOTALL)
|
||||||
|
|
||||||
|
def run(self, html):
|
||||||
|
return re.sub(self.pattern, self.convert, html)
|
||||||
|
|
||||||
|
def convert(self, match):
|
||||||
|
return '<del>' + match.group(1) + '</del>'
|
||||||
|
|
||||||
|
### XXX it currently only detects spoilers that are not at the beginning of the line. To be fixed.
|
||||||
|
class SpoilerExtension(markdown.extensions.Extension):
|
||||||
|
def extendMarkdown(self, md, md_globals):
|
||||||
|
md.inlinePatterns.register(markdown.inlinepatterns.SimpleTagInlineProcessor(r'()>!(.*?)!<', 'span class="spoiler"'), 'spoiler', 14)
|
||||||
|
|
||||||
#### DATABASE SCHEMA ####
|
#### DATABASE SCHEMA ####
|
||||||
|
|
||||||
database = SqliteDatabase(_getconf("config", "database_dir") + '/data.sqlite')
|
database = SqliteDatabase(_getconf("config", "database_dir") + '/data.sqlite')
|
||||||
|
|
@ -321,6 +339,9 @@ class PagePolicy(BaseModel):
|
||||||
(('page', 'key'), True),
|
(('page', 'key'), True),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# DEPRECATED
|
||||||
|
# It will be possibly removed in v0.6.
|
||||||
|
# Use external image URL instead.
|
||||||
class Upload(BaseModel):
|
class Upload(BaseModel):
|
||||||
name = CharField(256)
|
name = CharField(256)
|
||||||
url_name = CharField(256, null=True)
|
url_name = CharField(256, null=True)
|
||||||
|
|
@ -382,34 +403,11 @@ def init_db():
|
||||||
|
|
||||||
#### WIKI SYNTAX ####
|
#### WIKI SYNTAX ####
|
||||||
|
|
||||||
magic_word_filters = {}
|
|
||||||
|
|
||||||
def _replace_magic_word(match):
|
|
||||||
name = match.group(1)
|
|
||||||
if name not in magic_word_filters:
|
|
||||||
return match.group()
|
|
||||||
f = magic_word_filters[name]
|
|
||||||
try:
|
|
||||||
return f(*(x.strip() for x in match.group(2).split('|')))
|
|
||||||
except Exception:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def expand_magic_words(text):
|
|
||||||
'''
|
|
||||||
Replace the special markups in double curly brackets.
|
|
||||||
|
|
||||||
Unknown keywords are not replaced. Valid keywords with invalid arguments are replaced with nothing.
|
|
||||||
'''
|
|
||||||
return re.sub(MAGIC_RE, _replace_magic_word, text)
|
|
||||||
|
|
||||||
def md(text, expand_magic=False, toc=True):
|
def md(text, expand_magic=False, toc=True):
|
||||||
if expand_magic:
|
if expand_magic:
|
||||||
# DEPRECATED seeking for a better solution.
|
# DEPRECATED seeking for a better solution.
|
||||||
warnings.warn('Magic words are no more supported.', DeprecationWarning)
|
warnings.warn('Magic words are no more supported.', DeprecationWarning)
|
||||||
text = expand_magic_words(text)
|
extensions = ['tables', 'footnotes', 'fenced_code', 'sane_lists', StrikethroughExtension(), SpoilerExtension()]
|
||||||
extensions = ['tables', 'footnotes', 'fenced_code', 'sane_lists']
|
|
||||||
if markdown_strikethrough:
|
|
||||||
extensions.append("markdown_strikethrough.extension")
|
|
||||||
if toc:
|
if toc:
|
||||||
extensions.append('toc')
|
extensions.append('toc')
|
||||||
return markdown.Markdown(extensions=extensions).convert(text)
|
return markdown.Markdown(extensions=extensions).convert(text)
|
||||||
|
|
@ -418,59 +416,9 @@ def remove_tags(text, convert=True, headings=True):
|
||||||
if headings:
|
if headings:
|
||||||
text = re.sub(r'\#[^\n]*', '', text)
|
text = re.sub(r'\#[^\n]*', '', text)
|
||||||
if convert:
|
if convert:
|
||||||
text = md(text, expand_magic=False, toc=False)
|
text = md(text, toc=False)
|
||||||
return re.sub(r'<.*?>|\{\{.*?\}\}', '', text)
|
return re.sub(r'<.*?>|\{\{.*?\}\}', '', text)
|
||||||
|
|
||||||
|
|
||||||
### Magic words (deprecated!) ###
|
|
||||||
|
|
||||||
def expand_backto(pageid):
|
|
||||||
p = Page[pageid]
|
|
||||||
return '*« Main article: [{}]({}).*'.format(html.escape(p.title), p.get_url())
|
|
||||||
|
|
||||||
magic_word_filters['backto'] = expand_backto
|
|
||||||
|
|
||||||
def expand_upload(id, *opt):
|
|
||||||
try:
|
|
||||||
upload = Upload[id]
|
|
||||||
except Upload.DoesNotExist:
|
|
||||||
return ''
|
|
||||||
if opt:
|
|
||||||
desc = opt[-1]
|
|
||||||
else:
|
|
||||||
desc = None
|
|
||||||
classname = 'fig-right'
|
|
||||||
return '<figure class="{0}"><a href="/upload-info/{4}"><img alt="{1}" src="{2}" title="{1}"></a>{3}</figure>'.format(
|
|
||||||
classname, html.escape(upload.name), upload.url,
|
|
||||||
'<figcaption>{0}</figcaption>'.format(md(desc, expand_magic=False)) if desc else '',
|
|
||||||
upload.id)
|
|
||||||
|
|
||||||
magic_word_filters['media'] = expand_upload
|
|
||||||
|
|
||||||
def make_gallery(items):
|
|
||||||
result = []
|
|
||||||
for upload, desc in items:
|
|
||||||
result.append('<figure class="fig-gallery"><a href="/upload-info/{0}"><img alt="{1}" src="{2}" title="{1}"></a>{3}</figure>'.format(
|
|
||||||
upload.id, html.escape(upload.name), upload.url,
|
|
||||||
'<figcaption>{0}</figcaption>'.format(md(desc, expand_magic=False)) if desc else ''))
|
|
||||||
return '<div class="gallery">' + ''.join(result) + '</div>'
|
|
||||||
|
|
||||||
def expand_gallery(*ids):
|
|
||||||
items = []
|
|
||||||
for i in ids:
|
|
||||||
if ' ' in i:
|
|
||||||
id, desc = i.split(' ', 1)
|
|
||||||
else:
|
|
||||||
id, desc = i, ''
|
|
||||||
try:
|
|
||||||
upload = Upload[id]
|
|
||||||
except Upload.DoesNotExist:
|
|
||||||
continue
|
|
||||||
items.append((upload, desc))
|
|
||||||
return make_gallery(items)
|
|
||||||
|
|
||||||
magic_word_filters['gallery'] = expand_gallery
|
|
||||||
|
|
||||||
#### I18N ####
|
#### I18N ####
|
||||||
|
|
||||||
lang_poses = {'en': 1, 'en-US': 1, 'it': 2, 'it-IT': 2}
|
lang_poses = {'en': 1, 'en-US': 1, 'it': 2, 'it-IT': 2}
|
||||||
|
|
@ -549,8 +497,7 @@ def linebreaks(text):
|
||||||
def homepage():
|
def homepage():
|
||||||
page_limit = _getconf("appearance","items_per_page",20,cast=int)
|
page_limit = _getconf("appearance","items_per_page",20,cast=int)
|
||||||
return render_template('home.html', new_notes=Page.select()
|
return render_template('home.html', new_notes=Page.select()
|
||||||
.order_by(Page.touched.desc()).limit(page_limit),
|
.order_by(Page.touched.desc()).limit(page_limit))
|
||||||
gallery=make_gallery((x, '') for x in Upload.select().order_by(Upload.upload_date.desc()).limit(3)))
|
|
||||||
|
|
||||||
@app.route('/robots.txt')
|
@app.route('/robots.txt')
|
||||||
def robots():
|
def robots():
|
||||||
|
|
@ -795,28 +742,11 @@ def listtag(tag, page=1):
|
||||||
def media(fp):
|
def media(fp):
|
||||||
return send_from_directory(UPLOAD_DIR, fp)
|
return send_from_directory(UPLOAD_DIR, fp)
|
||||||
|
|
||||||
@app.route('/upload/', methods=['GET', 'POST'])
|
# symbolic route as of v0.5
|
||||||
|
@app.route('/upload/', methods=['GET'])
|
||||||
def upload():
|
def upload():
|
||||||
if request.method == 'POST':
|
|
||||||
name = request.form['name']
|
|
||||||
file = request.files['file']
|
|
||||||
filename = file.filename
|
|
||||||
ext = os.path.splitext(filename)[1]
|
|
||||||
content = file.read()
|
|
||||||
try:
|
|
||||||
upl = Upload.create_content(name, ext, content)
|
|
||||||
flash('File uploaded successfully')
|
|
||||||
return redirect('/upload-info/{}/'.format(upl.id))
|
|
||||||
except Exception:
|
|
||||||
sys.excepthook(*sys.exc_info())
|
|
||||||
flash('Unable to upload file. Try again later.')
|
|
||||||
return render_template('upload.html')
|
return render_template('upload.html')
|
||||||
|
|
||||||
@app.route('/upload-info/<int:id>/')
|
|
||||||
def upload_info(id):
|
|
||||||
upl = Upload[id]
|
|
||||||
return render_template('uploadinfo.html', upl=upl, type_list=upload_types_rev)
|
|
||||||
|
|
||||||
@app.route('/stats/')
|
@app.route('/stats/')
|
||||||
def stats():
|
def stats():
|
||||||
return render_template('stats.html',
|
return render_template('stats.html',
|
||||||
|
|
@ -881,7 +811,14 @@ def easter_y(y=None):
|
||||||
|
|
||||||
#### EXTENSIONS ####
|
#### EXTENSIONS ####
|
||||||
|
|
||||||
active_extensions = ['contactnova']
|
active_extensions = []
|
||||||
|
|
||||||
|
_i = 1
|
||||||
|
_extension_name = _getconf('extensions', 'ext.{}'.format(_i))
|
||||||
|
while _extension_name:
|
||||||
|
active_extensions.append(_extension_name)
|
||||||
|
_i += 1
|
||||||
|
_extension_name = _getconf('extensions', 'ext.{}'.format(_i))
|
||||||
|
|
||||||
for ext in active_extensions:
|
for ext in active_extensions:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
8
static/content.js
Normal file
8
static/content.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
for (let i of document.getElementsByClassName('spoiler')) {
|
||||||
|
i.onclick = function() {
|
||||||
|
this.classList.toggle('revealed');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
/* basic styles */
|
/* basic styles */
|
||||||
body{font-family:sans-serif}
|
body{font-family:sans-serif;background-color:#faf5e9}
|
||||||
.content{margin: 3em 1.3em}
|
.content{margin: 3em 1.3em}
|
||||||
|
|
||||||
/* content styles */
|
/* content styles */
|
||||||
#firstHeading {font-family: 'Oswald','Anton','Impact',sans-serif; text-align: center;font-size:3em}
|
#firstHeading {font-family:sans-serif; text-align: center;font-size:3em; font-weight: normal}
|
||||||
.inner-content{font-family:serif; margin: 1.7em auto; max-width: 1280px; line-height: 1.9; color: #1f2528}
|
.inner-content{font-family:serif; margin: 1.7em auto; max-width: 1280px; line-height: 1.9; color: #1f2528}
|
||||||
.inner-content em,.inner-content strong{color: black}
|
.inner-content em,.inner-content strong{color: black}
|
||||||
.inner-content h1{color: #99081f}
|
.inner-content h1{color: #99081f}
|
||||||
.inner-content table, .inner-content h2, .inner-content h3, .inner-content h4, .inner-content h5, .inner-content h6{font-family:sans-serif; color: black}
|
.inner-content table {font-family: sans-serif}
|
||||||
|
.inner-content h2, .inner-content h3, .inner-content h4, .inner-content h5, .inner-content h6{font-family:sans-serif; color: black; font-weight: normal}
|
||||||
.inner-content h3{margin:0.8em 0}
|
.inner-content h3{margin:0.8em 0}
|
||||||
.inner-content h4{margin:0.6em 0}
|
.inner-content h4{margin:0.6em 0}
|
||||||
.inner-content h5{margin:0.5em 0}
|
.inner-content h5{margin:0.5em 0}
|
||||||
|
|
@ -19,6 +20,9 @@ body{font-family:sans-serif}
|
||||||
.inner-content table > * > tr > th, .inner-content table > tr > th {background-color:#f9f9f9;border:#ccc 1px solid;padding:2px}
|
.inner-content table > * > tr > th, .inner-content table > tr > th {background-color:#f9f9f9;border:#ccc 1px solid;padding:2px}
|
||||||
.inner-content table > * > tr > td, .inner-content table > tr > td {border:#ccc 1px solid;padding:2px}
|
.inner-content table > * > tr > td, .inner-content table > tr > td {border:#ccc 1px solid;padding:2px}
|
||||||
|
|
||||||
|
/* spoiler styles */
|
||||||
|
.spoiler {color: black; background-color:black}
|
||||||
|
.spoiler.revealed {color: #1f2528; background-color: transparent}
|
||||||
|
|
||||||
/* interface styles */
|
/* interface styles */
|
||||||
.nl-list{list-style:none}
|
.nl-list{list-style:none}
|
||||||
|
|
@ -143,6 +147,8 @@ body.dark, .dark input, .dark textarea{background-color: #1f1f1f; color: white}
|
||||||
a.dark-theme-toggle-off{display: none}
|
a.dark-theme-toggle-off{display: none}
|
||||||
.dark a.dark-theme-toggle-off{display: inline}
|
.dark a.dark-theme-toggle-off{display: inline}
|
||||||
.dark a.dark-theme-toggle-on{display: none}
|
.dark a.dark-theme-toggle-on{display: none}
|
||||||
|
.dark .spoiler {color: white; background-color: white}
|
||||||
|
.dark .spoiler.revealed {color: white; background-color: transparent}
|
||||||
|
|
||||||
/* ?????? */
|
/* ?????? */
|
||||||
.wrap_responsive_cells {
|
.wrap_responsive_cells {
|
||||||
|
|
|
||||||
|
|
@ -56,4 +56,5 @@
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="/static/edit.js"></script>
|
<script src="/static/edit.js"></script>
|
||||||
|
<script src="/static/content.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
<div class="nl-new">
|
<div class="nl-new">
|
||||||
<a href="/create/"><button class="submit-primary">{{ T('new-note') }}</button></a>
|
<a href="/create/"><button class="submit-primary">{{ T('new-note') }}</button></a>
|
||||||
<a href="/upload/"><button class="submit-secondary">{{ T('upload-file') }}</button></a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>{{ T('latest-notes') }}</h2>
|
<h2>{{ T('latest-notes') }}</h2>
|
||||||
|
|
@ -31,8 +30,4 @@
|
||||||
<li><a href="/p/most_recent/">{{ T('show-all') }}</a></li>
|
<li><a href="/p/most_recent/">{{ T('show-all') }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2>{{ T('latest-uploads') }}</h2>
|
|
||||||
|
|
||||||
{# TODO security flaw; find a way to do this in a more safe manner! #}
|
|
||||||
{{ gallery|safe }}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li>Number of pages: <strong>{{ notes_count }}</strong></li>
|
<li>Number of pages: <strong>{{ notes_count }}</strong></li>
|
||||||
<li>Number of pages with URL set: <strong>{{ notes_with_url }}</strong></li>
|
<li>Number of pages with URL set: <strong>{{ notes_with_url }}</strong></li>
|
||||||
<li>Number of uploads: <strong>{{ upload_count }}</strong></li>
|
|
||||||
<li>Number of revisions: <strong>{{ revision_count }}</strong></li>
|
<li>Number of revisions: <strong>{{ revision_count }}</strong></li>
|
||||||
<li>Average revisions per page: <strong>{{ (revision_count / notes_count)|round(2) }}</strong></li>
|
<li>Average revisions per page: <strong>{{ (revision_count / notes_count)|round(2) }}</strong></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -3,41 +3,19 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Upload new file</h1>
|
<h1>Upload new file</h1>
|
||||||
|
|
||||||
<p>Types supported: <strong>.jpeg</strong>, <strong>.jpg</strong>, <strong>.png</strong>.</p>
|
<p>Uploads are no more supported. Please use this syntax instead for external images: <code></code>.</p>
|
||||||
|
|
||||||
<form method="POST" enctype="multipart/form-data">
|
<form method="POST" enctype="multipart/form-data">
|
||||||
<div>
|
<div>
|
||||||
<label for="name">Name: </label>
|
<label for="name">Name: </label>
|
||||||
<input type="text" id="name-input" name="name" required maxlength="256">
|
<input type="text" id="name-input" name="name" required maxlength="256" disabled="">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="file">File: </label>
|
<label for="file">File: </label>
|
||||||
<input type="file" id="file-input" name="file" accept="image/jpeg, image/png">
|
<input type="file" id="file-input" name="file" accept="image/jpeg, image/png" disabled="">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="submit" class="submit-primary" value="Upload">
|
<input type="submit" value="Upload" disabled="">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
|
||||||
<script>
|
|
||||||
(function(){
|
|
||||||
function last(a){
|
|
||||||
return a[a.length-1];
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileInput = document.getElementById('file-input');
|
|
||||||
var nameInput = document.getElementById('name-input');
|
|
||||||
fileInput.onchange = function(){
|
|
||||||
var name = last(fileInput.value.split(/[\/\\]/));
|
|
||||||
if(name.indexOf('.') >= 0){
|
|
||||||
name = name.replace(/\..*$/, '');
|
|
||||||
}
|
|
||||||
nameInput.value = name;
|
|
||||||
|
|
||||||
// TODO: add image preview
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
{% block json_info %}<script>window.page_info={{ p.js_info()|tojson|safe }};</script>{% endblock %}
|
{% block json_info %}<script>window.page_info={{ p.js_info()|tojson|safe }};</script>{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<article>
|
||||||
<h1 id="firstHeading">{{ p.title }}</h1>
|
<h1 id="firstHeading">{{ p.title }}</h1>
|
||||||
|
|
||||||
<div class="jump-to-actions">
|
<div class="jump-to-actions">
|
||||||
|
|
@ -28,6 +29,8 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</article>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block actions %}
|
{% block actions %}
|
||||||
|
|
@ -36,3 +39,7 @@
|
||||||
{{ T('last-changed') }} <time datetime="{{ rev.pub_date.isoformat() }}">{{ rev.pub_date.strftime('%B %-d, %Y at %H:%M:%S') }}</time> -
|
{{ T('last-changed') }} <time datetime="{{ rev.pub_date.isoformat() }}">{{ rev.pub_date.strftime('%B %-d, %Y at %H:%M:%S') }}</time> -
|
||||||
{{ T('page-id') }}: {{ p.id }}
|
{{ T('page-id') }}: {{ p.id }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="/static/content.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue