├── .gitignore ├── INSTALL.md ├── LICENSE ├── Pipfile ├── README.md ├── brutaldon ├── __init__.py ├── context_processors.py ├── forms.py ├── middleware │ └── timezone.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_account.py │ ├── 0003_auto_20180424_1255.py │ ├── 0004_auto_20180424_1424.py │ ├── 0005_auto_20180618_2103.py │ ├── 0006_auto_20180618_2112.py │ ├── 0007_auto_20180618_2115.py │ ├── 0008_auto_20180618_2140.py │ ├── 0009_auto_20180824_2241.py │ ├── 0010_auto_20180825_1009.py │ ├── 0011_auto_20180825_1017.py │ ├── 0012_auto_20180826_1853.py │ ├── 0013_auto_20180826_1935.py │ ├── 0014_account_note_seen.py │ ├── 0015_auto_20181001_1812.py │ ├── 0016_auto_20181009_1805.py │ ├── 0017_preference_poll_frequency.py │ ├── 0018_preference_filter_notifications.py │ ├── 0019_auto_20190124_0813.py │ ├── 0020_auto_20190127_2159.py │ ├── 0021_client_version.py │ ├── 0022_auto_20190506_0938.py │ ├── 0023_preference_bundle_notifications.py │ ├── 0024_auto_20200601_0945.py │ ├── 0025_preference_preview_sensitive.py │ └── __init__.py ├── models.py ├── settings.py ├── static │ ├── css │ │ ├── brutaldon-dark.css │ │ ├── brutaldon-material.css │ │ ├── brutaldon.css │ │ ├── brutstrap-tweaks.css │ │ ├── brutstrap.css │ │ ├── bulma-badge.min.css │ │ ├── bulma-tooltip.min.css │ │ ├── bulma.min.css │ │ ├── bulmaswatch-darkly.min.css │ │ ├── bulmaswatch-lux.min.css │ │ ├── bulmaswatch-materia.min.css │ │ ├── bulmaswatch-solar.min.css │ │ ├── bulmaswatch.min.css.map │ │ ├── fork-awesome.min.css │ │ ├── fork-awesome.min.css.map │ │ ├── fullbrutalism.css │ │ ├── magnific-popup.css │ │ ├── minimal-dark.css │ │ ├── minimal-large.css │ │ ├── minimal-small.css │ │ ├── vt240don-amber.css │ │ └── vt240don-green.css │ ├── fonts │ │ ├── forkawesome-webfont.eot │ │ ├── forkawesome-webfont.svg │ │ ├── forkawesome-webfont.ttf │ │ ├── forkawesome-webfont.woff │ │ └── forkawesome-webfont.woff2 │ ├── images │ │ ├── brutaldon-120.png │ │ ├── brutaldon-144.png │ │ ├── brutaldon-152.png │ │ ├── brutaldon-180.png │ │ ├── brutaldon-192.png │ │ ├── brutaldon-48.png │ │ ├── brutaldon-512.png │ │ ├── brutaldon-72.png │ │ ├── brutaldon-76.png │ │ ├── brutaldon-96.png │ │ ├── brutaldon-splash.png │ │ ├── brutaldon.png │ │ ├── lynx.gif │ │ ├── now9.gif │ │ └── sensitive.png │ ├── js │ │ ├── brutaldon-enhancements.js │ │ ├── intercooler.js │ │ ├── jquery.magnific-popup.js │ │ ├── jquery.magnific-popup.min.js │ │ ├── jquery.min.js │ │ ├── loading-attribute-polyfill.min.js │ │ └── mousetrap.min.js │ ├── manifest.webmanifest │ └── offline.html ├── templates │ ├── about.html │ ├── accounts │ │ ├── account_partial.html │ │ └── list.html │ ├── base.html │ ├── comma.html │ ├── error.html │ ├── filters │ │ ├── create.html │ │ ├── delete.html │ │ ├── edit.html │ │ └── list.html │ ├── intercooler │ │ ├── block.html │ │ ├── boost.html │ │ ├── fav.html │ │ ├── follow.html │ │ ├── mute.html │ │ ├── notes.html │ │ ├── post.html │ │ ├── search.html │ │ └── users.html │ ├── main │ │ ├── block.html │ │ ├── boost.html │ │ ├── delete.html │ │ ├── emoji.html │ │ ├── fav.html │ │ ├── follow.html │ │ ├── home_timeline.html │ │ ├── local_timeline.html │ │ ├── mute.html │ │ ├── notifications.html │ │ ├── post.html │ │ ├── post_minimal_partial.html │ │ ├── post_partial.html │ │ ├── public_timeline.html │ │ ├── redraft.html │ │ ├── reply.html │ │ ├── search.html │ │ ├── search_results.html │ │ ├── thread.html │ │ ├── timeline.html │ │ ├── toot_partial.html │ │ └── user.html │ ├── polls │ │ ├── completed_partial.html │ │ └── new_partial.html │ ├── privacy.html │ ├── requests │ │ ├── list.html │ │ └── request_partial.html │ └── setup │ │ ├── login-oauth.html │ │ ├── login.html │ │ └── settings.html ├── templatetags │ ├── __init__.py │ ├── humanetime.py │ └── taglinks.py ├── urls.py ├── views.py └── wsgi.py ├── docs └── screenshots │ ├── screenshot-firefox-2.png │ ├── screenshot-firefox-brutalist-2.png │ ├── screenshot-firefox-brutalist.png │ ├── screenshot-firefox.png │ └── screenshot-lynx.png ├── manage.py └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | .static_storage/ 56 | .media/ 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | bin 106 | db.sqlite3 107 | include/ 108 | lib64 109 | pip-selfcheck.json 110 | /Pipfile.lock 111 | /dev_https 112 | node_modules 113 | /TAGS 114 | .vscode 115 | package-lock.json 116 | yarn.lock 117 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | Installing brutaldon 2 | ==================== 3 | 4 | Brutaldon is a perfectly normal Django app, so if you've ever installed a Django app, it should be straightforward. It will work either as a local application, or installed on a server. 5 | 6 | For either case, you will need Python 3 installed to start with, including pip. 7 | 8 | Common steps 9 | --------------------------------------------------------- 10 | If you haven't already, you need to install [Pipenv][pe], a tool for managing Python virtual environments. 11 | 12 | You can install it just with `pip install pipenv`. 13 | 14 | [pe]: https://github.com/pypa/pipenv/ 15 | 16 | Development or local install 17 | ------------------------------------------------ 18 | In the top brutaldon directory, run `pipenv install`. This will install all the dependencies. Then run `pipenv run python ./manage.py migrate`. That will create a SQLite database the application needs. Then run `pipenv run python ./manage.py runserver`. That will start a local server on http://localhost:8000/. 19 | 20 | Point your browser to that address and log in to your instance. You will have to log in with the alternate (username and password) method. 21 | 22 | Server installation 23 | ---------------------------------------- 24 | This will depend on your server setup, and you should consult [Deploying Django][dd]. Be sure to read the [Deployment checklist][dc], because some things in it are security critical. You will also want to set up a database. Brutaldon doesn't use the database very heavily, so if you only have a few users, the default SQLite is probably fine and doesn't require any additional setup. 25 | 26 | One common step would be to install dependencies like this: `PIPENV_VENV_IN_PROJECT=1 pipenv install`. This will install dependencies within the project folder. 27 | 28 | Then edit brutaldon/settings.py. You definitely need to change the values of SECRET_KEY and ALLOWED_HOSTS. Also edit the database parameters to match the database you chose. Then run `pipenv run python ./manage.py migrate` to populate the database. 29 | 30 | I installed brutaldon with Apache and mod_wsgi. If you installed brutaldon in /usr/local/share/, you'd add config lines something like this to the virtual host brutaldon is installed in. 31 | 32 | ``` 33 | Alias /brutaldon/static /usr/local/share/brutaldon/brutaldon/static 34 | 35 | Require all granted 36 | 37 | 38 | WSGIScriptAlias /brutaldon /usr/local/share/brutaldon/brutaldon/wsgi.py 39 | WSGIDaemonProcess brutaldon python-path=/usr/local/share/brutaldon python-home=/usr/local/share/brutaldon/.venv 40 | 41 | 42 | 43 | Require all granted 44 | 45 | 46 | ``` 47 | 48 | Be sure you serve the entire site over https only. 49 | 50 | [dd]: https://docs.djangoproject.com/en/2.0/howto/deployment/ 51 | [dc]: https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 52 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | "beautifulsoup4" = "*" 8 | bleach = "*" 9 | certifi = "*" 10 | chardet = "*" 11 | decorator = "*" 12 | django-markdownify = "*" 13 | django-widget-tweaks = "*" 14 | "html5lib" = "*" 15 | idna = "*" 16 | markdown = "*" 17 | "mastodon.py" = ">=1.4.2" 18 | python-dateutil = "*" 19 | pytz = "*" 20 | requests = "*" 21 | six = "*" 22 | "urllib3" = "*" 23 | webencodings = "*" 24 | Django = "*" 25 | django-html_sanitizer = "*" 26 | inscriptis = "*" 27 | lxml = "*" 28 | 29 | [dev-packages] 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Brutaldon 2 | 3 | Note: If you are seeing this on Github, this repo is a mirror that may not be up-to-date. Please go to https://git.carcosa.net/jmcbray/brutaldon for the latest code. 4 | 5 | Brutaldon is a [brutalist][0], [Web 1.0][0.5] web interface for [Mastodon][1] and [Pleroma][p]. It is not a Mastodon-compatible social networking server; rather, it is just a client, like the Android or iOS client for Mastodon you may already be using, but it runs in a web server, and is accessed through a web browser. It works great in text-mode browsers such as [Lynx][2], [w3m][3], or [elinks][4], and also in more heavy-weight graphical browsers, such as Firefox. It works completely without JavaScript, but if JavaScript is available and enabled, it will be used to unobtrusively enhance the user experience. 6 | 7 | [0]:http://brutalistwebsites.com/ 8 | [0.5]: https://en.wikipedia.org/wiki/Web_2.0#%22Web_1.0%22 9 | [1]: https://joinmastodon.org/ 10 | [2]: https://lynx.browser.org/ 11 | [3]: https://w3m.sourceforge.net/ 12 | [4]: http://elinks.or.cz/ 13 | [p]: https://pleroma.social/ 14 | 15 | There is a hosted instance at [brutaldon.online][hosted] which you can use to log in to any instance. However, you are also encouraged to run your own, either locally or on a public server. 16 | 17 | [hosted]: https://brutaldon.online/ 18 | 19 | Brutaldon is ready for day to day use, and is my main way of interacting with the fediverse. It is still missing some features you might want, like lists, filters, and editing your own profile. 20 | Please see the issues tracker. 21 | 22 | ## Screenshots 23 | 24 | People love screenshots, whatever the project, so here we are. These screenshots are relatively old. 25 | 26 | 27 | 28 | 31 | 34 | 35 | 36 | 39 | 42 | 45 | 46 |
29 | Brutaldon in Lynx 30 | 32 | Brutaldon in Firefox 33 |
37 | Brutaldon in Firefox (2) 38 | 40 | Brutaldon in Firefox - Full Brutalism 41 | 43 | Brutaldon in Firefox - Full Brutalism (2) 44 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | ## Roadmap 54 | 55 | * [X] Single user read-only access; log in and read home timeline 56 | * [X] Fix edge cases of toot display (CW, media, boosts) 57 | * [X] Multi-user, multi-instance support 58 | * [X] Add support for reading local and federated timelines, notifications, favorites, threads 59 | * [X] Add support for tag timelines 60 | * [X] Add support for viewing profiles 61 | * [X] Add support for posting. 62 | * [X] Add support for posting media. 63 | * [X] Add support for favoriting and boosting toots. 64 | * [X] Add support for following, blocking, and muting users. 65 | 66 | ## Aesthetic 67 | 68 | No automatic page updates: refresh the page to see new toots. No endless scroll: there's a "next page" link. No autocompletion of anything: use another lynx process in another screen window to look things up. UTF8 clean. 69 | 70 | ## Tip Jar 71 | 72 | You can buy me a coffee to give me energy to work on this, but only if you have it to spare. 73 | [![ko-fi](https://www.ko-fi.com/img/donate_sm.png)](https://ko-fi.com/D1D7QBZC) 74 | -------------------------------------------------------------------------------- /brutaldon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/__init__.py -------------------------------------------------------------------------------- /brutaldon/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse 2 | 3 | 4 | def bookmarklet_url(request): 5 | share_url = request.build_absolute_uri(reverse("share")) 6 | return { 7 | "bookmarklet_url": f"javascript:location.href='{share_url}?url='+encodeURIComponent(location.href)+';title='+encodeURIComponent(document.title)" 8 | } 9 | -------------------------------------------------------------------------------- /brutaldon/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.conf import settings 3 | from django.utils.translation import gettext as _ 4 | from pytz import common_timezones 5 | from .models import Theme, Preference 6 | 7 | 8 | PRIVACY_CHOICES = ( 9 | ("public", _("Public")), 10 | ("unlisted", _("Unlisted")), 11 | ("private", _("Private")), 12 | ("direct", _("Direct")), 13 | ) 14 | 15 | timezones = [(tz, tz) for tz in common_timezones] 16 | 17 | 18 | class LoginForm(forms.Form): 19 | instance = forms.CharField(label=_("Instance"), max_length=256) 20 | email = forms.EmailField(label=_("Email")) 21 | password = forms.CharField(widget=forms.PasswordInput()) 22 | 23 | 24 | class OAuthLoginForm(forms.Form): 25 | instance = forms.CharField(label=_("Instance"), max_length=256) 26 | 27 | 28 | class PreferencesForm(forms.ModelForm): 29 | class Meta: 30 | model = Preference 31 | fields = Preference._fields 32 | 33 | 34 | class PostForm(forms.Form): 35 | """def status_post(self, status, in_reply_to_id=None, media_ids=None, 36 | sensitive=False, visibility=None, spoiler_text=None):""" 37 | 38 | status = forms.CharField(label="Toot", widget=forms.Textarea) 39 | visibility = forms.ChoiceField( 40 | label=_("Toot visibility"), choices=PRIVACY_CHOICES, required=False 41 | ) 42 | spoiler_text = forms.CharField(label=_("CW or Subject"), required=False) 43 | media_file_1 = forms.FileField(label=_("Media 1"), required=False) 44 | media_text_1 = forms.CharField(label=_("Describe media 1."), required=False) 45 | media_file_2 = forms.FileField(label=_("Media 2"), required=False) 46 | media_text_2 = forms.CharField(label=_("Describe media 2."), required=False) 47 | media_file_3 = forms.FileField(label=_("Media 3"), required=False) 48 | media_text_3 = forms.CharField(label=_("Describe media 3."), required=False) 49 | media_file_4 = forms.FileField(label=_("Media 4"), required=False) 50 | media_text_4 = forms.CharField(label=_("Describe media 4."), required=False) 51 | media_sensitive = forms.BooleanField(label=_("Sensitive media?"), required=False) 52 | 53 | 54 | class FilterForm(forms.Form): 55 | phrase = forms.CharField(label=_("Word or phrase to filter")) 56 | context_home = forms.BooleanField( 57 | label=_("In home timeline"), required=False, initial=True 58 | ) 59 | context_public = forms.BooleanField( 60 | label=_("In public timelines"), required=False, initial=True 61 | ) 62 | context_notes = forms.BooleanField( 63 | label=_("In notifications"), required=False, initial=True 64 | ) 65 | context_thread = forms.BooleanField( 66 | label=_("In thread contexts"), required=False, initial=True 67 | ) 68 | whole_word = forms.BooleanField( 69 | label=_("Whole words only"), required=False, initial=True 70 | ) 71 | expires_in = forms.TypedChoiceField( 72 | label=_("Expires in"), 73 | choices=( 74 | ("", "Never"), 75 | ("1800", "30 minutes"), 76 | ("3600", "1 hour"), 77 | ("21600", "6 hours"), 78 | ("43200", "12 hours"), 79 | ("86400", "1 day"), 80 | ("604800", "1 week"), 81 | ), 82 | coerce=int, 83 | required=False, 84 | ) 85 | -------------------------------------------------------------------------------- /brutaldon/middleware/timezone.py: -------------------------------------------------------------------------------- 1 | import pytz 2 | 3 | from django.utils import timezone 4 | from django.utils.deprecation import MiddlewareMixin 5 | 6 | 7 | class TimezoneMiddleware(MiddlewareMixin): 8 | def process_request(self, request): 9 | tzname = request.session.get("timezone", "UTC") 10 | if tzname: 11 | timezone.activate(pytz.timezone(tzname)) 12 | else: 13 | timezone.deactivate() 14 | -------------------------------------------------------------------------------- /brutaldon/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-04-23 18:25 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="Client", 15 | fields=[ 16 | ( 17 | "id", 18 | models.AutoField( 19 | auto_created=True, 20 | primary_key=True, 21 | serialize=False, 22 | verbose_name="ID", 23 | ), 24 | ), 25 | ("name", models.TextField(default="brutaldon")), 26 | ("api_base_id", models.URLField(default="mastodon.social")), 27 | ("client_id", models.TextField(blank=True, null=True)), 28 | ("client_secret", models.TextField(blank=True, null=True)), 29 | ], 30 | ) 31 | ] 32 | -------------------------------------------------------------------------------- /brutaldon/migrations/0002_account.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-04-23 21:34 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ("brutaldon", "0001_initial"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="Account", 18 | fields=[ 19 | ( 20 | "id", 21 | models.AutoField( 22 | auto_created=True, 23 | primary_key=True, 24 | serialize=False, 25 | verbose_name="ID", 26 | ), 27 | ), 28 | ("username", models.CharField(max_length=80)), 29 | ("access_token", models.TextField(blank=True, null=True)), 30 | ( 31 | "django_user", 32 | models.ForeignKey( 33 | null=True, 34 | on_delete=django.db.models.deletion.CASCADE, 35 | to=settings.AUTH_USER_MODEL, 36 | ), 37 | ), 38 | ], 39 | ) 40 | ] 41 | -------------------------------------------------------------------------------- /brutaldon/migrations/0003_auto_20180424_1255.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.4 on 2018-04-24 16:55 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [("brutaldon", "0002_account")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="account", 14 | name="client", 15 | field=models.ForeignKey( 16 | null=True, 17 | on_delete=django.db.models.deletion.SET_NULL, 18 | to="brutaldon.Client", 19 | ), 20 | ), 21 | migrations.AlterField( 22 | model_name="client", 23 | name="api_base_id", 24 | field=models.URLField(default="https://mastodon.social"), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /brutaldon/migrations/0004_auto_20180424_1424.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.4 on 2018-04-24 18:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("brutaldon", "0003_auto_20180424_1255")] 9 | 10 | operations = [ 11 | migrations.AlterField( 12 | model_name="account", 13 | name="username", 14 | field=models.EmailField(max_length=254), 15 | ) 16 | ] 17 | -------------------------------------------------------------------------------- /brutaldon/migrations/0005_auto_20180618_2103.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.6 on 2018-06-19 01:03 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [("brutaldon", "0004_auto_20180424_1424")] 10 | 11 | operations = [ 12 | migrations.CreateModel( 13 | name="Preference", 14 | fields=[ 15 | ( 16 | "id", 17 | models.AutoField( 18 | auto_created=True, 19 | primary_key=True, 20 | serialize=False, 21 | verbose_name="ID", 22 | ), 23 | ), 24 | ("data_saver", models.BooleanField(default=False)), 25 | ("fix_emojos", models.BooleanField(default=False)), 26 | ], 27 | ), 28 | migrations.CreateModel( 29 | name="Theme", 30 | fields=[ 31 | ( 32 | "id", 33 | models.AutoField( 34 | auto_created=True, 35 | primary_key=True, 36 | serialize=False, 37 | verbose_name="ID", 38 | ), 39 | ), 40 | ("name", models.TextField(max_length=80)), 41 | ("main_css", models.TextField(max_length=1024)), 42 | ( 43 | "tweaks_css", 44 | models.TextField(blank=True, max_length=1024, null=True), 45 | ), 46 | ("is_brutalist", models.BooleanField(default=False)), 47 | ], 48 | ), 49 | migrations.AddField( 50 | model_name="preference", 51 | name="theme", 52 | field=models.ForeignKey( 53 | null=True, 54 | on_delete=django.db.models.deletion.SET_NULL, 55 | to="brutaldon.Theme", 56 | ), 57 | ), 58 | migrations.AddField( 59 | model_name="account", 60 | name="preferences", 61 | field=models.ForeignKey( 62 | null=True, 63 | on_delete=django.db.models.deletion.SET_NULL, 64 | to="brutaldon.Preference", 65 | ), 66 | ), 67 | ] 68 | -------------------------------------------------------------------------------- /brutaldon/migrations/0006_auto_20180618_2112.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.6 on 2018-06-19 01:12 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("brutaldon", "0005_auto_20180618_2103")] 9 | 10 | operations = [ 11 | migrations.AlterField( 12 | model_name="theme", 13 | name="main_css", 14 | field=models.TextField( 15 | blank=True, default="css/fullbrutalism.css", max_length=1024, null=True 16 | ), 17 | ) 18 | ] 19 | -------------------------------------------------------------------------------- /brutaldon/migrations/0007_auto_20180618_2115.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.6 on 2018-06-19 01:03 2 | 3 | from django.db import migrations 4 | 5 | 6 | def set_up_default_themes(apps, schema_editor): 7 | Theme = apps.get_model("brutaldon", "Theme") 8 | default = Theme( 9 | name="default", 10 | main_css="css/bulma.min.css", 11 | tweaks_css="css/brutaldon.css", 12 | is_brutalist=False, 13 | ) 14 | default.save() 15 | dark = Theme( 16 | name="default dark", 17 | main_css="css/bulmaswatch-darkly.min.css", 18 | tweaks_css="css/brutaldon-dark.css", 19 | is_brutalist=False, 20 | ) 21 | dark.save() 22 | lux = Theme( 23 | name="Lux", 24 | main_css="css/bulmaswatch-lux.min.css", 25 | tweaks_css="css/brutaldon.css", 26 | is_brutalist=False, 27 | ) 28 | lux.save() 29 | solar = Theme( 30 | name="Solar", 31 | main_css="css/bulmaswatch-solar.min.css", 32 | tweaks_css="css/brutaldon.css", 33 | is_brutalist=False, 34 | ) 35 | solar.save() 36 | material = Theme( 37 | name="Material", 38 | main_css="css/bulmaswatch-materia.min.css", 39 | tweaks_css="css/brutaldon-material.css", 40 | is_brutalist=False, 41 | ) 42 | material.save() 43 | brutalism = Theme( 44 | name="FULLBRUTALISM", main_css="css/fullbrutalism.css", is_brutalist=True 45 | ) 46 | brutalism.save() 47 | brutstrap = Theme( 48 | name="Brutstrap", 49 | main_css="css/brutstrap.css", 50 | is_brutalist=True, 51 | tweaks_css="css/brutstrap-tweaks.css", 52 | ) 53 | brutstrap.save() 54 | large = Theme( 55 | name="Minimalist Large", main_css="css/minimal-large.css", is_brutalist=True 56 | ) 57 | large.save() 58 | small = Theme( 59 | name="Minimalist Small", main_css="css/minimal-small.css", is_brutalist=True 60 | ) 61 | small.save() 62 | dark2 = Theme( 63 | name="Minimalist Dark", main_css="css/minimal-dark.css", is_brutalist=True 64 | ) 65 | dark2.save() 66 | vt240 = Theme( 67 | name="vt240 amber", main_css="css/vt240don-amber.css", is_brutalist=True 68 | ) 69 | vt240.save() 70 | vt240_green = Theme( 71 | name="vt240 green", main_css="css/vt240don-green.css", is_brutalist=True 72 | ) 73 | vt240_green.save() 74 | minimal = Theme(name="No styling at all", main_css=None, is_brutalist=True) 75 | minimal.save() 76 | 77 | 78 | def delete_themes(apps, schema_editor): 79 | Theme = apps.get_model("brutaldon" "Theme") 80 | for theme in Theme.objects.all(): 81 | theme.delete() 82 | 83 | 84 | class Migration(migrations.Migration): 85 | 86 | dependencies = [("brutaldon", "0006_auto_20180618_2112")] 87 | 88 | operations = [migrations.RunPython(set_up_default_themes, delete_themes)] 89 | -------------------------------------------------------------------------------- /brutaldon/migrations/0008_auto_20180618_2140.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.6 on 2018-06-19 01:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("brutaldon", "0007_auto_20180618_2115")] 9 | 10 | operations = [ 11 | migrations.AlterField( 12 | model_name="theme", 13 | name="name", 14 | field=models.TextField(max_length=80, unique=True), 15 | ) 16 | ] 17 | -------------------------------------------------------------------------------- /brutaldon/migrations/0012_auto_20180826_1853.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-08-26 22:53 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("brutaldon", "0011_auto_20180825_1017")] 9 | 10 | operations = [ 11 | migrations.AlterField( 12 | model_name="account", 13 | name="username", 14 | field=models.EmailField(max_length=254, unique=True), 15 | ) 16 | ] 17 | -------------------------------------------------------------------------------- /brutaldon/migrations/0013_auto_20180826_1935.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-08-26 23:35 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [("brutaldon", "0012_auto_20180826_1853")] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="preference", 14 | name="theme", 15 | field=models.ForeignKey( 16 | default=1, 17 | on_delete=django.db.models.deletion.CASCADE, 18 | to="brutaldon.Theme", 19 | ), 20 | ) 21 | ] 22 | -------------------------------------------------------------------------------- /brutaldon/migrations/0014_account_note_seen.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.1 on 2018-09-08 11:47 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("brutaldon", "0013_auto_20180826_1935")] 9 | 10 | operations = [ 11 | migrations.AddField( 12 | model_name="account", name="note_seen", field=models.IntegerField(null=True) 13 | ) 14 | ] 15 | -------------------------------------------------------------------------------- /brutaldon/migrations/0015_auto_20181001_1812.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.1 on 2018-10-01 22:12 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("brutaldon", "0014_account_note_seen")] 9 | 10 | operations = [ 11 | migrations.AddField( 12 | model_name="preference", 13 | name="click_to_load", 14 | field=models.BooleanField(default=False), 15 | ), 16 | migrations.AddField( 17 | model_name="preference", 18 | name="no_javascript", 19 | field=models.BooleanField(default=False), 20 | ), 21 | migrations.AddField( 22 | model_name="preference", 23 | name="notifications", 24 | field=models.BooleanField(default=True), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /brutaldon/migrations/0016_auto_20181009_1805.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-10-09 22:05 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("brutaldon", "0015_auto_20181001_1812")] 9 | 10 | operations = [ 11 | migrations.AddField( 12 | model_name="preference", 13 | name="lightbox", 14 | field=models.BooleanField( 15 | default=False, help_text="Use a JavaScript lightbox to display media." 16 | ), 17 | ), 18 | migrations.AlterField( 19 | model_name="preference", 20 | name="click_to_load", 21 | field=models.BooleanField( 22 | default=False, 23 | help_text="Click to load more toots in the same page, rather than using pagination.", 24 | ), 25 | ), 26 | migrations.AlterField( 27 | model_name="preference", 28 | name="no_javascript", 29 | field=models.BooleanField( 30 | default=False, 31 | help_text="Disable all JavaScript. Overrides all other JavaScript options.", 32 | ), 33 | ), 34 | migrations.AlterField( 35 | model_name="preference", 36 | name="notifications", 37 | field=models.BooleanField( 38 | default=True, help_text="Display live notifications in header." 39 | ), 40 | ), 41 | ] 42 | -------------------------------------------------------------------------------- /brutaldon/migrations/0017_preference_poll_frequency.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2018-10-18 19:52 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("brutaldon", "0016_auto_20181009_1805")] 9 | 10 | operations = [ 11 | migrations.AddField( 12 | model_name="preference", 13 | name="poll_frequency", 14 | field=models.IntegerField( 15 | default=300, 16 | help_text="Number of seconds to wait between checking notifications. Default: 300", 17 | ), 18 | ) 19 | ] 20 | -------------------------------------------------------------------------------- /brutaldon/migrations/0018_preference_filter_notifications.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.5 on 2019-01-14 13:51 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("brutaldon", "0017_preference_poll_frequency")] 9 | 10 | operations = [ 11 | migrations.AddField( 12 | model_name="preference", 13 | name="filter_notifications", 14 | field=models.BooleanField( 15 | default=False, 16 | help_text="Exclude boosts and favs from your notifications.", 17 | ), 18 | ) 19 | ] 20 | -------------------------------------------------------------------------------- /brutaldon/migrations/0020_auto_20190127_2159.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.5 on 2019-01-28 02:59 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("brutaldon", "0019_auto_20190124_0813")] 9 | 10 | operations = [ 11 | migrations.AlterField( 12 | model_name="account", 13 | name="note_seen", 14 | field=models.CharField(blank=True, max_length=128, null=True), 15 | ) 16 | ] 17 | -------------------------------------------------------------------------------- /brutaldon/migrations/0021_client_version.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.5 on 2019-04-29 18:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("brutaldon", "0020_auto_20190127_2159")] 9 | 10 | operations = [ 11 | migrations.AddField( 12 | model_name="client", 13 | name="version", 14 | field=models.CharField(default="1.0", max_length=8), 15 | ) 16 | ] 17 | -------------------------------------------------------------------------------- /brutaldon/migrations/0022_auto_20190506_0938.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-06 13:38 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("brutaldon", "0021_client_version")] 9 | 10 | operations = [ 11 | migrations.AlterField( 12 | model_name="client", 13 | name="version", 14 | field=models.CharField(default="1.0", max_length=80), 15 | ) 16 | ] 17 | -------------------------------------------------------------------------------- /brutaldon/migrations/0023_preference_bundle_notifications.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.7 on 2019-11-04 23:53 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("brutaldon", "0022_auto_20190506_0938")] 9 | 10 | operations = [ 11 | migrations.AddField( 12 | model_name="preference", 13 | name="bundle_notifications", 14 | field=models.BooleanField( 15 | default=False, 16 | help_text="Collapse together boosts or likes of the same toot in the notifications page.", 17 | ), 18 | ) 19 | ] 20 | -------------------------------------------------------------------------------- /brutaldon/migrations/0025_preference_preview_sensitive.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.6 on 2020-06-01 14:17 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("brutaldon", "0024_auto_20200601_0945"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="preference", 15 | name="preview_sensitive", 16 | field=models.BooleanField( 17 | default=False, help_text='Show preview for media marked as "sensitive"' 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /brutaldon/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/migrations/__init__.py -------------------------------------------------------------------------------- /brutaldon/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.conf import settings 3 | from django.utils.translation import gettext as _ 4 | from pytz import common_timezones 5 | 6 | timezones = [(tz, tz) for tz in common_timezones] 7 | 8 | 9 | class Client(models.Model): 10 | name = models.CharField(default="brutaldon", max_length=80) 11 | api_base_id = models.URLField(default="https://mastodon.social") 12 | version = models.CharField(default="1.0", max_length=80) 13 | client_id = models.CharField(null=True, blank=True, max_length=2048) 14 | client_secret = models.CharField(null=True, blank=True, max_length=2048) 15 | 16 | def __str__(self): 17 | return self.name + ": " + self.api_base_id 18 | 19 | 20 | class Theme(models.Model): 21 | name = models.CharField(max_length=80, unique=True) 22 | prefix = models.CharField(max_length=40, null=True, default="default") 23 | main_css = models.CharField( 24 | max_length=1024, blank=True, null=True, default="css/fullbrutalism.css" 25 | ) 26 | tweaks_css = models.CharField(max_length=1024, blank=True, null=True) 27 | is_brutalist = models.BooleanField(default=False) 28 | 29 | def __str__(self): 30 | return self.name 31 | 32 | 33 | from django.db.models.fields.related_descriptors import ForeignKeyDeferredAttribute 34 | 35 | 36 | def set_fields(klass): 37 | fields = [] 38 | for n in dir(klass): 39 | assert n != "_fields" 40 | v = getattr(klass, n) 41 | if not hasattr(v, "field"): 42 | continue 43 | if not isinstance(v.field, models.Field): 44 | continue 45 | if isinstance(v, ForeignKeyDeferredAttribute): 46 | continue 47 | fields.append(n) 48 | setattr(klass, "_fields", fields) 49 | return klass 50 | 51 | 52 | @set_fields 53 | class Preference(models.Model): 54 | theme = models.ForeignKey(Theme, models.CASCADE, null=False, default=1) 55 | filter_replies = models.BooleanField(default=False) 56 | filter_boosts = models.BooleanField(default=False) 57 | timezone = models.CharField( 58 | max_length=80, blank=True, null=True, choices=timezones, default="UTC" 59 | ) 60 | preview_sensitive = models.BooleanField( 61 | default=False, help_text=_('Show preview for media marked as "sensitive"') 62 | ) 63 | 64 | no_javascript = models.BooleanField( 65 | default=False, 66 | help_text=_( 67 | """Disable all JavaScript. Overrides all other JavaScript options.""" 68 | ), 69 | ) 70 | notifications = models.BooleanField( 71 | default=True, help_text=_("""Display live notifications in header.""") 72 | ) 73 | click_to_load = models.BooleanField( 74 | default=False, 75 | help_text=_( 76 | """Click to load more toots in the same page, rather than using pagination.""" 77 | ), 78 | ) 79 | lightbox = models.BooleanField( 80 | default=False, help_text=_("""Use a JavaScript lightbox to display media.""") 81 | ) 82 | poll_frequency = models.IntegerField( 83 | default=300, 84 | help_text=_( 85 | """Number of seconds to wait between checking notifications. Default: 300""" 86 | ), 87 | ) 88 | filter_notifications = models.BooleanField( 89 | default=False, 90 | help_text=_("""Exclude boosts and favs from your notifications."""), 91 | ) 92 | bundle_notifications = models.BooleanField( 93 | default=False, 94 | help_text=_( 95 | """Collapse together boosts or likes of the same toot in the notifications page.""" 96 | ), 97 | ) 98 | 99 | 100 | class Account(models.Model): 101 | username = models.EmailField(unique=True) 102 | email = models.EmailField(null=True, blank=True) 103 | django_user = models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE, null=True) 104 | access_token = models.CharField(null=True, blank=True, max_length=2048) 105 | client = models.ForeignKey(Client, models.SET_NULL, null=True) 106 | preferences = models.ForeignKey(Preference, models.SET_NULL, null=True) 107 | note_seen = models.CharField(null=True, blank=True, max_length=128) 108 | -------------------------------------------------------------------------------- /brutaldon/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for brutaldon project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.0.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "6lq9!52j^)=m89))umaphx9ac%)b$k^gs%x1rkk^v^$u9zjz$@" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | "widget_tweaks", 41 | "sanitizer", 42 | "django.contrib.humanize", 43 | "brutaldon", 44 | ] 45 | 46 | MIDDLEWARE = [ 47 | "django.middleware.security.SecurityMiddleware", 48 | "django.contrib.sessions.middleware.SessionMiddleware", 49 | "django.middleware.common.CommonMiddleware", 50 | "django.middleware.csrf.CsrfViewMiddleware", 51 | "django.contrib.auth.middleware.AuthenticationMiddleware", 52 | "django.contrib.messages.middleware.MessageMiddleware", 53 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 54 | "brutaldon.middleware.timezone.TimezoneMiddleware", 55 | ] 56 | 57 | ROOT_URLCONF = "brutaldon.urls" 58 | 59 | TEMPLATES = [ 60 | { 61 | "BACKEND": "django.template.backends.django.DjangoTemplates", 62 | "DIRS": [], 63 | "APP_DIRS": True, 64 | "OPTIONS": { 65 | "context_processors": [ 66 | "django.template.context_processors.debug", 67 | "django.template.context_processors.request", 68 | "django.contrib.auth.context_processors.auth", 69 | "django.contrib.messages.context_processors.messages", 70 | "brutaldon.context_processors.bookmarklet_url", 71 | ] 72 | }, 73 | } 74 | ] 75 | 76 | WSGI_APPLICATION = "brutaldon.wsgi.application" 77 | 78 | 79 | # Database 80 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases 81 | 82 | DATABASES = { 83 | "default": { 84 | "ENGINE": "django.db.backends.sqlite3", 85 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"), 86 | } 87 | } 88 | 89 | 90 | # Password validation 91 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators 92 | 93 | AUTH_PASSWORD_VALIDATORS = [ 94 | { 95 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" 96 | }, 97 | {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, 98 | {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, 99 | {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, 100 | ] 101 | 102 | LOGGING = { 103 | "version": 1, 104 | "disable_existing_loggers": False, 105 | "filters": { 106 | "require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}, 107 | "require_debug_true": {"()": "django.utils.log.RequireDebugTrue"}, 108 | }, 109 | "formatters": { 110 | "django.server": { 111 | "()": "django.utils.log.ServerFormatter", 112 | "format": "[%(server_time)s] %(message)s", 113 | } 114 | }, 115 | "handlers": { 116 | "console": { 117 | "level": "INFO", 118 | "filters": ["require_debug_true"], 119 | "class": "logging.StreamHandler", 120 | }, 121 | "console_debug_false": { 122 | "level": "ERROR", 123 | "filters": ["require_debug_false"], 124 | "class": "logging.StreamHandler", 125 | }, 126 | "django.server": { 127 | "level": "INFO", 128 | "class": "logging.StreamHandler", 129 | "formatter": "django.server", 130 | }, 131 | "mail_admins": { 132 | "level": "ERROR", 133 | "filters": ["require_debug_false"], 134 | "class": "django.utils.log.AdminEmailHandler", 135 | }, 136 | }, 137 | "loggers": { 138 | "django": { 139 | "handlers": ["console", "console_debug_false", "mail_admins"], 140 | "level": "INFO", 141 | }, 142 | "django.server": { 143 | "handlers": ["django.server"], 144 | "level": "INFO", 145 | "propagate": False, 146 | }, 147 | }, 148 | } 149 | 150 | 151 | # Internationalization 152 | # https://docs.djangoproject.com/en/2.0/topics/i18n/ 153 | 154 | LANGUAGE_CODE = "en-us" 155 | 156 | TIME_ZONE = "America/New_York" 157 | 158 | USE_I18N = True 159 | 160 | USE_L10N = True 161 | 162 | USE_TZ = True 163 | 164 | 165 | # Static files (CSS, JavaScript, Images) 166 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 167 | 168 | STATIC_URL = "/static/" 169 | STATIC_ROOT = os.path.join(BASE_DIR, "static") 170 | 171 | # Sanitizer settings 172 | SANITIZER_ALLOWED_TAGS = [ 173 | "a", 174 | "p", 175 | "img", 176 | "br", 177 | "i", 178 | "strong", 179 | "em", 180 | "pre", 181 | "code", 182 | "ul", 183 | "li", 184 | "ol", 185 | "blockquote", 186 | "del", 187 | "span", 188 | "u", 189 | ] 190 | SANITIZER_ALLOWED_ATTRIBUTES = ["href", "src", "title", "alt", "class", "lang"] 191 | 192 | # File upload settings. 193 | # Important: media will not work if you change this. 194 | FILE_UPLOAD_HANDLERS = ["django.core.files.uploadhandler.TemporaryFileUploadHandler"] 195 | 196 | # Session serialization 197 | # Important: whatever you choose has to be able to serialize DateTime, so not JSON. 198 | SESSION_SERIALIZER = "django.contrib.sessions.serializers.PickleSerializer" 199 | 200 | # URL to redirect users to when not logged in 201 | ANONYMOUS_HOME_URL = "about" 202 | 203 | # URL to redirect galaxy brain users to 204 | RICKROLL_URL = "https://invidio.us/watch?v=dQw4w9WgXcQ" 205 | 206 | # Function to check if trying to add an account should trigger a special response 207 | def CHECK_INSTANCE_URL(url, redirect): 208 | if "gab.com" in url: 209 | return redirect(RICKROLL_URL) 210 | elif "shitposter.club" in url: 211 | return redirect(RICKROLL_URL) 212 | 213 | 214 | # Version number displayed on about page 215 | BRUTALDON_VERSION = "2.15.0" 216 | -------------------------------------------------------------------------------- /brutaldon/static/css/brutaldon-dark.css: -------------------------------------------------------------------------------- 1 | body.has-navbar-fixed-top { 2 | padding-top: 48px; 3 | } 4 | 5 | body > section > div.container { 6 | max-width: 100%; 7 | } 8 | 9 | 10 | .reblog-icon { 11 | position: relative; 12 | top: -24px; 13 | left: 40px; 14 | } 15 | 16 | img.fav-avatar { 17 | display: inline; 18 | 19 | } 20 | 21 | 22 | .media-content { 23 | padding: 1.25ex; 24 | } 25 | 26 | .is-max-128 img, .is-max-192 img, .is-max-256 img 27 | { 28 | bottom: 0; 29 | left: 0; 30 | position: absolute; 31 | right:0; 32 | top: 0; 33 | } 34 | 35 | .is-max-128 { 36 | max-height: 128px; 37 | max-width: 128px; 38 | } 39 | 40 | .is-max-256 { 41 | max-height: 256px; 42 | max-width: 256px; 43 | } 44 | 45 | .is-max-192 { 46 | max-height: 192px; 47 | max-width: 192px; 48 | } 49 | 50 | figure.media-left p.image a img 51 | { 52 | border-radius: 5px; 53 | } 54 | 55 | img.avatar 56 | { 57 | border-radius: 5px; 58 | } 59 | 60 | .active-context { 61 | background-color: #444; 62 | } 63 | 64 | h2.subtitle 65 | { 66 | margin-top: 2rem; 67 | margin-bottom: 1rem; 68 | } 69 | 70 | article.media.user-info .content img 71 | { 72 | max-height: 1.5rem; 73 | max-width: 1.5rem; 74 | } 75 | 76 | span.account-locked 77 | { 78 | margin-top: 48px; 79 | margin-left: -16px; 80 | } 81 | 82 | .errorlist 83 | { 84 | color: #FF0000; 85 | } 86 | 87 | .emoji-box 88 | { 89 | padding: .75rem; 90 | background-color: #444; 91 | border: 1px solid #ccc; 92 | border-radius: 5px; 93 | } 94 | 95 | img.emoji 96 | { 97 | display: inline; 98 | max-height: 1.5em; 99 | max-width: 1.5em; 100 | vertical-align: text-bottom; 101 | } 102 | 103 | emoji-link 104 | { 105 | font-size: 2em; 106 | } 107 | 108 | .content figure.attachment-image 109 | { 110 | text-align:left; 111 | margin-left: 0; 112 | margin-right: 0; 113 | } 114 | 115 | 116 | @media screen and (max-width: 768px) { 117 | .media { 118 | display: block; 119 | } 120 | } 121 | 122 | .level.attachments 123 | { 124 | overflow: auto; 125 | } 126 | 127 | .file-cta 128 | { 129 | -webkit-border-top-left-radius: 5px; 130 | -webkit-border-bottom-left-radius: 5px; 131 | -webkit-border-bottom-right-radius: 0; 132 | -webkit-border-top-right-radius: 0; 133 | -moz-border-radius-topleft: 5px; 134 | -moz-border-radius-bottomleft: 5px; 135 | -moz-border-radius-bottomright: 0; 136 | -moz-border-radius-topright: 0; 137 | border-top-left-radius: 5px; 138 | border-bottom-left-radius: 5px; 139 | border-bottom-right-radius: 0; 140 | border-top-right-radius: 0; 141 | height: 38px; 142 | } 143 | 144 | #page-load-indicator 145 | { 146 | width: 100%; 147 | opacity: 0.8; 148 | position: fixed; 149 | top: 0; 150 | left: 0; 151 | z-index: 666; 152 | transition: all 500ms; 153 | height: 2px; 154 | overflow: hidden; 155 | background-color: #ddd; 156 | display: none; 157 | } 158 | 159 | #page-load-indicator:before{ 160 | display: block; 161 | position: absolute; 162 | content: ""; 163 | left: -200px; 164 | width: 200px; 165 | height: 4px; 166 | background-color: #888; 167 | animation: page-loading 1.5s linear infinite; 168 | } 169 | 170 | @keyframes page-loading { 171 | from {left: -200px; width: 30%;} 172 | 50% {width: 30%;} 173 | 70% {width: 70%;} 174 | 80% { left: 50%;} 175 | 95% {left: 120%;} 176 | to {left: 100%;} 177 | } 178 | 179 | #status_count 180 | { 181 | margin-left: 90%; 182 | margin-top: 1rem; 183 | background-color: #888; 184 | color: #FFF; 185 | float: right; 186 | padding: 0.5ex; 187 | border-radius: 5px; 188 | min-height: 1.5rem; 189 | min-width: 1.5rem; 190 | font-size: 0.8em; 191 | text-align: right; 192 | } 193 | 194 | #before-main 195 | { 196 | width: 100%; 197 | height: 2em; 198 | background-color: #888; 199 | color: #444; 200 | margin-top: 12px; 201 | padding: 0; 202 | } 203 | 204 | #before-main span 205 | { 206 | margin-left: 50%; 207 | } 208 | 209 | 210 | input#id_poll_frequency 211 | { 212 | max-width: 10em; 213 | } 214 | 215 | .card 216 | { 217 | margin-top: 1em; 218 | margin: 0, auto; 219 | max-width: 90%; 220 | } 221 | 222 | #username_autocomplete 223 | { 224 | height: 0; 225 | } 226 | 227 | .media-content .content a:not(.mention) 228 | { 229 | text-decoration-line: underline; 230 | text-decoration-style: dotted; 231 | } 232 | 233 | .media-content a.level-item 234 | { 235 | text-decoration: none; 236 | } 237 | 238 | a.navbar-item span:nth-child(2):before 239 | { 240 | content: " "; 241 | } 242 | 243 | div.poll { 244 | margin-bottom: 1ex; 245 | margin-top: 1em; 246 | max-width: 90%; 247 | } 248 | 249 | /* Fix some rules that don't need to be there */ 250 | .content figure:not(:last-child) 251 | { 252 | margin-bottom: 0; 253 | } 254 | .content figure:not(:first-child) 255 | { 256 | margin-top: 0; 257 | } 258 | 259 | .input, 260 | .textarea { 261 | color: inherit; 262 | background-color: inherit; 263 | } 264 | 265 | input[type="text"] { 266 | color: inherit; 267 | background-color: inherit; 268 | } -------------------------------------------------------------------------------- /brutaldon/static/css/brutaldon-material.css: -------------------------------------------------------------------------------- 1 | body > section > div.container { 2 | max-width: 100%; 3 | } 4 | 5 | .reblog-icon { 6 | position: relative; 7 | top: -24px; 8 | left: 40px; 9 | } 10 | 11 | img.fav-avatar { 12 | display: inline; 13 | 14 | } 15 | 16 | 17 | .media-content { 18 | padding: 1.25ex; 19 | } 20 | 21 | .is-max-128 img, .is-max-192 img, .is-max-256 img 22 | { 23 | bottom: 0; 24 | left: 0; 25 | position: absolute; 26 | right:0; 27 | top: 0; 28 | } 29 | 30 | .is-max-128 { 31 | max-height: 128px; 32 | max-width: 128px; 33 | } 34 | 35 | .is-max-256 { 36 | max-height: 256px; 37 | max-width: 256px; 38 | } 39 | 40 | .is-max-192 { 41 | max-height: 192px; 42 | max-width: 192px; 43 | } 44 | 45 | 46 | figure.media-left p.image a img 47 | { 48 | border-radius: 5px; 49 | } 50 | 51 | img.avatar 52 | { 53 | border-radius: 5px; 54 | } 55 | 56 | .active-context { 57 | background-color: #FFF8DC; 58 | } 59 | 60 | h2.subtitle 61 | { 62 | margin-top: 2rem; 63 | margin-bottom: 1rem; 64 | } 65 | 66 | article.media.user-info .content img 67 | { 68 | max-height: 1.5rem; 69 | max-width: 1.5rem; 70 | } 71 | 72 | span.account-locked 73 | { 74 | margin-top: 48px; 75 | margin-left: -16px; 76 | } 77 | 78 | .errorlist 79 | { 80 | color: #FF0000; 81 | } 82 | 83 | .emoji-box 84 | { 85 | padding: .75rem; 86 | background-color: #FCFCFC; 87 | border: 1px solid #CCC; 88 | border-radius: 5px; 89 | } 90 | 91 | img.emoji 92 | { 93 | display: inline; 94 | max-height: 1.5em; 95 | max-width: 1.5em; 96 | vertical-align: text-bottom; 97 | } 98 | 99 | emoji-link 100 | { 101 | font-size: 2em; 102 | } 103 | 104 | .content figure.attachment-image 105 | { 106 | text-align:left; 107 | margin-left: 0; 108 | margin-right: 0; 109 | } 110 | 111 | @media screen and (max-width: 768px) { 112 | .media { 113 | display: block; 114 | } 115 | } 116 | 117 | #page-load-indicator 118 | { 119 | width: 100%; 120 | opacity: 0.8; 121 | position: fixed; 122 | top: 0; 123 | left: 0; 124 | z-index: 666; 125 | transition: all 500ms; 126 | height: 2px; 127 | overflow: hidden; 128 | background-color: #ddd; 129 | display: none; 130 | } 131 | 132 | #page-load-indicator:before{ 133 | display: block; 134 | position: absolute; 135 | content: ""; 136 | left: -200px; 137 | width: 200px; 138 | height: 4px; 139 | background-color: #888; 140 | animation: page-loading 1.5s linear infinite; 141 | } 142 | 143 | @keyframes page-loading { 144 | from {left: -200px; width: 30%;} 145 | 50% {width: 30%;} 146 | 70% {width: 70%;} 147 | 80% { left: 50%;} 148 | 95% {left: 120%;} 149 | to {left: 100%;} 150 | } 151 | 152 | #status_count 153 | { 154 | margin-left: 90%; 155 | margin-top: 1rem; 156 | background-color: #888; 157 | color: #FFF; 158 | float: right; 159 | padding: 0.5ex; 160 | border-radius: 5px; 161 | min-height: 1.5rem; 162 | min-width: 1.5rem; 163 | font-size: 0.8em; 164 | text-align: right; 165 | } 166 | 167 | #before-main 168 | { 169 | width: 100%; 170 | height: 2em; 171 | background-color: #DEDEDE; 172 | color: white; 173 | margin-top: 0; 174 | padding: 0; 175 | } 176 | 177 | #before-main span 178 | { 179 | margin-left: 50%; 180 | } 181 | 182 | input#id_poll_frequency 183 | { 184 | max-width: 10em; 185 | } 186 | 187 | body.has-navbar-fixed-top, html.has-navbar-fixed-top { 188 | padding-top: 5rem; 189 | } 190 | 191 | .card 192 | { 193 | margin-top: 1em; 194 | margin: 0, auto; 195 | max-width: 90%; 196 | } 197 | 198 | 199 | #username_autocomplete 200 | { 201 | height: 0; 202 | } 203 | 204 | .media-content .content a:not(.mention) 205 | { 206 | text-decoration-line: underline; 207 | text-decoration-style: dotted; 208 | } 209 | 210 | .media-content a.level-item 211 | { 212 | text-decoration: none; 213 | } 214 | 215 | 216 | a.navbar-item span:nth-child(2):before 217 | { 218 | content: " "; 219 | } 220 | 221 | 222 | div.poll { 223 | margin-bottom: 1ex; 224 | margin-top: 1em; 225 | max-width: 90%; 226 | } 227 | 228 | 229 | /* Fix some rules that don't need to be there */ 230 | .content figure:not(:last-child) 231 | { 232 | margin-bottom: 0; 233 | } 234 | .content figure:not(:first-child) 235 | { 236 | margin-top: 0; 237 | } 238 | -------------------------------------------------------------------------------- /brutaldon/static/css/brutaldon.css: -------------------------------------------------------------------------------- 1 | body > section > div.container { 2 | max-width: 100%; 3 | } 4 | 5 | .reblog-icon { 6 | position: relative; 7 | top: -24px; 8 | left: 40px; 9 | } 10 | 11 | img.fav-avatar { 12 | display: inline; 13 | 14 | } 15 | 16 | 17 | .media-content { 18 | padding: 1.25ex; 19 | } 20 | 21 | .is-max-128 img, .is-max-192 img, .is-max-256 img 22 | { 23 | bottom: 0; 24 | left: 0; 25 | position: absolute; 26 | right:0; 27 | top: 0; 28 | } 29 | 30 | .is-max-128 { 31 | max-height: 128px; 32 | max-width: 128px; 33 | } 34 | 35 | .is-max-256 { 36 | max-height: 256px; 37 | max-width: 256px; 38 | } 39 | 40 | .is-max-192 { 41 | max-height: 192px; 42 | max-width: 192px; 43 | } 44 | 45 | 46 | figure.media-left p.image a img 47 | { 48 | border-radius: 5px; 49 | } 50 | 51 | img.avatar 52 | { 53 | border-radius: 5px; 54 | } 55 | 56 | .active-context { 57 | background-color: #FFF8DC; 58 | } 59 | 60 | h2.subtitle 61 | { 62 | margin-top: 2rem; 63 | margin-bottom: 1rem; 64 | } 65 | 66 | article.media.user-info .content img 67 | { 68 | max-height: 1.5rem; 69 | max-width: 1.5rem; 70 | } 71 | 72 | span.account-locked 73 | { 74 | margin-top: 48px; 75 | margin-left: -16px; 76 | } 77 | 78 | .errorlist 79 | { 80 | color: #FF0000; 81 | } 82 | 83 | .emoji-box 84 | { 85 | padding: .75rem; 86 | background-color: #FCFCFC; 87 | border: 1px solid #CCC; 88 | border-radius: 5px; 89 | } 90 | 91 | img.emoji 92 | { 93 | display: inline; 94 | max-height: 1.5em; 95 | max-width: 1.5em; 96 | vertical-align: text-bottom; 97 | } 98 | 99 | emoji-link 100 | { 101 | font-size: 2em; 102 | } 103 | 104 | .content figure.attachment-image 105 | { 106 | text-align:left; 107 | margin-left: 0; 108 | margin-right: 0; 109 | } 110 | 111 | @media screen and (max-width: 768px) { 112 | .media { 113 | display: block; 114 | } 115 | } 116 | 117 | #page-load-indicator 118 | { 119 | width: 100%; 120 | opacity: 0.8; 121 | position: fixed; 122 | top: 0; 123 | left: 0; 124 | z-index: 666; 125 | transition: all 500ms; 126 | height: 2px; 127 | overflow: hidden; 128 | background-color: #ddd; 129 | display: none; 130 | } 131 | 132 | #page-load-indicator:before{ 133 | display: block; 134 | position: absolute; 135 | content: ""; 136 | left: -200px; 137 | width: 200px; 138 | height: 4px; 139 | background-color: #888; 140 | animation: page-loading 1.5s linear infinite; 141 | } 142 | 143 | @keyframes page-loading { 144 | from {left: -200px; width: 30%;} 145 | 50% {width: 30%;} 146 | 70% {width: 70%;} 147 | 80% { left: 50%;} 148 | 95% {left: 120%;} 149 | to {left: 100%;} 150 | } 151 | 152 | #status_count 153 | { 154 | margin-left: 90%; 155 | margin-top: 1rem; 156 | background-color: #888; 157 | color: #FFF; 158 | float: right; 159 | padding: 0.5ex; 160 | border-radius: 5px; 161 | min-height: 1.5rem; 162 | min-width: 1.5rem; 163 | font-size: 0.8em; 164 | text-align: right; 165 | } 166 | 167 | #before-main 168 | { 169 | width: 100%; 170 | height: 2em; 171 | background-color: #DEDEDE; 172 | color: white; 173 | margin-top: 0; 174 | padding: 0; 175 | } 176 | 177 | #before-main span 178 | { 179 | margin-left: 50%; 180 | } 181 | 182 | input#id_poll_frequency 183 | { 184 | max-width: 10em; 185 | } 186 | 187 | body.has-navbar-fixed-top, html.has-navbar-fixed-top { 188 | padding-top: 5rem; 189 | } 190 | 191 | .card 192 | { 193 | margin-top: 1em; 194 | margin: 0, auto; 195 | max-width: 90%; 196 | } 197 | 198 | 199 | #username_autocomplete 200 | { 201 | height: 0; 202 | } 203 | 204 | .media-content .content a:not(.mention) 205 | { 206 | text-decoration-line: underline; 207 | text-decoration-style: dotted; 208 | } 209 | 210 | .media-content a.level-item 211 | { 212 | text-decoration: none; 213 | } 214 | 215 | 216 | a.navbar-item span:nth-child(2):before 217 | { 218 | content: " "; 219 | } 220 | 221 | 222 | div.poll { 223 | margin-bottom: 1ex; 224 | margin-top: 1em; 225 | max-width: 90%; 226 | } 227 | 228 | 229 | /* Fix some rules that don't need to be there */ 230 | .content figure:not(:last-child) 231 | { 232 | margin-bottom: 0; 233 | } 234 | .content figure:not(:first-child) 235 | { 236 | margin-top: 0; 237 | } 238 | -------------------------------------------------------------------------------- /brutaldon/static/css/brutstrap-tweaks.css: -------------------------------------------------------------------------------- 1 | /* Tweaks to make brutstrap work with brutaldon's existing html */ 2 | 3 | /* Re-implemented brutstrap features for brutaldon's html structure */ 4 | 5 | nav 6 | { 7 | display: flex; 8 | } 9 | nav .navbar-menu, nav .navbar-brand 10 | { 11 | display: flex; 12 | margin: 0.2em; 13 | padding: 0; 14 | padding-bottom: 0.1em; 15 | } 16 | 17 | nav .navbar-item 18 | { 19 | display: inline; 20 | text-align: center; 21 | padding: 0 0.5em; 22 | } 23 | 24 | article /* As brutstrap's section */ 25 | { 26 | border-bottom: 0.1em solid #444; 27 | margin-top: 0.25em; 28 | margin-bottom: 1em; 29 | } 30 | 31 | article + hr /* Now redundant */ 32 | { 33 | display: none; 34 | } 35 | 36 | 37 | /* Bulma styles re-implemented for compatibility */ 38 | 39 | img.is-32x32 { 40 | float: left; 41 | max-width: 512px; 42 | max-height: auto; 43 | margin: 4px; 44 | } 45 | 46 | 47 | .level { 48 | clear: both; 49 | } 50 | 51 | .title { 52 | font-size: 3ex; 53 | font-weight: bold; 54 | margin-top: 1ex; 55 | margin-bottom: 1ex; 56 | } 57 | .subtitle { 58 | font-size: 1.5ex; 59 | font-weight: bold; 60 | margin-top: 0.25ex; 61 | margin-bottom: 0.25ex; 62 | } 63 | 64 | .image.is-32x32, .is-32x32 img, img.is-32x32 { 65 | width: 32px; 66 | height: 32px; 67 | } 68 | 69 | .image.is-48x48, .is-48x48 img, img.is-48x48 { 70 | width: 48px; 71 | height: 48px; 72 | } 73 | 74 | .image.is-64x64, .is-64x64 img, img.is-64x64 { 75 | width: 64px; 76 | height: 64px; 77 | } 78 | 79 | .image.is-96x96, .is-96x96 img, img.is-96x96 { 80 | width: 96px; 81 | height: 96px; 82 | } 83 | 84 | .is-max-128 img, .is-max-256 img 85 | { 86 | display: block; 87 | clear: both; 88 | } 89 | .is-max-128 { 90 | max-height: 128px; 91 | max-width: 128px; 92 | } 93 | 94 | .is-max-256 { 95 | max-height: 256px; 96 | max-width: 256px; 97 | } 98 | 99 | img.emoji 100 | { 101 | display: inline; 102 | max-height: 1.5rem; 103 | max-width: 1.5rem; 104 | vertical-align: text-bottom; 105 | } 106 | 107 | .modal { 108 | -webkit-box-align: center; 109 | -ms-flex-align: center; 110 | align-items: center; 111 | display: none; 112 | -webkit-box-pack: center; 113 | -ms-flex-pack: center; 114 | justify-content: center; 115 | overflow: hidden; 116 | position: fixed; 117 | z-index: 40; 118 | } 119 | .modal-background { 120 | position: absolute; 121 | background-color: rgba(10,10,10,.86); 122 | } 123 | .modal, .modal-background { 124 | bottom: 0; 125 | left: 0; 126 | right: 0; 127 | top: 0; 128 | } 129 | 130 | .modal-content 131 | { 132 | z-index: 60; 133 | background-color: #CCC; 134 | color: #000; 135 | padding: 1em; 136 | border: 0.2em solid #444; 137 | max-height: 90vh; 138 | overflow: auto; 139 | } 140 | 141 | .modal.is-active { 142 | display: flex; 143 | } 144 | 145 | .card 146 | { 147 | padding: 1em; 148 | margin-top: 1em; 149 | border: 0.2em solid black; 150 | } 151 | 152 | .card-header 153 | { 154 | padding-bottom: 1em; 155 | border-bottom: 0.2em solid black; 156 | } 157 | 158 | .card-image 159 | { 160 | padding: 1em; 161 | margin 0, auto; 162 | } 163 | 164 | .button 165 | { 166 | border: 0.2em solid #444; 167 | display: inline; 168 | padding: 0.4em; 169 | } 170 | 171 | /* Fscking levels */ 172 | .level { 173 | align-items: center; 174 | justify-content: space-between; 175 | display:flex; 176 | flex-wrap: wrap; 177 | } 178 | 179 | .level.is-mobile { 180 | display: flex; 181 | } 182 | 183 | .level.is-mobile .level-left, 184 | .level.is-mobile .level-right { 185 | display: flex; 186 | } 187 | .level.is-mobile .level-item { 188 | margin-right: 0.75rem; 189 | } 190 | 191 | 192 | .level code { 193 | border-radius: 4px; 194 | } 195 | 196 | .level img { 197 | display: inline-block; 198 | vertical-align: top; 199 | } 200 | 201 | .level-item { 202 | align-items: center; 203 | display: flex; 204 | flex-basis: auto; 205 | flex-grow: 0; 206 | flex-shrink: 0; 207 | justify-content: center; 208 | } 209 | 210 | .level-item .title, 211 | .level-item .subtitle { 212 | margin-bottom: 0; 213 | } 214 | 215 | .level-left, 216 | .level-right { 217 | flex-basis: auto; 218 | flex-grow: 0; 219 | flex-shrink: 0; 220 | } 221 | 222 | 223 | .level-left { 224 | align-items: center; 225 | justify-content: flex-start; 226 | } 227 | 228 | .level-right { 229 | align-items: center; 230 | justify-content: flex-end; 231 | } 232 | 233 | .media { 234 | align-items: flex-start; 235 | display: flex; 236 | text-align: left; 237 | } 238 | 239 | #username_autocomplete div.tooltip::after { 240 | content: attr(data-tooltip); 241 | font-size: 0.6em; 242 | margin-left: 1em; 243 | margin-right: 1em; 244 | border: 1pt solid #444; 245 | background-color: #FFF; 246 | padding: 0.2em; 247 | } 248 | 249 | /* Remaining brutaldon-specific tweaks */ 250 | 251 | body 252 | { 253 | /* brutstrap's default font-size is fine for full-text web 254 | pages, but too big for this purpose. */ 255 | font-size: 1em; 256 | } 257 | 258 | input, textarea 259 | { 260 | font-family: sans-serif; 261 | 262 | } 263 | 264 | textarea 265 | { 266 | width: 100%; 267 | } 268 | 269 | input#id_spoiler_text 270 | { 271 | width: 100%; 272 | } 273 | 274 | .media-content 275 | { 276 | margin-bottom: 1em; 277 | } 278 | 279 | .reblog-icon { 280 | position: relative; 281 | top: -24px; 282 | left: 40px; 283 | } 284 | 285 | img.fav-avatar { 286 | display: inline; 287 | 288 | } 289 | 290 | #status_count 291 | { 292 | margin-left: 90%; 293 | margin-top: 1rem; 294 | background-color: #888; 295 | color: #FFF; 296 | float: right; 297 | padding: 0.5ex; 298 | border-radius: 5px; 299 | min-height: 1.5rem; 300 | min-width: 1.5rem; 301 | font-size: 0.8em; 302 | text-align: right; 303 | } 304 | 305 | #username_autocomplete 306 | { 307 | height: 0; 308 | } 309 | 310 | .attachments figure { 311 | border: 0.2ex solid #444; 312 | max-width: 256px; 313 | padding: 1em; 314 | } 315 | 316 | figure.level-item > video 317 | { 318 | max-height: 256px; 319 | max-width: 256px; 320 | } 321 | 322 | table { 323 | border-collapse: collapse; 324 | border-spacing: 0; 325 | } 326 | td, th 327 | { 328 | padding: 0.25em; 329 | border: 1px solid #444; 330 | } 331 | 332 | td.empty-cell, th.empty-cell { border: none; } 333 | 334 | @media screen and (max-width: 768px) { 335 | .box { 336 | max-width: 90%; 337 | } 338 | .media { 339 | display: block; 340 | } 341 | .media-left { 342 | float: left; 343 | } 344 | .media-content .content p { 345 | clear: inline-end; 346 | } 347 | } 348 | 349 | @media screen and (min-width: 1024px) { 350 | .navbar, 351 | .navbar-menu, 352 | .navbar-start, 353 | .navbar-end { 354 | align-items: stretch; 355 | display: flex; 356 | } 357 | .navbar-start { 358 | justify-content: flex-start; 359 | margin-right: auto; 360 | } 361 | .navbar-end { 362 | justify-content: flex-end; 363 | margin-left: auto; 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /brutaldon/static/css/brutstrap.css: -------------------------------------------------------------------------------- 1 | /* Components */ 2 | body { 3 | position: relative; 4 | background-color: #eee; 5 | color: #444; 6 | font-family: serif; 7 | margin: 0 auto; 8 | padding-bottom: 6rem; 9 | min-height: 100%; 10 | font-size: 1.4em; 11 | } 12 | 13 | header { 14 | font-family: sans-serif; 15 | text-align: center; 16 | width: 100%; 17 | overflow: hidden; 18 | border-bottom: 0.5rem dashed #444; 19 | } 20 | main { 21 | width: 75vw; 22 | max-width: 40em; 23 | margin: 0 auto; 24 | line-height: 1.6; 25 | margin-bottom: 8rem; 26 | } 27 | footer { 28 | padding: 1em 0; 29 | position: absolute; 30 | right: 0; 31 | bottom: 0; 32 | left: 0; 33 | border-top: 0.25em dashed #444; 34 | } 35 | 36 | nav ul { 37 | list-style-type: none; 38 | margin: 0.2em; 39 | padding: 0; 40 | padding-bottom: 0.1em; 41 | } 42 | nav ul li { 43 | display: inline; 44 | text-align: center; 45 | padding: 0 0.5em; 46 | } 47 | 48 | ::selection { background-color: #777; color: #eee; } 49 | 50 | h1 { 51 | font-family: sans-serif; 52 | margin: 0.1em; 53 | } 54 | 55 | section { 56 | border-bottom: 0.1em solid #444; 57 | margin-bottom: 1em; 58 | } 59 | h3 { 60 | font-family: sans-serif; 61 | margin: 0.5em; 62 | font-size: 1.4em; 63 | } 64 | 65 | .admonition { 66 | margin: 0 auto; 67 | width: 55vw; 68 | max-width: 30em; 69 | margin-top: 1em; 70 | padding: 1em; 71 | border: 0.2em solid #444; 72 | } 73 | .admonition > .h2 { 74 | margin: 1em 0 0.5em 0; 75 | } 76 | .admonition p { margin: 0.3em; } 77 | 78 | .blockQuote { 79 | padding: 0.5rem; 80 | border-left: 0.1em solid #444; 81 | } 82 | .codeBlock { 83 | padding: 0.5em; 84 | border: 0.1em solid #444; 85 | white-space: pre-wrap; 86 | overflow-x: scroll; 87 | text-overflow: clip; 88 | } 89 | ul li { margin-bottom: 0.5em;} 90 | ul li p { 91 | margin-top: 0; 92 | } 93 | 94 | a { 95 | text-decoration: none; 96 | color: inherit; 97 | display: inline-block; 98 | position: relative; 99 | border-bottom: 0.1rem dotted; 100 | line-height: 1.2; 101 | transition: border 0.3s; 102 | } 103 | a:visited { 104 | color: #777; 105 | } 106 | a:hover { 107 | outline-style: none; 108 | border-bottom: 0.1rem solid; 109 | } 110 | a:focus { 111 | outline-style: none; 112 | border-bottom: 0.1rem solid; 113 | background: #777; 114 | } 115 | 116 | 117 | .-advertisement { 118 | width: 40vw; 119 | max-width: 30rem; 120 | margin: auto; 121 | } 122 | 123 | .-colophon { 124 | margin: 0 auto; 125 | width: 50vw; 126 | max-width: 40rem; 127 | } 128 | 129 | .-motd { 130 | text-align: center; 131 | width: 100%; 132 | overflow: hidden; 133 | } 134 | 135 | .-copyright { 136 | margin: 0 auto; 137 | width: 50vw; 138 | max-width: 40rem; 139 | padding-bottom: 0.2rem; 140 | } 141 | 142 | .-disclaimer { 143 | font-size: 1rem; 144 | } 145 | 146 | .-invertColor { 147 | background-color: #444; 148 | color: #eee; 149 | } 150 | .-invertColor ::selection { 151 | background-color: #eee; 152 | color: #444; 153 | } 154 | .-invertColor a:before { 155 | background: #eee; 156 | } 157 | .-invertColor a:focus { 158 | outline: 0.1em solid #eee; 159 | background: inherit; 160 | } 161 | .-invertColor a:visited { color: #bbb; } 162 | .-invertColor .admonition { border-color: #eee;} 163 | 164 | .-monospace { font-family: monospace; } 165 | 166 | .-colorLinks a { color: #3ac; } 167 | .-colorLinks a:hover { color: #5ce; } 168 | .-colorLinks a:visited { color: #b8c; } 169 | .-colorLinks a:visited:hover { color: #dae; } 170 | 171 | .-tooSmall { font-size: 1rem; } 172 | .-small { font-size: 1.4rem; } 173 | .-medium { font-size: 2rem; } 174 | .-large { font-size: 4rem; } 175 | 176 | .-fullColumn { 177 | margin: 0 auto; 178 | width: 75vw; 179 | max-width: 40em; 180 | } 181 | .-twoThirdColumn { 182 | margin: 0 auto; 183 | width: 55vw; 184 | max-width: 30em; 185 | } 186 | .-halfColumn { 187 | margin: 0 auto; 188 | width: 40vw; 189 | max-width: 22em; 190 | } 191 | -------------------------------------------------------------------------------- /brutaldon/static/css/bulma-badge.min.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.badge{position:relative;white-space:nowrap;position:relative}.badge[data-badge]::after{position:absolute;left:100%;margin:0;background:#00d1b2;background-clip:padding-box;border-radius:1rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block}.badge:not([data-badge=""])::after{padding:.3rem .5rem;text-align:center;white-space:nowrap}.badge[data-badge=""]::after{display:inline-block;vertical-align:inherit}.badge.is-badge-white:not([data-badge])::after,.badge.is-badge-white[data-badge]::after{background:#fff;color:#0a0a0a}.badge.is-badge-black:not([data-badge])::after,.badge.is-badge-black[data-badge]::after{background:#0a0a0a;color:#fff}.badge.is-badge-light:not([data-badge])::after,.badge.is-badge-light[data-badge]::after{background:#f5f5f5;color:#363636}.badge.is-badge-dark:not([data-badge])::after,.badge.is-badge-dark[data-badge]::after{background:#363636;color:#f5f5f5}.badge.is-badge-primary:not([data-badge])::after,.badge.is-badge-primary[data-badge]::after{background:#00d1b2;color:#fff}.badge.is-badge-link:not([data-badge])::after,.badge.is-badge-link[data-badge]::after{background:#3273dc;color:#fff}.badge.is-badge-info:not([data-badge])::after,.badge.is-badge-info[data-badge]::after{background:#209cee;color:#fff}.badge.is-badge-success:not([data-badge])::after,.badge.is-badge-success[data-badge]::after{background:#23d160;color:#fff}.badge.is-badge-warning:not([data-badge])::after,.badge.is-badge-warning[data-badge]::after{background:#ffdd57;color:rgba(0,0,0,.7)}.badge.is-badge-danger:not([data-badge])::after,.badge.is-badge-danger[data-badge]::after{background:#ff3860;color:#fff}.badge.is-badge-outlined[data-badge]::after{background-color:#fff;box-shadow:none;border:.1rem solid #00d1b2;color:#00d1b2}.badge.is-badge-outlined.is-badge-white[data-badge]::after{background:#fff;box-shadow:none;border:.1rem solid #fff;color:#fff}.badge.is-badge-outlined.is-badge-black[data-badge]::after{background:#fff;box-shadow:none;border:.1rem solid #0a0a0a;color:#0a0a0a}.badge.is-badge-outlined.is-badge-light[data-badge]::after{background:#fff;box-shadow:none;border:.1rem solid #f5f5f5;color:#f5f5f5}.badge.is-badge-outlined.is-badge-dark[data-badge]::after{background:#fff;box-shadow:none;border:.1rem solid #363636;color:#363636}.badge.is-badge-outlined.is-badge-primary[data-badge]::after{background:#fff;box-shadow:none;border:.1rem solid #00d1b2;color:#00d1b2}.badge.is-badge-outlined.is-badge-link[data-badge]::after{background:#fff;box-shadow:none;border:.1rem solid #3273dc;color:#3273dc}.badge.is-badge-outlined.is-badge-info[data-badge]::after{background:#fff;box-shadow:none;border:.1rem solid #209cee;color:#209cee}.badge.is-badge-outlined.is-badge-success[data-badge]::after{background:#fff;box-shadow:none;border:.1rem solid #23d160;color:#23d160}.badge.is-badge-outlined.is-badge-warning[data-badge]::after{background:#fff;box-shadow:none;border:.1rem solid #ffdd57;color:#ffdd57}.badge.is-badge-outlined.is-badge-danger[data-badge]::after{background:#fff;box-shadow:none;border:.1rem solid #ff3860;color:#ff3860}.badge[data-badge]::after{top:calc(0px - (1rem / 2));left:calc(100% - (1rem / 2));min-height:1rem;min-width:1rem}.badge:not([data-badge=""])::after{font-size:.75rem;line-height:.5rem}.badge[data-badge=""]::after{width:1rem}.badge.is-badge-left::after{left:calc(0px - (1rem / 2))}.badge.is-badge-bottom::after{top:calc(100% - (1rem / 2))}.badge.is-badge-bottom-left::after{left:calc(0px - (1rem / 2));top:calc(100% - (1rem / 2))}.badge.is-badge-small[data-badge]::after{top:calc(0px - (.75rem / 2));left:calc(100% - (.75rem / 2));min-height:.75rem;min-width:.75rem}.badge.is-badge-small:not([data-badge=""])::after{font-size:.5625rem;line-height:.375rem}.badge.is-badge-small[data-badge=""]::after{width:.75rem}.badge.is-badge-small.is-badge-left::after{left:calc(0px - (.75rem / 2))}.badge.is-badge-small.is-badge-bottom::after{top:calc(100% - (.75rem / 2))}.badge.is-badge-small.is-badge-bottom-left::after{left:calc(0px - (.75rem / 2));top:calc(100% - (.75rem / 2))}.badge.is-badge-medium[data-badge]::after{top:calc(0px - (1.25rem / 2));left:calc(100% - (1.25rem / 2));min-height:1.25rem;min-width:1.25rem}.badge.is-badge-medium:not([data-badge=""])::after{font-size:.9375rem;line-height:.625rem}.badge.is-badge-medium[data-badge=""]::after{width:1.25rem}.badge.is-badge-medium.is-badge-left::after{left:calc(0px - (1.25rem / 2))}.badge.is-badge-medium.is-badge-bottom::after{top:calc(100% - (1.25rem / 2))}.badge.is-badge-medium.is-badge-bottom-left::after{left:calc(0px - (1.25rem / 2));top:calc(100% - (1.25rem / 2))}.badge.is-badge-large[data-badge]::after{top:calc(0px - (1.5rem / 2));left:calc(100% - (1.5rem / 2));min-height:1.5rem;min-width:1.5rem}.badge.is-badge-large:not([data-badge=""])::after{font-size:1.125rem;line-height:.75rem}.badge.is-badge-large[data-badge=""]::after{width:1.5rem}.badge.is-badge-large.is-badge-left::after{left:calc(0px - (1.5rem / 2))}.badge.is-badge-large.is-badge-bottom::after{top:calc(100% - (1.5rem / 2))}.badge.is-badge-large.is-badge-bottom-left::after{left:calc(0px - (1.5rem / 2));top:calc(100% - (1.5rem / 2))} -------------------------------------------------------------------------------- /brutaldon/static/css/minimal-dark.css: -------------------------------------------------------------------------------- 1 | body, input, textarea, select { 2 | font-family: sans-serif; 3 | background-color: #111111; 4 | color: #CCCCCC; 5 | margin: 1ex; 6 | font-size: 11pt; 7 | } 8 | 9 | input[text], textarea 10 | { 11 | margin: 0 auto; 12 | position: relative; 13 | width: 100%; 14 | max-width: 100em; 15 | } 16 | 17 | a { 18 | color: cornflowerblue; 19 | text-decoration: underline; 20 | } 21 | 22 | a:active { 23 | color: lightcoral; 24 | text-decoration: underline; 25 | } 26 | 27 | a:visited { 28 | color: orchid; 29 | text-decoration: underline; 30 | } 31 | 32 | img.is-32x32 { 33 | float: left; 34 | max-width: 32px; 35 | max-height: auto; 36 | margin: 4px; 37 | } 38 | 39 | .container { 40 | margin: 0 auto; 41 | position: relative; 42 | } 43 | 44 | @media screen and (min-width: 1024px) { 45 | .container { 46 | max-width: 960px; 47 | width: 960px; 48 | } 49 | .container.is-fluid { 50 | margin-left: 64px; 51 | margin-right: 64px; 52 | max-width: none; 53 | width: auto; 54 | } 55 | .navbar, 56 | .navbar-menu, 57 | .navbar-start, 58 | .navbar-end { 59 | align-items: stretch; 60 | display: flex; 61 | } 62 | .navbar-start { 63 | justify-content: flex-start; 64 | margin-right: auto; 65 | } 66 | .navbar-end { 67 | justify-content: flex-end; 68 | margin-left: auto; 69 | } 70 | } 71 | 72 | @media screen and (max-width: 1279px) { 73 | .container.is-widescreen { 74 | max-width: 1152px; 75 | width: auto; 76 | } 77 | } 78 | 79 | @media screen and (max-width: 1471px) { 80 | .container.is-fullhd { 81 | max-width: 1344px; 82 | width: auto; 83 | } 84 | } 85 | 86 | @media screen and (min-width: 1280px) { 87 | .container { 88 | max-width: 1152px; 89 | width: 1152px; 90 | } 91 | } 92 | 93 | @media screen and (min-width: 1472px) { 94 | .container { 95 | max-width: 1344px; 96 | width: 1344px; 97 | } 98 | } 99 | 100 | main > div.container { 101 | max-width: 100ex; 102 | } 103 | 104 | .level { 105 | clear: both; 106 | } 107 | 108 | .title { 109 | font-size: 3ex; 110 | font-weight: bold; 111 | margin-top: 1ex; 112 | margin-bottom: 1ex; 113 | } 114 | .subtitle { 115 | font-size: 1.5ex; 116 | font-weight: bold; 117 | margin-top: 0.25ex; 118 | margin-bottom: 0.25ex; 119 | } 120 | .toot { 121 | clear: both; 122 | } 123 | 124 | .image.is-32x32, .is-32x32 img, img.is-32x32 { 125 | width: 32px; 126 | height: 32px; 127 | } 128 | 129 | .image.is-48x48, .is-48x48 img, img.is-48x48 { 130 | width: 48px; 131 | height: 48px; 132 | } 133 | 134 | .image.is-64x64, .is-64x64 img, img.is-64x64 { 135 | width: 64px; 136 | height: 64px; 137 | } 138 | 139 | .image.is-96x96, .is-96x96 img, img.is-96x96 { 140 | width: 96px; 141 | height: 96px; 142 | } 143 | 144 | .is-max-128 { 145 | max-height: 128px; 146 | max-width: 128px; 147 | } 148 | 149 | .is-max-256 { 150 | max-height: 256px; 151 | max-width: 256px; 152 | } 153 | 154 | .media { 155 | padding: 1ex; 156 | margin: 4px; 157 | overflow: auto; 158 | } 159 | 160 | .media.active-context { 161 | background-color: #2C2C2C; 162 | } 163 | 164 | 165 | .field 166 | { 167 | margin-top: 1em; 168 | } 169 | 170 | label 171 | { 172 | font-weight: bold; 173 | } 174 | 175 | .control, .select 176 | { 177 | margin-top: 0.5ex; 178 | margin-bottom: 0.5ex; 179 | } 180 | 181 | .account-avatar 182 | { 183 | display: inline-block; 184 | } 185 | .reblog-icon 186 | { 187 | margin-top: 32px; 188 | display: inline-block; 189 | } 190 | 191 | .media-content 192 | { 193 | margin-top: 1ex; 194 | } 195 | 196 | .media-content > div > p 197 | { 198 | margin-bottom: 1ex; 199 | } 200 | 201 | .textarea 202 | { 203 | max-width: 100%; 204 | } 205 | 206 | .errorlist 207 | { 208 | color: #FF0000; 209 | } 210 | 211 | img.emoji 212 | { 213 | display: inline; 214 | max-height: 1.5rem; 215 | max-width: 1.5rem; 216 | vertical-align: text-bottom; 217 | } 218 | 219 | hr.is-hidden 220 | { 221 | display: none; 222 | } 223 | 224 | .box 225 | { 226 | border-radius: 3px; 227 | border: 1px solid #000; 228 | padding: 1em; 229 | margin-bottom: 1em; 230 | background-color: #1C1C1C; 231 | color: #CCCCCC; 232 | } 233 | 234 | .modal { 235 | -webkit-box-align: center; 236 | -ms-flex-align: center; 237 | align-items: center; 238 | display: none; 239 | -webkit-box-pack: center; 240 | -ms-flex-pack: center; 241 | justify-content: center; 242 | overflow: hidden; 243 | position: fixed; 244 | z-index: 40; 245 | } 246 | .modal-background { 247 | position: absolute; 248 | background-color: rgba(10,10,10,.86); 249 | } 250 | .modal, .modal-background { 251 | bottom: 0; 252 | left: 0; 253 | right: 0; 254 | top: 0; 255 | } 256 | 257 | .modal-content 258 | { 259 | height: 90vh; 260 | overflow: auto; 261 | z-index: 60; 262 | } 263 | 264 | .modal.is-active { 265 | display: flex; 266 | } 267 | 268 | .navbar-item span { 269 | padding-right: 1ex; 270 | } 271 | 272 | .card 273 | { 274 | padding: 1em; 275 | margin-top: 1em; 276 | border: 0.2em solid white; 277 | } 278 | 279 | .card-header 280 | { 281 | padding-bottom: 1em; 282 | border-bottom: 0.2em solid white; 283 | } 284 | 285 | .card-image 286 | { 287 | padding: 1em; 288 | margin 0, auto; 289 | } 290 | 291 | .button 292 | { 293 | border: 0.2em solid #CCC; 294 | display: inline; 295 | padding: 0.4em; 296 | } 297 | -------------------------------------------------------------------------------- /brutaldon/static/css/minimal-large.css: -------------------------------------------------------------------------------- 1 | body, input, textarea, select { 2 | font-family: sans-serif; 3 | background-color: #FAFAFA; 4 | color: #000; 5 | margin: 1em; 6 | font-size: larger; 7 | } 8 | 9 | input[text], textarea 10 | { 11 | margin: 0 auto; 12 | position: relative; 13 | width: 100%; 14 | max-width: 120em; 15 | } 16 | 17 | a { 18 | color: blue; 19 | text-decoration: underline; 20 | } 21 | 22 | a:active { 23 | color: red; 24 | text-decoration: underline; 25 | } 26 | 27 | a:visited { 28 | color: purple; 29 | text-decoration: underline; 30 | } 31 | 32 | img.is-32x32 { 33 | float: left; 34 | max-width: 512px; 35 | max-height: auto; 36 | margin: 4px; 37 | } 38 | 39 | .container { 40 | margin: 0 auto; 41 | position: relative; 42 | } 43 | 44 | @media screen and (min-width: 1024px) { 45 | .container { 46 | max-width: 960px; 47 | width: 960px; 48 | } 49 | .container.is-fluid { 50 | margin-left: 64px; 51 | margin-right: 64px; 52 | max-width: none; 53 | width: auto; 54 | } 55 | .navbar, 56 | .navbar-menu, 57 | .navbar-start, 58 | .navbar-end { 59 | align-items: stretch; 60 | display: flex; 61 | } 62 | .navbar-start { 63 | justify-content: flex-start; 64 | margin-right: auto; 65 | } 66 | .navbar-end { 67 | justify-content: flex-end; 68 | margin-left: auto; 69 | } 70 | } 71 | 72 | @media screen and (max-width: 1279px) { 73 | .container.is-widescreen { 74 | max-width: 1152px; 75 | width: auto; 76 | } 77 | } 78 | 79 | @media screen and (max-width: 1471px) { 80 | .container.is-fullhd { 81 | max-width: 1344px; 82 | width: auto; 83 | } 84 | } 85 | 86 | @media screen and (min-width: 1280px) { 87 | .container { 88 | max-width: 1152px; 89 | width: 1152px; 90 | } 91 | } 92 | 93 | @media screen and (min-width: 1472px) { 94 | .container { 95 | max-width: 1344px; 96 | width: 1344px; 97 | } 98 | } 99 | 100 | body > section > div.container { 101 | max-width: 100ex; 102 | } 103 | 104 | .level { 105 | clear: both; 106 | } 107 | 108 | .title { 109 | font-size: 3ex; 110 | font-weight: bold; 111 | margin-top: 1ex; 112 | margin-bottom: 1ex; 113 | } 114 | .subtitle { 115 | font-size: 1.5ex; 116 | font-weight: bold; 117 | margin-top: 0.25ex; 118 | margin-bottom: 0.25ex; 119 | } 120 | .toot { 121 | clear: both; 122 | } 123 | 124 | .image.is-32x32, .is-32x32 img, img.is-32x32 { 125 | width: 32px; 126 | height: 32px; 127 | } 128 | 129 | .image.is-48x48, .is-48x48 img, img.is-48x48 { 130 | width: 48px; 131 | height: 48px; 132 | } 133 | 134 | .image.is-64x64, .is-64x64 img, img.is-64x64 { 135 | width: 64px; 136 | height: 64px; 137 | } 138 | 139 | .image.is-96x96, .is-96x96 img, img.is-96x96 { 140 | width: 96px; 141 | height: 96px; 142 | } 143 | 144 | .is-max-128 { 145 | max-height: 128px; 146 | max-width: 128px; 147 | } 148 | 149 | .is-max-256 { 150 | max-height: 256px; 151 | max-width: 256px; 152 | } 153 | 154 | .media { 155 | padding: 1ex; 156 | margin: 4px; 157 | overflow: auto; 158 | } 159 | 160 | .media.active-context { 161 | background-color: #DDD; 162 | } 163 | 164 | 165 | .field 166 | { 167 | margin-top: 1em; 168 | } 169 | 170 | label 171 | { 172 | font-weight: bold; 173 | } 174 | 175 | .control, .select 176 | { 177 | margin-top: 0.5ex; 178 | margin-bottom: 0.5ex; 179 | } 180 | 181 | .account-avatar 182 | { 183 | display: inline-block; 184 | } 185 | .reblog-icon 186 | { 187 | margin-top: 32px; 188 | display: inline-block; 189 | } 190 | 191 | .media-content 192 | { 193 | margin-top: 1ex; 194 | } 195 | 196 | .media-content > div > p 197 | { 198 | margin-bottom: 1ex; 199 | } 200 | 201 | .textarea 202 | { 203 | max-width: 100%; 204 | } 205 | 206 | .errorlist 207 | { 208 | color: #FF0000; 209 | } 210 | 211 | img.emoji 212 | { 213 | display: inline; 214 | max-height: 1.5rem; 215 | max-width: 1.5rem; 216 | vertical-align: text-bottom; 217 | } 218 | 219 | hr.is-hidden 220 | { 221 | display: none; 222 | } 223 | 224 | .box 225 | { 226 | border-radius: 5px; 227 | border: 1px solid #000; 228 | padding: 1.5em; 229 | margin-bottom: 1.5em; 230 | background-color: white; 231 | color: black; 232 | } 233 | 234 | .modal { 235 | -webkit-box-align: center; 236 | -ms-flex-align: center; 237 | align-items: center; 238 | display: none; 239 | -webkit-box-pack: center; 240 | -ms-flex-pack: center; 241 | justify-content: center; 242 | overflow: hidden; 243 | position: fixed; 244 | z-index: 40; 245 | } 246 | .modal-background { 247 | position: absolute; 248 | background-color: rgba(10,10,10,.86); 249 | } 250 | .modal, .modal-background { 251 | bottom: 0; 252 | left: 0; 253 | right: 0; 254 | top: 0; 255 | } 256 | 257 | .modal-content 258 | { 259 | height: 90vh; 260 | overflow: auto; 261 | z-index: 60; 262 | } 263 | 264 | .modal.is-active { 265 | display: flex; 266 | } 267 | 268 | -------------------------------------------------------------------------------- /brutaldon/static/css/minimal-small.css: -------------------------------------------------------------------------------- 1 | body, input, textarea, select { 2 | font-family: sans-serif; 3 | background-color: #FAFAFA; 4 | color: #000; 5 | margin: 1ex; 6 | font-size: 11pt; 7 | } 8 | 9 | input[text], textarea 10 | { 11 | margin: 0 auto; 12 | position: relative; 13 | width: 100%; 14 | max-width: 100em; 15 | } 16 | 17 | a { 18 | color: blue; 19 | text-decoration: underline; 20 | } 21 | 22 | a:active { 23 | color: red; 24 | text-decoration: underline; 25 | } 26 | 27 | a:visited { 28 | color: purple; 29 | text-decoration: underline; 30 | } 31 | 32 | img.is-32x32 { 33 | float: left; 34 | max-width: 32px; 35 | max-height: auto; 36 | margin: 4px; 37 | } 38 | 39 | .container { 40 | margin: 0 auto; 41 | position: relative; 42 | } 43 | 44 | @media screen and (min-width: 1024px) { 45 | .container { 46 | max-width: 960px; 47 | width: 960px; 48 | } 49 | .container.is-fluid { 50 | margin-left: 64px; 51 | margin-right: 64px; 52 | max-width: none; 53 | width: auto; 54 | } 55 | .navbar, 56 | .navbar-menu, 57 | .navbar-start, 58 | .navbar-end { 59 | align-items: stretch; 60 | display: flex; 61 | } 62 | .navbar-start { 63 | justify-content: flex-start; 64 | margin-right: auto; 65 | } 66 | .navbar-end { 67 | justify-content: flex-end; 68 | margin-left: auto; 69 | } 70 | } 71 | 72 | @media screen and (max-width: 1279px) { 73 | .container.is-widescreen { 74 | max-width: 1152px; 75 | width: auto; 76 | } 77 | } 78 | 79 | @media screen and (max-width: 1471px) { 80 | .container.is-fullhd { 81 | max-width: 1344px; 82 | width: auto; 83 | } 84 | } 85 | 86 | @media screen and (min-width: 1280px) { 87 | .container { 88 | max-width: 1152px; 89 | width: 1152px; 90 | } 91 | } 92 | 93 | @media screen and (min-width: 1472px) { 94 | .container { 95 | max-width: 1344px; 96 | width: 1344px; 97 | } 98 | } 99 | 100 | main > div.container { 101 | max-width: 100ex; 102 | } 103 | 104 | .level { 105 | clear: both; 106 | } 107 | 108 | .title { 109 | font-size: 3ex; 110 | font-weight: bold; 111 | margin-top: 1ex; 112 | margin-bottom: 1ex; 113 | } 114 | .subtitle { 115 | font-size: 1.5ex; 116 | font-weight: bold; 117 | margin-top: 0.25ex; 118 | margin-bottom: 0.25ex; 119 | } 120 | .toot { 121 | clear: both; 122 | } 123 | 124 | .image.is-32x32, .is-32x32 img, img.is-32x32 { 125 | width: 32px; 126 | height: 32px; 127 | } 128 | 129 | .image.is-48x48, .is-48x48 img, img.is-48x48 { 130 | width: 48px; 131 | height: 48px; 132 | } 133 | 134 | .image.is-64x64, .is-64x64 img, img.is-64x64 { 135 | width: 64px; 136 | height: 64px; 137 | } 138 | 139 | .image.is-96x96, .is-96x96 img, img.is-96x96 { 140 | width: 96px; 141 | height: 96px; 142 | } 143 | 144 | .is-max-128 { 145 | max-height: 128px; 146 | max-width: 128px; 147 | } 148 | 149 | .is-max-256 { 150 | max-height: 256px; 151 | max-width: 256px; 152 | } 153 | 154 | .media { 155 | padding: 1ex; 156 | margin: 4px; 157 | overflow: auto; 158 | } 159 | 160 | .media.active-context { 161 | background-color: #DDD; 162 | } 163 | 164 | 165 | .field 166 | { 167 | margin-top: 1em; 168 | } 169 | 170 | label 171 | { 172 | font-weight: bold; 173 | } 174 | 175 | .control, .select 176 | { 177 | margin-top: 0.5ex; 178 | margin-bottom: 0.5ex; 179 | } 180 | 181 | .account-avatar 182 | { 183 | display: inline-block; 184 | } 185 | .reblog-icon 186 | { 187 | margin-top: 32px; 188 | display: inline-block; 189 | } 190 | 191 | .media-content 192 | { 193 | margin-top: 1ex; 194 | } 195 | 196 | .media-content > div > p 197 | { 198 | margin-bottom: 1ex; 199 | } 200 | 201 | .textarea 202 | { 203 | max-width: 100%; 204 | } 205 | 206 | .errorlist 207 | { 208 | color: #FF0000; 209 | } 210 | 211 | img.emoji 212 | { 213 | display: inline; 214 | max-height: 1.5rem; 215 | max-width: 1.5rem; 216 | vertical-align: text-bottom; 217 | } 218 | 219 | hr.is-hidden 220 | { 221 | display: none; 222 | } 223 | 224 | .box 225 | { 226 | border-radius: 3px; 227 | border: 1px solid #000; 228 | padding: 1em; 229 | margin-bottom: 1em; 230 | background-color: white; 231 | color: black; 232 | } 233 | 234 | .modal { 235 | -webkit-box-align: center; 236 | -ms-flex-align: center; 237 | align-items: center; 238 | display: none; 239 | -webkit-box-pack: center; 240 | -ms-flex-pack: center; 241 | justify-content: center; 242 | overflow: hidden; 243 | position: fixed; 244 | z-index: 40; 245 | } 246 | .modal-background { 247 | position: absolute; 248 | background-color: rgba(10,10,10,.86); 249 | } 250 | .modal, .modal-background { 251 | bottom: 0; 252 | left: 0; 253 | right: 0; 254 | top: 0; 255 | } 256 | 257 | .modal-content 258 | { 259 | height: 90vh; 260 | overflow: auto; 261 | z-index: 60; 262 | } 263 | 264 | .modal.is-active { 265 | display: flex; 266 | } 267 | 268 | .navbar-item span { 269 | padding-right: 1ex; 270 | } 271 | 272 | .card 273 | { 274 | padding: 1em; 275 | margin-top: 1em; 276 | border: 0.2em solid black; 277 | } 278 | 279 | .card-header 280 | { 281 | padding-bottom: 1em; 282 | border-bottom: 0.2em solid black; 283 | } 284 | 285 | .card-image 286 | { 287 | padding: 1em; 288 | margin 0, auto; 289 | } 290 | 291 | .button 292 | { 293 | border: 0.2em solid #444; 294 | display: inline; 295 | padding: 0.4em; 296 | } 297 | -------------------------------------------------------------------------------- /brutaldon/static/css/vt240don-amber.css: -------------------------------------------------------------------------------- 1 | html, a, div, div.notification, body, body div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, figure, footer, header, menu, nav, section, time, mark, audio, video, details, summary, h1.title, h2.subtitle { 2 | font-family: "DEC Terminal Modern", Terminus, Inconsolata, Consolas, "Droid Sans Mono", "DejaVu Sans Mono", "Monaco", monospace; 3 | color: #ff7700; 4 | background-color: #000000; 5 | font-size: 1.2rem; 6 | } 7 | 8 | 9 | tr, td, ul, ol { 10 | border-color: #ff7700; 11 | outline: 1px solid #ff7700; 12 | padding-top: 5px; 13 | padding-bottom: 5px; 14 | margin-top: 5px; 15 | margin-bottom: 10px; 16 | } 17 | 18 | a, a.button, a.button.is-primary, input.button.is-primary, input, .input, .textarea, .footer, .label, select, textarea { 19 | color: #ff8800; 20 | border-color: #ff8800; 21 | background-color: #000000; 22 | font-weight: bolder; 23 | text-decoration-line: none; 24 | } 25 | 26 | a:hover, a.button:hover, a.button.is-primary:hover, input.button.is-primary:hover { 27 | color: #000000; 28 | background-color: #ff8800; 29 | } 30 | 31 | .media-content a:not(.mention):not(.tag) 32 | { 33 | text-decoration-line: underline; 34 | text-decoration-style: solid; 35 | text-decoration:color: #ff7700; 36 | } 37 | 38 | .control input[type=text], .control textarea 39 | { 40 | width: 100%; 41 | max-width: 80em; 42 | border: 1px solid #ff7700; 43 | margin-bottom: 1ex; 44 | } 45 | 46 | .control input, .control select 47 | { 48 | border: 1px solid #ff7700; 49 | background-color: #000000; 50 | margin-bottom: 1ex; 51 | } 52 | 53 | img { 54 | filter: grayscale(100%); 55 | -webkit-filter: grayscale(100%); 56 | } 57 | 58 | 59 | div.card-header-title, div.card-header-icon { 60 | color: #ff7700; 61 | } 62 | 63 | .container { 64 | margin: 0 auto; 65 | max-width: 80ex; 66 | } 67 | 68 | .box { 69 | padding: 1em; 70 | border-color: #ff7700; 71 | outline: 1px solid #ff7700; 72 | background-color: #000; 73 | margin-bottom: 1em; 74 | } 75 | 76 | hr.is-hidden { 77 | display: none; 78 | } 79 | 80 | .image.is-32x32, .is-32x32 img, img.is-32x32 { 81 | width: 32px; 82 | height: 32px; 83 | } 84 | 85 | .image.is-48x48, .is-48x48 img, img.is-48x48 { 86 | width: 48px; 87 | height: 48px; 88 | } 89 | 90 | .image.is-64x64, .is-64x64 img, img.is-64x64 { 91 | width: 64px; 92 | height: 64px; 93 | } 94 | 95 | .image.is-96x96, .is-96x96 img, img.is-96x96 { 96 | width: 96px; 97 | height: 96px; 98 | } 99 | 100 | .is-max-128 { 101 | max-height: 128px; 102 | max-width: 128px; 103 | } 104 | 105 | .is-max-256 { 106 | max-height: 256px; 107 | max-width: 256px; 108 | } 109 | 110 | 111 | .media { 112 | background-color: black; 113 | border-radius: 5px; 114 | /*-webkit-box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1); 115 | box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);*/ 116 | color: #4a4a00; 117 | padding: 1.25rem; 118 | margin-bottom: 0.75rem; 119 | margin-top: 0.75rem; 120 | } 121 | 122 | .is-max-128 { 123 | max-height: 128px; 124 | max-width: 128px; 125 | } 126 | 127 | .is-max-256 { 128 | max-height: 256px; 129 | max-width: 256px; 130 | } 131 | 132 | figure.media-left p.image a img 133 | { 134 | border-radius: 5px; 135 | } 136 | 137 | .active-context { 138 | background-color: #000000; 139 | border-color: #ffcc00; 140 | outline: 1px solid #ffcc00; 141 | } 142 | 143 | .account-avatar 144 | { 145 | display: inline-block; 146 | } 147 | .reblog-icon 148 | { 149 | margin-top: 32px; 150 | display: inline-block; 151 | } 152 | 153 | img.fav-avatar { 154 | display: inline; 155 | 156 | } 157 | 158 | .level { 159 | display: block; 160 | margin-bottom: 1ex; 161 | margin-top: 1ex; 162 | } 163 | 164 | img.emoji 165 | { 166 | display: inline; 167 | max-height: 1.5em; 168 | max-width: 1.5em; 169 | vertical-align: text-bottom; 170 | } 171 | 172 | .modal { 173 | -webkit-box-align: center; 174 | -ms-flex-align: center; 175 | align-items: center; 176 | display: none; 177 | -webkit-box-pack: center; 178 | -ms-flex-pack: center; 179 | justify-content: center; 180 | overflow: hidden; 181 | position: fixed; 182 | z-index: 40; 183 | } 184 | .modal-background { 185 | position: absolute; 186 | background-color: rgba(10,10,10,.86); 187 | } 188 | .modal, .modal-background { 189 | bottom: 0; 190 | left: 0; 191 | right: 0; 192 | top: 0; 193 | } 194 | 195 | .modal-content 196 | { 197 | z-index: 60; 198 | max-height: 90vh; 199 | overflow: auto; 200 | } 201 | 202 | .modal.is-active { 203 | display: flex; 204 | } 205 | 206 | .navbar-item { 207 | margin-right: 2em; 208 | } 209 | 210 | @media screen and (max-width: 768px) { 211 | .media { 212 | display: block; 213 | } 214 | } 215 | 216 | @media screen and (min-width: 1024px) { 217 | .navbar, 218 | .navbar-menu, 219 | .navbar-start, 220 | .navbar-end { 221 | align-items: stretch; 222 | display: flex; 223 | } 224 | .navbar-start { 225 | justify-content: flex-start; 226 | margin-right: auto; 227 | } 228 | .navbar-end { 229 | justify-content: flex-end; 230 | margin-left: auto; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /brutaldon/static/css/vt240don-green.css: -------------------------------------------------------------------------------- 1 | html, body, a, div, div.notification, body, body div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, figure, footer, header, menu, nav, section, time, mark, audio, video, details, summary, h1.title, h2.subtitle { 2 | font-family: "DEC Terminal Modern", Terminus, Inconsolata, Consolas, "Droid Sans Mono", "DejaVu Sans Mono", "Monaco", monospace; 3 | color: #00ff77; 4 | background-color: #000; 5 | font-size: 1.2rem; 6 | } 7 | 8 | tr, td, ul, ol { 9 | border-color: #00ff77; 10 | outline: 1px solid #00ff77; 11 | padding-top: 5px; 12 | padding-bottom: 5px; 13 | margin-top: 5px; 14 | margin-bottom: 10px; 15 | background-color: #000000; 16 | } 17 | 18 | a, a.button, a.button.is-primary, input.button.is-primary, input, .input, .textarea, .footer, .label, select, textarea { 19 | color: #00ff88; 20 | border-color: #00ff88; 21 | background-color: #000000; 22 | font-weight: bolder; 23 | text-decoration-line: none; 24 | font-size: 1.2rem; 25 | } 26 | 27 | 28 | a:hover, a.button:hover, a.button.is-primary:hover, input.button.is-primary:hover { 29 | color: #000000; 30 | background-color: #00ff88; 31 | } 32 | 33 | .media-content a:not(.mention):not(.tag) 34 | { 35 | text-decoration-line: underline; 36 | text-decoration-style: solid; 37 | text-decoration:color: #00ff77; 38 | } 39 | 40 | .control input[type=text], .control textarea 41 | { 42 | width: 100%; 43 | max-width: 80ex; 44 | border: 1px solid #00ff77; 45 | background-color: #000000; 46 | margin-bottom: 1ex; 47 | } 48 | 49 | .control input, .control select 50 | { 51 | border: 1px solid #00ff77; 52 | background-color: #000000; 53 | margin-bottom: 1ex; 54 | } 55 | 56 | img { 57 | filter: grayscale(100%); 58 | -webkit-filter: grayscale(100%); 59 | } 60 | 61 | 62 | div.card-header-title, div.card-header-icon { 63 | color: #00ff77; 64 | } 65 | 66 | .container { 67 | margin: 0 auto; 68 | max-width: 80ex; 69 | color: #00ff88; 70 | background-color: #000000; 71 | } 72 | 73 | .box { 74 | padding: 1em; 75 | border-color: #00ff77; 76 | outline: 1px solid #00ff77; 77 | background-color: #000; 78 | margin-bottom: 1em; 79 | } 80 | 81 | hr.is-hidden { 82 | display: none; 83 | } 84 | 85 | .image.is-32x32, .is-32x32 img, img.is-32x32 { 86 | width: 32px; 87 | height: 32px; 88 | } 89 | 90 | .image.is-48x48, .is-48x48 img, img.is-48x48 { 91 | width: 48px; 92 | height: 48px; 93 | } 94 | 95 | .image.is-64x64, .is-64x64 img, img.is-64x64 { 96 | width: 64px; 97 | height: 64px; 98 | } 99 | 100 | .image.is-96x96, .is-96x96 img, img.is-96x96 { 101 | width: 96px; 102 | height: 96px; 103 | } 104 | 105 | .is-max-128 { 106 | max-height: 128px; 107 | max-width: 128px; 108 | } 109 | 110 | .is-max-256 { 111 | max-height: 256px; 112 | max-width: 256px; 113 | } 114 | 115 | 116 | .media { 117 | background-color: black; 118 | border-radius: 5px; 119 | /*-webkit-box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1); 120 | box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);*/ 121 | color: #4a4a00; 122 | padding: 1.25rem; 123 | margin-bottom: 0.75rem; 124 | margin-top: 0.75rem; 125 | } 126 | 127 | .is-max-128 { 128 | max-height: 128px; 129 | max-width: 128px; 130 | } 131 | 132 | .is-max-256 { 133 | max-height: 256px; 134 | max-width: 256px; 135 | } 136 | 137 | figure.media-left p.image a img 138 | { 139 | border-radius: 5px; 140 | } 141 | 142 | .active-context { 143 | background-color: #000000; 144 | border-color: #00ff88; 145 | outline: 1px solid #00ff88; 146 | } 147 | 148 | .account-avatar 149 | { 150 | display: inline-block; 151 | } 152 | .reblog-icon 153 | { 154 | margin-top: 32px; 155 | display: inline-block; 156 | } 157 | 158 | img.fav-avatar { 159 | display: inline; 160 | 161 | } 162 | 163 | .level { 164 | display: block; 165 | margin-bottom: 1ex; 166 | margin-top: 1ex; 167 | } 168 | 169 | img.emoji 170 | { 171 | display: inline; 172 | max-height: 1.5em; 173 | max-width: 1.5em; 174 | vertical-align: text-bottom; 175 | } 176 | 177 | .modal { 178 | -webkit-box-align: center; 179 | -ms-flex-align: center; 180 | align-items: center; 181 | display: none; 182 | -webkit-box-pack: center; 183 | -ms-flex-pack: center; 184 | justify-content: center; 185 | overflow: hidden; 186 | position: fixed; 187 | z-index: 40; 188 | } 189 | .modal-background { 190 | position: absolute; 191 | background-color: rgba(10,10,10,.86); 192 | } 193 | .modal, .modal-background { 194 | bottom: 0; 195 | left: 0; 196 | right: 0; 197 | top: 0; 198 | } 199 | 200 | .modal-content 201 | { 202 | z-index: 60; 203 | max-height: 90vh; 204 | overflow: auto; 205 | } 206 | 207 | .modal.is-active { 208 | display: flex; 209 | } 210 | 211 | .navbar-item { 212 | margin-right: 2em; 213 | } 214 | 215 | @media screen and (max-width: 768px) { 216 | .media { 217 | display: block; 218 | } 219 | } 220 | 221 | @media screen and (min-width: 1024px) { 222 | .navbar, 223 | .navbar-menu, 224 | .navbar-start, 225 | .navbar-end { 226 | align-items: stretch; 227 | display: flex; 228 | } 229 | .navbar-start { 230 | justify-content: flex-start; 231 | margin-right: auto; 232 | } 233 | .navbar-end { 234 | justify-content: flex-end; 235 | margin-left: auto; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /brutaldon/static/fonts/forkawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/fonts/forkawesome-webfont.eot -------------------------------------------------------------------------------- /brutaldon/static/fonts/forkawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/fonts/forkawesome-webfont.ttf -------------------------------------------------------------------------------- /brutaldon/static/fonts/forkawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/fonts/forkawesome-webfont.woff -------------------------------------------------------------------------------- /brutaldon/static/fonts/forkawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/fonts/forkawesome-webfont.woff2 -------------------------------------------------------------------------------- /brutaldon/static/images/brutaldon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/brutaldon-120.png -------------------------------------------------------------------------------- /brutaldon/static/images/brutaldon-144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/brutaldon-144.png -------------------------------------------------------------------------------- /brutaldon/static/images/brutaldon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/brutaldon-152.png -------------------------------------------------------------------------------- /brutaldon/static/images/brutaldon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/brutaldon-180.png -------------------------------------------------------------------------------- /brutaldon/static/images/brutaldon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/brutaldon-192.png -------------------------------------------------------------------------------- /brutaldon/static/images/brutaldon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/brutaldon-48.png -------------------------------------------------------------------------------- /brutaldon/static/images/brutaldon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/brutaldon-512.png -------------------------------------------------------------------------------- /brutaldon/static/images/brutaldon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/brutaldon-72.png -------------------------------------------------------------------------------- /brutaldon/static/images/brutaldon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/brutaldon-76.png -------------------------------------------------------------------------------- /brutaldon/static/images/brutaldon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/brutaldon-96.png -------------------------------------------------------------------------------- /brutaldon/static/images/brutaldon-splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/brutaldon-splash.png -------------------------------------------------------------------------------- /brutaldon/static/images/brutaldon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/brutaldon.png -------------------------------------------------------------------------------- /brutaldon/static/images/lynx.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/lynx.gif -------------------------------------------------------------------------------- /brutaldon/static/images/now9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/now9.gif -------------------------------------------------------------------------------- /brutaldon/static/images/sensitive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/static/images/sensitive.png -------------------------------------------------------------------------------- /brutaldon/static/js/brutaldon-enhancements.js: -------------------------------------------------------------------------------- 1 | String.prototype.trunc = 2 | function(n){ 3 | return this.substr(0,n-1)+(this.length>n?'…':''); 4 | }; 5 | 6 | function setTitle(user, page) 7 | { 8 | document.title = `Brutaldon (${user}) – ${page}`; 9 | } 10 | 11 | function afterPage(user, page) 12 | { 13 | setTitle(user,page); 14 | var menu = document.querySelector('#navMenu'); 15 | menu.classList.remove('is-active'); 16 | var burger = document.querySelector('.navbar-burger'); 17 | burger.classList.remove('is-active'); 18 | $('#page-load-indicator').hide(); 19 | } 20 | 21 | function menuPrepare() { 22 | // Remove is-active from navbar menu 23 | var menu = document.querySelector('#navMenu'); 24 | menu.classList.remove('is-active'); 25 | 26 | // Pin the navbar to the top 27 | document.querySelector('body').classList.toggle("has-navbar-fixed-top"); 28 | document.querySelector('nav.navbar').classList.toggle("is-fixed-top"); 29 | 30 | // Add the burger 31 | var brand = document.querySelector('.navbar-brand'); 32 | var burger = document.createElement('a'); 33 | burger.classList.toggle('navbar-burger'); 34 | burger.setAttribute("aria-label", "menu"); 35 | burger.setAttribute("aria-expanded", "false"); 36 | burger.setAttribute("data-target", "navMenu"); 37 | for (var index = 0; index < 3; index++) 38 | { 39 | var span = document.createElement('span'); 40 | span.setAttribute('aria-hidden', "true"); 41 | burger.appendChild(span); 42 | } 43 | brand.appendChild(burger); 44 | 45 | 46 | 47 | // Get all "navbar-burger" elements 48 | var $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); 49 | 50 | // Check if there are any navbar burgers 51 | if ($navbarBurgers.length > 0) { 52 | 53 | // Add a click event on each of them 54 | $navbarBurgers.forEach(function ($el) { 55 | $el.addEventListener('click', function () { 56 | 57 | // Get the target from the "data-target" attribute 58 | var target = $el.dataset.target; 59 | var $target = document.getElementById(target); 60 | 61 | // Toggle the class on both the "navbar-burger" and the "navbar-menu" 62 | $el.classList.toggle('is-active'); 63 | $target.classList.toggle('is-active'); 64 | 65 | }); 66 | }); 67 | } 68 | 69 | } 70 | 71 | function expandCWButtonPrepare() 72 | { 73 | var theButton = document.querySelector('#expandCWs'); 74 | if (!theButton) { 75 | theButton = document.createElement('button'); 76 | theButton.id = "expandCWs"; 77 | theButton.textContent = "Expand CWs"; 78 | theButton.classList.toggle('button'); 79 | var title = document.querySelector('#title'); 80 | if (title) 81 | { 82 | title.insertAdjacentElement('afterend', theButton); 83 | var details = document.querySelectorAll('details'); 84 | var openState = false; 85 | 86 | if (details != null) { 87 | theButton.addEventListener('click', function() { 88 | openState = details.item(0).hasAttribute('open'); 89 | details.forEach(function ($el) { 90 | if (openState) 91 | { 92 | $el.removeAttribute('open'); 93 | } else 94 | { 95 | $el.setAttribute('open', ''); 96 | } 97 | }); 98 | openState = !openState; 99 | if (openState) { theButton.textContent = 'Collapse CWs'; } 100 | else { theButton.textContent = "Expand CWs"; }; 101 | theButton.classList.toggle('is-active'); 102 | }); 103 | } 104 | } 105 | } 106 | } 107 | 108 | function fileButtonUpdaters() 109 | { 110 | var file1 = document.getElementById("id_media_file_1"); 111 | file1.onchange = function(){ 112 | if (file1.files.length > 0) 113 | { 114 | document.getElementById('media_filename_1').innerHTML = file1.files[0].name.trunc(5); 115 | } 116 | }; 117 | var file2 = document.getElementById("id_media_file_2"); 118 | file2.onchange = function(){ 119 | if (file2.files.length > 0) 120 | { 121 | document.getElementById('media_filename_2').innerHTML = file2.files[0].name.trunc(5); 122 | } 123 | }; 124 | var file3 = document.getElementById("id_media_file_3"); 125 | file3.onchange = function(){ 126 | if (file3.files.length > 0) 127 | { 128 | document.getElementById('media_filename_3').innerHTML = file3.files[0].name.trunc(5); 129 | } 130 | }; 131 | var file4 = document.getElementById("id_media_file_4"); 132 | file4.onchange = function(){ 133 | if (file4.files.length > 0) 134 | { 135 | document.getElementById('media_filename_4').innerHTML = file4.files[0].name.trunc(5); 136 | } 137 | }; 138 | 139 | } 140 | 141 | function characterCountSetup() 142 | { 143 | if ($("#id_status").length) { 144 | $("#status_count").text(characterCount()); 145 | $("#id_status").keyup(function(){ 146 | $("#status_count").text(characterCount()); 147 | }); 148 | $("#id_spoiler_text").keyup(function(){ 149 | $("#status_count").text(characterCount()); 150 | }); 151 | } 152 | } 153 | 154 | function characterCount() 155 | { 156 | return $("#id_status").val().length + $("#id_spoiler_text").val().length; 157 | } 158 | 159 | -------------------------------------------------------------------------------- /brutaldon/static/js/loading-attribute-polyfill.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Loading attribute polyfill - https://github.com/mfranzke/loading-attribute-polyfill 3 | * @license Copyright(c) 2019 by Maximilian Franzke 4 | * Credits for the initial kickstarter / script to @Sora2455, and supported by @diogoterremoto, @dracos and @Flimm - many thanks for that ! 5 | */ 6 | !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=''+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"); -------------------------------------------------------------------------------- /brutaldon/static/js/mousetrap.min.js: -------------------------------------------------------------------------------- 1 | /* mousetrap v1.6.2 craig.is/killing/mice */ 2 | (function(p,t,h){function u(a,b,d){a.addEventListener?a.addEventListener(b,d,!1):a.attachEvent("on"+b,d)}function y(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return m[a.which]?m[a.which]:q[a.which]?q[a.which]:String.fromCharCode(a.which).toLowerCase()}function E(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function v(a){return"shift"==a||"ctrl"==a||"alt"==a|| 3 | "meta"==a}function z(a,b){var d,e=[];var c=a;"+"===c?c=["+"]:(c=c.replace(/\+{2}/g,"+plus"),c=c.split("+"));for(d=0;dh||m.hasOwnProperty(h)&&(n[m[h]]=h)}d=n[c]?"keydown":"keypress"}"keypress"==d&&e.length&&(d="keydown");return{key:k,modifiers:e,action:d}}function C(a,b){return null===a||a===t?!1:a===b?!0:C(a.parentNode,b)}function e(a){function b(a){a= 4 | a||{};var b=!1,l;for(l in n)a[l]?b=!0:n[l]=0;b||(w=!1)}function d(a,b,r,g,F,e){var l,D=[],h=r.type;if(!f._callbacks[a])return[];"keyup"==h&&v(a)&&(b=[a]);for(l=0;l":".","?":"/","|":"\\"},A={option:"alt",command:"meta","return":"enter", 9 | escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},n;for(h=1;20>h;++h)m[111+h]="f"+h;for(h=0;9>=h;++h)m[h+96]=h.toString();e.prototype.bind=function(a,b,d){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,d);return this};e.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};e.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};e.prototype.reset=function(){this._callbacks={}; 10 | this._directMap={};return this};e.prototype.stopCallback=function(a,b){return-1<(" "+b.className+" ").indexOf(" mousetrap ")||C(b,this.target)?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};e.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};e.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(m[b]=a[b]);n=null};e.init=function(){var a=e(t),b;for(b in a)"_"!==b.charAt(0)&&(e[b]=function(b){return function(){return a[b].apply(a, 11 | arguments)}}(b))};e.init();p.Mousetrap=e;"undefined"!==typeof module&&module.exports&&(module.exports=e);"function"===typeof define&&define.amd&&define(function(){return e})}})("undefined"!==typeof window?window:null,"undefined"!==typeof window?document:null); 12 | -------------------------------------------------------------------------------- /brutaldon/static/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | {"dir" : "ltr", 2 | "lang" : "en", 3 | "name" : "brutaldon", 4 | "scope" : "/", 5 | "display" : "minimal-ui", 6 | "start_url" : "/", 7 | "short_name" : "brutaldon", 8 | "theme_color" : "#CCCCCC", 9 | "description" : "", 10 | "orientation" : "any", 11 | "background_color" : "#CCCCCC", 12 | "icons" : [ 13 | { 14 | "src": "https://brutaldon.online/static/images/brutaldon.png", 15 | "sizes": "320x320" 16 | }, 17 | { 18 | "src": "https://brutaldon.online/static/images/brutaldon-48.png", 19 | "sizes": "48x48" 20 | }, 21 | { 22 | "src": "https://brutaldon.online/static/images/brutaldon-72.png", 23 | "sizes": "72x72" 24 | }, 25 | { 26 | "src": "https://brutaldon.online/static/images/brutaldon-76.png", 27 | "sizes": "76x76" 28 | }, 29 | { 30 | "src": "https://brutaldon.online/static/images/brutaldon-96.png", 31 | "sizes": "96x96" 32 | }, 33 | { 34 | "src": "https://brutaldon.online/static/images/brutaldon-120.png", 35 | "sizes": "120x120" 36 | }, 37 | { 38 | "src": "https://brutaldon.online/static/images/brutaldon-144.png", 39 | "sizes": "144x144" 40 | }, 41 | { 42 | "src": "https://brutaldon.online/static/images/brutaldon-152.png", 43 | "sizes": "152x152" 44 | }, 45 | { 46 | "src": "https://brutaldon.online/static/images/brutaldon-192.png", 47 | "sizes": "192x192" 48 | }, 49 | { 50 | "src": "https://brutaldon.online/static/images/brutaldon-180.png", 51 | "sizes": "180x180" 52 | }, 53 | { 54 | "src": "https://brutaldon.online/static/images/brutaldon-512.png", 55 | "sizes": "512x512" 56 | } 57 | 58 | ]} 59 | -------------------------------------------------------------------------------- /brutaldon/static/offline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | brutaldon 8 | 9 | 10 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
 
