Compare commits

...

5 Commits

19 changed files with 542 additions and 867 deletions

View File

@ -40,6 +40,56 @@ class CommentCollection(admin.Administerable):
return None
comment_markdown_help = """
Allows limited markdown, but no HTML.
**Emphasis:**
* `*italic*` *italic*
* `**bold**` **bold**
* `***bold italic***` ***bold italic***
* `~~strikethrough~~` ~~strikethrough~~
* `` `simple code` `` `simple code`
**Blockquotes:**
*Input:*
```md
> This is a blockquote.
> It can span multiple lines and will appear indented.
```
*Output:*
> This is a blockquote.
> It can span multiple lines and will appear indented.
**Fenced code blocks:**
*Input:*
````md
```
def example(x):
if x >= 2:
do_stuff(x)
else:
do_thing(x)
```
````
*Output:*
```
def example(x):
if x >= 2:
do_stuff(x)
else:
do_thing(x)
```
"""
class Comment(admin.Administerable):
autoform_blacklist = ('id',)
@ -51,7 +101,7 @@ class Comment(admin.Administerable):
captcha_passed = peewee.BooleanField(null=False, default=False, verbose_name="Passed captcha", help_text="Whether the comment passed the captcha challenge.")
moderation_passed = peewee.BooleanField(null=False, default=False, verbose_name="Passed moderation", help_text="Whether the comment passed moderation.")
nick = peewee.CharField(null=False, verbose_name='Your name')
text = markdown.SafeMarkdownTextField(verbose_name="Your message")
text = markdown.SafeMarkdownTextField(verbose_name="Your message", help_text=comment_markdown_help)
@werkzeug.utils.cached_property
def replies(self):

10
main.py
View File

@ -460,7 +460,7 @@ class FrontPage(rendering.Renderable):
else:
stmt = stmt.union_all(select)
stmt = stmt.order_by(stmt.c.created.desc())
stmt = stmt.order_by(stmt.c.created.desc(), stmt.c.name.asc())
results = stmt.dicts()
@ -1215,7 +1215,13 @@ class Page(admin.Administerable):
@rendering.page(title_show=False)
def view_page(path):
return Page.get(Page.path == path)
# thrown Page.DoesNotExist will automatically trigger 404.
page = Page.get(Page.path == path)
if page.published or flask.g.user:
return page
raise Page.DoesNotExist()
class PermaRedirect(admin.Administerable):

View File

