Compare commits

..

335 Commits

Author SHA1 Message Date
kibigo!
276432790a Introducing: Mastodon GO! 2017-11-04 05:48:42 -07:00
Ondřej Hruška
516eeeb43d option to add title to <Button>, use for toot buttons (#197) 2017-10-24 19:08:07 +02:00
David Yip
664c9aa708 Merge pull request #196 from glitch-soc/fix-imports
Added app/javascript for imports
2017-10-23 23:34:43 -05:00
kibigo!
119d477c8b Added app/javascript for imports 2017-10-23 20:22:48 -07:00
David Yip
4f01e6e8d5 Merge remote-tracking branch 'origin/master' into gs-master 2017-10-22 22:57:41 -05:00
Ondřej Hruška
d589dd7cd0 Compose buttons bar redesign + generalize dropdown (#194)
* Generalize compose dropdown for re-use

* wip stuffs

* new tootbox look and removed old doodle button files

* use the house icon for ...
2017-10-21 20:24:53 +02:00
beatrix
a7be86e875 hide mentions of muted accounts (in home col) (#190)
* hide mentions of muted accounts (in home col)

also cleans up some old crap

* add test
2017-10-20 10:49:54 -04:00
beatrix
b15dd05514 Merge pull request #191 from glitch-soc/garglamel-yaml
ƔAML update
2017-10-19 19:29:52 -04:00
kibigo!
21bafc6555 Updates to bio metadata script 2017-10-19 16:11:53 -07:00
beatrix
f5e2469485 Merge pull request #189 from glitch-soc/scrollable-compose-area
Make the compose area optionally scrollable
2017-10-19 12:28:47 -04:00
David Yip
9423553e5c Make the compose area optionally scrollable.
On desktop, the compose text box grows to accommodate the content.  On
mobile, the text box does not grow to accommodate text context, but does
grow to accommodate images.  It is possible in both cases to overflow
the available area, which makes accessing other UI elements (e.g.
visibility setttings) difficult.

This commit makes the compose area optionally scrollable, which allows
those UI elements to remain available even if they go off-screen.
2017-10-19 10:59:50 -05:00
David Yip
90770f6d59 Merge pull request #185 from glitch-soc/fix-null-status
workaround for null status
2017-10-18 17:01:31 -05:00
beatrix
c756651278 Merge pull request #188 from glitch-soc/merge-upstream-2-0-0
Merge with upstream v2.0.0
2017-10-18 17:57:42 -04:00
Ondřej Hruška
eb907a5bab formatting fix for eslint 2017-10-18 23:44:06 +02:00
David Yip
39c9cdf7fe Remove unused filesAttached property. 2017-10-18 13:20:45 -05:00
David Yip
86cf4468af Update stylesheet imports in glitch components.
Commit 6e54719474 moved the Mastodon
variables and mixins deeper in the directory hierarchy; this commit
brings the glitch components in line with that change.
2017-10-18 11:52:34 -05:00
David Yip
42e8c8eb0e Merge tag 'v2.0.0' into gs-master 2017-10-18 11:52:04 -05:00
Ondřej Hruška
4421f6598f Fix dual toot button wrong behavior after compose re-design 2017-10-16 23:32:13 +02:00
Ondřej Hruška
64a5e9a1de workaround for null status 2017-10-16 23:09:39 +02:00
beatrix
7364b26e4b Merge pull request #184 from tootsuite/master
Fix reduced motion breaking public galleries (#5423)
2017-10-16 17:05:42 -04:00
David Yip
313ba202ef Merge pull request #182 from glitch-soc/wide-tootbox-panel
Full-width tootbox panel to make room for buttons
2017-10-16 15:49:22 -05:00
David Yip
7c44ad6355 Merge pull request #181 from glitch-soc/upstream-merge-again
Merge upstream, pull in fixes for tootsuite/mastodon#{5409,5417}
2017-10-16 15:46:12 -05:00
Ondřej Hruška
37ff061d9b satisfy eslint and jest 2017-10-16 22:24:44 +02:00
Ondřej Hruška
3d7de06db4 rem a comment 2017-10-16 22:12:38 +02:00
Ondřej Hruška
26f08f0791 double-decker tootbox 2017-10-16 22:10:14 +02:00
Ondřej Hruška
8b9ee5f16b Merge remote-tracking branch 'upstream/master' into upstream-merge-again 2017-10-16 21:13:47 +02:00
Ondřej Hruška
4b397adb5b fix some lint errors 2017-10-16 21:13:34 +02:00
David Yip
65154869df Merge remote-tracking branch 'upstream/master' into upstream-merge-again 2017-10-16 09:54:16 -05:00
David Yip
7e9d93472c Merge remote-tracking branch 'upstream/master' into gs-master 2017-10-16 09:27:01 -05:00
David Yip
dbb1fce94d Merge remote-tracking branch 'upstream/master' into gs-master 2017-10-16 09:23:59 -05:00
David Yip
6cd5b3bbe5 Merge remote-tracking branch 'origin/master' into gs-master 2017-10-16 01:29:02 -05:00
David Yip
f72ad67a39 Merge pull request #180 from STJrInuyasha/glitch-soc/avatar-unification
Avatar unification, mk.II
2017-10-16 00:49:05 -05:00
Matthew Walsh
56094cb4bd 1.6 changed files for unified avatar styling -- updated to restore unity 2017-10-15 21:14:21 -07:00
David Yip
22df18e902 Merge pull request #173 from glitch-soc/zerowidthmoji
use zerowidth spaces with emojis
2017-10-15 20:04:19 -05:00
Ondřej Hruška
cdc22d23b9 Doodle improvements 2 (#176)
* Fix some doodle bugs and added Background color functionality

* added protections against accidental doodle erase, screen size changing

* resolve react warning about 'selected' on <option>
2017-10-14 12:24:35 +02:00
Ondřej Hruška
531dadad86 Doodle palette, options, fill tool (#172) 2017-10-14 00:58:38 +02:00
Ondřej Hruška
723890b6fa use zerowidth spaces for emojis 2017-10-14 00:48:53 +02:00
David Yip
daa04c39b6 Merge pull request #168 from glitch-soc/doodles
Initial doodle support
2017-10-13 11:48:53 -05:00
Ondřej Hruška
2af964ef20 Fixed a bug where the canvas would get transparent bg upon erase 2017-10-13 18:13:00 +02:00
Ondřej Hruška
33e806217f Initial doodle support 2017-10-13 18:07:01 +02:00
Ondřej Hruška
df626fdd43 Fix the notification bug with newlines 2017-10-12 21:28:17 +02:00
David Yip
6a2b1ba91e Another tabs-vs-spaces fix, but with ~feeling~ 2017-10-12 09:36:26 -05:00
David Yip
fe1c20cfb9 Fix tabs-vs-spaces issue pointed out by eslint. 2017-10-12 09:23:06 -05:00
David Yip
edd1a00faf Restore variable assignment in MuteService#call.
291feba6f1 made MuteService return the
result of Account#mute!; this commit restores that behavior.
2017-10-12 04:27:45 -05:00
David Yip
13e05fece2 Merge tag 'v2.0.0rc2' into gs-master 2017-10-12 04:22:59 -05:00
Ondřej Hruška
eeaa8fba43 Fix our media gallery not having alt text and tooltips 2017-10-11 22:02:24 +02:00
Ondřej Hruška
b0b5ff1b0f workaround for scrollIntoView bug ??? 2017-10-11 21:49:22 +02:00
Ondřej Hruška
bd51a16150 add duck beaks in haml 2017-10-11 21:34:00 +02:00
Ondřej Hruška
0bf9261e65 Merge remote-tracking branch 'upstream/master' 2017-10-11 21:27:17 +02:00
kibigo!
3cc6255a7e Removed length check which now happens in reducer 2017-10-11 11:50:11 -07:00
kibigo!
35e2cad4eb Fixed header emojification 2017-10-11 11:00:28 -07:00
kibigo!
8d6b9ba494 Merge upstream 2.0ish #165 2017-10-11 10:43:10 -07:00
beatrix
f0a2a6c875 try to tighten up local only toot stuff, like... properly (#163)
* try to tighten up local only toot stuff, like... properly

* try to un-break tests
2017-10-09 09:56:17 -04:00
Ondřej Hruška
92a3181dc6 improve static page metadata look 2017-10-08 10:46:59 +02:00
beatrix
7cc0da756d Merge pull request #161 from DJSundog/audio-uploads-feature
Audio uploads feature
2017-10-07 20:13:53 -04:00
DJ Sundog
85d5da86df raise Mastodon::ValidationError if audio file duration is over 60 secs 2017-10-07 16:14:34 -07:00
DJ Sundog
e10cff8226 update indentation 2017-10-07 13:55:11 -07:00
DJ Sundog
979b0d66a7 update indentation 2017-10-07 13:53:46 -07:00
DJ Sundog
6ca03a7f58 add faststart to audio transcoding 2017-10-07 19:59:22 +00:00
DJ Sundog
96ba3482b9 adding support for audio uploads, transcoded to mp4 videos 2017-10-07 19:54:10 +00:00
kibigo!
6610d57f91 Basic static metadata styling 2017-10-06 22:22:34 -07:00
Ondřej Hruška
1a8011648f Merge pull request #160 from glitch-soc/ugly-fix-notif-clearing
workaround for notif clearing
2017-10-06 09:34:12 +02:00
Ondřej Hruška
37e141bccb comment out unused stuff with Video 2017-10-06 09:23:28 +02:00
Ondřej Hruška
dcd8ff5308 fix haml quotes in a bid to make travis less grumpy 2017-10-06 09:07:04 +02:00
Ondřej Hruška
f82e90bf11 workaround for notif clearing 2017-10-05 21:55:02 +02:00
beatrix
b1217242fc Merge pull request #125 from glitch-soc/data-column
Add data-column="..." to all columns
2017-09-30 15:02:21 -04:00
beatrix
5dec67d964 Merge pull request #159 from glitch-soc/closed-instance-landing-page-login-form
add login form to landing page if regs are closed
2017-09-30 15:01:32 -04:00
Ondřej Hruška
ce3e0faf4d add login form to landing page if regs are closed 2017-09-29 16:41:18 +02:00
beatrix
c027a7bd4d Merge pull request #157 from glitch-soc/merging-upstream
ABRACA-HRRRRRRRRRRRNGGGGGGGHHH!!!!!!!!!!!!!!!!!!!
2017-09-28 21:48:28 -04:00
kibigo!
53f829dfa8 HAML fix 2017-09-28 18:16:04 -07:00
kibigo!
cf1f83ca2a Fixed lingering upstream Theme code 2017-09-28 17:45:16 -07:00
kibigo!
1522795853 new HAML for statuses 2017-09-28 17:18:12 -07:00
Ondřej Hruška
e9f9e3cc89 unbreak onboarding modal 2017-09-28 11:13:37 +02:00
Ondřej Hruška
cf24ce7e03 remove wrong emojify usage in old glitch components 2017-09-28 11:03:23 +02:00
Ondřej Hruška
74e4fc8f8a fix custom emoji not shown in statuses 2017-09-28 10:13:16 +02:00
Ondřej Hruška
9a5418942c fix some js errors due to number->string change of status ID 2017-09-28 10:04:20 +02:00
Ondřej Hruška
daad07b1d5 Merge remote-tracking branch 'origin/better-themeing' into merging-upstream 2017-09-28 09:20:59 +02:00
Ondřej Hruška
83bda6c1a8 Merge commit 'b9f59ebcc68e9da0a7158741a1a2ef3564e1321e' into merging-upstream 2017-09-28 09:18:35 +02:00
Ondřej Hruška
fcf0d2078e Skip-merge commit '0401a24558294b6941c30c922af3f2063dfd305e' into merging-upstream 2017-09-28 09:12:47 +02:00
Ondřej Hruška
9330ea1f4d Merge commit '4aea3f88a6d30f102a79c2da7fcfac96465ba1a8' into merging-upstream 2017-09-28 09:12:17 +02:00
Ondřej Hruška
06e299cef5 Revert "autoscroll emoji suggestions box"
This reverts commit c89cce0219.
2017-09-28 08:56:32 +02:00
Ondřej Hruška
8a24ad5828 Revert "Merge pull request #149 from glitch-soc/mojon-suggest"
This reverts commit fa3587645d, reversing
changes made to 15c9c2fd7e.
2017-09-28 08:55:53 +02:00
kibigo!
f7ca205f38 Fixed webpack config code 2017-09-25 19:35:54 -07:00
kibigo!
86e617a839 Better themeing support!! 2017-09-25 19:24:32 -07:00
Ondřej Hruška
210e6776fc Fix intl warning with disabled secondary toot btn
- Moved building the secondary btn text into the if to avoid trying to translate 'none' visibility
2017-09-23 23:41:25 +02:00
Ondřej Hruška
1924a71b5a Make secondary toot button work nicer with file attachments & revert to the original behavior if it's disabled 2017-09-23 23:38:24 +02:00
Ondřej Hruška
67f8277526 Add secondary toot button (opt-in) (#153)
Add secondary toot button + other toot button enhancements. 
Squashing so it's easy to revert if needed.
2017-09-23 23:11:02 +02:00
Ondřej Hruška
169d83f532 Remove "extra clickable spaces" made obsolete by status re-design 2017-09-23 00:33:35 +02:00
Ondřej Hruška
31dd261375 applied blackle's fix for gallery offset with full-width media 2017-09-22 21:47:14 +02:00
kibigo!
c2b479efec Long display names now ellipsis properly 2017-09-21 16:55:32 -07:00
kibigo!
a94dc21c79 Styling fixes for header redesign 2017-09-21 16:46:18 -07:00
David Yip
9512db920c Merge pull request #148 from glitch-soc/better-header
Improvements to status headers and content
2017-09-21 16:11:03 -05:00
Ondřej Hruška
c89cce0219 autoscroll emoji suggestions box 2017-09-21 15:31:24 +02:00
beatrix
fa3587645d Merge pull request #149 from glitch-soc/mojon-suggest
Emoji and Hashtag autocomplete
2017-09-20 23:02:23 -04:00
kibigo!
9ed51cecd0 Fixed notification appearance 2017-09-20 14:52:01 -07:00
Ondřej Hruška
514edd3c23 fulltext mojo suggestions 2017-09-20 22:13:09 +02:00
Ondřej Hruška
a3760b7729 TURBO shortcode search 2017-09-20 21:57:33 +02:00
Ondřej Hruška
cbf00168f1 add scrollbar to mojon suggestions list when too long 2017-09-20 21:53:08 +02:00
Ondřej Hruška
4f9a493d9d cache allShortcodes 2017-09-20 21:39:22 +02:00
Ondřej Hruška
8c0733a14e typo in comment 2017-09-20 21:28:44 +02:00
Ondřej Hruška
36a35be2ad Appease Travis 2017-09-20 20:34:11 +02:00
Ondřej Hruška
3783062450 Emoji and Hashtag autocomplete 2017-09-20 20:21:09 +02:00
kibigo!
15c9c2fd7e Pinned toots; embeds 2017-09-20 02:50:53 -07:00
kibigo!
227dbb6adb Added GIF hover-to-play support 2017-09-20 02:19:36 -07:00
kibigo!
769f62d96f Improvements to status headers 2017-09-20 01:57:08 -07:00
kibigo!
003bfd094e Fixed disabled boost SVG (#106) 2017-09-20 00:40:54 -07:00
kibigo!
fae8dce738 Fixed #133 (only parse links in status text) 2017-09-20 00:20:11 -07:00
Surinna Curtis
b0487488a7 Re-center hidden media text vertical 2017-09-16 11:44:31 -05:00
David Yip
f5d6bdd9c0 Merge pull request #147 from yipdw/sync/upstream
Fix error when following locked accounts (#4896)
2017-09-16 11:37:53 -05:00
Eugen Rochko
ad8e856a5b Fix error when following locked accounts (#4896)
(cherry picked from commit 0ef9d45d05)
2017-09-16 11:10:33 -05:00
Surinna Curtis
7ebd6ed03c Apply white-space: nowrap to account relationships icons 2017-09-14 07:37:39 -05:00
Surinna Curtis
59936b7a98 Tweak title text for mute notifications toggle to be clearer 2017-09-13 22:54:14 -05:00
Surinna Curtis
fd9a171129 fix typos in the migration 2017-09-13 22:35:48 -05:00
Surinna Curtis
6ba35630bc Add migration to default Mute#hide_notifications? to true 2017-09-13 22:26:43 -05:00
Surinna Curtis
79d8911116 Fixed a typo that was breaking the account mute API endpoint 2017-09-13 21:47:30 -05:00
Surinna Curtis
d880b3182b minor code style fixes oops 2017-09-13 21:47:30 -05:00
Surinna Curtis
f9d7b8a94f Refactor handling of default params for muting to make code cleaner 2017-09-13 21:47:30 -05:00
Surinna Curtis
211f0a9513 add an explanatory comment to AccountInteractions 2017-09-13 21:47:30 -05:00
Surinna Curtis
4a527154b7 fix a missing import 2017-09-13 21:47:30 -05:00
Surinna Curtis
df71eadaae In probably dead code, replace a dispatch of muteAccount that was skipping the modal with launching the mute modal. 2017-09-13 21:47:30 -05:00
Surinna Curtis
323d437a09 satisfy eslint 2017-09-13 21:47:30 -05:00
Surinna Curtis
3278c08c29 make the hide/unhide notifications buttons work 2017-09-13 21:47:30 -05:00
Surinna Curtis
0284fd723b Allow modifying the hide_notifications of a mute with the /api/v1/accounts/:id/mute endpoint 2017-09-13 21:47:30 -05:00
Surinna Curtis
0e0703dbd8 Show whether muted users' notifications are muted in account lists 2017-09-13 21:47:30 -05:00
Surinna Curtis
7dbcc7ed3d Expose whether a mute hides notifications in the api/v1/relationships endpoint 2017-09-13 21:47:30 -05:00
Surinna Curtis
83b3a0389c Add more specs for the /api/v1/mutes/details endpoint 2017-09-13 21:47:30 -05:00
Surinna Curtis
af2d793398 Define a serializer for /api/v1/mutes/details 2017-09-13 21:47:30 -05:00
Surinna Curtis
70592cdaba Add a /api/v1/mutes/details route that just returns the array of mutes. 2017-09-13 21:47:30 -05:00
Surinna Curtis
30b5254a5d Put the label for the hide notifications checkbox in a label element. 2017-09-13 21:47:30 -05:00
Surinna Curtis
0a207be99d add trailing newlines to files for Pork :) 2017-09-13 21:47:30 -05:00
Surinna Curtis
500c465226 specs for MuteService notifications params 2017-09-13 21:47:30 -05:00
Surinna Curtis
2ea9b164d3 Satisfy eslint. 2017-09-13 21:47:30 -05:00
Surinna Curtis
b1576c52df Convert profile header mute to use mute modal 2017-09-13 21:47:30 -05:00
Surinna Curtis
4612f7caea Break out a separate mute modal with a hide-notifications checkbox. 2017-09-13 21:47:30 -05:00
Surinna Curtis
0c547faf92 Less gross passing of notifications flag 2017-09-13 21:47:30 -05:00
Surinna Curtis
eaaf2170fe API support for muting notifications (and specs) 2017-09-13 21:47:30 -05:00
Surinna Curtis
6f7d00bfdd Add support for muting notifications in MuteService 2017-09-13 21:47:30 -05:00
Surinna Curtis
5c2e1869f0 specs testing that hide_notifications in mutes actually hides notifications 2017-09-13 21:47:30 -05:00
Surinna Curtis
0f2af2a974 Add specs for how mute! interacts with muting_notifications? 2017-09-13 21:47:30 -05:00
Surinna Curtis
27f8d7069b block notifications in notify_service from hard muted accounts 2017-09-13 21:47:30 -05:00
Surinna Curtis
44207b6af6 Add muting_notifications? and a notifications argument to mute! 2017-09-13 21:47:30 -05:00
Surinna Curtis
27e55da853 Add a hide_notifications column to mutes 2017-09-13 21:47:30 -05:00
Surinna Curtis
3cac5bc2c3 Fix a spuriously failing spec that assumed we required short bios like upstream 2017-09-10 19:43:52 -05:00
Surinna Curtis
29c44fa5fa Remove unused imports 2017-09-10 16:00:26 -05:00
Surinna Curtis
7a9c7d4e0b Merge pull request #143 from yipdw/sync/upstream
Merge with upstream's 1.6.0
2017-09-10 14:10:30 -05:00
David Yip
932571fa22 Merge tag 'v1.6.0' into sync/upstream 2017-09-10 13:04:27 -05:00
beatrix
c9df53044a Merge pull request #142 from glitch-soc/sync/upstream-1.6.0rc4
Merge with 1.6.0rc4

STRAP IN BUCKAWOO HERE WE GO AGAIN
2017-09-10 13:34:14 -04:00
David Yip
67ad453373 Merge tag 'v1.6.0rc4' into sync/upstream-1.6.0rc4
Conflicts:
      app/javascript/mastodon/features/getting_started/index.js
      app/javascript/packs/public.js
      app/javascript/styles/components.scss
2017-09-09 23:56:21 -05:00
beatrix
3dff74eecf Merge pull request #141 from yipdw/sync/upstream
Sync with upstream @ v1.6.0rc3

ohhhhhhhhhhh heck here we go
2017-09-09 20:11:48 -04:00
David Yip
14e1fb8d36 Add missing import (patch by @MightyPork) 2017-09-09 14:28:20 -05:00
David Yip
514fc908a3 Merge tag 'v1.6.0rc3' into sync/upstream 2017-09-09 14:28:08 -05:00
David Yip
b9f7bc149b Merge branch 'origin/master' into sync/upstream
Conflicts:
	app/javascript/mastodon/components/status_list.js
	app/javascript/mastodon/features/notifications/index.js
	app/javascript/mastodon/features/ui/components/modal_root.js
	app/javascript/mastodon/features/ui/components/onboarding_modal.js
	app/javascript/mastodon/features/ui/index.js
	app/javascript/styles/about.scss
	app/javascript/styles/accounts.scss
	app/javascript/styles/components.scss
	app/presenters/instance_presenter.rb
	app/services/post_status_service.rb
	app/services/reblog_service.rb
	app/views/about/more.html.haml
	app/views/about/show.html.haml
	app/views/accounts/_header.html.haml
	config/webpack/loaders/babel.js
	spec/controllers/api/v1/accounts/credentials_controller_spec.rb
2017-09-09 14:27:47 -05:00
aschmitz
e18ed4bbc7 Fix StatusLengthValidator tests
This fixes the StatusLengthValidator tests to use
StatusLengthValidator::MAX_CHARS to derive the length of the strings
tested, and so makes them resilient to changes in MAX_CHARS (such as the
one that made these tests fail, with it being changed from 500 to 512).

This seems a bit odd for some types of tests, but appears to make sense
here, where the testing goes beyond `status.length > MAX_CHARS`.
2017-09-02 11:37:40 -05:00
Surinna Curtis
667df47168 Remove workaround for fixed bug in SettingToggle
SettingToggle was toggling itself in response to keydown of space, and then the keyup was doing it again
2017-08-31 22:41:49 -05:00
Surinna Curtis
173a970752 UploadArea should only preventDefault for Escape
This will make accessibility for some things less effortful, since we won't have to define a prior event handler to do whatever should be happening by default.
2017-08-31 22:32:03 -05:00
Ondřej Hruška
cb42dd8497 Fix onboarding modal & add re-open button (#129)
* Added a button to open the onboarding modal

* Fixed onboarding modal

* added a show-me-around getting-started menu button
2017-08-08 21:42:29 +02:00
Ondřej Hruška
4dc0ddc601 fix regression - collapse button not working 2017-08-07 22:46:24 +02:00
Ondřej Hruška
7a1ca8b0df Merge remote-tracking branch 'upstream/master' 2017-08-07 22:43:52 +02:00
Ondřej Hruška
b8791ae79b always put @handles on a new line. undo some needless changes from prev cmmt 2017-08-07 21:59:49 +02:00
Ondřej Hruška
b9a2ceca35 removed ellipsis from .display-name 2017-08-07 21:24:19 +02:00
kibigo!
70c5eccc12 Compatibility regex for user profiles 2017-08-06 15:10:06 -07:00
Ondřej Hruška
eb7fc34708 Merge pull request #124 from glitch-soc/data-avatar-of
all checks have failed woooooo \*merges*
2017-08-06 21:49:26 +02:00
Ondřej Hruška
b6bf04ece2 Added data-column="{account|local|federated|favourites|hashtag|home|notifications|mutes|blocks|getting-started|follow-requests}" to Column 2017-08-06 21:27:47 +02:00
Ondřej Hruška
91836d577e Add data-avatar-of="@..." to all user avatars 2017-08-06 21:24:13 +02:00
Ondřej Hruška
7de0fa698d Updated glitch files to use the new Avatar class correctly 2017-08-06 21:23:59 +02:00
Ondřej Hruška
811d895f7b Merged upstream PR #4526 2017-08-06 21:23:36 +02:00
Ondřej Hruška
7b42d14f45 fix bug with data attrib for boost in home TL 2017-08-04 22:38:58 +02:00
Ondřej Hruška
f34f33c19e Add data- attributes to statuses for userstyle selectors (#117)
* Add data- attributes to statuses for userstyle selectors

* use const and template string, replace reblog->boosted and favourite->favourited

* more template strings because sorin-sama said so
2017-08-04 22:11:46 +02:00
kibigo!
8b58153583 Fixed onClick column links 2017-08-01 13:46:52 -07:00
kibigo!
8150689b48 Merge upstream (#111) 2017-08-01 13:20:29 -07:00
Gô Shoemake
b61e3daf98 Multiple frontend support (#110)
* Initial multiple frontend support

* Removed unnecessary require()

* Moved styles/images out of common
2017-07-30 19:28:21 +02:00
Ondřej Hruška
6ff084dbbb Improved notifications cleaning UI with set operations (#109)
* added notification cleaning drawer

* bugfix

* fully implemented set operations for notif cleaning

* i18n for notif cleaning drawer & improved logic slightly. Also added a confirm dialog

* - notif dismiss "overlay" now shoves the notif aside to avoid overlap
- added focus ring to header buttons
- removed notif overlay entirely from DOM if mode is disabled

* removed comment

* CSS tuning - inconsistent division lines fix
2017-07-30 12:36:28 -04:00
David Yip
9aaf3218d2 Add commit_hash to instance presenter double (#107)
glitch-soc's about page grabs said value from InstancePresenter; the
double needs to emulate that.
2017-07-28 19:25:30 -04:00
Ondřej Hruška
cb69e35b3b Add visibility icon to Detailed status 2017-07-27 00:41:28 +02:00
Ondřej Hruška
e82021e0e6 Merge pull request #97 from glitch-soc/LESS_UGLY_LANDING_PAGE_TEXT_CSS
Landing page CSS fixes
2017-07-25 23:32:59 +02:00
Ondřej Hruška
8925731c98 Improve landing page CSS and text contrast 2017-07-25 23:30:31 +02:00
Ondřej Hruška
4c233b4f3a Add .fa-external-link to instance list link 2017-07-25 23:30:13 +02:00
Ondřej Hruška
b7cf758fbe Fix JS errors and add back commit hash 2017-07-25 21:54:12 +02:00
Ondřej Hruška
7e5691804d Merge git://github.com/tootsuite/mastodon into tootsuite-master 2017-07-25 21:36:22 +02:00
Ondřej Hruška
852acbd738 amend 3ba7c1e7 to actually do what I wanted to do *facepalm* 2017-07-22 22:32:03 +02:00
Ondřej Hruška
6913426e48 Merge pull request #92 from tootsuite/master
tiny upstream merge
2017-07-22 22:20:17 +02:00
Ondřej Hruška
3ba7c1e725 Textarea does not auto-expand on mobile anymore (autofocus only on desktop) 2017-07-22 21:48:56 +02:00
Ondřej Hruška
9b74a12045 Adjust margins for mobile with navbar-under 2017-07-22 20:41:21 +02:00
Ondřej Hruška
74a0cc6a11 Added settings toggle to move the navbar at the bottom in mobile view (#93) 2017-07-22 19:51:34 +02:00
beatrix-bitrot
984d2d4cb6 Merge that good fresh upstream shit 2017-07-22 01:16:53 +00:00
Ondřej Hruška
0244019ca1 Fixed horrible outline around notif clearing checkbox & moved the overlay to a more sr-friendly place 2017-07-21 21:12:43 +02:00
Ondřej Hruška
604654ccb4 New notification cleaning mode (#89)
This PR adds a new notification cleaning mode, super perfectly tuned for accessibility, and removes the previous notification cleaning functionality as it's now redundant.

* w.i.p. notif clearing mode

* Better CSS for selected notification and shorter text if Stretch is off

* wip for rebase ~

* all working in notif clearing mode, except the actual removal

* bulk delete route for piggo

* cleaning + refactor. endpoint gives 422 for some reason

* formatting

* use the right route

* fix broken destroy_multiple

* load more notifs after succ cleaning

* satisfy eslint

* Removed CSS for the old notif delete button

* Tabindex=0 is mandatory

In order to make it possible to tab to this element you must have tab index = 0. Removing this violates WCAG and makes it impossible to use the interface without good eyesight and a mouse. So nobody with certain mobility impairments, vision impairments, or brain injuries would be able to use this feature if you don't have tabindex=0

* Corrected aria-label

Previous label implied a different behavior from what actually happens

* aria role localization & made the overlay behave like a checkbox

* checkboxes css and better contrast

* color tuning for the notif overlay

* fanceh checkboxes etc and nice backgrounds

* SHUT UP TRAVIS
2017-07-21 20:33:16 +02:00
beatrix
0efd7e7406 disable about page timeline 2017-07-20 17:26:00 -04:00
beatrix
e7edb4d1ee Merge pull request #87 from tootsuite/master
merge upstream
2017-07-20 11:24:32 -04:00
kibigo!
d235224692 Restructured local settings internals 2017-07-19 20:41:28 -07:00
kibigo!
0a678cf377 Fix for stylesheet split 2017-07-18 11:28:52 -07:00
Surinna Curtis
7a77f7b3bb Add sourceRoot/includePaths to loaders
Use the settings modal as an example/testcase
2017-07-18 11:27:48 -07:00
Ondřej Hruška
df74e26baf Merge branch 'tootsuite-master' 2017-07-18 18:59:00 +02:00
Ondřej Hruška
d69fa9e1f4 Merge changes from upstream with the CSS reload fix 2017-07-18 18:58:47 +02:00
Ondřej Hruška
c727eae441 Updated from tootsuite 2017-07-17 20:03:57 +02:00
kibigo!
d0aad1ac85 Documentation and cleanup 2017-07-16 17:13:16 -07:00
kibigo!
21b04af524 Fixes lack of <tbody> in header metadata table 2017-07-16 16:49:12 -07:00
Ondřej Hruška
3ea02314b9 split added glitch locales from vanilla (#82)
* Locale script now accepts overrides and new keys from glitch/locales

* Revert glitchsoc changes to mastodon/locales to prevent future merge conflicts
2017-07-16 01:15:25 +02:00
kibigo!
4715161a93 FIXED STUFF FROM THE MERGE SORRY ;_; 2017-07-15 15:42:39 -07:00
kibigo!
144db8ea1d poke and visible docs url 2017-07-15 15:34:46 -07:00
kibigo!
bc4202d00b Ported updates from #64 2017-07-15 15:10:06 -07:00
kibigo!
09cfc079b0 Merge upstream (#81) 2017-07-15 14:33:15 -07:00
Ondřej Hruška
08d021916d Fixed issue #72 - bad css in report dialog 2017-07-15 16:45:39 +02:00
Ondřej Hruška
99f24ab0c7 Raise search results count to 10 for test
reference: https://mastodon.xyz/users/lx/updates/278054
2017-07-15 15:42:24 +02:00
Ondřej Hruška
3a526e2369 Fix broken letterboxing in media previews 2017-07-15 15:38:18 +02:00
Gô Shoemake
51e3ac2534 Added link to docs website 2017-07-14 09:42:50 -07:00
Ondřej Hruška
75aafc932e Added buttons and menu items to dismiss individual notifications (#76)
* Added DELETE verb for notifications

* Added notification dismiss button to status dropdown

* Added reveal-on-hover notif dismiss button, added FollowNotification component
2017-07-14 11:03:43 -04:00
kibigo!
6ce806f913 Fixed faulty import on notifs 2017-07-13 03:36:12 -07:00
kibigo!
35fda84ba8 Documentation pt. I 2017-07-13 03:26:08 -07:00
kibigo!
5770d461b2 Moved glitch containers and commented unused files 2017-07-13 02:40:16 -07:00
kibigo!
2e0645c26c Updated readme and contrib docs 2017-07-12 23:55:55 -07:00
Ondřej Hruška
66b1174d25 Fix CW auto-expanding if collapsed toots are disabled 2017-07-12 19:52:36 +02:00
kibigo!
183f993b01 Linting fixes 2017-07-12 02:36:40 -07:00
Surinna Curtis
e53fbb4a09 local-only/compose advanced options tweaks.
Squashed commit of the following:

commit b9877e37f72fdd8134936e1014033b07cb6c3671
Author: Surinna Curtis <ekiru.0@gmail.com>
Date:   Wed Jul 12 00:50:10 2017 -0500

    account for the eye in the chars left count for local-only toots

commit 56ebfa96542e16daa1986cc45e07974801ee12dc
Author: Surinna Curtis <ekiru.0@gmail.com>
Date:   Wed Jul 12 00:21:02 2017 -0500

    factor out an AdvancedOptionsToggle to avoid unnecessary re-renders

commit 04cec44ab8744e4e0f52da488c9ec24b1b1422ef
Author: Surinna Curtis <ekiru.0@gmail.com>
Date:   Wed Jul 12 00:20:24 2017 -0500

    s/changeComposeAdvancedOption/toggleComposeAdvancedOption/g

commit af5815dee750d1aa8b797a9305e5ab3ce6774e3f
Author: Surinna Curtis <ekiru.0@gmail.com>
Date:   Tue Jul 11 23:55:19 2017 -0500

    clicking anywhere on the whole advanced option toggles
2017-07-12 02:14:17 -07:00
kibigo!
79d898ae0a Merge upstream!! #64 <3 <3 2017-07-12 02:03:17 -07:00
kibigo!
bcf7ee48e9 Importing vanillin from upstream ;) ;) 2017-07-12 01:10:17 -07:00
kibigo!
297921fce5 Moved glitch files to their own location ;) 2017-07-12 01:02:51 -07:00
DJ Sundog
74eff5456c First lame pass at adding optional git commit hash display on /about/… (#67)
* First lame pass at adding optional git commit hash display on /about/more page.

Currently, this is implemented by checking for the existence of a file called CURRENT_RELEASE in the home directory of the user running Mastodon. If the file exists, its contents are added.

I've modified my update process to include the following before precompiling assets:

git log -1 | head -n 1 | cut -d " " -f2 > ~/CURRENT_RELEASE

That puts the current commit hash into the file ~/CURRENT_RELEASE, but you figured that out because you're a smart cookie.

As I am quite sure this is a horrible methodology for implementing this, I look forward to any improvements you have to offer!

* Updated to handle instances that share a user - the CURRENT_RELEASE file now lives in the instance's base directory.

This also requires modifying the update hook to `git log -1 | head -n 1 | cut -d " " -f2 > CURRENT_RELEASE`
2017-07-11 20:32:16 -04:00
Ondřej Hruška
60d27b4302 Fixed #66 disabling collapse collapses all toots 2017-07-11 20:48:31 +02:00
Ondřej Hruška
08d19778d5 Fix extra clickable spaces for narrow screen 2017-07-11 17:53:50 +02:00
Ondřej Hruška
9f7a5aac1e Toot context menu is now centered 2017-07-10 10:21:20 -04:00
Ondřej Hruška
945c5812d3 Added extra clickable area in status gutter 2017-07-10 09:29:20 -04:00
Ondřej Hruška
667b567606 Make avatar in compose navbar hover-to-play 2017-07-10 10:26:46 +02:00
Ondřej Hruška
8e2b1f79e4 Small indent fix in components.scss 2017-07-09 14:00:13 +02:00
Ondřej Hruška
345290a905 Avatar in the detail column no longer autoplays 2017-07-09 07:54:33 -04:00
Ondřej Hruška
2fb78fefc6 Fix fullwidth media CSS bugs with NSFW video, and bad spoiler margin on static pages (#60)
* Fix fullwidth style not applied to NSFW video correctly

* Fix botched video .media-spoiler margin on static pages
2017-07-09 09:07:14 +02:00
Ondřej Hruška
dc2b8bdecd Added a toggle for full-width media previews 2017-07-08 06:46:12 -04:00
Ondřej Hruška
e3c2183c12 New design for visibility icons 2017-07-07 23:07:16 -04:00
kibigo!
86f8df7903 Fixed avis on static pages 2017-07-07 15:56:05 -07:00
Ondřej Hruška
d41cec90cf Added toot visibility icons and removed Boost btn changing icon 2017-07-07 06:31:11 -04:00
Ondřej Hruška
7859e6ad45 Fix back button to never go to a different website using history len check 2017-07-07 05:59:15 -04:00
Surinna Curtis
3464bb30f8 replies to local-only toots default to local-only, and fix some regex bugs 2017-07-06 22:30:51 -05:00
Ondřej Hruška
d87d70e89a Fixed js type error in advanced_options_dropdown.js 2017-07-06 14:46:45 -07:00
kibigo!
0c7ee5c792 Fixed non-status notification styling 2017-07-05 19:26:19 -07:00
kibigo!
bba75c15f1 Statuses redux!
- Better unified reblogs, statuses, and notifications
- Polished up collapsed toots greatly
- Apologies to bea if this makes everything more difficult
2017-07-05 18:51:23 -07:00
Ondřej Hruška
4cbbea5881 Improved CSS for drawer to restore original looks (full height) 2017-07-05 07:21:36 -04:00
Ondřej Hruška
167c392efd Fix drawer clipping dropdowns 2017-07-05 07:21:36 -04:00
Surinna Curtis
193f354d3e a real default for advanced options long description 2017-07-05 00:09:20 -05:00
Surinna Curtis
6b67b91eb1 satisfy eslint 2017-07-04 21:33:53 -07:00
Surinna Curtis
6b77424660 some adjustments to open/active for advanced options dropdown 2017-07-04 21:33:53 -07:00
Surinna Curtis
301c185878 highlight … button if any options enabled 2017-07-04 21:33:53 -07:00
Surinna Curtis
cb7f54891f Revert "change active/hover display on advanced options"
This reverts commit ade773cb0a.
2017-07-04 21:33:53 -07:00
Surinna Curtis
f6ce1a9592 toggles for advanced options 2017-07-04 21:33:53 -07:00
Surinna Curtis
aee64b996c change active/hover display on advanced options 2017-07-04 21:33:53 -07:00
Surinna Curtis
0c71c0ccc8 reset advanced options when appropriate 2017-07-04 21:33:53 -07:00
Surinna Curtis
49e82c1e0f add an eye when submitting a toot with do_not_federate enabled 2017-07-04 21:33:53 -07:00
Surinna Curtis
556cede00f Local-only option and dropdown all working 2017-07-04 21:33:53 -07:00
Surinna Curtis
b73ee36949 Reduce advanced options dropdown width 2017-07-04 21:33:53 -07:00
Surinna Curtis
dd49c10cdb Further improvements to dropdown html 2017-07-04 21:33:53 -07:00
Surinna Curtis
85d5249479 The beginnings of an advanced options dropdown 2017-07-04 21:33:53 -07:00
Surinna Curtis
ff9f2088f7 Move layout override into app settings modal
Squashed commit of the following:

commit 3842f879865818a3299f8283f8ed1b43c5566500
Author: Surinna Curtis <ekiru.0@gmail.com>
Date:   Mon Jul 3 19:57:39 2017 -0500

    Fix some style issues

commit 08628a0234392ecac90e869a1272f429de0b6db2
Author: Surinna Curtis <ekiru.0@gmail.com>
Date:   Mon Jul 3 19:35:22 2017 -0500

    Improved styling and layout of select app settings

commit 1787a7c20f2bf7101c6d6830450564178314a737
Author: Surinna Curtis <ekiru.0@gmail.com>
Date:   Mon Jul 3 17:13:53 2017 -0500

    remove the layout selector ui from the drawer

commit 7d93b180b1e615e2c36210ad6f119fe80a2778d8
Author: Surinna Curtis <ekiru.0@gmail.com>
Date:   Mon Jul 3 17:01:51 2017 -0500

    Add layout setting to app settings modal
2017-07-03 21:51:00 -05:00
adbelle
15227c713d Remove color lightening for search results section
This will cause it to match the shading of the container behind it.
2017-06-30 18:38:36 -04:00
adbelle
30736f4886 Add opaque background to search results section 2017-06-30 18:38:36 -04:00
kibigo!
c58877862d createBio function added 2017-06-30 15:03:31 -07:00
beatrix
0e310f1ee3 put travis thing in readme.md 2017-06-30 12:36:10 -04:00
beatrix-bitrot
7dd4d9de96 try fix failing controller spec caused by long bios 2017-06-30 16:27:52 +00:00
kibigo!
46f83bb28b Styling fixes to media spoilers 2017-06-30 02:56:19 -07:00
kibigo!
ec2daae71c Media display improvements
- built in fullwidth styling
 - letterbox settings toggle
 - media no longer counts towards height when making toot-collapsing
measurements
2017-06-30 02:18:25 -07:00
kibigo!
b525caf40a Fixing an aspect of #32 which had previously escaped my attention 2017-06-29 23:39:57 -07:00
kibigo!
fc65b691df Images now behind CWs on static pages 2017-06-29 22:59:28 -07:00
kibigo!
651c3d643c Images behind CWs in detailed statuses too 2017-06-29 22:48:22 -07:00
kibigo!
cc4cba8afd Improvements to image icon appearance on SHOW MORE 2017-06-29 01:43:15 -07:00
kibigo!
99889ea57d Tiny Status improvements
- Media toots are no longer auto-collapsed if the media is behind a CW
- Display names no longer appear clickable when a toot is collapsed
- Fixed #36 by adding reduplicating the media icon inside the SHOW
MORE/LESS
2017-06-28 23:23:05 -07:00
kibigo!
19690d3e33 Fixes #32 bug with revealing sensitive media 2017-06-28 22:57:30 -07:00
kibigo!
0b371da971 Fixed improper centering of columns-area 2017-06-28 22:18:22 -07:00
kibigo!
2d8ebdcc72 Initial store for local settings is now Immutable all the way down 2017-06-28 22:11:21 -07:00
kibigo!
595c6de32c Added App Setttings Modal 2017-06-28 22:00:54 -07:00
kibigo!
6cbbdc805f Fixed Regexp.new syntax 2017-06-28 15:16:13 -07:00
kibigo!
7b1d233f4f Linear gradient no longer extends under metadata on account pages 2017-06-28 01:10:28 -07:00
kibigo!
03f9648377 Improvements to static metadata styling, especially for mobile 2017-06-28 00:57:32 -07:00
kibigo!
6107e95404 Backend YAML Processing + Profile Metadata on Static Pages 2017-06-28 00:27:44 -07:00
kibigo!
36805a39db Moved reblog wrapper outside of <Status> 2017-06-27 18:34:30 -07:00
kibigo!
ab4632a41e Allow line-breaks in bio metadata 2017-06-27 16:20:35 -07:00
beatrix-bitrot
ddafde942c Merge remote-tracking branch 'upstream/master' 2017-06-27 20:46:13 +00:00
kibigo!
e6300de142 Profile metadata improvements 2017-06-27 05:48:26 -07:00
Surinna Curtis
a6f5111c79 Eyes with variation-selector-16 now also do local-only 2017-06-26 20:51:11 -05:00
kibigo!
59503a88ae Disable account linking on collapsed toots too 2017-06-26 15:41:09 -07:00
kibigo!
5df7bc3a8b Disable links on collapsed toots 2017-06-26 15:22:03 -07:00
kibigo!
c806fef865 Notifications collapsed by default 2017-06-26 14:28:43 -07:00
beatrix-bitrot
49ba78d6f8 fix account spec broken by change to 500char bios 2017-06-26 20:55:44 +00:00
Surinna Curtis
7b53d4bbca Bio length on client side mostly doesn't count metadata 2017-06-26 09:34:31 -05:00
Surinna Curtis
4f36aad6e8 don't count bio metadata against bio length on server 2017-06-26 09:34:31 -05:00
kibigo!
56ca33a6d3 Time needs to be just a little wider to accomodate minutes 2017-06-25 19:38:58 -07:00
kibigo!
aeff898137 We want wrapping here, sorry 😥 2017-06-25 19:26:59 -07:00
kibigo!
b323e00bf3 Merge branch 'master' of https://github.com/glitch-soc/mastodon 2017-06-25 19:19:52 -07:00
kibigo!
a520b118e4 Expand toot by clicking on body [fixed!] 2017-06-25 19:19:45 -07:00
beatrix-bitrot
93fc8aa14c keyword muting and local only tooting WIP 2017-06-26 02:19:21 +00:00
beatrix-bitrot
c0a665865e update bio length to 500 2017-06-26 02:18:52 +00:00
kibigo!
38a1299975 Stick images inside of CWs 2017-06-25 19:15:03 -07:00
kibigo!
96e1f75679 Fixes to overflows wrt drawer/column 2017-06-25 18:05:28 -07:00
kibigo!
3a99552f0c Fixed bad drawer custom.css 2017-06-25 17:55:48 -07:00
kibigo!
22cc5c0dec Improvements to action bar positioning 2017-06-25 17:35:27 -07:00
Surinna Curtis
efa425206c Move status action bar left
This also resolves the issues where sometimes the timestamps wrap and also sits unpleasantly close to the … button.
2017-06-25 18:24:50 -05:00
kibigo!
e60f27d649 Clicking on a collapsed toot just uncollapses it. 2017-06-25 16:09:32 -07:00
kibigo!
6a50e73089 Auto-uncollapse when expanding CW 2017-06-25 15:52:55 -07:00
kibigo!
b1f9892e63 [custom.scss] More media height fixes 2017-06-25 15:29:23 -07:00
kibigo!
d6e3918d92 Disables CW [Show More/Less] links in collapsed toots 2017-06-25 15:25:19 -07:00
kibigo!
6909bbdc9e [custom.scss] max-height of media now a fixed value 2017-06-25 14:44:52 -07:00
kibigo!
ddc6b85912 Color adjustment to media icons 2017-06-25 14:25:35 -07:00
kibigo!
4bc237fcfe Adds media icons to toots 2017-06-25 14:22:11 -07:00
kibigo!
efacfec3ed Media previews for collapsed toots 2017-06-25 13:57:52 -07:00
kibigo!
8ea779e59a Patching rebase errors 2017-06-25 12:51:31 -07:00
Shel Raphen
7eda83a36a Glitchsocification 2017-06-25 19:21:51 +00:00
kibigo!
af178d0ba6 Removed no-longer-necessary custom style 2017-06-24 21:28:30 -07:00
Gô Shoemake
e4326b3f12 Merge pull request #26 from ekiru/feature/manual-column-layout-setting
User-controlled multi-column/single-column overrides
2017-06-24 20:44:43 -07:00
kibigo!
b8a5052d53 Better style handling at small sizes 2017-06-24 20:36:19 -07:00
kibigo!
7427680e75 Allowed little media rules 2017-06-24 20:14:58 -07:00
kibigo!
ca0d30c04b OKAY THIS WORKS THIS WORKS 2017-06-24 20:04:46 -07:00
kibigo!
da05cde721 Better settings handling with localSettings (new!) 2017-06-24 19:56:37 -07:00
kibigo!
4c37f629bc Don't change layout of static pages 2017-06-24 18:30:59 -07:00
kibigo!
ddba5d3b8c Use Redux store to keep track of layout 2017-06-24 18:30:30 -07:00
Surinna Curtis
ceb545c080 Pass in correct "singleColumn" prop value when auto-columns is not used. 2017-06-24 15:29:46 -05:00
Surinna Curtis
a70468aa56 Support overriding media queries for deciding between single-column/multi-column layouts with a class 2017-06-24 15:29:46 -05:00
beatrix-bitrot
8b23bf7cbd clean up old avatar class 2017-06-24 03:51:01 +00:00
Matthew Walsh
f1a60d4b81 Unified avatar styling
Avatars now have consistent styling across all pages; border radius can be adjusted with a SASS variable ($ui-avatar-border-size)
2017-06-24 03:03:27 +00:00
kibigo!
2513d92c54 Un-hide dropdown menu ;P 2017-06-23 19:39:44 -07:00
kibigo!
414dfb3955 ESLint improvements for Profile Metadata 2017-06-23 18:43:30 -07:00
Gô Shoemake
67adbcc60c Reblog support for collapsed toots 2017-06-23 18:23:26 -07:00
beatrix-bitrot
453b9c6e7e missing punctuation 2017-06-23 22:01:04 +00:00
beatrix
d9b9bb8c5e glitch the getting started image 2017-06-23 21:50:45 +00:00
kibigo!
40ecbfd4a9 Very minor styling improvements to toot-collapsing 2017-06-23 21:50:45 +00:00
kibigo!
4fe45dda9a Updates height upon collapsing 2017-06-23 21:50:45 +00:00
kibigo!
4bd7482a7a Minor collapsing button improvements~ 2017-06-23 21:50:45 +00:00
kibigo!
93c52301ad Collapsable toots [1/??] 2017-06-23 21:50:45 +00:00
kibigo!
0d3ec19e89 Profile Metadata HACK 😈 2017-06-23 21:45:14 +00:00
Go Shoemake
62a75891ab Fixes drawer so stuff doesn't overflow 2017-06-23 21:45:14 +00:00
Charlotte Fields
b27842dc70 cybre cleanup 2017-06-23 21:45:14 +00:00
Chronister
39b6b37b74 cybrespace to 1.4.2 2017-06-23 21:45:14 +00:00
Chronister
65528fc54e All cybrespace changes through 5/28 2017-06-23 21:45:14 +00:00
Charlotte Fields
382572c213 adding cybre changes 2017-06-23 21:45:14 +00:00
beatrix-bitrot
9bc593d675 update local modifications for cors and cp 2017-06-23 21:45:14 +00:00
beatrix-bitrot
09f7ad3614 silly readme update to test automated deploys 2017-06-23 21:45:14 +00:00
beatrix-bitrot
7c2ea42cd5 update README.md 2017-06-23 21:45:14 +00:00
beatrix
ea785d0baf Update README.md 2017-06-23 21:45:14 +00:00
Beatrix Bitrot
a337c5dbe5 CORS tweaks 2017-06-23 21:45:14 +00:00
753 changed files with 12456 additions and 20663 deletions

View File

@@ -1,36 +1,21 @@
version: "2"
checks:
argument-count:
enabled: false
complex-logic:
enabled: false
file-lines:
enabled: false
method-complexity:
enabled: false
method-count:
enabled: false
method-lines:
enabled: false
nested-control-flow:
enabled: false
return-statements:
enabled: false
similar-code:
enabled: false
identical-code:
enabled: false
plugins:
engines:
brakeman:
enabled: true
bundler-audit:
enabled: true
duplication:
enabled: false
eslint:
enabled: true
rubocop:
enabled: true
scss-lint:
enabled: true
exclude_patterns:
ratings:
paths:
- "**.rb"
- "**.js"
- "**.scss"
exclude_paths:
- spec/
- vendor/asset

View File

@@ -35,17 +35,6 @@ PAPERCLIP_SECRET=$PAPERCLIP_SECRET
SECRET_KEY_BASE=$SECRET_KEY_BASE
OTP_SECRET=$OTP_SECRET
# VAPID keys (used for push notifications)
# You can generate the keys using the following command (first is the private key, second is the public one)
# You should only generate this once per instance. If you later decide to change it, all push subscription will
# be invalidated, requiring the users to access the website again to resubscribe.
#
# Generate with `rake mastodon:webpush:generate_vapid_key` task (`nanobox run bundle exec rake mastodon:webpush:generate_vapid_key`)
#
# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
VAPID_PRIVATE_KEY=$VAPID_PRIVATE_KEY
VAPID_PUBLIC_KEY=$VAPID_PUBLIC_KEY
# Registrations
# Single user mode will disable registrations and redirect frontpage to the first profile
# SINGLE_USER_MODE=true
@@ -73,7 +62,7 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
#SMTP_OPENSSL_VERIFY_MODE=peer
#SMTP_ENABLE_STARTTLS_AUTO=true
#SMTP_TLS=true
# Optional user upload path and URL (images, avatars). Default is :rails_root/public/system. If you set this variable, you are responsible for making your HTTP server (eg. nginx) serve these files.
# PAPERCLIP_ROOT_PATH=/var/lib/mastodon/public-system
@@ -102,23 +91,6 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
# S3_ENDPOINT=
# S3_SIGNATURE_VERSION=
# Swift (optional)
# SWIFT_ENABLED=true
# SWIFT_USERNAME=
# For Keystone V3, the value for SWIFT_TENANT should be the project name
# SWIFT_TENANT=
# SWIFT_PASSWORD=
# Keystone V2 and V3 URLs are supported. Use a V3 URL if possible to avoid
# issues with token rate-limiting during high load.
# SWIFT_AUTH_URL=
# SWIFT_CONTAINER=
# SWIFT_OBJECT_URL=
# SWIFT_REGION=
# Defaults to 'default'
# SWIFT_DOMAIN_NAME=
# Defaults to 60 seconds. Set to 0 to disable
# SWIFT_CACHE_TTL=
# Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
# S3_CLOUDFRONT_HOST=

View File

@@ -11,11 +11,10 @@ DB_PASS=
DB_PORT=5432
# Federation
# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
# Note: Changing LOCAL_DOMAIN or LOCAL_HTTPS at a later time will cause unwanted side effects.
# LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
LOCAL_DOMAIN=example.com
# Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links)
LOCAL_HTTPS=true
# Use this only if you need to run mastodon on a different domain than the one used for federation.
# You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md

View File

@@ -29,6 +29,11 @@ settings:
import/ignore:
- node_modules
- \\.(css|scss|json)$
import/resolver:
node:
moduleDirectory:
- node_modules
- app/javascript
rules:
brace-style: warn

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "app/javascript/themes/mastodon-go"]
path = app/javascript/themes/mastodon-go
url = https://github.com/marrus-sh/mastodon-go

View File

@@ -27,14 +27,11 @@ addons:
apt:
sources:
- trusty-media
- sourceline: deb https://dl.yarnpkg.com/debian/ stable main
key_url: https://dl.yarnpkg.com/debian/pubkey.gpg
packages:
- ffmpeg
- libicu-dev
- libprotobuf-dev
- protobuf-compiler
- yarn
- libicu-dev
rvm:
- 2.3.4
@@ -45,6 +42,7 @@ services:
install:
- nvm install
- npm install -g yarn
- bundle install --path=vendor/bundle --without development production --retry=3 --jobs=16
- yarn install

View File

@@ -1,46 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at eugen@zeonfederated.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -1,3 +1,36 @@
# Contributing to Mastodon Glitch Edition #
Thank you for your interest in contributing to the `glitch-soc` project!
Here are some guidelines, and ways you can help.
> (This document is a bit of a work-in-progress, so please bear with us.
> If you don't see what you're looking for here, please don't hesitate to reach out!)
## Planning ##
Right now a lot of the planning for this project takes place in our development Discord, or through GitHub Issues and Projects.
We're working on ways to improve the planning structure and better solicit feedback, and if you feel like you can help in this respect, feel free to give us a holler.
## Documentation ##
The documentation for this repository is available at [`glitch-soc/docs`](https://github.com/glitch-soc/docs) (online at [glitch-soc.github.io/docs/](https://glitch-soc.github.io/docs/)).
Right now, we've mostly focused on the features that make this fork different from upstream in some manner.
Adding screenshots, improving descriptions, and so forth are all ways to help contribute to the project even if you don't know any code.
## Frontend Development ##
Check out [the documentation here](https://glitch-soc.github.io/docs/contributing/frontend/) for more information.
## Backend Development ##
See the guidelines below.
- - -
You should also try to follow the guidelines set out in the original `CONTRIBUTING.md` from `tootsuite/mastodon`, reproduced below.
<blockquote>
CONTRIBUTING
============
@@ -49,3 +82,5 @@ It is expected that you have a working development environment set up (see back-
* If you are introducing new strings, they must be using localization methods
If the JavaScript or CSS assets won't compile due to a syntax error, it's a good sign that the pull request isn't ready for submission yet.
</blockquote>

View File

@@ -7,8 +7,8 @@ ENV UID=991 GID=991 \
RAILS_SERVE_STATIC_FILES=true \
RAILS_ENV=production NODE_ENV=production
ARG YARN_VERSION=1.3.2
ARG YARN_DOWNLOAD_SHA256=6cfe82e530ef0837212f13e45c1565ba53f5199eec2527b85ecbcd88bf26821d
ARG YARN_VERSION=1.1.0
ARG YARN_DOWNLOAD_SHA256=171c1f9ee93c488c0d774ac6e9c72649047c3f896277d88d0f805266519430f3
ARG LIBICONV_VERSION=1.15
ARG LIBICONV_DOWNLOAD_SHA256=ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178
@@ -48,7 +48,7 @@ RUN apk -U upgrade \
&& rm yarn.tar.gz \
&& mv /tmp/src/yarn-v$YARN_VERSION /opt/yarn \
&& ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \
&& wget -O libiconv.tar.gz "https://ftp.gnu.org/pub/gnu/libiconv/libiconv-$LIBICONV_VERSION.tar.gz" \
&& wget -O libiconv.tar.gz "http://ftp.gnu.org/pub/gnu/libiconv/libiconv-$LIBICONV_VERSION.tar.gz" \
&& echo "$LIBICONV_DOWNLOAD_SHA256 *libiconv.tar.gz" | sha256sum -c - \
&& tar -xzf libiconv.tar.gz -C /tmp/src \
&& rm libiconv.tar.gz \

36
Gemfile
View File

@@ -14,10 +14,8 @@ gem 'pg', '~> 0.20'
gem 'pghero', '~> 1.7'
gem 'dotenv-rails', '~> 2.2'
gem 'aws-sdk', '~> 2.10', require: false
gem 'fog-core', '~> 1.45'
gem 'fog-local', '~> 0.4', require: false
gem 'fog-openstack', '~> 0.1', require: false
gem 'aws-sdk', '~> 2.9'
gem 'fog-openstack', '~> 0.1'
gem 'paperclip', '~> 5.1'
gem 'paperclip-av-transcoder', '~> 0.6'
@@ -28,7 +26,7 @@ gem 'browser'
gem 'charlock_holmes', '~> 0.7.5'
gem 'iso-639'
gem 'cld3', '~> 3.2.0'
gem 'devise', '~> 4.3'
gem 'devise', '~> 4.2'
gem 'devise-two-factor', '~> 3.0'
gem 'doorkeeper', '~> 4.2'
gem 'fast_blank', '~> 1.0'
@@ -40,15 +38,16 @@ gem 'http', '~> 2.2'
gem 'http_accept_language', '~> 2.1'
gem 'httplog', '~> 0.99'
gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.1'
gem 'kaminari', '~> 1.0'
gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.1'
gem 'nokogiri', '~> 1.8'
gem 'nokogiri', '~> 1.7'
gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.3'
gem 'oj', '~> 3.0'
gem 'ostatus2', '~> 2.0'
gem 'ox', '~> 2.8'
gem 'ox', '~> 2.5'
gem 'pundit', '~> 1.1'
gem 'rabl', '~> 0.13'
gem 'rack-attack', '~> 5.0'
gem 'rack-cors', '~> 0.4', require: 'rack/cors'
gem 'rack-timeout', '~> 0.4'
@@ -58,7 +57,6 @@ gem 'redis', '~> 3.3', require: ['redis', 'redis/connection/hiredis']
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 0.10'
gem 'ruby-oembed', '~> 0.12', require: 'oembed'
gem 'ruby-progressbar', '~> 1.4'
gem 'sanitize', '~> 4.4'
gem 'sidekiq', '~> 5.0'
gem 'sidekiq-scheduler', '~> 2.1'
@@ -77,15 +75,15 @@ gem 'json-ld-preloaded', '~> 2.2.1'
gem 'rdf-normalize', '~> 0.3.1'
group :development, :test do
gem 'fabrication', '~> 2.18'
gem 'fabrication', '~> 2.16'
gem 'fuubar', '~> 2.2'
gem 'i18n-tasks', '~> 0.9', require: false
gem 'pry-rails', '~> 0.3'
gem 'rspec-rails', '~> 3.7'
gem 'rspec-rails', '~> 3.6'
end
group :test do
gem 'capybara', '~> 2.15'
gem 'capybara', '~> 2.14'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 1.7'
gem 'microformats', '~> 4.0'
@@ -93,13 +91,13 @@ group :test do
gem 'rspec-sidekiq', '~> 3.0'
gem 'simplecov', '~> 0.14', require: false
gem 'webmock', '~> 3.0'
gem 'parallel_tests', '~> 2.17'
gem 'parallel_tests', '~> 2.14'
end
group :development do
gem 'active_record_query_trace', '~> 1.5'
gem 'annotate', '~> 2.7'
gem 'better_errors', '~> 2.4'
gem 'better_errors', '~> 2.1'
gem 'binding_of_caller', '~> 0.7'
gem 'bullet', '~> 5.5'
gem 'letter_opener', '~> 1.4'
@@ -107,15 +105,15 @@ group :development do
gem 'rubocop', require: false
gem 'brakeman', '~> 4.0', require: false
gem 'bundler-audit', '~> 0.6', require: false
gem 'scss_lint', '~> 0.55', require: false
gem 'scss_lint', '~> 0.53', require: false
gem 'capistrano', '~> 3.10'
gem 'capistrano-rails', '~> 1.3'
gem 'capistrano', '~> 3.8'
gem 'capistrano-rails', '~> 1.2'
gem 'capistrano-rbenv', '~> 2.1'
gem 'capistrano-yarn', '~> 2.0'
end
group :production do
gem 'lograge', '~> 0.7'
gem 'lograge', '~> 0.5'
gem 'redis-rails', '~> 5.0'
end

View File

@@ -24,11 +24,11 @@ GEM
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
active_model_serializers (0.10.7)
active_model_serializers (0.10.6)
actionpack (>= 4.1, < 6)
activemodel (>= 4.1, < 6)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
jsonapi-renderer (>= 0.1.1.beta1, < 0.2)
active_record_query_trace (1.5.4)
activejob (5.1.4)
activesupport (= 5.1.4)
@@ -57,25 +57,25 @@ GEM
encryptor (~> 3.0.0)
av (0.9.0)
cocaine (~> 0.5.3)
aws-sdk (2.10.100)
aws-sdk-resources (= 2.10.100)
aws-sdk-core (2.10.100)
aws-sdk (2.10.46)
aws-sdk-resources (= 2.10.46)
aws-sdk-core (2.10.46)
aws-sigv4 (~> 1.0)
jmespath (~> 1.0)
aws-sdk-resources (2.10.100)
aws-sdk-core (= 2.10.100)
aws-sdk-resources (2.10.46)
aws-sdk-core (= 2.10.46)
aws-sigv4 (1.0.2)
bcrypt (3.1.11)
better_errors (2.4.0)
better_errors (2.3.0)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
binding_of_caller (0.7.3)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
bootsnap (1.1.5)
bootsnap (1.1.3)
msgpack (~> 1.0)
brakeman (4.0.1)
browser (2.5.2)
browser (2.5.1)
builder (3.2.3)
bullet (5.6.1)
activesupport (>= 3.0.0)
@@ -83,23 +83,23 @@ GEM
bundler-audit (0.6.0)
bundler (~> 1.2)
thor (~> 0.18)
capistrano (3.10.0)
capistrano (3.9.1)
airbrussh (>= 1.0.0)
i18n
rake (>= 10.0.0)
sshkit (>= 1.9.0)
capistrano-bundler (1.3.0)
capistrano-bundler (1.2.0)
capistrano (~> 3.1)
sshkit (~> 1.2)
capistrano-rails (1.3.1)
capistrano-rails (1.3.0)
capistrano (~> 3.1)
capistrano-bundler (~> 1.1)
capistrano-rbenv (2.1.3)
capistrano-rbenv (2.1.1)
capistrano (~> 3.1)
sshkit (~> 1.3)
capistrano-yarn (2.0.2)
capistrano (~> 3.0)
capybara (2.16.1)
capybara (2.15.1)
addressable
mini_mime (>= 0.1.3)
nokogiri (>= 1.3.3)
@@ -110,7 +110,7 @@ GEM
activesupport
charlock_holmes (0.7.5)
chunky_png (1.3.8)
cld3 (3.2.1)
cld3 (3.2.0)
ffi (>= 1.1.0, < 1.10.0)
climate_control (0.2.0)
cocaine (0.5.8)
@@ -121,7 +121,7 @@ GEM
connection_pool (2.2.1)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.3)
crass (1.0.2)
debug_inspector (0.0.3)
devise (4.3.0)
bcrypt (~> 3.0)
@@ -129,11 +129,11 @@ GEM
railties (>= 4.1.0, < 5.2)
responders
warden (~> 1.2.3)
devise-two-factor (3.0.2)
activesupport (< 5.2)
devise-two-factor (3.0.0)
activesupport
attr_encrypted (>= 1.3, < 4, != 2)
devise (~> 4.0)
railties (< 5.2)
railties
rotp (~> 2.0)
diff-lcs (1.3)
docile (1.1.5)
@@ -150,12 +150,12 @@ GEM
thread
thread_safe
encryptor (3.0.0)
erubi (1.7.0)
et-orbi (1.0.8)
erubi (1.6.1)
et-orbi (1.0.5)
tzinfo
excon (0.59.0)
execjs (2.7.0)
fabrication (2.18.0)
fabrication (2.16.3)
faker (1.8.4)
i18n (~> 0.5)
fast_blank (1.0.0)
@@ -167,9 +167,7 @@ GEM
fog-json (1.0.2)
fog-core (~> 1.0)
multi_json (~> 1.10)
fog-local (0.4.0)
fog-core (~> 1.27)
fog-openstack (0.1.22)
fog-openstack (0.1.21)
fog-core (>= 1.40)
fog-json (>= 1.0)
ipaddress (>= 0.8)
@@ -177,14 +175,14 @@ GEM
fuubar (2.2.0)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
globalid (0.4.1)
globalid (0.4.0)
activesupport (>= 4.2.0)
goldfinger (2.0.1)
addressable (~> 2.5)
http (~> 2.2)
nokogiri (~> 1.8)
oj (~> 3.0)
hamlit (2.8.5)
hamlit (2.8.4)
temple (>= 0.8.0)
thor
tilt
@@ -196,7 +194,7 @@ GEM
hamster (3.0.0)
concurrent-ruby (~> 1.0)
hashdiff (0.3.7)
highline (1.7.10)
highline (1.7.8)
hiredis (0.6.1)
hkdf (0.3.0)
htmlentities (4.3.4)
@@ -213,9 +211,8 @@ GEM
httplog (0.99.7)
colorize
rack
i18n (0.9.1)
concurrent-ruby (~> 1.0)
i18n-tasks (0.9.19)
i18n (0.8.6)
i18n-tasks (0.9.18)
activesupport (>= 4.0.2)
ast (>= 2.1.0)
easy_translate (>= 0.5.0)
@@ -230,27 +227,27 @@ GEM
iso-639 (0.2.8)
jmespath (1.3.1)
json (2.1.0)
json-ld (2.1.7)
json-ld (2.1.5)
multi_json (~> 1.12)
rdf (~> 2.2, >= 2.2.8)
rdf (~> 2.2)
json-ld-preloaded (2.2.2)
json-ld (~> 2.1, >= 2.1.5)
multi_json (~> 1.11)
rdf (~> 2.2)
jsonapi-renderer (0.2.0)
jwt (2.1.0)
kaminari (1.1.1)
jsonapi-renderer (0.1.3)
jwt (1.5.6)
kaminari (1.0.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.1.1)
kaminari-activerecord (= 1.1.1)
kaminari-core (= 1.1.1)
kaminari-actionview (1.1.1)
kaminari-actionview (= 1.0.1)
kaminari-activerecord (= 1.0.1)
kaminari-core (= 1.0.1)
kaminari-actionview (1.0.1)
actionview
kaminari-core (= 1.1.1)
kaminari-activerecord (1.1.1)
kaminari-core (= 1.0.1)
kaminari-activerecord (1.0.1)
activerecord
kaminari-core (= 1.1.1)
kaminari-core (1.1.1)
kaminari-core (= 1.0.1)
kaminari-core (1.0.1)
launchy (2.4.3)
addressable (~> 2.3)
letter_opener (1.4.1)
@@ -260,19 +257,18 @@ GEM
letter_opener (~> 1.0)
railties (>= 3.2)
link_header (0.0.8)
lograge (0.7.1)
lograge (0.6.0)
actionpack (>= 4, < 5.2)
activesupport (>= 4, < 5.2)
railties (>= 4, < 5.2)
request_store (~> 1.0)
loofah (2.1.1)
crass (~> 1.0.2)
loofah (2.0.3)
nokogiri (>= 1.5.9)
mail (2.7.0)
mini_mime (>= 0.1.1)
mail (2.6.6)
mime-types (>= 1.16, < 4)
mario-redis-lock (1.2.0)
redis (~> 3, >= 3.0.5)
method_source (0.9.0)
method_source (0.8.2)
microformats (4.0.7)
json
nokogiri
@@ -280,8 +276,8 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mimemagic (0.3.2)
mini_mime (1.0.0)
mini_portile2 (2.3.0)
mini_mime (0.1.4)
mini_portile2 (2.2.0)
minitest (5.10.3)
msgpack (1.1.0)
multi_json (1.12.2)
@@ -289,8 +285,8 @@ GEM
net-ssh (>= 2.6.5)
net-ssh (4.2.0)
nio4r (2.1.0)
nokogiri (1.8.1)
mini_portile2 (~> 2.3.0)
nokogiri (1.8.0)
mini_portile2 (~> 2.2.0)
nokogumbo (1.4.13)
nokogiri
nsa (0.2.4)
@@ -298,13 +294,15 @@ GEM
concurrent-ruby (~> 1.0.0)
sidekiq (>= 3.5.0)
statsd-ruby (~> 1.2.0)
oj (3.3.9)
oj (3.3.5)
openssl (2.0.5)
orm_adapter (0.5.0)
ostatus2 (2.0.2)
ostatus2 (2.0.1)
addressable (~> 2.4)
http (~> 2.0)
nokogiri (~> 1.6)
ox (2.8.2)
openssl (~> 2.0)
ox (2.6.0)
paperclip (5.1.0)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
@@ -315,24 +313,27 @@ GEM
av (~> 0.9.0)
paperclip (>= 2.5.2)
parallel (1.12.0)
parallel_tests (2.19.0)
parallel_tests (2.15.0)
parallel
parser (2.4.0.2)
ast (~> 2.3)
parser (2.4.0.0)
ast (~> 2.2)
pg (0.21.0)
pghero (1.7.0)
activerecord
pkg-config (1.2.8)
pkg-config (1.2.7)
powerpack (0.1.1)
pry (0.11.3)
pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
method_source (~> 0.8.1)
slop (~> 3.4)
pry-rails (0.3.6)
pry (>= 0.10.4)
public_suffix (3.0.1)
puma (3.11.0)
public_suffix (3.0.0)
puma (3.10.0)
pundit (1.1.0)
activesupport (>= 3.0.0)
rabl (0.13.1)
activesupport (>= 2.3.14)
rack (2.0.3)
rack-attack (5.0.1)
rack
@@ -341,7 +342,7 @@ GEM
rack
rack-proxy (0.6.2)
rack
rack-test (0.8.2)
rack-test (0.7.0)
rack (>= 1.0, < 3)
rack-timeout (0.4.2)
rails (5.1.4)
@@ -378,34 +379,31 @@ GEM
thor (>= 0.18.1, < 2.0)
rainbow (2.2.2)
rake
rake (12.3.0)
rb-fsevent (0.10.2)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
rdf (2.2.12)
rake (12.1.0)
rdf (2.2.9)
hamster (~> 3.0)
link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.3.2)
rdf (~> 2.0)
redis (3.3.5)
redis-actionpack (5.0.2)
redis (3.3.3)
redis-actionpack (5.0.1)
actionpack (>= 4.0, < 6)
redis-rack (>= 1, < 3)
redis-store (>= 1.1.0, < 2)
redis-activesupport (5.0.4)
redis-store (>= 1.1.0, < 1.4.0)
redis-activesupport (5.0.3)
activesupport (>= 3, < 6)
redis-store (>= 1.3, < 2)
redis-namespace (1.6.0)
redis (>= 3.0.4)
redis-rack (2.0.3)
redis-store (~> 1.3.0)
redis-namespace (1.5.3)
redis (~> 3.0, >= 3.0.4)
redis-rack (2.0.2)
rack (>= 1.5, < 3)
redis-store (>= 1.2, < 2)
redis-store (>= 1.2, < 1.4)
redis-rails (5.0.2)
redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2)
redis-store (1.4.1)
redis (>= 2.2, < 5)
redis-store (1.3.0)
redis (>= 2.2)
request_store (1.3.2)
responders (2.4.0)
actionpack (>= 4.2.0, < 5.3)
@@ -413,27 +411,27 @@ GEM
rotp (2.1.2)
rqrcode (0.10.1)
chunky_png (~> 1.0)
rspec-core (3.7.0)
rspec-support (~> 3.7.0)
rspec-expectations (3.7.0)
rspec-core (3.6.0)
rspec-support (~> 3.6.0)
rspec-expectations (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0)
rspec-mocks (3.7.0)
rspec-support (~> 3.6.0)
rspec-mocks (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0)
rspec-rails (3.7.2)
rspec-support (~> 3.6.0)
rspec-rails (3.6.1)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 3.7.0)
rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.7.0)
rspec-support (~> 3.7.0)
rspec-core (~> 3.6.0)
rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.6.0)
rspec-support (~> 3.6.0)
rspec-sidekiq (3.0.3)
rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0)
rspec-support (3.7.0)
rubocop (0.51.0)
rspec-support (3.6.0)
rubocop (0.50.0)
parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
@@ -441,7 +439,7 @@ GEM
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-oembed (0.12.0)
ruby-progressbar (1.9.0)
ruby-progressbar (1.8.3)
rufus-scheduler (3.4.2)
et-orbi (~> 1.0)
safe_yaml (1.0.4)
@@ -449,24 +447,20 @@ GEM
crass (~> 1.0.2)
nokogiri (>= 1.4.4)
nokogumbo (~> 1.4.1)
sass (3.5.3)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
scss_lint (0.56.0)
sass (3.4.25)
scss_lint (0.54.0)
rake (>= 0.9, < 13)
sass (~> 3.5.3)
sidekiq (5.0.5)
sass (~> 3.4.20)
sidekiq (5.0.4)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
redis (>= 3.3.4, < 5)
redis (~> 3.3, >= 3.3.3)
sidekiq-bulk (0.1.1)
activesupport
sidekiq
sidekiq-scheduler (2.1.10)
redis (>= 3, < 5)
sidekiq-scheduler (2.1.9)
redis (~> 3)
rufus-scheduler (~> 3.2)
sidekiq (>= 3)
tilt (>= 1.4.0)
@@ -483,6 +477,7 @@ GEM
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
slop (3.6.0)
sprockets (3.7.1)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
@@ -490,7 +485,7 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sshkit (1.15.1)
sshkit (1.14.0)
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
statsd-ruby (1.2.1)
@@ -505,9 +500,9 @@ GEM
tilt (2.0.8)
twitter-text (1.14.7)
unf (~> 0.1.0)
tzinfo (1.2.4)
tzinfo (1.2.3)
thread_safe (~> 0.1)
tzinfo-data (1.2017.3)
tzinfo-data (1.2017.2)
tzinfo (>= 1.0.0)
uglifier (3.2.0)
execjs (>= 0.3.0, < 3)
@@ -518,20 +513,20 @@ GEM
uniform_notifier (1.10.0)
warden (1.2.7)
rack (>= 1.0)
webmock (3.1.1)
webmock (3.1.0)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
webpacker (3.0.2)
webpacker (3.0.1)
activesupport (>= 4.2)
rack-proxy (>= 0.6.1)
railties (>= 4.2)
webpush (0.3.3)
webpush (0.3.2)
hkdf (~> 0.2)
jwt (~> 2.0)
jwt
websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3)
websocket-extensions (0.1.2)
xpath (2.1.0)
nokogiri (~> 1.3)
@@ -543,31 +538,29 @@ DEPENDENCIES
active_record_query_trace (~> 1.5)
addressable (~> 2.5)
annotate (~> 2.7)
aws-sdk (~> 2.10)
better_errors (~> 2.4)
aws-sdk (~> 2.9)
better_errors (~> 2.1)
binding_of_caller (~> 0.7)
bootsnap
brakeman (~> 4.0)
browser
bullet (~> 5.5)
bundler-audit (~> 0.6)
capistrano (~> 3.10)
capistrano-rails (~> 1.3)
capistrano (~> 3.8)
capistrano-rails (~> 1.2)
capistrano-rbenv (~> 2.1)
capistrano-yarn (~> 2.0)
capybara (~> 2.15)
capybara (~> 2.14)
charlock_holmes (~> 0.7.5)
cld3 (~> 3.2.0)
climate_control (~> 0.2)
devise (~> 4.3)
devise (~> 4.2)
devise-two-factor (~> 3.0)
doorkeeper (~> 4.2)
dotenv-rails (~> 2.2)
fabrication (~> 2.18)
fabrication (~> 2.16)
faker (~> 1.7)
fast_blank (~> 1.0)
fog-core (~> 1.45)
fog-local (~> 0.4)
fog-openstack (~> 0.1)
fuubar (~> 2.2)
goldfinger (~> 2.0)
@@ -581,28 +574,29 @@ DEPENDENCIES
idn-ruby
iso-639
json-ld-preloaded (~> 2.2.1)
kaminari (~> 1.1)
kaminari (~> 1.0)
letter_opener (~> 1.4)
letter_opener_web (~> 1.3)
link_header (~> 0.0)
lograge (~> 0.7)
lograge (~> 0.5)
mario-redis-lock (~> 1.2)
microformats (~> 4.0)
mime-types (~> 3.1)
nokogiri (~> 1.8)
nokogiri (~> 1.7)
nsa (~> 0.2)
oj (~> 3.3)
oj (~> 3.0)
ostatus2 (~> 2.0)
ox (~> 2.8)
ox (~> 2.5)
paperclip (~> 5.1)
paperclip-av-transcoder (~> 0.6)
parallel_tests (~> 2.17)
parallel_tests (~> 2.14)
pg (~> 0.20)
pghero (~> 1.7)
pkg-config (~> 1.2)
pry-rails (~> 0.3)
puma (~> 3.10)
pundit (~> 1.1)
rabl (~> 0.13)
rack-attack (~> 5.0)
rack-cors (~> 0.4)
rack-timeout (~> 0.4)
@@ -615,13 +609,12 @@ DEPENDENCIES
redis-namespace (~> 1.5)
redis-rails (~> 5.0)
rqrcode (~> 0.10)
rspec-rails (~> 3.7)
rspec-rails (~> 3.6)
rspec-sidekiq (~> 3.0)
rubocop
ruby-oembed (~> 0.12)
ruby-progressbar (~> 1.4)
sanitize (~> 4.4)
scss_lint (~> 0.55)
scss_lint (~> 0.53)
sidekiq (~> 5.0)
sidekiq-bulk (~> 0.1.1)
sidekiq-scheduler (~> 2.1)
@@ -642,4 +635,4 @@ RUBY VERSION
ruby 2.4.2p198
BUNDLED WITH
1.16.1
1.15.4

View File

@@ -1,85 +1,10 @@
![Mastodon](https://i.imgur.com/NhZc40l.png)
========
# Mastodon Glitch Edition #
[![Build Status](https://img.shields.io/travis/tootsuite/mastodon.svg)][travis]
[![Code Climate](https://img.shields.io/codeclimate/maintainability/tootsuite/mastodon.svg)][code_climate]
> Now with automated deploys!
[travis]: https://travis-ci.org/tootsuite/mastodon
[code_climate]: https://codeclimate.com/github/tootsuite/mastodon
[![Build Status](https://travis-ci.org/glitch-soc/mastodon.svg?branch=master)](https://travis-ci.org/glitch-soc/mastodon)
Mastodon is a **free, open-source social network server** based on **open web protocols** like ActivityPub and OStatus. The social focus of the project is a viable decentralized alternative to commercial social media silos that returns the control of the content distribution channels to the people. The technical focus of the project is a good user interface, a clean REST API for 3rd party apps and robust anti-abuse tools.
So here's the deal: we all work on this code, and then it runs on dev.glitch.social and anyone who uses that does so absolutely at their own risk. can you dig it?
Click on the screenshot below to watch a demo of the UI:
[![Screenshot](https://i.imgur.com/pG3Nnz3.jpg)][youtube_demo]
[youtube_demo]: https://www.youtube.com/watch?v=YO1jQ8_rAMU
**Ruby on Rails** is used for the back-end, while **React.js** and Redux are used for the dynamic front-end. A static front-end for public resources (profiles and statuses) is also provided.
If you would like, you can [support the development of this project on Patreon][patreon]. Alternatively, you can donate to this BTC address: `17j2g7vpgHhLuXhN4bueZFCvdxxieyRVWd`
[patreon]: https://www.patreon.com/user?u=619786
---
## Resources
- [Frequently Asked Questions](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md)
- [Use this tool to find Twitter friends on Mastodon](https://bridge.joinmastodon.org)
- [API overview](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md)
- [List of Mastodon instances](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md)
- [List of apps](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md)
- [List of sponsors](https://joinmastodon.org/sponsors)
## Features
**No vendor lock-in: Fully interoperable with any conforming platform**
It doesn't have to be Mastodon, whatever implements ActivityPub or OStatus is part of the social network!
**Real-time timeline updates**
See the updates of people you're following appear in real-time in the UI via WebSockets. There's a firehose view as well!
**Federated thread resolving**
If someone you follow replies to a user unknown to the server, the server fetches the full thread so you can view it without leaving the UI
**Media attachments like images and short videos**
Upload and view images and WebM/MP4 videos attached to the updates. Videos with no audio track are treated like GIFs; normal videos are looped - like vines!
**OAuth2 and a straightforward REST API**
Mastodon acts as an OAuth2 provider so 3rd party apps can use the API
**Fast response times**
Mastodon tries to be as fast and responsive as possible, so all long-running tasks are delegated to background processing
**Deployable via Docker**
You don't need to mess with dependencies and configuration if you want to try Mastodon, if you have Docker and Docker Compose the deployment is extremely easy
---
## Development
Please follow the [development guide](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Development-guide.md) from the documentation repository.
## Deployment
There are guides in the documentation repository for [deploying on various platforms](https://github.com/tootsuite/documentation#running-mastodon).
## Contributing
You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository. [Here are the guidelines for code contributions](CONTRIBUTING.md)
**IRC channel**: #mastodon on irc.freenode.net
---
## Extra credits
The elephant friend illustrations are created by [Dopatwo](https://mastodon.social/@dopatwo)
- You can view documentation for this project at [glitch-soc.github.io/docs/](https://glitch-soc.github.io/docs/).
- And contributing guidelines are available [here](CONTRIBUTING.md) and [here](https://glitch-soc.github.io/docs/contributing/).

2
Vagrantfile vendored
View File

@@ -83,7 +83,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.provider :virtualbox do |vb|
vb.name = "mastodon"
vb.customize ["modifyvm", :id, "--memory", "2048"]
vb.customize ["modifyvm", :id, "--memory", "4096"]
# Disable VirtualBox DNS proxy to skip long-delay IPv6 resolutions.
# https://github.com/mitchellh/vagrant/issues/1172

View File

@@ -2,8 +2,7 @@
class AccountsController < ApplicationController
include AccountControllerConcern
before_action :set_cache_headers
include SignatureVerification
def show
respond_to do |format|
@@ -27,11 +26,10 @@ class AccountsController < ApplicationController
end
format.json do
skip_session!
render_cached_json(['activitypub', 'actor', @account.cache_key], content_type: 'application/activity+json') do
ActiveModelSerializers::SerializableResource.new(@account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter)
end
render json: @account,
serializer: ActivityPub::ActorSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
end
end
end

View File

@@ -1,22 +0,0 @@
# frozen_string_literal: true
class ActivityPub::FollowsController < Api::BaseController
include SignatureVerification
def show
render json: follow_request,
serializer: ActivityPub::FollowSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
end
private
def follow_request
FollowRequest.includes(:account).references(:account).find_by!(
id: params.require(:id),
accounts: { domain: nil, username: params.require(:account_username) },
target_account: signed_request_account
)
end
end

View File

@@ -1,41 +1,31 @@
# frozen_string_literal: true
module Admin
class AccountModerationNotesController < BaseController
before_action :set_account_moderation_note, only: [:destroy]
def create
authorize AccountModerationNote, :create?
@account_moderation_note = current_account.account_moderation_notes.new(resource_params)
if @account_moderation_note.save
redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.created_msg')
else
@account = @account_moderation_note.target_account
@moderation_notes = @account.targeted_moderation_notes.latest
render template: 'admin/accounts/show'
end
end
def destroy
authorize @account_moderation_note, :destroy?
@account_moderation_note.destroy!
redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg')
end
private
def resource_params
params.require(:account_moderation_note).permit(
:content,
:target_account_id
)
end
def set_account_moderation_note
@account_moderation_note = AccountModerationNote.find(params[:id])
class Admin::AccountModerationNotesController < Admin::BaseController
def create
@account_moderation_note = current_account.account_moderation_notes.new(resource_params)
if @account_moderation_note.save
@target_account = @account_moderation_note.target_account
redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.created_msg')
else
@account = @account_moderation_note.target_account
@moderation_notes = @account.targeted_moderation_notes.latest
render template: 'admin/accounts/show'
end
end
def destroy
@account_moderation_note = AccountModerationNote.find(params[:id])
@target_account = @account_moderation_note.target_account
@account_moderation_note.destroy
redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg')
end
private
def resource_params
params.require(:account_moderation_note).permit(
:content,
:target_account_id
)
end
end

View File

@@ -2,57 +2,29 @@
module Admin
class AccountsController < BaseController
before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :enable, :disable, :memorialize]
before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload]
before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
before_action :require_local_account!, only: [:enable, :disable, :memorialize]
def index
authorize :account, :index?
@accounts = filtered_accounts.page(params[:page])
end
def show
authorize @account, :show?
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
@moderation_notes = @account.targeted_moderation_notes.latest
end
def subscribe
authorize @account, :subscribe?
Pubsubhubbub::SubscribeWorker.perform_async(@account.id)
redirect_to admin_account_path(@account.id)
end
def unsubscribe
authorize @account, :unsubscribe?
Pubsubhubbub::UnsubscribeWorker.perform_async(@account.id)
redirect_to admin_account_path(@account.id)
end
def memorialize
authorize @account, :memorialize?
@account.memorialize!
log_action :memorialize, @account
redirect_to admin_account_path(@account.id)
end
def enable
authorize @account.user, :enable?
@account.user.enable!
log_action :enable, @account.user
redirect_to admin_account_path(@account.id)
end
def disable
authorize @account.user, :disable?
@account.user.disable!
log_action :disable, @account.user
redirect_to admin_account_path(@account.id)
end
def redownload
authorize @account, :redownload?
@account.reset_avatar!
@account.reset_header!
@account.save!
@@ -70,10 +42,6 @@ module Admin
redirect_to admin_account_path(@account.id) if @account.local?
end
def require_local_account!
redirect_to admin_account_path(@account.id) unless @account.local? && @account.user.present?
end
def filtered_accounts
AccountFilter.new(filter_params).results
end
@@ -89,8 +57,7 @@ module Admin
:username,
:display_name,
:email,
:ip,
:staff
:ip
)
end
end

View File

@@ -1,9 +0,0 @@
# frozen_string_literal: true
module Admin
class ActionLogsController < BaseController
def index
@action_logs = Admin::ActionLog.page(params[:page])
end
end
end

View File

@@ -2,10 +2,7 @@
module Admin
class BaseController < ApplicationController
include Authorization
include AccountableConcern
before_action :require_staff!
before_action :require_admin!
layout 'admin'
end

View File

@@ -2,19 +2,15 @@
module Admin
class ConfirmationsController < BaseController
before_action :set_user
def create
authorize @user, :confirm?
@user.confirm!
log_action :confirm, @user
account_user.confirm
redirect_to admin_accounts_path
end
private
def set_user
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
def account_user
Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
end
end
end

View File

@@ -3,82 +3,50 @@
module Admin
class CustomEmojisController < BaseController
before_action :set_custom_emoji, except: [:index, :new, :create]
before_action :set_filter_params
def index
authorize :custom_emoji, :index?
@custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
@custom_emojis = filtered_custom_emojis.page(params[:page])
end
def new
authorize :custom_emoji, :create?
@custom_emoji = CustomEmoji.new
end
def create
authorize :custom_emoji, :create?
@custom_emoji = CustomEmoji.new(resource_params)
if @custom_emoji.save
log_action :create, @custom_emoji
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.created_msg')
else
render :new
end
end
def update
authorize @custom_emoji, :update?
if @custom_emoji.update(resource_params)
log_action :update, @custom_emoji
flash[:notice] = I18n.t('admin.custom_emojis.updated_msg')
else
flash[:alert] = I18n.t('admin.custom_emojis.update_failed_msg')
end
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
end
def destroy
authorize @custom_emoji, :destroy?
@custom_emoji.destroy!
log_action :destroy, @custom_emoji
flash[:notice] = I18n.t('admin.custom_emojis.destroyed_msg')
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
@custom_emoji.destroy
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg')
end
def copy
authorize @custom_emoji, :copy?
emoji = CustomEmoji.find_or_initialize_by(domain: nil,
shortcode: @custom_emoji.shortcode)
emoji.image = @custom_emoji.image
emoji = CustomEmoji.new(domain: nil, shortcode: @custom_emoji.shortcode, image: @custom_emoji.image)
if emoji.save
log_action :create, emoji
flash[:notice] = I18n.t('admin.custom_emojis.copied_msg')
else
flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg')
end
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
redirect_to admin_custom_emojis_path(params[:page])
end
def enable
authorize @custom_emoji, :enable?
@custom_emoji.update!(disabled: false)
log_action :enable, @custom_emoji
flash[:notice] = I18n.t('admin.custom_emojis.enabled_msg')
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.enabled_msg')
end
def disable
authorize @custom_emoji, :disable?
@custom_emoji.update!(disabled: true)
log_action :disable, @custom_emoji
flash[:notice] = I18n.t('admin.custom_emojis.disabled_msg')
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.disabled_msg')
end
private
@@ -87,12 +55,8 @@ module Admin
@custom_emoji = CustomEmoji.find(params[:id])
end
def set_filter_params
@filter_params = filter_params.to_hash.symbolize_keys
end
def resource_params
params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
params.require(:custom_emoji).permit(:shortcode, :image)
end
def filtered_custom_emojis
@@ -102,9 +66,7 @@ module Admin
def filter_params
params.permit(
:local,
:remote,
:by_domain,
:shortcode
:remote
)
end
end

View File

@@ -5,37 +5,28 @@ module Admin
before_action :set_domain_block, only: [:show, :destroy]
def index
authorize :domain_block, :index?
@domain_blocks = DomainBlock.page(params[:page])
end
def new
authorize :domain_block, :create?
@domain_block = DomainBlock.new
end
def create
authorize :domain_block, :create?
@domain_block = DomainBlock.new(resource_params)
if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id)
log_action :create, @domain_block
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.created_msg')
else
render :new
end
end
def show
authorize @domain_block, :show?
end
def show; end
def destroy
authorize @domain_block, :destroy?
UnblockDomainService.new.call(@domain_block, retroactive_unblock?)
log_action :destroy, @domain_block
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg')
end

View File

@@ -5,22 +5,17 @@ module Admin
before_action :set_email_domain_block, only: [:show, :destroy]
def index
authorize :email_domain_block, :index?
@email_domain_blocks = EmailDomainBlock.page(params[:page])
end
def new
authorize :email_domain_block, :create?
@email_domain_block = EmailDomainBlock.new
end
def create
authorize :email_domain_block, :create?
@email_domain_block = EmailDomainBlock.new(resource_params)
if @email_domain_block.save
log_action :create, @email_domain_block
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg')
else
render :new
@@ -28,9 +23,7 @@ module Admin
end
def destroy
authorize @email_domain_block, :destroy?
@email_domain_block.destroy!
log_action :destroy, @email_domain_block
@email_domain_block.destroy
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg')
end

View File

@@ -3,12 +3,10 @@
module Admin
class InstancesController < BaseController
def index
authorize :instance, :index?
@instances = ordered_instances
end
def resubscribe
authorize :instance, :resubscribe?
params.require(:by_domain)
Pubsubhubbub::SubscribeWorker.push_bulk(subscribeable_accounts.pluck(:id))
redirect_to admin_instances_path

View File

@@ -1,47 +0,0 @@
# frozen_string_literal: true
module Admin
class InvitesController < BaseController
def index
authorize :invite, :index?
@invites = filtered_invites.includes(user: :account).page(params[:page])
@invite = Invite.new
end
def create
authorize :invite, :create?
@invite = Invite.new(resource_params)
@invite.user = current_user
if @invite.save
redirect_to admin_invites_path
else
@invites = Invite.page(params[:page])
render :index
end
end
def destroy
@invite = Invite.find(params[:id])
authorize @invite, :destroy?
@invite.expire!
redirect_to admin_invites_path
end
private
def resource_params
params.require(:invite).permit(:max_uses, :expires_in)
end
def filtered_invites
InviteFilter.new(filter_params).results
end
def filter_params
params.permit(:available, :expired)
end
end
end

View File

@@ -2,29 +2,26 @@
module Admin
class ReportedStatusesController < BaseController
include Authorization
before_action :set_report
before_action :set_status, only: [:update, :destroy]
def create
authorize :status, :update?
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account))
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
@form = Form::StatusBatch.new(form_status_batch_params)
flash[:alert] = t('admin.statuses.failed_to_execute') unless @form.save
redirect_to admin_report_path(@report)
end
def update
authorize @status, :update?
@status.update!(status_params)
log_action :update, @status
@status.update(status_params)
redirect_to admin_report_path(@report)
end
def destroy
authorize @status, :destroy?
RemovalWorker.perform_async(@status.id)
log_action :destroy, @status
render json: @status
end

View File

@@ -5,17 +5,14 @@ module Admin
before_action :set_report, except: [:index]
def index
authorize :report, :index?
@reports = filtered_reports.page(params[:page])
end
def show
authorize @report, :show?
@form = Form::StatusBatch.new
end
def update
authorize @report, :update?
process_report
redirect_to admin_report_path(@report)
end
@@ -25,17 +22,12 @@ module Admin
def process_report
case params[:outcome].to_s
when 'resolve'
@report.update!(action_taken_by_current_attributes)
log_action :resolve, @report
@report.update(action_taken_by_current_attributes)
when 'suspend'
Admin::SuspensionWorker.perform_async(@report.target_account.id)
log_action :resolve, @report
log_action :suspend, @report.target_account
resolve_all_target_account_reports
when 'silence'
@report.target_account.update!(silenced: true)
log_action :resolve, @report
log_action :silence, @report.target_account
@report.target_account.update(silenced: true)
resolve_all_target_account_reports
else
raise ActiveRecord::RecordNotFound

View File

@@ -2,19 +2,17 @@
module Admin
class ResetsController < BaseController
before_action :set_user
before_action :set_account
def create
authorize @user, :reset_password?
@user.send_reset_password_instructions
log_action :reset_password, @user
@account.user.send_reset_password_instructions
redirect_to admin_accounts_path
end
private
def set_user
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
def set_account
@account = Account.find(params[:account_id])
end
end
end

View File

@@ -1,27 +0,0 @@
# frozen_string_literal: true
module Admin
class RolesController < BaseController
before_action :set_user
def promote
authorize @user, :promote?
@user.promote!
log_action :promote, @user
redirect_to admin_account_path(@user.account_id)
end
def demote
authorize @user, :demote?
@user.demote!
log_action :demote, @user
redirect_to admin_account_path(@user.account_id)
end
private
def set_user
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
end
end
end

View File

@@ -13,21 +13,14 @@ module Admin
closed_registrations_message
open_deletion
timeline_preview
show_staff_badge
bootstrap_timeline_accounts
thumbnail
min_invite_role
activity_api_enabled
peers_api_enabled
).freeze
BOOLEAN_SETTINGS = %w(
open_registrations
open_deletion
timeline_preview
show_staff_badge
activity_api_enabled
peers_api_enabled
).freeze
UPLOAD_SETTINGS = %w(
@@ -35,13 +28,10 @@ module Admin
).freeze
def edit
authorize :settings, :show?
@admin_settings = Form::AdminSettings.new
end
def update
authorize :settings, :update?
settings_params.each do |key, value|
if UPLOAD_SETTINGS.include?(key)
upload = SiteUpload.where(var: key).first_or_initialize(var: key)

View File

@@ -5,16 +5,12 @@ module Admin
before_action :set_account
def create
authorize @account, :silence?
@account.update!(silenced: true)
log_action :silence, @account
@account.update(silenced: true)
redirect_to admin_accounts_path
end
def destroy
authorize @account, :unsilence?
@account.update!(silenced: false)
log_action :unsilence, @account
@account.update(silenced: false)
redirect_to admin_accounts_path
end

View File

@@ -2,6 +2,8 @@
module Admin
class StatusesController < BaseController
include Authorization
helper_method :current_params
before_action :set_account
@@ -10,39 +12,31 @@ module Admin
PER_PAGE = 20
def index
authorize :status, :index?
@statuses = @account.statuses
if params[:media]
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct
@statuses.merge!(Status.where(id: account_media_status_ids))
end
@statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)
@form = Form::StatusBatch.new
@form = Form::StatusBatch.new
end
def create
authorize :status, :update?
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account))
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
@form = Form::StatusBatch.new(form_status_batch_params)
flash[:alert] = t('admin.statuses.failed_to_execute') unless @form.save
redirect_to admin_account_statuses_path(@account.id, current_params)
end
def update
authorize @status, :update?
@status.update!(status_params)
log_action :update, @status
@status.update(status_params)
redirect_to admin_account_statuses_path(@account.id, current_params)
end
def destroy
authorize @status, :destroy?
RemovalWorker.perform_async(@status.id)
log_action :destroy, @status
render json: @status
end
@@ -66,7 +60,6 @@ module Admin
def current_params
page = (params[:page] || 1).to_i
{
media: params[:media],
page: page > 1 && page,

View File

@@ -3,7 +3,6 @@
module Admin
class SubscriptionsController < BaseController
def index
authorize :subscription, :index?
@subscriptions = ordered_subscriptions.page(requested_page)
end

View File

@@ -5,16 +5,12 @@ module Admin
before_action :set_account
def create
authorize @account, :suspend?
Admin::SuspensionWorker.perform_async(@account.id)
log_action :suspend, @account
redirect_to admin_accounts_path
end
def destroy
authorize @account, :unsuspend?
@account.unsuspend!
log_action :unsuspend, @account
@account.update(suspended: false)
redirect_to admin_accounts_path
end

View File

@@ -5,9 +5,7 @@ module Admin
before_action :set_user
def destroy
authorize @user, :disable_2fa?
@user.disable_two_factor!
log_action :disable_2fa, @user
redirect_to admin_accounts_path
end

View File

@@ -72,4 +72,19 @@ class Api::BaseController < ApplicationController
def render_empty
render json: {}, status: 200
end
def set_maps(statuses) # rubocop:disable Style/AccessorMethodName
if current_account.nil?
@reblogs_map = {}
@favourites_map = {}
@mutes_map = {}
return
end
status_ids = statuses.compact.flat_map { |s| [s.id, s.reblog_of_id] }.uniq
conversation_ids = statuses.compact.map(&:conversation_id).compact.uniq
@reblogs_map = Status.reblogs_map(status_ids, current_account)
@favourites_map = Status.favourites_map(status_ids, current_account)
@mutes_map = Status.mutes_map(conversation_ids, current_account)
end
end

View File

@@ -1,20 +0,0 @@
# frozen_string_literal: true
class Api::V1::Accounts::ListsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read }
before_action :require_user!
before_action :set_account
respond_to :json
def index
@lists = @account.lists.where(account: current_account)
render json: @lists, each_serializer: REST::ListSerializer
end
private
def set_account
@account = Account.find(params[:account_id])
end
end

View File

@@ -17,13 +17,12 @@ class Api::V1::Accounts::SearchController < Api::BaseController
AccountSearchService.new.call(
params[:q],
limit_param(DEFAULT_ACCOUNTS_LIMIT),
current_account,
resolve: truthy_param?(:resolve),
following: truthy_param?(:following)
resolving_search?,
current_account
)
end
def truthy_param?(key)
params[key] == 'true'
def resolving_search?
params[:resolve] == 'true'
end
end

View File

@@ -13,9 +13,9 @@ class Api::V1::AccountsController < Api::BaseController
end
def follow
FollowService.new.call(current_user.account, @account.acct, reblogs: params[:reblogs])
FollowService.new.call(current_user.account, @account.acct)
options = @account.locked? ? {} : { following_map: { @account.id => { reblogs: params[:reblogs] } }, requested_map: { @account.id => false } }
options = @account.locked? ? {} : { following_map: { @account.id => true }, requested_map: { @account.id => false } }
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
end
@@ -51,7 +51,7 @@ class Api::V1::AccountsController < Api::BaseController
@account = Account.find(params[:id])
end
def relationships(**options)
def relationships(options = {})
AccountRelationshipsPresenter.new([@account.id], current_user.account_id, options)
end
end

View File

@@ -1,36 +0,0 @@
# frozen_string_literal: true
class Api::V1::Instances::ActivityController < Api::BaseController
before_action :require_enabled_api!
respond_to :json
def show
render_cached_json('api:v1:instances:activity:show', expires_in: 1.day) { activity }
end
private
def activity
weeks = []
12.times do |i|
day = i.weeks.ago.to_date
week_id = day.cweek
week = Date.commercial(day.cwyear, week_id)
weeks << {
week: week.to_time.to_i.to_s,
statuses: Redis.current.get("activity:statuses:local:#{week_id}") || 0,
logins: Redis.current.pfcount("activity:logins:#{week_id}"),
registrations: Redis.current.get("activity:accounts:local:#{week_id}") || 0,
}
end
weeks
end
def require_enabled_api!
head 404 unless Setting.activity_api_enabled
end
end

View File

@@ -1,17 +0,0 @@
# frozen_string_literal: true
class Api::V1::Instances::PeersController < Api::BaseController
before_action :require_enabled_api!
respond_to :json
def index
render_cached_json('api:v1:instances:peers:index', expires_in: 1.day) { Account.remote.domains }
end
private
def require_enabled_api!
head 404 unless Setting.peers_api_enabled
end
end

View File

@@ -1,97 +0,0 @@
# frozen_string_literal: true
class Api::V1::Lists::AccountsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read }, only: [:show]
before_action -> { doorkeeper_authorize! :write }, except: [:show]
before_action :require_user!
before_action :set_list
after_action :insert_pagination_headers, only: :show
def show
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
end
def create
ApplicationRecord.transaction do
list_accounts.each do |account|
@list.accounts << account
end
end
render_empty
end
def destroy
ListAccount.where(list: @list, account_id: account_ids).destroy_all
render_empty
end
private
def set_list
@list = List.where(account: current_account).find(params[:list_id])
end
def load_accounts
if unlimited?
@list.accounts.all
else
@list.accounts.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id])
end
end
def list_accounts
Account.find(account_ids)
end
def account_ids
Array(resource_params[:account_ids])
end
def resource_params
params.permit(account_ids: [])
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def next_path
return if unlimited?
if records_continue?
api_v1_list_accounts_url pagination_params(max_id: pagination_max_id)
end
end
def prev_path
return if unlimited?
unless @accounts.empty?
api_v1_list_accounts_url pagination_params(since_id: pagination_since_id)
end
end
def pagination_max_id
@accounts.last.id
end
def pagination_since_id
@accounts.first.id
end
def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end
def pagination_params(core_params)
params.permit(:limit).merge(core_params)
end
def unlimited?
params[:limit] == '0'
end
end

View File

@@ -1,43 +0,0 @@
# frozen_string_literal: true
class Api::V1::ListsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read }, only: [:index, :show]
before_action -> { doorkeeper_authorize! :write }, except: [:index, :show]
before_action :require_user!
before_action :set_list, except: [:index, :create]
def index
@lists = List.where(account: current_account).all
render json: @lists, each_serializer: REST::ListSerializer
end
def show
render json: @list, serializer: REST::ListSerializer
end
def create
@list = List.create!(list_params.merge(account: current_account))
render json: @list, serializer: REST::ListSerializer
end
def update
@list.update!(list_params)
render json: @list, serializer: REST::ListSerializer
end
def destroy
@list.destroy!
render_empty
end
private
def set_list
@list = List.where(account: current_account).find(params[:id])
end
def list_params
params.permit(:title)
end
end

View File

@@ -8,10 +8,15 @@ class Api::V1::MutesController < Api::BaseController
respond_to :json
def index
@accounts = load_accounts
@data = @accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
end
def details
@data = @mutes = load_mutes
render json: @mutes, each_serializer: REST::MuteSerializer
end
private
def load_accounts
@@ -22,6 +27,10 @@ class Api::V1::MutesController < Api::BaseController
Account.includes(:muted_by).references(:muted_by)
end
def load_mutes
paginated_mutes.includes(:account, :target_account).to_a
end
def paginated_mutes
Mute.where(account: current_account).paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
@@ -36,26 +45,34 @@ class Api::V1::MutesController < Api::BaseController
def next_path
if records_continue?
api_v1_mutes_url pagination_params(max_id: pagination_max_id)
url_for pagination_params(max_id: pagination_max_id)
end
end
def prev_path
unless @accounts.empty?
api_v1_mutes_url pagination_params(since_id: pagination_since_id)
unless@data.empty?
url_for pagination_params(since_id: pagination_since_id)
end
end
def pagination_max_id
@accounts.last.muted_by_ids.last
if params[:action] == "details"
@mutes.last.id
else
@accounts.last.muted_by_ids.last
end
end
def pagination_since_id
@accounts.first.muted_by_ids.first
if params[:action] == "details"
@mutes.first.id
else
@accounts.first.muted_by_ids.first
end
end
def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
@data.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end
def pagination_params(core_params)

View File

@@ -24,11 +24,20 @@ class Api::V1::NotificationsController < Api::BaseController
render_empty
end
def destroy
dismiss
end
def dismiss
current_account.notifications.find_by!(id: params[:id]).destroy!
render_empty
end
def destroy_multiple
current_account.notifications.where(id: params[:ids]).destroy_all
render_empty
end
private
def load_notifications

View File

@@ -19,7 +19,7 @@ class Api::V1::ReportsController < Api::BaseController
comment: report_params[:comment]
)
User.staff.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
User.admins.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
render json: @report, serializer: REST::ReportSerializer
end

View File

@@ -1,9 +1,7 @@
# frozen_string_literal: true
class Api::V1::SearchController < Api::BaseController
include Authorization
RESULTS_LIMIT = 5
RESULTS_LIMIT = 10
before_action -> { doorkeeper_authorize! :read }
before_action :require_user!
@@ -11,24 +9,12 @@ class Api::V1::SearchController < Api::BaseController
respond_to :json
def index
@search = Search.new(search)
@search = Search.new(search_results)
render json: @search, serializer: REST::SearchSerializer
end
private
def search
search_results.tap do |search|
search[:statuses].keep_if do |status|
begin
authorize status, :show?
rescue Mastodon::NotPermittedError
false
end
end
end
end
def search_results
SearchService.new.call(
params[:q],

View File

@@ -31,7 +31,7 @@ class Api::V1::Timelines::HomeController < Api::BaseController
end
def account_home_feed
HomeFeed.new(current_account)
Feed.new(:home, current_account)
end
def insert_pagination_headers

View File

@@ -1,66 +0,0 @@
# frozen_string_literal: true
class Api::V1::Timelines::ListController < Api::BaseController
before_action -> { doorkeeper_authorize! :read }
before_action :require_user!
before_action :set_list
before_action :set_statuses
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show
render json: @statuses,
each_serializer: REST::StatusSerializer,
relationships: StatusRelationshipsPresenter.new(@statuses, current_user.account_id)
end
private
def set_list
@list = List.where(account: current_account).find(params[:id])
end
def set_statuses
@statuses = cached_list_statuses
end
def cached_list_statuses
cache_collection list_statuses, Status
end
def list_statuses
list_feed.get(
limit_param(DEFAULT_STATUSES_LIMIT),
params[:max_id],
params[:since_id]
)
end
def list_feed
ListFeed.new(@list)
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.permit(:limit).merge(core_params)
end
def next_path
api_v1_timelines_list_url params[:id], pagination_params(max_id: pagination_max_id)
end
def prev_path
api_v1_timelines_list_url params[:id], pagination_params(since_id: pagination_since_id)
end
def pagination_max_id
@statuses.last.id
end
def pagination_since_id
@statuses.first.id
end
end

View File

@@ -28,8 +28,6 @@ class Api::Web::PushSubscriptionsController < Api::BaseController
},
}
data.deep_merge!(params[:data]) if params[:data]
web_subscription = ::Web::PushSubscription.create!(
endpoint: params[:subscription][:endpoint],
key_p256dh: params[:subscription][:keys][:p256dh],

View File

@@ -18,7 +18,6 @@ class ApplicationController < ActionController::Base
rescue_from ActionController::RoutingError, with: :not_found
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
rescue_from Mastodon::NotPermittedError, with: :forbidden
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
before_action :check_suspension, if: :user_signed_in?
@@ -41,10 +40,6 @@ class ApplicationController < ActionController::Base
redirect_to root_path unless current_user&.admin?
end
def require_staff!
redirect_to root_path unless current_user&.staff?
end
def check_suspension
forbidden if current_user.account.suspended?
end
@@ -104,7 +99,7 @@ class ApplicationController < ActionController::Base
unless uncached_ids.empty?
uncached = klass.where(id: uncached_ids).with_includes.map { |item| [item.id, item] }.to_h
uncached.each_value do |item|
uncached.values.each do |item|
Rails.cache.write(item.cache_key, item)
end
end
@@ -121,26 +116,4 @@ class ApplicationController < ActionController::Base
end
end
end
def render_cached_json(cache_key, **options)
options[:expires_in] ||= 3.minutes
options[:public] ||= true
cache_key = cache_key.join(':') if cache_key.is_a?(Enumerable)
content_type = options.delete(:content_type) || 'application/json'
data = Rails.cache.fetch(cache_key, { raw: true }.merge(options)) do
yield.to_json
end
expires_in options[:expires_in], public: options[:public]
render json: data, content_type: content_type
end
def set_cache_headers
response.headers['Vary'] = 'Accept'
end
def skip_session!
request.session_options[:skip] = true
end
end

View File

@@ -2,4 +2,10 @@
class Auth::ConfirmationsController < Devise::ConfirmationsController
layout 'auth'
def show
super do |user|
BootstrapTimelineWorker.perform_async(user.account_id) if user.errors.empty?
end
end
end

View File

@@ -16,16 +16,13 @@ class Auth::RegistrationsController < Devise::RegistrationsController
def build_resource(hash = nil)
super(hash)
resource.locale = I18n.locale
resource.invite_code = params[:invite_code] if resource.invite_code.blank?
resource.locale = I18n.locale
resource.build_account if resource.account.nil?
end
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up) do |u|
u.permit({ account_attributes: [:username] }, :email, :password, :password_confirmation, :invite_code)
u.permit({ account_attributes: [:username] }, :email, :password, :password_confirmation)
end
end
@@ -37,24 +34,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
new_user_session_path
end
def after_update_path_for(_resource)
edit_user_registration_path
end
def check_enabled_registrations
redirect_to root_path if single_user_mode? || !allowed_registrations?
end
def allowed_registrations?
Setting.open_registrations || (invite_code.present? && Invite.find_by(code: invite_code)&.valid_for_use?)
end
def invite_code
if params[:user]
params[:user][:invite_code]
else
params[:invite_code]
end
redirect_to root_path if single_user_mode? || !Setting.open_registrations
end
private

View File

@@ -62,7 +62,7 @@ class Auth::SessionsController < Devise::SessionsController
if user_params[:otp_attempt].present? && session[:otp_user_id]
authenticate_with_two_factor_via_otp(user)
elsif user&.valid_password?(user_params[:password])
elsif user && user.valid_password?(user_params[:password])
prompt_for_two_factor(user)
end
end

View File

@@ -4,7 +4,6 @@ class AuthorizeFollowsController < ApplicationController
layout 'modal'
before_action :authenticate_user!
before_action :set_body_classes
def show
@account = located_account || render(:error)
@@ -59,8 +58,4 @@ class AuthorizeFollowsController < ApplicationController
def acct_params
params.fetch(:acct, '')
end
def set_body_classes
@body_classes = 'modal-layout'
end
end

View File

@@ -1,9 +0,0 @@
# frozen_string_literal: true
module AccountableConcern
extend ActiveSupport::Concern
def log_action(action, target)
Admin::ActionLog.create(account: current_account, action: action, target: target)
end
end

View File

@@ -2,7 +2,6 @@
module Authorization
extend ActiveSupport::Concern
include Pundit
def pundit_user

View File

@@ -44,8 +44,7 @@ module RateLimitHeaders
end
def api_throttle_data
most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_, v| v[:limit] }
request.env['rack.attack.throttle_data'][most_limited_type]
request.env['rack.attack.throttle_data']['api']
end
def request_time

View File

@@ -17,7 +17,6 @@ module UserTrackingConcern
# Mark as signed-in today
current_user.update_tracked_fields!(request)
ActivityTracker.record('activity:logins', current_user.id)
# Regenerate feed if needed
regenerate_feed! if user_needs_feed_update?

View File

@@ -2,16 +2,14 @@
class EmojisController < ApplicationController
before_action :set_emoji
before_action :set_cache_headers
def show
respond_to do |format|
format.json do
skip_session!
render_cached_json(['activitypub', 'emoji', @emoji.cache_key], content_type: 'application/activity+json') do
ActiveModelSerializers::SerializableResource.new(@emoji, serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter)
end
render json: @emoji,
serializer: ActivityPub::EmojiSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
end
end
end

View File

@@ -6,6 +6,7 @@ class HomeController < ApplicationController
def index
@body_classes = 'app-body'
@frontend = (params[:frontend] and Rails.configuration.x.available_frontends.include? params[:frontend] + '.js') ? params[:frontend] : 'mastodon'
end
private

View File

@@ -1,43 +0,0 @@
# frozen_string_literal: true
class InvitesController < ApplicationController
include Authorization
layout 'admin'
before_action :authenticate_user!
def index
authorize :invite, :create?
@invites = Invite.where(user: current_user)
@invite = Invite.new(expires_in: 1.day.to_i)
end
def create
authorize :invite, :create?
@invite = Invite.new(resource_params)
@invite.user = current_user
if @invite.save
redirect_to invites_path
else
@invites = Invite.where(user: current_user)
render :index
end
end
def destroy
@invite = Invite.where(user: current_user).find(params[:id])
authorize @invite, :destroy?
@invite.expire!
redirect_to invites_path
end
private
def resource_params
params.require(:invite).permit(:max_uses, :expires_in)
end
end

View File

@@ -38,8 +38,4 @@ class RemoteFollowController < ApplicationController
def suspended_account?
@account.suspended?
end
def set_body_classes
@body_classes = 'modal-layout'
end
end

View File

@@ -1,34 +0,0 @@
# frozen_string_literal: true
class Settings::MigrationsController < ApplicationController
layout 'admin'
before_action :authenticate_user!
def show
@migration = Form::Migration.new(account: current_account.moved_to_account)
end
def update
@migration = Form::Migration.new(resource_params)
if @migration.valid? && migration_account_changed?
current_account.update!(moved_to_account: @migration.account)
ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
redirect_to settings_migration_path, notice: I18n.t('migrations.updated_msg')
else
render :show
end
end
private
def resource_params
params.require(:migration).permit(:acct)
end
def migration_account_changed?
current_account.moved_to_account_id != @migration.account&.id &&
current_account.id != @migration.account&.id
end
end

View File

@@ -26,7 +26,7 @@ class Settings::NotificationsController < ApplicationController
def user_settings_params
params.require(:user).permit(
notification_emails: %i(follow follow_request reblog favourite mention digest),
interactions: %i(must_be_follower must_be_following must_be_following_dm)
interactions: %i(must_be_follower must_be_following)
)
end
end

View File

@@ -25,6 +25,6 @@ class SharesController < ApplicationController
end
def set_body_classes
@body_classes = 'modal-layout compose-standalone'
@body_classes = 'compose-standalone'
end
end

View File

@@ -10,7 +10,6 @@ class StatusesController < ApplicationController
before_action :set_link_headers
before_action :check_account_suspension
before_action :redirect_to_original, only: [:show]
before_action :set_cache_headers
def show
respond_to do |format|
@@ -22,21 +21,19 @@ class StatusesController < ApplicationController
end
format.json do
skip_session! unless @stream_entry.hidden?
render_cached_json(['activitypub', 'note', @status.cache_key], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter)
end
render json: @status,
serializer: ActivityPub::NoteSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
end
end
end
def activity
skip_session!
render_cached_json(['activitypub', 'activity', @status.cache_key], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter)
end
render json: @status,
serializer: ActivityPub::ActivitySerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
end
def embed

View File

@@ -48,7 +48,7 @@ class StreamEntriesController < ApplicationController
@type = @stream_entry.activity_type.downcase
raise ActiveRecord::RecordNotFound if @stream_entry.activity.nil?
authorize @stream_entry.activity, :show? if @stream_entry.hidden?
authorize @stream_entry.activity, :show? if @stream_entry.hidden? || @stream_entry.local_only?
rescue Mastodon::NotPermittedError
# Reraise in order to get a 404
raise ActiveRecord::RecordNotFound

View File

@@ -1,19 +1,15 @@
# frozen_string_literal: true
module WellKnown
class HostMetaController < ActionController::Base
class HostMetaController < ApplicationController
include RoutingHelper
before_action { response.headers['Vary'] = 'Accept' }
def show
@webfinger_template = "#{webfinger_url}?resource={uri}"
respond_to do |format|
format.xml { render content_type: 'application/xrd+xml' }
end
expires_in(3.days, public: true)
end
end
end

View File

@@ -1,25 +1,23 @@
# frozen_string_literal: true
module WellKnown
class WebfingerController < ActionController::Base
class WebfingerController < ApplicationController
include RoutingHelper
before_action { response.headers['Vary'] = 'Accept' }
def show
@account = Account.find_local!(username_from_resource)
@canonical_account_uri = @account.to_webfinger_s
@magic_key = pem_to_magic_key(@account.keypair.public_key)
respond_to do |format|
format.any(:json, :html) do
render json: @account, serializer: WebfingerSerializer, content_type: 'application/jrd+json'
render formats: :json, content_type: 'application/jrd+json'
end
format.xml do
render content_type: 'application/xrd+xml'
end
end
expires_in(3.days, public: true)
rescue ActiveRecord::RecordNotFound
head 404
end
@@ -37,6 +35,21 @@ module WellKnown
WebfingerResource.new(resource_user).username
end
def pem_to_magic_key(public_key)
modulus, exponent = [public_key.n, public_key.e].map do |component|
result = []
until component.zero?
result << [component % 256].pack('C')
component >>= 8
end
result.reverse.join
end
(['RSA'] + [modulus, exponent].map { |n| Base64.urlsafe_encode64(n) }).join('.')
end
def resource_param
params.require(:resource)
end

View File

@@ -1,103 +0,0 @@
# frozen_string_literal: true
module Admin::ActionLogsHelper
def log_target(log)
if log.target
linkable_log_target(log.target)
else
log_target_from_history(log.target_type, log.recorded_changes)
end
end
def linkable_log_target(record)
case record.class.name
when 'Account'
link_to record.acct, admin_account_path(record.id)
when 'User'
link_to record.account.acct, admin_account_path(record.account_id)
when 'CustomEmoji'
record.shortcode
when 'Report'
link_to "##{record.id}", admin_report_path(record)
when 'DomainBlock', 'EmailDomainBlock'
link_to record.domain, "https://#{record.domain}"
when 'Status'
link_to record.account.acct, TagManager.instance.url_for(record)
end
end
def log_target_from_history(type, attributes)
case type
when 'CustomEmoji'
attributes['shortcode']
when 'DomainBlock', 'EmailDomainBlock'
link_to attributes['domain'], "https://#{attributes['domain']}"
when 'Status'
tmp_status = Status.new(attributes)
link_to tmp_status.account&.acct || "##{tmp_status.account_id}", TagManager.instance.url_for(tmp_status)
end
end
def relevant_log_changes(log)
if log.target_type == 'CustomEmoji' && [:enable, :disable, :destroy].include?(log.action)
log.recorded_changes.slice('domain')
elsif log.target_type == 'CustomEmoji' && log.action == :update
log.recorded_changes.slice('domain', 'visible_in_picker')
elsif log.target_type == 'User' && [:promote, :demote].include?(log.action)
log.recorded_changes.slice('moderator', 'admin')
elsif log.target_type == 'DomainBlock'
log.recorded_changes.slice('severity', 'reject_media')
elsif log.target_type == 'Status' && log.action == :update
log.recorded_changes.slice('sensitive')
end
end
def log_extra_attributes(hash)
safe_join(hash.to_a.map { |key, value| safe_join([content_tag(:span, key, class: 'diff-key'), '=', log_change(value)]) }, ' ')
end
def log_change(val)
return content_tag(:span, val, class: 'diff-neutral') unless val.is_a?(Array)
safe_join([content_tag(:span, val.first, class: 'diff-old'), content_tag(:span, val.last, class: 'diff-new')], '→')
end
def icon_for_log(log)
case log.target_type
when 'Account', 'User'
'user'
when 'CustomEmoji'
'file'
when 'Report'
'flag'
when 'DomainBlock'
'lock'
when 'EmailDomainBlock'
'envelope'
when 'Status'
'pencil'
end
end
def class_for_log_icon(log)
case log.action
when :enable, :unsuspend, :unsilence, :confirm, :promote, :resolve
'positive'
when :create
opposite_verbs?(log) ? 'negative' : 'positive'
when :update, :reset_password, :disable_2fa, :memorialize
'neutral'
when :demote, :silence, :disable, :suspend
'negative'
when :destroy
opposite_verbs?(log) ? 'positive' : 'negative'
else
''
end
end
private
def opposite_verbs?(log)
%w(DomainBlock EmailDomainBlock).include?(log.target_type)
end
end

View File

@@ -1,12 +1,10 @@
# frozen_string_literal: true
module Admin::FilterHelper
ACCOUNT_FILTERS = %i(local remote by_domain silenced suspended recent username display_name email ip staff).freeze
REPORT_FILTERS = %i(resolved account_id target_account_id).freeze
INVITE_FILTER = %i(available expired).freeze
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
ACCOUNT_FILTERS = %i(local remote by_domain silenced suspended recent username display_name email ip).freeze
REPORT_FILTERS = %i(resolved account_id target_account_id).freeze
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS
def filter_link_to(text, link_to_params, link_class_params = link_to_params)
new_url = filtered_url_for(link_to_params)
@@ -14,13 +12,13 @@ module Admin::FilterHelper
link_to text, new_url, class: filter_link_class(new_class)
end
def table_link_to(icon, text, path, **options)
def table_link_to(icon, text, path, options = {})
link_to safe_join([fa_icon(icon), text]), path, options.merge(class: 'table-action-link')
end
def selected?(more_params)
new_url = filtered_url_for(more_params)
filter_link_class(new_url) == 'selected'
filter_link_class(new_url) == 'selected' ? true : false
end
private

View File

@@ -5,7 +5,7 @@ module ApplicationHelper
current_page?(path) ? 'active' : ''
end
def active_link_to(label, path, **options)
def active_link_to(label, path, options = {})
link_to label, path, options.merge(class: active_nav_class(path))
end
@@ -35,11 +35,6 @@ module ApplicationHelper
Rails.env.production? ? site_title : "#{site_title} (Dev)"
end
def can?(action, record)
return false if record.nil?
policy(record).public_send("#{action}?")
end
def fa_icon(icon, attributes = {})
class_names = attributes[:class]&.split(' ') || []
class_names << 'fa'
@@ -48,10 +43,6 @@ module ApplicationHelper
content_tag(:i, nil, attributes.merge(class: class_names.join(' ')))
end
def custom_emoji_tag(custom_emoji)
image_tag(custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:")
end
def opengraph(property, content)
tag(:meta, content: content, property: property)
end

View File

@@ -9,28 +9,6 @@ module JsonLdHelper
value.is_a?(Array) ? value.first : value
end
# The url attribute can be a string, an array of strings, or an array of objects.
# The objects could include a mimeType. Not-included mimeType means it's text/html.
def url_to_href(value, preferred_type = nil)
single_value = if value.is_a?(Array) && !value.first.is_a?(String)
value.find { |link| preferred_type.nil? || ((link['mimeType'].presence || 'text/html') == preferred_type) }
elsif value.is_a?(Array)
value.first
else
value
end
if single_value.nil? || single_value.is_a?(String)
single_value
else
single_value['href']
end
end
def as_array(value)
value.is_a?(Array) ? value : [value]
end
def value_or_id(value)
value.is_a?(String) || value.nil? ? value : value['id']
end

View File

@@ -4,7 +4,6 @@ module RoutingHelper
extend ActiveSupport::Concern
include Rails.application.routes.url_helpers
include ActionView::Helpers::AssetTagHelper
include Webpacker::Helper
included do
def default_url_options
@@ -12,16 +11,12 @@ module RoutingHelper
end
end
def full_asset_url(source, **options)
def full_asset_url(source, options = {})
source = ActionController::Base.helpers.asset_url(source, options) unless use_storage?
URI.join(root_url, source).to_s
end
def full_pack_url(source, **options)
full_asset_url(asset_pack_path(source, options))
end
private
def use_storage?

View File

@@ -10,7 +10,6 @@ module SettingsHelper
eo: 'Esperanto',
es: 'Español',
fa: 'فارسی',
gl: 'Galego',
fi: 'Suomi',
fr: 'Français',
he: 'עברית',
@@ -28,9 +27,6 @@ module SettingsHelper
pt: 'Português',
'pt-BR': 'Português do Brasil',
ru: 'Русский',
sk: 'Slovensky',
sr: 'Српски',
'sr-Latn': 'Srpski (latinica)',
sv: 'Svenska',
th: 'ภาษาไทย',
tr: 'Türkçe',

View File

@@ -0,0 +1,93 @@
/*
`actions/local_settings`
========================
> For more information on the contents of this file, please contact:
>
> - kibigo! [@kibi@glitch.social]
This file provides our Redux actions related to local settings. It
consists of the following:
- __`changesLocalSetting(key, value)` :__
Changes the local setting with the given `key` to the given
`value`. `key` **MUST** be an array of strings, as required by
`Immutable.Map.prototype.getIn()`.
- __`saveLocalSettings()` :__
Saves the local settings to `localStorage` as a JSON object. We
shouldn't ever need to call this ourselves.
*/
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
Constants:
----------
We provide the following constants:
- __`LOCAL_SETTING_CHANGE` :__
This string constant is used to dispatch a setting change to our
reducer in `reducers/local_settings`, where the setting is
actually changed.
*/
export const LOCAL_SETTING_CHANGE = 'LOCAL_SETTING_CHANGE';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
`changeLocalSetting(key, value)`:
---------------------------------
Changes the local setting with the given `key` to the given `value`.
`key` **MUST** be an array of strings, as required by
`Immutable.Map.prototype.getIn()`.
To accomplish this, we just dispatch a `LOCAL_SETTING_CHANGE` to our
reducer in `reducers/local_settings`.
*/
export function changeLocalSetting(key, value) {
return dispatch => {
dispatch({
type: LOCAL_SETTING_CHANGE,
key,
value,
});
dispatch(saveLocalSettings());
};
};
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
`saveLocalSettings()`:
----------------------
Saves the local settings to `localStorage` as a JSON object.
`changeLocalSetting()` calls this whenever it changes a setting. We
shouldn't ever need to call this ourselves.
> __TODO :__
> Right now `saveLocalSettings()` doesn't keep track of which user
> is currently signed in, but it might be better to give each user
> their *own* local settings.
*/
export function saveLocalSettings() {
return (_, getState) => {
const localSettings = getState().get('local_settings').toJS();
localStorage.setItem('mastodon-settings', JSON.stringify(localSettings));
};
};

View File

@@ -0,0 +1,227 @@
/*
`<AccountHeader>`
=================
> For more information on the contents of this file, please contact:
>
> - kibigo! [@kibi@glitch.social]
Original file by @gargron@mastodon.social et al as part of
tootsuite/mastodon. We've expanded it in order to handle user bio
frontmatter.
The `<AccountHeader>` component provides the header for account
timelines. It is a fairly simple component which mostly just consists
of a `render()` method.
__Props:__
- __`account` (`ImmutablePropTypes.map`) :__
The account to render a header for.
- __`me` (`PropTypes.number.isRequired`) :__
The id of the currently-signed-in account.
- __`onFollow` (`PropTypes.func.isRequired`) :__
The function to call when the user clicks the "follow" button.
- __`intl` (`PropTypes.object.isRequired`) :__
Our internationalization object, inserted by `@injectIntl`.
*/
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
Imports:
--------
*/
// Package imports //
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
// Mastodon imports //
import emojify from '../../../mastodon/features/emoji/emoji';
import IconButton from '../../../mastodon/components/icon_button';
import Avatar from '../../../mastodon/components/avatar';
// Our imports //
import { processBio } from '../../util/bio_metadata';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
Inital setup:
-------------
The `messages` constant is used to define any messages that we need
from inside props. In our case, these are the `unfollow`, `follow`, and
`requested` messages used in the `title` of our buttons.
*/
const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
follow: { id: 'account.follow', defaultMessage: 'Follow' },
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
});
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
Implementation:
---------------
*/
@injectIntl
export default class AccountHeader extends ImmutablePureComponent {
static propTypes = {
account : ImmutablePropTypes.map,
me : PropTypes.string.isRequired,
onFollow : PropTypes.func.isRequired,
intl : PropTypes.object.isRequired,
};
/*
### `render()`
The `render()` function is used to render our component.
*/
render () {
const { account, me, intl } = this.props;
/*
If no `account` is provided, then we can't render a header. Otherwise,
we get the `displayName` for the account, if available. If it's blank,
then we set the `displayName` to just be the `username` of the account.
*/
if (!account) {
return null;
}
let displayName = account.get('display_name_html');
let info = '';
let actionBtn = '';
let following = false;
/*
Next, we handle the account relationships. If the account follows the
user, then we add an `info` message. If the user has requested a
follow, then we disable the `actionBtn` and display an hourglass.
Otherwise, if the account isn't blocked, we set the `actionBtn` to the
appropriate icon.
*/
if (me !== account.get('id')) {
if (account.getIn(['relationship', 'followed_by'])) {
info = (
<span className='account--follows-info'>
<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />
</span>
);
}
if (account.getIn(['relationship', 'requested'])) {
actionBtn = (
<div className='account--action-button'>
<IconButton size={26} disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />
</div>
);
} else if (!account.getIn(['relationship', 'blocking'])) {
following = account.getIn(['relationship', 'following']);
actionBtn = (
<div className='account--action-button'>
<IconButton
size={26}
icon={following ? 'user-times' : 'user-plus'}
active={following}
title={intl.formatMessage(following ? messages.unfollow : messages.follow)}
onClick={this.props.onFollow}
/>
</div>
);
}
}
/*
we extract the `text` and
`metadata` from our account's `note` using `processBio()`.
*/
const { text, metadata } = processBio(account.get('note'));
/*
Here, we render our component using all the things we've defined above.
*/
return (
<div className='account__header__wrapper'>
<div
className='account__header'
style={{ backgroundImage: `url(${account.get('header')})` }}
>
<div>
<a href={account.get('url')} target='_blank' rel='noopener'>
<span className='account__header__avatar'>
<Avatar account={account} size={90} />
</span>
<span
className='account__header__display-name'
dangerouslySetInnerHTML={{ __html: displayName }}
/>
</a>
<span className='account__header__username'>
@{account.get('acct')}
{account.get('locked') ? <i className='fa fa-lock' /> : null}
</span>
<div className='account__header__content' dangerouslySetInnerHTML={{ __html: emojify(text) }} />
{info}
{actionBtn}
</div>
</div>
{metadata.length && (
<table className='account__metadata'>
<tbody>
{(() => {
let data = [];
for (let i = 0; i < metadata.length; i++) {
data.push(
<tr key={i}>
<th scope='row'><div dangerouslySetInnerHTML={{ __html: emojify(metadata[i][0]) }} /></th>
<td><div dangerouslySetInnerHTML={{ __html: emojify(metadata[i][1]) }} /></td>
</tr>
);
}
return data;
})()}
</tbody>
</table>
) || null}
</div>
);
}
}

View File

@@ -0,0 +1,80 @@
/*
`<NotificationPurgeButtonsContainer>`
=========================
This container connects `<NotificationPurgeButtons>`s to the Redux store.
*/
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
Imports:
--------
*/
// Package imports //
import { connect } from 'react-redux';
// Our imports //
import NotificationPurgeButtons from './notification_purge_buttons';
import {
deleteMarkedNotifications,
enterNotificationClearingMode,
markAllNotifications,
} from '../../../../mastodon/actions/notifications';
import { defineMessages, injectIntl } from 'react-intl';
import { openModal } from '../../../../mastodon/actions/modal';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
Dispatch mapping:
-----------------
The `mapDispatchToProps()` function maps dispatches to our store to the
various props of our component. We only need to provide a dispatch for
deleting notifications.
*/
const messages = defineMessages({
clearMessage: { id: 'notifications.marked_clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all selected notifications?' },
clearConfirm: { id: 'notifications.marked_clear', defaultMessage: 'Clear selected notifications' },
});
const mapDispatchToProps = (dispatch, { intl }) => ({
onEnterCleaningMode(yes) {
dispatch(enterNotificationClearingMode(yes));
},
onDeleteMarked() {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.clearMessage),
confirm: intl.formatMessage(messages.clearConfirm),
onConfirm: () => dispatch(deleteMarkedNotifications()),
}));
},
onMarkAll() {
dispatch(markAllNotifications(true));
},
onMarkNone() {
dispatch(markAllNotifications(false));
},
onInvert() {
dispatch(markAllNotifications(null));
},
});
const mapStateToProps = state => ({
markNewForDelete: state.getIn(['notifications', 'markNewForDelete']),
});
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(NotificationPurgeButtons));

View File

@@ -0,0 +1,62 @@
/**
* Buttons widget for controlling the notification clearing mode.
* In idle state, the cleaning mode button is shown. When the mode is active,
* a Confirm and Abort buttons are shown in its place.
*/
// Package imports //
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
// Mastodon imports //
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const messages = defineMessages({
btnAll : { id: 'notification_purge.btn_all', defaultMessage: 'Select\nall' },
btnNone : { id: 'notification_purge.btn_none', defaultMessage: 'Select\nnone' },
btnInvert : { id: 'notification_purge.btn_invert', defaultMessage: 'Invert\nselection' },
btnApply : { id: 'notification_purge.btn_apply', defaultMessage: 'Clear\nselected' },
});
@injectIntl
export default class NotificationPurgeButtons extends ImmutablePureComponent {
static propTypes = {
onDeleteMarked : PropTypes.func.isRequired,
onMarkAll : PropTypes.func.isRequired,
onMarkNone : PropTypes.func.isRequired,
onInvert : PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
markNewForDelete: PropTypes.bool,
};
render () {
const { intl, markNewForDelete } = this.props;
//className='active'
return (
<div className='column-header__notif-cleaning-buttons'>
<button onClick={this.props.onMarkAll} className={markNewForDelete ? 'active' : ''}>
<b></b><br />{intl.formatMessage(messages.btnAll)}
</button>
<button onClick={this.props.onMarkNone} className={!markNewForDelete ? 'active' : ''}>
<b></b><br />{intl.formatMessage(messages.btnNone)}
</button>
<button onClick={this.props.onInvert}>
<b>¬</b><br />{intl.formatMessage(messages.btnInvert)}
</button>
<button onClick={this.props.onDeleteMarked}>
<i className='fa fa-trash' /><br />{intl.formatMessage(messages.btnApply)}
</button>
</div>
);
}
}

View File

@@ -0,0 +1,66 @@
/*
`<ComposeAdvancedOptionsContainer>`
===================================
This container connects `<ComposeAdvancedOptions>` to the Redux store.
*/
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
Imports:
--------
*/
// Package imports //
import { connect } from 'react-redux';
// Mastodon imports //
import { toggleComposeAdvancedOption } from '../../../../mastodon/actions/compose';
// Our imports //
import ComposeAdvancedOptions from '.';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
State mapping:
--------------
The `mapStateToProps()` function maps various state properties to the
props of our component. The only property we care about is
`compose.advanced_options`.
*/
const mapStateToProps = state => ({
values: state.getIn(['compose', 'advanced_options']),
});
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
Dispatch mapping:
-----------------
The `mapDispatchToProps()` function maps dispatches to our store to the
various props of our component. We just need to provide a dispatch for
when an advanced option toggle changes.
*/
const mapDispatchToProps = dispatch => ({
onChange (option) {
dispatch(toggleComposeAdvancedOption(option));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(ComposeAdvancedOptions);

View File

@@ -0,0 +1,163 @@
/*
`<ComposeAdvancedOptions>`
==========================
> For more information on the contents of this file, please contact:
>
> - surinna [@srn@dev.glitch.social]
This adds an advanced options dropdown to the toot compose box, for
toggles that don't necessarily fit elsewhere.
__Props:__
- __`values` (`ImmutablePropTypes.contains(…).isRequired`) :__
An Immutable map with the following values:
- __`do_not_federate` (`PropTypes.bool.isRequired`) :__
Specifies whether or not to federate the status.
- __`onChange` (`PropTypes.func.isRequired`) :__
The function to call when a toggle is changed. We pass this from
our container to the toggle.
- __`intl` (`PropTypes.object.isRequired`) :__
Our internationalization object, inserted by `@injectIntl`.
__State:__
- __`open` :__
This tells whether the dropdown is currently open or closed.
*/
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
Imports:
--------
*/
// Package imports //
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, defineMessages } from 'react-intl';
// Our imports //
import ComposeAdvancedOptionsToggle from './toggle';
import ComposeDropdown from '../dropdown/index';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
Inital setup:
-------------
The `messages` constant is used to define any messages that we need
from inside props. These are the various titles and labels on our
toggles.
`iconStyle` styles the icon used for the dropdown button.
*/
const messages = defineMessages({
local_only_short :
{ id: 'advanced-options.local-only.short', defaultMessage: 'Local-only' },
local_only_long :
{ id: 'advanced-options.local-only.long', defaultMessage: 'Do not post to other instances' },
advanced_options_icon_title :
{ id: 'advanced_options.icon_title', defaultMessage: 'Advanced options' },
});
/*
Implementation:
---------------
*/
@injectIntl
export default class ComposeAdvancedOptions extends React.PureComponent {
static propTypes = {
values : ImmutablePropTypes.contains({
do_not_federate : PropTypes.bool.isRequired,
}).isRequired,
onChange : PropTypes.func.isRequired,
intl : PropTypes.object.isRequired,
};
/*
### `render()`
`render()` actually puts our component on the screen.
*/
render () {
const { intl, values } = this.props;
/*
The `options` array provides all of the available advanced options
alongside their icon, text, and name.
*/
const options = [
{ icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, name: 'do_not_federate' },
];
/*
`anyEnabled` tells us if any of our advanced options have been enabled.
*/
const anyEnabled = values.some((enabled) => enabled);
/*
`optionElems` takes our `options` and creates
`<ComposeAdvancedOptionsToggle>`s out of them. We use the `name` of the
toggle as its `key` so that React can keep track of it.
*/
const optionElems = options.map((option) => {
return (
<ComposeAdvancedOptionsToggle
onChange={this.props.onChange}
active={values.get(option.name)}
key={option.name}
name={option.name}
shortText={intl.formatMessage(option.shortText)}
longText={intl.formatMessage(option.longText)}
/>
);
});
/*
Finally, we can render our component.
*/
return (
<ComposeDropdown
title={intl.formatMessage(messages.advanced_options_icon_title)}
icon='home'
highlight={anyEnabled}
>
{optionElems}
</ComposeDropdown>
);
}
}

View File

@@ -0,0 +1,103 @@
/*
`<ComposeAdvancedOptionsToggle>`
================================
> For more information on the contents of this file, please contact:
>
> - surinna [@srn@dev.glitch.social]
This creates the toggle used by `<ComposeAdvancedOptions>`.
__Props:__
- __`onChange` (`PropTypes.func`) :__
This provides the function to call when the toggle is
(de-?)activated.
- __`active` (`PropTypes.bool`) :__
This prop controls whether the toggle is currently active or not.
- __`name` (`PropTypes.string`) :__
This identifies the toggle, and is sent to `onChange()` when it is
called.
- __`shortText` (`PropTypes.string`) :__
This is a short string used as the title of the toggle.
- __`longText` (`PropTypes.string`) :__
This is a longer string used as a subtitle for the toggle.
*/
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
Imports:
--------
*/
// Package imports //
import React from 'react';
import PropTypes from 'prop-types';
import Toggle from 'react-toggle';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
Implementation:
---------------
*/
export default class ComposeAdvancedOptionsToggle extends React.PureComponent {
static propTypes = {
onChange: PropTypes.func.isRequired,
active: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
shortText: PropTypes.string.isRequired,
longText: PropTypes.string.isRequired,
}
/*
### `onToggle()`
The `onToggle()` function simply calls the `onChange()` prop with the
toggle's `name`.
*/
onToggle = () => {
this.props.onChange(this.props.name);
}
/*
### `render()`
The `render()` function is used to render our component. We just render
a `<Toggle>` and place next to it our text.
*/
render() {
const { active, shortText, longText } = this.props;
return (
<div role='button' tabIndex='0' className='advanced-options-dropdown__option' onClick={this.onToggle}>
<div className='advanced-options-dropdown__option__toggle'>
<Toggle checked={active} onChange={this.onToggle} />
</div>
<div className='advanced-options-dropdown__option__content'>
<strong>{shortText}</strong>
{longText}
</div>
</div>
);
}
}

View File

@@ -0,0 +1,133 @@
// Package imports //
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { injectIntl, defineMessages } from 'react-intl';
// Our imports //
import ComposeDropdown from '../dropdown/index';
import { uploadCompose } from '../../../../mastodon/actions/compose';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { openModal } from '../../../../mastodon/actions/modal';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const messages = defineMessages({
upload :
{ id: 'compose.attach.upload', defaultMessage: 'Upload a file' },
doodle :
{ id: 'compose.attach.doodle', defaultMessage: 'Draw something' },
attach :
{ id: 'compose.attach', defaultMessage: 'Attach...' },
});
const mapStateToProps = state => ({
// This horrible expression is copied from vanilla upload_button_container
disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')),
resetFileKey: state.getIn(['compose', 'resetFileKey']),
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
});
const mapDispatchToProps = dispatch => ({
onSelectFile (files) {
dispatch(uploadCompose(files));
},
onOpenDoodle () {
dispatch(openModal('DOODLE', { noEsc: true }));
},
});
@injectIntl
@connect(mapStateToProps, mapDispatchToProps)
export default class ComposeAttachOptions extends ImmutablePureComponent {
static propTypes = {
intl : PropTypes.object.isRequired,
resetFileKey: PropTypes.number,
acceptContentTypes: ImmutablePropTypes.listOf(PropTypes.string).isRequired,
disabled: PropTypes.bool,
onSelectFile: PropTypes.func.isRequired,
onOpenDoodle: PropTypes.func.isRequired,
};
handleItemClick = bt => {
if (bt === 'upload') {
this.fileElement.click();
}
if (bt === 'doodle') {
this.props.onOpenDoodle();
}
this.dropdown.setState({ open: false });
};
handleFileChange = (e) => {
if (e.target.files.length > 0) {
this.props.onSelectFile(e.target.files);
}
}
setFileRef = (c) => {
this.fileElement = c;
}
setDropdownRef = (c) => {
this.dropdown = c;
}
render () {
const { intl, resetFileKey, disabled, acceptContentTypes } = this.props;
const options = [
{ icon: 'cloud-upload', text: messages.upload, name: 'upload' },
{ icon: 'paint-brush', text: messages.doodle, name: 'doodle' },
];
const optionElems = options.map((item) => {
const hdl = () => this.handleItemClick(item.name);
return (
<div
role='button'
tabIndex='0'
key={item.name}
onClick={hdl}
className='privacy-dropdown__option'
>
<div className='privacy-dropdown__option__icon'>
<i className={`fa fa-fw fa-${item.icon}`} />
</div>
<div className='privacy-dropdown__option__content'>
<strong>{intl.formatMessage(item.text)}</strong>
</div>
</div>
);
});
return (
<div>
<ComposeDropdown
title={intl.formatMessage(messages.attach)}
icon='paperclip'
disabled={disabled}
ref={this.setDropdownRef}
>
{optionElems}
</ComposeDropdown>
<input
key={resetFileKey}
ref={this.setFileRef}
type='file'
multiple={false}
accept={acceptContentTypes.toArray().join(',')}
onChange={this.handleFileChange}
disabled={disabled}
style={{ display: 'none' }}
/>
</div>
);
}
}

View File

@@ -0,0 +1,77 @@
// Package imports //
import React from 'react';
import PropTypes from 'prop-types';
// Mastodon imports //
import IconButton from '../../../../mastodon/components/icon_button';
const iconStyle = {
height : null,
lineHeight : '27px',
};
export default class ComposeDropdown extends React.PureComponent {
static propTypes = {
title: PropTypes.string.isRequired,
icon: PropTypes.string,
highlight: PropTypes.bool,
disabled: PropTypes.bool,
children: PropTypes.arrayOf(PropTypes.node).isRequired,
};
state = {
open: false,
};
onGlobalClick = (e) => {
if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
this.setState({ open: false });
}
};
componentDidMount () {
window.addEventListener('click', this.onGlobalClick);
window.addEventListener('touchstart', this.onGlobalClick);
}
componentWillUnmount () {
window.removeEventListener('click', this.onGlobalClick);
window.removeEventListener('touchstart', this.onGlobalClick);
}
onToggleDropdown = () => {
if (this.props.disabled) return;
this.setState({ open: !this.state.open });
};
setRef = (c) => {
this.node = c;
};
render () {
const { open } = this.state;
let { highlight, title, icon, disabled } = this.props;
if (!icon) icon = 'ellipsis-h';
return (
<div ref={this.setRef} className={`advanced-options-dropdown ${open ? 'open' : ''} ${highlight ? 'active' : ''} `}>
<div className='advanced-options-dropdown__value'>
<IconButton
className={'inverted'}
title={title}
icon={icon} active={open || highlight}
size={18}
style={iconStyle}
disabled={disabled}
onClick={this.onToggleDropdown}
/>
</div>
<div className='advanced-options-dropdown__dropdown'>
{this.props.children}
</div>
</div>
);
}
}

View File

@@ -0,0 +1,24 @@
// Package imports //
import { connect } from 'react-redux';
// Mastodon imports //
import { closeModal } from '../../../mastodon/actions/modal';
// Our imports //
import { changeLocalSetting } from '../../../glitch/actions/local_settings';
import LocalSettings from '.';
const mapStateToProps = state => ({
settings: state.get('local_settings'),
});
const mapDispatchToProps = dispatch => ({
onChange (setting, value) {
dispatch(changeLocalSetting(setting, value));
},
onClose () {
dispatch(closeModal());
},
});
export default connect(mapStateToProps, mapDispatchToProps)(LocalSettings);

View File

@@ -0,0 +1,50 @@
// Package imports
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
// Our imports
import LocalSettingsPage from './page';
import LocalSettingsNavigation from './navigation';
// Stylesheet imports
import './style.scss';
export default class LocalSettings extends React.PureComponent {
static propTypes = {
onChange: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
settings: ImmutablePropTypes.map.isRequired,
};
state = {
currentIndex: 0,
};
navigateTo = (index) =>
this.setState({ currentIndex: +index });
render () {
const { navigateTo } = this;
const { onChange, onClose, settings } = this.props;
const { currentIndex } = this.state;
return (
<div className='glitch modal-root__modal local-settings'>
<LocalSettingsNavigation
index={currentIndex}
onClose={onClose}
onNavigate={navigateTo}
/>
<LocalSettingsPage
index={currentIndex}
onChange={onChange}
settings={settings}
/>
</div>
);
}
}

View File

@@ -0,0 +1,74 @@
// Package imports
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, defineMessages } from 'react-intl';
// Our imports
import LocalSettingsNavigationItem from './item';
// Stylesheet imports
import './style.scss';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const messages = defineMessages({
general: { id: 'settings.general', defaultMessage: 'General' },
collapsed: { id: 'settings.collapsed_statuses', defaultMessage: 'Collapsed toots' },
media: { id: 'settings.media', defaultMessage: 'Media' },
preferences: { id: 'settings.preferences', defaultMessage: 'Preferences' },
close: { id: 'settings.close', defaultMessage: 'Close' },
});
@injectIntl
export default class LocalSettingsNavigation extends React.PureComponent {
static propTypes = {
index : PropTypes.number,
intl : PropTypes.object.isRequired,
onClose : PropTypes.func.isRequired,
onNavigate : PropTypes.func.isRequired,
};
render () {
const { index, intl, onClose, onNavigate } = this.props;
return (
<nav className='glitch local-settings__navigation'>
<LocalSettingsNavigationItem
active={index === 0}
index={0}
onNavigate={onNavigate}
title={intl.formatMessage(messages.general)}
/>
<LocalSettingsNavigationItem
active={index === 1}
index={1}
onNavigate={onNavigate}
title={intl.formatMessage(messages.collapsed)}
/>
<LocalSettingsNavigationItem
active={index === 2}
index={2}
onNavigate={onNavigate}
title={intl.formatMessage(messages.media)}
/>
<LocalSettingsNavigationItem
active={index === 3}
href='/settings/preferences'
index={3}
icon='cog'
title={intl.formatMessage(messages.preferences)}
/>
<LocalSettingsNavigationItem
active={index === 4}
className='close'
index={4}
onNavigate={onClose}
title={intl.formatMessage(messages.close)}
/>
</nav>
);
}
}

View File

@@ -0,0 +1,69 @@
// Package imports
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
// Stylesheet imports
import './style.scss';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
export default class LocalSettingsPage extends React.PureComponent {
static propTypes = {
active: PropTypes.bool,
className: PropTypes.string,
href: PropTypes.string,
icon: PropTypes.string,
index: PropTypes.number.isRequired,
onNavigate: PropTypes.func,
title: PropTypes.string,
};
handleClick = (e) => {
const { index, onNavigate } = this.props;
if (onNavigate) {
onNavigate(index);
e.preventDefault();
}
}
render () {
const { handleClick } = this;
const {
active,
className,
href,
icon,
onNavigate,
title,
} = this.props;
const finalClassName = classNames('glitch', 'local-settings__navigation__item', {
active,
}, className);
const iconElem = icon ? <i className={`fa fa-fw fa-${icon}`} /> : null;
if (href) return (
<a
href={href}
className={finalClassName}
>
{iconElem} {title}
</a>
);
else if (onNavigate) return (
<a
onClick={handleClick}
role='button'
tabIndex='0'
className={finalClassName}
>
{iconElem} {title}
</a>
);
else return null;
}
}

View File

@@ -0,0 +1,27 @@
@import 'styles/mastodon/variables';
.glitch.local-settings__navigation__item {
display: block;
padding: 15px 20px;
color: inherit;
background: $primary-text-color;
border-bottom: 1px $ui-primary-color solid;
cursor: pointer;
text-decoration: none;
outline: none;
transition: background .3s;
&:hover {
background: $ui-secondary-color;
}
&.active {
background: $ui-highlight-color;
color: $primary-text-color;
}
&.close, &.close:hover {
background: $error-value-color;
color: $primary-text-color;
}
}

View File

@@ -0,0 +1,10 @@
@import 'styles/mastodon/variables';
.glitch.local-settings__navigation {
background: $primary-text-color;
color: $ui-base-color;
width: 200px;
font-size: 15px;
line-height: 20px;
overflow-y: auto;
}

View File

@@ -0,0 +1,202 @@
// Package imports
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
// Our imports
import LocalSettingsPageItem from './item';
// Stylesheet imports
import './style.scss';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const messages = defineMessages({
layout_auto: { id: 'layout.auto', defaultMessage: 'Auto' },
layout_desktop: { id: 'layout.desktop', defaultMessage: 'Desktop' },
layout_mobile: { id: 'layout.single', defaultMessage: 'Mobile' },
side_arm_none: { id: 'settings.side_arm.none', defaultMessage: 'None' },
});
@injectIntl
export default class LocalSettingsPage extends React.PureComponent {
static propTypes = {
index : PropTypes.number,
intl : PropTypes.object.isRequired,
onChange : PropTypes.func.isRequired,
settings : ImmutablePropTypes.map.isRequired,
};
pages = [
({ intl, onChange, settings }) => (
<div className='glitch local-settings__page general'>
<h1><FormattedMessage id='settings.general' defaultMessage='General' /></h1>
<LocalSettingsPageItem
settings={settings}
item={['layout']}
id='mastodon-settings--layout'
options={[
{ value: 'auto', message: intl.formatMessage(messages.layout_auto) },
{ value: 'multiple', message: intl.formatMessage(messages.layout_desktop) },
{ value: 'single', message: intl.formatMessage(messages.layout_mobile) },
]}
onChange={onChange}
>
<FormattedMessage id='settings.layout' defaultMessage='Layout:' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['stretch']}
id='mastodon-settings--stretch'
onChange={onChange}
>
<FormattedMessage id='settings.wide_view' defaultMessage='Wide view (Desktop mode only)' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['navbar_under']}
id='mastodon-settings--navbar_under'
onChange={onChange}
>
<FormattedMessage id='settings.navbar_under' defaultMessage='Navbar at the bottom (Mobile only)' />
</LocalSettingsPageItem>
<section>
<h2><FormattedMessage id='settings.compose_box_opts' defaultMessage='Compose box options' /></h2>
<LocalSettingsPageItem
settings={settings}
item={['side_arm']}
id='mastodon-settings--side_arm'
options={[
{ value: 'none', message: intl.formatMessage(messages.side_arm_none) },
{ value: 'direct', message: intl.formatMessage({ id: 'privacy.direct.short' }) },
{ value: 'private', message: intl.formatMessage({ id: 'privacy.private.short' }) },
{ value: 'unlisted', message: intl.formatMessage({ id: 'privacy.unlisted.short' }) },
{ value: 'public', message: intl.formatMessage({ id: 'privacy.public.short' }) },
]}
onChange={onChange}
>
<FormattedMessage id='settings.side_arm' defaultMessage='Secondary toot button:' />
</LocalSettingsPageItem>
</section>
</div>
),
({ onChange, settings }) => (
<div className='glitch local-settings__page collapsed'>
<h1><FormattedMessage id='settings.collapsed_statuses' defaultMessage='Collapsed toots' /></h1>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'enabled']}
id='mastodon-settings--collapsed-enabled'
onChange={onChange}
>
<FormattedMessage id='settings.enable_collapsed' defaultMessage='Enable collapsed toots' />
</LocalSettingsPageItem>
<section>
<h2><FormattedMessage id='settings.auto_collapse' defaultMessage='Automatic collapsing' /></h2>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'auto', 'all']}
id='mastodon-settings--collapsed-auto-all'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
>
<FormattedMessage id='settings.auto_collapse_all' defaultMessage='Everything' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'auto', 'notifications']}
id='mastodon-settings--collapsed-auto-notifications'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
>
<FormattedMessage id='settings.auto_collapse_notifications' defaultMessage='Notifications' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'auto', 'lengthy']}
id='mastodon-settings--collapsed-auto-lengthy'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
>
<FormattedMessage id='settings.auto_collapse_lengthy' defaultMessage='Lengthy toots' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'auto', 'replies']}
id='mastodon-settings--collapsed-auto-replies'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
>
<FormattedMessage id='settings.auto_collapse_replies' defaultMessage='Replies' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'auto', 'media']}
id='mastodon-settings--collapsed-auto-media'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
>
<FormattedMessage id='settings.auto_collapse_media' defaultMessage='Toots with media' />
</LocalSettingsPageItem>
</section>
<section>
<h2><FormattedMessage id='settings.image_backgrounds' defaultMessage='Image backgrounds' /></h2>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'backgrounds', 'user_backgrounds']}
id='mastodon-settings--collapsed-user-backgrouns'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
>
<FormattedMessage id='settings.image_backgrounds_users' defaultMessage='Give collapsed toots an image background' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'backgrounds', 'preview_images']}
id='mastodon-settings--collapsed-preview-images'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
>
<FormattedMessage id='settings.image_backgrounds_media' defaultMessage='Preview collapsed toot media' />
</LocalSettingsPageItem>
</section>
</div>
),
({ onChange, settings }) => (
<div className='glitch local-settings__page media'>
<h1><FormattedMessage id='settings.media' defaultMessage='Media' /></h1>
<LocalSettingsPageItem
settings={settings}
item={['media', 'letterbox']}
id='mastodon-settings--media-letterbox'
onChange={onChange}
>
<FormattedMessage id='settings.media_letterbox' defaultMessage='Letterbox media' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['media', 'fullwidth']}
id='mastodon-settings--media-fullwidth'
onChange={onChange}
>
<FormattedMessage id='settings.media_fullwidth' defaultMessage='Full-width media previews' />
</LocalSettingsPageItem>
</div>
),
];
render () {
const { pages } = this;
const { index, intl, onChange, settings } = this.props;
const CurrentPage = pages[index] || pages[0];
return <CurrentPage intl={intl} onChange={onChange} settings={settings} />;
}
}