23 | 24 | 35 | 36 |
37 |
38 | 39 | 40 |
41 | 43 | 44 |

Brutaldon

45 |

a brutalist web interface for Mastodon

46 |
47 | 48 |

49 | Either you are offline currently, or brutaldon is down. Please try again later. 50 |

51 | 52 | 53 |
54 |
55 |
56 | 57 | 75 | 76 | 77 | 78 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /brutaldon/templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block content %} 5 | 6 |
7 | 9 | 10 |

Brutaldon

11 |

a brutalist web interface for Mastodon

12 |
13 | 14 |

15 | Brutaldon is a client for Mastodon. You can use it to log in to any Mastodon instance from any browser, including text browsers such as lynx. 16 |

17 |

18 | You do not need a separate brutaldon account to use it. Brutaldon will authenticate you to your instance. 19 |

20 |
21 | 22 |
23 |

24 | 25 | Log in 26 | 27 |

28 | {% if request.session.instance %} 29 | {% if request.session.username or request.session.access_token %} 30 | 31 |

32 | 33 | Or continue to your home timeline. 34 | 35 |

36 | {% endif %} 37 | {% endif %} 38 | 39 |
40 | 41 |
42 | This is version {{ version }} of brutaldon. 43 |
44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /brutaldon/templates/accounts/account_partial.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {{ account.user.acct }} 6 |