@ -169,74 +169,100 @@ def renderable_inline_render(self, tokens, idx, options, env):
def section_core_rule(state):
tokens = []
nesting = 0 # NOTE: Section nesting isn't really a thing, a bool whether a section is opened should suffice
open_next = False # whether to open a new section in the next loop iteration
if not state.inlineMode: # inline rendering shouldn't contain sections
regexp_renderable = re.compile('^#\[.+/.+\]\s*$')
tokens = []
nesting = 0 # NOTE: Section nesting isn't really a thing, a bool whether a section is opened should suffice
open_next = False # whether to open a new section in the next loop iteration
for idx, token in enumerate(state.tokens):
# less-than-stringent hack to identify rules of renderable_plugin.
# this is needed because core rules are evaluated *before* inline
# rules so we only have raw type 'inline' tokens instead of
# 'renderable_inline' ones. the alternative would be using
# the same string parsing logic as the actual plugin, i.e.
# renderable_inline_rule with faked state object, or
# decoupling the parsing from the rule
regexp_renderable = re.compile('^#\[.+/.+\]\s*$')
if open_next:
for idx, token in enumerate(state.tokens):
open_next = False
tokens.append(markdown_it.token.Token('section_open', 'section', 1))
nesting += 1
if idx == 0: # for the first element in the document
if token.type != 'heading_open':
tokens.append(markdown_it.token.Token('section_open', 'section', 1))
nesting += 1
else:
open_next = True
elif token.type == 'heading_open' and token.tag == 'h2':
tokens.append(markdown_it.token.Token('section_close', 'section', -1))
nesting -= 1
elif token.type == 'heading_close' and token.tag == 'h2':
open_next = True
elif token.type == 'inline' and isinstance(token.content, str) and\
re.match(regexp_renderable, token.content): # if current token is an inline renderable
# check for directly following inline renderable
if len(state.tokens) >= idx + 4:
# if we have enough following tokens for the
# </p><p>#[foo/bar] structure to exist
token_check = state.tokens[idx + 3] # +3 to skip paragraph_close/paragraph_open tokens
if token_check.type == 'inline' and isinstance(token_check.content, str) and \
re.match(regexp_renderable, token_check.content): # next token is also an inline renderable
open_next = False
else:
open_next = True
else:
if open_next:
open_next = False
tokens.append(markdown_it.token.Token('section_open', 'section', 1))
nesting += 1
if nesting > 0: # if we are *inside* a <section>
if idx == 0: # for the first element in the document
# close the section to break out the inline renderable
# and allow it to take the full width. opening a new
# section afterwards handled through open_next set earlier.
renderable_at_start = False
if len(state.tokens) >= idx + 2: # token after current one exists
# if markdown source begins with a renderable_plugin rule,
# the first *token* will be a paragraph_open, with the
# inline token containing the renderable following
# immediately after it
token_check = state.tokens[idx + 1]
if token_check.type == 'inline' and isinstance(token_check.content, str) and \
re.match(regexp_renderable, token_check.content): # found match for plugin_renderable
renderable_at_start = True
if renderable_at_start:
open_next = False
elif token.type != 'heading_open':
tokens.append(markdown_it.token.Token('section_open', 'section', 1))
nesting += 1
else:
open_next = True
elif token.type == 'heading_open' and token.tag == 'h2':
tokens.append(markdown_it.token.Token('section_close', 'section', -1))
nesting -= 1
tokens.append(token)
elif token.type == 'heading_close' and token.tag == 'h2':
open_next = True
if nesting > 0: # close any remaining nesting levels
for i in range(nesting, 0, -1):
tokens.append(markdown_it.token.Token('section_close', 'section', -1))
elif token.type == 'inline' and isinstance(token.content, str) and\
re.match(regexp_renderable, token.content): # if current token is an inline renderable
state.tokens = tokens
# check for directly following inline renderable
if len(state.tokens) >= idx + 4:
# if we have enough following tokens for the
# </p><p>#[foo/bar] structure to exist
token_check = state.tokens[idx + 3] # +3 to skip paragraph_close/paragraph_open tokens
if token_check.type == 'inline' and isinstance(token_check.content, str) and \
re.match(regexp_renderable, token_check.content): # found match for plugin_renderable
open_next = False
else:
open_next = True
else:
open_next = False
if nesting > 0: # if we are *inside* a <section>
# close the section to break out the inline renderable
# and allow it to take the full width. opening a new
# section afterwards handled through open_next set earlier.
tokens.append(markdown_it.token.Token('section_close', 'section', -1))
nesting -= 1
tokens.append(token)
if nesting > 0: # close any remaining nesting levels
for i in range(nesting, 0, -1):
tokens.append(markdown_it.token.Token('section_close', 'section', -1))
state.tokens = tokens
def section_open(self, tokens, idx, options, env):
return '<section>'
@ -248,9 +274,9 @@ def section_plugin(md):
# splits output into <section>s, based on <h2> placements
md.core.ruler.push("section_core", section_core_rule)
md.add_render_rule("section_open", section_open)
md.add_render_rule("section_close", section_close)
md.core.ruler.push('section_core', section_core_rule)
md.add_render_rule('section_open', section_open)
md.add_render_rule('section_close', section_close)
md_unsafe = markdown_it.MarkdownIt('commonmark', {'highlight': code_highlight})
md_unsafe.enable('table')

View File

@ -161,8 +161,6 @@ class TagCollection(admin.Administerable):
@werkzeug.utils.cached_property
def parent(self):
import pudb; pudb.set_trace()
stmt = None
for taggable_name, taggable in Taggable.__class_descendants__.items():

View File

