Skip to content
This repository has been archived by the owner on Feb 2, 2021. It is now read-only.

Threaded threads #78

Open
wants to merge 42 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
bf0394c
textareas and inputs in bulmaswatch-darkly were hurting my eyes.
May 30, 2020
2dfdb0b
Support feature_set= other than mainline for Mastodon
May 30, 2020
12d7b4c
Handle feature_set errors automatically
May 30, 2020
0b93eb7
Refactoring preferences to be more cohesive
May 30, 2020
0439440
Foreign keys add magic members
May 30, 2020
39f13d6
Allow previewing sensitive images
May 30, 2020
543a83c
Add open_details preference
May 31, 2020
c0caab4
Missed a few status_post calls
May 31, 2020
9415def
whoops, left an old mistake in the code
May 31, 2020
7b86cf7
Viewing threads as a tree, not flattened
Jun 1, 2020
4e94878
Cleaning up, adding tokens
Jun 1, 2020
4faba56
Debugging a lookup error
Jun 1, 2020
82b2c9f
Still trying to figure out what my data structure is
Jun 1, 2020
2c8cdac
OK no hashable errors anymore
Jun 1, 2020
2308d65
Adding an outer IN/OUT
Jun 1, 2020
933961b
Trying to figure out why only the root post is coming through
Jun 1, 2020
631c140
Nothing at all?
Jun 1, 2020
7552f4e
Monitor tree building progress
Jun 1, 2020
cc826ff
Still trying...
Jun 1, 2020
e887978
Maybe the root is getting filtered out?
Jun 1, 2020
09c5a4f
Yet more debugging
Jun 1, 2020
43cb98a
Sorting to make it easier to read when debugging
Jun 1, 2020
e8b5cf6
Removing redundant code
Jun 1, 2020
05605bf
Leftovers?
Jun 1, 2020
e76c758
Leftovers isn't lazy enough
Jun 1, 2020
3d96963
auto 0 20 244
Jun 1, 2020
8cda922
Pulling any missing posts by ID
Jun 1, 2020
5187715
Consistent naming
Jun 1, 2020
2cf2e77
The root post is not included in its descendants
Jun 1, 2020
f900be0
Why is the root post displaying twice?
Jun 1, 2020
b306f38
The template prints out the root by itself
Jun 1, 2020
a0433a1
Changing the template, prettifying a bit
Jun 1, 2020
50f05d7
Merge branch 'preview_sensitive' into threaded_threads
Jun 1, 2020
d682f62
Merge branch 'open_details_preference' into HEAD
Jun 1, 2020
aee3b13
Merge branch 'dark_textareas' into threaded_threads
Jun 1, 2020
77e352b
Preference setting for whether to make threads a tree or not
Jun 1, 2020
70e0eea
Tree thread preference seems to be working.
Jun 1, 2020
f2f8e7a
This is needed to make settings waste more sp--I mean prettier
Jun 1, 2020
72c6ba5
Re-enable filtering
Jun 1, 2020
36fc686
Actually return the toots being looked up
Jun 1, 2020
e2cf103
Allowing for horizontal scrolling for deeply nested threads.
Jun 1, 2020
2fc42ad
Removing some debugging output I missed
Jun 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 1 addition & 13 deletions brutaldon/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,7 @@ class OAuthLoginForm(forms.Form):
class PreferencesForm(forms.ModelForm):
class Meta:
model = Preference
fields = [
"theme",
"filter_replies",
"filter_boosts",
"timezone",
"no_javascript",
"notifications",
"click_to_load",
"lightbox",
"filter_notifications",
"bundle_notifications",
"poll_frequency",
]
fields = Preference._fields


class PostForm(forms.Form):
Expand Down
25 changes: 25 additions & 0 deletions brutaldon/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,39 @@ class Theme(models.Model):
def __str__(self):
return self.name