7 |
8 |
9 | {{ account.user.display_name }} ({{ account.user.username }}) 10 |
11 |
12 |
13 | {% csrf_token %} 14 | 15 | 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /brutaldon/templates/accounts/list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | 4 | {% block content %} 5 |
6 |

Signed-in accounts

7 | 8 | {% if not accounts %} 9 |

No accounts.

10 | {% endif %} 11 | 12 | 13 | {% for account in accounts %} 14 | {% include "accounts/account_partial.html" %} 15 | {% endfor %} 16 | 17 |

18 | Or log in to another account to 19 | add it to the list. 20 |

21 | 22 |

23 | Or log out of all 24 | accounts. 25 |

26 | 27 |
28 | 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /brutaldon/templates/comma.html: -------------------------------------------------------------------------------- 1 | {% if not forloop.first %} 2 | {% if forloop.last %}, and 3 | {% else %}, 4 | {% endif %} 5 | {% endif %} 6 | -------------------------------------------------------------------------------- /brutaldon/templates/error.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | -------------------------------------------------------------------------------- /brutaldon/templates/filters/create.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | 4 | {% block content %} 5 | 6 |
7 |

Create Filter

8 | 9 |
10 | {% csrf_token %} 11 | 12 |
13 | {{ form.non_field_errors }} 14 |
15 | 16 |
17 | 18 |
19 | {% render_field form.phrase class+="input" %} 20 |
21 |
22 | 23 |
24 |
25 | 29 |
30 |
31 | 35 |
36 |
37 | 41 |
42 |
43 | 47 |
48 |
49 | 50 |
51 | 55 |
56 | 57 |
58 | 59 |
60 |
61 | {% render_field form.expires_in class+="select" %} 62 | 63 | 64 | 65 |
66 |
67 |
68 | 69 |
70 | 72 |
73 | 74 |
75 |
76 | 77 | 78 | {% endblock %} 79 | -------------------------------------------------------------------------------- /brutaldon/templates/filters/delete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | 4 | {% block content %} 5 |