@ -638,7 +638,7 @@ nav a.trail {
background-color: var(--color-highlight-inactive);
color: var(--color-highlight-contrast);
}
body > .menus .searchminiform button:hover,
body > .menus .searchminiform button:focus
{
@ -783,6 +783,34 @@ article.errorpage > .content > .message {
scale: 1.2;
}
article.tagcollection ul {
list-style: none;
padding: var(--distance-main);
}
article.tagcollection ul li {
display: inline-block;
}
article.tagcollection ul li a {
display: inline-block;
padding: var(--distance-minor);
background: var(--color-highlight-inactive);
color: var(--color-highlight-contrast);
font-weight: 300;
text-decoration: none;
transition-duration: 0.2s;
transition-timing-function: var(--ease-bounce);
transition-property: scale, background-color;
}
article.tagcollection ul li a:hover,
article.tagcollection ul li a:focus {
background: var(--color-highlight);
scale: 1.1;
}
/* common constructs */
article .unpublished {
@ -913,22 +941,53 @@ a.download:active {
.mode-teaser,
.mode-admin-teaser {
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative; /* enables z-index */
background: var(--color-bg-alt);
transition: scale 0.2s var(--ease-bounce);
backdrop-filter: blur(8px);
}
.mode-teaser a {
.mode-teaser > .content,
.mode-teaser > .content .teaser-link,
.mode-admin-teaser > .content,
.mode-admin-teaser > .content .teaser-link {
display: flex;
flex-direction: column;
}
.mode-teaser > .content,
.mode-teaser > .content .teaser-link,
.mode-admin-teaser > .content,
.mode-admin-teaser > .content .teaser-link {
flex-grow: 1;
}
.mode-teaser a,
.mode-admin-teaser a {
text-decoration: none;
color: var(--color-text-main);
}
.mode-teaser:hover {
scale: 1.1;
.mode-teaser:hover,
.mode-teaser:has(:focus),
.mode-admin-teaser:hover,
.mode-admin-teaser:has(:focus) {
scale: 1.05;
z-index: 50;
}
.mode-teaser > .content .teaser-link section {
background: transparent;
margin: 0;
padding: 0;
}
/*.mode-teaser > :first-child,
.mode-admin-teaser > :first-child {*/
/* keeping original selector around in case it was actually a smart thing to do */
@ -957,8 +1016,7 @@ a.download:active {
margin: 0 auto;
}
.mode-teaser .lead-image .description,
.mode-admin-teaser .lead-image .description {
.lead-image .description {
display: none;
}
@ -1041,11 +1099,7 @@ article.administerable > header > article.upload {
.listing > ul > li > .mode-teaser,
.listing > ul > li > .mode-admin-teaser {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
height: 100%; /* makes all teasers in the same row have the same height */
}
/* renderables */
@ -1070,6 +1124,7 @@ article.administerable > .content section {
font-size: 120%;
text-align: justify;
line-height: 120%;
}
article.administerable > .content section:last-child {
@ -1092,13 +1147,16 @@ article.administerable > .content section:has(p:empty:only-child) {
/* uploads */
article.upload {
margin-bottom: var(--distance-main);
}
article.upload .description {
position: absolute;
translate: 0 -100%;
max-width: var(--distance-typographic-width);
margin: 0 auto;
padding: var(--distance-main);
background: var(--color-bg-alt);
backdrop-filter: blur(8px);
padding: var(--distance-main);
font-style: italic;
}
article.image img {
@ -1122,16 +1180,26 @@ article.audio audio {
background: #444;
}
article.audio audio::before {
content: " ";
display: block;
width: 4rem;
height: 4rem;
background: #f00;
article.image .description {
position: absolute;
left: 50vw;
translate: -50% -100%;
}
article.file .description {
padding: 0;
padding-left: 4.5rem;
translate: 0 100%;
background: transparent;
backdrop-filter: none;
pointer-events: none;
}
.fileinfo {
height: 4rem; /* remove -100% y-translated filesize from height */
width: 100%;
max-width: var(--distance-typographic-width);
margin: 0 auto;
}
.fileinfo .filesize {
@ -1508,25 +1576,6 @@ body > .menus .searchminiform .field-help-wrapper {
background: var(--color-bg-alt);
}
/*.propagandapiece .files > .fileinfo {
display: table-row;
}
.propagandapiece .files > .fileinfo > * {
display: table-cell;
background: var(--color-highlight);
color: var(--color-highlight-contrast);
padding: var(--distance-minor);
}
.propagandapiece .files > .fileinfo > *:first-child {
font-weight: bold;
}
.propagandapiece .files > .fileinfo > *:last-child {
text-align: right;
}*/
.propagandapiece .modal article,
.propagandapiece .modal .content {
min-height: 100vh;

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 2.5 MiB

View File

@ -1,10 +1,12 @@
<!doctype html>
<html>
<head>
<title>{% if title %}{{ title }} | {{ site_name }}{% else %}{{ site_name }}{% endif %}</title>
<title>{% if title %}{{ title }} | {{ site_name }}{% else %}{{ site_name }}{% endif %}</title>
<link rel="stylesheet" href="/theme/css/main.css" />
<link rel="stylesheet" href="/theme/css/highlight.css" />
<link rel="stylesheet" href="/theme/css/nativeads.js.css" />
</head>
<body
{% if content is renderable %}

View File

@ -1,4 +1,3 @@
{% extends 'default/templates/renderable/audio.html' %}
{% block header %}{% endblock %}
{% block fileinfo %}{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends 'default/templates/renderable/upload.html' %}
{% block content %}
{% block fileview %}
<audio
controls
{% if content.description %}
@ -10,5 +10,4 @@
<source src={{ content.url('raw') }} type="{{ content.media_type }}" />
Audio playback not supported by your browser.
</audio>
{% block fileinfo %}{{ super() }}{% endblock %}
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends 'default/templates/renderable/upload.html' %}
{% block content %}
{{ self.hints() }}
{{ self.fileview() }}
{{ self.description() }}
{{ self.fileinfo() }} {# fileinfo is essentially fileview for raw files #}
{% endblock %}

View File

@ -1,21 +1,11 @@
{% extends 'default/templates/renderable/upload.html' %}
{% block content %}
{% if not content.published %}
<span class="warning unpublished" title="This {{ content._lowerclass }} has NOT been published."></span>
{% endif %}
<img
src="{{ content.url('raw') }}"
{% if content.description %}
alt="{{ content.description }}"
title="{{ content.description }}"
{% endif %}
/>
{% block fileview %}
<img
src="{{ content.url('raw') }}"
{% if content.description %}
<div class="description">{{ content.description.render() }}</div>
alt="{{ content.description }}"
title="{{ content.description }}"
{% endif %}
{% block fileinfo %}{{ super() }}{% endblock %}
/>
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends 'default/templates/renderable/leadimagecontent.html' %}
{% block content %}
<a href="{{ content.url() }}">
<a class="teaser-link" href="{{ content.url() }}">
<header>
<div class="lead-image">
{% if content.lead_image_id %}

View File

@ -2,9 +2,11 @@
{% block content %}
<div>
<span>Match path:</span> <span class="match-path path">{{ content.match_path }}</span>
<span>Match path:</span> <code class="match-path path">{{ content.match_path }}</code>
</div>
<div>
<span>Redirect path:</span> <span class="redirect-path path">{{ content.redirect_path }}</span>
<span>Redirect path:</span> <code class="redirect-path path">{{ content.redirect_path }}</code>
</div>
<a class="permaredirect-link" target="_blank" href="{{ content.match_path }}">Try it</a>
{% endblock %}

View File

@ -2,7 +2,7 @@
{% block content %}
<a href="{{ content.url() }}">
<a class="teaser-link" href="{{ content.url() }}">
<div class="preview">
{% for piece in content.pieces_ordered %}

View File

@ -1,3 +1,4 @@
{% if config.DEBUG %}<!-- template begins: {{ self._TemplateReference__context.name }} -->{% endif %}
<article class="{{ content.css_classes }} mode-{{ mode }}">
{% if self.header()|trim %}
@ -20,7 +21,14 @@
{% if self.footer()|trim %}
<footer>
{% block footer %}{% endblock %}
{% block footer %}
{% if self.meta()|trim %}
<div class="meta">
{% block meta %}{% endblock %}
</div>
{% endif %}
{% endblock %}
</footer>
{% endif %}
</article>
{% if config.DEBUG %}<!-- template ends: {{ self._TemplateReference__context.name }} -->{% endif %}

View File

@ -1,7 +1,14 @@
{% extends 'default/templates/renderable/administerable.html' %}
{% block content %}
<a class="url" href="{{ content.url }}" style="background-image: url('/theme/dynamic/scoredlink/{{ content.id }}');">{{ content.url }}</a>
<a
class="url"
href="{{ content.url }}"
target="_blank"
rel="noreferrer noopener"
style="background-image: url('/theme/dynamic/scoredlink/{{ content.id }}');"
title="This URL loads data from {{ content.external_site_count }} external sites (median: {{ content.median }}, mean: {{ content.mean }}, min {{ content.min }}, max: {{ content.max }}, set size: {{ content.set_size }})"
>{{ content.url }}</a>
<ul class="fields">
<li class="field external-site-count">

View File

@ -1,8 +1,11 @@
{% extends 'default/templates/renderable/administerable.html' %}
{% block footer %}
{% block meta %}
{{ super() }}
{% if content.tag_collection_id and content.tag_collection.items|length %}
{{ content.tag_collection.render('inline') }}
{% endif %}
{% endblock %}

View File

@ -2,9 +2,20 @@
{% block content %}
{% if not content.published %}
<span class="warning unpublished" title="This {{ content._lowerclass }} has NOT been published."></span>
{% endif %}
{% block hints %}
{% if not content.published %}
<span class="warning unpublished" title="This {{ content._lowerclass }} has NOT been published."></span>
{% endif %}
{% endblock %}
{% block fileview %}
{% endblock %}
{% block description %}
{% if content.description %}
<div class="description">{{ content.description.render('inline') }}</div>
{% endif %}
{% endblock %}
{% block fileinfo %}
<div class="fileinfo">

View File

@ -1,6 +1,6 @@
{% extends 'default/templates/renderable/upload.html' %}
{% block content %}
{% block fileview %}
<video
controls
{% if content.description %}
@ -10,5 +10,4 @@
<source src={{ content.url('raw') }} type="{{ content.media_type }}" />
Video playback not supported by your browser.
</video>
{% block fileinfo %}{{ super() }}{% endblock %}
{% endblock %}