Merge pull request #66 from garbados/master

Bring Brutaldon mirror up to date

Note the primary repository is https://git.carcosa.net/jmcbray/brutaldon
This commit is contained in:
GCU Prosthetic Conscience 2020-01-31 13:58:33 -05:00 committed by GitHub
commit 00e35409ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 557 additions and 137 deletions

1
.gitignore vendored
View File

@ -112,3 +112,4 @@ pip-selfcheck.json
node_modules node_modules
/TAGS /TAGS
.vscode .vscode
package-lock.json

View File

@ -27,4 +27,3 @@ inscriptis = "*"
lxml = "*" lxml = "*"
[dev-packages] [dev-packages]

View File

@ -0,0 +1,8 @@
from django.urls import reverse
def bookmarklet_url(request):
share_url = request.build_absolute_uri(reverse("share"))
return {
"bookmarklet_url": f"javascript:location.href='{share_url}?url='+encodeURIComponent(location.href)+';title='+encodeURIComponent(document.title)"
}

View File

@ -38,6 +38,7 @@ class PreferencesForm(forms.ModelForm):
"click_to_load", "click_to_load",
"lightbox", "lightbox",
"filter_notifications", "filter_notifications",
"bundle_notifications",
"poll_frequency", "poll_frequency",
] ]

View File

@ -33,6 +33,13 @@ def set_up_default_themes(apps, schema_editor):
is_brutalist=False, is_brutalist=False,
) )
solar.save() solar.save()
material = Theme(
name="Material",
main_css="css/bulmaswatch-materia.min.css",
tweaks_css="css/brutaldon-material.css",
is_brutalist=False,
)
material.save()
brutalism = Theme( brutalism = Theme(
name="FULLBRUTALISM", main_css="css/fullbrutalism.css", is_brutalist=True name="FULLBRUTALISM", main_css="css/fullbrutalism.css", is_brutalist=True
) )

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.7 on 2019-11-04 23:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("brutaldon", "0022_auto_20190506_0938")]
operations = [
migrations.AddField(
model_name="preference",
name="bundle_notifications",
field=models.BooleanField(
default=False,
help_text="Collapse together boosts or likes of the same toot in the notifications page.",
),
)
]

View File

@ -65,6 +65,12 @@ class Preference(models.Model):
default=False, default=False,
help_text=_("""Exclude boosts and favs from your notifications."""), help_text=_("""Exclude boosts and favs from your notifications."""),
) )
bundle_notifications = models.BooleanField(
default=False,
help_text=_(
"""Collapse together boosts or likes of the same toot in the notifications page."""
),
)
class Account(models.Model): class Account(models.Model):

View File

@ -67,6 +67,7 @@ TEMPLATES = [
"django.template.context_processors.request", "django.template.context_processors.request",
"django.contrib.auth.context_processors.auth", "django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages", "django.contrib.messages.context_processors.messages",
"brutaldon.context_processors.bookmarklet_url",
] ]
}, },
} }
@ -203,4 +204,4 @@ ANONYMOUS_HOME_URL = "about"
GAB_RICKROLL_URL = "https://invidio.us/watch?v=dQw4w9WgXcQ" GAB_RICKROLL_URL = "https://invidio.us/watch?v=dQw4w9WgXcQ"
# Version number displayed on about page # Version number displayed on about page
BRUTALDON_VERSION = "2.12.1" BRUTALDON_VERSION = "2.14.1"

View File