Delete that filter?

6 | 7 |
8 |

Phrase: {{ filter.phrase }}

9 |

Context: {{ filter.context|join:", " }}

10 |

Whole word? {{ filter.whole_word }}

11 |
12 | 13 |
14 |
15 | {% csrf_token %} 16 |
17 |
18 |
19 | 20 |
21 |
22 |
23 |
24 | 26 |
27 |
28 |
29 |
30 |
31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /brutaldon/templates/filters/edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | 4 | {% block content %} 5 | 6 |
7 |

Edit Filter

8 | 9 |
11 | {% csrf_token %} 12 | 13 |
14 | {{ form.non_field_errors }} 15 |
16 | 17 |
18 | 19 |
20 | {% render_field form.phrase class+="input" %} 21 |
22 |
23 | 24 |
25 |
26 | 30 |
31 |
32 | 36 |
37 |
38 | 42 |
43 |
44 | 48 |
49 |
50 | 51 |
52 | 56 |
57 | 58 |
59 | 60 |
61 |
62 | {% render_field form.expires_in class+="select" %} 63 | 64 | 65 | 66 |
67 |
68 |
69 | 70 |
71 | 73 |
74 | 75 |
76 |
77 | 78 | 79 | {% endblock %} 80 | -------------------------------------------------------------------------------- /brutaldon/templates/filters/list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | 4 | {% block content %} 5 |
6 |

