Remove magic words and uploads, deprecate upload endpoints, style changes, version advance

This commit is contained in:
Yusur 2022-05-10 22:35:21 +02:00
parent 5d18cee75e
commit 733ea8cce1
8 changed files with 87 additions and 156 deletions

141
app.py
View file

@ -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 '*&laquo; 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
View file

@ -0,0 +1,8 @@
(function() {
for (let i of document.getElementsByClassName('spoiler')) {
i.onclick = function() {
this.classList.toggle('revealed');
}
};
})();

View file

@ -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 {

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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>

View file

@ -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>![alt text](https://www.example.com/path/to/image.jpg)</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 %}

View file

@ -5,29 +5,32 @@
{% 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 %}
<h1 id="firstHeading">{{ p.title }}</h1> <article>
<h1 id="firstHeading">{{ p.title }}</h1>
<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>
</div> </div>
{% block history_nav %}{% endblock %} {% block history_nav %}{% endblock %}
<div class="inner-content"> <div class="inner-content">
{{ rev.html()|safe }} {{ rev.html()|safe }}
</div> </div>
{% if p.tags %}
<div class="page-tags">
<p>{{ T('tags') }}:</p>
<ul>
{% for tag in p.tags %}
<li><a href="/tags/{{ tag.name }}/">#{{ tag.name }}</a> <span class="tag-count">({{ tag.popularity() }})</span></li>
{% endfor %}
</ul>
</div>
{% endif %}
</article>
{% if p.tags %}
<div class="page-tags">
<p>{{ T('tags') }}:</p>
<ul>
{% for tag in p.tags %}
<li><a href="/tags/{{ tag.name }}/">#{{ tag.name }}</a> <span class="tag-count">({{ tag.popularity() }})</span></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% 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 %}