View File

@@ -0,0 +1,90 @@
// Package imports
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
// Stylesheet imports
import './style.scss';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
export default class LocalSettingsPageItem extends React.PureComponent {
static propTypes = {
children: PropTypes.element.isRequired,
dependsOn: PropTypes.array,
dependsOnNot: PropTypes.array,
id: PropTypes.string.isRequired,
item: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
options: PropTypes.arrayOf(PropTypes.shape({
value: PropTypes.string.isRequired,
message: PropTypes.string.isRequired,
})),
settings: ImmutablePropTypes.map.isRequired,
};
handleChange = e => {
const { target } = e;
const { item, onChange, options } = this.props;
if (options && options.length > 0) onChange(item, target.value);
else onChange(item, target.checked);
}
render () {
const { handleChange } = this;
const { settings, item, id, options, children, dependsOn, dependsOnNot } = this.props;
let enabled = true;
if (dependsOn) {
for (let i = 0; i < dependsOn.length; i++) {
enabled = enabled && settings.getIn(dependsOn[i]);
}
}
if (dependsOnNot) {
for (let i = 0; i < dependsOnNot.length; i++) {
enabled = enabled && !settings.getIn(dependsOnNot[i]);
}
}
if (options && options.length > 0) {
const currentValue = settings.getIn(item);
const optionElems = options && options.length > 0 && options.map((opt) => (
<option
key={opt.value}
value={opt.value}
>
{opt.message}
</option>
));
return (
<label className='glitch local-settings__page__item' htmlFor={id}>
<p>{children}</p>
<p>
<select
id={id}
disabled={!enabled}
onBlur={handleChange}
onChange={handleChange}
value={currentValue}
>
{optionElems}
</select>
</p>
</label>
);
} else return (
<label className='glitch local-settings__page__item' htmlFor={id}>
<input
id={id}
type='checkbox'
checked={settings.getIn(item)}
onChange={handleChange}
disabled={!enabled}
/>
{children}
</label>
);
}
}