Filters

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for filter in filters %} 19 | 20 | 21 | 26 | 32 | 42 | 47 | 48 | {% endfor %} 49 | 50 |
PhraseFilter contexts
{{ filter.phrase }} 22 | {% for context in filter.context %} 23 | {{ context }} 24 | {% endfor %} 25 | 27 | 28 | 29 | Edit filter 30 | 31 | 33 | 39 | 40 | Delete filter 41 | 43 | 46 |
51 | 52 |

53 | 54 | Create filter 55 | 56 |

57 | 58 | {% endblock %} 59 | -------------------------------------------------------------------------------- /brutaldon/templates/intercooler/block.html: -------------------------------------------------------------------------------- 1 | {% if not relationship.blocking %} 2 | 6 | 7 | 8 | {% else %} 9 | 13 | 14 | 15 | {% endif %} 16 | -------------------------------------------------------------------------------- /brutaldon/templates/intercooler/boost.html: -------------------------------------------------------------------------------- 1 | {% if toot.visibility != 'private' and toot.visibility != 'direct' %} 2 | {% if toot.reblogged %} 3 | 4 | Boosted 5 | {% else %} 6 | 7 | Boost 8 | {% endif %} 9 | 10 | 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /brutaldon/templates/intercooler/fav.html: -------------------------------------------------------------------------------- 1 | {% if toot.favourited %} 2 | 3 | Favorited 4 | {% else %} 5 | 6 | Favorite 7 | {% endif %} 8 | 9 | 10 | -------------------------------------------------------------------------------- /brutaldon/templates/intercooler/follow.html: -------------------------------------------------------------------------------- 1 | {% if relationship.requested %} 2 | 6 | 7 | 8 | {% elif not relationship.following %} 9 | 13 | 14 | 15 | {% else %} 16 | 20 | 21 | 22 | {% endif %} 23 | -------------------------------------------------------------------------------- /brutaldon/templates/intercooler/mute.html: -------------------------------------------------------------------------------- 1 | {% if not relationship.muting %} 2 | 6 | 7 | 8 | {% else %} 9 | 13 | 14 | 15 | {% endif %} 16 | -------------------------------------------------------------------------------- /brutaldon/templates/intercooler/notes.html: -------------------------------------------------------------------------------- 1 | 2 | {% if not preferences.theme.is_brutalist %} 3 | 8 | Notifications 9 | 10 | {% else %} 11 | Notifications ({{ notifications }}) 12 | {% endif %} 13 | 14 | -------------------------------------------------------------------------------- /brutaldon/templates/intercooler/post.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 20 | -------------------------------------------------------------------------------- /brutaldon/templates/intercooler/search.html: -------------------------------------------------------------------------------- 1 | 2 | 31 | 32 | 33 | 38 | -------------------------------------------------------------------------------- /brutaldon/templates/intercooler/users.html: -------------------------------------------------------------------------------- 1 |
3 | -------------------------------------------------------------------------------- /brutaldon/templates/main/block.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load taglinks %} 3 | {% load sanitizer %} 4 | 5 | {% block title %}Brutaldon ({{ own_acct.username }}) - confirm (un)block {% endblock %} 6 | 7 | {% block content %} 8 | {% if relationship.blocking %} 9 |

