Compare commits

...

437 Commits

Author SHA1 Message Date
Tuan 8f4f42da45 Merge branch with upstream 3 years ago
Claire a434f370f3
Merge pull request #1550 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Claire 6dd51448a6 [Glitch] Fix OCR failure when erroneous lang data is in cache 3 years ago
Claire 4aa78027ea Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
Claire 8428faa085
Fix OCR failure when erroneous lang data is in cache (#16386) 3 years ago
dependabot[bot] ee91c8a3a1
Bump rubocop from 1.16.0 to 1.16.1 (#16395) 3 years ago
dependabot[bot] 318b18f46a
Bump doorkeeper from 5.5.1 to 5.5.2 (#16391) 3 years ago
dependabot[bot] 172153d223
Bump webpack-merge from 5.7.3 to 5.8.0 (#16405) 3 years ago
dependabot[bot] 67dc72f749
Bump aws-sdk-s3 from 1.96.0 to 1.96.1 (#16398) 3 years ago
dependabot[bot] 930c7af2d9
Bump brakeman from 5.0.1 to 5.0.4 (#16397) 3 years ago
dependabot[bot] 42dc78569f
Bump redis from 4.2.5 to 4.3.1 (#16396) 3 years ago
dependabot[bot] 883d0ff33b
Bump strong_migrations from 0.7.6 to 0.7.7 (#16393) 3 years ago
dependabot[bot] 6f47cde77d
Bump idn-ruby from 0.1.0 to 0.1.2 (#16392) 3 years ago
dependabot[bot] e22d1698ec
Bump @babel/runtime from 7.14.0 to 7.14.5 (#16404) 3 years ago
dependabot[bot] 30491a2f3b
Bump @babel/core from 7.14.3 to 7.14.5 (#16399) 3 years ago
dependabot[bot] a8f44209c0
Bump @testing-library/jest-dom from 5.13.0 to 5.14.1 (#16403) 3 years ago
dependabot[bot] 0b72aecab3
Bump sidekiq-scheduler from 3.0.1 to 3.1.0 (#16365) 3 years ago
dependabot[bot] a1c7ab968a
Bump rubocop from 1.15.0 to 1.16.0 (#16368) 3 years ago
dependabot[bot] ba3de5cbc6
Bump @testing-library/jest-dom from 5.12.0 to 5.13.0 (#16371) 3 years ago
dependabot[bot] 9abf81d10e
Bump aws-sdk-s3 from 1.95.1 to 1.96.0 (#16372) 3 years ago
dependabot[bot] d7eefab24d
Bump concurrent-ruby from 1.1.8 to 1.1.9 (#16373) 3 years ago
dependabot[bot] 246c0d1146
Bump eslint from 7.27.0 to 7.28.0 (#16374) 3 years ago
dependabot[bot] 6b305ede9b
Bump fastimage from 2.2.3 to 2.2.4 (#16370) 3 years ago
dependabot[bot] d5e8eea632
Bump react-textarea-autosize from 8.3.2 to 8.3.3 (#16375) 3 years ago
dependabot[bot] a128ad33ba
Bump sidekiq-unique-jobs from 7.0.11 to 7.0.12 (#16367) 3 years ago
dependabot[bot] b292b8af33
Bump makara from 0.5.0 to 0.5.1 (#16369) 3 years ago
dependabot[bot] e008671a60
Bump nokogiri from 1.11.6 to 1.11.7 (#16366) 3 years ago
dependabot[bot] 4d8abc5d9c
Bump ox from 2.14.4 to 2.14.5 (#16364) 3 years ago
dependabot[bot] 7b27e080ed
Bump sass from 1.34.0 to 1.34.1 (#16377) 3 years ago
Claire db5baf3d1a
Merge pull request #1547 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Claire 1ac5427ddc [Glitch] Update emoji codepoint mappings to v13.1 3 years ago
Claire fa383531a7 [Glitch] Fix deprecated slash as division in SASS files 3 years ago
Eugen Rochko d2c9f39c0b [Glitch] Add assets from Twemoji 13.1.0 3 years ago
Claire 0157caacef Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
Claire f6088922c0
Update emoji codepoint mappings to v13.1 (#16352) 3 years ago
Eugen Rochko d6486c969f
Bump version to 3.4.1 (#16350) 3 years ago
Eugen Rochko 2fba280353
New Crowdin updates (#16288) 3 years ago
Eugen Rochko 1410dffdf4
Fix e-mail confirmations API not working correctly (#16348) 3 years ago
Claire 11d3c065a5
Fix migration script not being able to run if it fails midway (#16312) 3 years ago
Claire 526332c545
Fix account deletion sometimes failing because of optimistic locks (#16317) 3 years ago
Claire be8079f637
Fix deprecated slash as division in SASS files (#16347) 3 years ago
Claire abf4c2ab21
Fix `tootctl search deploy` on Ruby 3 (#16346) 3 years ago
Eugen Rochko abd7b4636a
Add assets from Twemoji 13.1.0 (#16345) 3 years ago
dependabot[bot] aafac8dc71
Bump babel-jest from 26.6.3 to 27.0.2 (#16338) 3 years ago
dependabot[bot] ea837e4a46
Bump css-loader from 5.2.5 to 5.2.6 (#16335) 3 years ago
dependabot[bot] 0c64bb45e5
Bump nokogiri from 1.11.5 to 1.11.6 (#16332) 3 years ago
dependabot[bot] 19ac24ad85
Bump aws-sdk-s3 from 1.95.0 to 1.95.1 (#16333) 3 years ago
dependabot[bot] 121d15f11c
Bump @babel/preset-env from 7.14.2 to 7.14.4 (#16337) 3 years ago
dependabot[bot] 75bf15212b
Bump eslint-plugin-react from 7.23.2 to 7.24.0 (#16339) 3 years ago
dependabot[bot] 4c41171c54
Bump eslint-plugin-import from 2.23.3 to 2.23.4 (#16334) 3 years ago
dependabot[bot] 1654bea0ab
Bump react-swipeable-views from 0.13.9 to 0.14.0 (#16336) 3 years ago
koyu 8be1ec13ae
Fix spelling error in i18n workflow (#16326) 3 years ago
Claire 3b27b09acb
Fix some IDs in instance actor outbox (#16343) 3 years ago
Claire 02dffa8edd
Merge pull request #1546 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Claire fb8c5979b4 Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
Jeong Arm 5ef216d032
Remove set-cookie header on custom.css (#16314) 3 years ago
dependabot[bot] f173275e33
Bump ws from 7.4.5 to 7.4.6 (#16329) 3 years ago
dependabot[bot] f18e3caa1a
Bump eslint from 7.26.0 to 7.27.0 (#16304) 3 years ago
dependabot[bot] cf08a595af
Bump rubocop from 1.14.0 to 1.15.0 (#16300) 3 years ago
dependabot[bot] 30cdedfa6e
Bump httplog from 1.4.3 to 1.5.0 (#16303) 3 years ago
Claire bbc9ca5d38
Merge pull request #1544 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Claire 10a31a934f Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
dependabot[bot] 2efe711a51
Bump aws-sdk-s3 from 1.94.1 to 1.95.0 (#16298) 3 years ago
dependabot[bot] 8fb9dfa9cd
Bump eslint-plugin-import from 2.23.2 to 2.23.3 (#16299) 3 years ago
dependabot[bot] a2aa51b7e4
Bump nokogiri from 1.11.4 to 1.11.5 (#16301) 3 years ago
dependabot[bot] fa1ce2a6cd
Bump puma from 5.3.1 to 5.3.2 (#16302) 3 years ago
dependabot[bot] 2e759b9c10
Bump sass from 1.33.0 to 1.34.0 (#16307) 3 years ago
dependabot[bot] 1af1d4d015
Bump sidekiq-unique-jobs from 7.0.10 to 7.0.11 (#16296) 3 years ago
dependabot[bot] 01dfa58aab
Bump webpacker from 5.3.0 to 5.4.0 (#16297) 3 years ago
dependabot[bot] 800a6c4424
Bump @babel/core from 7.14.2 to 7.14.3 (#16305) 3 years ago
dependabot[bot] 0783ec18c1
Bump webpack-bundle-analyzer from 4.4.1 to 4.4.2 (#16308) 3 years ago
dependabot[bot] 9e6a9e0774
Bump css-loader from 5.2.4 to 5.2.5 (#16309) 3 years ago
dependabot[bot] a581da059e
Bump dns-packet from 1.3.1 to 1.3.4 (#16319) 3 years ago
dependabot[bot] f4caad0b6b
Bump browserslist from 4.14.5 to 4.16.6 (#16311) 3 years ago
Valentin Lorentz 16b524feb6
Change IRC channel to LiberaChat in README (#16279) 3 years ago
Mélanie Chauvel fd5ab80eed
Fix some typos and improve some UI text (#16283) 3 years ago
Claire b715cede4d
Fix mailer jobs for deleted notifications erroring out (#16294) 3 years ago
Jeong Arm fcdae10072
Ignore git related files from docker build (#16282) 3 years ago
Claire 2024aef0f9
Merge pull request #1542 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Claire 12f8f39e25
Fix media proxy RedisLocks auto-releasing too fast (#16291) 3 years ago
Yamagishi Kazutoshi 1db28332b5
Add Actions for check i18n (#16285) 3 years ago
Zero King 8027d921ab [Glitch] Remove duplicate CSS properties 3 years ago
Claire 1a591ffc8b [Glitch] Fix unread notification count when polling 3 years ago
Zero King bb0c9fcde7 [Glitch] Remove duplicate CSS property of margin 3 years ago
Claire 5f334807d2 Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
dependabot[bot] 92a9fcf0e1
Bump @babel/plugin-transform-runtime from 7.13.15 to 7.14.3 (#16286) 3 years ago
dependabot[bot] d237dd9204
Bump sass from 1.32.12 to 1.33.0 (#16287) 3 years ago
dependabot[bot] 649118714e
Bump @babel/plugin-proposal-decorators from 7.13.15 to 7.14.2 (#16261) 3 years ago
dependabot[bot] d8ac96bd39
Bump @babel/preset-env from 7.14.1 to 7.14.2 (#16267) 3 years ago
dependabot[bot] dc86f709e3
Bump simple-navigation from 4.1.0 to 4.3.0 (#16255) 3 years ago
dependabot[bot] e6265336b6
Bump @testing-library/react from 11.2.6 to 11.2.7 (#16260) 3 years ago
dependabot[bot] 8ce6cc8bf9
Bump faker from 2.17.0 to 2.18.0 (#16259) 3 years ago
dependabot[bot] 0bfb1fecd1
Bump eslint-plugin-import from 2.22.1 to 2.23.2 (#16262) 3 years ago
dependabot[bot] a6b0f0ac83
Bump dotenv from 9.0.1 to 9.0.2 (#16265) 3 years ago
dependabot[bot] 6a9389fab8
Bump sass-loader from 10.1.1 to 10.2.0 (#16266) 3 years ago
dependabot[bot] 3012a12f02
Bump webmock from 3.12.2 to 3.13.0 (#16254) 3 years ago
dependabot[bot] 126e51e71e
Bump sidekiq-unique-jobs from 7.0.9 to 7.0.10 (#16253) 3 years ago
dependabot[bot] 6d491d0bba
Bump @babel/plugin-transform-runtime from 7.13.15 to 7.14.2 (#16263) 3 years ago
dependabot[bot] 970f59738f
Bump @babel/core from 7.14.0 to 7.14.2 (#16258) 3 years ago
dependabot[bot] 85f5689a49
Bump react-select from 4.3.0 to 4.3.1 (#16268) 3 years ago
Eugen Rochko 01adffdecb
New Crowdin updates (#16281) 3 years ago
Eugen Rochko 7f0d58b478
New Crowdin updates (#16269) 3 years ago
Claire 9a19227f17
Fix some RedisLocks auto-releasing too fast (#16276) 3 years ago
Zero King 028ba13eb3
Remove duplicate CSS properties (#16278) 3 years ago
Claire 92f1d739b5
Fix unread notification count when polling (#16272) 3 years ago
Zero King 689974b1ed
Remove duplicate CSS property of margin (#16277) 3 years ago
Claire 03952c7d49
Merge pull request #1541 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Claire f3d96d4ecb Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
dependabot[bot] 08d6a7764f
Bump nokogiri from 1.11.3 to 1.11.4 (#16252) 3 years ago
dependabot[bot] 3f599c3433
Bump puma from 5.3.0 to 5.3.1 (#16257) 3 years ago
Claire 97539b6a96
Fix host check on healthcheck path not being disabled (#16270) 3 years ago
Mélanie Chauvel d137d2ab87
Replace “status” and “message” by “post” in WebUI (#16271) 3 years ago
Claire 567745c1e6
Merge pull request #1539 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Claire 69737b703a Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
Eugen Rochko 4c7efdba40
Bump version to 3.4.0 (#16239) 3 years ago
Eugen Rochko d862728ae1
Add more checks to `repo:check_locales_files` (#16249) 3 years ago
Eugen Rochko 132adcc8da
New Crowdin updates (#16241) 3 years ago
Yamagishi Kazutoshi 8cdeaf49ac
Fix security policy (#16248) 3 years ago
Eugen Rochko cf699f7bf9
Add pull request title recommendations to CONTRIBUTING.md (#16247) 3 years ago
Eugen Rochko e92f312aab
Change Node.js requirement in README (#16246) 3 years ago
dogelover911 52c4e17f7e
Add width and height to attachments in ActivityPub (#16245) 3 years ago
Jeong Arm f09322f9cc
Disable host check on healthcheck path (#16243) 3 years ago
Eugen Rochko 6528f8162e
New Crowdin updates (#16207) 3 years ago
Claire 76064e6608
Update fix-duplicates maintenance script to support latest migrations (#16231) 3 years ago
chandrn7 2840f995d5
updated node version in Vagrantfile (#16230) 3 years ago
Claire 678e07c544 Fix local-only toggle being buggy when replying to remote toot 3 years ago
Claire db2f6fa49b
Merge pull request #1535 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Claire 671a95c545 [Glitch] Fix follow recommendations UI in advanced layout 3 years ago
Mélanie Chauvel 67c2c0a41e [Glitch] Fix dialog close button 3 years ago
Claire dc58d02192 Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
Claire 70f6f2e9b7
Fix empty CW when a content-less toot with a CW is encountered twice (#16220) 3 years ago
Claire 4f747d9f83
Fix follow recommendations UI in advanced layout (#16215) 3 years ago
Mélanie Chauvel 0464240f19
Fix dialog close button (#16219) 3 years ago
rinsuki 5ed5f62705
Fix animated GIF generates animated thumbnail (#16216) 3 years ago
Eugen Rochko be353bccbb
Fix error when rendering actor with hashtags in bio (#16218) 3 years ago
Takeshi Umeda c403c3695b
Fix to be able to redownload avatar and header (#16190) 3 years ago
abcang b5ad787ebf
Fix rubocop warning (#16214) 3 years ago
abcang 7032d4f582
Ignore brakeman false positive warning (#16213) 3 years ago
abcang be6f4e013a
Fix eslint error (#16212) 3 years ago
Eugen Rochko 7bd2b54a46
Bump version to 3.4.0rc2 (#16206) 3 years ago
Yamagishi Kazutoshi a097ec997d
Run `i18n-tasks normalize` (#16208) 3 years ago
Claire ffc3f8eebe
Merge pull request #1534 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Claire 7ac8bcf209 Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
dependabot[bot] d279666bd4
Bump brakeman from 5.0.0 to 5.0.1 (#16146) 3 years ago
Eugen Rochko f8d2dbdefa
New Crowdin updates (#16183) 3 years ago
dependabot[bot] a8cce402e0
Bump rubocop from 1.13.0 to 1.14.0 (#16192) 3 years ago
dependabot[bot] 7342d3b132
Bump glob from 7.1.6 to 7.1.7 (#16204) 3 years ago
dependabot[bot] 6ac84c1e33
Bump @babel/preset-env from 7.14.0 to 7.14.1 (#16203) 3 years ago
dependabot[bot] 74fa5542a6
Bump yargs from 17.0.0 to 17.0.1 (#16201) 3 years ago
dependabot[bot] 3605df1e6f
Bump eslint from 7.25.0 to 7.26.0 (#16200) 3 years ago
dependabot[bot] 25ee7c7be6
Bump utf-8-validate from 5.0.4 to 5.0.5 (#16199) 3 years ago
dependabot[bot] 04e4e864c1
Bump rqrcode from 1.2.0 to 2.0.0 (#16198) 3 years ago
dependabot[bot] cd80ab8826
Bump stackprof from 0.2.16 to 0.2.17 (#16197) 3 years ago
dependabot[bot] 43e8b4982f
Bump rubocop-rails from 2.9.1 to 2.10.1 (#16193) 3 years ago
dependabot[bot] a4243eefdc
Bump aws-sdk-s3 from 1.94.0 to 1.94.1 (#16191) 3 years ago
dependabot[bot] 10e95ed0c8
Bump dotenv from 8.2.0 to 9.0.1 (#16202) 3 years ago
Claire afb7882189
Fix blocking someone not clearing up list feeds (#16205) 3 years ago
dependabot[bot] 10cd2d1e86
Bump puma from 5.2.2 to 5.3.0 (#16194) 3 years ago
dependabot[bot] 08769fd69f
Bump rails from 6.1.3.1 to 6.1.3.2 (#16196) 3 years ago
Claire fc8baba8cb
Merge pull request #1533 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Mélanie Chauvel 299ab28d5f [Glitch] Make media_gallery.toggle_visible less confusing to translate 3 years ago
Claire 87c3a0d0b3 Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
Takeshi Umeda 9b18914c35
Add a Redis environment variable for sidekiq (#16188) 3 years ago
Claire f9c2d1b9a1
Fix FollowRecommendationsScheduler failing because of unpopulated views (#16189) 3 years ago
rinsuki 4d31cef19d
Fix breaking change about format of accounts.created_at (#16186) 3 years ago
Eugen Rochko d1442a06c3
Bump version to 3.4.0rc1 (#16053) 3 years ago
Eugen Rochko bd1abac370
New Crowdin updates (#16094) 3 years ago
Takeshi Umeda 9da5e0b350
Fix webfinger_update_due to run WebFinger on stale activitypub-account (#16182) 3 years ago
Mélanie Chauvel 68181b9506
Make media_gallery.toggle_visible less confusing to translate (#15993) 3 years ago
Claire 5233e99106
Merge pull request #1531 from ClearlyClaire/glitch-soc/features/upstream-media-modal 3 years ago
Claire df82963ea4
Merge pull request #1532 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Claire fbf097f7e1 Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
Marcin Mikołajczak 196cef8977 [Glitch] Add transition to media modal background 3 years ago
Takeshi Umeda 34cc6f45ac [Glitch] Fix expand video on public page 3 years ago
ThibG d8e756eda8 [Glitch] Fix media modal buttons not showing up on mobile 3 years ago
Eugen Rochko d973a90bcc [Glitch] Fix not being able to open audio modal in web UI 3 years ago
Eugen Rochko f29f8caccb [Glitch] Fix too low contrast on new media modal background in web UI 3 years ago
Eugen Rochko cccc65651d [Glitch] Fix media modal crashing when media has no blurhash 3 years ago
Eugen Rochko 9dde2400d0 [Glitch] Fix media modal regression on public pages 3 years ago
Eugen Rochko 50b100df00 Change media modals look in web UI 3 years ago
dependabot[bot] 81b19e28ff
Bump hosted-git-info from 2.8.8 to 2.8.9 (#16177) 3 years ago
Eugen Rochko d30dd5b1db [Glitch] Change home timeline to reload after follow recommendations in web UI 3 years ago
Eugen Rochko 5fa4138bd8 [Glitch] Fix "You might be interested in" flashing while searching in web UI 3 years ago
Eugen Rochko b571bc3278 [Glitch] Add empty state message for follow recommendations in web UI 3 years ago
Claire 56135a77f9 [Glitch] Hide floating action button on onboarding page 3 years ago
Eugen Rochko 2f2c77c747 [Glitch] Change follow recommendations to be limited to 20 instead of 40 in web UI 3 years ago
Eugen Rochko 0936ae4132 [Glitch] Fix newlines not being considered sentence separators in account note 3 years ago
Eugen Rochko 89bc4dff6f [Glitch] Change onboarding by replacing tutorial with follow recommendations in web UI 3 years ago
Claire 8615848348 Disable onboarding modal (keep it in “Show me around” from getting started menu) 3 years ago
Claire 2ad7bf08b4
Bump nodejs version requirement to node 12 (#16175) 3 years ago
Claire 50f8ee2e07
Merge pull request #1526 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Eugen Rochko 1294f9ee4f
Remove PubSubHubbub-related columns from accounts table (#16170) 3 years ago
Eugen Rochko e08b31a706 [Glitch] Add joined date to profiles in web UI 3 years ago
fusagiko / takayamaki dbb942999c [Glitch] fix component name 3 years ago
Claire 50b430d9a2 Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
Claire d8e0c8a89e
Do not populate AccountSummaries and FollowRecommendation views on migration (#16173) 3 years ago
Claire a5f91a11d0
Fix older migrations on Ruby 3 (#16174) 3 years ago
Eugen Rochko 0ad240cb6b
Change home timeline to reload after follow recommendations in web UI (#16160) 3 years ago
Eugen Rochko 74081433d0
Change trending hashtags to be affected be reblogs (#16164) 3 years ago
Eugen Rochko 2c77d97e0d
Add joined date to profiles in web UI (#16169) 3 years ago
dependabot[bot] b42a8ef7d9
Bump url-parse from 1.4.7 to 1.5.1 (#16172) 3 years ago
dependabot[bot] b44585aa89
Bump webpacker from 5.2.1 to 5.3.0 (#16144) 3 years ago
dependabot[bot] 9e5bdd8007
Bump devise from 4.7.3 to 4.8.0 (#16140) 3 years ago
dependabot[bot] 09f051d0b7
Bump yargs from 16.2.0 to 17.0.0 (#16149) 3 years ago
Claire 566fc90913
Add Ruby 3.0 support (#16046) 3 years ago
Eugen Rochko 0a3fa034fc
Fix "You might be interested in" flashing while searching in web UI (#16162) 3 years ago
Eugen Rochko 8d75bd002d
Add empty state message for follow recommendations in web UI (#16161) 3 years ago
Eugen Rochko 6d9ad30bf8
Fix media redownload worker retrying on unexpected response codes (#16111) 3 years ago
Claire aa1b43f467
Fix display of toots without text content (#15665) 3 years ago
Takeshi Umeda 7cb34b32f8
Add management of delivery availability in Federation settings (#15771) 3 years ago
Claire d9ae3db8d5
Improve performance of follow recommendation scheduler (#16159) 3 years ago
Eugen Rochko 351c744590
Fix error when trying to render component for media without meta (#16112) 3 years ago
Eugen Rochko 059df83d1d
Fix database serialization failure returning HTTP 500 (#16101) 3 years ago
Eugen Rochko 036556d350
Fix media processing getting stuck on too much stdin/stderr (#16136) 3 years ago
Claire dfa002932d
Workaround Resolv::DNS-induced hangs by installing resolv 0.1.0 (#16157) 3 years ago
Takeshi Umeda 25345c90ff
Fix how to change connection pool for rails 6 (#16158) 3 years ago
Mélanie Chauvel 19d3809ad5
Improve description of keyboard shortcuts (#16129) 3 years ago
Yamagishi Kazutoshi dde0529fbc
Remove dependency for @babel/plugin-proposal-class-properties (#16155) 3 years ago
Claire d95128c99d
Revert default Ruby version to 2.7.2 (#16154) 3 years ago
Eugen Rochko 3639862dee
Fix existing username validator not allowing multiple accounts (#16153) 3 years ago
Eugen Rochko fab65848d2
Fix empty home feed before first follow has finished processing (#16152) 3 years ago
Claire 8c44b723bb
Change confirmations controller to redirect to / for approved users (#16151) 3 years ago
dependabot[bot] 09bb92875d
Bump mini-css-extract-plugin from 1.5.0 to 1.6.0 (#16150) 3 years ago
dependabot[bot] 8a33b7ac49
Bump @babel/preset-env from 7.13.15 to 7.14.0 (#16145) 3 years ago
fusagiko / takayamaki c35a6b9e01
fix component name (#16138) 3 years ago
dependabot[bot] b75ecb083f
Bump @babel/runtime from 7.13.17 to 7.14.0 (#16148) 3 years ago
dependabot[bot] 970ffd8d1a
Bump aws-sdk-s3 from 1.93.1 to 1.94.0 (#16143) 3 years ago
dependabot[bot] c35befb3e4
Bump @babel/core from 7.13.16 to 7.14.0 (#16141) 3 years ago
dependabot[bot] bcf63e6e26
Bump sass from 1.32.11 to 1.32.12 (#16142) 3 years ago
dependabot[bot] a2b3a5f42b
Bump marky from 1.2.1 to 1.2.2 (#16147) 3 years ago
dependabot[bot] 60446ddd4f
Bump sidekiq-unique-jobs from 7.0.8 to 7.0.9 (#16139) 3 years ago
Eugen Rochko c5c46dd6ee
Fix "cb is not a function" error in streaming API server (#16134) 3 years ago
Eugen Rochko aafe65a142
Change log level of worker start/end to warn in streaming API (#16110) 3 years ago
Eugen Rochko f627d2eb93
Fix trying to fetch key from empty URI when verifying HTTP signature (#16100) 3 years ago
Takeshi Umeda 422df9d670
Fix cache redis not being used (#16131) 3 years ago
Yurii Izorkin 7da104eb11
templates/systemd/mastodon: optimize SystemCallFilters (#16127) 3 years ago
Ikko Ashimine 0bc909687a
Fix typo in db.rake (#16126) 3 years ago
abcang d0fc69d721
Further improve the media attached status query for accounts (#16106) 3 years ago
Eugen Rochko f78cbc0c32
Fix thread resolve worker retrying when status no longer exists (#16109) 3 years ago
dependabot[bot] c4deca6a21
Bump redux from 4.0.5 to 4.1.0 (#16117) 3 years ago
dependabot[bot] d4d19706f8
Bump rubocop from 1.12.1 to 1.13.0 (#16115) 3 years ago
dependabot[bot] 588c6978d3
Bump @testing-library/jest-dom from 5.11.10 to 5.12.0 (#16113) 3 years ago
dependabot[bot] 06d2bfc8b2
Bump redis from 3.1.1 to 3.1.2 (#16121) 3 years ago
dependabot[bot] 9a31c5999a
Bump react-redux from 7.2.3 to 7.2.4 (#16122) 3 years ago
dependabot[bot] 082465f8d0
Bump webpack-assets-manifest from 4.0.5 to 4.0.6 (#16123) 3 years ago
dependabot[bot] 0591ef3f05
Bump eslint from 7.24.0 to 7.25.0 (#16120) 3 years ago
dependabot[bot] ede4d6da4d
Bump css-loader from 5.2.2 to 5.2.4 (#16119) 3 years ago
dependabot[bot] 87b3ce73c0
Bump sass from 1.32.10 to 1.32.11 (#16116) 3 years ago
Claire a346912030 Add environment variables to control custom emoji size limits 3 years ago
dependabot[bot] a5763fb225
Bump @babel/core from 7.13.15 to 7.13.16 (#16114) 3 years ago
dependabot[bot] 49541f29c8
Bump @babel/runtime from 7.13.10 to 7.13.17 (#16118) 3 years ago
abcang 1f47511023
Improve media attached status query (#16105) 3 years ago
abcang 7f0c49c58a
Improve tag search query (#16104) 3 years ago
Eugen Rochko daccc07dc1
Change auto-following admin-selected accounts, show in recommendations (#16078) 3 years ago
Yurii Izorkin 863ae47b51
templates/systemd/mastodon: update sandbox mode (#16103) 3 years ago
Eugen Rochko f4b7c6b619
Fix nil error when removing status caused by race condition (#16099) 3 years ago
Claire a6564d56d6
Fix edge case where accepted follow cannot be processed because of follow limit (#16098) 3 years ago
Takeshi Umeda 2360191434
Fix guard against DNS rebinding attacks (#16095) 3 years ago
Eugen Rochko 2eb17360df
Fix delete of local reply to local parent not being forwarded (#16096) 3 years ago
Eugen Rochko 3230c244f9
Restore `es` locale removed by Crowdin (#16092) 3 years ago
Eugen Rochko cafc7ad064
Add af, gd and si locales (#16090) 3 years ago
Eugen Rochko e39925a7d1
New Crowdin updates (#16088) 3 years ago
Eugen Rochko 8d5ab51c61
Change the noun 'toot' to 'post' in simple_form.en.yml as well (#16089) 3 years ago
Eugen Rochko c6d8ee99a0
New Crowdin updates (#15719) 3 years ago
Claire a627e59116
Merge pull request #1523 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Eugen Rochko 9cc283f0b4
Change the nouns "toot" and "status" to "post" (#16080) 3 years ago
Takeshi Umeda 8323023464
Add guard against DNS rebinding attacks (#16087) 3 years ago
Claire e243092a5a
Add DM icon back on HTML view of DMs (#16086) 3 years ago
Claire 536892b8ae Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
Takeshi Umeda b1d4f21db0 [Glitch] Fix not to show follow button in global suggestion 3 years ago
Takeshi Umeda 72eac238ba [Glitch] Fix to update suggestion list after dismiss 3 years ago
Claire 8fc0b592cb [Glitch] Add border to 🚲 emoji 3 years ago
Eugen Rochko e6501d5112 [Glitch] Add cold-start follow recommendations 3 years ago
Claire 0b36e3419d
Fix processing of remote Delete activities (#16084) 3 years ago
Claire 2c322addf3
Hide floating action button on onboarding page (#16082) 3 years ago
Claire 97da7b7307
Merge pull request #1521 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Eugen Rochko 7762d3d275
Change follow recommendations to be limited to 20 instead of 40 in web UI (#16077) 3 years ago
Eugen Rochko 23b102f661
Add "recommended" label to activity/peers API toggles in admin UI (#16081) 3 years ago
Claire 4b115d070c Fix the follow recommendation admin page on glitch-soc 3 years ago
Claire e2a2bc9021 Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
Eugen Rochko b5ac17c4b6
Fix newlines not being considered sentence separators in account note (#16079) 3 years ago
dependabot[bot] 18a2589ad3
Bump webpack-assets-manifest from 4.0.4 to 4.0.5 (#16070) 3 years ago
dependabot[bot] 969c8cbcfe
Bump js-yaml from 4.0.0 to 4.1.0 (#16067) 3 years ago
dependabot[bot] a16c726d6d
Bump aws-sdk-s3 from 1.93.0 to 1.93.1 (#16071) 3 years ago
dependabot[bot] 4f54602165
Bump mini-css-extract-plugin from 1.4.1 to 1.5.0 (#16061) 3 years ago
dependabot[bot] 613b04446e
Bump css-loader from 5.2.1 to 5.2.2 (#16074) 3 years ago
dependabot[bot] eb7813836a
Bump sass from 1.32.8 to 1.32.10 (#16063) 3 years ago
dependabot[bot] 01c5922dbc
Bump webpack-bundle-analyzer from 4.4.0 to 4.4.1 (#16073) 3 years ago
Claire 1efcbb9cfe
Add Message-ID header to outgoing emails (#16076) 3 years ago
dependabot[bot] 5b384d1a26
Bump redis from 3.1.0 to 3.1.1 (#16065) 3 years ago
dependabot[bot] 5a036e1274
Bump ws from 7.4.4 to 7.4.5 (#16072) 3 years ago
dependabot[bot] 6ce9f4f403
Bump ssri from 6.0.1 to 6.0.2 (#16075) 3 years ago
dependabot[bot] dee0f2e8f0
Bump tty-prompt from 0.23.0 to 0.23.1 (#16066) 3 years ago
dependabot[bot] 43f42310ae
Bump cld3 from 3.4.1 to 3.4.2 (#16069) 3 years ago
dependabot[bot] cf1b874a3e
Bump oj from 3.11.3 to 3.11.5 (#16068) 3 years ago
dependabot[bot] 3b2744eb21
Bump connection_pool from 2.2.3 to 2.2.5 (#16062) 3 years ago
dependabot[bot] 64688b536a
Bump sidekiq-unique-jobs from 7.0.7 to 7.0.8 (#16064) 3 years ago
Eugen Rochko bf903dc510
Change onboarding by replacing tutorial with follow recommendations in web UI (#16060) 3 years ago
Eugen Rochko ca3bc1b09f
Refactor StatusReachFinder to handle followers and relays as well (#16051) 3 years ago
Eugen Rochko 6d6000f61f
Fix remote reporters not receiving suspend/unsuspend activities (#16050) 3 years ago
Eugen Rochko 480d7c9478
Fix missing source strings and inconsistent lead text style in admin UI (#16052) 3 years ago
Eugen Rochko b3ceb3dcc4
Add canonical e-mail blocks for suspended accounts (#16049) 3 years ago
Eugen Rochko 170e05db12
Fix wrong timestamp_id identifier for accounts table in schema.rb (#16048) 3 years ago
Eugen Rochko dde8739020
Fix reports of already suspended accounts being recorded (#16047) 3 years ago
Takeshi Umeda baed52c2a7
Fix not to show follow button in global suggestion (#16045) 3 years ago
Takeshi Umeda 9bb3341849
Fix to update suggestion list after dismiss (#16044) 3 years ago
Eugen Rochko 3b8d085436
Fix app name, website and redirect URIs not having a maximum length (#16042) 3 years ago
Eugen Rochko 3d82a1de05
Change option labels on edit profile page (#16041) 3 years ago
Claire d5edf22d91
Change account ids to snowflake ids (#15844) 3 years ago
Eugen Rochko ce2148c571
Add `policy` param to `POST /api/v1/push/subscriptions` (#16040) 3 years ago
Takeshi Umeda c968d22ee9
Fix an error with 'multiple mentions with same username' (#16038) 3 years ago
Claire e78d06eecf
Add border to 🚲 emoji (#16035) 3 years ago
Claire 71f335c2fc
Add HTTP header to explicitly opt out of FLoC by default (#16036) 3 years ago
Eugen Rochko bb68a9570e
Bump nsa from git to 0.2.8 (#16033) 3 years ago
dependabot[bot] 4ebded04f6
Bump eslint-plugin-promise from 4.3.1 to 5.1.0 (#16022) 3 years ago
dependabot[bot] 78717bb7b1
Bump css-loader from 5.2.0 to 5.2.1 (#16029) 3 years ago
dependabot[bot] 26608624bb
Bump @babel/preset-env from 7.13.12 to 7.13.15 (#16028) 3 years ago
dependabot[bot] 8d416b2e69
Bump @babel/core from 7.13.14 to 7.13.15 (#16027) 3 years ago
dependabot[bot] 7ad074f3f0
Bump webpack-assets-manifest from 4.0.2 to 4.0.4 (#16025) 3 years ago
dependabot[bot] a50ffc7e3d
Bump eslint-plugin-react from 7.23.1 to 7.23.2 (#16030) 3 years ago
dependabot[bot] 6bcb3f863b
Bump mini-css-extract-plugin from 1.4.0 to 1.4.1 (#16031) 3 years ago
dependabot[bot] 8836b97ee9
Bump @babel/plugin-transform-runtime from 7.13.10 to 7.13.15 (#16023) 3 years ago
dependabot[bot] 24f7979dde
Bump eslint from 7.23.0 to 7.24.0 (#16018) 3 years ago
dependabot[bot] 06e9dced2a
Bump @babel/plugin-proposal-decorators from 7.13.5 to 7.13.15 (#16021) 3 years ago
dependabot[bot] a5dd162dc5
Bump cssnano from 4.1.10 to 4.1.11 (#16020) 3 years ago
dependabot[bot] 04fe071279
Bump parallel_tests from 3.6.0 to 3.7.0 (#16024) 3 years ago
Eugen Rochko 120965eb0b
Change Web Push API deliveries to use request pooling (#16014) 3 years ago
dependabot[bot] 463875f645
Bump pkg-config from 1.4.5 to 1.4.6 (#16019) 3 years ago
dependabot[bot] 8c1b1a536d
Bump doorkeeper from 5.5.0 to 5.5.1 (#16016) 3 years ago
dependabot[bot] 2e4ae2fc7c
Bump nokogiri from 1.11.2 to 1.11.3 (#16017) 3 years ago
dependabot[bot] 290591333a
Bump sidekiq from 6.2.0 to 6.2.1 (#16026) 3 years ago
Eugen Rochko f7117646af
Add cold-start follow recommendations (#15945) 3 years ago
Eugen Rochko ad61265268
Remove dependency on pluck_each gem (#16012) 3 years ago
Eugen Rochko 619fad6cf8
Remove spam check and dependency on nilsimsa gem (#16011) 3 years ago
Eugen Rochko 7183d9a113
Change multiple mentions with same username to render with domain (#15718) 3 years ago
Eugen Rochko b3e9094e14
Bump devise-two-factor from git to 4.0.0 (#15987) 3 years ago
Daigo 3 Dango 3f8d0de82e
Upgrade Ruby to 2.7.3 (#16004) 3 years ago
Eugen Rochko 3f2533ca8e
Fix autoloading deprecation warnings from Rails 6 (#16010) 3 years ago
Eugen Rochko ed7d459d7f
Fix deprecation warning for Sidekiq web session secret (#16009) 3 years ago
Sean bf74a7e06d
Update copyright year (#16003) 3 years ago
Claire df326b8b5c
Merge pull request #1519 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Claire c5fe0864d1
Fix SidekiqProcessCheck checking for a queue name that isn't used in Mastodon (#16002) 3 years ago
Claire 5c225b03db Fix glitch-soc-specific health check spec 3 years ago
dependabot[bot] 2fe84f194b
Bump redis from 3.0.2 to 3.1.0 (#15998) 3 years ago
dependabot[bot] c55bd01cf9
Bump classnames from 2.2.6 to 2.3.1 (#16000) 3 years ago
Eugen Rochko 2ae8c41e5d [Glitch] Add system checks to dashboard in admin UI 3 years ago
Claire 6be0b4b014 [Glitch] Fix crash in old browsers 3 years ago
Claire c901ae77d4 Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
Noiob 39b9a0619a Remove superfluous toot length check 3 years ago
Claire 117f6638d0
Fix SVG files not being correctly included in templates (#16001) 3 years ago
dependabot[bot] 3511797e3f
Bump rubocop from 1.12.0 to 1.12.1 (#15996) 3 years ago
dependabot[bot] c847f83772
Bump webpack-assets-manifest from 4.0.1 to 4.0.2 (#15999) 3 years ago
dependabot[bot] 47d093f058
Bump @testing-library/react from 11.2.5 to 11.2.6 (#15997) 3 years ago
Eugen Rochko 487e37d6d4
Add system checks to dashboard in admin UI (#15989) 3 years ago
Eugen Rochko 82cce18227
Change health check (#15988) 3 years ago
Claire abad99fa10
Fix crash in old browsers (#15985) 3 years ago
abcang ddabbbf5a6
Fix DB connection pool settings in CLI (#15983) 3 years ago
Claire b7ec2a9002
Merge pull request #1517 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Claire 15efa32cca Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
dependabot[bot] f5bcc6bc65
Bump react-select from 4.0.2 to 4.3.0 (#15969) 3 years ago
dependabot[bot] 8391ac55a8
Bump eslint from 7.22.0 to 7.23.0 (#15971) 3 years ago
dependabot[bot] 2d43f6b8d0
Bump @babel/preset-react from 7.12.13 to 7.13.13 (#15965) 3 years ago
dependabot[bot] 4a062d1c14
Bump react-redux from 7.2.2 to 7.2.3 (#15963) 3 years ago
dependabot[bot] bc0eb29446
Bump @babel/core from 7.13.10 to 7.13.14 (#15979) 3 years ago
dependabot[bot] a394b08fd7
Bump aws-sdk-s3 from 1.92.0 to 1.93.0 (#15973) 3 years ago
dependabot[bot] 6fca11f0b8
Bump eslint-plugin-react from 7.22.0 to 7.23.1 (#15967) 3 years ago
dependabot[bot] 6b2eacbeeb
Bump rubocop from 1.11.0 to 1.12.0 (#15970) 3 years ago
dependabot[bot] 6fe6412bcc
Bump parallel_tests from 3.5.2 to 3.6.0 (#15968) 3 years ago
dependabot[bot] 1d61af9a9a
Bump fabrication from 2.21.1 to 2.22.0 (#15966) 3 years ago
dependabot[bot] b800cdc26a
Bump webmock from 3.12.1 to 3.12.2 (#15964) 3 years ago
dependabot[bot] 383be67a3a
Bump @testing-library/jest-dom from 5.11.9 to 5.11.10 (#15972) 3 years ago
dependabot[bot] 860f0ed06f
Bump mini-css-extract-plugin from 1.3.9 to 1.4.0 (#15976) 3 years ago
dependabot[bot] a24baa7c15
Bump @babel/preset-env from 7.13.10 to 7.13.12 (#15975) 3 years ago
dependabot[bot] af6da8e0b3
Bump css-loader from 5.1.3 to 5.2.0 (#15961) 3 years ago
Mashiro e3f1107975
build: install shared-mime-info in Dockerfile (#15978) 3 years ago
dependabot[bot] 84eedff19a
Bump pghero from 2.8.0 to 2.8.1 (#15962) 3 years ago
dependabot[bot] 479d58242b
Bump rails from 6.1.3 to 6.1.3.1 (#15960) 3 years ago
Claire a650a1157d
Fix /admin/tags/:id crashing since Rails 6.1 update (#15953) 3 years ago
Claire 59f94593d0
Add warning in admin dashboard if some required queues are not handled (#15954) 3 years ago
Eugen Rochko dd1eb9918a
Add `email` param to `POST /api/v1/emails/confirmations` (#15949) 3 years ago
Claire a2a85d5ae0
Merge pull request #1516 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Marcin Mikołajczak f8e50eaea3
Add transition to media modal background (#15843) 3 years ago
Claire 5ea53b6158 [Glitch] Fix compose form behavior in mobile view 3 years ago
dependabot[bot] b61e44461c [Glitch] Bump react-select from 3.2.0 to 4.0.2 3 years ago
Stanislas d33351af3c
tootctl emoji import: case insensitive duplicate check (#15738) 3 years ago
Claire 9541605024 Fix ActiveRecord monkey-patching migration hack 3 years ago
Claire f60c99a8fb Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
dependabot[bot] 88d69d3261
Bump brakeman from 4.10.1 to 5.0.0 (#15656) 3 years ago
Yurii Izorkin 297a3cf904
templates/systemd/mastodon: enable sandbox mode (#15937) 3 years ago
Claire cbd0ee1d07
Update Mastodon to Rails 6.1 (#15910) 3 years ago
Claire 82556834cf
Change mastodon:setup to not call assets:precompile in docker (#13942) 3 years ago
Claire 1c4dee4554
Fix Mastodon not understanding as:Public and Public (#15948) 3 years ago
Claire 034f37b85a
Fix compose form behavior in mobile view (#15555) 3 years ago
Claire d7c1c41859
Merge pull request #1515 from ClearlyClaire/glitch-soc/features/notifications-design 3 years ago
Claire 49814d5799
Switch from deprecated ClusterWS/cws to ws package (#15932) 3 years ago
dependabot[bot] c3aef491d6
Bump react-select from 3.2.0 to 4.0.2 (#15624) 3 years ago
dependabot[bot] 1b3ebcfe53
Bump aws-sdk-s3 from 1.91.0 to 1.92.0 (#15938) 3 years ago
dependabot[bot] 6b5cda6ec3
Bump css-loader from 5.1.2 to 5.1.3 (#15940) 3 years ago
dependabot[bot] 229968bdff
Bump rspec-rails from 5.0.0 to 5.0.1 (#15939) 3 years ago
dependabot[bot] b5288798a8
Bump ox from 2.14.3 to 2.14.4 (#15941) 3 years ago
dependabot[bot] 64942fa31d
Bump sidekiq from 6.1.3 to 6.2.0 (#15943) 3 years ago
dependabot[bot] 472d5005c0
Bump blurhash from 0.1.4 to 0.1.5 (#15942) 3 years ago
dependabot[bot] 829a598f1d
Bump sidekiq-unique-jobs from 7.0.4 to 7.0.7 (#15944) 3 years ago
Sandro 46d3d3169e
Docker: Use precompiled jemalloc, format, apply hadolint suggestions (#10823) 3 years ago
Claire 876840e9ef
Fix brakeman warning (#15870) 3 years ago
Claire 051efed5ed
Bypass MX validation for explicitly allowed domains (#15930) 3 years ago
Claire d023eefbcc
Fix push notification title for polls (#15931) 3 years ago
Claire 5d48402be1
Fixing the hero widget (#15926) 3 years ago
Claire 39a490c70e
Fix custom CSS when CDN_HOST is set (#15927) 3 years ago
Claire 0ff4264c3e
Add missing push notification title for polls (#15929) 3 years ago
Marcin Mikołajczak 8fa11b0e83
Add missing `en.notification_mailer.status.subject` (#15564) 3 years ago
Eugen Rochko af8fe6e1e9
WIP (#15222) 3 years ago
Claire 2bb573d021 Messing around with box-shadow 3 years ago
Claire e71f4d468b Add more button states? 3 years ago
Claire 200d7a1708 Change notification settings UI to be more compact 3 years ago
Claire c7f04961b6
Merge pull request #1513 from ClearlyClaire/glitch-soc/merge-upstream 3 years ago
Claire 3b7b607300 Migrate glitch-soc local notification settings to upstream system 3 years ago
Claire db6551ec09 Add option to opt out of unread notification markers 3 years ago
Claire 3ad6ef72cb Merge branch 'main' into glitch-soc/merge-upstream 3 years ago
Claire 741d0952b1
Improve account counters handling (#15913) 3 years ago
Claire c31c95ffe4
Remove MySQL-specific code from Mastodon::MigrationHelpers (#15924) 3 years ago
Claire 82caed594c
Change deduplication order of tootctl maintenance fix-duplicates (#15923) 3 years ago
Claire b358229834
Further preparation for Rails 6 (#15916) 3 years ago
Claire 55ac2b9c60
Add option to opt out of unread notification markers (#15842) 3 years ago
Claire 9aaaa96d2f
Use more robust hook for loading timestamp_id function into database (#15919) 3 years ago
Claire a4dcaef53b
Prepare Mastodon for zeitwerk autoloader (#15917) 3 years ago
Claire 5027abecd1
Fix cache_collection crashing when given an empty collection (#15921) 3 years ago
Claire 43eff898a0
Prepare Mastodon for Rails 6 (#15911) 3 years ago
dependabot[bot] 9cb6bc56fa
Bump rspec-rails from 4.1.0 to 5.0.0 (#15876) 3 years ago
dependabot[bot] 0c9ce7b451
Bump @babel/plugin-transform-runtime from 7.13.9 to 7.13.10 (#15903) 3 years ago
dependabot[bot] 506010abc5
Bump bundler-audit from 0.7.0.1 to 0.8.0 (#15877) 3 years ago
dependabot[bot] 8066f7baf0
Bump @babel/runtime from 7.13.9 to 7.13.10 (#15904) 3 years ago
dependabot[bot] aa97433e00
Bump @babel/preset-env from 7.13.9 to 7.13.10 (#15901) 3 years ago
dependabot[bot] 89ff042292
Bump eslint from 7.21.0 to 7.22.0 (#15906) 3 years ago
dependabot[bot] 9cd6a076b7
Bump css-loader from 5.1.1 to 5.1.2 (#15905) 3 years ago
dependabot[bot] 211e5164c4
Bump @babel/core from 7.13.8 to 7.13.10 (#15902) 3 years ago
dependabot[bot] a5b178094b
Bump react-toggle from 4.1.1 to 4.1.2 (#15900) 3 years ago
  1. 48
      .circleci/config.yml
  2. 4
      .dockerignore
  3. 7
      .env.production.sample
  4. 34
      .github/workflows/check-i18n.yml
  5. 485
      AUTHORS.md
  6. 164
      CHANGELOG.md
  7. 12
      CONTRIBUTING.md
  8. 73
      Dockerfile
  9. 58
      Gemfile
  10. 381
      Gemfile.lock
  11. 5
      SECURITY.md
  12. 2
      Vagrantfile
  13. 6
      app/controllers/accounts_controller.rb
  14. 8
      app/controllers/activitypub/outboxes_controller.rb
  15. 2
      app/controllers/admin/dashboard_controller.rb
  16. 2
      app/controllers/admin/domain_blocks_controller.rb
  17. 53
      app/controllers/admin/follow_recommendations_controller.rb
  18. 44
      app/controllers/admin/instances_controller.rb
  19. 3
      app/controllers/admin/statuses_controller.rb
  20. 4
      app/controllers/admin/tags_controller.rb
  21. 4
      app/controllers/api/v1/accounts_controller.rb
  22. 9
      app/controllers/api/v1/emails/confirmations_controller.rb
  23. 2
      app/controllers/api/v1/follow_requests_controller.rb
  24. 28
      app/controllers/api/v1/push/subscriptions_controller.rb
  25. 10
      app/controllers/api/v1/suggestions_controller.rb
  26. 19
      app/controllers/api/v2/suggestions_controller.rb
  27. 25
      app/controllers/api/web/push_subscriptions_controller.rb
  28. 21
      app/controllers/application_controller.rb
  29. 4
      app/controllers/auth/confirmations_controller.rb
  30. 4
      app/controllers/concerns/cache_concern.rb
  31. 5
      app/controllers/custom_css_controller.rb
  32. 10
      app/controllers/directories_controller.rb
  33. 7
      app/controllers/health_controller.rb
  34. 2
      app/controllers/media_proxy_controller.rb
  35. 5
      app/controllers/statuses_controller.rb
  36. 4
      app/helpers/admin/action_logs_helper.rb
  37. 18
      app/helpers/email_helper.rb
  38. 2
      app/helpers/jsonld_helper.rb
  39. 4
      app/helpers/settings_helper.rb
  40. 80
      app/helpers/statuses_helper.rb
  41. 1
      app/javascript/flavours/glitch/actions/importer/normalizer.js
  42. 1
      app/javascript/flavours/glitch/actions/search.js
  43. 17
      app/javascript/flavours/glitch/actions/store.js
  44. 24
      app/javascript/flavours/glitch/actions/suggestions.js
  45. 14
      app/javascript/flavours/glitch/actions/timelines.js
  46. 112
      app/javascript/flavours/glitch/blurhash.js
  47. 6
      app/javascript/flavours/glitch/components/account.js
  48. 9
      app/javascript/flavours/glitch/components/logo.js
  49. 2
      app/javascript/flavours/glitch/components/media_gallery.js
  50. 18
      app/javascript/flavours/glitch/components/modal_root.js
  51. 4
      app/javascript/flavours/glitch/components/regeneration_indicator.js
  52. 22
      app/javascript/flavours/glitch/components/status.js
  53. 2
      app/javascript/flavours/glitch/containers/mastodon.js
  54. 32
      app/javascript/flavours/glitch/containers/media_container.js
  55. 8
      app/javascript/flavours/glitch/containers/status_container.js
  56. 2
      app/javascript/flavours/glitch/features/account/components/header.js
  57. 9
      app/javascript/flavours/glitch/features/account_gallery/index.js
  58. 10
      app/javascript/flavours/glitch/features/compose/components/compose_form.js
  59. 6
      app/javascript/flavours/glitch/features/compose/components/publisher.js
  60. 18
      app/javascript/flavours/glitch/features/compose/components/search_results.js
  61. 25
      app/javascript/flavours/glitch/features/emoji_picker/index.js
  62. 85
      app/javascript/flavours/glitch/features/follow_recommendations/components/account.js
  63. 109
      app/javascript/flavours/glitch/features/follow_recommendations/index.js
  64. 2
      app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js
  65. 4
      app/javascript/flavours/glitch/features/home_timeline/index.js
  66. 8
      app/javascript/flavours/glitch/features/local_settings/page/index.js
  67. 82
      app/javascript/flavours/glitch/features/notifications/components/column_settings.js
  68. 41
      app/javascript/flavours/glitch/features/notifications/components/pill_bar_button.js
  69. 6
      app/javascript/flavours/glitch/features/notifications/index.js
  70. 28
      app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js
  71. 4
      app/javascript/flavours/glitch/features/status/components/detailed_status.js
  72. 8
      app/javascript/flavours/glitch/features/status/index.js
  73. 32
      app/javascript/flavours/glitch/features/ui/components/audio_modal.js
  74. 31
      app/javascript/flavours/glitch/features/ui/components/columns_area.js
  75. 17
      app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js
  76. 83
      app/javascript/flavours/glitch/features/ui/components/media_modal.js
  77. 13
      app/javascript/flavours/glitch/features/ui/components/modal_root.js
  78. 2
      app/javascript/flavours/glitch/features/ui/components/report_modal.js
  79. 29
      app/javascript/flavours/glitch/features/ui/components/video_modal.js
  80. 11
      app/javascript/flavours/glitch/features/ui/index.js
  81. 27
      app/javascript/flavours/glitch/features/video/index.js
  82. 4
      app/javascript/flavours/glitch/reducers/compose.js
  83. 1
      app/javascript/flavours/glitch/reducers/local_settings.js
  84. 2
      app/javascript/flavours/glitch/reducers/notifications.js
  85. 1
      app/javascript/flavours/glitch/reducers/settings.js
  86. 8
      app/javascript/flavours/glitch/reducers/suggestions.js
  87. 7
      app/javascript/flavours/glitch/reducers/timelines.js
  88. 1
      app/javascript/flavours/glitch/styles/about.scss
  89. 34
      app/javascript/flavours/glitch/styles/components/accounts.scss
  90. 36
      app/javascript/flavours/glitch/styles/components/boost.scss
  91. 133
      app/javascript/flavours/glitch/styles/components/columns.scss
  92. 65
      app/javascript/flavours/glitch/styles/components/emoji_picker.scss
  93. 5
      app/javascript/flavours/glitch/styles/components/index.scss
  94. 129
      app/javascript/flavours/glitch/styles/components/media.scss
  95. 8
      app/javascript/flavours/glitch/styles/components/modal.scss
  96. 15
      app/javascript/flavours/glitch/styles/components/status.scss
  97. 42
      app/javascript/flavours/glitch/styles/forms.scss
  98. 7
      app/javascript/flavours/glitch/styles/widgets.scss
  99. 4
      app/javascript/flavours/glitch/util/async-components.js
  100. 34
      app/javascript/flavours/glitch/util/emoji/emoji_compressed.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -129,6 +129,13 @@ jobs:
environment: *ruby_environment
<<: *install_ruby_dependencies
install-ruby3.0:
<<: *defaults
docker:
- image: circleci/ruby:3.0-buster-node
environment: *ruby_environment
<<: *install_ruby_dependencies
build:
<<: *defaults
steps:
@ -187,6 +194,18 @@ jobs:
- image: circleci/redis:5-alpine
<<: *test_steps
test-ruby3.0:
<<: *defaults
docker:
- image: circleci/ruby:3.0-buster-node
environment: *ruby_environment
- image: circleci/postgres:12.2
environment:
POSTGRES_USER: root
POSTGRES_HOST_AUTH_METHOD: trust
- image: circleci/redis:5-alpine
<<: *test_steps
test-webui:
<<: *defaults
docker:
@ -197,24 +216,6 @@ jobs:
name: Run jest
command: yarn test:jest
check-i18n:
<<: *defaults
steps:
- *attach_workspace
- *install_system_dependencies
- run:
name: Check locale file normalization
command: bundle exec i18n-tasks check-normalized
- run:
name: Check for unused strings
command: bundle exec i18n-tasks unused -l en
- run:
name: Check for wrong string interpolations
command: bundle exec i18n-tasks check-consistent-interpolations
- run:
name: Check that all required locale files exist
command: bundle exec rake repo:check_locales_files
workflows:
version: 2
build-and-test:
@ -227,6 +228,10 @@ workflows:
requires:
- install
- install-ruby2.7
- install-ruby3.0:
requires:
- install
- install-ruby2.7
- build:
requires:
- install-ruby2.7
@ -241,9 +246,10 @@ workflows:
requires:
- install-ruby2.6
- build
- test-ruby3.0:
requires:
- install-ruby3.0
- build
- test-webui:
requires:
- install
- check-i18n:
requires:
- install-ruby2.7

@ -1,6 +1,10 @@
.bundle
.env
.env.*
.git
.gitattributes
.gitignore
.github
public/system
public/assets
public/packs

@ -269,3 +269,10 @@ MAX_POLL_OPTION_CHARS=100
# Maximum search results to display
# Only relevant when elasticsearch is installed
# MAX_SEARCH_RESULTS=20
# Maximum custom emoji file sizes
# If undefined or smaller than MAX_EMOJI_SIZE, the value
# of MAX_EMOJI_SIZE will be used for MAX_REMOTE_EMOJI_SIZE
# Units are in bytes
MAX_EMOJI_SIZE=51200
MAX_REMOTE_EMOJI_SIZE=204800

@ -0,0 +1,34 @@
name: Check i18n
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
RAILS_ENV: test
jobs:
check-i18n:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '2.7'
bundler-cache: true
- name: Check locale file normalization
run: bundle exec i18n-tasks check-normalized
- name: Check for unused strings
run: bundle exec i18n-tasks unused -l en
- name: Check for wrong string interpolations
run: bundle exec i18n-tasks check-consistent-interpolations
- name: Check that all required locale files exist
run: bundle exec rake repo:check_locales_files

@ -5,39 +5,38 @@ Mastodon is available on [GitHub](https://github.com/tootsuite/mastodon)
and provided thanks to the work of the following contributors:
* [Gargron](https://github.com/Gargron)
* [ThibG](https://github.com/ThibG)
* [dependabot-preview[bot]](https://github.com/apps/dependabot-preview)
* [dependabot[bot]](https://github.com/apps/dependabot)
* [ClearlyClaire](https://github.com/ClearlyClaire)
* [dependabot-preview[bot]](https://github.com/apps/dependabot-preview)
* [ykzts](https://github.com/ykzts)
* [akihikodaki](https://github.com/akihikodaki)
* [mjankowski](https://github.com/mjankowski)
* [unarist](https://github.com/unarist)
* [abcang](https://github.com/abcang)
* [yiskah](https://github.com/yiskah)
* [noellabo](https://github.com/noellabo)
* [nolanlawson](https://github.com/nolanlawson)
* [abcang](https://github.com/abcang)
* [mayaeh](https://github.com/mayaeh)
* [ysksn](https://github.com/ysksn)
* [sorin-davidoi](https://github.com/sorin-davidoi)
* [noellabo](https://github.com/noellabo)
* [lynlynlynx](https://github.com/lynlynlynx)
* [m4sk1n](mailto:me@m4sk.in)
* [Marcin Mikołajczak](mailto:me@m4sk.in)
* [Kjwon15](https://github.com/Kjwon15)
* [tribela](https://github.com/tribela)
* [renatolond](https://github.com/renatolond)
* [alpaca-tc](https://github.com/alpaca-tc)
* [jeroenpraat](https://github.com/jeroenpraat)
* [zunda](https://github.com/zunda)
* [nclm](https://github.com/nclm)
* [ineffyble](https://github.com/ineffyble)
* [zunda](https://github.com/zunda)
* [shleeable](https://github.com/shleeable)
* [Masoud Abkenar](mailto:ampbox@gmail.com)
* [blackle](https://github.com/blackle)
* [Quent-in](https://github.com/Quent-in)
* [JantsoP](https://github.com/JantsoP)
* [ariasuni](https://github.com/ariasuni)
* [nullkal](https://github.com/nullkal)
* [yookoala](https://github.com/yookoala)
* [Brawaru](https://github.com/Brawaru)
* [ariasuni](https://github.com/ariasuni)
* [Aditoo17](https://github.com/Aditoo17)
* [Quenty31](https://github.com/Quenty31)
* [marek-lach](https://github.com/marek-lach)
@ -45,7 +44,9 @@ and provided thanks to the work of the following contributors:
* [ashfurrow](https://github.com/ashfurrow)
* [danhunsaker](https://github.com/danhunsaker)
* [eramdam](https://github.com/eramdam)
* [Jeroen](mailto:jeroenpraat@users.noreply.github.com)
* [takayamaki](https://github.com/takayamaki)
* [dunn](https://github.com/dunn)
* [masarakki](https://github.com/masarakki)
* [ticky](https://github.com/ticky)
* [trwnh](https://github.com/trwnh)
@ -53,15 +54,15 @@ and provided thanks to the work of the following contributors:
* [hinaloe](https://github.com/hinaloe)
* [hcmiya](https://github.com/hcmiya)
* [stephenburgess8](https://github.com/stephenburgess8)
* [Wonderfall](mailto:wonderfall@targaryen.house)
* [Wonderfall](https://github.com/Wonderfall)
* [matteoaquila](https://github.com/matteoaquila)
* [yukimochi](https://github.com/yukimochi)
* [palindromordnilap](https://github.com/palindromordnilap)
* [rkarabut](https://github.com/rkarabut)
* [jeroenpraat](mailto:jeroenpraat@users.noreply.github.com)
* [nightpool](https://github.com/nightpool)
* [Artoria2e5](https://github.com/Artoria2e5)
* [marrus-sh](https://github.com/marrus-sh)
* [dunn](https://github.com/dunn)
* [krainboltgreene](https://github.com/krainboltgreene)
* [pfigel](https://github.com/pfigel)
* [BoFFire](https://github.com/BoFFire)
@ -73,18 +74,19 @@ and provided thanks to the work of the following contributors:
* [SerCom_KC](mailto:sercom-kc@users.noreply.github.com)
* [Sylvhem](https://github.com/Sylvhem)
* [MitarashiDango](https://github.com/MitarashiDango)
* [rinsuki](https://github.com/rinsuki)
* [angristan](https://github.com/angristan)
* [JeanGauthier](https://github.com/JeanGauthier)
* [kschaper](https://github.com/kschaper)
* [beatrix-bitrot](https://github.com/beatrix-bitrot)
* [koyuawsmbrtn](https://github.com/koyuawsmbrtn)
* [BenLubar](https://github.com/BenLubar)
* [mkljczk](https://github.com/mkljczk)
* [adbelle](https://github.com/adbelle)
* [evanminto](https://github.com/evanminto)
* [MightyPork](https://github.com/MightyPork)
* [ashleyhull-versent](https://github.com/ashleyhull-versent)
* [yhirano55](https://github.com/yhirano55)
* [rinsuki](https://github.com/rinsuki)
* [devkral](https://github.com/devkral)
* [camponez](https://github.com/camponez)
* [hugogameiro](https://github.com/hugogameiro)
@ -100,7 +102,6 @@ and provided thanks to the work of the following contributors:
* [lindwurm](https://github.com/lindwurm)
* [victorhck](mailto:victorhck@geeko.site)
* [voidsatisfaction](https://github.com/voidsatisfaction)
* [mkljczk](https://github.com/mkljczk)
* [hikari-no-yume](https://github.com/hikari-no-yume)
* [seefood](https://github.com/seefood)
* [jackjennings](https://github.com/jackjennings)
@ -135,10 +136,11 @@ and provided thanks to the work of the following contributors:
* [kadiix](https://github.com/kadiix)
* [kodacs](https://github.com/kodacs)
* [marcin mikołajczak](mailto:me@m4sk.in)
* [JMendyk](https://github.com/JMendyk)
* [KScl](https://github.com/KScl)
* [sterdev](https://github.com/sterdev)
* [mashirozx](https://github.com/mashirozx)
* [TheKinrar](https://github.com/TheKinrar)
* [007lva](https://github.com/007lva)
* [AA4ch1](https://github.com/AA4ch1)
* [alexgleason](https://github.com/alexgleason)
* [Bèr Kessels](mailto:ber@berk.es)
@ -150,6 +152,7 @@ and provided thanks to the work of the following contributors:
* [hendotcat](https://github.com/hendotcat)
* [d6rkaiz](https://github.com/d6rkaiz)
* [ladyisatis](https://github.com/ladyisatis)
* [JMendyk](https://github.com/JMendyk)
* [JohnD28](https://github.com/JohnD28)
* [znz](https://github.com/znz)
* [saper](https://github.com/saper)
@ -159,6 +162,7 @@ and provided thanks to the work of the following contributors:
* [ekiru](https://github.com/ekiru)
* [geta6](https://github.com/geta6)
* [happycoloredbanana](https://github.com/happycoloredbanana)
* [joenepraat](https://github.com/joenepraat)
* [leopku](https://github.com/leopku)
* [SansPseudoFix](https://github.com/SansPseudoFix)
* [spla](mailto:sp@mastodont.cat)
@ -169,13 +173,13 @@ and provided thanks to the work of the following contributors:
* [nzws](https://github.com/nzws)
* [duxovni](https://github.com/duxovni)
* [smorimoto](https://github.com/smorimoto)
* [mashirozx](https://github.com/mashirozx)
* [178inaba](https://github.com/178inaba)
* [acid-chicken](https://github.com/acid-chicken)
* [xgess](https://github.com/xgess)
* [alyssais](https://github.com/alyssais)
* [aablinov](https://github.com/aablinov)
* [stalker314314](https://github.com/stalker314314)
* [cohosh](https://github.com/cohosh)
* [cutls](https://github.com/cutls)
* [huertanix](https://github.com/huertanix)
* [eleboucher](https://github.com/eleboucher)
@ -184,7 +188,7 @@ and provided thanks to the work of the following contributors:
* [treby](https://github.com/treby)
* [jpdevries](https://github.com/jpdevries)
* [gdpelican](https://github.com/gdpelican)
* [Korbinian](mailto:kontakt@korbinian-michl.de)
* [MonaLisaOverrdrive](https://github.com/MonaLisaOverrdrive)
* [Kurtis Rainbolt-Greene](mailto:me@kurtisrainboltgreene.name)
* [panarom](https://github.com/panarom)
* [Dar13](https://github.com/Dar13)
@ -204,6 +208,7 @@ and provided thanks to the work of the following contributors:
* [gled-rs](https://github.com/gled-rs)
* [Valentin_NC](mailto:valentin.ouvrard@nautile.sarl)
* [R0ckweb](https://github.com/R0ckweb)
* [Izorkin](https://github.com/Izorkin)
* [unasuke](https://github.com/unasuke)
* [caasi](https://github.com/caasi)
* [chr-1x](https://github.com/chr-1x)
@ -211,13 +216,14 @@ and provided thanks to the work of the following contributors:
* [foxiehkins](https://github.com/foxiehkins)
* [highemerly](https://github.com/highemerly)
* [hoodie](mailto:hoodiekitten@outlook.com)
* [kaiyou](https://github.com/kaiyou)
* [luzi82](https://github.com/luzi82)
* [slice](https://github.com/slice)
* [tmm576](https://github.com/tmm576)
* [unsmell](mailto:unsmell@users.noreply.github.com)
* [valerauko](https://github.com/valerauko)
* [chriswmartin](https://github.com/chriswmartin)
* [vahnj](https://github.com/vahnj)
* [SuperSandro2000](https://github.com/SuperSandro2000)
* [ikuradon](https://github.com/ikuradon)
* [AndreLewin](https://github.com/AndreLewin)
* [0xflotus](https://github.com/0xflotus)
@ -254,17 +260,20 @@ and provided thanks to the work of the following contributors:
* [ian-kelling](https://github.com/ian-kelling)
* [immae](https://github.com/immae)
* [J0WI](https://github.com/J0WI)
* [vahnj](https://github.com/vahnj)
* [foozmeat](https://github.com/foozmeat)
* [jasonrhodes](https://github.com/jasonrhodes)
* [Jason Snell](mailto:jason@newrelic.com)
* [jviide](https://github.com/jviide)
* [YuleZ](https://github.com/YuleZ)
* [jtracey](https://github.com/jtracey)
* [crakaC](https://github.com/crakaC)
* [tkbky](https://github.com/tkbky)
* [Kaylee](mailto:kaylee@codethat.sucks)
* [Kazhnuz](https://github.com/Kazhnuz)
* [mkody](https://github.com/mkody)
* [connyduck](https://github.com/connyduck)
* [Tak](https://github.com/Tak)
* [LindseyB](https://github.com/LindseyB)
* [Lorenz Diener](mailto:halcyon@icosahedron.website)
* [Markus Amalthea Magnuson](mailto:markus.magnuson@gmail.com)
@ -282,9 +291,9 @@ and provided thanks to the work of the following contributors:
* [lumenwrites](https://github.com/lumenwrites)
* [remram44](https://github.com/remram44)
* [sts10](https://github.com/sts10)
* [SuperSandro2000](https://github.com/SuperSandro2000)
* [u1-liquid](https://github.com/u1-liquid)
* [rosylilly](https://github.com/rosylilly)
* [withshubh](https://github.com/withshubh)
* [sim6](https://github.com/sim6)
* [Sir-Boops](https://github.com/Sir-Boops)
* [stemid](https://github.com/stemid)
@ -305,15 +314,16 @@ and provided thanks to the work of the following contributors:
* [anon5r](https://github.com/anon5r)
* [aus-social](https://github.com/aus-social)
* [bsky](mailto:me@imbsky.net)
* [chandrn7](https://github.com/chandrn7)
* [codl](https://github.com/codl)
* [cpsdqs](https://github.com/cpsdqs)
* [barzamin](https://github.com/barzamin)
* [gol-cha](https://github.com/gol-cha)
* [fhalna](https://github.com/fhalna)
* [haoyayoi](https://github.com/haoyayoi)
* [ik11235](https://github.com/ik11235)
* [kawax](https://github.com/kawax)
* [shrft](https://github.com/shrft)
* [007lva](https://github.com/007lva)
* [mbajur](https://github.com/mbajur)
* [matsurai25](https://github.com/matsurai25)
* [mecab](https://github.com/mecab)
@ -353,7 +363,7 @@ and provided thanks to the work of the following contributors:
* [a2](https://github.com/a2)
* [alfiedotwtf](https://github.com/alfiedotwtf)
* [0xa](https://github.com/0xa)
* [ArisuOngaku](https://github.com/ArisuOngaku)
* [ashpieboop](https://github.com/ashpieboop)
* [virtualpain](https://github.com/virtualpain)
* [sapphirus](https://github.com/sapphirus)
* [amandavisconti](https://github.com/amandavisconti)
@ -367,6 +377,7 @@ and provided thanks to the work of the following contributors:
* [orlea](https://github.com/orlea)
* [armandfardeau](https://github.com/armandfardeau)
* [raboof](https://github.com/raboof)
* [aldatsa](https://github.com/aldatsa)
* [jumbosushi](https://github.com/jumbosushi)
* [acuteaura](https://github.com/acuteaura)
* [ayumin](https://github.com/ayumin)
@ -375,7 +386,7 @@ and provided thanks to the work of the following contributors:
* [li-bei](https://github.com/li-bei)
* [Benedikt Geißler](mailto:benedikt@g5r.eu)
* [BenisonSebastian](https://github.com/BenisonSebastian)
* [blakebarnett](https://github.com/blakebarnett)
* [Blake](mailto:blake.barnett@postmates.com)
* [Brad Janke](mailto:brad.janke@gmail.com)
* [bclindner](https://github.com/bclindner)
* [brycied00d](https://github.com/brycied00d)
@ -395,10 +406,12 @@ and provided thanks to the work of the following contributors:
* [colindean](https://github.com/colindean)
* [DeeUnderscore](https://github.com/DeeUnderscore)
* [dachinat](https://github.com/dachinat)
* [monsterpit-firedemon](https://github.com/monsterpit-firedemon)
* [Daggertooth](mailto:dev@monsterpit.net)
* [watilde](https://github.com/watilde)
* [dalehenries](https://github.com/dalehenries)
* [daprice](https://github.com/daprice)
* [da2x](https://github.com/da2x)
* [danieljakots](https://github.com/danieljakots)
* [codesections](https://github.com/codesections)
* [dar5hak](https://github.com/dar5hak)
* [kant](https://github.com/kant)
@ -423,6 +436,7 @@ and provided thanks to the work of the following contributors:
* [espenronnevik](https://github.com/espenronnevik)
* [Expenses](mailto:expenses@airmail.cc)
* [fabianonline](https://github.com/fabianonline)
* [shello](https://github.com/shello)
* [Finariel](https://github.com/Finariel)
* [siuying](https://github.com/siuying)
* [zoc](https://github.com/zoc)
@ -433,7 +447,7 @@ and provided thanks to the work of the following contributors:
* [hattori6789](https://github.com/hattori6789)
* [algernon](https://github.com/algernon)
* [Fastbyte01](https://github.com/Fastbyte01)
* [myfreeweb](https://github.com/myfreeweb)
* [unrelentingtech](https://github.com/unrelentingtech)
* [gfaivre](https://github.com/gfaivre)
* [Fiaxhs](https://github.com/Fiaxhs)
* [rasjonell](https://github.com/rasjonell)
@ -445,17 +459,20 @@ and provided thanks to the work of the following contributors:
* [Habu-Kagumba](https://github.com/Habu-Kagumba)
* [suzukaze](https://github.com/suzukaze)
* [Hiromi-Kai](https://github.com/Hiromi-Kai)
* [hishamhm](https://github.com/hishamhm)
* [Slaynash](https://github.com/Slaynash)
* [musashino205](https://github.com/musashino205)
* [iwaim](https://github.com/iwaim)
* [valrus](https://github.com/valrus)
* [IMcD23](https://github.com/IMcD23)
* [yi0713](https://github.com/yi0713)
* [iblech](https://github.com/iblech)
* [Hisham Muhammad](mailto:hisham@gobolinux.org)
* [Hugo "Slaynash" Flores](mailto:hugoflores@hotmail.fr)
* [INAGAKI Hiroshi](mailto:musashino205@users.noreply.github.com)
* [IWAI, Masaharu](mailto:iwaim.sub@gmail.com)
* [Ian McCowan](mailto:imccowan@gmail.com)
* [Ian McDowell](mailto:me@ianmcdowell.net)
* [Iijima Yasushi](mailto:kurage.cc@gmail.com)
* [Ikko Ashimine](mailto:eltociear@gmail.com)
* [Ingo Blechschmidt](mailto:iblech@web.de)
* [J Yeary](mailto:usbsnowcrash@users.noreply.github.com)
* [jack-michaud](https://github.com/jack-michaud)
* [Floppy](https://github.com/Floppy)
* [Jack Michaud](mailto:jack-michaud@users.noreply.github.com)
* [Jakub Mendyk](mailto:jakubmendyk.szkola@gmail.com)
* [James](mailto:james.allen.vaughan@gmail.com)
* [James Smith](mailto:james@floppy.org.uk)
* [Jarek Lipski](mailto:pub@loomchild.net)
* [Jennifer Glauche](mailto:=^.^=@github19.jglauche.de)
* [Jennifer Kruse](mailto:jenkr55@gmail.com)
@ -464,6 +481,7 @@ and provided thanks to the work of the following contributors:
* [Jessica K. Litwin](mailto:jessica@litw.in)
* [Jo Decker](mailto:trolldecker@users.noreply.github.com)
* [Joan Montané](mailto:jmontane@users.noreply.github.com)
* [Joe](mailto:401283+htmlbyjoe@users.noreply.github.com)
* [Jonathan Klee](mailto:klee.jonathan@gmail.com)
* [Jordan Guerder](mailto:jguerder@fr.pulseheberg.net)
* [Joseph Mingrone](mailto:jehops@users.noreply.github.com)
@ -483,7 +501,6 @@ and provided thanks to the work of the following contributors:
* [Krzysztof Jurewicz](mailto:krzysztof.jurewicz@gmail.com)
* [Leo Wzukw](mailto:leowzukw@users.noreply.github.com)
* [Leonie](mailto:62470640+bubblineyuri@users.noreply.github.com)
* [Levi Bard](mailto:taktaktaktaktaktaktaktaktaktak@gmail.com)
* [Lex Alexander](mailto:l.alexander10@gmail.com)
* [Lorenz Diener](mailto:lorenzd@gmail.com)
* [Luc Didry](mailto:ldidry@users.noreply.github.com)
@ -560,6 +577,7 @@ and provided thanks to the work of the following contributors:
* [ScienJus](mailto:i@scienjus.com)
* [Scott Larkin](mailto:scott@codeclimate.com)
* [Scott Sweeny](mailto:scott@ssweeny.net)
* [Sean](mailto:sean@sean.taipei)
* [Sebastian Hübner](mailto:imolein@users.noreply.github.com)
* [Sebastian Morr](mailto:sebastian@morr.cc)
* [Sergei Č](mailto:noiwex1911@gmail.com)
@ -570,8 +588,10 @@ and provided thanks to the work of the following contributors:
* [Shouko Yu](mailto:imshouko@gmail.com)
* [Sina Mashek](mailto:sina@mashek.xyz)
* [Soft. Dev](mailto:24978+nileshkumar@users.noreply.github.com)
* [Sophie Parker](mailto:dev@cortices.me)
* [Soshi Kato](mailto:mail@sossii.com)
* [Spanky](mailto:2788886+spankyworks@users.noreply.github.com)
* [Stanislas](mailto:stanislas.lange@pm.me)
* [StefOfficiel](mailto:pichard.stephane@free.fr)
* [Steven Tappert](mailto:admin@dark-it.net)
* [Stéphane Guillou](mailto:stephane.guillou@member.fsf.org)
@ -630,9 +650,9 @@ and provided thanks to the work of the following contributors:
* [evilny0](mailto:evilny0@moomoocamp.net)
* [febrezo](mailto:felixbrezo@gmail.com)
* [fsubal](mailto:fsubal@users.noreply.github.com)
* [fusagiko / takayamaki](mailto:24884114+takayamaki@users.noreply.github.com)
* [fusshi-](mailto:dikky1218@users.noreply.github.com)
* [gentaro](mailto:gentaroooo@gmail.com)
* [gol-cha](mailto:info@mevo.xyz)
* [guigeekz](mailto:pattusg@gmail.com)
* [hakoai](mailto:hk--76@qa2.so-net.ne.jp)
* [haosbvnker](mailto:github@chaosbunker.com)
@ -645,7 +665,7 @@ and provided thanks to the work of the following contributors:
* [jooops](mailto:joops@autistici.org)
* [jukper](mailto:jukkaperanto@gmail.com)
* [jumoru](mailto:jumoru@mailbox.org)
* [kaiyou](mailto:pierre@jaury.eu)
* [kaias1jp](mailto:kaias1jp@gmail.com)
* [karlyeurl](mailto:karl.yeurl@gmail.com)
* [kawaguchi](mailto:jiikko@users.noreply.github.com)
* [kedama](mailto:32974885+kedamadq@users.noreply.github.com)
@ -705,104 +725,131 @@ This document is provided for informational purposes only. Since it is only upda
Following people have contributed to translation of Mastodon:
- ᏦᏁᎢᎵᏫ 😷 (KNTRO) (*Spanish, Argentina*)
- GunChleoc (*Scottish Gaelic*)
- ᛤᚤᛠᛥⴲ 👽 (KNTRO) (*Spanish, Argentina*)
- adrmzz (*Sardinian*)
- Hồ Nhất Duy (kantcer) (*Vietnamese*)
- Zoltán Gera (gerazo) (*Hungarian*)
- Sveinn í Felli (sveinki) (*Icelandic*)
- qezwan (*Persian, Sorani (Kurdish)*)
- Hồ Nhất Duy (kantcer) (*Vietnamese*)
- NCAA (*Danish*)
- Ramdziana F Y (rafeyu) (*Indonesian*)
- taicv (*Vietnamese*)
- Zoltán Gera (gerazo) (*Hungarian*)
- ButterflyOfFire (BoFFire) (*French, Arabic, Kabyle*)
- adrmzz (*Sardinian*)
- Ramdziana F Y (rafeyu) (*Indonesian*)
- Evert Prants (IcyDiamond) (*Estonian*)
- Daniele Lira Mereb (danilmereb) (*Portuguese, Brazilian*)
- Xosé M. (XoseM) (*Spanish, Galician*)
- Kristijan Tkalec (lapor) (*Slovenian*)
- stan ionut (stanionut12) (*Romanian*)
- Evert Prants (IcyDiamond) (*Estonian*)
- Besnik_b (*Albanian*)
- Emanuel Pina (emanuelpina) (*Portuguese*)
- Jeong Arm (Kjwon15) (*Japanese, Korean, Esperanto*)
- Alix Rossi (palindromordnilap) (*French, Esperanto, Corsican*)
- Thai Localization (thl10n) (*Thai*)
- Daniele Lira Mereb (danilmereb) (*Portuguese, Brazilian*)
- Joene (joenepraat) (*Dutch*)
- Kristijan Tkalec (lapor) (*Slovenian*)
- stan ionut (stanionut12) (*Romanian*)
- spla (*Spanish, Catalan*)
- мачко (ma4ko) (*Bulgarian*)
- 奈卜拉 (nebula_moe) (*Chinese Simplified*)
- Jeong Arm (Kjwon15) (*Japanese, Korean, Esperanto*)
- kamee (*Armenian*)
- AJ-عجائب البرمجة (Esmail_Hazem) (*Arabic*)
- Michal Stanke (mstanke) (*Czech*)
- Alix Rossi (palindromordnilap) (*French, Corsican*)
- spla (*Spanish, Catalan*)
- Imre Kristoffer Eilertsen (DandelionSprout) (*Norwegian*)
- Jeroen (jeroenpraat) (*Dutch*)
- borys_sh (*Ukrainian*)
- Miguel Mayol (mitcoes) (*Spanish, Catalan*)
- Danial Behzadi (danialbehzadi) (*Persian*)
- yeft (*Chinese Traditional, Chinese Traditional, Hong Kong*)
- borys_sh (*Ukrainian*)
- Asier Iturralde Sarasola (aldatsa) (*Basque*)
- Imre Kristoffer Eilertsen (DandelionSprout) (*Norwegian*)
- koyu (*German*)
- yeft (*Chinese Traditional, Chinese Traditional, Hong Kong*)
- Miguel Mayol (mitcoes) (*Spanish, Catalan*)
- Sasha Sorokin (Brawaru) (*French, Catalan, Danish, German, Greek, Hungarian, Armenian, Korean, Russian, Albanian, Swedish, Ukrainian, Vietnamese, Galician*)
- Roboron (*Spanish*)
- Koala Yeung (yookoala) (*Chinese Traditional, Hong Kong*)
- Ondřej Pokorný (unextro) (*Czech*)
- Osoitz (*Basque*)
- Peterandre (*Norwegian, Norwegian Nynorsk*)
- tzium (*Sardinian*)
- Mélanie Chauvel (ariasuni) (*French, Arabic, Czech, German, Greek, Hungarian, Slovenian, Ukrainian, Chinese Simplified, Portuguese, Brazilian, Persian, Norwegian Nynorsk, Esperanto, Breton, Corsican, Sardinian, Kabyle*)
- Iváns (Ivans_translator) (*Galician*)
- Sasha Sorokin (Sasha-Sorokin) (*French, Catalan, Danish, German, Greek, Hungarian, Armenian, Korean, Russian, Albanian, Swedish, Ukrainian, Vietnamese, Galician*)
- kamee (*Armenian*)
- Maya Minatsuki (mayaeh) (*Japanese*)
- Manuel Viens (manuelviens) (*French*)
- Alessandro Levati (Oct326) (*Italian*)
- lamnatos (*Greek*)
- Sean Young (assanges) (*Chinese Traditional*)
- tolstoevsky (*Russian*)
- enolp (*Asturian*)
- FédiQuébec (manuelviens) (*French*)
- lamnatos (*Greek*)
- Maya Minatsuki (mayaeh) (*Japanese*)
- Jasmine Cam Andrever (gourmas) (*Cornish*)
- gagik_ (*Armenian*)
- Masoud Abkenar (mabkenar) (*Persian*)
- Alessandro Levati (Oct326) (*Italian*)
- arshat (*Kazakh*)
- Roboron (*Spanish*)
- ariasuni (*French, Arabic, Czech, German, Greek, Hungarian, Slovenian, Ukrainian, Chinese Simplified, Portuguese, Brazilian, Persian, Norwegian Nynorsk, Esperanto, Breton, Corsican, Sardinian, Kabyle*)
- Marcin Mikołajczak (mkljczkk) (*Czech, Polish, Russian*)
- Marek Ľach (mareklach) (*Polish, Slovak*)
- Ali Demirtaş (alidemirtas) (*Turkish*)
- Blak Ouille (BlakOuille16) (*French*)
- Em St Cenydd (cancennau) (*Welsh*)
- Marek Ľach (mareklach) (*Polish, Slovak*)
- Diluns (*Occitan*)
- Muha Aliss (muhaaliss) (*Turkish*)
- Jurica (ahjk) (*Croatian*)
- Aditoo17 (*Czech*)
- Diluns (*Occitan*)
- gagik_ (*Armenian*)
- vishnuvaratharajan (*Tamil*)
- Marcin Mikołajczak (mkljczkk) (*Czech, Polish, Russian*)
- pulmonarycosignerkindness (*Swedish*)
- cybergene (cyber-gene) (*Japanese*)
- Takeçi (polygoat) (*French, Italian*)
- xatier (*Chinese Traditional*)
- Ihor Hordiichuk (ihor_ck) (*Ukrainian*)
- regulartranslator (*Portuguese, Brazilian*)
- ozzii (*French, Serbian (Cyrillic)*)
- Irfan (Irfan_Radz) (*Malay*)
- Saederup92 (*Danish*)
- Akarshan Biswas (biswasab) (*Bengali, Sanskrit*)
- Yi-Jyun Pan (pan93412) (*Chinese Traditional*)
- Rafael H L Moretti (Moretti) (*Portuguese, Brazilian*)
- d5Ziif3K (*Ukrainian*)
- GiorgioHerbie (*Italian*)
- Rafael H L Moretti (Moretti) (*Portuguese, Brazilian*)
- Saederup92 (*Danish*)
- christalleras (*Norwegian Nynorsk*)
- cybergene (cyber-gene) (*Japanese*)
- Taloran (*Norwegian Nynorsk*)
- ThibG (*French, Icelandic*)
- xatier (*Chinese Traditional*)
- otrapersona (*Spanish, Spanish, Mexico*)
- Store (HelaBasa) (*Sinhala*)
- Mauzi (*German, Swedish*)
- atarashiako (*Chinese Simplified*)
- 101010 (101010pl) (*Polish*)
- erictapen (*German*)
- Tagomago (tagomago) (*French, Spanish*)
- Jaz-Michael King (jazmichaelking) (*Welsh*)
- coxde (*Chinese Simplified*)
- T. E. Kalaycı (tekrei) (*Turkish*)
- silkevicious (*Italian*)
- Floxu (fredrikdim1) (*Norwegian Nynorsk*)
- Ryo (DrRyo) (*Korean*)
- Bertil Hedkvist (Berrahed) (*Swedish*)
- William(ѕ)ⁿ (wmlgr) (*Spanish*)
- norayr (*Armenian*)
- Satnam S Virdi (pika10singh) (*Punjabi*)
- Tiago Epifânio (tfve) (*Portuguese*)
- Ryo (DrRyo) (*Korean*)
- Balázs Meskó (mesko.balazs) (*Hungarian*)
- Sokratis Alichanidis (alichani) (*Greek*)
- Mentor Gashi (mentorgashi.com) (*Albanian*)
- Jaz-Michael King (jazmichaelking) (*Welsh*)
- carolinagiorno (*Portuguese, Brazilian*)
- Hayk Khachatryan (brutusromanus123) (*Armenian*)
- Roby Thomas (roby.thomas) (*Malayalam*)
- Bharat Kumar (Marwari) (*Hindi*)
- Austra Muizniece (aus_m) (*Latvian*)
- ThonyVezbe (*Breton*)
- v4vachan (*Malayalam*)
- dkdarshan760 (*Sanskrit*)
- Tagomago (tagomago) (*French, Spanish*)
- tykayn (*French*)
- axi (*Finnish*)
- Selyan Slimane AMIRI (slimane_AMIRI) (*Kabyle*)
- Balázs Meskó (mesko.balazs) (*Hungarian*)
- Selyan Slimane AMIRI (SelyanKab) (*Kabyle*)
- Timur Seber (seber) (*Tatar*)
- taoxvx (*Danish*)
- Hrach Mkrtchyan (mhrach87) (*Armenian*)
- sabri (thetomatoisavegetable) (*Spanish, Spanish, Argentina*)
- Dewi (Unkorneg) (*French, Breton*)
- Coelacanthus (*Chinese Simplified*)
- CoelacanthusHex (*Chinese Simplified*)
- syncopams (*Chinese Simplified, Chinese Traditional, Chinese Traditional, Hong Kong*)
- Rhys Harrison (rhedders) (*Esperanto*)
- Hakim Oubouali (zenata1) (*Standard Moroccan Tamazight*)
- SteinarK (*Norwegian Nynorsk*)
- Sokratis Alichanidis (alichani) (*Greek*)
- Lalo Tafolla (lalotafo) (*Spanish, Spanish, Mexico*)
- Mathias B. Vagnes (vagnes) (*Norwegian*)
- dashersyed (*Urdu (Pakistan)*)
- Acolyte (666noob404) (*Ukrainian*)
@ -811,104 +858,124 @@ Following people have contributed to translation of Mastodon:
- Damjan Dimitrioski (gnud) (*Macedonian*)
- PPNplus (*Thai*)
- shioko (*Chinese Simplified*)
- v4vachan (*Malayalam*)
- Hakim Oubouali (zenata1) (*Standard Moroccan Tamazight*)
- ZiriSut (*Kabyle*)
- Evgeny Petrov (kondra007) (*Russian*)
- Gwenn (Belvar) (*Breton*)
- StanleyFrew (*French*)
- Hayk Khachatryan (brutusromanus123) (*Armenian*)
- Nikita Epifanov (Nikets) (*Russian*)
- jaranta (*Finnish*)
- Felicia (midsommar) (*Swedish*)
- Slobodan Simić (Слободан Симић) (slsimic) (*Serbian (Cyrillic)*)
- Felicia Jongleur (midsommar) (*Swedish*)
- Denys (dector) (*Ukrainian*)
- iVampireSP (*Chinese Simplified, Chinese Traditional*)
- Pukima (pukimaaa) (*German*)
- 游荡 (MamaShip) (*Chinese Simplified*)
- Vanege (*Esperanto*)
- Rikard Linde (rikardlinde) (*Swedish*)
- Jess Rafn (therealyez) (*Danish*)
- strubbl (*German*)
- Stasiek Michalski (hellcp) (*Polish*)
- dxwc (*Bengali*)
- jmontane (*Catalan*)
- Liboide (*Spanish*)
- Hexandcube (hexandcube) (*Polish*)
- Chris Kay (chriskarasoulis) (*Greek*)
- Johan Schiff (schyffel) (*Swedish*)
- Arunmozhi (tecoholic) (*Tamil*)
- zer0-x (ZER0-X) (*Arabic*)
- kat (katktv) (*Russian, Ukrainian*)
- Rikard Linde (rikardlinde) (*Swedish*)
- Lauren Liberda (selfisekai) (*Polish*)
- mynameismonkey (*Welsh*)
- oti4500 (*Hungarian, Ukrainian*)
- Laura (selfisekai) (*Polish*)
- Rachida S. (ZiriSut) (*Kabyle*)
- Mats Gunnar Ahlqvist (goqbi) (*Swedish*)
- diazepan (*Spanish, Spanish, Argentina*)
- marzuquccen (*Kabyle*)
- Juan José Salvador Piedra (JuanjoSalvador) (*Spanish*)
- VictorCorreia (victorcorreia1984) (*Afrikaans*)
- Tigran (tigransimonyan) (*Armenian*)
- Juan José Salvador Piedra (JuanjoSalvador) (*Spanish*)
- BurekzFinezt (*Serbian (Cyrillic)*)
- SHeija (*Finnish*)
- Gearguy (*Finnish*)
- atriix (*Swedish*)
- Jack R (isaac.97_WT) (*Spanish*)
- antonyho (*Chinese Traditional, Hong Kong*)
- asnomgtu (*Hungarian*)
- ahangarha (*Persian*)
- andruhov (*Russian, Ukrainian*)
- Aryamik Sharma (Aryamik) (*Swedish, Hindi*)
- phena109 (*Chinese Traditional, Hong Kong*)
- Aryamik Sharma (Aryamik) (*Swedish, Hindi*)
- Unmual (*Spanish*)
- 森の子リスのミーコの大冒険 (Phroneris) (*Japanese*)
- るいーね (ruine) (*Japanese*)
- ahangarha (*Persian*)
- Sam Tux (imahbub) (*Bengali*)
- Kristoffer Grundström (Umeaboy) (*Swedish*)
- igordrozniak (*Polish*)
- Unmual (*Spanish*)
- Isaac Huang (caasih) (*Chinese Traditional*)
- AW Unad (awcodify) (*Indonesian*)
- Allen Zhong (AstroProfundis) (*Chinese Simplified*)
- Cutls (cutls) (*Japanese*)
- Ray (Ipsumry) (*Spanish*)
- Falling Snowdin (tghgg) (*Vietnamese*)
- coxde (*Chinese Simplified*)
- Ray (Ipsumry) (*Spanish*)
- Gianfranco Fronteddu (gianfro.gianfro) (*Sardinian*)
- Rasmus Lindroth (RasmusLindroth) (*Swedish*)
- Andrea Lo Iacono (niels0n) (*Italian*)
- Parodper (*Galician*)
- fucsia (*Italian*)
- NadieAishi (*Spanish, Spanish, Mexico*)
- Kinshuk Sunil (kinshuksunil) (*Hindi*)
- Ullas Joseph (ullasjoseph) (*Malayalam*)
- Goudarz Jafari (Goudarz) (*Persian*)
- Yu-Pai Liu (tedliou) (*Chinese Traditional*)
- Amarin Cemthong (acitmaster) (*Thai*)
- Johannes Nilsson (nlssn) (*Swedish*)
- juanda097 (juanda-097) (*Spanish*)
- Anunnakey (*Macedonian*)
- fragola (*Italian*)
- erikkemp (*Dutch*)
- erikstl (*Esperanto*)
- twpenguin (*Chinese Traditional*)
- bobchao (*Chinese Traditional*)
- Esther (esthermations) (*Portuguese*)
- twpenguin (*Chinese Traditional*)
- MadeInSteak (*Finnish*)
- Heimen Stoffels (vistausss) (*Dutch*)
- Esther (esthermations) (*Portuguese*)
- t_aus_m (*German*)
- Heimen Stoffels (Vistaus) (*Dutch*)
- Rajarshi Guha (rajarshiguha) (*Bengali*)
- Andrew (iAndrew3) (*Romanian*)
- Mo_der Steven (SakuraPuare) (*Chinese Simplified*)
- Gopal Sharma (gopalvirat) (*Hindi*)
- arethsu (*Swedish*)
- Tofiq Abdula (Xwla) (*Sorani (Kurdish)*)
- Carlos Solís (csolisr) (*Esperanto*)
- Tofiq Abdula (Xwla) (*Sorani (Kurdish)*)
- Parthan S Ramanujam (parthan) (*Tamil*)
- Kasper Nymand (KasperNymand) (*Danish*)
- Jeff Huang (s8321414) (*Chinese Traditional*)
- TS (morte) (*Finnish*)
- subram (*Turkish*)
- SensDeViata (*Ukrainian*)
- Ptrcmd (ptrcmd) (*Chinese Traditional*)
- SergioFMiranda (*Portuguese, Brazilian*)
- Scvoet (scvoet) (*Chinese Simplified*)
- Percy (scvoet) (*Chinese Simplified*)
- Vivek K J (Vivekkj) (*Malayalam*)
- hiroTS (*Chinese Traditional*)
- johne32rus23 (*Russian*)
- AzureNya (*Chinese Simplified*)
- OctolinGamer (octolingamer) (*Portuguese, Brazilian*)
- Ram varma (ram4varma) (*Tamil*)
- Hexandcube (hexandcube) (*Polish*)
- 北䑓如法 (Nyoho) (*Japanese*)
- Pukima (Pukimaa) (*German*)
- diorama (*Italian*)
- Daniel Dimitrov (daniel.dimitrov) (*Bulgarian*)
- frumble (*German*)
- kekkepikkuni (*Tamil*)
- Neo_Chen (NeoChen1024) (*Chinese Traditional*)
- oorsutri (*Tamil*)
- Rhys Harrison (rhedders) (*Esperanto*)
- Neo_Chen (NeoChen1024) (*Chinese Traditional*)
- Nithin V (Nithin896) (*Tamil*)
- Marcus Myge (mygg-priv) (*Norwegian*)
- Miro Rauhala (mirorauhala) (*Finnish*)
- diorama (*Italian*)
- AlexKoala (alexkoala) (*Korean*)
- ಚಿ ನಟರ (chiraag-nataraj) (*Kannada*)
- Aswin C (officialcjunior) (*Malayalam*)
- Guillaume Turchini (orion78fr) (*French*)
- Ganesh D (auntgd) (*Marathi*)
- mawoka-myblock (mawoka) (*German*)
- dragnucs2 (*Arabic*)
- Ryan Ho (koungho) (*Chinese Traditional*)
- Pedro Henrique (exploronauta) (*Portuguese, Brazilian*)
@ -916,203 +983,245 @@ Following people have contributed to translation of Mastodon:
- Vasanthan (vasanthan) (*Tamil*)
- 硫酸鶏 (acid_chicken) (*Japanese*)
- clarmin b8 (clarminb8) (*Sorani (Kurdish)*)
- programizer (*German*)
- manukp (*Malayalam*)
- psymyn (*Hebrew*)
- earth dweller (sanethoughtyt) (*Marathi*)
- psymyn (*Hebrew*)
- meijerivoi (toilet) (*Finnish*)
- essaar (*Tamil*)
- serubeena (*Swedish*)
- Karol Kosek (krkkPL) (*Polish*)
- Rintan (*Japanese*)
- valarivan (*Tamil*)
- Karol Kosek (krkkPL) (*Polish*)
- Khó͘ Tiat-lêng (khotiatleng) (*Chinese Traditional, Taigi*)
- Hernik (hernik27) (*Czech*)
- Sebastián Andil (Selrond) (*Slovak*)
- valarivan (*Tamil*)
- kuchengrab (*German*)
- friedbeans (*Croatian*)
- Abi Turi (abi123) (*Georgian*)
- Hinaloe (hinaloe) (*Japanese*)
- filippodb (*Italian*)
- Sebastián Andil (Selrond) (*Slovak*)
- KEINOS (*Japanese*)
- filippodb (*Italian*)
- Asbjørn Olling (a2) (*Danish*)
- Balázs Meskó (meskobalazs) (*Hungarian*)
- Bottle (suryasalem2010) (*Tamil*)
- JzshAC (*Chinese Simplified*)
- Wrya ali (John12) (*Sorani (Kurdish)*)
- Khóo (khootiatling) (*Chinese Traditional*)
- Steven Tappert (sammy8806) (*German*)
- JzshAC (*Chinese Simplified*)
- Antillion (antillion99) (*Spanish*)
- Pukima (Pukimaa) (*German*)
- Steven Tappert (sammy8806) (*German*)
- Reg3xp (*Persian*)
- hiphipvargas (*Portuguese*)
- Wassim EL BOUHAMIDI (elbouhamidiw) (*Arabic*)
- gowthamanb (*Tamil*)
- hiphipvargas (*Portuguese*)
- Ch. (sftblw) (*Korean*)
- Jeff Huang (s8321414) (*Chinese Traditional*)
- Arttu Ylhävuori (arttu.ylhavuori) (*Finnish*)
- tctovsli (*Norwegian Nynorsk*)
- Timo Tijhof (Krinkle) (*Dutch*)
- Mikkel B. Goldschmidt (mikkelbjoern) (*Danish*)
- mecqor labi (mecqorlabi) (*Persian*)
- Odyssey346 (alexader612) (*Norwegian*)
- Yamagishi Kazutoshi (ykzts) (*Japanese, Icelandic, Sorani (Kurdish)*)
- Eban (ebanDev) (*French, Esperanto*)
- vjasiegd (*Polish*)
- SamitiMed (samiti3d) (*Thai*)
- Nícolas Lavinicki (nclavinicki) (*Portuguese, Brazilian*)
- snatcher (*Portuguese, Brazilian*)
- Rekan Adl (rekan-adl1) (*Sorani (Kurdish)*)
- VSx86 (*Russian*)
- umelard (*Hebrew*)
- Antara2Cinta (Se7enTime) (*Indonesian*)
- VSx86 (*Russian*)
- Daniel Dimitrov (danny-dimitrov) (*Bulgarian*)
- parnikkapore (*Thai*)
- mynameismonkey (*Welsh*)
- Sherwan Othman (sherwanothman11) (*Sorani (Kurdish)*)
- Yassine Aït-El-Mouden (yaitelmouden) (*Standard Moroccan Tamazight*)
- SKELET (*Danish*)
- Mo_der Steven (SakuraPuare) (*Chinese Simplified*)
- Fei Yang (Fei1Yang) (*Chinese Traditional*)
- ALEM FARID (faridatcemlulaqbayli) (*Kabyle*)
- Ğani (freegnu) (*Tatar*)
- Renato "Lond" Cerqueira (renatolond) (*Portuguese, Brazilian*)
- enipra (*Armenian*)
- ALEM FARID (faridatcemlulaqbayli) (*Kabyle*)
- musix (*Persian*)
- Renato "Lond" Cerqueira (renatolond) (*Portuguese, Brazilian*)
- ギャラ (gyara) (*Japanese, Chinese Simplified*)
- Hougo (hougo) (*French*)
- ybardapurkar (*Marathi*)
- 亜緯丹穂 (ayiniho) (*Japanese*)
- Adrián Lattes (haztecaso) (*Spanish*)
- Mordi Sacks (MordiSacks) (*Hebrew*)
- Trinsec (*Dutch*)
- Tigran's Tips (tigrank08) (*Armenian*)
- TracyJacks (*Chinese Simplified*)
- Szabolcs Gál (galszabolcs810624) (*Hungarian*)
- Vladislav Săcrieriu (vladislavs14) (*Romanian*)
- danreznik (*Hebrew*)
- rasheedgm (*Kannada*)
- GatoOscuro (*Spanish*)
- mecqor labi (mecqorlabi) (*Persian*)
- omquylzu (*Latvian*)
- c6ristian (*German*)
- Belkacem Mohammed (belkacem77) (*Kabyle*)
- lexxai (*Ukrainian*)
- Navjot Singh (nspeaks) (*Hindi*)
- omquylzu (*Latvian*)
- Ozai (*German*)
- Sahak Petrosyan (petrosyan) (*Armenian*)
- siamano (*Thai, Esperanto*)
- Oymate (*Bengali*)
- Viorel-Cătălin Răpițeanu (rapiteanu) (*Romanian*)
- siamano (*Thai, Esperanto*)
- Siddhartha Sarathi Basu (quinoa_biryani) (*Bengali*)
- Pachara Chantawong (pachara2202) (*Thai*)
- mkljczk (*Polish*)
- Skew (noan.perrot) (*French*)
- Zijian Zhao (jobs2512821228) (*Chinese Simplified*)
- turtle836 (*German*)
- Skew (noan.perrot) (*French*)
- mkljczk (*Polish*)
- Guru Prasath Anandapadmanaban (guruprasath) (*Tamil*)
- Lamin (laminne) (*Japanese*)
- turtle836 (*German*)
- Marcepanek_ (thekingmarcepan) (*Polish*)
- Feruz Oripov (FeruzOripov) (*Russian*)
- Lamin (laminne) (*Japanese*)
- Yann Aguettaz (yann-a) (*French*)
- Feruz Oripov (FeruzOripov) (*Russian*)
- serapolis (*Chinese Simplified, Chinese Traditional*)
- Mick Onio (xgc.redes) (*Asturian*)
- Tianqi Zhang (tina.zhang040609) (*Chinese Simplified*)
- Malik Mann (dermalikmann) (*German*)
- dadosch (*German*)
- r3dsp1 (*Chinese Traditional, Hong Kong*)
- padulafacundo (*Spanish*)
- hg6 (*Hindi*)
- Tianqi Zhang (tina.zhang040609) (*Chinese Simplified*)
- padulafacundo (*Spanish*)
- johannes hove-henriksen (J0hsHH) (*Norwegian*)
- Orlando Murcio (Atos20) (*Spanish, Mexico*)
- Padraic Calpin (padraic-padraic) (*Slovenian*)
- cenegd (*Chinese Simplified*)
- piupiupiudiu (*Chinese Simplified*)
- shdy (*German*)
- Padraic Calpin (padraic-padraic) (*Slovenian*)
- Ильзира Рахматуллина (rahmatullinailzira53) (*Tatar*)
- cenegd (*Chinese Simplified*)
- Hugh Liu (youloveonlymeh) (*Chinese Simplified*)
- Pixelcode (realpixelcode) (*German*)
- Yogesh K S (yogi) (*Kannada*)
- Adithya K (adithyak04) (*Malayalam*)
- Dennis Reimund (reimunddennis7) (*German*)
- Rakino (rakino) (*Chinese Simplified*)
- Miquel Sabaté Solà (mssola) (*Catalan*)
- Michał Sidor (michcioperz) (*Polish*)
- AmazighNM (*Kabyle*)
- Miquel Sabaté Solà (mssola) (*Catalan*)
- Jothipazhani Nagarajan (jothipazhani.n) (*Tamil*)
- Clash Clans (KURD12345) (*Sorani (Kurdish)*)
- hallomaurits (*Dutch*)
- alnd hezh (alndhezh) (*Sorani (Kurdish)*)
- Clash Clans (KURD12345) (*Sorani (Kurdish)*)
- Solid Rhino (SolidRhino) (*Dutch*)
- Metehan Özyürek (MetehanOzyurek) (*Turkish*)
- 林水溶 (shuiRong) (*Chinese Simplified*)
- Sébastien Feugère (smonff) (*French*)
- Y.Yamashiro (uist1idrju3i) (*Japanese*)
- Takeshi Umeda (noellabo) (*Japanese*)
- k_taka (peaceroad) (*Japanese*)
- Hallo Abdullah (hallo_hamza12) (*Sorani (Kurdish)*)
- hussama (*Portuguese, Brazilian*)
- Sébastien Feugère (smonff) (*French*)
- 林水溶 (shuiRong) (*Chinese Simplified*)
- eichkat3r (*German*)
- OminousCry (*Russian*)
- SnDer (*Dutch*)
- Hallo Abdullah (hallo_hamza12) (*Sorani (Kurdish)*)
- Ashok314 (ashok314) (*Hindi*)
- PifyZ (*French*)
- OminousCry (*Russian*)
- Robert Yano (throwcalmbobaway) (*Spanish, Mexico*)
- Tom_ (*Czech*)
- Tagada (Tagadda) (*French*)
- shafouz (*Portuguese, Brazilian*)
- Yasin İsa YILDIRIM (redsfyre) (*Turkish*)
- eichkat3r (*German*)
- SnDer (*Dutch*)
- Kahina Mess (K_hina) (*Kabyle*)
- Nathaël Noguès (NatNgs) (*French*)
- Kk (kishorkumara3) (*Kannada*)
- Swati Sani (swatisani) (*Urdu (Pakistan)*)
- Kk (kishorkumara3) (*Kannada*)
- Daniel M. (daniconil) (*Catalan*)
- Shrinivasan T (tshrinivasan) (*Tamil*)
- さっかりんにーさん (saccharin23) (*Japanese*)
- 夜楓Yoka (Yoka2627) (*Chinese Simplified*)
- Daniel M. (daniconil) (*Catalan*)
- Nathaël Noguès (NatNgs) (*French*)
- さっかりんにーさん (saccharin23) (*Japanese*)
- Rex_sa (rex07) (*Arabic*)
- Robin van der Vliet (RobinvanderVliet) (*Esperanto*)
- Vikatakavi (*Kannada*)
- SusVersiva (*Catalan*)
- Tradjincal (tradjincal) (*French*)
- pullopen (*Chinese Simplified*)
- Robin van der Vliet (RobinvanderVliet) (*Esperanto*)
- SusVersiva (*Catalan*)
- Marvin (magicmarvman) (*German*)
- Zinkokooo (*Basque*)
- mmokhi (*Persian*)
- Livingston Samuel (livingston) (*Tamil*)
- prabhjot (*Hindi*)
- sergioaraujo1 (*Portuguese, Brazilian*)
- CyberAmoeba (pseudoobscura) (*Chinese Simplified*)
- tsundoker (*Malayalam*)
- eorn (*Breton*)
- prabhjot (*Hindi*)
- mmokhi (*Persian*)
- sergioaraujo1 (*Portuguese, Brazilian*)
- Entelekheia-ousia (*Chinese Simplified*)
- Pierre Morvan (Iriep) (*Breton*)
- oscfd (*Spanish*)
- skaaarrr (*German*)
- Ricardo Colin (rysard) (*Spanish*)
- mkljczk (mykylyjczyk) (*Polish*)
- Philipp Fischbeck (PFischbeck) (*German*)
- fedot (*Russian*)
- Paz Galindo (paz.almendra.g) (*Spanish*)
- GaggiX (*Italian*)
- ralozkolya (*Georgian*)
- Ricardo Colin (rysard) (*Spanish*)
- Philipp Fischbeck (PFischbeck) (*German*)
- Zoé Bőle (zoe1337) (*German*)
- EzigboOmenana (*Cornish*)
- GaggiX (*Italian*)
- Lukas Fülling (lfuelling) (*German*)
- JackXu (Merman-Jack) (*Chinese Simplified*)
- Aymeric (AymBroussier) (*French*)
- ralozkolya (*Georgian*)
- Apple (blackteaovo) (*Chinese Simplified*)
- asala4544 (*Basque*)
- Xurxo Guerra (xguerrap) (*Galician*)
- qwerty287 (*German*)
- Anoop (anoopp) (*Malayalam*)
- pezcurrel (*Italian*)
- Samir Tighzert (samir_t7) (*Kabyle*)
- Dremski (*Bulgarian*)
- Xurxo Guerra (xguerrap) (*Galician*)
- Dennis Reimund (reimund_dennis) (*German*)
- ru_mactunnag (*Scottish Gaelic*)
- Nocta (*French*)
- Aymeric (AymBroussier) (*French*)
- mashirozx (*Chinese Simplified*)
- Albatroz Jeremias (albjeremias) (*Portuguese*)
- Samir Tighzert (samir_t7) (*Kabyle*)
- Apple (blackteaovo) (*Chinese Simplified*)
- Nocta (*French*)
- OpenAlgeria (*Arabic*)
- tamaina (*Japanese*)
- Matias Lavik (matiaslavik) (*Norwegian Nynorsk*)
- Amith Raj Shetty (amithraj1989) (*Kannada*)
- abidin toumi (Zet24) (*Arabic*)
- mikel (mikelalas) (*Spanish*)
- OpenAlgeria (*Arabic*)
- random_person (*Spanish*)
- Sais Lakshmanan (Saislakshmanan) (*Tamil*)
- Trond Boksasp (boksasp) (*Norwegian*)
- xpac1985 (xpac) (*German*)
- Kaede (kaedech) (*Japanese*)
- ÀŘǾŚ PÀŚĦÀÍ (arospashai) (*Sorani (Kurdish)*)
- Matias Lavik (matiaslavik) (*Norwegian Nynorsk*)
- Zlr- (cZeler) (*French*)
- Mohammad Adnan Mahmood (adnanmig) (*Arabic*)
- mimikun (*Japanese*)
- smedvedev (*Russian*)
- mikel (mikelalas) (*Spanish*)
- asretro (*Chinese Traditional, Hong Kong*)
- tamaina (*Japanese*)
- Aman Alam (aalam) (*Punjabi*)
- ÀŘǾŚ PÀŚĦÀÍ (arospashai) (*Sorani (Kurdish)*)
- Kaede (kaedech) (*Japanese*)
- Doug (douglasalvespe) (*Portuguese, Brazilian*)
- Trond Boksasp (boksasp) (*Norwegian*)
- Fleva (*Sardinian*)
- Mohammad Adnan Mahmood (adnanmig) (*Arabic*)
- Sais Lakshmanan (Saislakshmanan) (*Tamil*)
- Amith Raj Shetty (amithraj1989) (*Kannada*)
- random_person (*Spanish*)
- Abijeet Patro (Abijeet) (*Basque*)
- SamOak (*Portuguese, Brazilian*)
- Aries (orlea) (*Japanese*)
- Bartek Fijałkowski (brateq) (*Polish*)
- NeverMine17 (*Russian*)
- Brodi (brodi1) (*Dutch*)
- Ács Zoltán (zoli111) (*Hungarian*)
- capiscuas (*Spanish*)
- Benjamin Cobb (benjamincobb) (*German*)
- djoerd (*Dutch*)
- waweic (*German*)
- Amir Kurdo (kuraking202) (*Sorani (Kurdish)*)
- dobrado (*Portuguese, Brazilian*)
- Baban Abdulrahman (baban.abdulrehman) (*Sorani (Kurdish)*)
- ebrezhoneg (*Breton*)
- dashty (*Sorani (Kurdish)*)
- dcapillae (*Spanish*)
- Azad ahmad (dashty) (*Sorani (Kurdish)*)
- Salh_haji6 (*Sorani (Kurdish)*)
- Amir Kurdo (kuraking202) (*Sorani (Kurdish)*)
- おさ (osapon) (*Japanese*)
- Ranj A Abdulqadir (RanjAhmed) (*Sorani (Kurdish)*)
- umonaca (*Chinese Simplified*)
- Bartek Fijałkowski (brateq) (*Polish*)
- tateisu (*Japanese*)
- centumix (*Japanese*)
- Jari Ronkainen (ronchaine) (*Finnish*)
- Savarín Electrográfico Marmota Intergalactica (herrero.maty) (*Spanish*)
- Torsten Högel (torstenhoegel) (*German*)
- Abijeet Patro (Abijeet) (*Basque*)
- Ács Zoltán (acszoltan111) (*Hungarian*)
- Benjamin Cobb (benjamincobb) (*German*)
- waweic (*German*)
- Aries (orlea) (*Japanese*)
- ebrezhoneg (*Breton*)
- 于晚霞 (xissshawww) (*Chinese Simplified*)
- silverscat_3 (SilversCat) (*Japanese*)
- centumix (*Japanese*)
- umonaca (*Chinese Simplified*)
- Ni Futchi (futchitwo) (*Japanese*)
- おさ (osapon) (*Japanese*)
- kavitha129 (*Tamil*)
- dcapillae (*Spanish*)
- SamOak (*Portuguese, Brazilian*)
- capiscuas (*Spanish*)
- NeverMine17 (*Russian*)
- Nithya Mary (nithyamary25) (*Tamil*)
- t_aus_m (*German*)
- dobrado (*Portuguese, Brazilian*)
- Hannah (Aniqueper1) (*Chinese Simplified*)
- Jiniux (*Italian*)
- 于晚霞 (xissshawww) (*Chinese Simplified*)
- Jari Ronkainen (ronchaine) (*Finnish*)
- Nithya Mary (nithyamary25) (*Tamil*)

@ -3,6 +3,170 @@ Changelog
All notable changes to this project will be documented in this file.
## [3.4.1] - 2021-06-03
### Added
- Add new emoji assets from Twemoji 13.1.0 ([Gargron](https://github.com/tootsuite/mastodon/pull/16345))
### Fixed
- Fix some ActivityPub identifiers in server actor outbox ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16343))
- Fix custom CSS path setting cookies and being uncacheable due to it ([tribela](https://github.com/tootsuite/mastodon/pull/16314))
- Fix unread notification count when polling in web UI ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16272))
- Fix health check not being accessible through localhost ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16270))
- Fix some redis locks auto-releasing too fast ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16276), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16291))
- Fix e-mail confirmations API not working correctly ([Gargron](https://github.com/tootsuite/mastodon/pull/16348))
- Fix migration script not being able to run if it fails midway ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16312))
- Fix account deletion sometimes failing because of optimistic locks ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16317))
- Fix deprecated slash as division in SASS files ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16347))
- Fix `tootctl search deploy` compatibility error on Ruby 3 ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16346))
- Fix mailer jobs for deleted notifications erroring out ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16294))
## [3.4.0] - 2021-05-16
### Added
- **Add follow recommendations for onboarding** ([Gargron](https://github.com/tootsuite/mastodon/pull/15945), [Gargron](https://github.com/tootsuite/mastodon/pull/16161), [Gargron](https://github.com/tootsuite/mastodon/pull/16060), [Gargron](https://github.com/tootsuite/mastodon/pull/16077), [Gargron](https://github.com/tootsuite/mastodon/pull/16078), [Gargron](https://github.com/tootsuite/mastodon/pull/16160), [Gargron](https://github.com/tootsuite/mastodon/pull/16079), [noellabo](https://github.com/tootsuite/mastodon/pull/16044), [noellabo](https://github.com/tootsuite/mastodon/pull/16045), [Gargron](https://github.com/tootsuite/mastodon/pull/16152), [Gargron](https://github.com/tootsuite/mastodon/pull/16153), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16082), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16173), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16159), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16189))
- Tutorial on first web UI launch has been replaced with follow suggestions
- Follow suggestions take user locale into account and are a mix of accounts most followed by currently active local users, and accounts that wrote the most shared/favourited posts in the last 30 days
- Only accounts that have opted-in to being discoverable from their profile settings, and that do not require follow requests, will be suggested
- Moderators can review suggestions for every supported locale and suppress specific suggestions from appearing and admins can ensure certain accounts always show up in suggestions from the settings area
- New users no longer automatically follow admins
- **Add server rules** ([Gargron](https://github.com/tootsuite/mastodon/pull/15769), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15778))
- Admins can create and edit itemized server rules
- They are available through the REST API and on the about page
- **Add canonical e-mail blocks for suspended accounts** ([Gargron](https://github.com/tootsuite/mastodon/pull/16049))
- Normally, people can make multiple accounts using the same e-mail address using the `+` trick or by inserting or removing `.` characters from the first part of their address
- Once an account is suspended, it will no longer be possible for the e-mail address used by that account to be used for new sign-ups in any of its forms
- Add management of delivery availability in admin UI ([noellabo](https://github.com/tootsuite/mastodon/pull/15771))
- **Add system checks to dashboard in admin UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/15989), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15954), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16002))
- The dashboard will now warn you if you some Sidekiq queues are not being processed, if you have not defined any server rules, or if you forgot to run database migrations from the latest Mastodon upgrade
- Add inline description of moderation actions in admin UI ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15792))
- Add "recommended" label to activity/peers API toggles in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/16081))
- Add joined date to profiles in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/16169), [rinsuki](https://github.com/tootsuite/mastodon/pull/16186))
- Add transition to media modal background in web UI ([mkljczk](https://github.com/tootsuite/mastodon/pull/15843))
- Add option to opt-out of unread notification markers in web UI ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15842))
- Add borders to 📱, 🚲, and 📲 emojis in web UI ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15794), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16035))
- Add dropdown for boost privacy in boost confirmation modal in web UI ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15704))
- Add support for Ruby 3.0 ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16046), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16174))
- Add `Message-ID` header to outgoing emails ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16076))
- Some e-mail spam filters penalize e-mails that have a `Message-ID` header that uses a different domain name than the sending e-mail address. Now, the same domain will be used
- Add `af`, `gd` and `si` locales ([Gargron](https://github.com/tootsuite/mastodon/pull/16090))
- Add guard against DNS rebinding attacks ([noellabo](https://github.com/tootsuite/mastodon/pull/16087), [noellabo](https://github.com/tootsuite/mastodon/pull/16095))
- Add HTTP header to explicitly opt-out of FLoC by default ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16036))
- Add missing push notification title for polls and statuses ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15929), [mkljczk](https://github.com/tootsuite/mastodon/pull/15564), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15931))
- Add `POST /api/v1/emails/confirmations` to REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/15816), [Gargron](https://github.com/tootsuite/mastodon/pull/15949))
- This method allows an app through which a user signed-up to request a new confirmation e-mail to be sent, or to change the e-mail of the account before it is confirmed
- Add `GET /api/v1/accounts/lookup` to REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/15740), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15750))
- This method allows to quickly convert a username of a known account to an ID that can be used with the REST API, or to check if a username is available
for sign-up
- Add `policy` param to `POST /api/v1/push/subscriptions` in REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/16040))
- This param allows an app to control from whom notifications should be delivered as push notifications to the app
- Add `details` to error response for `POST /api/v1/accounts` in REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/15803))
- This attribute allows an app to display more helpful information to the user about why the sign-up did not succeed
- Add `SIDEKIQ_REDIS_URL` and related environment variables to optionally use a separate Redis server for Sidekiq ([noellabo](https://github.com/tootsuite/mastodon/pull/16188))
### Changed
- Change trending hashtags to be affected be reblogs ([Gargron](https://github.com/tootsuite/mastodon/pull/16164))
- Previously, only original posts contributed to a hashtag's trending score
- Now, reblogs of posts will also contribute to that hashtag's trending score
- Change e-mail confirmation link to always redirect to web UI ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16151))
- Change log level of worker lifecycle to WARN in streaming API ([Gargron](https://github.com/tootsuite/mastodon/pull/16110))
- Since running with INFO log level in production is not always desirable, it is easy to miss when a worker is shutdown and a new one is started
- Change the nouns "toot" and "status" to "post" in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/16080), [Gargron](https://github.com/tootsuite/mastodon/pull/16089))
- To be clear, the button still says "Toot!"
- Change order of dropdown menu on posts to be more intuitive in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/15647))
- Change description of keyboard shortcuts in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/16129))
- Change option labels on edit profile page ([Gargron](https://github.com/tootsuite/mastodon/pull/16041))
- "Lock account" is now "Require follow requests"
- "List this account on the directory" is now "Suggest account to others"
- "Hide your network" is now "Hide your social graph"
- Change newly generated account IDs to not be enumerable ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15844))
- Change Web Push API deliveries to use request pooling ([Gargron](https://github.com/tootsuite/mastodon/pull/16014))
- Change multiple mentions with same username to render with domain ([Gargron](https://github.com/tootsuite/mastodon/pull/15718), [noellabo](https://github.com/tootsuite/mastodon/pull/16038))
- When a post contains mentions of two or more users who have the same username, but on different domains, render their names with domain to help disambiguate them
- Always render the domain of usernames used in profile metadata
- Change health check endpoint to reveal less information ([Gargron](https://github.com/tootsuite/mastodon/pull/15988))
- Change account counters to use upsert (requires Postgres >= 9.5) ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15913))
- Change `mastodon:setup` to not call `assets:precompile` in Docker ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/13942))
- **Change max. image dimensions to 1920x1080px (1080p)** ([Gargron](https://github.com/tootsuite/mastodon/pull/15690))
- Previously, this was 1280x1280px
- This is the amount of pixels that original images get downsized to
- Change custom emoji to be animated when hovering container in web UI ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15637))
- Change streaming API from deprecated ClusterWS/cws to ws ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15932))
- Change systemd configuration to add sandboxing features ([Izorkin](https://github.com/tootsuite/mastodon/pull/15937), [Izorkin](https://github.com/tootsuite/mastodon/pull/16103), [Izorkin](https://github.com/tootsuite/mastodon/pull/16127))
- Change nginx configuration to make running Onion service easier ([cohosh](https://github.com/tootsuite/mastodon/pull/15498))
- Change Helm configuration ([dunn](https://github.com/tootsuite/mastodon/pull/15722), [dunn](https://github.com/tootsuite/mastodon/pull/15728), [dunn](https://github.com/tootsuite/mastodon/pull/15748), [dunn](https://github.com/tootsuite/mastodon/pull/15749), [dunn](https://github.com/tootsuite/mastodon/pull/15767))
- Change Docker configuration ([SuperSandro2000](https://github.com/tootsuite/mastodon/pull/10823), [mashirozx](https://github.com/tootsuite/mastodon/pull/15978))
### Removed
- Remove PubSubHubbub-related columns from accounts table ([Gargron](https://github.com/tootsuite/mastodon/pull/16170), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15857))
- Remove dependency on @babel/plugin-proposal-class-properties ([ykzts](https://github.com/tootsuite/mastodon/pull/16155))
- Remove dependency on pluck_each gem ([Gargron](https://github.com/tootsuite/mastodon/pull/16012))
- Remove spam check and dependency on nilsimsa gem ([Gargron](https://github.com/tootsuite/mastodon/pull/16011))
- Remove MySQL-specific code from Mastodon::MigrationHelpers ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15924))
- Remove IE11 from supported browsers target ([gol-cha](https://github.com/tootsuite/mastodon/pull/15779))
### Fixed
- Fix "You might be interested in" flashing while searching in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/16162))
- Fix display of posts without text content in web UI ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15665))
- Fix Google Translate breaking web UI ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15610), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15611))
- Fix web UI crashing when SVG support is disabled ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15809))
- Fix web UI crash when a status opened in the media modal is deleted ([kaias1jp](https://github.com/tootsuite/mastodon/pull/15701))
- Fix OCR language data failing to load in web UI ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15519))
- Fix footer links not being clickable in Safari in web UI ([noellabo](https://github.com/tootsuite/mastodon/pull/15496))
- Fix autofocus/autoselection not working on mobile in web UI ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15555), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15985))
- Fix media redownload worker retrying on unexpected response codes ([Gargron](https://github.com/tootsuite/mastodon/pull/16111))
- Fix thread resolve worker retrying when status no longer exists ([Gargron](https://github.com/tootsuite/mastodon/pull/16109))
- Fix n+1 queries when rendering statuses in REST API ([abcang](https://github.com/tootsuite/mastodon/pull/15641))
- Fix n+1 queries when rendering notifications in REST API ([abcang](https://github.com/tootsuite/mastodon/pull/15640))
- Fix delete of local reply to local parent not being forwarded ([Gargron](https://github.com/tootsuite/mastodon/pull/16096))
- Fix remote reporters not receiving suspend/unsuspend activities ([Gargron](https://github.com/tootsuite/mastodon/pull/16050))
- Fix understanding (not fully qualified) `as:Public` and `Public` ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15948))
- Fix actor update not being distributed on profile picture deletion ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15461))
- Fix processing of incoming Delete activities ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16084))
- Fix processing of incoming Block activities ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15546))
- Fix processing of incoming Update activities of unknown accounts ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15514))
- Fix URIs of repeat follow requests not being recorded ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15662))
- Fix error on requests with no `Digest` header ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15782))
- Fix activity object not requiring signature in secure mode ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15592))
- Fix database serialization failure returning HTTP 500 ([Gargron](https://github.com/tootsuite/mastodon/pull/16101))
- Fix media processing getting stuck on too much stdin/stderr ([Gargron](https://github.com/tootsuite/mastodon/pull/16136))
- Fix some inefficient array manipulations ([007lva](https://github.com/tootsuite/mastodon/pull/15513), [007lva](https://github.com/tootsuite/mastodon/pull/15527))
- Fix some inefficient regex matching ([007lva](https://github.com/tootsuite/mastodon/pull/15528))
- Fix some inefficient SQL queries ([abcang](https://github.com/tootsuite/mastodon/pull/16104), [abcang](https://github.com/tootsuite/mastodon/pull/16106), [abcang](https://github.com/tootsuite/mastodon/pull/16105))
- Fix trying to fetch key from empty URI when verifying HTTP signature ([Gargron](https://github.com/tootsuite/mastodon/pull/16100))
- Fix `tootctl maintenance fix-duplicates` failures ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15923), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15515))
- Fix error when removing status caused by race condition ([Gargron](https://github.com/tootsuite/mastodon/pull/16099))
- Fix blocking someone not clearing up list feeds ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16205))
- Fix misspelled URLs character counting ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15382))
- Fix Sidekiq hanging forever due to a Resolv bug in Ruby 2.7.3 ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16157))
- Fix edge case where follow limit interferes with accepting a follow ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16098))
- Fix inconsistent lead text style in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/16052), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/16086))
- Fix reports of already suspended accounts being recorded ([Gargron](https://github.com/tootsuite/mastodon/pull/16047))
- Fix sign-up restrictions based on IP addresses not being enforced ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15607))
- Fix YouTube embeds failing due to YouTube serving wrong OEmbed URLs ([Gargron](https://github.com/tootsuite/mastodon/pull/15716))
- Fix error when rendering public pages with media without meta ([Gargron](https://github.com/tootsuite/mastodon/pull/16112))
- Fix misaligned logo on follow button on public pages ([noellabo](https://github.com/tootsuite/mastodon/pull/15458))
- Fix video modal not working on public pages ([noellabo](https://github.com/tootsuite/mastodon/pull/15469))
- Fix race conditions on account migration creation ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15597))
- Fix not being able to change world filter expiration back to “Never” ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15858))
- Fix `.env.vagrant` not setting `RAILS_ENV` variable ([chandrn7](https://github.com/tootsuite/mastodon/pull/15709))
- Fix error when muting users with `duration` in REST API ([Tak](https://github.com/tootsuite/mastodon/pull/15516))
- Fix border padding on front page in light theme ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15926))
- Fix wrong URL to custom CSS when `CDN_HOST` is used ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15927))
- Fix `tootctl accounts unfollow` ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15639))
- Fix `tootctl emoji import` wasting time on MacOS shadow files ([cortices](https://github.com/tootsuite/mastodon/pull/15430))
- Fix `tootctl emoji import` not treating shortcodes as case-insensitive ([angristan](https://github.com/tootsuite/mastodon/pull/15738))
- Fix some issues with SAML account creation ([Gargron](https://github.com/tootsuite/mastodon/pull/15222), [kaiyou](https://github.com/tootsuite/mastodon/pull/15511))
- Fix MX validation applying for explicitly allowed e-mail domains ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15930))
- Fix share page not using configured custom mascot ([tribela](https://github.com/tootsuite/mastodon/pull/15687))
- Fix instance actor not being automatically created if it wasn't seeded properly ([ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15693))
- Fix HTTPS enforcement preventing Mastodon from being run as an Onion service ([cohosh](https://github.com/tootsuite/mastodon/pull/15560), [jtracey](https://github.com/tootsuite/mastodon/pull/15741), [ClearlyClaire](https://github.com/tootsuite/mastodon/pull/15712), [cohosh](https://github.com/tootsuite/mastodon/pull/15725))
- Fix app name, website and redirect URIs not having a maximum length ([Gargron](https://github.com/tootsuite/mastodon/pull/16042))
## [3.3.0] - 2020-12-27
### Added

@ -58,9 +58,17 @@ You can submit translations via [Crowdin](https://crowdin.com/project/mastodon).
## Pull requests
Please use clean, concise titles for your pull requests. We use commit squashing, so the final commit in the master branch will carry the title of the pull request.
**Please use clean, concise titles for your pull requests.** Unless the pull request is about refactoring code, updating dependencies or other internal tasks, assume that the person reading the pull request title is not a programmer or Mastodon developer, but instead a Mastodon user or server administrator, and **try to describe your change or fix from their perspective**. We use commit squashing, so the final commit in the main branch will carry the title of the pull request, and commits from the main branch are fed into the changelog. The changelog is separated into [keepachangelog.com categories](https://keepachangelog.com/en/1.0.0/), and while that spec does not prescribe how the entries ought to be named, for easier sorting, start your pull request titles using one of the verbs "Add", "Change", "Deprecate", "Remove", or "Fix" (present tense).
The smaller the set of changes in the pull request is, the quicker it can be reviewed and merged. Splitting tasks into multiple smaller pull requests is often preferable.
Example:
|Not ideal|Better|
|---|----|
|Fixed NoMethodError in RemovalWorker|Fix nil error when removing statuses caused by race condition|
It is not always possible to phrase every change in such a manner, but it is desired.
**The smaller the set of changes in the pull request is, the quicker it can be reviewed and merged.** Splitting tasks into multiple smaller pull requests is often preferable.
**Pull requests that do not pass automated checks may not be reviewed**. In particular, you need to keep in mind:

@ -1,7 +1,7 @@
FROM ubuntu:20.04 as build-dep
# Use bash for the shell
SHELL ["/usr/bin/bash", "-c"]
SHELL ["/bin/bash", "-c"]
# Install Node v12 (LTS)
ENV NODE_VER="12.21.0"
@ -17,35 +17,19 @@ RUN ARCH= && \
*) echo "unsupported architecture"; exit 1 ;; \
esac && \
echo "Etc/UTC" > /etc/localtime && \
apt update && \
apt -y install wget python && \
apt-get update && \
apt-get install -y --no-install-recommends ca-certificates wget python && \
cd ~ && \
wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-$ARCH.tar.gz && \
wget -q https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-$ARCH.tar.gz && \
tar xf node-v$NODE_VER-linux-$ARCH.tar.gz && \
rm node-v$NODE_VER-linux-$ARCH.tar.gz && \
mv node-v$NODE_VER-linux-$ARCH /opt/node
# Install jemalloc
ENV JE_VER="5.2.1"
RUN apt update && \
apt -y install make autoconf gcc g++ && \
cd ~ && \
wget https://github.com/jemalloc/jemalloc/archive/$JE_VER.tar.gz && \
tar xf $JE_VER.tar.gz && \
cd jemalloc-$JE_VER && \
./autogen.sh && \
./configure --prefix=/opt/jemalloc && \
make -j$(nproc) > /dev/null && \
make install_bin install_include install_lib && \
cd .. && rm -rf jemalloc-$JE_VER $JE_VER.tar.gz
# Install Ruby
ENV RUBY_VER="2.7.2"
ENV CPPFLAGS="-I/opt/jemalloc/include"
ENV LDFLAGS="-L/opt/jemalloc/lib/"
RUN apt update && \
apt -y install build-essential \
bison libyaml-dev libgdbm-dev libreadline-dev \
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential \
bison libyaml-dev libgdbm-dev libreadline-dev libjemalloc-dev \
libncurses5-dev libffi-dev zlib1g-dev libssl-dev && \
cd ~ && \
wget https://cache.ruby-lang.org/pub/ruby/${RUBY_VER%.*}/ruby-$RUBY_VER.tar.gz && \
@ -55,25 +39,24 @@ RUN apt update && \
--with-jemalloc \
--with-shared \
--disable-install-doc && \
ln -s /opt/jemalloc/lib/* /usr/lib/ && \
make -j$(nproc) > /dev/null && \
make -j"$(nproc)" > /dev/null && \
make install && \
cd .. && rm -rf ruby-$RUBY_VER.tar.gz ruby-$RUBY_VER
rm -rf ../ruby-$RUBY_VER.tar.gz ../ruby-$RUBY_VER
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin"
RUN npm install -g yarn && \
gem install bundler && \
apt update && \
apt -y install git libicu-dev libidn11-dev \
libpq-dev libprotobuf-dev protobuf-compiler
apt-get update && \
apt-get install -y --no-install-recommends git libicu-dev libidn11-dev \
libpq-dev libprotobuf-dev protobuf-compiler shared-mime-info
COPY Gemfile* package.json yarn.lock /opt/mastodon/
RUN cd /opt/mastodon && \
bundle config set deployment 'true' && \
bundle config set without 'development test' && \
bundle install -j$(nproc) && \
bundle install -j"$(nproc)" && \
yarn install --pure-lockfile
FROM ubuntu:20.04
@ -81,7 +64,6 @@ FROM ubuntu:20.04
# Copy over all the langs needed for runtime
COPY --from=build-dep /opt/node /opt/node
COPY --from=build-dep /opt/ruby /opt/ruby
COPY --from=build-dep /opt/jemalloc /opt/jemalloc
# Add more PATHs to the PATH
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin:/opt/mastodon/bin"
@ -89,35 +71,26 @@ ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin:/opt/mastodon/bin"
# Create the mastodon user
ARG UID=991
ARG GID=991
RUN apt update && \
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update && \
echo "Etc/UTC" > /etc/localtime && \
ln -s /opt/jemalloc/lib/* /usr/lib/ && \
apt install -y whois wget && \
apt-get install -y --no-install-recommends whois wget && \
addgroup --gid $GID mastodon && \
useradd -m -u $UID -g $GID -d /opt/mastodon mastodon && \
echo "mastodon:`head /dev/urandom | tr -dc A-Za-z0-9 | head -c 24 | mkpasswd -s -m sha-256`" | chpasswd
echo "mastodon:$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 24 | mkpasswd -s -m sha-256)" | chpasswd && \
rm -rf /var/lib/apt/lists/*
# Install mastodon runtime deps
RUN apt -y --no-install-recommends install \
libssl1.1 libpq5 imagemagick ffmpeg \
RUN apt-get update && \
apt-get -y --no-install-recommends install \
libssl1.1 libpq5 imagemagick ffmpeg libjemalloc2 \
libicu66 libprotobuf17 libidn11 libyaml-0-2 \
file ca-certificates tzdata libreadline8 && \
apt -y install gcc && \
file ca-certificates tzdata libreadline8 gcc tini && \
ln -s /opt/mastodon /mastodon && \
gem install bundler && \
rm -rf /var/cache && \
rm -rf /var/lib/apt/lists/*
# Add tini
ENV TINI_VERSION="0.19.0"
RUN dpkgArch="$(dpkg --print-architecture)" && \
ARCH=$dpkgArch && \
wget https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-$ARCH \
https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-$ARCH.sha256sum && \
cat tini-$ARCH.sha256sum | sha256sum -c - && \
mv tini-$ARCH /tini && rm tini-$ARCH.sha256sum && \
chmod +x /tini
# Copy over mastodon source, and dependencies from building, and set permissions
COPY --chown=mastodon:mastodon . /opt/mastodon
COPY --from=build-dep --chown=mastodon:mastodon /opt/mastodon /opt/mastodon
@ -140,5 +113,5 @@ RUN cd ~ && \
# Set the work dir and the container entry point
WORKDIR /opt/mastodon
ENTRYPOINT ["/tini", "--"]
ENTRYPOINT ["/usr/bin/tini", "--"]
EXPOSE 3000 4000

@ -1,12 +1,12 @@
# frozen_string_literal: true
source 'https://rubygems.org'
ruby '>= 2.5.0', '< 3.0.0'
ruby '>= 2.5.0', '< 3.1.0'
gem 'pkg-config', '~> 1.4'
gem 'puma', '~> 5.2'
gem 'rails', '~> 5.2.4.5'
gem 'puma', '~> 5.3'
gem 'rails', '~> 6.1.3'
gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 1.1'
gem 'rack', '~> 2.2.3'
@ -17,12 +17,10 @@ gem 'makara', '~> 0.5'
gem 'pghero', '~> 2.8'
gem 'dotenv-rails', '~> 2.7'
gem 'aws-sdk-s3', '~> 1.91', require: false
gem 'aws-sdk-s3', '~> 1.96', require: false
gem 'fog-core', '<= 2.1.0'
gem 'fog-openstack', '~> 0.3', require: false
gem 'paperclip', '~> 6.0'
gem 'paperclip-av-transcoder', '~> 0.6'
gem 'streamio-ffmpeg', '~> 3.0'
gem 'blurhash', '~> 0.1'
gem 'active_model_serializers', '~> 0.10'
@ -32,9 +30,9 @@ gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'iso-639'
gem 'chewy', '~> 5.2'
gem 'cld3', '~> 3.4.1'
gem 'devise', '~> 4.7'
gem 'devise-two-factor', '~> 3.1'
gem 'cld3', '~> 3.4.2'
gem 'devise', '~> 4.8'
gem 'devise-two-factor', '~> 4.0'
group :pam_authentication, optional: true do
gem 'devise_pam_authenticatable2', '~> 9.2'
@ -54,16 +52,14 @@ gem 'fast_blank', '~> 1.0'
gem 'fastimage'
gem 'hiredis', '~> 0.6'
gem 'redis-namespace', '~> 1.8'
gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
gem 'htmlentities', '~> 4.3'
gem 'http', '~> 4.4'
gem 'http_accept_language', '~> 2.1'
gem 'httplog', '~> 1.4.3'
gem 'httplog', '~> 1.5.0'
gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.2'
gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar'
gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
gem 'nokogiri', '~> 1.11'
gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.11'
@ -75,19 +71,19 @@ gem 'pundit', '~> 2.1'
gem 'premailer-rails'
gem 'rack-attack', '~> 6.5'
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
gem 'rails-i18n', '~> 5.1'
gem 'rails-i18n', '~> 6.0'
gem 'rails-settings-cached', '~> 0.6'
gem 'redis', '~> 4.2', require: ['redis', 'redis/connection/hiredis']
gem 'redis', '~> 4.3', require: ['redis', 'redis/connection/hiredis']
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 1.2'
gem 'rqrcode', '~> 2.0'
gem 'ruby-progressbar', '~> 1.11'
gem 'sanitize', '~> 5.2'
gem 'scenic', '~> 1.5'
gem 'sidekiq', '~> 6.1'
gem 'sidekiq-scheduler', '~> 3.0'
gem 'sidekiq', '~> 6.2'
gem 'sidekiq-scheduler', '~> 3.1'
gem 'sidekiq-unique-jobs', '~> 7.0'
gem 'sidekiq-bulk', '~>0.2.0'
gem 'simple-navigation', '~> 4.1'
gem 'simple-navigation', '~> 4.3'
gem 'simple_form', '~> 5.1'
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
gem 'stoplight', '~> 2.2.1'
@ -95,8 +91,8 @@ gem 'strong_migrations', '~> 0.7'
gem 'tty-prompt', '~> 0.23', require: false
gem 'twitter-text', '~> 3.1.0'
gem 'tzinfo-data', '~> 1.2021'
gem 'webpacker', '~> 5.2'
gem 'webpush'
gem 'webpacker', '~> 5.4'
gem 'webpush', '~> 0.3'
gem 'webauthn', '~> 3.0.0.alpha1'
gem 'json-ld'
@ -106,12 +102,12 @@ gem 'rdf-normalize', '~> 0.4'
gem 'redcarpet', '~> 3.5'
group :development, :test do
gem 'fabrication', '~> 2.21'
gem 'fabrication', '~> 2.22'
gem 'fuubar', '~> 2.5'
gem 'i18n-tasks', '~> 0.9', require: false
gem 'pry-byebug', '~> 3.9'
gem 'pry-rails', '~> 0.3'
gem 'rspec-rails', '~> 4.1'
gem 'rspec-rails', '~> 5.0'
end
group :production, :test do
@ -121,13 +117,13 @@ end
group :test do
gem 'capybara', '~> 3.35'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 2.17'
gem 'faker', '~> 2.18'
gem 'microformats', '~> 4.2'
gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.1'
gem 'simplecov', '~> 0.21', require: false
gem 'webmock', '~> 3.12'
gem 'parallel_tests', '~> 3.5'
gem 'webmock', '~> 3.13'
gem 'parallel_tests', '~> 3.7'
gem 'rspec_junit_formatter', '~> 0.4'
end
@ -140,10 +136,10 @@ group :development do
gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.4'
gem 'memory_profiler'
gem 'rubocop', '~> 1.11', require: false
gem 'rubocop-rails', '~> 2.9', require: false
gem 'brakeman', '~> 4.10', require: false
gem 'bundler-audit', '~> 0.7', require: false
gem 'rubocop', '~> 1.16', require: false
gem 'rubocop-rails', '~> 2.10', require: false
gem 'brakeman', '~> 5.0', require: false
gem 'bundler-audit', '~> 0.8', require: false
gem 'capistrano', '~> 3.16'
gem 'capistrano-rails', '~> 1.6'
@ -155,11 +151,11 @@ end
group :production do
gem 'lograge', '~> 0.11'
gem 'redis-rails', '~> 5.0'
end
gem 'concurrent-ruby', require: false
gem 'connection_pool', require: false
gem 'xorcist', '~> 1.1'
gem 'pluck_each', '~> 0.1.3'
gem 'resolv', '~> 0.1.0'

@ -1,68 +1,71 @@
GIT
remote: https://github.com/ianheggie/health_check
revision: 0b799ead604f900ed50685e9b2d469cd2befba5b
ref: 0b799ead604f900ed50685e9b2d469cd2befba5b
specs:
health_check (4.0.0.pre)
rails (>= 4.0)
GIT
remote: https://github.com/witgo/nilsimsa
revision: fd184883048b922b176939f851338d0a4971a532
ref: fd184883048b922b176939f851338d0a4971a532
specs:
nilsimsa (1.1.2)
GEM
remote: https://rubygems.org/
specs:
actioncable (5.2.4.5)
actionpack (= 5.2.4.5)
actioncable (6.1.3.2)
actionpack (= 6.1.3.2)
activesupport (= 6.1.3.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailer (5.2.4.5)
actionpack (= 5.2.4.5)
actionview (= 5.2.4.5)
activejob (= 5.2.4.5)
actionmailbox (6.1.3.2)
actionpack (= 6.1.3.2)
activejob (= 6.1.3.2)
activerecord (= 6.1.3.2)
activestorage (= 6.1.3.2)
activesupport (= 6.1.3.2)
mail (>= 2.7.1)
actionmailer (6.1.3.2)
actionpack (= 6.1.3.2)
actionview (= 6.1.3.2)
activejob (= 6.1.3.2)
activesupport (= 6.1.3.2)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (5.2.4.5)
actionview (= 5.2.4.5)
activesupport (= 5.2.4.5)
rack (~> 2.0, >= 2.0.8)
actionpack (6.1.3.2)
actionview (= 6.1.3.2)
activesupport (= 6.1.3.2)
rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.2.4.5)
activesupport (= 5.2.4.5)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.3.2)
actionpack (= 6.1.3.2)
activerecord (= 6.1.3.2)
activestorage (= 6.1.3.2)
activesupport (= 6.1.3.2)
nokogiri (>= 1.8.5)
actionview (6.1.3.2)
activesupport (= 6.1.3.2)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
active_model_serializers (0.10.12)
actionpack (>= 4.1, < 6.2)
activemodel (>= 4.1, < 6.2)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_record_query_trace (1.8)
activejob (5.2.4.5)
activesupport (= 5.2.4.5)
activejob (6.1.3.2)
activesupport (= 6.1.3.2)
globalid (>= 0.3.6)
activemodel (5.2.4.5)
activesupport (= 5.2.4.5)
activerecord (5.2.4.5)
activemodel (= 5.2.4.5)
activesupport (= 5.2.4.5)
arel (>= 9.0)
activestorage (5.2.4.5)
actionpack (= 5.2.4.5)
activerecord (= 5.2.4.5)
marcel (~> 0.3.1)
activesupport (5.2.4.5)
activemodel (6.1.3.2)
activesupport (= 6.1.3.2)
activerecord (6.1.3.2)
activemodel (= 6.1.3.2)
activesupport (= 6.1.3.2)
activestorage (6.1.3.2)
actionpack (= 6.1.3.2)
activejob (= 6.1.3.2)
activerecord (= 6.1.3.2)
activesupport (= 6.1.3.2)
marcel (~> 1.0.0)
mini_mime (~> 1.0.2)
activesupport (6.1.3.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
airbrussh (1.4.0)
@ -71,16 +74,13 @@ GEM
annotate (3.1.1)
activerecord (>= 3.2, < 7.0)
rake (>= 10.4, < 14.0)
arel (9.0.0)
ast (2.4.2)
attr_encrypted (3.1.0)
encryptor (~> 3.0.0)
av (0.9.0)
cocaine (~> 0.5.3)
awrence (1.1.1)
aws-eventstream (1.1.1)
aws-partitions (1.432.0)
aws-sdk-core (3.113.0)
aws-partitions (1.467.0)
aws-sdk-core (3.114.2)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
@ -88,7 +88,7 @@ GEM
aws-sdk-kms (1.43.0)
aws-sdk-core (~> 3, >= 3.112.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.91.0)
aws-sdk-s3 (1.96.1)
aws-sdk-core (~> 3, >= 3.112.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
@ -102,22 +102,22 @@ GEM
bindata (2.4.8)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
blurhash (0.1.4)
ffi (~> 1.10.0)
blurhash (0.1.5)
ffi (~> 1.14)
bootsnap (1.6.0)
msgpack (~> 1.0)
brakeman (4.10.1)
brakeman (5.0.4)
browser (4.2.0)
brpoplpush-redis_script (0.1.1)
brpoplpush-redis_script (0.1.2)
concurrent-ruby (~> 1.0, >= 1.0.5)
redis (>= 1.0, <= 5.0)
builder (3.2.4)
bullet (6.1.4)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
bundler-audit (0.7.0.1)
bundler-audit (0.8.0)
bundler (>= 1.2.0, < 3)
thor (>= 0.18, < 2)
thor (~> 1.0)
byebug (11.1.3)
capistrano (3.16.0)
airbrussh (>= 1.0.0)
@ -150,16 +150,14 @@ GEM
activesupport (>= 5.2)
elasticsearch (>= 2.0.0)
elasticsearch-dsl
chunky_png (1.3.15)
cld3 (3.4.1)
ffi (>= 1.1.0, < 1.15.0)
chunky_png (1.4.0)
cld3 (3.4.2)
ffi (>= 1.1.0, < 1.16.0)
climate_control (0.2.0)
cocaine (0.5.8)
climate_control (>= 0.0.3, < 1.0)
coderay (1.1.3)
color_diff (0.1)
concurrent-ruby (1.1.8)
connection_pool (2.2.3)
concurrent-ruby (1.1.9)
connection_pool (2.2.5)
cose (1.0.0)
cbor (~> 0.5.9)
openssl-signature_algorithm (~> 0.4.0)
@ -169,18 +167,18 @@ GEM
css_parser (1.7.1)
addressable
debug_inspector (1.0.0)
devise (4.7.3)
devise (4.8.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise-two-factor (3.1.0)
activesupport (< 6.1)
devise-two-factor (4.0.0)
activesupport (< 6.2)
attr_encrypted (>= 1.3, < 4, != 2)
devise (~> 4.0)
railties (< 6.1)
rotp (~> 2.0)
railties (< 6.2)
rotp (~> 6.0)
devise_pam_authenticatable2 (9.2.0)
devise (>= 4.0.0)
rpam2 (~> 4.0)
@ -190,7 +188,7 @@ GEM
docile (1.3.4)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (5.5.0)
doorkeeper (5.5.2)
railties (>= 5)
dotenv (2.7.6)
dotenv-rails (2.7.6)
@ -212,8 +210,8 @@ GEM
et-orbi (1.2.4)
tzinfo
excon (0.76.0)
fabrication (2.21.1)
faker (2.17.0)
fabrication (2.22.0)
faker (2.18.0)
i18n (>= 1.6, < 2)
faraday (1.3.0)
faraday-net_http (~> 1.0)
@ -221,8 +219,8 @@ GEM
ruby2_keywords
faraday-net_http (1.0.1)
fast_blank (1.0.0)
fastimage (2.2.3)
ffi (1.10.0)
fastimage (2.2.4)
ffi (1.15.0)
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
@ -239,9 +237,9 @@ GEM
fog-json (>= 1.0)
ipaddress (>= 0.8)
formatador (0.2.5)
fugit (1.3.9)
fugit (1.4.5)
et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.3)
raabro (~> 1.4)
fuubar (2.5.1)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
@ -275,10 +273,10 @@ GEM
http-parser (1.2.1)
ffi-compiler (>= 1.0, < 2.0)
http_accept_language (2.1.1)
httplog (1.4.3)
httplog (1.5.0)
rack (>= 1.0)
rainbow (>= 2.0.0)
i18n (1.8.9)
i18n (1.8.10)
concurrent-ruby (~> 1.0)
i18n-tasks (0.9.34)
activesupport (>= 4.0.2)
@ -290,11 +288,11 @@ GEM
rails-i18n
rainbow (>= 2.2.2, < 4.0)
terminal-table (>= 1.5.1)
idn-ruby (0.1.0)
idn-ruby (0.1.2)
ipaddress (0.8.3)
iso-639 (0.3.5)
jmespath (1.4.0)
json (2.3.1)
json (2.5.1)
json-canonicalization (0.2.1)
json-ld (3.1.9)
htmlentities (~> 4.3)
@ -334,28 +332,29 @@ GEM
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
loofah (2.9.0)
loofah (2.10.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
mini_mime (>= 0.1.1)
makara (0.5.0)
activerecord (>= 3.0.0)
marcel (0.3.3)
mimemagic (~> 0.3.2)
makara (0.5.1)
activerecord (>= 5.2.0)
marcel (1.0.1)
mario-redis-lock (1.2.1)
redis (>= 3.0.5)
memory_profiler (1.0.0)
method_source (1.0.0)
microformats (4.2.1)
microformats (4.3.1)
json (~> 2.2)
nokogiri (~> 1.10)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2020.0512)
mimemagic (0.3.5)
mini_mime (1.0.2)
mini_portile2 (2.5.0)
mimemagic (0.3.10)
nokogiri (~> 1)
rake
mini_mime (1.0.3)
mini_portile2 (2.5.3)
minitest (5.14.4)
msgpack (1.4.2)
multi_json (1.15.0)
@ -365,17 +364,17 @@ GEM
net-ssh (>= 2.6.5, < 7.0.0)
net-ssh (6.1.0)
nio4r (2.5.7)
nokogiri (1.11.2)
nokogiri (1.11.7)
mini_portile2 (~> 2.5.0)
racc (~> 1.4)
nokogumbo (2.0.4)
nokogiri (~> 1.8, >= 1.8.4)
nsa (0.2.7)
activesupport (>= 4.2, < 6)
nsa (0.2.8)
activesupport (>= 4.2, < 7)
concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0)
oj (3.11.3)
oj (3.11.5)
omniauth (1.9.1)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
@ -392,31 +391,25 @@ GEM
openssl (2.2.0)
openssl-signature_algorithm (0.4.0)
orm_adapter (0.5.0)
ox (2.14.3)
ox (2.14.5)
paperclip (6.0.0)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
mime-types
mimemagic (~> 0.3.0)
terrapin (~> 0.6.0)
paperclip-av-transcoder (0.6.4)
av (~> 0.9.0)
paperclip (>= 2.5.2)
parallel (1.20.1)
parallel_tests (3.5.2)
parallel_tests (3.7.0)
parallel
parser (3.0.0.0)
parser (3.0.1.1)
ast (~> 2.4.1)
parslet (2.0.0)
pastel (0.8.0)
tty-color (~> 0.5)
pg (1.2.3)
pghero (2.8.0)
pghero (2.8.1)
activerecord (>= 5)
pkg-config (1.4.5)
pluck_each (0.1.3)
activerecord (> 3.2.0)
activesupport (> 3.0.0)
pkg-config (1.4.6)
posix-spawn (0.3.15)
premailer (1.14.2)
addressable
@ -435,11 +428,11 @@ GEM
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.6)
puma (5.2.2)
puma (5.3.2)
nio4r (~> 2.0)
pundit (2.1.0)
activesupport (>= 3.0.0)
raabro (1.3.3)
raabro (1.4.0)
racc (1.5.2)
rack (2.2.3)
rack-attack (6.5.0)
@ -450,18 +443,20 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (5.2.4.5)
actioncable (= 5.2.4.5)
actionmailer (= 5.2.4.5)
actionpack (= 5.2.4.5)
actionview (= 5.2.4.5)
activejob (= 5.2.4.5)
activemodel (= 5.2.4.5)
activerecord (= 5.2.4.5)
activestorage (= 5.2.4.5)
activesupport (= 5.2.4.5)
bundler (>= 1.3.0)
railties (= 5.2.4.5)
rails (6.1.3.2)
actioncable (= 6.1.3.2)
actionmailbox (= 6.1.3.2)
actionmailer (= 6.1.3.2)
actionpack (= 6.1.3.2)
actiontext (= 6.1.3.2)
actionview (= 6.1.3.2)
activejob (= 6.1.3.2)
activemodel (= 6.1.3.2)
activerecord (= 6.1.3.2)
activestorage (= 6.1.3.2)
activesupport (= 6.1.3.2)
bundler (>= 1.15.0)
railties (= 6.1.3.2)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@ -472,17 +467,17 @@ GEM
nokogiri (>= 1.6)
rails-html-sanitizer (1.3.0)
loofah (~> 2.3)
rails-i18n (5.1.3)
rails-i18n (6.0.0)
i18n (>= 0.7, < 2)
railties (>= 5.0, < 6)
railties (>= 6.0.0, < 7)
rails-settings-cached (0.6.6)
rails (>= 4.2.0)
railties (5.2.4.5)
actionpack (= 5.2.4.5)
activesupport (= 5.2.4.5)
railties (6.1.3.2)
actionpack (= 6.1.3.2)
activesupport (= 6.1.3.2)
method_source
rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0)
thor (~> 1.0)
rainbow (3.0.0)
rake (13.0.3)
rdf (3.1.13)
@ -491,38 +486,23 @@ GEM
rdf-normalize (0.4.0)
rdf (~> 3.1)
redcarpet (3.5.1)
redis (4.2.5)
redis-actionpack (5.2.0)
actionpack (>= 5, < 7)
redis-rack (>= 2.1.0, < 3)
redis-store (>= 1.1.0, < 2)
redis-activesupport (5.2.0)
activesupport (>= 3, < 7)
redis-store (>= 1.3, < 2)
redis (4.3.1)
redis-namespace (1.8.1)
redis (>= 3.0.4)
redis-rack (2.1.3)
rack (>= 2.0.8, < 3)
redis-store (>= 1.2, < 2)
redis-rails (5.0.2)
redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2)
redis-store (1.9.0)
redis (>= 4, < 5)
regexp_parser (2.1.1)
request_store (1.5.0)
rack (>= 1.4)
resolv (0.1.0)
responders (3.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
rexml (3.2.4)
rotp (2.1.2)
rexml (3.2.5)
rotp (6.2.0)
rpam2 (4.0.2)
rqrcode (1.2.0)
rqrcode (2.0.0)
chunky_png (~> 1.0)
rqrcode_core (~> 0.2)
rqrcode_core (0.2.0)
rqrcode_core (~> 1.0)
rqrcode_core (1.0.0)
rspec-core (3.10.1)
rspec-support (~> 3.10.0)
rspec-expectations (3.10.1)
@ -531,10 +511,10 @@ GEM
rspec-mocks (3.10.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-rails (4.1.0)
actionpack (>= 4.2)
activesupport (>= 4.2)
railties (>= 4.2)
rspec-rails (5.0.1)
actionpack (>= 5.2)
activesupport (>= 5.2)
railties (>= 5.2)
rspec-core (~> 3.10)
rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10)
@ -545,26 +525,26 @@ GEM
rspec-support (3.10.2)
rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.11.0)
rubocop (1.16.1)
parallel (~> 1.10)
parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml
rubocop-ast (>= 1.2.0, < 2.0)
rubocop-ast (>= 1.7.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.4.1)
parser (>= 2.7.1.5)
rubocop-rails (2.9.1)
rubocop-ast (1.7.0)
parser (>= 3.0.1.1)
rubocop-rails (2.10.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 0.90.0, < 2.0)
rubocop (>= 1.7.0, < 2.0)
ruby-progressbar (1.11.0)
ruby-saml (1.11.0)
nokogiri (>= 1.5.10)
ruby2_keywords (0.0.4)
rufus-scheduler (3.6.0)
rufus-scheduler (3.7.0)
fugit (~> 1.1, >= 1.1.6)
safety_net_attestation (0.4.0)
jwt (~> 2.0)
@ -576,26 +556,26 @@ GEM
activerecord (>= 4.0.0)
railties (>= 4.0.0)
securecompare (1.0.0)
semantic_range (2.3.0)
sidekiq (6.1.3)
semantic_range (3.0.0)
sidekiq (6.2.1)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
sidekiq-bulk (0.2.0)
sidekiq
sidekiq-scheduler (3.0.1)
sidekiq-scheduler (3.1.0)
e2mmap
redis (>= 3, < 5)
rufus-scheduler (~> 3.2)
sidekiq (>= 3)
thwait
tilt (>= 1.4.0)
sidekiq-unique-jobs (7.0.4)
brpoplpush-redis_script (> 0.0.0, <= 2.0.0)
sidekiq-unique-jobs (7.0.12)
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
concurrent-ruby (~> 1.0, >= 1.0.5)
sidekiq (>= 5.0, < 7.0)
thor (>= 0.20, < 2.0)
simple-navigation (4.1.0)
simple-navigation (4.3.0)
activesupport (>= 2.3.2)
simple_form (5.1.0)
actionpack (>= 5.2)
@ -616,12 +596,10 @@ GEM
sshkit (1.21.2)
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
stackprof (0.2.16)
statsd-ruby (1.4.0)
stackprof (0.2.17)
statsd-ruby (1.5.0)
stoplight (2.2.1)
streamio-ffmpeg (3.0.2)
multi_json (~> 1.8)
strong_migrations (0.7.6)
strong_migrations (0.7.7)
activerecord (>= 5)
temple (0.8.2)
terminal-table (3.0.0)
@ -629,7 +607,6 @@ GEM
terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0)
thor (1.1.0)
thread_safe (0.3.6)
thwait (0.2.0)
e2mmap
tilt (2.0.10)
@ -638,7 +615,7 @@ GEM
openssl-signature_algorithm (~> 0.4.0)
tty-color (0.6.0)
tty-cursor (0.7.1)
tty-prompt (0.23.0)
tty-prompt (0.23.1)
pastel (~> 0.8)
tty-reader (~> 0.8)
tty-reader (0.9.0)
@ -649,8 +626,8 @@ GEM
twitter-text (3.1.0)
idn-ruby
unf (~> 0.1.0)
tzinfo (1.2.9)
thread_safe (~> 0.1)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2021.1)
tzinfo (>= 1.0.0)
unf (0.1.4)
@ -670,11 +647,11 @@ GEM
safety_net_attestation (~> 0.4.0)
securecompare (~> 1.0)
tpm-key_attestation (~> 0.9.0)
webmock (3.12.1)
webmock (3.13.0)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webpacker (5.2.1)
webpacker (5.4.0)
activesupport (>= 5.2)
rack-proxy (>= 0.6.1)
railties (>= 5.2)
@ -689,6 +666,7 @@ GEM
xorcist (1.1.2)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.4.2)
PLATFORMS
ruby
@ -698,15 +676,15 @@ DEPENDENCIES
active_record_query_trace (~> 1.8)
addressable (~> 2.7)
annotate (~> 3.1)
aws-sdk-s3 (~> 1.91)
aws-sdk-s3 (~> 1.96)
better_errors (~> 2.9)
binding_of_caller (~> 1.0)
blurhash (~> 0.1)
bootsnap (~> 1.6.0)
brakeman (~> 4.10)
brakeman (~> 5.0)
browser
bullet (~> 6.1)
bundler-audit (~> 0.7)
bundler-audit (~> 0.8)
capistrano (~> 3.16)
capistrano-rails (~> 1.6)
capistrano-rbenv (~> 2.2)
@ -714,32 +692,31 @@ DEPENDENCIES
capybara (~> 3.35)
charlock_holmes (~> 0.7.7)
chewy (~> 5.2)
cld3 (~> 3.4.1)
cld3 (~> 3.4.2)
climate_control (~> 0.2)
color_diff (~> 0.1)
concurrent-ruby
connection_pool
devise (~> 4.7)
devise-two-factor (~> 3.1)
devise (~> 4.8)
devise-two-factor (~> 4.0)
devise_pam_authenticatable2 (~> 9.2)
discard (~> 1.2)
doorkeeper (~> 5.5)
dotenv-rails (~> 2.7)
ed25519 (~> 1.2)
fabrication (~> 2.21)
faker (~> 2.17)
fabrication (~> 2.22)
faker (~> 2.18)
fast_blank (~> 1.0)
fastimage
fog-core (<= 2.1.0)
fog-openstack (~> 0.3)
fuubar (~> 2.5)
hamlit-rails (~> 0.2)
health_check!
hiredis (~> 0.6)
htmlentities (~> 4.3)
http (~> 4.4)
http_accept_language (~> 2.1)
httplog (~> 1.4.3)
httplog (~> 1.5.0)
i18n-tasks (~> 0.9)
idn-ruby
iso-639
@ -756,7 +733,6 @@ DEPENDENCIES
microformats (~> 4.2)
mime-types (~> 3.3.1)
net-ldap (~> 0.17)
nilsimsa!
nokogiri (~> 1.11)
nsa (~> 0.2)
oj (~> 3.11)
@ -766,61 +742,58 @@ DEPENDENCIES
omniauth-saml (~> 1.10)
ox (~> 2.14)
paperclip (~> 6.0)
paperclip-av-transcoder (~> 0.6)
parallel (~> 1.20)
parallel_tests (~> 3.5)
parallel_tests (~> 3.7)
parslet
pg (~> 1.2)
pghero (~> 2.8)
pkg-config (~> 1.4)
pluck_each (~> 0.1.3)
posix-spawn
premailer-rails
private_address_check (~> 0.5)
pry-byebug (~> 3.9)
pry-rails (~> 0.3)
puma (~> 5.2)
puma (~> 5.3)
pundit (~> 2.1)
rack (~> 2.2.3)
rack-attack (~> 6.5)
rack-cors (~> 1.1)
rails (~> 5.2.4.5)
rails (~> 6.1.3)
rails-controller-testing (~> 1.0)
rails-i18n (~> 5.1)
rails-i18n (~> 6.0)
rails-settings-cached (~> 0.6)
rdf-normalize (~> 0.4)
redcarpet (~> 3.5)
redis (~> 4.2)
redis (~> 4.3)
redis-namespace (~> 1.8)
redis-rails (~> 5.0)
rqrcode (~> 1.2)
rspec-rails (~> 4.1)
resolv (~> 0.1.0)
rqrcode (~> 2.0)
rspec-rails (~> 5.0)
rspec-sidekiq (~> 3.1)
rspec_junit_formatter (~> 0.4)
rubocop (~> 1.11)
rubocop-rails (~> 2.9)
rubocop (~> 1.16)
rubocop-rails (~> 2.10)
ruby-progressbar (~> 1.11)
sanitize (~> 5.2)
scenic (~> 1.5)
sidekiq (~> 6.1)
sidekiq (~> 6.2)
sidekiq-bulk (~> 0.2.0)
sidekiq-scheduler (~> 3.0)
sidekiq-scheduler (~> 3.1)
sidekiq-unique-jobs (~> 7.0)
simple-navigation (~> 4.1)
simple-navigation (~> 4.3)
simple_form (~> 5.1)
simplecov (~> 0.21)
sprockets (~> 3.7.2)
sprockets-rails (~> 3.2)
stackprof
stoplight (~> 2.2.1)
streamio-ffmpeg (~> 3.0)
strong_migrations (~> 0.7)
thor (~> 1.1)
tty-prompt (~> 0.23)
twitter-text (~> 3.1.0)
tzinfo-data (~> 1.2021)
webauthn (~> 3.0.0.alpha1)
webmock (~> 3.12)
webpacker (~> 5.2)
webpush
webmock (~> 3.13)
webpacker (~> 5.4)
webpush (~> 0.3)
xorcist (~> 1.1)

@ -4,8 +4,9 @@
| Version | Supported |
| ------- | ------------------ |
| 3.1.x | :white_check_mark: |
| < 3.1 | :x: |
| 3.4.x | :white_check_mark: |
| 3.3.x | :white_check_mark: |
| < 3.3 | :x: |
## Reporting a Vulnerability

2
Vagrantfile vendored

@ -12,7 +12,7 @@ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main'
# Add repo for NodeJS
curl -sL https://deb.nodesource.com/setup_10.x | sudo bash -
curl -sL https://deb.nodesource.com/setup_12.x | sudo bash -
# Add firewall rule to redirect 80 to PORT and save
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]}

@ -78,11 +78,7 @@ class AccountsController < ApplicationController
end
def only_media_scope
Status.where(id: account_media_status_ids)
end
def account_media_status_ids
@account.media_attachments.attached.reorder(nil).select(:status_id).group(:status_id)
Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)
end
def no_replies_scope

@ -20,7 +20,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
def outbox_presenter
if page_requested?
ActivityPub::CollectionPresenter.new(
id: outbox_url(page_params),
id: outbox_url(**page_params),
type: :ordered,
part_of: outbox_url,
prev: prev_page,
@ -29,7 +29,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
)
else
ActivityPub::CollectionPresenter.new(
id: account_outbox_url(@account),
id: outbox_url,
type: :ordered,
size: @account.statuses_count,
first: outbox_url(page: true),
@ -47,11 +47,11 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
end
def next_page
account_outbox_url(@account, page: true, max_id: @statuses.last.id) if @statuses.size == LIMIT
outbox_url(page: true, max_id: @statuses.last.id) if @statuses.size == LIMIT
end
def prev_page
account_outbox_url(@account, page: true, min_id: @statuses.first.id) unless @statuses.empty?
outbox_url(page: true, min_id: @statuses.first.id) unless @statuses.empty?
end
def set_statuses

@ -4,6 +4,7 @@ require 'sidekiq/api'
module Admin
class DashboardController < BaseController
def index
@system_checks = Admin::SystemCheck.perform
@users_count = User.count
@pending_users_count = User.pending.count
@registrations_week = Redis.current.get("activity:accounts:local:#{current_week}") || 0
@ -35,7 +36,6 @@ module Admin
@profile_directory = Setting.profile_directory
@timeline_preview = Setting.timeline_preview
@keybase_integration = Setting.enable_keybase
@spam_check_enabled = Setting.spam_check_enabled
@trends_enabled = Setting.trends
end

@ -22,7 +22,7 @@ module Admin
if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
@domain_block.save
flash.now[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe # rubocop:disable Rails/OutputSafety
@domain_block.errors[:domain].clear
@domain_block.errors.delete(:domain)
render :new
else
if existing_domain_block.present?

@ -0,0 +1,53 @@
# frozen_string_literal: true
module Admin
class FollowRecommendationsController < BaseController
before_action :set_language
def show
authorize :follow_recommendation, :show?
@form = Form::AccountBatch.new
@accounts = filtered_follow_recommendations
end
def update
@form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button))
@form.save
rescue ActionController::ParameterMissing
# Do nothing
ensure
redirect_to admin_follow_recommendations_path(filter_params)
end
private
def set_language
@language = follow_recommendation_filter.language
end
def filtered_follow_recommendations
follow_recommendation_filter.results
end
def follow_recommendation_filter
@follow_recommendation_filter ||= FollowRecommendationFilter.new(filter_params)
end
def form_account_batch_params
params.require(:form_account_batch).permit(:action, account_ids: [])
end
def filter_params
params.slice(*FollowRecommendationFilter::KEYS).permit(*FollowRecommendationFilter::KEYS)
end
def action_from_button
if params[:suppress]
'suppress_follow_recommendation'
elsif params[:unsuppress]
'unsuppress_follow_recommendation'
end
end
end
end

@ -3,7 +3,8 @@
module Admin
class InstancesController < BaseController
before_action :set_instances, only: :index
before_action :set_instance, only: :show
before_action :set_instance, except: :index
before_action :set_exhausted_deliveries_days, only: :show
def index
authorize :instance, :index?
@ -13,14 +14,55 @@ module Admin
authorize :instance, :show?
end
def clear_delivery_errors
authorize :delivery, :clear_delivery_errors?
@instance.delivery_failure_tracker.clear_failures!
redirect_to admin_instance_path(@instance.domain)
end
def restart_delivery
authorize :delivery, :restart_delivery?
last_unavailable_domain = unavailable_domain
if last_unavailable_domain.present?
@instance.delivery_failure_tracker.track_success!
log_action :destroy, last_unavailable_domain
end
redirect_to admin_instance_path(@instance.domain)
end
def stop_delivery
authorize :delivery, :stop_delivery?
UnavailableDomain.create(domain: @instance.domain)
log_action :create, unavailable_domain
redirect_to admin_instance_path(@instance.domain)
end
private
def set_instance
@instance = Instance.find(params[:id])
end
def set_exhausted_deliveries_days
@exhausted_deliveries_days = @instance.delivery_failure_tracker.exhausted_deliveries_days
end
def set_instances
@instances = filtered_instances.page(params[:page])
warning_domains_map = DeliveryFailureTracker.warning_domains_map
@instances.each do |instance|
instance.failure_days = warning_domains_map[instance.domain]
end
end
def unavailable_domain
UnavailableDomain.find_by(domain: @instance.domain)
end
def filtered_instances

@ -14,8 +14,7 @@ module Admin
@statuses = @account.statuses.where(visibility: [:public, :unlisted])
if params[:media]
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).group(:status_id)
@statuses.merge!(Status.where(id: account_media_status_ids))
@statuses.merge!(Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id))
end
@statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)

@ -59,8 +59,8 @@ module Admin
.where(Status.arel_table[:id].gteq(Mastodon::Snowflake.id_at(Time.now.utc.beginning_of_day)))
.joins(:account)
.group('accounts.domain')
.reorder('statuses_count desc')
.pluck('accounts.domain, count(*) AS statuses_count')
.reorder(statuses_count: :desc)
.pluck(Arel.sql('accounts.domain, count(*) AS statuses_count'))
end
def set_counters

@ -35,7 +35,7 @@ class Api::V1::AccountsController < Api::BaseController
follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, with_rate_limit: true)
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify? } }, requested_map: { @account.id => false } }
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(**options)
end
def block
@ -70,7 +70,7 @@ class Api::V1::AccountsController < Api::BaseController
end
def relationships(**options)
AccountRelationshipsPresenter.new([@account.id], current_user.account_id, options)
AccountRelationshipsPresenter.new([@account.id], current_user.account_id, **options)
end
def account_params

@ -3,9 +3,12 @@
class Api::V1::Emails::ConfirmationsController < Api::BaseController
before_action :doorkeeper_authorize!
before_action :require_user_owned_by_application!
before_action :require_user_not_confirmed!
def create
current_user.resend_confirmation_instructions if current_user.unconfirmed_email.present?
current_user.update!(email: params[:email]) if params.key?(:email)
current_user.resend_confirmation_instructions
render_empty
end
@ -14,4 +17,8 @@ class Api::V1::Emails::ConfirmationsController < Api::BaseController
def require_user_owned_by_application!
render json: { error: 'This method is only available to the application the user originally signed-up with' }, status: :forbidden unless current_user && current_user.created_by_application_id == doorkeeper_token.application_id
end
def require_user_not_confirmed!
render json: { error: 'This method is only available while the e-mail is awaiting confirmation' }, status: :forbidden if current_user.confirmed? || current_user.unconfirmed_email.blank?
end
end

@ -29,7 +29,7 @@ class Api::V1::FollowRequestsController < Api::BaseController
end
def relationships(**options)
AccountRelationshipsPresenter.new([params[:id]], current_user.account_id, options)
AccountRelationshipsPresenter.new([params[:id]], current_user.account_id, **options)
end
def load_accounts

@ -3,13 +3,13 @@
class Api::V1::Push::SubscriptionsController < Api::BaseController
before_action -> { doorkeeper_authorize! :push }
before_action :require_user!
before_action :set_web_push_subscription
before_action :check_web_push_subscription, only: [:show, :update]
before_action :set_push_subscription
before_action :check_push_subscription, only: [:show, :update]
def create
@web_subscription&.destroy!
@push_subscription&.destroy!
@web_subscription = ::Web::PushSubscription.create!(
@push_subscription = Web::PushSubscription.create!(
endpoint: subscription_params[:endpoint],
key_p256dh: subscription_params[:keys][:p256dh],
key_auth: subscription_params[:keys][:auth],
@ -18,31 +18,31 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
access_token_id: doorkeeper_token.id
)
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
end
def show
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
end
def update
@web_subscription.update!(data: data_params)
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
@push_subscription.update!(data: data_params)
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
end
def destroy
@web_subscription&.destroy!
@push_subscription&.destroy!
render_empty
end
private
def set_web_push_subscription
@web_subscription = ::Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id)
def set_push_subscription
@push_subscription = Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id)
end
def check_web_push_subscription
not_found if @web_subscription.nil?
def check_push_subscription
not_found if @push_subscription.nil?
end
def subscription_params
@ -52,6 +52,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
def data_params
return {} if params[:data].blank?
params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
params.require(:data).permit(:policy, alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
end
end

@ -5,20 +5,20 @@ class Api::V1::SuggestionsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read }
before_action :require_user!
before_action :set_accounts
def index
render json: @accounts, each_serializer: REST::AccountSerializer
suggestions = suggestions_source.get(current_account, limit: limit_param(DEFAULT_ACCOUNTS_LIMIT))
render json: suggestions.map(&:account), each_serializer: REST::AccountSerializer
end
def destroy
PotentialFriendshipTracker.remove(current_account.id, params[:id])
suggestions_source.remove(current_account, params[:id])
render_empty
end
private
def set_accounts
@accounts = PotentialFriendshipTracker.get(current_account.id, limit: limit_param(DEFAULT_ACCOUNTS_LIMIT))
def suggestions_source
AccountSuggestions::PastInteractionsSource.new
end
end

@ -0,0 +1,19 @@
# frozen_string_literal: true
class Api::V2::SuggestionsController < Api::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :read }
before_action :require_user!
before_action :set_suggestions
def index
render json: @suggestions, each_serializer: REST::SuggestionSerializer
end
private
def set_suggestions
@suggestions = AccountSuggestions.get(current_account, limit_param(DEFAULT_ACCOUNTS_LIMIT))
end
end

@ -2,6 +2,7 @@
class Api::Web::PushSubscriptionsController < Api::Web::BaseController
before_action :require_user!
before_action :set_push_subscription, only: :update
def create
active_session = current_session
@ -15,9 +16,11 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
alerts_enabled = active_session.detection.device.mobile? || active_session.detection.device.tablet?
data = {
policy: 'all',
alerts: {
follow: alerts_enabled,
follow_request: false,
follow_request: alerts_enabled,
favourite: alerts_enabled,
reblog: alerts_enabled,
mention: alerts_enabled,
@ -28,7 +31,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
data.deep_merge!(data_params) if params[:data]
web_subscription = ::Web::PushSubscription.create!(
push_subscription = ::Web::PushSubscription.create!(
endpoint: subscription_params[:endpoint],
key_p256dh: subscription_params[:keys][:p256dh],
key_auth: subscription_params[:keys][:auth],
@ -37,27 +40,27 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
access_token_id: active_session.access_token_id
)
active_session.update!(web_push_subscription: web_subscription)
active_session.update!(web_push_subscription: push_subscription)
render json: web_subscription, serializer: REST::WebPushSubscriptionSerializer
render json: push_subscription, serializer: REST::WebPushSubscriptionSerializer
end
def update
params.require([:id])
web_subscription = ::Web::PushSubscription.find(params[:id])
web_subscription.update!(data: data_params)
render json: web_subscription, serializer: REST::WebPushSubscriptionSerializer
@push_subscription.update!(data: data_params)
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
end
private
def set_push_subscription
@push_subscription = ::Web::PushSubscription.find(params[:id])
end
def subscription_params
@subscription_params ||= params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh])
end
def data_params
@data_params ||= params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
@data_params ||= params.require(:data).permit(:policy, alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
end
end

@ -5,8 +5,6 @@ class ApplicationController < ActionController::Base
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
force_ssl if: :https_enabled?
include Localized
include UserTrackingConcern
include SessionTrackingConcern
@ -21,17 +19,16 @@ class ApplicationController < ActionController::Base
helper_method :use_seamless_external_login?
helper_method :whitelist_mode?
rescue_from ActionController::RoutingError, with: :not_found
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
rescue_from ActionController::UnknownFormat, with: :not_acceptable
rescue_from ActionController::ParameterMissing, with: :bad_request
rescue_from Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActionController::ParameterMissing, Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
rescue_from Mastodon::NotPermittedError, with: :forbidden
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight, with: :service_unavailable
rescue_from ActionController::RoutingError, ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActionController::UnknownFormat, with: :not_acceptable
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight, ActiveRecord::SerializationFailure, with: :service_unavailable
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
before_action :require_functional!, if: :user_signed_in?
@ -43,10 +40,6 @@ class ApplicationController < ActionController::Base
private
def https_enabled?
Rails.env.production? && !request.path.start_with?('/health') && !request.headers["Host"].end_with?(".onion")
end
def authorized_fetch_mode?
ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.whitelist_mode
end

@ -22,7 +22,9 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
end
def require_unconfirmed!
redirect_to edit_user_registration_path if user_signed_in? && current_user.confirmed? && current_user.unconfirmed_email.blank?
if user_signed_in? && current_user.confirmed? && current_user.unconfirmed_email.blank?
redirect_to(current_user.approved? ? root_path : edit_user_registration_path)
end
end
def set_body_classes

@ -31,7 +31,9 @@ module CacheConcern
def cache_collection(raw, klass)
return raw unless klass.respond_to?(:with_includes)
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
return [] if raw.empty?
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys

@ -3,11 +3,16 @@
class CustomCssController < ApplicationController
skip_before_action :store_current_location
skip_before_action :require_functional!
skip_before_action :update_user_sign_in
skip_before_action :set_session_activity
skip_around_action :set_locale
before_action :set_cache_headers
def show
expires_in 3.minutes, public: true
request.session_options[:skip] = true
render plain: Setting.custom_css || '', content_type: 'text/css'
end
end

@ -6,7 +6,6 @@ class DirectoriesController < ApplicationController
before_action :authenticate_user!, if: :whitelist_mode?
before_action :require_enabled!
before_action :set_instance_presenter
before_action :set_tag, only: :show
before_action :set_accounts
before_action :set_pack
@ -16,10 +15,6 @@ class DirectoriesController < ApplicationController
render :index
end
def show
render :index
end
private
def set_pack
@ -30,13 +25,8 @@ class DirectoriesController < ApplicationController
return not_found unless Setting.profile_directory
end
def set_tag
@tag = Tag.discoverable.find_normalized!(params[:id])
end
def set_accounts
@accounts = Account.local.discoverable.by_recent_status.page(params[:page]).per(20).tap do |query|
query.merge!(Account.tagged_with(@tag.id)) if @tag
query.merge!(Account.not_excluded_by_account(current_account)) if current_account
end
end

@ -0,0 +1,7 @@
# frozen_string_literal: true
class HealthController < ActionController::Base
def show
render plain: 'OK'
end
end

@ -45,7 +45,7 @@ class MediaProxyController < ApplicationController
end
def lock_options
{ redis: Redis.current, key: "media_download:#{params[:id]}" }
{ redis: Redis.current, key: "media_download:#{params[:id]}", autorelease: 15.minutes.seconds }
end
def reject_media?

@ -16,7 +16,6 @@ class StatusesController < ApplicationController
before_action :set_referrer_policy_header, only: :show
before_action :set_cache_headers
before_action :set_body_classes
before_action :set_autoplay, only: :embed
skip_around_action :set_locale, if: -> { request.format == :json }
skip_before_action :require_functional!, only: [:show, :embed], unless: :whitelist_mode?
@ -85,8 +84,4 @@ class StatusesController < ApplicationController
def set_referrer_policy_header
response.headers['Referrer-Policy'] = 'origin' unless @status.distributable?
end
def set_autoplay
@autoplay = truthy_param?(:autoplay)
end
end

@ -21,7 +21,7 @@ module Admin::ActionLogsHelper
record.shortcode
when 'Report'
link_to "##{record.id}", admin_report_path(record)
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock'
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain'
link_to record.domain, "https://#{record.domain}"
when 'Status'
link_to record.account.acct, ActivityPub::TagManager.instance.url_for(record)
@ -38,7 +38,7 @@ module Admin::ActionLogsHelper
case type
when 'CustomEmoji'
attributes['shortcode']
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock'
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain'
link_to attributes['domain'], "https://#{attributes['domain']}"
when 'Status'
tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count'))

@ -0,0 +1,18 @@
# frozen_string_literal: true
module EmailHelper
def self.included(base)
base.extend(self)
end
def email_to_canonical_email(str)
username, domain = str.downcase.split('@', 2)
username, = username.gsub('.', '').split('+', 2)
"#{username}@#{domain}"
end
def email_to_canonical_email_hash(str)
Digest::SHA2.new(256).hexdigest(email_to_canonical_email(str))
end
end

@ -67,7 +67,7 @@ module JsonLdHelper
unless id
json = fetch_resource_without_id_validation(uri, on_behalf_of)
return unless json
return if !json.is_a?(Hash) || unsupported_uri_scheme?(json['id'])
uri = json['id']
end

@ -2,6 +2,7 @@
module SettingsHelper
HUMAN_LOCALES = {
af: 'Afrikaans',
ar: 'العربية',
ast: 'Asturianu',
bg: 'Български',
@ -17,6 +18,7 @@ module SettingsHelper
en: 'English',
eo: 'Esperanto',
'es-AR': 'Español (Argentina)',
'es-MX': 'Español (México)',
es: 'Español',
et: 'Eesti',
eu: 'Euskara',
@ -24,6 +26,7 @@ module SettingsHelper
fi: 'Suomi',
fr: 'Français',
ga: 'Gaeilge',
gd: 'Gàidhlig',
gl: 'Galego',
he: 'עברית',
hi: 'हि',
@ -59,6 +62,7 @@ module SettingsHelper
ru: 'Русский',
sa: 'सतम',
sc: 'Sardu',
si: 'සහල',
sk: 'Slovenčina',
sl: 'Slovenščina',
sq: 'Shqip',

@ -130,4 +130,84 @@ module StatusesHelper
def embedded_view?
params[:controller] == EMBEDDED_CONTROLLER && params[:action] == EMBEDDED_ACTION
end
def render_video_component(status, **options)
video = status.media_attachments.first
meta = video.file.meta || {}
component_params = {
sensitive: sensitized?(status, current_account),
src: full_asset_url(video.file.url(:original)),
preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)),
alt: video.description,
blurhash: video.blurhash,
frameRate: meta.dig('original', 'frame_rate'),
inline: true,
media: [
ActiveModelSerializers::SerializableResource.new(video, serializer: REST::MediaAttachmentSerializer),
].as_json,
}.merge(**options)
react_component :video, component_params do
render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
end
end
def render_audio_component(status, **options)
audio = status.media_attachments.first
meta = audio.file.meta || {}
component_params = {
src: full_asset_url(audio.file.url(:original)),
poster: full_asset_url(audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url),
alt: audio.description,
backgroundColor: meta.dig('colors', 'background'),
foregroundColor: meta.dig('colors', 'foreground'),
accentColor: meta.dig('colors', 'accent'),
duration: meta.dig('original', 'duration'),
}.merge(**options)
react_component :audio, component_params do
render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
end
end
def render_media_gallery_component(status, **options)
component_params = {
sensitive: sensitized?(status, current_account),
autoplay: prefers_autoplay?,
media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json },
}.merge(**options)
react_component :media_gallery, component_params do
render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
end
end
def render_card_component(status, **options)
component_params = {
sensitive: sensitized?(status, current_account),
maxDescription: 160,
card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json,
}.merge(**options)
react_component :card, component_params
end
def render_poll_component(status, **options)
component_params = {
disabled: true,
poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json,
}.merge(**options)
react_component :poll, component_params do
render partial: 'statuses/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: prefers_autoplay? }
end
end
def prefers_autoplay?
ActiveModel::Type::Boolean.new.cast(params[:autoplay]) || current_user&.setting_auto_play_gif
end
end

@ -24,6 +24,7 @@ export function normalizeAccount(account) {
account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap);
account.note_emojified = emojify(account.note, emojiMap);
account.note_plain = unescapeHTML(account.note);
if (account.fields) {
account.fields = account.fields.map(pair => ({

@ -32,6 +32,7 @@ export function submitSearch() {
const value = getState().getIn(['search', 'value']);
if (value.length === 0) {
dispatch(fetchSearchSuccess({ accounts: [], statuses: [], hashtags: [] }, ''));
return;
}

@ -1,6 +1,7 @@
import { Iterable, fromJS } from 'immutable';
import { hydrateCompose } from './compose';
import { importFetchedAccounts } from './importer';
import { saveSettings } from './settings';
export const STORE_HYDRATE = 'STORE_HYDRATE';
export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY';
@ -9,9 +10,22 @@ const convertState = rawState =>
fromJS(rawState, (k, v) =>
Iterable.isIndexed(v) ? v.toList() : v.toMap());
const applyMigrations = (state) => {
return state.withMutations(state => {
// Migrate glitch-soc local-only “Show unread marker” setting to Mastodon's setting
if (state.getIn(['local_settings', 'notifications', 'show_unread']) !== undefined) {
// Only change if the Mastodon setting does not deviate from default
if (state.getIn(['settings', 'notifications', 'showUnread']) !== false) {
state.setIn(['settings', 'notifications', 'showUnread'], state.getIn(['local_settings', 'notifications', 'show_unread']));
}
state.removeIn(['local_settings', 'notifications', 'show_unread'])
}
});
};
export function hydrateStore(rawState) {
return dispatch => {
const state = convertState(rawState);
const state = applyMigrations(convertState(rawState));
dispatch({
type: STORE_HYDRATE,
@ -20,5 +34,6 @@ export function hydrateStore(rawState) {
dispatch(hydrateCompose());
dispatch(importFetchedAccounts(Object.values(rawState.accounts)));
dispatch(saveSettings());
};
};

@ -1,5 +1,6 @@
import api from 'flavours/glitch/util/api';
import { importFetchedAccounts } from './importer';
import { fetchRelationships } from './accounts';
export const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST';
export const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS';
@ -7,13 +8,17 @@ export const SUGGESTIONS_FETCH_FAIL = 'SUGGESTIONS_FETCH_FAIL';
export const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS';
export function fetchSuggestions() {
export function fetchSuggestions(withRelationships = false) {
return (dispatch, getState) => {
dispatch(fetchSuggestionsRequest());
api(getState).get('/api/v1/suggestions').then(response => {
dispatch(importFetchedAccounts(response.data));
api(getState).get('/api/v2/suggestions', { params: { limit: 20 } }).then(response => {
dispatch(importFetchedAccounts(response.data.map(x => x.account)));
dispatch(fetchSuggestionsSuccess(response.data));
if (withRelationships) {
dispatch(fetchRelationships(response.data.map(item => item.account.id)));
}
}).catch(error => dispatch(fetchSuggestionsFail(error)));
};
};
@ -25,10 +30,10 @@ export function fetchSuggestionsRequest() {
};
};
export function fetchSuggestionsSuccess(accounts) {
export function fetchSuggestionsSuccess(suggestions) {
return {
type: SUGGESTIONS_FETCH_SUCCESS,
accounts,
suggestions,
skipLoading: true,
};
};
@ -48,5 +53,12 @@ export const dismissSuggestion = accountId => (dispatch, getState) => {
id: accountId,
});
api(getState).delete(`/api/v1/suggestions/${accountId}`);
api(getState).delete(`/api/v1/suggestions/${accountId}`).then(() => {
dispatch(fetchSuggestionsRequest());
api(getState).get('/api/v2/suggestions').then(response => {
dispatch(importFetchedAccounts(response.data.map(x => x.account)));
dispatch(fetchSuggestionsSuccess(response.data));
}).catch(error => dispatch(fetchSuggestionsFail(error)));
}).catch(() => {});
};

@ -20,6 +20,8 @@ export const TIMELINE_LOAD_PENDING = 'TIMELINE_LOAD_PENDING';
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
export const TIMELINE_CONNECT = 'TIMELINE_CONNECT';
export const TIMELINE_MARK_AS_PARTIAL = 'TIMELINE_MARK_AS_PARTIAL';
export const loadPending = timeline => ({
type: TIMELINE_LOAD_PENDING,
timeline,
@ -31,6 +33,13 @@ export function updateTimeline(timeline, status, accept) {
return;
}
if (getState().getIn(['timelines', timeline, 'isPartial'])) {
// Prevent new items from being added to a partial timeline,
// since it will be reloaded anyway
return;
}
const filters = getFiltersRegex(getState(), { contextType: timeline });
const dropRegex = filters[0];
const regex = filters[1];
@ -198,3 +207,8 @@ export const disconnectTimeline = timeline => ({
timeline,
usePendingItems: preferPendingItems,
});
export const markAsPartial = timeline => ({
type: TIMELINE_MARK_AS_PARTIAL,
timeline,
});

@ -0,0 +1,112 @@
const DIGIT_CHARACTERS = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'#',
'$',
'%',
'*',
'+',
',',
'-',
'.',
':',
';',
'=',
'?',
'@',
'[',
']',
'^',
'_',
'{',
'|',
'}',
'~',
];
export const decode83 = (str) => {
let value = 0;
let c, digit;
for (let i = 0; i < str.length; i++) {
c = str[i];
digit = DIGIT_CHARACTERS.indexOf(c);
value = value * 83 + digit;
}
return value;
};
export const intToRGB = int => ({
r: Math.max(0, (int >> 16)),
g: Math.max(0, (int >> 8) & 255),
b: Math.max(0, (int & 255)),
});
export const getAverageFromBlurhash = blurhash => {
if (!blurhash) {
return null;
}
return intToRGB(decode83(blurhash.slice(2, 6)));
};

@ -87,8 +87,10 @@ class Account extends ImmutablePureComponent {
let buttons;
if (onActionClick && actionIcon) {
buttons = <IconButton icon={actionIcon} title={actionTitle} onClick={this.handleAction} />;
if (onActionClick) {
if (actionIcon) {
buttons = <IconButton icon={actionIcon} title={actionTitle} onClick={this.handleAction} />;
}
} else if (account.get('id') !== me && !small && account.get('relationship', null) !== null) {
const following = account.getIn(['relationship', 'following']);
const requested = account.getIn(['relationship', 'requested']);

@ -0,0 +1,9 @@
import React from 'react';
const Logo = () => (
<svg viewBox='0 0 216.4144 232.00976' className='logo'>
<use xlinkHref='#mastodon-svg-logo' />
</svg>
);
export default Logo;

@ -24,7 +24,7 @@ const messages = defineMessages({
id: 'status.sensitive_toggle',
},
toggle_visible: {
defaultMessage: 'Hide {number, plural, one {image} other {images}}',
defaultMessage: '{number, plural, one {Hide image} other {Hide images}}',
id: 'media_gallery.toggle_visible',
},
warning: {

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import 'wicg-inert';
import { createBrowserHistory } from 'history';
import { multiply } from 'color-blend';
export default class ModalRoot extends React.PureComponent {
static contextTypes = {
@ -11,6 +12,11 @@ export default class ModalRoot extends React.PureComponent {
static propTypes = {
children: PropTypes.node,
onClose: PropTypes.func.isRequired,
backgroundColor: PropTypes.shape({
r: PropTypes.number,
g: PropTypes.number,
b: PropTypes.number,
}),
noEsc: PropTypes.bool,
};
@ -68,9 +74,7 @@ export default class ModalRoot extends React.PureComponent {
Promise.resolve().then(() => {
this.activeElement.focus({ preventScroll: true });
this.activeElement = null;
}).catch((error) => {
console.error(error);
});
}).catch(console.error);
this.handleModalClose();
}
@ -120,10 +124,16 @@ export default class ModalRoot extends React.PureComponent {
);
}
let backgroundColor = null;
if (this.props.backgroundColor) {
backgroundColor = multiply({ ...this.props.backgroundColor, a: 1 }, { r: 0, g: 0, b: 0, a: 0.7 });
}
return (
<div className='modal-root' ref={this.setRef}>
<div style={{ pointerEvents: visible ? 'auto' : 'none' }}>
<div role='presentation' className='modal-root__overlay' onClick={onClose} />
<div role='presentation' className='modal-root__overlay' onClick={onClose} style={{ backgroundColor: backgroundColor ? `rgba(${backgroundColor.r}, ${backgroundColor.g}, ${backgroundColor.b}, 0.7)` : null }} />
<div role='dialog' className='modal-root__container'>{children}</div>
</div>
</div>

@ -2,7 +2,7 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
import illustration from 'flavours/glitch/images/elephant_ui_working.svg';
const MissingIndicator = () => (
const RegenerationIndicator = () => (
<div className='regeneration-indicator'>
<div className='regeneration-indicator__figure'>
<img src={illustration} alt='' />
@ -15,4 +15,4 @@ const MissingIndicator = () => (
</div>
);
export default MissingIndicator;
export default RegenerationIndicator;

@ -378,22 +378,26 @@ class Status extends ImmutablePureComponent {
}
};
handleOpenVideo = (media, options) => {
this.props.onOpenVideo(media, options);
handleOpenVideo = (options) => {
const { status } = this.props;
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options);
}
handleOpenMedia = (media, index) => {
this.props.onOpenMedia(this.props.status.get('id'), media, index);
}
handleHotkeyOpenMedia = e => {
const { status, onOpenMedia, onOpenVideo } = this.props;
const statusId = status.get('id');
e.preventDefault();
if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
// TODO: toggle play/paused?
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
onOpenVideo(status.getIn(['media_attachments', 0]), { startTime: 0 });
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
onOpenVideo(statusId, status.getIn(['media_attachments', 0]), { startTime: 0 });
} else {
onOpenMedia(status.get('media_attachments'), 0);
onOpenMedia(statusId, status.get('media_attachments'), 0);
}
}
}
@ -657,7 +661,7 @@ class Status extends ImmutablePureComponent {
letterbox={settings.getIn(['media', 'letterbox'])}
fullwidth={settings.getIn(['media', 'fullwidth'])}
hidden={isCollapsed || !isExpanded}
onOpenMedia={this.props.onOpenMedia}
onOpenMedia={this.handleOpenMedia}
cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth}
visible={this.state.showMedia}
@ -675,7 +679,7 @@ class Status extends ImmutablePureComponent {
} else if (status.get('card') && settings.get('inline_preview_cards')) {
media = (
<Card
onOpenMedia={this.props.onOpenMedia}
onOpenMedia={this.handleOpenMedia}
card={status.get('card')}
compact
cacheWidth={this.props.cacheMediaWidth}

@ -2,7 +2,6 @@ import React from 'react';
import { Provider } from 'react-redux';
import PropTypes from 'prop-types';
import configureStore from 'flavours/glitch/store/configureStore';
import { showOnboardingOnce } from 'flavours/glitch/actions/onboarding';
import { BrowserRouter, Route } from 'react-router-dom';
import { ScrollContext } from 'react-router-scroll-4';
import UI from 'flavours/glitch/features/ui';
@ -32,7 +31,6 @@ export default class Mastodon extends React.PureComponent {
componentDidMount() {
this.disconnect = store.dispatch(connectUserStream());
store.dispatch(showOnboardingOnce());
}
componentWillUnmount () {

@ -2,7 +2,7 @@ import React, { PureComponent, Fragment } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { IntlProvider, addLocaleData } from 'react-intl';
import { List as ImmutableList, fromJS } from 'immutable';
import { fromJS } from 'immutable';
import { getLocale } from 'mastodon/locales';
import { getScrollbarWidth } from 'flavours/glitch/util/scrollbar';
import MediaGallery from 'flavours/glitch/components/media_gallery';
@ -30,6 +30,8 @@ export default class MediaContainer extends PureComponent {
media: null,
index: null,
time: null,
backgroundColor: null,
options: null,
};
handleOpenMedia = (media, index) => {
@ -39,20 +41,32 @@ export default class MediaContainer extends PureComponent {
this.setState({ media, index });
}
handleOpenVideo = (video, time) => {
const media = ImmutableList([video]);
handleOpenVideo = (options) => {
const { components } = this.props;
const { media } = JSON.parse(components[options.componetIndex].getAttribute('data-props'));
const mediaList = fromJS(media);
document.body.classList.add('with-modals--active');
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
this.setState({ media, time });
this.setState({ media: mediaList, options });
}
handleCloseMedia = () => {
document.body.classList.remove('with-modals--active');
document.documentElement.style.marginRight = 0;
this.setState({ media: null, index: null, time: null });
this.setState({
media: null,
index: null,
time: null,
backgroundColor: null,
options: null,
});
}
setBackgroundColor = color => {
this.setState({ backgroundColor: color });
}
render () {
@ -73,6 +87,7 @@ export default class MediaContainer extends PureComponent {
...(hashtag ? { hashtag: fromJS(hashtag) } : {}),
...(componentName === 'Video' ? {
componetIndex: i,
onOpenVideo: this.handleOpenVideo,
} : {
onOpenMedia: this.handleOpenMedia,
@ -85,13 +100,16 @@ export default class MediaContainer extends PureComponent {
);
})}
<ModalRoot onClose={this.handleCloseMedia}>
<ModalRoot backgroundColor={this.state.backgroundColor} onClose={this.handleCloseMedia}>
{this.state.media && (
<MediaModal
media={this.state.media}
index={this.state.index || 0}
time={this.state.time}
currentTime={this.state.options?.startTime}
autoPlay={this.state.options?.autoPlay}
volume={this.state.options?.defaultVolume}
onClose={this.handleCloseMedia}
onChangeBackgroundColor={this.setBackgroundColor}
/>
)}
</ModalRoot>

@ -177,12 +177,12 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
dispatch(mentionCompose(account, router));
},
onOpenMedia (media, index) {
dispatch(openModal('MEDIA', { media, index }));
onOpenMedia (statusId, media, index) {
dispatch(openModal('MEDIA', { statusId, media, index }));
},
onOpenVideo (media, options) {
dispatch(openModal('VIDEO', { media, options }));
onOpenVideo (statusId, media, options) {
dispatch(openModal('VIDEO', { statusId, media, options }));
},
onBlock (status) {

@ -328,6 +328,8 @@ class Header extends ImmutablePureComponent {
)}
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
<div className='account__header__joined'><FormattedMessage id='account.joined' defaultMessage='Joined {date}' values={{ date: intl.formatDate(account.get('created_at'), { year: 'numeric', month: 'short', day: '2-digit' }) }} /></div>
</div>
</div>
)}

@ -114,15 +114,18 @@ class AccountGallery extends ImmutablePureComponent {
}
handleOpenMedia = attachment => {
const { dispatch } = this.props;
const statusId = attachment.getIn(['status', 'id']);
if (attachment.get('type') === 'video') {
this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status'), options: { autoPlay: true } }));
dispatch(openModal('VIDEO', { media: attachment, statusId, options: { autoPlay: true } }));
} else if (attachment.get('type') === 'audio') {
this.props.dispatch(openModal('AUDIO', { media: attachment, status: attachment.get('status'), options: { autoPlay: true } }));
dispatch(openModal('AUDIO', { media: attachment, statusId, options: { autoPlay: true } }));
} else {
const media = attachment.getIn(['status', 'media_attachments']);
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
this.props.dispatch(openModal('MEDIA', { media, index, status: attachment.get('status') }));
dispatch(openModal('MEDIA', { media, index, statusId }));
}
}

@ -199,6 +199,14 @@ class ComposeForm extends ImmutablePureComponent {
}
}
componentDidMount () {
this._updateFocusAndSelection({ });
}
componentDidUpdate (prevProps) {
this._updateFocusAndSelection(prevProps);
}
// This statement does several things:
// - If we're beginning a reply, and,
// - Replying to zero or one users, places the cursor at the end
@ -206,7 +214,7 @@ class ComposeForm extends ImmutablePureComponent {
// - Replying to more than one user, selects any usernames past
// the first; this provides a convenient shortcut to drop
// everyone else from the conversation.
componentDidUpdate (prevProps) {
_updateFocusAndSelection = (prevProps) => {
const {
textarea,
spoilerText,

@ -47,7 +47,7 @@ class Publisher extends ImmutablePureComponent {
const diff = maxChars - length(countText || '');
const computedClass = classNames('composer--publisher', {
disabled: disabled || diff < 0,
disabled: disabled,
over: diff < 0,
});
@ -56,7 +56,7 @@ class Publisher extends ImmutablePureComponent {
{sideArm && sideArm !== 'none' ? (
<Button
className='side_arm'
disabled={disabled || diff < 0}
disabled={disabled}
onClick={onSecondarySubmit}
style={{ padding: null }}
text={
@ -110,7 +110,7 @@ class Publisher extends ImmutablePureComponent {
}()}
title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${privacy}.short` })}`}
onClick={this.handleSubmit}
disabled={disabled || diff < 0}
disabled={disabled}
/>
</div>
);

@ -33,6 +33,12 @@ class SearchResults extends ImmutablePureComponent {
}
}
componentDidUpdate () {
if (this.props.searchTerm === '') {
this.props.fetchSuggestions();
}
}
handleLoadMoreAccounts = () => this.props.expandSearch('accounts');
handleLoadMoreStatuses = () => this.props.expandSearch('statuses');
@ -42,7 +48,7 @@ class SearchResults extends ImmutablePureComponent {
render () {
const { intl, results, suggestions, dismissSuggestion, searchTerm } = this.props;
if (results.isEmpty() && !suggestions.isEmpty()) {
if (searchTerm === '' && !suggestions.isEmpty()) {
return (
<div className='drawer--results'>
<div className='trends'>
@ -51,12 +57,12 @@ class SearchResults extends ImmutablePureComponent {
<FormattedMessage id='suggestions.header' defaultMessage='You might be interested in…' />
</div>
{suggestions && suggestions.map(accountId => (
{suggestions && suggestions.map(suggestion => (
<AccountContainer
key={accountId}
id={accountId}
actionIcon='times'
actionTitle={intl.formatMessage(messages.dismissSuggestion)}
key={suggestion.get('account')}
id={suggestion.get('account')}
actionIcon={suggestion.get('source') === 'past_interaction' ? 'times' : null}
actionTitle={suggestion.get('source') === 'past_interaction' ? intl.formatMessage(messages.dismissSuggestion) : null}
onActionClick={dismissSuggestion}
/>
))}

@ -5,7 +5,7 @@ import { Map as ImmutableMap } from 'immutable';
import { useEmoji } from 'flavours/glitch/actions/emojis';
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { EmojiPicker as EmojiPickerAsync } from 'flavours/glitch/util/async-components';
import Overlay from 'react-overlays/lib/Overlay';
import classNames from 'classnames';
@ -18,7 +18,6 @@ import { assetHost } from 'flavours/glitch/util/config';
const messages = defineMessages({
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emojos!! (╯°□°)╯︵ ┻━┻' },
custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' },
recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' },
search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' },
@ -108,9 +107,26 @@ const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
let EmojiPicker, Emoji; // load asynchronously
const backgroundImageFn = () => `${assetHost}/emoji/sheet_10.png`;
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
const backgroundImageFn = () => `${assetHost}/emoji/sheet_13.png`;
const notFoundFn = () => (
<div className='emoji-mart-no-results'>
<Emoji
emoji='sleuth_or_spy'
set='twitter'
size={32}
sheetSize={32}
backgroundImageFn={backgroundImageFn}
/>
<div className='emoji-mart-no-results-label'>
<FormattedMessage id='emoji_button.not_found' defaultMessage='No matching emojis found' />
</div>
</div>
);
class ModifierPickerMenu extends React.PureComponent {
static propTypes = {
@ -262,7 +278,6 @@ class EmojiPickerMenu extends React.PureComponent {
return {
search: intl.formatMessage(messages.emoji_search),
notfound: intl.formatMessage(messages.emoji_not_found),
categories: {
search: intl.formatMessage(messages.search_results),
recent: intl.formatMessage(messages.recent),
@ -343,7 +358,9 @@ class EmojiPickerMenu extends React.PureComponent {
recent={frequentlyUsedEmojis}
skin={skinTone}
showPreview={false}
showSkinTones={false}
backgroundImageFn={backgroundImageFn}
notFound={notFoundFn}
autoFocus
emojiTooltip
native={useSystemEmojiFont}

@ -0,0 +1,85 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import { makeGetAccount } from 'flavours/glitch/selectors';
import Avatar from 'flavours/glitch/components/avatar';
import DisplayName from 'flavours/glitch/components/display_name';
import Permalink from 'flavours/glitch/components/permalink';
import IconButton from 'flavours/glitch/components/icon_button';
import { injectIntl, defineMessages } from 'react-intl';
import { followAccount, unfollowAccount } from 'flavours/glitch/actions/accounts';
const messages = defineMessages({
follow: { id: 'account.follow', defaultMessage: 'Follow' },
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
});
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
const mapStateToProps = (state, props) => ({
account: getAccount(state, props.id),
});
return mapStateToProps;
};
const getFirstSentence = str => {
const arr = str.split(/(([\.\?!]+\s)|[.。?!\n•])/);
return arr[0];
};
export default @connect(makeMapStateToProps)
@injectIntl
class Account extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
};
handleFollow = () => {
const { account, dispatch } = this.props;
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
dispatch(unfollowAccount(account.get('id')));
} else {
dispatch(followAccount(account.get('id')));
}
}
render () {
const { account, intl } = this.props;
let button;
if (account.getIn(['relationship', 'following'])) {
button = <IconButton icon='check' title={intl.formatMessage(messages.unfollow)} active onClick={this.handleFollow} />;
} else {
button = <IconButton icon='plus' title={intl.formatMessage(messages.follow)} onClick={this.handleFollow} />;
}
return (
<div className='account follow-recommendations-account'>
<div className='account__wrapper'>
<Permalink className='account__display-name account__display-name--with-note' title={account.get('acct')} href={account.get('url')} to={`/accounts/${account.get('id')}`}>
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
<DisplayName account={account} />
<div className='account__note'>{getFirstSentence(account.get('note_plain'))}</div>
</Permalink>
<div className='account__relationship'>
{button}
</div>
</div>
</div>
);
}
}

@ -0,0 +1,109 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { fetchSuggestions } from 'flavours/glitch/actions/suggestions';
import { changeSetting, saveSettings } from 'flavours/glitch/actions/settings';
import { requestBrowserPermission } from 'flavours/glitch/actions/notifications';
import { markAsPartial } from 'flavours/glitch/actions/timelines';
import Column from 'flavours/glitch/features/ui/components/column';
import Account from './components/account';
import Logo from 'flavours/glitch/components/logo';
import imageGreeting from 'mastodon/../images/elephant_ui_greeting.svg';
import Button from 'flavours/glitch/components/button';
const mapStateToProps = state => ({
suggestions: state.getIn(['suggestions', 'items']),
isLoading: state.getIn(['suggestions', 'isLoading']),
});
export default @connect(mapStateToProps)
class FollowRecommendations extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
};
static propTypes = {
dispatch: PropTypes.func.isRequired,
suggestions: ImmutablePropTypes.list,
isLoading: PropTypes.bool,
};
componentDidMount () {
const { dispatch, suggestions } = this.props;
// Don't re-fetch if we're e.g. navigating backwards to this page,
// since we don't want followed accounts to disappear from the list
if (suggestions.size === 0) {
dispatch(fetchSuggestions(true));
}
}
componentWillUnmount () {
const { dispatch } = this.props;
// Force the home timeline to be reloaded when the user navigates
// to it; if the user is new, it would've been empty before
dispatch(markAsPartial('home'));
}
handleDone = () => {
const { dispatch } = this.props;
const { router } = this.context;
dispatch(requestBrowserPermission((permission) => {
if (permission === 'granted') {
dispatch(changeSetting(['notifications', 'alerts', 'follow'], true));
dispatch(changeSetting(['notifications', 'alerts', 'favourite'], true));
dispatch(changeSetting(['notifications', 'alerts', 'reblog'], true));
dispatch(changeSetting(['notifications', 'alerts', 'mention'], true));
dispatch(changeSetting(['notifications', 'alerts', 'poll'], true));
dispatch(changeSetting(['notifications', 'alerts', 'status'], true));
dispatch(saveSettings());
}
}));
router.history.push('/timelines/home');
}
render () {
const { suggestions, isLoading } = this.props;
return (
<Column>
<div className='scrollable follow-recommendations-container'>
<div className='column-title'>
<Logo />
<h3><FormattedMessage id='follow_recommendations.heading' defaultMessage="Follow people you'd like to see posts from! Here are some suggestions." /></h3>
<p><FormattedMessage id='follow_recommendations.lead' defaultMessage="Posts from people you follow will show up in chronological order on your home feed. Don't be afraid to make mistakes, you can unfollow people just as easily any time!" /></p>
</div>
{!isLoading && (
<React.Fragment>
<div className='column-list'>
{suggestions.size > 0 ? suggestions.map(suggestion => (
<Account key={suggestion.get('account')} id={suggestion.get('account')} />
)) : (
<div className='column-list__empty-message'>
<FormattedMessage id='empty_column.follow_recommendations' defaultMessage='Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.' />
</div>
)}
</div>
<div className='column-actions'>
<img src={imageGreeting} alt='' className='column-actions__background' />
<Button onClick={this.handleDone}><FormattedMessage id='follow_recommendations.done' defaultMessage='Done' /></Button>
</div>
</React.Fragment>
)}
</div>
</Column>
);
}
}

@ -59,7 +59,7 @@ class ColumnSettings extends React.PureComponent {
{this.modeLabel(mode)}
</span>
<NonceProvider nonce={document.querySelector('meta[name=style-nonce]').content}>
<NonceProvider nonce={document.querySelector('meta[name=style-nonce]').content} cacheKey='tags'>
<AsyncSelect
isMulti
autoFocus

@ -72,7 +72,7 @@ class HomeTimeline extends React.PureComponent {
}
componentDidMount () {
this.props.dispatch(fetchAnnouncements());
setTimeout(() => this.props.dispatch(fetchAnnouncements()), 700);
this._checkIfReloadNeeded(false, this.props.isPartial);
}
@ -152,7 +152,7 @@ class HomeTimeline extends React.PureComponent {
scrollKey={`home_timeline-${columnId}`}
onLoadMore={this.handleLoadMore}
timelineId='home'
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Visit {public} or use search to get started and meet other users.' values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />}
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Follow more people to fill it up. {suggestions}' values={{ suggestions: <Link to='/start'><FormattedMessage id='empty_column.home.suggestions' defaultMessage='See some suggestions' /></Link> }} />}
bindToDocument={!multiColumn}
/>
</Column>

@ -113,14 +113,6 @@ class LocalSettingsPage extends React.PureComponent {
<FormattedMessage id='settings.notifications.favicon_badge' defaultMessage='Unread notifications favicon badge' />
<span className='hint'><FormattedMessage id='settings.notifications.favicon_badge.hint' defaultMessage="Add a badge for unread notifications to the favicon" /></span>
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['notifications', 'show_unread']}
id='mastodon-settings--notifications-show_unread'
onChange={onChange}
>
<FormattedMessage id='settings.notifications.show_unread' defaultMessage='Show unread marker' />
</LocalSettingsPageItem>
</section>
<section>
<h2><FormattedMessage id='settings.layout_opts' defaultMessage='Layout options' /></h2>

@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
import ClearColumnButton from './clear_column_button';
import GrantPermissionButton from './grant_permission_button';
import SettingToggle from './setting_toggle';
import PillBarButton from './pill_bar_button';
export default class ColumnSettings extends React.PureComponent {
@ -34,7 +35,6 @@ export default class ColumnSettings extends React.PureComponent {
const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
const pushMeta = showPushSettings && <FormattedMessage id='notifications.column_settings.push_meta' defaultMessage='This device' />;
return (
<div>
@ -56,6 +56,16 @@ export default class ColumnSettings extends React.PureComponent {
<ClearColumnButton onClick={onClear} />
</div>
<div role='group' aria-labelledby='notifications-unread-markers'>
<span id='notifications-unread-markers' className='column-settings__section'>
<FormattedMessage id='notifications.column_settings.unread_markers.category' defaultMessage='Unread notification markers' />
</span>
<div className='column-settings__row'>
<SettingToggle id='unread-notification-markers' prefix='notifications' settings={settings} settingPath={['showUnread']} onChange={onChange} label={filterShowStr} />
</div>
</div>
<div role='group' aria-labelledby='notifications-filter-bar'>
<span id='notifications-filter-bar' className='column-settings__section'>
<FormattedMessage id='notifications.column_settings.filter_bar.category' defaultMessage='Quick filter bar' />
@ -70,77 +80,77 @@ export default class ColumnSettings extends React.PureComponent {
<div role='group' aria-labelledby='notifications-follow'>
<span id='notifications-follow' className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
<div className='column-settings__row'>
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
<div className='column-settings__pillbar'>
<PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow']} onChange={onChange} label={alertStr} />
{showPushSettings && <PillBarButton prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} onChange={this.onPushChange} label={pushStr} />}
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
</div>
</div>
<div role='group' aria-labelledby='notifications-follow-request'>
<span id='notifications-follow-request' className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow_request' defaultMessage='New follow requests:' /></span>
<div className='column-settings__row'>
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow_request']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow_request']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow_request']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow_request']} onChange={onChange} label={soundStr} />
<div className='column-settings__pillbar'>
<PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow_request']} onChange={onChange} label={alertStr} />
{showPushSettings && <PillBarButton prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow_request']} onChange={this.onPushChange} label={pushStr} />}
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'follow_request']} onChange={onChange} label={showStr} />
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'follow_request']} onChange={onChange} label={soundStr} />
</div>
</div>
<div role='group' aria-labelledby='notifications-favourite'>
<span id='notifications-favourite' className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
<div className='column-settings__row'>
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favourite']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'favourite']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
<div className='column-settings__pillbar'>
<PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
{showPushSettings && <PillBarButton prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favourite']} onChange={this.onPushChange} label={pushStr} />}
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'favourite']} onChange={onChange} label={showStr} />
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
</div>
</div>
<div role='group' aria-labelledby='notifications-mention'>
<span id='notifications-mention' className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
<div className='column-settings__row'>
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'mention']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'mention']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'mention']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'mention']} onChange={onChange} label={soundStr} />
<div className='column-settings__pillbar'>
<PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'mention']} onChange={onChange} label={alertStr} />
{showPushSettings && <PillBarButton prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'mention']} onChange={this.onPushChange} label={pushStr} />}
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'mention']} onChange={onChange} label={showStr} />
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'mention']} onChange={onChange} label={soundStr} />
</div>
</div>
<div role='group' aria-labelledby='notifications-reblog'>
<span id='notifications-reblog' className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
<div className='column-settings__row'>
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'reblog']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'reblog']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
<div className='column-settings__pillbar'>
<PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
{showPushSettings && <PillBarButton prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'reblog']} onChange={this.onPushChange} label={pushStr} />}
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'reblog']} onChange={onChange} label={showStr} />
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
</div>
</div>
<div role='group' aria-labelledby='notifications-poll'>
<span id='notifications-poll' className='column-settings__section'><FormattedMessage id='notifications.column_settings.poll' defaultMessage='Poll results:' /></span>
<div className='column-settings__row'>
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'poll']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'poll']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'poll']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'poll']} onChange={onChange} label={soundStr} />
<div className='column-settings__pillbar'>
<PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'poll']} onChange={onChange} label={alertStr} />
{showPushSettings && <PillBarButton prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'poll']} onChange={this.onPushChange} label={pushStr} />}
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'poll']} onChange={onChange} label={showStr} />
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'poll']} onChange={onChange} label={soundStr} />
</div>
</div>
<div role='group' aria-labelledby='notifications-status'>
<span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.status' defaultMessage='New toots:' /></span>
<div className='column-settings__row'>
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'status']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'status']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'status']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'status']} onChange={onChange} label={soundStr} />
<div className='column-settings__pillbar'>
<PillBarButton disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'status']} onChange={onChange} label={alertStr} />
{showPushSettings && <PillBarButton prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'status']} onChange={this.onPushChange} label={pushStr} />}
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'status']} onChange={onChange} label={showStr} />
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'status']} onChange={onChange} label={soundStr} />
</div>
</div>
</div>

@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import classNames from 'classnames'
export default class PillBarButton extends React.PureComponent {
static propTypes = {
prefix: PropTypes.string,
settings: ImmutablePropTypes.map.isRequired,
settingPath: PropTypes.array.isRequired,
label: PropTypes.node.isRequired,
onChange: PropTypes.func.isRequired,
disabled: PropTypes.bool,
}
onChange = () => {
const { settings, settingPath } = this.props;
this.props.onChange(settingPath, !settings.getIn(settingPath));
}
render () {
const { prefix, settings, settingPath, label, disabled } = this.props;
const id = ['setting-pillbar-button', prefix, ...settingPath].filter(Boolean).join('-');
const active = settings.getIn(settingPath);
return (
<button
key={id}
id={id}
className={classNames('pillbar-button', { active })}
disabled={disabled}
onClick={this.onChange}
aria-pressed={active}
>
{label}
</button>
);
}
}

@ -67,8 +67,8 @@ const mapStateToProps = state => ({
hasMore: state.getIn(['notifications', 'hasMore']),
numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
notifCleaningActive: state.getIn(['notifications', 'cleaningMode']),
lastReadId: state.getIn(['local_settings', 'notifications', 'show_unread']) ? state.getIn(['notifications', 'readMarkerId']) : '0',
canMarkAsRead: state.getIn(['local_settings', 'notifications', 'show_unread']) && state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
lastReadId: state.getIn(['settings', 'notifications', 'showUnread']) ? state.getIn(['notifications', 'readMarkerId']) : '0',
canMarkAsRead: state.getIn(['settings', 'notifications', 'showUnread']) && state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default' && !state.getIn(['settings', 'notifications', 'dismissPermissionBanner']),
});
@ -224,7 +224,7 @@ class Notifications extends React.PureComponent {
const { notifCleaning, notifCleaningActive } = this.props;
const { animatingNCD } = this.state;
const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
let scrollableContent = null;

@ -23,6 +23,7 @@ const messages = defineMessages({
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
open: { id: 'status.open', defaultMessage: 'Expand this status' },
});
const makeMapStateToProps = () => {
@ -52,11 +53,19 @@ class Footer extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired,
askReplyConfirmation: PropTypes.bool,
showReplyCount: PropTypes.bool,
withOpenButton: PropTypes.bool,
onClose: PropTypes.func,
};
_performReply = () => {
const { dispatch, status } = this.props;
dispatch(replyCompose(status, this.context.router.history));
const { dispatch, status, onClose } = this.props;
const { router } = this.context;
if (onClose) {
onClose();
}
dispatch(replyCompose(status, router.history));
};
handleReplyClick = () => {
@ -100,8 +109,20 @@ class Footer extends ImmutablePureComponent {
}
};
handleOpenClick = e => {
const { router } = this.context;
if (e.button !== 0 || !router) {
return;
}
const { status } = this.props;
router.history.push(`/statuses/${status.get('id')}`);
}
render () {
const { status, intl, showReplyCount } = this.props;
const { status, intl, showReplyCount, withOpenButton } = this.props;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
@ -156,6 +177,7 @@ class Footer extends ImmutablePureComponent {
{replyButton}
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
{withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' onClick={this.handleOpenClick} />}
</div>
);
}

@ -68,8 +68,8 @@ export default class DetailedStatus extends ImmutablePureComponent {
e.stopPropagation();
}
handleOpenVideo = (media, options) => {
this.props.onOpenVideo(media, options);
handleOpenVideo = (options) => {
this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), options);
}
_measureHeight (heightJustChanged) {

@ -316,11 +316,11 @@ class Status extends ImmutablePureComponent {
}
handleOpenMedia = (media, index) => {
this.props.dispatch(openModal('MEDIA', { media, index }));
this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index }));
}
handleOpenVideo = (media, options) => {
this.props.dispatch(openModal('VIDEO', { media, options }));
this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, options }));
}
handleHotkeyOpenMedia = e => {
@ -329,9 +329,7 @@ class Status extends ImmutablePureComponent {
e.preventDefault();
if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
// TODO: toggle play/paused?
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
this.handleOpenVideo(status.getIn(['media_attachments', 0]), { startTime: 0 });
} else {
this.handleOpenMedia(status.get('media_attachments'), 0);

@ -4,12 +4,10 @@ import PropTypes from 'prop-types';
import Audio from 'flavours/glitch/features/audio';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import Icon from 'flavours/glitch/components/icon';
import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
const mapStateToProps = (state, { status }) => ({
account: state.getIn(['accounts', status.get('account')]),
const mapStateToProps = (state, { statusId }) => ({
accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']),
});
export default @connect(mapStateToProps)
@ -17,27 +15,21 @@ class AudioModal extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
status: ImmutablePropTypes.map,
statusId: PropTypes.string.isRequired,
accountStaticAvatar: PropTypes.string.isRequired,
options: PropTypes.shape({
autoPlay: PropTypes.bool,
}),
account: ImmutablePropTypes.map,
onClose: PropTypes.func.isRequired,
onChangeBackgroundColor: PropTypes.func.isRequired,
};
static contextTypes = {
router: PropTypes.object,
};
handleStatusClick = e => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
}
}
render () {
const { media, status, account } = this.props;
const { media, accountStaticAvatar, statusId, onClose } = this.props;
const options = this.props.options || {};
return (
@ -48,7 +40,7 @@ class AudioModal extends ImmutablePureComponent {
alt={media.get('description')}
duration={media.getIn(['meta', 'original', 'duration'], 0)}
height={150}
poster={media.get('preview_url') || account.get('avatar_static')}
poster={media.get('preview_url') || accountStaticAvatar}
backgroundColor={media.getIn(['meta', 'colors', 'background'])}
foregroundColor={media.getIn(['meta', 'colors', 'foreground'])}
accentColor={media.getIn(['meta', 'colors', 'accent'])}
@ -56,11 +48,9 @@ class AudioModal extends ImmutablePureComponent {
/>
</div>
{status && (
<div className={classNames('media-modal__meta')}>
<a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
</div>
)}
<div className='media-modal__overlay'>
{statusId && <Footer statusId={statusId} withOpenButton onClose={onClose} />}
</div>
</div>
);
}

@ -47,7 +47,7 @@ const componentMap = {
'DIRECTORY': Directory,
};
const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started/);
const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started|^\/start/);
const messages = defineMessages({
publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
@ -70,8 +70,12 @@ class ColumnsArea extends ImmutablePureComponent {
openSettings: PropTypes.func,
};
// Corresponds to (max-width: 600px + (285px * 1) + (10px * 1)) in SCSS
mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 895px)');
state = {
shouldAnimate: false,
renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches),
}
componentWillReceiveProps() {
@ -85,6 +89,15 @@ class ColumnsArea extends ImmutablePureComponent {
this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
}
if (this.mediaQuery) {
if (this.mediaQuery.addEventListener) {
this.mediaQuery.addEventListener('change', this.handleLayoutChange);
} else {
this.mediaQuery.addListener(this.handleLayoutChange);
}
this.setState({ renderComposePanel: !this.mediaQuery.matches });
}
this.lastIndex = getIndex(this.context.router.history.location.pathname);
this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl');
@ -114,6 +127,14 @@ class ColumnsArea extends ImmutablePureComponent {
if (!this.props.singleColumn) {
this.node.removeEventListener('wheel', this.handleWheel);
}
if (this.mediaQuery) {
if (this.mediaQuery.removeEventListener) {
this.mediaQuery.removeEventListener('change', this.handleLayoutChange);
} else {
this.mediaQuery.removeListener(this.handleLayouteChange);
}
}
}
handleChildrenContentChange() {
@ -123,6 +144,10 @@ class ColumnsArea extends ImmutablePureComponent {
}
}
handleLayoutChange = (e) => {
this.setState({ renderComposePanel: !e.matches });
}
handleSwipe = (index) => {
this.pendingIndex = index;
@ -186,7 +211,7 @@ class ColumnsArea extends ImmutablePureComponent {
render () {
const { columns, children, singleColumn, swipeToChangeColumns, intl, navbarUnder, openSettings } = this.props;
const { shouldAnimate } = this.state;
const { shouldAnimate, renderComposePanel } = this.state;
const columnIndex = getIndex(this.context.router.history.location.pathname);
@ -205,7 +230,7 @@ class ColumnsArea extends ImmutablePureComponent {
<div className='columns-area__panels'>
<div className='columns-area__panels__pane columns-area__panels__pane--compositional'>
<div className='columns-area__panels__pane__inner'>
<ComposePanel />
{renderComposePanel && <ComposePanel />}
</div>
</div>

@ -219,6 +219,10 @@ class FocalPointModal extends ImmutablePureComponent {
}
handleTextDetection = () => {
this._detectText();
}
_detectText = (refreshCache = false) => {
const { media } = this.props;
this.setState({ detecting: true });
@ -235,6 +239,7 @@ class FocalPointModal extends ImmutablePureComponent {
this.setState({ ocrStatus: 'preparing', progress });
}
},
cacheMethod: refreshCache ? 'refresh' : 'write',
});
let media_url = media.get('url');
@ -247,14 +252,20 @@ class FocalPointModal extends ImmutablePureComponent {
}
}
(async () => {
return (async () => {
await worker.load();
await worker.loadLanguage('eng');
await worker.initialize('eng');
const { data: { text } } = await worker.recognize(media_url);
this.setState({ description: removeExtraLineBreaks(text), dirty: true, detecting: false });
await worker.terminate();
})();
})().catch((e) => {
if (refreshCache) {
throw e;
} else {
this._detectText(true);
}
});
}).catch((e) => {
console.error(e);
this.setState({ detecting: false });
@ -309,7 +320,7 @@ class FocalPointModal extends ImmutablePureComponent {
return (
<div className='modal-root__modal report-modal' style={{ maxWidth: 960 }}>
<div className='report-modal__target'>
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
<IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
<FormattedMessage id='upload_modal.edit_media' defaultMessage='Edit media' />
</div>

@ -4,12 +4,14 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Video from 'flavours/glitch/features/video';
import classNames from 'classnames';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl } from 'react-intl';
import IconButton from 'flavours/glitch/components/icon_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImageLoader from './image_loader';
import Icon from 'flavours/glitch/components/icon';
import GIFV from 'flavours/glitch/components/gifv';
import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
import { getAverageFromBlurhash } from 'flavours/glitch/blurhash';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
@ -26,10 +28,14 @@ class MediaModal extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.list.isRequired,
status: ImmutablePropTypes.map,
statusId: PropTypes.string,
index: PropTypes.number.isRequired,
onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
onChangeBackgroundColor: PropTypes.func.isRequired,
currentTime: PropTypes.number,
autoPlay: PropTypes.bool,
volume: PropTypes.number,
};
state = {
@ -64,6 +70,7 @@ class MediaModal extends ImmutablePureComponent {
handleChangeIndex = (e) => {
const index = Number(e.currentTarget.getAttribute('data-index'));
this.setState({
index: index % this.props.media.size,
zoomButtonHidden: true,
@ -87,10 +94,12 @@ class MediaModal extends ImmutablePureComponent {
componentDidMount () {
window.addEventListener('keydown', this.handleKeyDown, false);
this._sendBackgroundColor();
}
componentWillUnmount () {
window.removeEventListener('keydown', this.handleKeyDown);
this.props.onChangeBackgroundColor(null);
}
getIndex () {
@ -106,30 +115,38 @@ class MediaModal extends ImmutablePureComponent {
handleStatusClick = e => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
this.context.router.history.push(`/statuses/${this.props.statusId}`);
}
this._sendBackgroundColor();
}
componentDidUpdate (prevProps, prevState) {
if (prevState.index !== this.state.index) {
this._sendBackgroundColor();
}
}
_sendBackgroundColor () {
const { media, onChangeBackgroundColor } = this.props;
const index = this.getIndex();
const blurhash = media.getIn([index, 'blurhash']);
if (blurhash) {
const backgroundColor = getAverageFromBlurhash(blurhash);
onChangeBackgroundColor(backgroundColor);
}
}
render () {
const { media, status, intl, onClose } = this.props;
const { media, statusId, intl, onClose } = this.props;
const { navigationHidden } = this.state;
const index = this.getIndex();
let pagination = [];
const leftNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' fixedWidth /></button>;
const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' fixedWidth /></button>;
if (media.size > 1) {
pagination = media.map((item, i) => {
const classes = ['media-modal__button'];
if (i === index) {
classes.push('media-modal__button--active');
}
return (<li className='media-modal__page-dot' key={i}><button tabIndex='0' className={classes.join(' ')} onClick={this.handleChangeIndex} data-index={i}>{i + 1}</button></li>);
});
}
const content = media.map((image) => {
const width = image.getIn(['meta', 'original', 'width']) || null;
const height = image.getIn(['meta', 'original', 'height']) || null;
@ -148,7 +165,7 @@ class MediaModal extends ImmutablePureComponent {
/>
);
} else if (image.get('type') === 'video') {
const { time } = this.props;
const { currentTime, autoPlay, volume } = this.props;
return (
<Video
@ -157,7 +174,10 @@ class MediaModal extends ImmutablePureComponent {
src={image.get('url')}
width={image.get('width')}
height={image.get('height')}
currentTime={time || 0}
frameRate={image.getIn(['meta', 'original', 'frame_rate'])}
currentTime={currentTime || 0}
autoPlay={autoPlay || false}
volume={volume || 1}
onCloseVideo={onClose}
detailed
alt={image.get('description')}
@ -197,13 +217,19 @@ class MediaModal extends ImmutablePureComponent {
'media-modal__navigation--hidden': navigationHidden,
});
let pagination;
if (media.size > 1) {
pagination = media.map((item, i) => (
<button key={i} className={classNames('media-modal__page-dot', { active: i === index })} data-index={i} onClick={this.handleChangeIndex}>
{i + 1}
</button>
));
}
return (
<div className='modal-root__modal media-modal'>
<div
className='media-modal__closer'
role='presentation'
onClick={onClose}
>
<div className='media-modal__closer' role='presentation' onClick={onClose} >
<ReactSwipeableViews
style={swipeableViewsStyle}
containerStyle={containerStyle}
@ -221,15 +247,10 @@ class MediaModal extends ImmutablePureComponent {
{leftNav}
{rightNav}
{status && (
<div className={classNames('media-modal__meta', { 'media-modal__meta--shifted': media.size > 1 })}>
<a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
</div>
)}
<ul className='media-modal__pagination'>
{pagination}
</ul>
<div className='media-modal__overlay'>
{pagination && <ul className='media-modal__pagination'>{pagination}</ul>}
{statusId && <Footer statusId={statusId} withOpenButton onClose={onClose} />}
</div>
</div>
</div>
);

@ -57,6 +57,10 @@ export default class ModalRoot extends React.PureComponent {
onClose: PropTypes.func.isRequired,
};
state = {
backgroundColor: null,
};
getSnapshotBeforeUpdate () {
return { visible: !!this.props.type };
}
@ -71,6 +75,10 @@ export default class ModalRoot extends React.PureComponent {
}
}
setBackgroundColor = color => {
this.setState({ backgroundColor: color });
}
renderLoading = modalId => () => {
return ['MEDIA', 'VIDEO', 'BOOST', 'FAVOURITE', 'DOODLE', 'GIPHY', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
}
@ -83,13 +91,14 @@ export default class ModalRoot extends React.PureComponent {
render () {
const { type, props, onClose } = this.props;
const { backgroundColor } = this.state;
const visible = !!type;
return (
<Base onClose={onClose} noEsc={props ? props.noEsc : false}>
<Base backgroundColor={backgroundColor} onClose={onClose} noEsc={props ? props.noEsc : false}>
{visible && (
<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}>
{(SpecificComponent) => <SpecificComponent {...props} onClose={onClose} />}
{(SpecificComponent) => <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={onClose} />}
</BundleContainer>
)}
</Base>

@ -91,7 +91,7 @@ class ReportModal extends ImmutablePureComponent {
return (
<div className='modal-root__modal report-modal'>
<div className='report-modal__target'>
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
<IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
<FormattedMessage id='report.target' defaultMessage='Report {target}' values={{ target: <strong>{account.get('acct')}</strong> }} />
</div>

@ -3,9 +3,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Video from 'flavours/glitch/features/video';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import Icon from 'flavours/glitch/components/icon';
import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
import { getAverageFromBlurhash } from 'flavours/glitch/blurhash';
export default class VideoModal extends ImmutablePureComponent {
@ -15,24 +14,28 @@ export default class VideoModal extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
status: ImmutablePropTypes.map,
statusId: PropTypes.string,
options: PropTypes.shape({
startTime: PropTypes.number,
autoPlay: PropTypes.bool,
defaultVolume: PropTypes.number,
}),
onClose: PropTypes.func.isRequired,
onChangeBackgroundColor: PropTypes.func.isRequired,
};
handleStatusClick = e => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
componentDidMount () {
const { media, onChangeBackgroundColor, onClose } = this.props;
const backgroundColor = getAverageFromBlurhash(media.get('blurhash'));
if (backgroundColor) {
onChangeBackgroundColor(backgroundColor);
}
}
render () {
const { media, status, onClose } = this.props;
const { media, statusId, onClose } = this.props;
const options = this.props.options || {};
return (
@ -52,11 +55,9 @@ export default class VideoModal extends ImmutablePureComponent {
/>
</div>
{status && (
<div className={classNames('media-modal__meta')}>
<a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
</div>
)}
<div className='media-modal__overlay'>
{statusId && <Footer statusId={statusId} withOpenButton onClose={onClose} />}
</div>
</div>
);
}

@ -50,9 +50,11 @@ import {
Search,
GettingStartedMisc,
Directory,
FollowRecommendations,
} from 'flavours/glitch/util/async-components';
import { HotKeys } from 'react-hotkeys';
import { me } from 'flavours/glitch/util/initial_state';
import { closeOnboarding, INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
// Dummy import, to make sure that <Status /> ends up in the application bundle.
@ -75,6 +77,7 @@ const mapStateToProps = state => ({
showFaviconBadge: state.getIn(['local_settings', 'notifications', 'favicon_badge']),
hicolorPrivacyIcons: state.getIn(['local_settings', 'hicolor_privacy_icons']),
moved: state.getIn(['accounts', me, 'moved']) && state.getIn(['accounts', state.getIn(['accounts', me, 'moved'])]),
firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
});
const keyMap = {
@ -207,6 +210,7 @@ class SwitchingColumnsArea extends React.PureComponent {
<WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
<WrappedRoute path='/start' component={FollowRecommendations} content={children} />
<WrappedRoute path='/search' component={Search} content={children} />
<WrappedRoute path='/directory' component={Directory} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
@ -260,6 +264,7 @@ class UI extends React.Component {
unreadNotifications: PropTypes.number,
showFaviconBadge: PropTypes.bool,
moved: PropTypes.map,
firstLaunch: PropTypes.bool,
};
state = {
@ -378,6 +383,12 @@ class UI extends React.Component {
this.favicon = new Favico({ animation:"none" });
// On first launch, redirect to the follow recommendations page
if (this.props.firstLaunch) {
this.context.router.history.replace('/start');
this.props.dispatch(closeOnboarding());
}
this.props.dispatch(fetchMarkers());
this.props.dispatch(expandHomeTimeline());
this.props.dispatch(expandNotifications());

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { fromJS, is } from 'immutable';
import { is } from 'immutable';
import { throttle, debounce } from 'lodash';
import classNames from 'classnames';
import { isFullscreen, requestFullscreen, exitFullscreen } from 'flavours/glitch/util/fullscreen';
@ -120,10 +120,10 @@ class Video extends React.PureComponent {
deployPictureInPicture: PropTypes.func,
preventPlayback: PropTypes.bool,
blurhash: PropTypes.string,
link: PropTypes.node,
autoPlay: PropTypes.bool,
volume: PropTypes.number,
muted: PropTypes.bool,
componetIndex: PropTypes.number,
};
static defaultProps = {
@ -510,25 +510,14 @@ class Video extends React.PureComponent {
}
handleOpenVideo = () => {
const { src, preview, width, height, alt } = this.props;
const media = fromJS({
type: 'video',
url: src,
preview_url: preview,
description: alt,
width,
height,
});
this.video.pause();
const options = {
this.props.onOpenVideo({
startTime: this.video.currentTime,
autoPlay: !this.state.paused,
defaultVolume: this.state.volume,
};
this.video.pause();
this.props.onOpenVideo(media, options);
componetIndex: this.props.componetIndex,
});
}
handleCloseVideo = () => {
@ -548,7 +537,7 @@ class Video extends React.PureComponent {
}
render () {
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive, link, editable, blurhash } = this.props;
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive, editable, blurhash } = this.props;
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
const progress = Math.min((currentTime / duration) * 100, 100);
const playerStyle = {};
@ -666,8 +655,6 @@ class Video extends React.PureComponent {
<span className='video-player__time-total'>{formatTime(Math.floor(duration))}</span>
</span>
)}
{link && <span className='video-player__link'>{link}</span>}
</div>
<div className='video-player__buttons right'>

@ -385,7 +385,7 @@ export default function compose(state = initialState, action) {
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
map.update(
'advanced_options',
map => map.merge(new ImmutableMap({ do_not_federate: action.status.get('local_only') }))
map => map.merge(new ImmutableMap({ do_not_federate: !!action.status.get('local_only') }))
);
map.set('focusDate', new Date());
map.set('caretPosition', null);
@ -505,7 +505,7 @@ export default function compose(state = initialState, action) {
case COMPOSE_GIPHY_SET:
return state.mergeIn(['giphy'], action.options);
case REDRAFT:
const do_not_federate = action.status.get('local_only', false);
const do_not_federate = !!action.status.get('local_only');
let text = action.raw_text || unescapeHTML(expandMentions(action.status));
if (do_not_federate) text = text.replace(/ ?👁\ufe0f?\u200b?$/, '');
return state.withMutations(map => {

@ -55,7 +55,6 @@ const initialState = ImmutableMap({
notifications : ImmutableMap({
favicon_badge : false,
tab_badge : true,
show_unread : true,
}),
});

@ -112,7 +112,7 @@ const expandNormalizedNotifications = (state, notifications, next, isLoadingRece
}
if (shouldCountUnreadNotifications(state)) {
mutable.update('unread', unread => unread + items.count(item => compareId(item.get('id'), lastReadId) > 0));
mutable.set('unread', mutable.get('pendingItems').count(item => item !== null) + mutable.get('items').count(item => item && compareId(item.get('id'), lastReadId) > 0));
} else {
const mostRecent = items.find(item => item !== null);
if (mostRecent && compareId(lastReadId, mostRecent.get('id')) < 0) {

@ -49,6 +49,7 @@ const initialState = ImmutableMap({
}),
dismissPermissionBanner: false,
showUnread: true,
shows: ImmutableMap({
follow: true,

@ -19,18 +19,18 @@ export default function suggestionsReducer(state = initialState, action) {
return state.set('isLoading', true);
case SUGGESTIONS_FETCH_SUCCESS:
return state.withMutations(map => {
map.set('items', fromJS(action.accounts.map(x => x.id)));
map.set('items', fromJS(action.suggestions.map(x => ({ ...x, account: x.account.id }))));
map.set('isLoading', false);
});
case SUGGESTIONS_FETCH_FAIL:
return state.set('isLoading', false);
case SUGGESTIONS_DISMISS:
return state.update('items', list => list.filterNot(id => id === action.id));
return state.update('items', list => list.filterNot(x => x.account === action.id));
case ACCOUNT_BLOCK_SUCCESS:
case ACCOUNT_MUTE_SUCCESS:
return state.update('items', list => list.filterNot(id => id === action.relationship.id));
return state.update('items', list => list.filterNot(x => x.account === action.relationship.id));
case DOMAIN_BLOCK_SUCCESS:
return state.update('items', list => list.filterNot(id => action.accounts.includes(id)));
return state.update('items', list => list.filterNot(x => action.accounts.includes(x.account)));
default:
return state;
}

@ -9,6 +9,7 @@ import {
TIMELINE_CONNECT,
TIMELINE_DISCONNECT,
TIMELINE_LOAD_PENDING,
TIMELINE_MARK_AS_PARTIAL,
} from 'flavours/glitch/actions/timelines';
import {
ACCOUNT_BLOCK_SUCCESS,
@ -173,6 +174,12 @@ export default function timelines(state = initialState, action) {
initialTimeline,
map => map.set('online', false).update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items),
);
case TIMELINE_MARK_AS_PARTIAL:
return state.update(
action.timeline,
initialTimeline,
map => map.set('isPartial', true).set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('unread', 0),
);
default:
return state;
}

@ -324,7 +324,6 @@ $small-breakpoint: 960px;
font-family: $font-sans-serif, sans-serif;
font-size: 16px;
font-weight: 400;
font-size: 16px;
line-height: 30px;
margin-bottom: 12px;
color: $darker-text-color;

@ -11,6 +11,19 @@
overflow: hidden;
text-decoration: none;
font-size: 14px;
&--with-note {
strong {
display: inline;
}
}
}
&__note {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: $ui-secondary-color;
}
&.small {
@ -26,6 +39,16 @@
}
}
.follow-recommendations-account {
.icon-button {
color: $ui-primary-color;
&.active {
color: $valid-value-color;
}
}
}
.account__wrapper {
display: flex;
}
@ -555,6 +578,17 @@
color: $primary-text-color;
}
.account__header__joined {
font-size: 14px;
padding: 5px 15px;
color: $darker-text-color;
.columns-area--mobile & {
padding-left: 20px;
padding-right: 20px;
}
}
.account__header__fields {
margin: 0;
border-top: 1px solid lighten($ui-base-color, 12%);

File diff suppressed because one or more lines are too long

@ -483,6 +483,73 @@
margin-right: 5px;
}
.column-settings__pillbar {
display: flex;
overflow: hidden;
background-color: transparent;
border: 0;
border-radius: 4px;
margin-bottom: 10px;
align-items: stretch;
}
.pillbar-button {
border: 0;
color: #fafafa;
padding: 2px;
margin: 0;
margin-left: 2px;
font-size: inherit;
flex: auto;
background-color: $ui-base-color;
transition-property: background-color, box-shadow;
transition: all 0.2s ease;
&[disabled] {
cursor: not-allowed;
opacity: 0.5;
}
&:not([disabled]) {
&:hover {
background-color: darken($ui-base-color, 10%);
}
&:focus {
background-color: darken($ui-base-color, 15%);
}
&:active {
background-color: darken($ui-base-color, 20%);
}
&.active {
background-color: $ui-highlight-color;
box-shadow: inset 0 5px darken($ui-highlight-color, 20%);
&:hover {
background-color: lighten($ui-highlight-color, 10%);
box-shadow: inset 0 5px darken($ui-highlight-color, 10%);
}
&:focus {
background-color: lighten($ui-highlight-color, 15%);
box-shadow: inset 0 5px darken($ui-highlight-color, 5%);
}
&:active {
background-color: lighten($ui-highlight-color, 20%);
box-shadow: inset 0 5px $ui-highlight-color;
}
}
}
/* TODO: check RTL? */
&:first-child {
margin-left: 0;
}
}
.empty-column-indicator,
.error-column,
.follow_requests-unlocked_explanation {
@ -729,3 +796,69 @@
text-align: center;
}
}
.column-title {
text-align: center;
padding: 40px;
.logo {
fill: $primary-text-color;
width: 50px;
margin: 0 auto;
margin-bottom: 40px;
}
h3 {
font-size: 24px;
line-height: 1.5;
font-weight: 700;
margin-bottom: 10px;
}
p {
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: $darker-text-color;
}
}
.follow-recommendations-container {
display: flex;
flex-direction: column;
}
.column-actions {
display: flex;
align-items: start;
justify-content: center;
padding: 40px;
padding-top: 40px;
padding-bottom: 200px;
flex-grow: 1;
position: relative;
&__background {
position: absolute;
left: 0;
bottom: 0;
height: 220px;
width: auto;
}
}
.column-list {
margin: 0 20px;
border: 1px solid lighten($ui-base-color, 8%);
background: darken($ui-base-color, 2%);
border-radius: 4px;
&__empty-message {
padding: 40px;
text-align: center;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: $darker-text-color;
}
}

@ -48,6 +48,8 @@
overflow: hidden;
transition: color .1s ease-out;
cursor: pointer;
background: transparent;
border: 0;
&:hover {
color: darken($lighter-text-color, 4%);
@ -106,11 +108,13 @@
padding: 10px;
padding-right: 45px;
background: $simple-background-color;
position: relative;
input {
font-size: 14px;
font-weight: 400;
padding: 7px 9px;
padding-right: 25px;
font-family: inherit;
display: block;
width: 100%;
@ -131,6 +135,30 @@
}
}
.emoji-mart-search-icon {
position: absolute;
top: 18px;
right: 45px + 5px;
z-index: 2;
padding: 2px 5px 1px;
border: 0;
background: none;
transition: all 100ms linear;
transition-property: opacity;
pointer-events: auto;
opacity: 0.7;
&:disabled {
cursor: default;
pointer-events: none;
opacity: 0.3;
}
svg {
fill: $action-button-color;
}
}
.emoji-mart-category .emoji-mart-emoji {
cursor: pointer;
@ -169,9 +197,36 @@
}
}
/* For screenreaders only, via https://stackoverflow.com/a/19758620 */
.emoji-mart-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
.emoji-mart-category-list {
margin: 0;
padding: 0;
}
.emoji-mart-category-list li {
list-style: none;
margin: 0;
padding: 0;
display: inline-block;
}
.emoji-mart-emoji {
position: relative;
display: inline-block;
background: transparent;
border: 0;
padding: 0;
font-size: 0;
span {
@ -182,19 +237,17 @@
.emoji-mart-no-results {
font-size: 14px;
color: $light-text-color;
text-align: center;
padding: 5px 6px;
padding-top: 70px;
color: $light-text-color;
.emoji-mart-category-label {
display: none;
}
.emoji-mart-no-results-label {
.emoji-mart-no-results-label {
margin-top: .2em;
}
.emoji-mart-emoji:hover::before {
cursor: default;
content: none;
}
}

@ -334,11 +334,11 @@
}
}
.star-icon.active {
.icon-button.star-icon.active {
color: $gold-star;
}
.bookmark-icon.active {
.icon-button.bookmark-icon.active {
color: $red-bookmark;
}
@ -1240,7 +1240,6 @@ button.icon-button.active i.fa-retweet {
span {
display: block;
float: left;
margin-left: 50%;
transform: translateX(-50%);
margin: 82px 0 0 50%;
white-space: nowrap;

@ -187,16 +187,19 @@
height: 100%;
position: relative;
.extended-video-player {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
&__close,
&__zoom-button {
color: rgba($white, 0.7);
video {
max-width: $media-modal-media-max-width;
max-height: $media-modal-media-max-height;
&:hover,
&:focus,
&:active {
color: $white;
background-color: rgba($white, 0.15);
}
&:focus {
background-color: rgba($white, 0.3);
}
}
}
@ -233,10 +236,10 @@
}
.media-modal__nav {
background: rgba($base-overlay-background, 0.5);
background: transparent;
box-sizing: border-box;
border: 0;
color: $primary-text-color;
color: rgba($primary-text-color, 0.7);
cursor: pointer;
display: flex;
align-items: center;
@ -247,6 +250,12 @@
position: absolute;
top: 0;
bottom: 0;
&:hover,
&:focus,
&:active {
color: $primary-text-color;
}
}
.media-modal__nav--left {
@ -257,58 +266,86 @@
right: 0;
}
.media-modal__pagination {
width: 100%;
text-align: center;
.media-modal__overlay {
max-width: 600px;
position: absolute;
left: 0;
bottom: 20px;
pointer-events: none;
}
right: 0;
bottom: 0;
margin: 0 auto;
.media-modal__meta {
text-align: center;
position: absolute;
left: 0;
bottom: 20px;
width: 100%;
pointer-events: none;
.picture-in-picture__footer {
border-radius: 0;
background: transparent;
padding: 20px 0;
&--shifted {
bottom: 62px;
}
.icon-button {
color: $white;
a {
pointer-events: auto;
text-decoration: none;
font-weight: 500;
color: $ui-secondary-color;
&:hover,
&:focus,
&:active {
color: $white;
background-color: rgba($white, 0.15);
}
&:hover,
&:focus,
&:active {
text-decoration: underline;
&:focus {
background-color: rgba($white, 0.3);
}
&.active {
color: $highlight-text-color;
&:hover,
&:focus,
&:active {
background: rgba($highlight-text-color, 0.15);
}
&:focus {
background: rgba($highlight-text-color, 0.3);
}
}
&.star-icon.active {
color: $gold-star;
&:hover,
&:focus,
&:active {
background: rgba($gold-star, 0.15);
}
&:focus {
background: rgba($gold-star, 0.3);
}
}
}
}
}
.media-modal__page-dot {
display: inline-block;
.media-modal__pagination {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
.media-modal__button {
.media-modal__page-dot {
flex: 0 0 auto;
background-color: $white;
height: 12px;
width: 12px;
border-radius: 6px;
margin: 10px;
opacity: 0.4;
height: 6px;
width: 6px;
border-radius: 50%;
margin: 0 4px;
padding: 0;
border: 0;
font-size: 0;
}
transition: opacity .2s ease-in-out;
.media-modal__button--active {
background-color: $ui-highlight-color;
&.active {
opacity: 1;
}
}
.media-modal__close {

@ -14,6 +14,7 @@
right: 0;
bottom: 0;
background: rgba($base-overlay-background, 0.7);
transition: background 0.5s;
}
.modal-root__container {
@ -846,9 +847,10 @@
.report-modal__target {
padding: 15px;
.media-modal__close {
top: 14px;
right: 15px;
.report-modal__close {
position: absolute;
top: 10px;
right: 10px;
}
}

@ -1122,21 +1122,6 @@ a.status-card.compact:hover {
.audio-player {
border-radius: 0;
}
@media screen and (max-width: 415px) {
width: 210px;
bottom: 10px;
right: 10px;
&__footer {
display: none;
}
.video-player,
.audio-player {
border-radius: 0 0 4px 4px;
}
}
}
.picture-in-picture-placeholder {

@ -595,6 +595,12 @@ code {
color: $valid-value-color;
}
&.warning {
border: 1px solid rgba($gold-star, 0.5);
background: rgba($gold-star, 0.25);
color: $gold-star;
}
&.alert {
border: 1px solid rgba($error-value-color, 0.5);
background: rgba($error-value-color, 0.1);
@ -616,6 +622,19 @@ code {
}
}
&.warning a {
font-weight: 700;
color: inherit;
text-decoration: underline;
&:hover,
&:focus,
&:active {
text-decoration: none;
color: inherit;
}
}
p {
margin-bottom: 15px;
}
@ -672,6 +691,29 @@ code {
}
}
.flash-message-stack {
margin-bottom: 30px;
.flash-message {
border-radius: 0;
margin-bottom: 0;
border-top-width: 0;
&:first-child {
border-radius: 4px 4px 0 0;
border-top-width: 1px;
}
&:last-child {
border-radius: 0 0 4px 4px;
&:first-child {
border-radius: 4px;
}
}
}
}
.form-footer {
margin-top: 30px;
text-align: center;

@ -1,3 +1,5 @@
@use "sass:math";
.hero-widget {
margin-bottom: 10px;
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
@ -489,10 +491,10 @@ $fluid-breakpoint: $maximum-width + 20px;
}
&__item {
width: (960px - 20px) / 3;
width: math.div(960px - 20px, 3);
@media screen and (max-width: $fluid-breakpoint) {
width: (940px - 20px) / 3;
width: math.div(940px - 20px, 3);
}
@media screen and (max-width: 640px) {
@ -584,7 +586,6 @@ $fluid-breakpoint: $maximum-width + 20px;
display: block;
font-weight: 500;
padding: 15px;
overflow: hidden;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

@ -169,3 +169,7 @@ export function Tesseract () {
export function Directory () {
return import(/* webpackChunkName: "features/glitch/async/directory" */'flavours/glitch/features/directory');
}
export function FollowRecommendations () {
return import(/* webpackChunkName: "features/glitch/async/follow_recommendations" */'flavours/glitch/features/follow_recommendations');
}

@ -7,30 +7,38 @@
const { unicodeToFilename } = require('./unicode_to_filename');
const { unicodeToUnifiedName } = require('./unicode_to_unified_name');
const emojiMap = require('./emoji_map.json');
const emojiMap = require('./emoji_map.json');
const { emojiIndex } = require('emoji-mart');
const { uncompress: emojiMartUncompress } = require('emoji-mart/dist/utils/data');
let data = require('emoji-mart/data/all.json');
if(data.compressed) {
data = emojiMartUncompress(data);
}
const emojiMartData = data;
const emojiMartData = data;
const excluded = ['®', '©', '™'];
const skins = ['🏻', '🏼', '🏽', '🏾', '🏿'];
const skinTones = ['🏻', '🏼', '🏽', '🏾', '🏿'];
const shortcodeMap = {};
const shortCodesToEmojiData = {};
const emojisWithoutShortCodes = [];
Object.keys(emojiIndex.emojis).forEach(key => {
shortcodeMap[emojiIndex.emojis[key].native] = emojiIndex.emojis[key].id;
let emoji = emojiIndex.emojis[key];
// Emojis with skin tone modifiers are stored like this
if (Object.prototype.hasOwnProperty.call(emoji, '1')) {
emoji = emoji['1'];
}
shortcodeMap[emoji.native] = emoji.id;
});
const stripModifiers = unicode => {
skins.forEach(tone => {
skinTones.forEach(tone => {
unicode = unicode.replace(tone, '');
});
@ -65,13 +73,22 @@ Object.keys(emojiMap).forEach(key => {
if (!Array.isArray(shortCodesToEmojiData[shortcode])) {
shortCodesToEmojiData[shortcode] = [[]];
}
shortCodesToEmojiData[shortcode][0].push(filenameData);
}
});
Object.keys(emojiIndex.emojis).forEach(key => {
const { native } = emojiIndex.emojis[key];
let emoji = emojiIndex.emojis[key];
// Emojis with skin tone modifiers are stored like this
if (Object.prototype.hasOwnProperty.call(emoji, '1')) {
emoji = emoji['1'];
}
const { native } = emoji;
let { short_names, search, unified } = emojiMartData.emojis[key];
if (short_names[0] !== key) {
throw new Error('The compresser expects the first short_code to be the ' +
'key. It may need to be rewritten if the emoji change such that this ' +
@ -81,11 +98,16 @@ Object.keys(emojiIndex.emojis).forEach(key => {
short_names = short_names.slice(1); // first short name can be inferred from the key
const searchData = [native, short_names, search];
if (unicodeToUnifiedName(native) !== unified) {
// unified name can't be derived from unicodeToUnifiedName
searchData.push(unified);
}
if (!Array.isArray(shortCodesToEmojiData[key])) {
shortCodesToEmojiData[key] = [[]];
}
shortCodesToEmojiData[key].push(searchData);
});

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

Loading…
Cancel
Save