@ -0,0 +1,237 @@
body > section > div.container {
max-width: 100%;
}
.reblog-icon {
position: relative;
top: -24px;
left: 40px;
}
img.fav-avatar {
display: inline;
}
.media-content {
padding: 1.25ex;
}
.is-max-128 img, .is-max-192 img, .is-max-256 img
{
bottom: 0;
left: 0;
position: absolute;
right:0;
top: 0;
}
.is-max-128 {
max-height: 128px;
max-width: 128px;
}
.is-max-256 {
max-height: 256px;
max-width: 256px;
}
.is-max-192 {
max-height: 192px;
max-width: 192px;
}
figure.media-left p.image a img
{
border-radius: 5px;
}
img.avatar
{
border-radius: 5px;
}
.active-context {
background-color: #FFF8DC;
}
h2.subtitle
{
margin-top: 2rem;
margin-bottom: 1rem;
}
article.media.user-info .content img
{
max-height: 1.5rem;
max-width: 1.5rem;
}
span.account-locked
{
margin-top: 48px;
margin-left: -16px;
}
.errorlist
{
color: #FF0000;
}
.emoji-box
{
padding: .75rem;
background-color: #FCFCFC;
border: 1px solid #CCC;
border-radius: 5px;
}
img.emoji
{
display: inline;
max-height: 1.5em;
max-width: 1.5em;
vertical-align: text-bottom;
}
emoji-link
{
font-size: 2em;
}
.content figure.attachment-image
{
text-align:left;
margin-left: 0;
margin-right: 0;
}
@media screen and (max-width: 768px) {
.media {
display: block;
}
}
#page-load-indicator
{
width: 100%;
opacity: 0.8;
position: fixed;
top: 0;
left: 0;
z-index: 666;
transition: all 500ms;
height: 2px;
overflow: hidden;
background-color: #ddd;
display: none;
}
#page-load-indicator:before{
display: block;
position: absolute;
content: "";
left: -200px;
width: 200px;
height: 4px;
background-color: #888;
animation: page-loading 1.5s linear infinite;
}
@keyframes page-loading {
from {left: -200px; width: 30%;}
50% {width: 30%;}
70% {width: 70%;}
80% { left: 50%;}
95% {left: 120%;}
to {left: 100%;}
}
#status_count
{
margin-left: 90%;
margin-top: 1rem;
background-color: #888;
color: #FFF;
float: right;
padding: 0.5ex;
border-radius: 5px;
min-height: 1.5rem;
min-width: 1.5rem;
font-size: 0.8em;
text-align: right;
}
#before-main
{
width: 100%;
height: 2em;
background-color: #DEDEDE;
color: white;
margin-top: 0;
padding: 0;
}
#before-main span
{
margin-left: 50%;
}
input#id_poll_frequency
{
max-width: 10em;
}
body.has-navbar-fixed-top, html.has-navbar-fixed-top {
padding-top: 5rem;
}
.card
{
margin-top: 1em;
margin: 0, auto;
max-width: 90%;
}
#username_autocomplete
{
height: 0;
}
.media-content .content a:not(.mention)
{
text-decoration-line: underline;
text-decoration-style: dotted;
}
.media-content a.level-item
{
text-decoration: none;
}
a.navbar-item span:nth-child(2):before
{
content: " ";
}
div.poll {
margin-bottom: 1ex;
margin-top: 1em;
max-width: 90%;
}
/* Fix some rules that don't need to be there */
.content figure:not(:last-child)
{
margin-bottom: 0;
}
.content figure:not(:first-child)
{
margin-top: 0;
}

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@ html, a, div, div.notification, body, body div, span, object, iframe, h1, h2, h3
font-family: "DEC Terminal Modern", Terminus, Inconsolata, Consolas, "Droid Sans Mono", "DejaVu Sans Mono", "Monaco", monospace; font-family: "DEC Terminal Modern", Terminus, Inconsolata, Consolas, "Droid Sans Mono", "DejaVu Sans Mono", "Monaco", monospace;
color: #ff7700; color: #ff7700;
background-color: #000000; background-color: #000000;
font-size: medium; font-size: 1.2rem;
} }
@ -21,7 +21,6 @@ a, a.button, a.button.is-primary, input.button.is-primary, input, .input, .texta
background-color: #000000; background-color: #000000;
font-weight: bolder; font-weight: bolder;
text-decoration-line: none; text-decoration-line: none;
font-size: medium;
} }
a:hover, a.button:hover, a.button.is-primary:hover, input.button.is-primary:hover { a:hover, a.button:hover, a.button.is-primary:hover, input.button.is-primary:hover {
@ -204,6 +203,9 @@ img.emoji
display: flex; display: flex;
} }
.navbar-item {
margin-right: 2em;
}
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
.media { .media {

View File

@ -2,7 +2,7 @@ html, body, a, div, div.notification, body, body div, span, object, iframe, h1,
font-family: "DEC Terminal Modern", Terminus, Inconsolata, Consolas, "Droid Sans Mono", "DejaVu Sans Mono", "Monaco", monospace; font-family: "DEC Terminal Modern", Terminus, Inconsolata, Consolas, "Droid Sans Mono", "DejaVu Sans Mono", "Monaco", monospace;
color: #00ff77; color: #00ff77;
background-color: #000; background-color: #000;
font-size: medium; font-size: 1.2rem;
} }
tr, td, ul, ol { tr, td, ul, ol {
@ -21,7 +21,7 @@ a, a.button, a.button.is-primary, input.button.is-primary, input, .input, .texta
background-color: #000000; background-color: #000000;
font-weight: bolder; font-weight: bolder;
text-decoration-line: none; text-decoration-line: none;
font-size: medium; font-size: 1.2rem;
} }
@ -208,6 +208,9 @@ img.emoji
display: flex; display: flex;
} }
.navbar-item {
margin-right: 2em;
}
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
.media { .media {

View File

@ -72,7 +72,7 @@ function expandCWButtonPrepare()
{ {
var theButton = document.querySelector('#expandCWs'); var theButton = document.querySelector('#expandCWs');
if (!theButton) { if (!theButton) {
theButton = document.createElement('p'); theButton = document.createElement('button');
theButton.id = "expandCWs"; theButton.id = "expandCWs";
theButton.textContent = "Expand CWs"; theButton.textContent = "Expand CWs";
theButton.classList.toggle('button'); theButton.classList.toggle('button');

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
/*
* Loading attribute polyfill - https://github.com/mfranzke/loading-attribute-polyfill
* @license Copyright(c) 2019 by Maximilian Franzke
* Credits for the initial kickstarter / script to @Sora2455, and supported by @diogoterremoto, @dracos and @Flimm - many thanks for that !
*/
!function(e,t){"use strict";var r,a,o={rootMargin:"256px 0px",threshold:.01,lazyImage:'img[loading="lazy"]',lazyIframe:'iframe[loading="lazy"]'},n={loading:"loading"in HTMLImageElement.prototype&&"loading"in HTMLIFrameElement.prototype,scrolling:"onscroll"in window};"undefined"!=typeof NodeList&&NodeList.prototype&&!NodeList.prototype.forEach&&(NodeList.prototype.forEach=Array.prototype.forEach),"IntersectionObserver"in window&&(r=new IntersectionObserver(function(e,t){e.forEach(function(e){if(0!==e.intersectionRatio){var r=e.target;t.unobserve(r),c(r)}})},o)),a="requestAnimationFrame"in window?window.requestAnimationFrame:function(e){e()};var i="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";function c(e){var t,r,a=[];"picture"===e.parentNode.tagName.toLowerCase()&&(t=e.parentNode,(r=t.querySelector("source[data-lazy-remove]"))&&t.removeChild(r),a=Array.prototype.slice.call(e.parentNode.querySelectorAll("source"))),a.push(e),a.forEach(function(e){e.dataset.lazySrcset&&(e.setAttribute("srcset",e.dataset.lazySrcset),delete e.dataset.lazySrcset)}),e.setAttribute("src",e.dataset.lazySrc),delete e.dataset.lazySrc}function d(e){var t=document.createElement("div");for(t.innerHTML=function(e){var t=e.textContent||e.innerHTML;return!n.loading&&n.scrolling&&(void 0===r?t=t.replace(/(?:\r\n|\r|\n|\t| )src=/g,' lazyload="1" src='):("picture"===e.parentNode.tagName.toLowerCase()&&(t='<source srcset="'+i+'" data-lazy-remove="true"></source>'+t),t=t.replace(/(?:\r\n|\r|\n|\t| )srcset=/g," data-lazy-srcset=").replace(/(?:\r\n|\r|\n|\t| )src=/g,' src="'+i+'" data-lazy-src='))),t}(e);t.firstChild;)n.loading||!n.scrolling||void 0===r||!t.firstChild.tagName||"img"!==t.firstChild.tagName.toLowerCase()&&"iframe"!==t.firstChild.tagName.toLowerCase()||r.observe(t.firstChild),e.parentNode.insertBefore(t.firstChild,e);e.parentNode.removeChild(e)}function s(){document.querySelectorAll("noscript."+e).forEach(d),window.matchMedia("print").addListener(function(e){e.matches&&document.querySelectorAll(o.lazyImage+"[data-lazy-src],"+o.lazyIframe+"[data-lazy-src]").forEach(function(e){c(e)})})}/comp|inter/.test(document.readyState)?a(s):"addEventListener"in document?document.addEventListener("DOMContentLoaded",function(){a(s)}):document.attachEvent("onreadystatechange",function(){"complete"===document.readyState&&s()})}("loading-lazy");

View File

@ -46,6 +46,7 @@
<script type="text/javascript" src="{% static 'js/intercooler.js' %}"></script> <script type="text/javascript" src="{% static 'js/intercooler.js' %}"></script>
<script type="application/javascript" src="{% static 'js/mousetrap.min.js' %}"></script> <script type="application/javascript" src="{% static 'js/mousetrap.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery.magnific-popup.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/jquery.magnific-popup.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/loading-attribute-polyfill.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/brutaldon-enhancements.js' %}"></script> <script type="text/javascript" src="{% static 'js/brutaldon-enhancements.js' %}"></script>
{% block page_scripts %} {% block page_scripts %}
{% endblock %} {% endblock %}
@ -80,12 +81,12 @@
<div class="navbar-brand"> <div class="navbar-brand">
<a class="navbar-item" href="{% url "home" %}"> <a class="navbar-item" href="{% url "home" %}">
{% if own_acct %} {% if own_acct %}
<img src="{{ own_acct.avatar_static }}" <img src="{{ own_acct.avatar_static }}"
class="image is-32x32 avatar" class="image is-32x32 avatar"
alt="Brutaldon ('{{ own_acct.username }}')"> alt="Brutaldon ('{{ own_acct.username }}')">
{% else %} {% else %}
<img src="{% static "images/brutaldon.png" %}" <img loading="lazy" src="{% static "images/brutaldon.png" %}"
class="image is-32x32" alt="Brutaldon"> class="image is-32x32" alt="Brutaldon">
{% endif %} {% endif %}
</a> </a>
</div> </div>
@ -182,13 +183,16 @@
<a class="level-item is-size-7" href="https://github.com/jfmcbrayer/brutaldon"> <a class="level-item is-size-7" href="https://github.com/jfmcbrayer/brutaldon">
Source Source
</a> </a>
<span class="level-item is-size-7" >Bookmarklet: <a href="{{ bookmarklet_url }}">Share via brutaldon</a></span>
</div> </div>
<div class="level-right"> <div class="level-right">
{% if preferences.theme.is_brutalist %} {% if preferences.theme.is_brutalist %}
<img class="level-item" src="{% static '/images/lynx.gif' %}" <noscript class="loading-lazy">
<img loading="lazy" class="level-item" src="{% static '/images/lynx.gif' %}"
alt="Lynx Now!"> alt="Lynx Now!">
<img class="level-item" src="{% static '/images/now9.gif' %}" <img loading="lazy" class="level-item" src="{% static '/images/now9.gif' %}"
alt="Netscape Now!"> alt="Netscape Now!">
</noscript>
{% endif %} {% endif %}
<a class="level-item is-size-7" href="{% url "privacy" %}"> <a class="level-item is-size-7" href="{% url "privacy" %}">
Privacy Privacy

View File

@ -0,0 +1,5 @@
{% if not forloop.first %}
{% if forloop.last %}, and
{% else %},
{% endif %}
{% endif %}

View File

@ -1,9 +1,10 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load humanetime %} {% load humanetime %}
{% load taglinks %} {% load taglinks %}
{% load sanitizer %}
{% block title %} {% block title %}
Brutaldon ({{ own_acct.username }}) - Notifications timelime Brutaldon ({{ own_acct.username }}) - Notifications timelime
{% endblock %} {% endblock %}
{% comment %} {% comment %}
@ -21,69 +22,99 @@ mastodon.notifications()[0]
{% block content %} {% block content %}
<h1 class="title">Your notifications timeline</h1> <h1 class="title">Your notifications timeline</h1>
{% for note in notes %} {% for group in groups %}
{% if note.type == 'mention' %} {% if bundle_notifications and group.0.type in bundleable %}
<p> {% if group.0.type == 'favourite' %}
<strong>{{ note.account.display_name }}</strong> <p>
(<a href="{{ note.account.url | localuser }}">{{ note.account.acct }}</a>) {% for account in group.accounts %}
mentioned you. {% include "comma.html" %}{{ account.display_name | fix_emojos:account.emojis |strip_html |safe }}
</p> (<a href="{{ account.url | localuser}}">{{ account.acct }}</a>)
<br> {% endfor %}
{% include "main/toot_partial.html" with toot=note.status reblog=False %} favorited your toot.
<hr class="is-hidden"> </p>
{% elif note.type == 'reblog' %} {% include "main/toot_partial.html" with toot=group.0.status %}
<p> <hr class="is-hidden">
{{ note.account.display_name }} {% elif group.0.type == 'reblog' %}
(<a href="{{ note.account.url | localuser }}">{{ note.account.acct }}</a>) <p>
boosted your toot. {% for account in group.accounts %}
(<span> {% include "comma.html" %}{{ account.display_name | fix_emojos:account.emojis |strip_html |safe }}
<small>{{ note.created_at |humane_time }}</small> (<a href="{{ account.url | localuser }}">{{ account.acct }}</a>)
</span>) {% endfor %}
</p> boosted your toot.
{% include "main/toot_partial.html" with toot=note.status reblog=True reblog_by=note.account.acct reblog_icon=note.account.avatar_static %} </p>
<hr class="is-hidden"> {% include "main/toot_partial.html" with toot=group.0.status reblog=True reblog_by=group.0.account.acct reblog_icon=group.0.account.avatar_static %}
{% elif note.type == 'favourite' %} <hr class="is-hidden">
<p> {% endif %}
{{ note.account.display_name }} {% else %}
(<a href="{{ note.account.url | localuser}}">{{ note.account.acct }}</a>) {% for note in group %}
favorited your toot. {% if note.type == 'mention' %}
(<span> <p>
<small>{{ note.created_at |humane_time }}</small> <strong>{{ note.account.display_name | fix_emojos:note.account.emojis |strip_html |safe }}</strong>
</span>) (<a href="{{ note.account.url | localuser }}">{{ note.account.acct }}</a>)
</p> mentioned you.
{% include "main/toot_partial.html" with toot=note.status %} </p>
<hr class="is-hidden"> <br>
{% elif note.type == 'follow' %} {% include "main/toot_partial.html" with toot=note.status reblog=False %}
<article class="media"> <hr class="is-hidden">
<figure class="media-left"> {% elif note.type == 'reblog' %}
<p class="image is-64x64"> <p>
<img src="{{ note.account.avatar_static }}" alt=""> {{ note.account.display_name | fix_emojos:note.account.emojis |strip_html |safe }}
</p> (<a href="{{ note.account.url | localuser }}">{{ note.account.acct }}</a>)
</figure> boosted your toot.
<div class="media-content" > (<span>
<div class="content"> <small>{{ note.created_at |humane_time }}</small>
<strong>{{ note.account.display_name }}</strong> </span>)
(<a href="{{ note.account.url |localuser }}">{{ note.account.acct }}</a>) </p>
followed you. {% include "main/toot_partial.html" with toot=note.status reblog=True reblog_by=note.account.acct reblog_icon=note.account.avatar_static %}
(<a href="{{ note.url }}"> <hr class="is-hidden">
<small>{{ note.created_at |humane_time }}</small> {% elif note.type == 'favourite' %}
</a>) <p>
</div> {{ note.account.display_name | fix_emojos:note.account.emojis |strip_html |safe }}
</div> (<a href="{{ note.account.url | localuser}}">{{ note.account.acct }}</a>)
</article> favorited your toot.
<hr class="is-hidden"> (<span>
{% endif %} <small>{{ note.created_at |humane_time }}</small>
{% endfor %} </span>)
</p>
{% include "main/toot_partial.html" with toot=note.status %}
<hr class="is-hidden">
{% elif note.type == 'follow' %}
<article class="media">
<figure class="media-left">
<p class="image is-64x64">
<img src="{{ note.account.avatar_static }}" alt="">
</p>
</figure>
<div class="media-content" >
<div class="content">
<strong>{{ note.account.display_name | fix_emojos:note.account.emojis |strip_html |safe }}</strong>
(<a href="{{ note.account.url |localuser }}">{{ note.account.acct }}</a>)
followed you.
(<a href="{{ note.url }}">
<small>{{ note.created_at |humane_time }}</small>
</a>)
</div>
</div>
</article>
<hr class="is-hidden">
{% elif note.type == 'poll' %}
<p>A poll you created or voted in has ended.</p>
{% include "main/toot_partial.html" with toot=note.status %}
<hr class="is-hidden">
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
<nav class="pagination is-centered" role="navigation" aria-label="pagination"> <nav class="pagination is-centered" role="navigation" aria-label="pagination">
{% if prev %} {% if prev %}
<a class="pagination-next" href="{% url 'note_prev' prev.min_id %}">Newer</a> <a class="pagination-next" href="{% url 'note_prev' prev.min_id %}">Newer</a>
{% endif %} {% endif %}
{% if next %} {% if next %}
<a class="pagination-previous" href="{% url 'note_next' next.max_id %}">Older</a> <a class="pagination-previous" href="{% url 'note_next' next.max_id %}">Older</a>
{% endif %} {% endif %}
</nav> </nav>
{% endblock %} {% endblock %}

View File

@ -4,7 +4,7 @@
{% load taglinks %} {% load taglinks %}
{% load static %} {% load static %}
{% if toot %}
{% if active %} {% if active %}
<article id="toot-{{toot.id}}" class="media box active-context"> <article id="toot-{{toot.id}}" class="media box active-context">
{% else %} {% else %}
@ -14,14 +14,14 @@
<figure class="media-left"> <figure class="media-left">
<p class="image is-64x64 account-avatar"> <p class="image is-64x64 account-avatar">
<a href="{% url "user" toot.account.acct %}"> <a href="{% url "user" toot.account.acct %}">
<img src="{{ toot.account.avatar_static }}" <img loading="auto" src="{{ toot.account.avatar_static }}"
alt=""> alt="">
</a> </a>
</p> </p>
{% if reblog %} {% if reblog %}
<p class="image is-32x32 reblog-icon" > <p class="image is-32x32 reblog-icon" >
<a href="{% url "user" reblog_by %}"> <a href="{% url "user" reblog_by %}">
<img src ="{{ reblog_icon }}" alt=""> <img loading="auto" src ="{{ reblog_icon }}" alt="">
</a> </a>
</p> </p>
{% endif %} {% endif %}
@ -29,7 +29,7 @@
<div class="media-content"> <div class="media-content">
<div class="content"> <div class="content">
<p> <p>
<strong>{{ toot.account.display_name }}</strong> <strong>{{ toot.account.display_name | fix_emojos:toot.account.emojis | strip_html |safe}}</strong>
<small><a href="{% url "user" toot.account.acct %}"> <small><a href="{% url "user" toot.account.acct %}">
@{{ toot.account.acct }}</a></small> @{{ toot.account.acct }}</a></small>
<a href="{{ toot.url }}"> <a href="{{ toot.url }}">
@ -71,9 +71,11 @@
{% if toot.card.image %} {% if toot.card.image %}
<div class="column is-one-third"> <div class="column is-one-third">
<a href="{{ toot.card.url }}"> <a href="{{ toot.card.url }}">
<img alt="{{ toot.card.title }}" <noscript class="loading-lazy">
<img loading="lazy" alt="{{ toot.card.title }}"
src="{{ toot.card.image }}" src="{{ toot.card.image }}"
class="is-max-128"> class="is-max-128">
</noscript>
</a> </a>
</div> </div>
{% endif %} {% endif %}
@ -85,7 +87,7 @@
</a> </a>
</strong> </strong>
<p>{{ toot.card.description }}</p> <p>{{ toot.card.description |truncatechars_html:500}}</p>
</div> </div>
</div> </div>
</div> </div>
@ -98,10 +100,11 @@
{% if media.type == "image" %} {% if media.type == "image" %}
<figure class="column attachment-image"> <figure class="column attachment-image">
<a href="{{ media.url }}"> <a href="{{ media.url }}">
<noscript class="loading-lazy">
{% if toot.sensitive %} {% if toot.sensitive %}
<img src="{% static "images/sensitive.png" %}" <img loading="lazy" src="{% static "images/sensitive.png" %}"
{% else %} {% else %}
<img src="{{ media.preview_url }}" <img loading="lazy" src="{{ media.preview_url }}"
{% endif %} {% endif %}
alt="{% if media.description %} alt="{% if media.description %}
{{ media.description }} {{ media.description }}
@ -114,6 +117,7 @@
title="{{ media.description }}" title="{{ media.description }}"
{% endif %} {% endif %}
class="image is-max-256"> class="image is-max-256">
</noscript>
</a> </a>
</figure> </figure>
{% else %} {% else %}
@ -122,10 +126,11 @@
poster="{{ media.preview_url }}"> poster="{{ media.preview_url }}">
<source src="{{ media.url }}" type="video/mp4"> <source src="{{ media.url }}" type="video/mp4">
<a href="{{ media.url }}"> <a href="{{ media.url }}">
<noscript class="loading-lazy">
{% if toot.sensitive %} {% if toot.sensitive %}
<img src="{% static "images/sensitive.png" %}" <img loading="lazy" src="{% static "images/sensitive.png" %}"
{% else %} {% else %}
<img src="{{ media.preview_url }}" <img loading="lazy" src="{{ media.preview_url }}"
{% endif %} {% endif %}
alt="{% if media.description %} alt="{% if media.description %}
{{ media.description }} {{ media.description }}
@ -138,6 +143,7 @@
title="{{ media.description }}" title="{{ media.description }}"
{% endif %} {% endif %}
class="image is-max-256"> class="image is-max-256">
</noscript>
</a> </a>
</video> </video>
</figure> </figure>
@ -227,3 +233,4 @@
</div> </div>
<div class="media-right"></div> <div class="media-right"></div>
</article> </article>
{% endif %}

View File

@ -125,6 +125,23 @@
</div> </div>
</div> </div>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_bundle_notifications">
{% render_field form.bundle_notifications class+="checkbox" %}
{{ form.bundle_notifications.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.bundle_notifications.help_text }}
</p>
</div>
<div class="column is-half">
</div>
</div>
<div class="columns"> <div class="columns">
<div class="column is-quarter"> <div class="column is-quarter">
<label class="label" for="id_poll_frequency"> <label class="label" for="id_poll_frequency">
@ -149,6 +166,12 @@
value="Save" class="button is-primary" > value="Save" class="button is-primary" >
</div> </div>
</form> </form>
<h2 class="subtitle">Bookmarklet</h2>
<p>
<a href="{{ bookmarklet_url }}">Share via brutaldon</a>
</p>
<h2 class="subtitle">Filters and More</h2> <h2 class="subtitle">Filters and More</h2>
<p><a href="{% url "list_filters" %}">List filters</a></p> <p><a href="{% url "list_filters" %}">List filters</a></p>
<p><a href="{% url "follow_requests" %}">Follow requests</a></p> <p><a href="{% url "follow_requests" %}">Follow requests</a></p>

View File

@ -69,5 +69,6 @@ urlpatterns = [
path("accounts/", views.accounts, name="accounts"), path("accounts/", views.accounts, name="accounts"),
path("accounts/<id>", views.accounts, name="accounts"), path("accounts/<id>", views.accounts, name="accounts"),
path("vote/<id>", views.vote, name="vote"), path("vote/<id>", views.vote, name="vote"),
path("share/", views.share, name="share"),
path("", views.home, name=""), path("", views.home, name=""),
] ]

View File

@ -4,7 +4,6 @@ from django.conf import settings as django_settings
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.urls import reverse from django.urls import reverse
from django.views.decorators.cache import never_cache, cache_page from django.views.decorators.cache import never_cache, cache_page
from django.urls import reverse
from django.core.files.uploadhandler import TemporaryFileUploadHandler from django.core.files.uploadhandler import TemporaryFileUploadHandler
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from brutaldon.forms import ( from brutaldon.forms import (
@ -24,8 +23,10 @@ from mastodon import (
) )
from urllib import parse from urllib import parse
from pdb import set_trace from pdb import set_trace
from itertools import groupby
from inscriptis import get_text from inscriptis import get_text
from time import sleep from time import sleep
from requests import Session
import re import re
@ -33,11 +34,43 @@ class NotLoggedInException(Exception):
pass pass
class LabeledList(list):
"""A subclass of list that can accept additional attributes"""
def __new__(self, *args, **kwargs):
return super(LabeledList, self).__new__(self, args, kwargs)
def __init(self, *args, **kwargs):
if len(args) == 1 and hasattr(args[0], "__iter__"):
list.__init__(self, args[0])
else:
list.__init__(self, args)
self.__dict__.update(kwargs)
def __call(self, **kwargs):
self.__dict__.update(kwargs)
return self
global sessons_cache
sessions_cache = {}
VISIBILITIES = ["direct", "private", "unlisted", "public"]
### ###
### Utility functions ### Utility functions
### ###
def get_session(domain):
if domain in sessions_cache:
return sessions_cache[domain]
else:
s = Session()
sessions_cache[domain] = s
return s
def get_usercontext(request): def get_usercontext(request):
if is_logged_in(request): if is_logged_in(request):
try: try:
@ -55,6 +88,7 @@ def get_usercontext(request):
client_secret=client.client_secret, client_secret=client.client_secret,
access_token=user.access_token, access_token=user.access_token,
api_base_url=client.api_base_id, api_base_url=client.api_base_id,
session=get_session(client.api_base_id),
ratelimit_method="throw", ratelimit_method="throw",
) )
return user, mastodon return user, mastodon
@ -150,6 +184,12 @@ def user_search_inner(request, query):
) )
def min_visibility(visibility1, visibility2):
return VISIBILITIES[
min(VISIBILITIES.index(visibility1), VISIBILITIES.index(visibility2))
]
def timeline( def timeline(
request, request,
timeline="home", timeline="home",
@ -590,17 +630,42 @@ def note(request, next=None, prev=None):
next = notes[-1]._pagination_next next = notes[-1]._pagination_next
except (IndexError, AttributeError, KeyError): except (IndexError, AttributeError, KeyError):
next = None next = None
# Now group notes into lists based on type and status
groups = []
if account.preferences.bundle_notifications:
def bundle_key(note):
try:
return str(note.status.id) + note.type
except:
return str(note.id) + note.type
def group_sort_key(group):
return max([k.id for k in group])
sorted_notes = sorted(notes, key=bundle_key, reverse=True)
for _, group in groupby(sorted_notes, bundle_key):
group = LabeledList(group)
group.accounts = [x.account for x in group]
groups.append(group)
groups.sort(key=group_sort_key, reverse=True)
else:
groups.append(notes)
return render( return render(
request, request,
"main/notifications.html", "main/notifications.html",
{ {
"notes": notes, "notes": notes,
"groups": groups,
"timeline": "Notifications", "timeline": "Notifications",
"timeline_name": "Notifications", "timeline_name": "Notifications",
"own_acct": request.session["active_user"], "own_acct": request.session["active_user"],
"preferences": account.preferences, "preferences": account.preferences,
"prev": prev, "prev": prev,
"next": next, "next": next,
"bundleable": ["favourite", "reblog"],
"bundle_notifications": account.preferences.bundle_notifications,
}, },
) )
@ -713,6 +778,9 @@ def settings(request):
account.preferences.filter_notifications = form.cleaned_data[ account.preferences.filter_notifications = form.cleaned_data[
"filter_notifications" "filter_notifications"
] ]
account.preferences.bundle_notifications = form.cleaned_data[
"bundle_notifications"
]
account.preferences.poll_frequency = form.cleaned_data["poll_frequency"] account.preferences.poll_frequency = form.cleaned_data["poll_frequency"]
request.session["timezone"] = account.preferences.timezone request.session["timezone"] = account.preferences.timezone
account.preferences.save() account.preferences.save()
@ -998,7 +1066,9 @@ def reply(request, id):
form = PostForm( form = PostForm(
initial={ initial={
"status": initial_text, "status": initial_text,
"visibility": toot.visibility, "visibility": min_visibility(
toot.visibility, request.session["active_user"].source.privacy
),
"spoiler_text": toot.spoiler_text, "spoiler_text": toot.spoiler_text,
} }
) )
@ -1097,6 +1167,37 @@ def reply(request, id):
return HttpResponseRedirect(reverse("reply", args=[id]) + "#toot-" + str(id)) return HttpResponseRedirect(reverse("reply", args=[id]) + "#toot-" + str(id))
@br_login_required
def share(request):
account, mastodon = get_usercontext(request)
if request.method == "GET":
params = request.GET
if request.method == "POST":
params = request.POST
title = params.get("title")
url = params.get("url")
if title:
initial_text = f"{title}\n\n{url}"
else:
initial_text = f"{url}"
form = PostForm(
initial={
"status": initial_text,
"visibility": request.session["active_user"].source.privacy,
}
)
return render(
request,
"main/post.html",
{
"form": form,
"own_acct": request.session["active_user"],
"preferences": account.preferences,
},
)
@never_cache @never_cache
@br_login_required @br_login_required
def fav(request, id): def fav(request, id):

View File

@ -1,11 +1,12 @@
{ {
"dependencies": { "dependencies": {
"bulma": "^0.7.1", "bulma": "^0.7.5",
"bulma-extensions": "^2.2.2", "bulma-extensions": "^2.2.2",
"bulmaswatch": "^0.6.2", "bulmaswatch": "^0.6.2",
"fork-awesome": "^1.1.0", "fork-awesome": "^1.1.0",
"intercooler": "^1.2.1", "intercooler": "^1.2.1",
"jquery": "^3.3.1", "jquery": "^3.4.1",
"loading-attribute-polyfill": "^1.2.0",
"magnific-popup": "^1.1.0", "magnific-popup": "^1.1.0",
"mousetrap": "^1.6.2" "mousetrap": "^1.6.2"
} }

View File

@ -1,43 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
bulma-extensions@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/bulma-extensions/-/bulma-extensions-2.2.2.tgz#d46f11d1242a3fdc60a983afaea0b5e6c137362d"
integrity sha512-6hV6SiRT7RZD5+scn/462Dz8RURRgxuvlCy+R5LH9U6FLn7Q/sInAA5VdK8c4DKpxZjfsipp72EAKM7yzgzRig==
bulma@^0.7.1:
version "0.7.4"
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.7.4.tgz#7e74512e9118d9799021339e67e365ee0ac4f3f9"
integrity sha512-krG2rP6eAX1WE0sf6O0SC/FUVSOBX4m1PBC2+GKLpb2pX0qanaDqcv9U2nu75egFrsHkI0zdWYuk/oGwoszVWg==
bulmaswatch@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/bulmaswatch/-/bulmaswatch-0.6.2.tgz#5d102b1986d5f48d7f45ff68354c5fe367dc1c76"
integrity sha512-IMYjMqEnHQ1Yhtzkia3ojeFRvJr8DnLBiDMUuvLcGtJwyaAuZ8AQ4tT3TK21lcnt7cngeM3CKzBtkbQulWQI9w==
fork-awesome@^1.1.0:
version "1.1.7"
resolved "https://registry.yarnpkg.com/fork-awesome/-/fork-awesome-1.1.7.tgz#1427da1cac3d1713046ee88427e5fcecb9501d21"
integrity sha512-IHI7XCSXrKfUIWslse8c/PaaVDT1oBaYge+ju40ihL2ooiQeBpTr4wvIXhgTd2NuhntlvX+M5jYHAPTzNlmv0g==
intercooler@^1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/intercooler/-/intercooler-1.2.2.tgz#f2d79dcc22fbba4de6dbe782039545df65dbe680"
integrity sha512-YpSnEWowbqunu06SCcVe7hC9zu8GkGM/ouk7B61T6CL33WgfH10wyVidFFX0rPtzLzMz1TG/vIzOeac5vrqRJA==
jquery@^3.3.1:
version "3.4.0"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.0.tgz#8de513fa0fa4b2c7d2e48a530e26f0596936efdf"
integrity sha512-ggRCXln9zEqv6OqAGXFEcshF5dSBvCkzj6Gm2gzuR5fWawaX8t7cxKVkkygKODrDAzKdoYw3l/e3pm3vlT4IbQ==
magnific-popup@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/magnific-popup/-/magnific-popup-1.1.0.tgz#3e7362c5bd18f6785fe99e59d013e20af33d3049"
integrity sha1-PnNixb0Y9nhf6Z5Z0BPiCvM9MEk=
mousetrap@^1.6.2:
version "1.6.3"
resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.3.tgz#80fee49665fd478bccf072c9d46bdf1bfed3558a"
integrity sha512-bd+nzwhhs9ifsUrC2tWaSgm24/oo2c83zaRyZQF06hYA6sANfsXHtnZ19AbbbDXCDzeH5nZBSQ4NvCjgD62tJA==