Unblock this user?

10 | {% else %} 11 |

Block this user?

12 | {% endif %} 13 | 14 | 40 | 41 |
42 | {% csrf_token %} 43 |
44 |
45 |
46 | 47 |
48 |
49 |
50 |
51 | 53 |
54 |
55 |
56 |
57 | 58 | {% endblock %} 59 | -------------------------------------------------------------------------------- /brutaldon/templates/main/boost.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} Brutaldon ({{ own_acct.username }}) - confirm boost {% endblock %} 4 | 5 | {% block content %} 6 | {% if toot.reblogged %} 7 |

Unboost that toot?

8 | {% else %} 9 |

Boost that toot?

10 | {% endif %} 11 | 12 | {% include "main/toot_partial.html" with toot=toot %} 13 |
14 | {% csrf_token %} 15 |
16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 | {% if toot.reblogged %} 24 | 26 | {% else %} 27 | 29 | {% endif %} 30 |
31 |
32 |
33 |
34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /brutaldon/templates/main/delete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} Brutaldon - confirm delete {% endblock %} 4 | 5 | {% block content %} 6 |

Delete that toot?

7 | 8 | {% include "main/toot_partial.html" with toot=toot %} 9 |
10 | {% csrf_token %} 11 |
12 |
13 |
14 | 15 |
16 |
17 |
18 |
19 | 21 |
22 |
23 |
24 |
25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /brutaldon/templates/main/emoji.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Brutaldon ({{ own_acct.username }}) - Custom emoji reference {% endblock %} 4 | 5 | {% block content %} 6 |
7 |