from django.db.models.fields.related_descriptors import ForeignKeyDeferredAttribute
def set_fields(klass):
fields = []
for n in dir(klass):
assert n != "_fields"
v = getattr(klass, n)
if not hasattr(v, 'field'): continue
if not isinstance(v.field, models.Field): continue
if isinstance(v, ForeignKeyDeferredAttribute): continue
fields.append(n)
setattr(klass, '_fields', fields)
return klass

@set_fields
class Preference(models.Model):
theme = models.ForeignKey(Theme, models.CASCADE, null=False, default=1)
filter_replies = models.BooleanField(default=False)
filter_boosts = models.BooleanField(default=False)
timezone = models.CharField(
max_length=80, blank=True, null=True, choices=timezones, default="UTC"
)
preview_sensitive = models.BooleanField(
default=False,
help_text=_(
'Show preview for media marked as "sensitive"'))
open_detail = models.BooleanField(
default=False,
help_text=_(
'Open details (posts with subjects) by default'))
tree_threads = models.BooleanField(
default=True,
help_text=_(
'Display threads as a tree, or as a flat list if disabled.'))
no_javascript = models.BooleanField(
default=False,
help_text=_(
Expand Down
22 changes: 22 additions & 0 deletions brutaldon/static/css/brutaldon-dark.css
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,25 @@ div.poll {
{
margin-top: 0;
}

html {
overflow-x: inherit;
}
li {
margin-top: 0.1vh;
min-width: 40em;
}
ul {
padding-left: 2vw;
}

.input,
.textarea {
color: inherit;
background-color: inherit;
}

input[type="text"] {
color: inherit;
background-color: inherit;
}
36 changes: 26 additions & 10 deletions brutaldon/templates/main/thread.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,32 @@
<h1 id="title" class="title">
Thread
</h1>
{% include "main/toot_partial.html" with toot=root %}
{% for descendant in descendants %}
{% if descendant == toot %}
{% include "main/toot_partial.html" with toot=toot active=True %}
{% else %}
{% include "main/toot_partial.html" with toot=descendant %}
{% endif %}
<hr class="is-hidden">
{% endfor %}

{% if preferences.tree_threads %}
{% for op in toots %}
{% if op == IN %}
<ul>
{% elif op == OUT %}
</ul>
{% else %}
<li>
{% if op.toot == activetoot %}
{% include "main/toot_partial.html" with toot=op.toot active=True %}
{% else %}
{% include "main/toot_partial.html" with toot=op.toot %}
{% endif %}
</li>
{% endif %}
{% endfor %}
{% else %}
{% include "main/toot_partial.html" with toot=root %}
{% for cur in descendants %}
{% if cur == activetoot %}
{% include "main/toot_partial.html" with toot=cur active=True %}
{% else %}
{% include "main/toot_partial.html" with toot=cur %}
{% endif %}
{% endfor %}
{% endif %}
{% if not preferences.no_javascript %}
<script type="application/javascript">
Intercooler.ready(expandCWButtonPrepare);
Expand Down
10 changes: 7 additions & 3 deletions brutaldon/templates/main/toot_partial.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@
{% endif %}
</p>
{% if toot.spoiler_text %}
<details class="toot">
<details class="toot"
{% if preferences.open_detail %}
open=""
{% endif %}
>
<summary><strong>{{ toot.spoiler_text }} </strong></summary>
<div class="toot">
{{ toot.content | relink_toot | fix_emojos:toot.emojis | strip_html | safe }}
Expand Down Expand Up @@ -101,7 +105,7 @@
<figure class="column attachment-image">
<a href="{{ media.url }}">
<noscript class="loading-lazy">
{% if toot.sensitive %}
{% if toot.sensitive and not preferences.preview_sensitive %}
<img loading="lazy" src="{% static "images/sensitive.png" %}"
{% else %}
<img loading="lazy" src="{{ media.preview_url }}"
Expand All @@ -127,7 +131,7 @@
<source src="{{ media.url }}" type="video/mp4">
<a href="{{ media.url }}">
<noscript class="loading-lazy">
{% if toot.sensitive %}
{% if toot.sensitive and not preferences.preview_sensitive %}
<img loading="lazy" src="{% static "images/sensitive.png" %}"
{% else %}
<img loading="lazy" src="{{ media.preview_url }}"
Expand Down
47 changes: 46 additions & 1 deletion brutaldon/templates/setup/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,52 @@ <h2 class="subtitle">General Options</h2>
</div>
</div>
</div>

<h2 class="subtitle">Options</h2>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_preview_sensitive">
{% render_field form.preview_sensitive class+="checkbox" %}
{{ form.preview_sensitive.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.preview_sensitive.help_text }}
</p>
</div>
<div class="column is-half">
</div>
</div>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_open_details">
{% render_field form.open_detail class+="checkbox" %}
{{ form.open_detail.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.open_detail.help_text }}
</p>
</div>
<div class="column is-half">
</div>
</div>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_tree_threads">
{% render_field form.tree_threads class+="checkbox" %}
{{ form.tree_threads.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.tree_threads.help_text }}
</p>
</div>
<div class="column is-half">
</div>
</div>
<h2 class="subtitle">Timeline Options</h2>
<div class="field">
<label class="label checkbox">
Expand Down
98 changes: 98 additions & 0 deletions brutaldon/threadtree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
class filtered_toot:
class account:
acct = "(filtered)"
display_name = "(filtered)"
emojis = ()
avatar_static = 'about:blank'
created_at = None
spoiler_text = None
content = ""
poll = None
card = None
media_attachments = None
sensitive = False
id = None
replies_count = -1
visibility = 'filtered'
favourited = False
in_reply_to_id = None


def maketree(mastodon, oktoot, root, descendants):
# Apply filters later...
# descendants = [toot for toot in descendants if oktoot(toot)]
lookup = dict((descendant.id, descendant) for descendant in descendants)
lookup[root.id] = root
replies = {}
roots = set([root.id])
def lookup_or_fetch(id):
if not id in lookup:
toot = mastodon.status(id)
if not oktoot(toot):
# just a placeholder so it doesn't mess up the UI
return filtered_toot
lookup[id] = toot
return toot
return lookup[id]
def getreps(id):
if id in replies:
reps = replies[id]
else:
reps = set()
replies[id] = reps
return reps
for descendant in descendants:
if not descendant.in_reply_to_id:
roots.add(descendant.id)
else:
reps = getreps(descendant.in_reply_to_id)
reps.add(descendant.id)
reps = getreps(descendant.in_reply_to_account_id)
reps.add(descendant.id)
seen = set()
def onelevel(reps):
for rep in sorted(reps):
if rep in seen: continue
seen.add(rep)
subreps = replies.get(rep)
if subreps:
yield lookup_or_fetch(rep), onelevel(subreps)
else:
yield lookup_or_fetch(rep), ()
def leftovers():
for leftover in set(lookup.keys()) - seen:
yield lookup_or_fetch(leftover)
return onelevel(roots), leftovers

# returns (status, gen[(status, gen[(status, ...), (status, ())]), ...])

# django can't do recursion well so we'll turn the tree
# ((A, (B, C)))
# into
# (in, in, A, in, B, C, out, out, out)

IN = 0
OUT = 1
class TOOT:
toot = None
def __init__(self, toot):
self.toot = toot

def unmaketree(tree):
for toot, children in tree:
yield TOOT(toot)
if children:
yield IN
yield from unmaketree(children)
yield OUT

def build(mastodon, oktoot, root, descendants):
tree, leftover = maketree(mastodon, oktoot, root, descendants)
yield IN
yield from unmaketree(tree)
yield OUT
yield IN
leftover = tuple(leftover())
for toot in leftover:
yield TOOT(toot)
yield OUT
Loading