Compare commits
5 Commits
3d6259cdcd
...
f02a85dba5
Author | SHA1 | Date | |
---|---|---|---|
f02a85dba5 | |||
fabe3b5742 | |||
58221b2e20 | |||
2b54f90f05 | |||
4b639a8211 |
@ -31,10 +31,12 @@ class CommentCollection(admin.Administerable):
|
|||||||
# NOTE: This is essentially a copy of TagCollection.parent
|
# NOTE: This is essentially a copy of TagCollection.parent
|
||||||
for cls in Commentable.__class_descendants__.values():
|
for cls in Commentable.__class_descendants__.values():
|
||||||
|
|
||||||
try:
|
if cls not in app.models_abstract:
|
||||||
return cls.select().where(cls.comment_collection == self).get()
|
|
||||||
except cls.DoesNotExist:
|
try:
|
||||||
pass # continue to next loop iteration
|
return cls.select().where(cls.comment_collection == self).get()
|
||||||
|
except cls.DoesNotExist:
|
||||||
|
pass # continue to next loop iteration
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
47
main.py
47
main.py
@ -535,10 +535,18 @@ class ScoredLink(admin.Administerable):
|
|||||||
autoform_blacklist = ['id', 'external_site_count', 'last_scrape']
|
autoform_blacklist = ['id', 'external_site_count', 'last_scrape']
|
||||||
hue_range = (80,320) # hue (degrees) from 0 to .max
|
hue_range = (80,320) # hue (degrees) from 0 to .max
|
||||||
|
|
||||||
url = peewee.CharField(unique=True) # TODO: Regexp constraint
|
url = peewee.CharField(unique=True) # TODO: Regexp constraint # FIXME: overrides url function
|
||||||
external_site_count = peewee.IntegerField(null=True)
|
external_site_count = peewee.IntegerField(null=True)
|
||||||
last_scrape = peewee.DateTimeField(null=True, default=None)
|
last_scrape = peewee.DateTimeField(null=True, default=None)
|
||||||
|
|
||||||
|
explanation_external_site_count = markdown.SafeMarkdownString("""
|
||||||
|
How many **third-party sites** the linked content loads data from.
|
||||||
|
|
||||||
|
This is relevant to your privacy because each of those third-party sites
|
||||||
|
gets – at the very least – to see that you visited the linked content.
|
||||||
|
|
||||||
|
*Lower* is better, **0** is *ideal*.""")
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -595,6 +603,9 @@ class ScoredLink(admin.Administerable):
|
|||||||
@property
|
@property
|
||||||
def external_site_counts(self):
|
def external_site_counts(self):
|
||||||
|
|
||||||
|
# TODO: We should probably cache this
|
||||||
|
# (and invalidate/rebuild cache at least in .save?)
|
||||||
|
|
||||||
cls = self.__class__
|
cls = self.__class__
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -620,6 +631,10 @@ class ScoredLink(admin.Administerable):
|
|||||||
def mean(self):
|
def mean(self):
|
||||||
return sum(self.external_site_counts) / float(len(self.external_site_counts))
|
return sum(self.external_site_counts) / float(len(self.external_site_counts))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min(self):
|
||||||
|
return min(self.external_site_counts)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max(self):
|
def max(self):
|
||||||
return max(self.external_site_counts)
|
return max(self.external_site_counts)
|
||||||
@ -707,7 +722,7 @@ def renderer_link_close(self, tokens, idx, options, env):
|
|||||||
link.save(force_insert=True)
|
link.save(force_insert=True)
|
||||||
|
|
||||||
if isinstance(link.external_site_count, int):
|
if isinstance(link.external_site_count, int):
|
||||||
icon = f'<img class="scoredlink-icon" src="/theme/dynamic/scoredlink/{link.id}" title="This URL loads data from {link.external_site_count} external sites (median: {link.median}, mean: {link.mean}, set size: {link.set_size})" />'
|
icon = f'<img class="scoredlink-icon" src="/theme/dynamic/scoredlink/{link.id}" title="This URL loads data from {link.external_site_count} external sites (median: {link.median}, mean: {link.mean}, min {link.min}, max: {link.max}, set size: {link.set_size})" />'
|
||||||
|
|
||||||
if icon:
|
if icon:
|
||||||
return f'</a>{icon}'
|
return f'</a>{icon}'
|
||||||
@ -743,15 +758,12 @@ class Article(LeadImageContent):
|
|||||||
teaser = markdown.MarkdownTextField(null=False, verbose_name='Teaser')
|
teaser = markdown.MarkdownTextField(null=False, verbose_name='Teaser')
|
||||||
text = markdown.MarkdownTextField(null=False, verbose_name='Text')
|
text = markdown.MarkdownTextField(null=False, verbose_name='Text')
|
||||||
|
|
||||||
@FrontPage.include
|
class LinkForm(tagging.TaggableForm):
|
||||||
@app.expose('/project/')
|
|
||||||
class Project(LeadImageContent):
|
|
||||||
|
|
||||||
title = markdown.SafeMarkdownCharField(null=False, verbose_name='Title')
|
"""
|
||||||
teaser = markdown.MarkdownTextField(null=False, verbose_name='Teaser')
|
More comfortable handing for a 'link' field that's a foreign key to ScoredLink.
|
||||||
text = markdown.MarkdownTextField(null=False, verbose_name='Text')
|
Used by Project and CuratedContent.
|
||||||
|
"""
|
||||||
class CuratedArtForm(tagging.TaggableForm):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
@ -791,11 +803,24 @@ class CuratedArtForm(tagging.TaggableForm):
|
|||||||
|
|
||||||
return super().process(submit)
|
return super().process(submit)
|
||||||
|
|
||||||
|
|
||||||
|
@FrontPage.include
|
||||||
|
@app.expose('/project/')
|
||||||
|
class Project(LeadImageContent):
|
||||||
|
|
||||||
|
autoform_class = LinkForm
|
||||||
|
autoform_blacklist = ('id', 'created', 'comment_collection_id', 'link_id')
|
||||||
|
|
||||||
|
title = markdown.SafeMarkdownCharField(null=False, verbose_name='Title')
|
||||||
|
teaser = markdown.MarkdownTextField(null=False, verbose_name='Teaser')
|
||||||
|
text = markdown.MarkdownTextField(null=False, verbose_name='Text')
|
||||||
|
link = peewee.ForeignKeyField(ScoredLink, null=True, verbose_name='Link', help_text='URL for the original content, if any')
|
||||||
|
|
||||||
@FrontPage.include
|
@FrontPage.include
|
||||||
@app.expose('/curated/')
|
@app.expose('/curated/')
|
||||||
class CuratedArt(LeadImageContent):
|
class CuratedArt(LeadImageContent):
|
||||||
|
|
||||||
autoform_class = CuratedArtForm
|
autoform_class = LinkForm
|
||||||
autoform_blacklist = ('id', 'created', 'comment_collection_id', 'link_id')
|
autoform_blacklist = ('id', 'created', 'comment_collection_id', 'link_id')
|
||||||
|
|
||||||
title = markdown.SafeMarkdownCharField(null=False, verbose_name='Title')
|
title = markdown.SafeMarkdownCharField(null=False, verbose_name='Title')
|
||||||
|
@ -160,7 +160,7 @@ def renderable_inline_render(self, tokens, idx, options, env):
|
|||||||
# in MarkdownString.render.
|
# in MarkdownString.render.
|
||||||
body = str(instance.render(mode))
|
body = str(instance.render(mode))
|
||||||
|
|
||||||
# this is ahack to break renderables out of <section>s
|
# this is a hack to break renderables out of <section>s
|
||||||
# and avoid invalid </section></p><section>…</p>
|
# and avoid invalid </section></p><section>…</p>
|
||||||
# any resulting "empty" <section><p></p></section> hidden via css
|
# any resulting "empty" <section><p></p></section> hidden via css
|
||||||
return f'</p></section>{ body }<section><p>'
|
return f'</p></section>{ body }<section><p>'
|
||||||
|
@ -662,10 +662,14 @@ article .unpublished {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background: var(--color-bg-alt);
|
background: var(--color-bg-alt);
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
color: var(--color-error);
|
color: var(--color-error-inactive);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal a[href="#close"]:hover {
|
||||||
|
color: var(--color-error);
|
||||||
|
}
|
||||||
|
|
||||||
.modal img {
|
.modal img {
|
||||||
/* enable images to be shown at full scale, even if too big for viewport */
|
/* enable images to be shown at full scale, even if too big for viewport */
|
||||||
max-width: none !important;
|
max-width: none !important;
|
||||||
@ -687,7 +691,8 @@ article .unpublished {
|
|||||||
background: var(--color-highlight-inactive);
|
background: var(--color-highlight-inactive);
|
||||||
color: var(--color-highlight-contrast);
|
color: var(--color-highlight-contrast);
|
||||||
cursor: s-resize;
|
cursor: s-resize;
|
||||||
font-size: 1.5rem;
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
transition: background 0.2s ease-in-out;
|
transition: background 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -855,15 +860,21 @@ article.renderable > .content > h6 {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
article.administerable > .content > section {
|
article.administerable > .content section {
|
||||||
background: hsla(0 0 5 / 90%);
|
background: hsla(0 0 5 / 90%);
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
max-width: var(--distance-typographic-width);
|
max-width: var(--distance-typographic-width);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: var(--distance-main);
|
padding: var(--distance-main);
|
||||||
|
margin-bottom: var(--distance-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
article.administerable > .content > section:has(p:empty:only-child) {
|
article.administerable > .content section:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
article.administerable > .content section:has(p:empty:only-child) {
|
||||||
|
|
||||||
/* this is a hack that takes care of an edge case for another hack.
|
/* this is a hack that takes care of an edge case for another hack.
|
||||||
* namely, the inline_renderable and section markdown plugins have
|
* namely, the inline_renderable and section markdown plugins have
|
||||||
@ -911,6 +922,7 @@ article.gallery {}
|
|||||||
article.gallery .gallery-preview {
|
article.gallery .gallery-preview {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
article.gallery img.preview {
|
article.gallery img.preview {
|
||||||
@ -1183,13 +1195,133 @@ body > .menus .searchminiform .field-help-wrapper {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ScoredLink view */
|
||||||
|
.scoredlink {
|
||||||
|
max-width: var(--distance-typographic-width);
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.scoredlink ul.fields {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scoredlink a.url {
|
||||||
|
/*background-image: url('/theme/svg/link.svg#active');*/
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
background-position-x: right;
|
||||||
|
padding-right: 1.5em; /* leave enough space for background icon */
|
||||||
|
font-size: 150%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scoredlink .field.external-site-count {
|
||||||
|
font-size: 120%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scoredlink .field .value {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scoredlink .field .explanation {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: var(--distance-main);
|
||||||
|
}
|
||||||
|
|
||||||
/* Propaganda view */
|
/* Propaganda view */
|
||||||
|
|
||||||
|
.propaganda .text,
|
||||||
|
.propaganda .pieces {
|
||||||
|
margin-top: var(--distance-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.propaganda.mode-full .propagandapiece {
|
||||||
|
/*padding: var(--distance-main) 0;*/
|
||||||
|
margin-bottom: var(--distance-main);
|
||||||
|
padding-bottom: 1px; /* hack to ensure margin doesn't spill out of container */
|
||||||
|
}
|
||||||
|
|
||||||
.propagandapiece .preview img {
|
.propagandapiece .preview img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.propagandapiece:nth-child(odd) {
|
||||||
|
background: var(--color-bg-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.propagandapiece:nth-child(odd) > .content > section {
|
||||||
|
background: transparent;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.propagandapiece:nth-child(even) {
|
||||||
|
}
|
||||||
|
|
||||||
|
.propagandapiece .files {
|
||||||
|
width: var(--distance-typographic-width);
|
||||||
|
padding: var(--distance-main);
|
||||||
|
margin: var(--distance-main) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.propagandapiece:nth-child(odd) .files {
|
||||||
|
background: var(--color-bg-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.propagandapiece:nth-child(even) .files {
|
||||||
|
background: var(--color-bg-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.propagandapiece .files > .fileinfo {
|
||||||
|
height: 4rem; /* remove -100% y-translated filesize from height */
|
||||||
|
}
|
||||||
|
|
||||||
|
.propagandapiece .files > .fileinfo a.download {
|
||||||
|
display: block;
|
||||||
|
padding-left: 4.5rem;
|
||||||
|
line-height: 4rem; /* smaller size, determines background size */
|
||||||
|
background-image: url('/theme/svg/download.svg');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.propagandapiece .files > .fileinfo a.download:hover {
|
||||||
|
background-image: url('/theme/svg/download.svg#hover');
|
||||||
|
}
|
||||||
|
|
||||||
|
.propagandapiece .files > .fileinfo a.download:active {
|
||||||
|
background-image: url('/theme/svg/download.svg#pressed');
|
||||||
|
}
|
||||||
|
|
||||||
|
.propagandapiece .files .fileinfo .filesize {
|
||||||
|
display: block;
|
||||||
|
margin-left: 4.5rem;
|
||||||
|
translate: 0 -100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*.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 article,
|
||||||
.propagandapiece .modal .content {
|
.propagandapiece .modal .content {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@ -1208,9 +1340,29 @@ body > .menus .searchminiform .field-help-wrapper {
|
|||||||
.propaganda.mode-teaser .preview {
|
.propaganda.mode-teaser .preview {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.propaganda.mode-teaser .preview > * {
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-basis: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.propaganda.mode-teaser .preview > :first-child {
|
||||||
|
flex-basis: 100%;
|
||||||
|
flex-grow: 4;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.propaganda.mode-teaser .preview > :nth-child(2),
|
||||||
|
.propaganda.mode-teaser .preview > :nth-child(3) {
|
||||||
|
flex-basis: 50%;
|
||||||
|
flex-grow: 2;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.propaganda.mode-teaser .preview .content,
|
.propaganda.mode-teaser .preview .content,
|
||||||
.propaganda.mode-teaser .preview article.upload,
|
.propaganda.mode-teaser .preview article.upload,
|
||||||
.propaganda.mode-teaser .preview img {
|
.propaganda.mode-teaser .preview img {
|
||||||
|
55
themes/default/assets/svg/download.svg
Normal file
55
themes/default/assets/svg/download.svg
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
rect {
|
||||||
|
fill: #580;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hover:target rect {
|
||||||
|
fill: url(#fill-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pressed:target rect {
|
||||||
|
fill: #af0;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<g id="hover">
|
||||||
|
<g id="pressed">
|
||||||
|
|
||||||
|
<mask id="shape">
|
||||||
|
<path
|
||||||
|
fill="white"
|
||||||
|
d="m 20.04638,29.992405 h -9.963128 c -2.083252,0 -3.083252,2 -0.03436,5.048888 L 45.007595,70 C 50,74.992405 50,74.992405 55.045878,69.946527 L 90.032052,34.960353 C 93,31.992405 92,29.992405 90.019847,29.992405 H 80 v -15 c 0,-5 -5,-10 -10,-10 H 10 c 5,0 10,5 10,10"
|
||||||
|
id="path1" />
|
||||||
|
<path
|
||||||
|
fill="white"
|
||||||
|
d="m 5,95 h 90 c 5,0 5,-15 -10,-15 H 15 C 0,80 0,95 5,95 Z"
|
||||||
|
id="path3" />
|
||||||
|
</mask>
|
||||||
|
|
||||||
|
<linearGradient id="fill-hover" x1="0" x2="0" y1="-100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#580" />
|
||||||
|
<stop offset="30%" stop-color="#af0" />
|
||||||
|
<stop offset="60%" stop-color="#580" />
|
||||||
|
<animate attributeName="y1" dur="2000ms" from="-100%" to="100%" repeatCount="indefinite" />
|
||||||
|
<animate attributeName="y2" dur="2000ms" from="100%" to="200%" repeatCount="indefinite" />
|
||||||
|
</linearGradient>
|
||||||
|
|
||||||
|
<rect x="0" y="0" width="100" height="100" mask="url(#shape)" />
|
||||||
|
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -17,6 +17,8 @@
|
|||||||
{% if mode == 'full' and content.comment_enabled %}
|
{% if mode == 'full' and content.comment_enabled %}
|
||||||
<div class="comments">
|
<div class="comments">
|
||||||
|
|
||||||
|
<h2>Comments</h2>
|
||||||
|
|
||||||
{{ content.comment_form().render() }}
|
{{ content.comment_form().render() }}
|
||||||
|
|
||||||
{% for comment in content.comments %}
|
{% for comment in content.comments %}
|
||||||
|
11
themes/default/templates/renderable/curatedart-full.html
Normal file
11
themes/default/templates/renderable/curatedart-full.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'default/templates/renderable/leadimagecontent.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{{ super() }}
|
||||||
|
|
||||||
|
{% if content.link %}
|
||||||
|
{{ content.link.render() }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
11
themes/default/templates/renderable/project-full.html
Normal file
11
themes/default/templates/renderable/project-full.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'default/templates/renderable/leadimagecontent.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{{ super() }}
|
||||||
|
|
||||||
|
{% if content.link %}
|
||||||
|
{{ content.link.render() }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -9,6 +9,7 @@
|
|||||||
{{ piece.render('inline-teaser') }}
|
{{ piece.render('inline-teaser') }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ content.teaser.render() }}
|
{{ content.teaser.render() }}
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
|
@ -2,10 +2,14 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{{ content.text.render() }}
|
<div class="text">
|
||||||
|
{{ content.text.render() }}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% for piece in content.pieces_ordered %}
|
<div class="pieces">
|
||||||
{{ piece.render('full') }}
|
{% for piece in content.pieces_ordered %}
|
||||||
{% endfor %}
|
{{ piece.render('full') }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
{% for item in content.items_ordered[1:] %}
|
{% for item in content.items_ordered[1:] %}
|
||||||
{% if item.upload %}
|
{% if item.upload %}
|
||||||
<div class="fileinfo">
|
<div class="fileinfo">
|
||||||
<a href="{{ item.upload.url('raw') }}" target="_blank">{{ item.upload.filename }}</a>
|
<a class="download" href="{{ item.upload.url('download') }}" target="_blank">{{ item.upload.filename }}</a>
|
||||||
<span class="filesize">{{ item.upload.filesize|filesizeformat }}</span>
|
<span class="filesize">{{ item.upload.filesize|filesizeformat }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -1,9 +1,35 @@
|
|||||||
{% extends 'default/templates/renderable/administerable.html' %}
|
{% extends 'default/templates/renderable/administerable.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<ul>
|
<a class="url" href="{{ content.url }}" style="background-image: url('/theme/dynamic/scoredlink/{{ content.id }}');">{{ content.url }}</a>
|
||||||
<li class="url">URL: {{ content.url }}</li>
|
<ul class="fields">
|
||||||
<li class="external-site-count">External site count: {{ content.external_site_count }}</li>
|
<li class="field external-site-count">
|
||||||
<li class="last-scrape">Last scrape: {{ content.last_scrape or 'never' }}</li>
|
|
||||||
</ul>
|
<span class="name">External site count</span>
|
||||||
|
<span class="value" style="color: {{ content.color.css() }};">{{ content.external_site_count }}</span>
|
||||||
|
|
||||||
|
<input id="scoredlink-{{ content.id }}-external-site-count-toggle" class="toggle-input" type="checkbox" />
|
||||||
|
<label for="scoredlink-{{ content.id }}-external-site-count-toggle" class="toggle-label" title="{{ content.__class__.explanation_external_site_count }}">?</label>
|
||||||
|
<div class="explanation">
|
||||||
|
|
||||||
|
{{ content.__class__.explanation_external_site_count.render() }}
|
||||||
|
|
||||||
|
<span>The statistical spread of our dataset currently looks like this:</span>
|
||||||
|
<ul class="statistic-info">
|
||||||
|
<li class="stat"><span>Median:</span> <span class="value">{{ content.median }}</span></li>
|
||||||
|
<li class="stat"><span>Mean:</span> <span class="value">{{ content.mean }}</span></li>
|
||||||
|
<li class="stat"><span>Min:</span> <span class="value">{{ content.min }}</span></li>
|
||||||
|
<li class="stat"><span>Max:</span> <span class="value">{{ content.max }}</span></li>
|
||||||
|
<li class="stat"><span>Set size:</span> <span class="value">{{ content.set_size }}</span></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="field last-scrape">
|
||||||
|
<span class="name">Last scrape</span>
|
||||||
|
<span class="value">{{ content.last_scrape|prettydate or 'never' }}</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</dl>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
15
upload.py
15
upload.py
@ -113,8 +113,14 @@ class Upload(admin.Named):
|
|||||||
|
|
||||||
def url(self, mode='raw'):
|
def url(self, mode='raw'):
|
||||||
|
|
||||||
if mode == 'raw':
|
if mode in ('raw', 'download'):
|
||||||
return f'/upload/{self.subdirectory}/{self.name}/' # FIXME: make this less hacky, if possible
|
|
||||||
|
url = f'/upload/{self.subdirectory}/{self.name}/' # FIXME: make this less hacky, if possible
|
||||||
|
|
||||||
|
if mode == 'download':
|
||||||
|
url += '?download'
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
||||||
return super().url(mode=mode)
|
return super().url(mode=mode)
|
||||||
|
|
||||||
@ -291,6 +297,9 @@ def serve_upload(class_key, name):
|
|||||||
instance = cls.load(name) # this failing is handled by error_catchall
|
instance = cls.load(name) # this failing is handled by error_catchall
|
||||||
|
|
||||||
if flask.g.user or instance.published:
|
if flask.g.user or instance.published:
|
||||||
return flask.send_from_directory(f'upload/{cls.subdirectory}', instance.filename)
|
|
||||||
|
download = 'download' in flask.request.args
|
||||||
|
|
||||||
|
return flask.send_from_directory(f'upload/{cls.subdirectory}', instance.filename, as_attachment=download)
|
||||||
|
|
||||||
flask.abort(404)
|
flask.abort(404)
|
||||||
|
7
util.py
7
util.py
@ -186,14 +186,11 @@ class Color(object):
|
|||||||
self.alpha
|
self.alpha
|
||||||
)
|
)
|
||||||
|
|
||||||
def css_rgb(self):
|
def css(self):
|
||||||
return f'rgb({self.red * 100}% {self.green * 100}% {self.blue * 100}%)'
|
|
||||||
|
|
||||||
def css_rgba(self):
|
|
||||||
"""
|
"""
|
||||||
newstyle rgb(), NOT rgba().
|
newstyle rgb(), NOT rgba().
|
||||||
"""
|
"""
|
||||||
return f'rgb({self.red * 100}% {self.green * 100}% {self.blue * 100}%) / {self.alpha * 100}%'
|
return f'rgb({round(self.red * 255)} {round(self.green * 255)} {round(self.blue * 255)} / {self.alpha * 100}%)'
|
||||||
|
|
||||||
def clone(self):
|
def clone(self):
|
||||||
return Color(red=self.red, green=self.green, blue=self.blue, alpha=self.alpha)
|
return Color(red=self.red, green=self.green, blue=self.blue, alpha=self.alpha)
|
||||||
|
Loading…
Reference in New Issue
Block a user