Custom emoji reference

8 |

9 | Copy shortcodes from the list below to use your instance's custom emoji. 10 |

11 |
12 | 13 |
14 |
15 |
16 | {% for emojo in emojos %} 17 |
18 |

19 | 20 | :{{ emojo.shortcode }}: 21 |

22 |
23 | {% endfor %} 24 |
25 |
26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /brutaldon/templates/main/fav.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} Brutaldon ({{ own_acct.username }}) - confirm favorite {% endblock %} 4 | 5 | {% block content %} 6 | {% if toot.favourited %} 7 |

Unfav that toot?

8 | {% else %} 9 |

Fav that toot?

10 | {% endif %} 11 | 12 | {% include "main/toot_partial.html" with toot=toot %} 13 |
14 | {% csrf_token %} 15 |
16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 | {% if toot.favorited %} 24 | 26 | {% else %} 27 | 29 | {% endif %} 30 |
31 |
32 |
33 |
34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /brutaldon/templates/main/follow.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load taglinks %} 3 | {% load sanitizer %} 4 | 5 | {% block title %}Brutaldon ({{ own_acct.username }}) - confirm (un)follow {% endblock %} 6 | 7 | {% block content %} 8 | {% if relationship.requested %} 9 |

Cancel follow request?

10 | {% elif relationship.following %} 11 |

Unfollow this user?

12 | {% else %} 13 |

Follow this user?

14 | {% endif %} 15 | 16 | 42 | 43 |
44 | {% csrf_token %} 45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 |
53 | 55 |
56 |
57 |
58 |
59 | 60 | {% endblock %} 61 | -------------------------------------------------------------------------------- /brutaldon/templates/main/home_timeline.html: -------------------------------------------------------------------------------- 1 | {% extends "main/timeline.html" %} 2 | -------------------------------------------------------------------------------- /brutaldon/templates/main/local_timeline.html: -------------------------------------------------------------------------------- 1 | {% extends "main/timeline.html" %} 2 | 3 | {% block pagination %} 4 | 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /brutaldon/templates/main/mute.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load taglinks %} 3 | {% load sanitizer %} 4 | 5 | {% block title %}Brutaldon - confirm (un)mute {% endblock %} 6 | 7 | {% block content %} 8 | {% if relationship.muting %} 9 |

Unmute this user?

10 | {% else %} 11 |

Mute this user?

12 | {% endif %} 13 | 14 | 40 | 41 |
42 | {% csrf_token %} 43 |
44 |
45 |
46 | 47 |
48 |
49 |
50 |
51 | 53 |
54 |
55 |
56 |
57 | 58 | {% endblock %} 59 | -------------------------------------------------------------------------------- /brutaldon/templates/main/notifications.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanetime %} 3 | {% load taglinks %} 4 | {% load sanitizer %} 5 | 6 | {% block title %} 7 | Brutaldon ({{ own_acct.username }}) - Notifications timelime 8 | {% endblock %} 9 | 10 | {% comment %} 11 | mastodon.notifications()[0] 12 | # Returns the following dictionary: 13 | { 14 | 'id': # id of the notification 15 | 'type': # "mention", "reblog", "favourite" or "follow" 16 | 'created_at': # The time the notification was created 17 | 'account': # User dict of the user from whom the notification originates 18 | 'status': # In case of "mention", the mentioning status 19 | # In case of reblog / favourite, the reblogged / favourited status 20 | } 21 | {% endcomment %} 22 | 23 | 24 | {% block content %} 25 |

Your notifications timeline

26 | {% for group in groups %} 27 | {% if bundle_notifications and group.0.type in bundleable %} 28 | {% if group.0.type == 'favourite' %} 29 |

30 | {% for account in group.accounts %} 31 | {% include "comma.html" %}{{ account.display_name | fix_emojos:account.emojis |strip_html |safe }} 32 | ({{ account.acct }}) 33 | {% endfor %} 34 | favorited your toot. 35 |

36 | {% include "main/toot_partial.html" with toot=group.0.status %} 37 | 38 | {% elif group.0.type == 'reblog' %} 39 |

40 | {% for account in group.accounts %} 41 | {% include "comma.html" %}{{ account.display_name | fix_emojos:account.emojis |strip_html |safe }} 42 | ({{ account.acct }}) 43 | {% endfor %} 44 | boosted your toot. 45 |

46 | {% 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 %} 47 | 48 | {% endif %} 49 | {% else %} 50 | {% for note in group %} 51 | {% if note.type == 'mention' %} 52 |

53 | {{ note.account.display_name | fix_emojos:note.account.emojis |strip_html |safe }} 54 | ({{ note.account.acct }}) 55 | mentioned you. 56 |

57 |
58 | {% include "main/toot_partial.html" with toot=note.status reblog=False %} 59 | 60 | {% elif note.type == 'reblog' %} 61 |

62 | {{ note.account.display_name | fix_emojos:note.account.emojis |strip_html |safe }} 63 | ({{ note.account.acct }}) 64 | boosted your toot. 65 | ( 66 | {{ note.created_at |humane_time }} 67 | ) 68 |

69 | {% include "main/toot_partial.html" with toot=note.status reblog=True reblog_by=note.account.acct reblog_icon=note.account.avatar_static %} 70 | 71 | {% elif note.type == 'favourite' %} 72 |

73 | {{ note.account.display_name | fix_emojos:note.account.emojis |strip_html |safe }} 74 | ({{ note.account.acct }}) 75 | favorited your toot. 76 | ( 77 | {{ note.created_at |humane_time }} 78 | ) 79 |

80 | {% include "main/toot_partial.html" with toot=note.status %} 81 | 82 | {% elif note.type == 'follow' %} 83 | 100 | 101 | {% elif note.type == 'poll' %} 102 |

A poll you created or voted in has ended.

103 | {% include "main/toot_partial.html" with toot=note.status %} 104 | 105 | {% endif %} 106 | {% endfor %} 107 | {% endif %} 108 | {% endfor %} 109 | 110 | 118 | 119 | 120 | {% endblock %} 121 | -------------------------------------------------------------------------------- /brutaldon/templates/main/post.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block title %} Brutaldon ({{ own_acct.username }}) - toot {% endblock %} 5 | 6 | {% block content %} 7 |

Toot!

8 |
9 | {% include "main/post_partial.html" %} 10 |
11 | {% endblock %} 12 | 13 | {% block page_scripts_inline %} 14 | 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /brutaldon/templates/main/post_minimal_partial.html: -------------------------------------------------------------------------------- 1 | {% load widget_tweaks %} 2 |
3 | {% csrf_token %} 4 | 5 |
6 | {{ form.non_field_errors }} 7 |
8 |
9 | 10 |
11 | 12 |
13 | {% render_field form.spoiler_text class+="input mousetrap" placeholder="Optional" %} 14 |
15 |
16 | 17 |
18 | 19 |
20 | 25 |
26 |
27 |
28 |
29 | 30 | 50 | {% if not preferences.theme.no_javascript %} 51 | 54 | {% endif %} 55 |
56 | -------------------------------------------------------------------------------- /brutaldon/templates/main/post_partial.html: -------------------------------------------------------------------------------- 1 | {% load widget_tweaks %} 2 | 3 | {% if reply %} 4 |
5 | {% elif redraft %} 6 | 7 | {% else %} 8 | 9 | {% endif %} 10 | {% csrf_token %} 11 | 12 |
13 | {{ form.non_field_errors }} 14 |
15 |
16 | 17 |
18 | 19 |
20 | {% render_field form.spoiler_text class+="input mousetrap" placeholder="Optional" %} 21 |
22 |
23 | 24 |
25 | 26 |
27 | 32 |
33 |
34 |
35 |
36 | 37 |
38 | 39 |
40 |
41 | {% render_field form.visibility class+="select"%} 42 | 43 | 44 | 45 |
46 |
47 |
48 | 49 |
50 |
51 | 62 |
63 |
64 | {% render_field form.media_text_1 class+="input mousetrap" placeholder="Describe attachment" %} 65 |
66 |
67 | 68 |
69 |
70 | 81 |
82 |
83 | {% render_field form.media_text_2 class+="input mousetrap" placeholder="Describe attachment" %} 84 |
85 |
86 | 87 |
88 |
89 | 100 |
101 |
102 | {% render_field form.media_text_3 class+="input mousetrap" placeholder="Describe attachment" %} 103 |
104 |
105 |
106 |
107 | 118 |
119 |
120 | {% render_field form.media_text_4 class+="input mousetrap" placeholder="Describe attachment" %} 121 |
122 |
123 | 124 | 125 |
126 |
127 | 128 | [{{ own_acct.acct }}] 129 | 130 | 132 | 😊 134 |
135 |
136 | {% if not preferences.no_javascript %} 137 | 142 | {% endif %} 143 |
144 | -------------------------------------------------------------------------------- /brutaldon/templates/main/public_timeline.html: -------------------------------------------------------------------------------- 1 | {% extends "main/timeline.html" %} 2 | 3 | {% block pagination %} 4 | 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /brutaldon/templates/main/redraft.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 | {% load static %} 4 | 5 | {% block title %} 6 | Brutaldon ({{ own_acct.username }}) - reply 7 | {% endblock %} 8 | 9 | {% block page_scripts %} 10 | 11 | {% endblock %} 12 | 13 | {% block content %} 14 |

Redraft

15 | {% include "main/toot_partial.html" with toot=toot active=True %} 16 | 17 |
18 |

19 | Submitting this form will post this replacement toot, and 20 | delete the original toot. The replacement toot will not 21 | have any favs, boosts, or replies that the original toot had. 22 | Currently, media attachments must be re-uploaded. Sorry, working on it. 23 |

24 |
25 | 26 |
27 | {% include "main/post_partial.html" %} 28 |
29 | 30 | {% endblock %} 31 | 32 | {% block page_scripts_inline %} 33 | 42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /brutaldon/templates/main/reply.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 | {% load static %} 4 | 5 | {% block title %} 6 | Brutaldon ({{ own_acct.username }}) - reply 7 | {% endblock %} 8 | 9 | {% block page_scripts %} 10 | 11 | {% endblock %} 12 | 13 | {% block content %} 14 |

Thread

15 | {% for ancestor in context.ancestors %} 16 | {% include "main/toot_partial.html" with toot=ancestor %} 17 | 18 | {% endfor %} 19 | {% include "main/toot_partial.html" with toot=toot active=True %} 20 | 21 |
22 | {% include "main/post_partial.html" %} 23 |
24 | 25 | {% endblock %} 26 | 27 | {% block page_scripts_inline %} 28 | 37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /brutaldon/templates/main/search.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | 4 | {% block title %}brutaldon ({{ own_acct.username }}) – search {% endblock %} 5 | 6 | {% block content %} 7 |

Search

8 | 9 |
10 |
11 | You can search for tags, users, or for specific toots by URL. You may 12 | also be able to full-text search for toots you have previously 13 | interacted with. 14 |
15 |
16 | 17 |
18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 |
26 |
27 | 28 |
29 | 30 |
31 |
32 | 33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /brutaldon/templates/main/search_results.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 | {% load taglinks %} 4 | {% load sanitizer %} 5 | 6 | {% block title %} 7 | Brutaldon ({{ own_acct.username }}) - search results 8 | {% endblock %} 9 | 10 | {% comment %} 11 | mastodon.search("") 12 | # Returns the following dictionary 13 | { 14 | 'accounts': # List of account dicts resulting from the query 15 | 'hashtags': # List of hashtag dicts resulting from the query 16 | 'statuses': # List of toot dicts resulting from the query 17 | } 18 | 19 | {% endcomment %} 20 | 21 | {% block content %} 22 | 23 |
24 |
25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 |
33 | 34 |
35 | 36 |
37 |
38 |
39 | 40 |

Search results

41 |
42 |

Users

43 | {% for user in results.accounts %} 44 | 70 | 71 | 72 | {% endfor %} 73 | 74 | 75 |

Tags

76 |
    77 | {% for tag in results.hashtags %} 78 |
  • #{{ tag.name }}
  • 79 | {% endfor %} 80 |
81 | 82 |

Toots

83 | {% for toot in results.statuses %} 84 | {% include "main/toot_partial.html" with toot=toot reblog=False %} 85 | {% endfor %} 86 |
87 | {% endblock %} 88 | -------------------------------------------------------------------------------- /brutaldon/templates/main/thread.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 | 4 | {% block title %} 5 | Brutaldon ({{ own_acct.username }}) - thread 6 | {% endblock %} 7 | 8 | {% comment %} 9 | mastodon.status_context() 10 | # Returns the following dictionary: 11 | { 12 | 'ancestors': # A list of toot dicts 13 | 'descendants': # A list of toot dicts 14 | } 15 | {% endcomment %} 16 | 17 | {% block content %} 18 |

19 | Thread 20 |

21 | {% include "main/toot_partial.html" with toot=root %} 22 | {% for descendant in descendants %} 23 | {% if descendant == toot %} 24 | {% include "main/toot_partial.html" with toot=toot active=True %} 25 | {% else %} 26 | {% include "main/toot_partial.html" with toot=descendant %} 27 | {% endif %} 28 | 29 | {% endfor %} 30 | 31 | {% if not preferences.no_javascript %} 32 | 35 | {% endif %} 36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /brutaldon/templates/main/timeline.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 | {% load static %} 4 | {% load cache %} 5 | 6 | {% block title %} 7 | Brutaldon ({{ own_acct.username }}) - {{ timeline_name }} timelime 8 | {% endblock %} 9 | 10 | {% block page_scripts %} 11 | {% if not fullbrutalism %} 12 | 13 | {% endif %} 14 | {% endblock %} 15 | 16 | {% block content %} 17 | {% if form %} 18 |