View File

@@ -0,0 +1,7 @@
@import 'styles/mastodon/variables';
.glitch.local-settings__page__item {
select {
margin-bottom: 5px;
}
}

View File

@@ -0,0 +1,9 @@
@import 'styles/mastodon/variables';
.glitch.local-settings__page {
display: block;
flex: auto;
padding: 15px 20px 15px 20px;
width: 360px;
overflow-y: auto;
}

View File

@@ -0,0 +1,34 @@
@import 'styles/mastodon/variables';
.glitch.local-settings {
position: relative;
display: flex;
flex-direction: row;
background: $ui-secondary-color;
color: $ui-base-color;
border-radius: 8px;
height: 80vh;
width: 80vw;
max-width: 740px;
max-height: 450px;
overflow: hidden;
label {
display: block;
}
h1 {
font-size: 18px;
font-weight: 500;
line-height: 24px;
margin-bottom: 20px;
}
h2 {
font-size: 15px;
font-weight: 500;
line-height: 20px;
margin-top: 20px;
margin-bottom: 10px;
}
}

View File

@@ -0,0 +1,48 @@
/*
`<NotificationContainer>`
=========================
This container connects `<Notification>`s to the Redux store.
*/
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/*
Imports:
--------
*/
// Package imports //
import { connect } from 'react-redux';
// Our imports //
import Notification from '.';
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const mapStateToProps = (state, props) => {
// replace account id with object
let leNotif = props.notification.set('account', state.getIn(['accounts', props.notification.get('account')]));
// populate markedForDelete from state - is mysteriously lost somewhere
for (let n of state.getIn(['notifications', 'items'])) {
if (n.get('id') === props.notification.get('id')) {
leNotif = leNotif.set('markedForDelete', n.get('markedForDelete'));
break;
}
}
return ({
notification: leNotif,
settings: state.get('local_settings'),
notifCleaning: state.getIn(['notifications', 'cleaningMode']),
});
};
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
export default connect(mapStateToProps)(Notification);