Post

19 |
20 | {% include "main/post_minimal_partial.html" %} 21 |
22 | 23 | {% endif %} 24 |

Your {{ timeline_name }} timeline

25 |
26 | {% for toot in toots %} 27 | {% cache 600 toot_partial toot.id %} 28 | {% if toot.reblog %} 29 | {% include "main/toot_partial.html" with toot=toot.reblog reblog=True reblog_by=toot.account.acct reblog_icon=toot.account.avatar_static %} 30 | {% else %} 31 | {% include "main/toot_partial.html" with toot=toot reblog=False %} 32 | {% endif %} 33 | {% endcache %} 34 | 35 | {% endfor %} 36 | 37 | {% block pagination %} 38 |
39 |
40 | {% if next %} 41 |

42 | 52 | Older 53 | 54 |

55 |

56 | {% endif %} 57 | {% if prev %} 58 |

59 | 61 | Newer 62 | 63 |

64 | {% endif %} 65 |
66 |
67 | {% endblock %} 68 |
69 | {% endblock %} 70 | 71 | {% block page_scripts_inline %} 72 | 81 | {% endblock %} 82 | -------------------------------------------------------------------------------- /brutaldon/templates/main/user.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load humanize %} 3 | {% load sanitizer %} 4 | {% load taglinks %} 5 | 6 | {% block title %} 7 | Brutaldon ({{ own_acct.username }}) - {{ user.acct }} timelime 8 | {% endblock %} 9 | 10 | {% block content %} 11 | 12 |
13 | {% if not preferences.theme.is_brutalist %} 14 |
15 | {% else %} 16 |
17 | {% endif %} 18 | 23 |
24 | Avatar 25 | {% if user.locked %} 26 | 29 | {% endif %} 30 |
31 |
32 |
33 |
34 | {{ user.note | relink_toot | strip_html | safe }} 35 |
36 | {% if user.acct != own_acct.acct %} 37 |
38 |
39 | {% if relationship.requested %} 40 | 44 | 45 | 46 | {% elif not relationship.following %} 47 | 51 | 52 | 53 | {% else %} 54 | 59 | 60 | 61 | {% endif %} 62 | 64 | 65 | 66 | 68 | 69 | 70 | 71 |
72 |
73 | {% if not relationship.muting %} 74 | 79 | 80 | 81 | {% else %} 82 | 86 | 87 | 88 | {% endif %} 89 | {% if not relationship.blocking %} 90 | 95 | 96 | 97 | {% else %} 98 | 102 | 103 | 104 | {% endif %} 105 |
106 |
107 | {% endif %} 108 |
109 |
110 | 111 |
112 | 113 | {% for toot in toots %} 114 | {% if toot.reblog %} 115 | {% include "main/toot_partial.html" with toot=toot.reblog reblog=True reblog_by=toot.account.acct reblog_icon=toot.account.avatar_static %} 116 | {% else %} 117 | {% include "main/toot_partial.html" with toot=toot reblog=False %} 118 | {% endif %} 119 | 120 | {% endfor %} 121 | 129 | 130 | {% endblock %} 131 | -------------------------------------------------------------------------------- /brutaldon/templates/polls/completed_partial.html: -------------------------------------------------------------------------------- 1 | {% load sanitizer %} 2 | {% load taglinks %} 3 | {% load static %} 4 |
5 | {% for option in toot.poll.options %} 6 |
7 | {{ option.title }} 8 | ({{ option.votes_count}} vote{{ option.votes_count|pluralize }}) 9 | 10 |
11 |
12 | 17 |
18 | {% endfor %} 19 |
20 | -------------------------------------------------------------------------------- /brutaldon/templates/polls/new_partial.html: -------------------------------------------------------------------------------- 1 | {% load sanitizer %} 2 | {% load taglinks %} 3 | {% load static %} 4 | 5 |
10 | {% csrf_token %} 11 | {% for option in toot.poll.options %} 12 |
13 | {% if toot.poll.multiple %} 14 | 20 | {% else %} 21 | 27 | {% endif %} 28 |
29 | {% endfor %} 30 | 31 | 33 | 34 |
35 | -------------------------------------------------------------------------------- /brutaldon/templates/privacy.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block content %} 5 |

Privacy statement

6 |

A shameful attempt at ass-covering

7 | 8 |
9 |

Summary

10 |

11 | Brutaldon tries to collect as little information about you as possible. The information that it collects in order to log you in to your instance and to implement client features is stored as transiently as possible. 12 |

13 |
14 |
15 |

16 | Information that is stored until a database wipe 17 |

18 |

19 | If you use the old login form (instance, email, password), which is only recommended if you running your own copy of brutaldon behind a firewall, the email you use to sign in will be stored along with an access token and the name of the instance you connected to. Your password will never be stored. We will never share your email with anyone for any reason, unless legally forced to do so. We will never send you email except in the case of an unforseen emergency requiring us to. 20 |

21 |

22 | If you use the normal login form, your email will not be stored, only your instance name and access token. 23 |

24 |

25 | Both methods of login will store your username and instance name. All of your brutaldon settings (theme, timezone, etc) are stored and associated with your username. 26 |

27 | 28 |

29 | You can always revoke an access token through the web interface of your instance. 30 |

31 |

32 | Information that is stored only during your session 33 |

34 |

35 | Data stored in your session is deleted when you log out, or periodically by the server. 36 |

37 |

38 |

39 | Data stored in the session includes: 40 |

41 |
    42 |
  • A reference to your information in the database.
  • 43 |
  • All of your public Mastodon profile information. This is needed for some client functionality.
  • 44 |
45 |
46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /brutaldon/templates/requests/list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | 4 | {% block content %} 5 |
6 |

Follow requests

7 | 8 | {% if not requests %} 9 |

No follow requests.

10 | {% endif %} 11 | 12 | 13 | {% for request in requests %} 14 | {% include "requests/request_partial.html" %} 15 | {% endfor %} 16 | 17 |
18 | 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /brutaldon/templates/requests/request_partial.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {{ request.acct }} 6 |

7 |
8 |
9 | {{ request.display_name }} ({{ request.acct }}) 10 |
11 |
12 |
13 | {% csrf_token %} 14 | 17 | 20 |
21 |
22 | -------------------------------------------------------------------------------- /brutaldon/templates/setup/login-oauth.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | 4 | {% block content %} 5 |

Log in to your instance

6 | 7 |
8 | {% csrf_token %} 9 |
10 | 11 |
12 | {% render_field form.instance class+="input" %} 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 | 22 |
23 |
24 | 25 |
26 | 27 |
28 | Not able to log in with this form? Maybe your brutaldon instance isn't 29 | visible on the internet to your Mastodon instance? If so, you can use 30 | the old login form. 31 |
32 |
33 | 34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /brutaldon/templates/setup/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | 4 | {% block content %} 5 |

Log in to your instance

6 | 7 |
8 | {% csrf_token %} 9 |
10 | 11 |
12 | {% render_field form.instance class+="input" %} 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 | 21 |
22 | {% render_field form.email class+="input" %} 23 | 24 | 25 | 26 |
27 |
28 |
29 | 30 |
31 | {% render_field form.password class+="input" type="password" %} 32 | 33 | 34 | 35 |
36 |
37 |
38 | 40 |
41 |
42 | {% if form.errors %} 43 |

44 | {{ form.non_field_errors | escape }} 45 |

46 | {% endif %} 47 | 48 |
49 | 50 |

51 | This information is only used to log you in to your instance for the 52 | first time. Brutaldon never stores your username and password; it 53 | only uses it to acquire a token which you can disable from the 54 | settings page of your Mastodon instance. However, you do need to 55 | absolutely trust the person running this copy of brutaldon! If that's 56 | not the case, use the OAuth login form. 57 |

58 |
59 | 60 | {% endblock %} 61 | -------------------------------------------------------------------------------- /brutaldon/templates/setup/settings.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | 4 | {% block content %} 5 |
6 |

Settings

7 |
8 | {% csrf_token %} 9 | 10 |

General Options

11 |
12 | 13 |
14 |
15 | {% render_field form.theme class+="select" %} 16 | 17 | 18 |
19 |
20 |
21 | 22 |
23 | 24 |
25 |
26 | {% render_field form.timezone class+="select" %} 27 | 28 | 29 |
30 |
31 |
32 | 33 |

Content Options

34 |
35 |
36 | 40 |
41 |
42 |

43 | {{ form.preview_sensitive.help_text }} 44 |

45 |
46 |
47 | 48 |
49 |
50 | 51 |

Timeline Options

52 |
53 | 57 |
58 |
59 | 63 |
64 |
65 |
66 | 70 |
71 |
72 |

73 | {{ form.filter_notifications.help_text }} 74 |

75 |
76 |
77 | 78 |
79 |
80 |
81 |
82 | 86 |
87 |
88 |

89 | {{ form.bundle_notifications.help_text }} 90 |

91 |
92 |
93 | 94 |
95 |
96 | 97 |

JavaScript Options

98 |
99 |
100 | 104 |
105 |
106 |

107 | {{ form.no_javascript.help_text }} 108 |

109 |
110 |
111 | 112 |
113 |
114 |
115 |
116 | 120 |
121 |
122 |

123 | {{ form.notifications.help_text }} 124 |

125 |
126 |
127 | 128 |
129 |
130 |
131 |
132 | 136 |
137 |
138 |

139 | {{ form.click_to_load.help_text }} 140 |

141 |
142 |
143 | 144 |
145 |
146 |
147 |
148 | 152 |
153 |
154 |

155 | {{ form.lightbox.help_text }} 156 |

157 |
158 |
159 | 160 |
161 |
162 | 163 |
164 |
165 | 168 |
169 | {% render_field form.poll_frequency class+="input" %} 170 |
171 |
172 |
173 |

174 | {{ form.poll_frequency.help_text }} 175 |

176 |
177 |
178 | 179 |
180 |
181 | 182 |
183 | 185 |
186 |
187 | 188 |

Bookmarklet

189 |

190 | Share via brutaldon 191 |

192 | 193 |

Filters and More

194 |

List filters

195 |

Follow requests

196 | 197 |
198 | {% endblock %} 199 | -------------------------------------------------------------------------------- /brutaldon/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/brutaldon/templatetags/__init__.py -------------------------------------------------------------------------------- /brutaldon/templatetags/humanetime.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from django.utils.timezone import get_default_timezone, get_current_timezone, localtime 3 | from django.utils.timezone import now as django_now 4 | from django.utils.translation import gettext as _ 5 | from django import template 6 | 7 | register = template.Library() 8 | 9 | 10 | @register.filter(is_safe=True) 11 | def humane_time(arg): 12 | """Returns a time string that is humane but not relative (unlike Django's humanetime) 13 | 14 | For times less than 6 hours ago: display date and time to the minute. 15 | For times less than 12 hours ago: display date and time to the hour. 16 | For times more than 12 hours ago display date and "time of day". 17 | For times more than 2 days ago display date. 18 | For times more than 6 months ago, display month and year. 19 | For times more than 10 years ago, display year. 20 | 21 | Prefer words to numbers, unless it is too long. 22 | 23 | The goal is a date/time that is always accurate no matter how long it's 24 | been sitting there waiting for you to look at it, but is only precise 25 | to a degree you are liable to care about. 26 | 27 | It is not safe to use on future times. 28 | 29 | FIXME: work out how best to make these strings translatable 30 | 31 | """ 32 | now = django_now() 33 | arg = localtime(arg) 34 | diff = now - arg 35 | 36 | if arg.tzinfo == now.tzinfo: 37 | utc = " (UTC)" 38 | else: 39 | utc = "" 40 | if diff < timedelta(hours=6): 41 | return arg.strftime("%a, %b %d, %Y at %I:%M %p") + utc 42 | elif diff < timedelta(hours=12): 43 | return arg.strftime("%a, %b %d, %Y around %I %p") + utc 44 | elif diff < timedelta(hours=36): 45 | return arg.strftime("%a, %b %d, %Y in the ") + time_of_day(arg.hour) + utc 46 | elif diff < timedelta(days=6 * 28): 47 | return arg.strftime("%b %d, %Y") 48 | elif diff < timedelta(days=10 * 365): 49 | return arg.strftime("%b, %Y") 50 | else: 51 | return arg.strftime("%Y") 52 | 53 | 54 | def time_of_day(hour): 55 | """Return a description of what time of day an hour is. 56 | 57 | This is very english-centric and probably not translatable. 58 | """ 59 | if hour < 3: 60 | return _("wee hours") 61 | elif hour < 6: 62 | return _("early morning") 63 | elif hour < 12: 64 | return _("morning") 65 | elif hour < 18: 66 | return _("afternoon") 67 | elif hour < 22: 68 | return _("evening") 69 | else: 70 | return _("night") 71 | -------------------------------------------------------------------------------- /brutaldon/templatetags/taglinks.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from bs4 import BeautifulSoup 3 | from urllib import parse 4 | from django.urls import reverse 5 | from django.utils.translation import gettext as _ 6 | from pdb import set_trace 7 | 8 | register = template.Library() 9 | 10 | 11 | @register.filter 12 | def pdb(element): 13 | set_trace() 14 | return element 15 | 16 | 17 | @register.filter 18 | def relink_tags(value): 19 | """Treat the text as html, and replace tag links with app-internal tag links 20 | 21 | Currently, this only works for tags in toots coming from Mastodon servers, 22 | not necessarily GNU Social, Pleroma, or other fediverse servers, because 23 | it relies on the markup that Mastodon puts on tags. 24 | 25 | FIXME: handle arbitrary tag links 26 | """ 27 | value = value.replace("'", "'") 28 | soup = BeautifulSoup(value, "html.parser") 29 | for link in soup.find_all("a", class_="hashtag"): 30 | try: 31 | link["href"] = reverse("tag", args=[link.span.string]) 32 | except: 33 | continue 34 | return soup.decode(formatter="html") 35 | 36 | 37 | @register.filter 38 | def relink_mentions(value): 39 | """Treat the text as html, and replace mention links with app-internal links 40 | 41 | Currently, this only works for mentions in toots coming from Mastodon servers, 42 | not necessarily GNU Social, Pleroma, or other fediverse servers, because 43 | it relies on the markup that Mastodon puts on mentions. 44 | 45 | FIXME: handle arbitrary mention links 46 | """ 47 | value = value.replace("'", "'") 48 | soup = BeautifulSoup(value, "html.parser") 49 | for link in soup.find_all("a", class_="mention"): 50 | parsed = parse.urlparse(link["href"]) 51 | try: 52 | instance = parsed[1] 53 | user = parsed[2][2:] 54 | link["href"] = reverse("user", args=[user + "@" + instance]) 55 | except: 56 | continue 57 | return soup.decode(formatter="html") 58 | 59 | 60 | @register.filter 61 | def relink_toot(value): 62 | return relink_tags(relink_mentions(value)) 63 | 64 | 65 | @register.filter 66 | def localuser(value): 67 | """Convert a remote user link to local""" 68 | try: 69 | parsed = parse.urlparse(value) 70 | instance = parsed[1] 71 | if parsed[2].startswith("/@"): 72 | user = parsed[2][2:] 73 | else: 74 | user = parsed[2].split("/")[-1] 75 | local = reverse("user", args=[user + "@" + instance]) 76 | except: 77 | local = value 78 | return local 79 | 80 | 81 | @register.filter 82 | def fix_emojos(value, emojos): 83 | """Replace instances of recognized custom emoji :shortcodes: in value with image link tags 84 | """ 85 | for emojo in emojos: 86 | try: 87 | value = value.replace( 88 | ":%(shortcode)s:" % emojo, 89 | ':%(shortcode)s:' 90 | % emojo, 91 | ) 92 | except: 93 | continue 94 | return value 95 | -------------------------------------------------------------------------------- /brutaldon/urls.py: -------------------------------------------------------------------------------- 1 | """brutaldon URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path 18 | from brutaldon import views 19 | 20 | urlpatterns = [ 21 | path("admin/", admin.site.urls), 22 | path("about", views.about, name="about"), 23 | path("privacy", views.privacy, name="privacy"), 24 | path("home/next/", views.home, name="home_next"), 25 | path("home/prev/", views.home, name="home_prev"), 26 | path("home", views.home, name="home"), 27 | path("login", views.login, name="login"), 28 | path("oldlogin", views.old_login, name="oldlogin"), 29 | path("logout", views.logout, name="logout"), 30 | path("oauth_callback", views.oauth_callback, name="oauth_callback"), 31 | path("error", views.error, name="error"), 32 | path("local", views.local, name="local"), 33 | path("local/next/", views.local, name="local_next"), 34 | path("local/prev/", views.local, name="local_prev"), 35 | path("fed", views.fed, name="fed"), 36 | path("fed/next/", views.fed, name="fed_next"), 37 | path("fed/prev/", views.fed, name="fed_prev"), 38 | path("note", views.note, name="note"), 39 | path("note/next", views.note, name="note_next"), 40 | path("note/prev/", views.note, name="note_prev"), 41 | path("notes_count", views.notes_count, name="notes_count"), 42 | path("user_search", views.user_search, name="user_search"), 43 | path("settings", views.settings, name="settings"), 44 | path("thread/", views.thread, name="thread"), 45 | path("tags/", views.tag, name="tag"), 46 | path("user/", views.home, name="user_bad"), 47 | path("user/", views.user, name="user"), 48 | # next/prev are integers, but pleroma uses 128 bit integers 49 | # ...encoded in Base62. 50 | # aka a "flake_id" 51 | # from baseconv import base62, but we don't need to decode it 52 | # just pass it along back to pleroma but it is NOT an 53 | path("user//next/", views.user, name="user_next"), 54 | path("user//prev/", views.user, name="user_prev"), 55 | path("toot/", views.toot, name="toot"), 56 | path("toot", views.toot, name="toot"), 57 | path("reply/", views.reply, name="reply"), 58 | path("redraft/", views.redraft, name="redraft"), 59 | path("fav/", views.fav, name="fav"), 60 | path("boost/", views.boost, name="boost"), 61 | path("delete/", views.delete, name="delete"), 62 | path("follow/", views.follow, name="follow"), 63 | path("block/", views.block, name="block"), 64 | path("mute/", views.mute, name="mute"), 65 | path("search", views.search, name="search"), 66 | path("search_results", views.search_results, name="search_results"), 67 | path("emoji", views.emoji_reference, name="emoji"), 68 | path("filters/list", views.list_filters, name="list_filters"), 69 | path("filters/create", views.create_filter, name="create_filter"), 70 | path("filters/delete/", views.delete_filter, name="delete_filter"), 71 | path("filters/edit/", views.edit_filter, name="edit_filter"), 72 | path("requests/", views.follow_requests, name="follow_requests"), 73 | path("requests/", views.follow_requests, name="follow_requests"), 74 | path("accounts/", views.accounts, name="accounts"), 75 | path("accounts/", views.accounts, name="accounts"), 76 | path("vote/", views.vote, name="vote"), 77 | path("share/", views.share, name="share"), 78 | path("", views.home, name=""), 79 | ] 80 | -------------------------------------------------------------------------------- /brutaldon/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for brutaldon project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "brutaldon.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /docs/screenshots/screenshot-firefox-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/docs/screenshots/screenshot-firefox-2.png -------------------------------------------------------------------------------- /docs/screenshots/screenshot-firefox-brutalist-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/docs/screenshots/screenshot-firefox-brutalist-2.png -------------------------------------------------------------------------------- /docs/screenshots/screenshot-firefox-brutalist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/docs/screenshots/screenshot-firefox-brutalist.png -------------------------------------------------------------------------------- /docs/screenshots/screenshot-firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/docs/screenshots/screenshot-firefox.png -------------------------------------------------------------------------------- /docs/screenshots/screenshot-lynx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfmcbrayer/brutaldon/e6c5273a2f9135d57fc8568d423a58e59bc5ec08/docs/screenshots/screenshot-lynx.png -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "brutaldon.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "bulma": "^0.7.5", 4 | "bulma-extensions": "^2.2.2", 5 | "bulmaswatch": "^0.6.2", 6 | "fork-awesome": "^1.1.0", 7 | "intercooler": "^1.2.1", 8 | "jquery": "^3.4.1", 9 | "loading-attribute-polyfill": "^1.2.0", 10 | "magnific-popup": "^1.1.0", 11 | "mousetrap": "^1.6.2" 12 | } 13 | } 14 | --------------------------------------------------------------------------------