View File

@@ -0,0 +1,72 @@
// `<NotificationFollow>`
// ======================
// * * * * * * * //
// Imports
// -------
// Package imports.
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
// Mastodon imports.
import Permalink from '../../../mastodon/components/permalink';
import AccountContainer from '../../../mastodon/containers/account_container';
// Our imports.
import NotificationOverlayContainer from '../notification/overlay/container';
// * * * * * * * //
// Implementation
// --------------
export default class NotificationFollow extends ImmutablePureComponent {
static propTypes = {
id : PropTypes.string.isRequired,
account : ImmutablePropTypes.map.isRequired,
notification : ImmutablePropTypes.map.isRequired,
};
render () {
const { account, notification } = this.props;
// Links to the display name.
const displayName = account.get('display_name_html') || account.get('username');
const link = (
<Permalink
className='notification__display-name'
href={account.get('url')}
title={account.get('acct')}
to={`/accounts/${account.get('id')}`}
dangerouslySetInnerHTML={{ __html: displayName }}
/>
);
// Renders.
return (
<div className='notification notification-follow'>
<div className='notification__message'>
<div className='notification__favourite-icon-wrapper'>
<i className='fa fa-fw fa-user-plus' />
</div>
<FormattedMessage
id='notification.follow'
defaultMessage='{name} followed you'
values={{ name: link }}
/>
</div>
<AccountContainer id={account.get('id')} withNote={false} />
<NotificationOverlayContainer notification={notification} />
</div>
);
}
}

Some files were not shown because too many files have changed in this diff Show More