diff --git a/.codeclimate.yml b/.codeclimate.yml index a70459764..571507a54 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -30,8 +30,8 @@ plugins: channel: eslint-5 rubocop: enabled: true - channel: rubocop-0-54 - scss-lint: + channel: rubocop-0-71 + sass-lint: enabled: true exclude_patterns: - spec/ diff --git a/.dependabot/config.yml b/.dependabot/config.yml new file mode 100644 index 000000000..07929aa07 --- /dev/null +++ b/.dependabot/config.yml @@ -0,0 +1,10 @@ +version: 1 + +update_configs: + - package_manager: "ruby:bundler" + directory: "/" + update_schedule: "weekly" + + - package_manager: "javascript" + directory: "/" + update_schedule: "weekly" diff --git a/.env.production.sample b/.env.production.sample index d1164efdc..d66b05050 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -10,6 +10,7 @@ DB_NAME=postgres DB_PASS= DB_PORT=5432 # Optional ElasticSearch configuration +# You may also set ES_PREFIX to share the same cluster between multiple Mastodon servers (falls back to REDIS_NAMESPACE if not set) # ES_ENABLED=true # ES_HOST=es # ES_PORT=9200 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..91ee92a2e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +patreon: mastodon +open_collective: mastodon diff --git a/.rubocop.yml b/.rubocop.yml index 59e8a757a..8bd4c867f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,6 @@ +require: + - rubocop-rails + AllCops: TargetRubyVersion: 2.3 Exclude: @@ -80,7 +83,10 @@ Rails/HttpStatus: Rails/Exit: Exclude: - 'lib/mastodon/*' - - 'lib/cli' + - 'lib/cli.rb' + +Rails/HelperInstanceVariable: + Enabled: false Style/ClassAndModuleChildren: Enabled: false diff --git a/.sass-lint.yml b/.sass-lint.yml new file mode 100644 index 000000000..a84adff3f --- /dev/null +++ b/.sass-lint.yml @@ -0,0 +1,37 @@ +# Linter Documentation: +# https://github.com/sasstools/sass-lint/tree/v1.13.1/docs/options + +files: + include: app/javascript/styles/**/*.scss + ignore: + - app/javascript/styles/mastodon/reset.scss + +rules: + # Disallows + no-color-literals: 0 + no-css-comments: 0 + no-duplicate-properties: 0 + no-ids: 0 + no-important: 0 + no-mergeable-selectors: 0 + no-misspelled-properties: 0 + no-qualifying-elements: 0 + no-transition-all: 0 + no-vendor-prefixes: 0 + + # Nesting + force-element-nesting: 0 + force-attribute-nesting: 0 + force-pseudo-nesting: 0 + + # Name Formats + class-name-format: 0 + leading-zero: 0 + + # Style Guide + attribute-quotes: 0 + hex-length: 0 + indentation: 0 + nesting-depth: 0 + property-sort-order: 0 + quotes: 0 diff --git a/.scss-lint.yml b/.scss-lint.yml deleted file mode 100644 index 5d7cc4da5..000000000 --- a/.scss-lint.yml +++ /dev/null @@ -1,264 +0,0 @@ -# Linter Documentation: -# https://github.com/brigade/scss-lint/blob/v0.42.2/lib/scss_lint/linter/README.md - -scss_files: 'app/javascript/styles/**/*.scss' - -exclude: - - app/javascript/styles/reset.scss - -linters: - # Reports when you use improper spacing around ! (the "bang") in !default, - # !global, !important, and !optional flags. - BangFormat: - enabled: false - - # Whether or not to prefer `border: 0` over `border: none`. - BorderZero: - enabled: false - - # Reports when you define a rule set using a selector with chained classes - # (a.k.a. adjoining classes). - ChainedClasses: - enabled: false - - # Prefer hexadecimal color codes over color keywords. - # (e.g. `color: green` is a color keyword) - ColorKeyword: - enabled: false - - # Prefer color literals (keywords or hexadecimal codes) to be used only in - # variable declarations. They should be referred to via variables everywhere - # else. - ColorVariable: - enabled: true - - # Which form of comments to prefer in CSS. - Comment: - enabled: false - - # Reports @debug statements (which you probably left behind accidentally). - DebugStatement: - enabled: false - - # Rule sets should be ordered as follows: - # - @extend declarations - # - @include declarations without inner @content - # - properties, @include declarations with inner @content - # - nested rule sets. - DeclarationOrder: - enabled: false - - # `scss-lint:disable` control comments should be preceded by a comment - # explaining why these linters are being disabled for this file. - # See https://github.com/brigade/scss-lint#disabling-linters-via-source for - # more information. - DisableLinterReason: - enabled: true - - # Reports when you define the same property twice in a single rule set. - DuplicateProperty: - enabled: false - - # Separate rule, function, and mixin declarations with empty lines. - EmptyLineBetweenBlocks: - enabled: true - - # Reports when you have an empty rule set. - EmptyRule: - enabled: true - - # Reports when you have an @extend directive. - ExtendDirective: - enabled: false - - # Files should always have a final newline. This results in better diffs - # when adding lines to the file, since SCM systems such as git won't - # think that you touched the last line. - FinalNewline: - enabled: false - - # HEX colors should use three-character values where possible. - HexLength: - enabled: false - - # HEX color values should use lower-case colors to differentiate between - # letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`. - HexNotation: - enabled: true - - # Avoid using ID selectors. - IdSelector: - enabled: false - - # The basenames of @imported SCSS partials should not begin with an - # underscore and should not include the filename extension. - ImportPath: - enabled: false - - # Avoid using !important in properties. It is usually indicative of a - # misunderstanding of CSS specificity and can lead to brittle code. - ImportantRule: - enabled: false - - # Indentation should always be done in increments of 2 spaces. - Indentation: - enabled: true - width: 2 - - # Don't write leading zeros for numeric values with a decimal point. - LeadingZero: - enabled: false - - # Reports when you define the same selector twice in a single sheet. - MergeableSelector: - enabled: false - - # Functions, mixins, variables, and placeholders should be declared - # with all lowercase letters and hyphens instead of underscores. - NameFormat: - enabled: false - - # Avoid nesting selectors too deeply. - NestingDepth: - enabled: false - - # Always use placeholder selectors in @extend. - PlaceholderInExtend: - enabled: false - - # Sort properties in a strict order. - PropertySortOrder: - enabled: false - - # Reports when you use an unknown or disabled CSS property - # (ignoring vendor-prefixed properties). - PropertySpelling: - enabled: false - - # Configure which units are allowed for property values. - PropertyUnits: - enabled: false - - # Pseudo-elements, like ::before, and ::first-letter, should be declared - # with two colons. Pseudo-classes, like :hover and :first-child, should - # be declared with one colon. - PseudoElement: - enabled: true - - # Avoid qualifying elements in selectors (also known as "tag-qualifying"). - QualifyingElement: - enabled: false - - # Don't write selectors with a depth of applicability greater than 3. - SelectorDepth: - enabled: false - - # Selectors should always use hyphenated-lowercase, rather than camelCase or - # snake_case. - SelectorFormat: - enabled: false - convention: hyphenated_lowercase - - # Prefer the shortest shorthand form possible for properties that support it. - Shorthand: - enabled: true - - # Each property should have its own line, except in the special case of - # single line rulesets. - SingleLinePerProperty: - enabled: true - allow_single_line_rule_sets: true - - # Split selectors onto separate lines after each comma, and have each - # individual selector occupy a single line. - SingleLinePerSelector: - enabled: true - - # Commas in lists should be followed by a space. - SpaceAfterComma: - enabled: false - - # Properties should be formatted with a single space separating the colon - # from the property's value. - SpaceAfterPropertyColon: - enabled: true - - # Properties should be formatted with no space between the name and the - # colon. - SpaceAfterPropertyName: - enabled: true - - # Variables should be formatted with a single space separating the colon - # from the variable's value. - SpaceAfterVariableColon: - enabled: true - - # Variables should be formatted with no space between the name and the - # colon. - SpaceAfterVariableName: - enabled: false - - # Operators should be formatted with a single space on both sides of an - # infix operator. - SpaceAroundOperator: - enabled: true - - # Opening braces should be preceded by a single space. - SpaceBeforeBrace: - enabled: true - - # Parentheses should not be padded with spaces. - SpaceBetweenParens: - enabled: false - - # Enforces that string literals should be written with a consistent form - # of quotes (single or double). - StringQuotes: - enabled: false - - # Property values, @extend, @include, and @import directives, and variable - # declarations should always end with a semicolon. - TrailingSemicolon: - enabled: true - - # Reports lines containing trailing whitespace. - TrailingWhitespace: - enabled: true - - # Don't write trailing zeros for numeric values with a decimal point. - TrailingZero: - enabled: false - - # Don't use the `all` keyword to specify transition properties. - TransitionAll: - enabled: false - - # Numeric values should not contain unnecessary fractional portions. - UnnecessaryMantissa: - enabled: false - - # Do not use parent selector references (&) when they would otherwise - # be unnecessary. - UnnecessaryParentReference: - enabled: false - - # URLs should be valid and not contain protocols or domain names. - UrlFormat: - enabled: true - - # URLs should always be enclosed within quotes. - UrlQuotes: - enabled: true - - # Properties, like color and font, are easier to read and maintain - # when defined using variables rather than literals. - VariableForProperty: - enabled: false - - # Avoid vendor prefixes. Or rather: don't write them yourself. - VendorPrefix: - enabled: false - - # Omit length units on zero values, e.g. `0px` vs. `0`. - ZeroUnit: - enabled: true diff --git a/.yarnclean b/.yarnclean index f2de52869..0cc2b50d7 100644 --- a/.yarnclean +++ b/.yarnclean @@ -43,4 +43,4 @@ Gruntfile.js # for specific ignore !.svgo.yml - +!sass-lint/**/*.yml diff --git a/AUTHORS.md b/AUTHORS.md index 3171214e0..8d3aaf480 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -6,8 +6,8 @@ and provided thanks to the work of the following contributors: * [Gargron](https://github.com/Gargron) * [ykzts](https://github.com/ykzts) -* [akihikodaki](https://github.com/akihikodaki) * [ThibG](https://github.com/ThibG) +* [akihikodaki](https://github.com/akihikodaki) * [mjankowski](https://github.com/mjankowski) * [dependabot[bot]](https://github.com/apps/dependabot) * [unarist](https://github.com/unarist) @@ -27,14 +27,14 @@ and provided thanks to the work of the following contributors: * [blackle](https://github.com/blackle) * [Quent-in](https://github.com/Quent-in) * [JantsoP](https://github.com/JantsoP) -* [mabkenar](https://github.com/mabkenar) * [Kjwon15](https://github.com/Kjwon15) +* [mabkenar](https://github.com/mabkenar) * [nullkal](https://github.com/nullkal) * [yookoala](https://github.com/yookoala) * [shuheiktgw](https://github.com/shuheiktgw) * [ashfurrow](https://github.com/ashfurrow) -* [Quenty31](https://github.com/Quenty31) * [zunda](https://github.com/zunda) +* [Quenty31](https://github.com/Quenty31) * [eramdam](https://github.com/eramdam) * [takayamaki](https://github.com/takayamaki) * [masarakki](https://github.com/masarakki) @@ -45,8 +45,8 @@ and provided thanks to the work of the following contributors: * [stephenburgess8](https://github.com/stephenburgess8) * [Wonderfall](https://github.com/Wonderfall) * [matteoaquila](https://github.com/matteoaquila) -* [rkarabut](https://github.com/rkarabut) * [yukimochi](https://github.com/yukimochi) +* [rkarabut](https://github.com/rkarabut) * [Artoria2e5](https://github.com/Artoria2e5) * [nightpool](https://github.com/nightpool) * [marrus-sh](https://github.com/marrus-sh) @@ -64,11 +64,14 @@ and provided thanks to the work of the following contributors: * [MaciekBaron](https://github.com/MaciekBaron) * [MitarashiDango](mailto:mitarashidango@users.noreply.github.com) * [beatrix-bitrot](https://github.com/beatrix-bitrot) +* [Aditoo17](https://github.com/Aditoo17) * [adbelle](https://github.com/adbelle) * [evanminto](https://github.com/evanminto) * [MightyPork](https://github.com/MightyPork) * [yhirano55](https://github.com/yhirano55) +* [rinsuki](https://github.com/rinsuki) * [camponez](https://github.com/camponez) +* [hinaloe](https://github.com/hinaloe) * [SerCom-KC](https://github.com/SerCom-KC) * [aschmitz](https://github.com/aschmitz) * [devkral](https://github.com/devkral) @@ -81,10 +84,8 @@ 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) -* [rinsuki](https://github.com/rinsuki) * [hikari-no-yume](https://github.com/hikari-no-yume) * [angristan](https://github.com/angristan) -* [hinaloe](https://github.com/hinaloe) * [seefood](https://github.com/seefood) * [jackjennings](https://github.com/jackjennings) * [spla](mailto:spla@mastodont.cat) @@ -102,9 +103,10 @@ and provided thanks to the work of the following contributors: * [victorhck](https://github.com/victorhck) * [kedamaDQ](https://github.com/kedamaDQ) * [puckipedia](https://github.com/puckipedia) +* [trwnh](https://github.com/trwnh) * [fvh-P](https://github.com/fvh-P) -* [contraexemplo](https://github.com/contraexemplo) -* [Aditoo17](https://github.com/Aditoo17) +* [Anna e só](mailto:contraexemplos@gmail.com) +* [BenLubar](https://github.com/BenLubar) * [kazu9su](https://github.com/kazu9su) * [Komic](https://github.com/Komic) * [lmorchard](https://github.com/lmorchard) @@ -117,7 +119,6 @@ and provided thanks to the work of the following contributors: * [goofy-bz](mailto:goofy@babelzilla.org) * [kadiix](https://github.com/kadiix) * [kodacs](https://github.com/kodacs) -* [trwnh](https://github.com/trwnh) * [JMendyk](https://github.com/JMendyk) * [KScl](https://github.com/KScl) * [sterdev](https://github.com/sterdev) @@ -133,6 +134,7 @@ and provided thanks to the work of the following contributors: * [Reverite](https://github.com/Reverite) * [JohnD28](https://github.com/JohnD28) * [znz](https://github.com/znz) +* [marek-lach](https://github.com/marek-lach) * [Naouak](https://github.com/Naouak) * [pawelngei](https://github.com/pawelngei) * [rtucker](https://github.com/rtucker) @@ -150,7 +152,6 @@ and provided thanks to the work of the following contributors: * [178inaba](https://github.com/178inaba) * [alyssais](https://github.com/alyssais) * [hiphref](https://github.com/hiphref) -* [BenLubar](https://github.com/BenLubar) * [stalker314314](https://github.com/stalker314314) * [huertanix](https://github.com/huertanix) * [genesixx](https://github.com/genesixx) @@ -161,16 +162,16 @@ and provided thanks to the work of the following contributors: * [kmichl](https://github.com/kmichl) * [Kurtis Rainbolt-Greene](mailto:me@kurtisrainboltgreene.name) * [saper](https://github.com/saper) -* [marek-lach](https://github.com/marek-lach) * [nevillepark](https://github.com/nevillepark) * [ornithocoder](https://github.com/ornithocoder) * [pierreozoux](https://github.com/pierreozoux) * [qguv](https://github.com/qguv) * [Ram Lmn](mailto:ramlmn@users.noreply.github.com) +* [sascha-sl](https://github.com/sascha-sl) * [harukasan](https://github.com/harukasan) * [stamak](https://github.com/stamak) * [Technowix](mailto:technowix@users.noreply.github.com) -* [Eychics](https://github.com/Eychics) +* [Zoeille](https://github.com/Zoeille) * [Thor Harald Johansen](mailto:thj@thj.no) * [0x70b1a5](https://github.com/0x70b1a5) * [gled-rs](https://github.com/gled-rs) @@ -244,9 +245,9 @@ and provided thanks to the work of the following contributors: * [raymestalez](https://github.com/raymestalez) * [remram44](https://github.com/remram44) * [sts10](https://github.com/sts10) -* [sascha-sl](https://github.com/sascha-sl) * [u1-liquid](https://github.com/u1-liquid) * [sim6](https://github.com/sim6) +* [Sir-Boops](https://github.com/Sir-Boops) * [stemid](https://github.com/stemid) * [sumdog](https://github.com/sumdog) * [ThomasLeister](https://github.com/ThomasLeister) @@ -316,8 +317,11 @@ and provided thanks to the work of the following contributors: * [Andreas Drop](mailto:andy@remline.de) * [andi1984](https://github.com/andi1984) * [schas002](https://github.com/schas002) +* [contraexemplo](https://github.com/contraexemplo) * [abackstrom](https://github.com/abackstrom) +* [armandfardeau](https://github.com/armandfardeau) * [jumbosushi](https://github.com/jumbosushi) +* [aurelien-reeves](https://github.com/aurelien-reeves) * [ayumin](https://github.com/ayumin) * [BaptisteGelez](https://github.com/BaptisteGelez) * [bzg](https://github.com/bzg) @@ -335,7 +339,7 @@ and provided thanks to the work of the following contributors: * [Motoma](https://github.com/Motoma) * [chriswk](https://github.com/chriswk) * [csu](https://github.com/csu) -* [clarcharr](https://github.com/clarcharr) +* [clarfon](https://github.com/clarfon) * [kklleemm](https://github.com/kklleemm) * [colindean](https://github.com/colindean) * [dachinat](https://github.com/dachinat) @@ -358,6 +362,7 @@ and provided thanks to the work of the following contributors: * [eai04191](https://github.com/eai04191) * [d3vgru](https://github.com/d3vgru) * [Elizafox](https://github.com/Elizafox) +* [enewhuis](https://github.com/enewhuis) * [ericblade](https://github.com/ericblade) * [mikoim](https://github.com/mikoim) * [espenronnevik](https://github.com/espenronnevik) @@ -446,6 +451,7 @@ and provided thanks to the work of the following contributors: * [mouse-reeve](https://github.com/mouse-reeve) * [Mozinet-fr](https://github.com/Mozinet-fr) * [lae](https://github.com/lae) +* [nosada](https://github.com/nosada) * [Nanamachi](https://github.com/Nanamachi) * [orinthe](https://github.com/orinthe) * [NecroTechno](https://github.com/NecroTechno) @@ -462,10 +468,11 @@ and provided thanks to the work of the following contributors: * [noppa](https://github.com/noppa) * [Otakan951](https://github.com/Otakan951) * [fahy](https://github.com/fahy) -* [PatrickRWells](https://github.com/PatrickRWells) -* [Pangoraw](https://github.com/Pangoraw) -* [peterkeen](https://github.com/peterkeen) -* [pgate](https://github.com/pgate) +* [PatrickRWells](mailto:32802366+patrickrwells@users.noreply.github.com) +* [Paul](mailto:naydex.mc+github@gmail.com) +* [Pete Keen](mailto:pete@petekeen.net) +* [Pierre-Morgan Gate](mailto:pgate@users.noreply.github.com) +* [Ratmir Karabut](mailto:rkarabut@sfmodern.ru) * [Reto Kromer](mailto:retokromer@users.noreply.github.com) * [Rey Tucker](mailto:git@reytucker.us) * [Rob Watson](mailto:rfwatson@users.noreply.github.com) @@ -488,7 +495,6 @@ and provided thanks to the work of the following contributors: * [Sho Kusano](mailto:rosylilly@aduca.org) * [Shouko Yu](mailto:imshouko@gmail.com) * [Sina Mashek](mailto:sina@mashek.xyz) -* [Sir-Boops](mailto:admin@boops.me) * [Soshi Kato](mailto:mail@sossii.com) * [Spanky](mailto:2788886+spankyworks@users.noreply.github.com) * [Stanislas](mailto:angristan@pm.me) @@ -555,12 +561,14 @@ and provided thanks to the work of the following contributors: * [karlyeurl](mailto:karl.yeurl@gmail.com) * [kedama](mailto:32974885+kedamadq@users.noreply.github.com) * [kodai](mailto:shirafuta.kodai@gmail.com) +* [koyu](mailto:me@koyu.space) * [kuro5hin](mailto:rusty@kuro5hin.org) * [luzpaz](mailto:luzpaz@users.noreply.github.com) * [maxypy](mailto:maxime@mpigou.fr) * [mhe](mailto:mail@marcus-herrmann.com) * [mike castleman](mailto:m@mlcastle.net) * [mimikun](mailto:dzdzble_effort_311@outlook.jp) +* [mohemohe](mailto:mohemohe@users.noreply.github.com) * [mshrtkch](mailto:mshrtkch@users.noreply.github.com) * [muan](mailto:muan@github.com) * [namelessGonbai](mailto:43787036+namelessgonbai@users.noreply.github.com) @@ -599,243 +607,338 @@ This document is provided for informational purposes only. Since it is only upda Following people have contributed to translation of Mastodon: +- **Albanian** + - Besnik Bleta + - Aditoo - **Arabic** - ButterflyOfFire + - Aditoo + - Amrz0 - **Asturian** - ButterflyOfFire - Enol P. + - Aditoo - **Basque** + - Osoitz + - Aditoo - Aitzol - ButterflyOfFire - - Gorka Azkarate - - Osoitz - Peru Iparragirre + - Gorka Azkarate +- **Bengali** + - dxwc - **Bulgarian** - ButterflyOfFire + - Aditoo - **Catalan** + - spla + - Aditoo - ButterflyOfFire - Joan Montané - Jose Luis - - spla - **Chinese (Hong Kong)** - ButterflyOfFire - Luzi Leung + - Aditoo - **Chinese (Simplified)** - Allen Zhong - ButterflyOfFire - SerCom_KC + - martialarts + - Kaitian Xie + - Aditoo + - pan93412 - **Chinese (Traditional)** + - Aditoo - ButterflyOfFire - James58899 - - Jeff Huang + - pan93412 - S1ttidoe477 - SHA265 + - Jeff Huang - **Corsican** - Alix D. R. + - Aditoo - ButterflyOfFire - **Croatian** - ButterflyOfFire + - Aditoo - **Czech** - - ButterflyOfFire - - Lorem Ipsum + - Aditoo - Marek Ľach + - ButterflyOfFire - **Danish** - - ButterflyOfFire + - Einhjeriar - Rasmus Sæderup -- **Dutch** + - Aditoo + - ButterflyOfFire +- **Dutch** + - Albakham - ButterflyOfFire - - Jelv - jeroenpraat - rscmbbng + - Aditoo + - Jelv - **English** - ButterflyOfFire - Renato "Lond" Cerqueira +- **English (United Kingdom)** + - Albakham - **Esperanto** + - Aditoo - ButterflyOfFire + - Becci Cat - Jeong Arm - - Martin Bodin - Mélanie Chauvel - Vanege + - Martin Bodin - tuxayo/Victor Grousset - **Finnish** - ButterflyOfFire - - Jonne Arjoranta - - S Heija + - Mikko Poussu - Taru Luojola + - S Heija + - Aditoo + - Jonne Arjoranta - **French** - - Alda Marteau-Hardi + - Albakham - Alix D. R. - - Baptiste Jonglez - ButterflyOfFire - - Franck Paul - - Jean-Baptiste Holcroft + - codl + - Leia + - Alda Marteau-Hardi + - Mélanie Chauvel + - Paul Marques Mota + - azenet + - Olivier Humbert + - Aditoo - Jonathan Chan - Letiteuf55 - - Martin Bodin - - Mélanie Chauvel - - Olivier Humbert - - Paul Marques Mota - - Sylvhem + - Baptiste Jonglez + - goofy-mdn + - Jean-Baptiste Holcroft - Technowix - - Thibaut Girka + - Martin Bodin - Théodore - - azenet - - codl + - Thibaut Girka + - Franck Paul + - Sylvhem - **Galician** - ButterflyOfFire - Xose M. + - Aditoo - manequim - **Georgian** - ButterflyOfFire + - Aditoo - **German** - - Benedikt Geißler + - Aditoo - ButterflyOfFire - Daniel - - Eugen Rochko - - Koyu Berteon - - Patrick Figel - - Weblate Admin - averageunicorn - - ePirat - - koyu + - Koyu Berteon - larsreineke + - koyu + - Austin Jones - lilo + - Benedikt Geißler + - ePirat + - Eugen Rochko + - Weblate Admin + - Patrick Figel - **Greek** - - Antonis - - ButterflyOfFire - Dimitris Maroulidis + - Antonis + - Aditoo + - ButterflyOfFire - Konstantinos Grevenitis - **Hebrew** - ButterflyOfFire + - Aditoo - Ira - Yaron Shahrabani - **Hungarian** - - Adam Paszternak - ButterflyOfFire + - Adam Paszternak + - Aditoo - Tibike Miklós - **Ido** - ButterflyOfFire + - Aditoo - **Indonesian** - - Alfiana Sibuea + - afachri - ButterflyOfFire - Dito Kurnia Pratama - Eirworks - - afachri + - Aditoo + - Alfiana Sibuea - se7entime +- **Irish** + - Albakham + - Kevin Houlihan - **Italian** - Alessandro Levati + - Albakham - ButterflyOfFire + - Marcin Mikołajczak + - Aditoo - Giuseppe Pignataro - Stefano - **Japanese** - - ButterflyOfFire - - Kumasun Morino - - Yamagishi Kazutoshi + - Hinaloe + - 小鳥遊まりあ - mayaeh - osapon - - unarist - - 小鳥遊まりあ - 森の子リスのミーコの大冒険 -- **Korean** + - Kumasun Morino + - Yamagishi Kazutoshi + - Aditoo - ButterflyOfFire - Jeong Arm + - unarist +- **Kazakh** + - arshat + - Aditoo +- **Korean** + - Aditoo + - Jeong Arm + - ButterflyOfFire - Minori Hiraoka - Yamagishi Kazutoshi +- **Lithuanian** + - Sarunas Medeikis - **Malay** - - ButterflyOfFire - Muhammad Nur Hidayat (MNH48) + - Aditoo + - ButterflyOfFire - **Norwegian (old code)** - ButterflyOfFire - Espen Rønnevik + - Aditoo - Tale - **Occitan** + - Aditoo - ButterflyOfFire - - Maxenç - Quenti2 - Quentí + - Maxenç - **Persian** - - ButterflyOfFire - Masoud Abkenar -- **Polish** + - Aditoo + - ButterflyOfFire +- **Polish** + - Aditoo + - Albakham - ButterflyOfFire - - Jakub Mendyk - - Marcin Mikołajczak - - Marek Ľach - Stasiek Michalski + - Marcin Mikołajczak + - Jakub Mendyk + - Marek Ľach - krkk - **Portuguese** + - Albakham + - João Pinheiro + - manequim + - Aditoo - ButterflyOfFire - Hugo Gameiro - - manequim - **Portuguese (Brazil)** - - André Andrade + - Aditoo + - Albakham - Anna e só - - ButterflyOfFire - Renato "Lond" Cerqueira -- **Romanian** + - André Andrade - ButterflyOfFire +- **Romanian** - adrianbblk + - ButterflyOfFire + - Aditoo - **Russian** - - Andrew Zyabin + - Albakham - ButterflyOfFire - Evgeny Petrov + - Aditoo + - Павел Гастелло + - Andrew Zyabin - Yaron Shahrabani - **Serbian** - Branko Kokanovic - Burekz Finezt + - Aditoo - ButterflyOfFire - **Serbian (latin)** - ButterflyOfFire + - Aditoo - **Slovak** + - Aditoo - ButterflyOfFire - Ivan Pleva - - Lorem Ipsum - Marek Ľach - Peter - **Slovenian** - - ButterflyOfFire - Kristijan Tkalec + - Aditoo + - ButterflyOfFire - **Spanish** - - Angeles Broullón - - Antón López + - Albakham - ButterflyOfFire - Carlos Mondragon + - Antón López + - Max Winkler + - Pablo de la Concepción Sanz + - Sergio Soriano + - Angeles Broullón + - Lothar Wolf + - Aditoo - David Charte - Emmanuel - - Lothar Wolf - - Pablo de la Concepción Sanz - **Swedish** - ButterflyOfFire - - Elias Mårtenson - Isak Holmström - Shellkr + - Aditoo + - Elias Mårtenson - Stefan Midjich - Tim Stahel + - Jonas Hultén - **Telugu** + - avndp + - Ranjith Tellakula + - Aditoo - ButterflyOfFire - Joseph Nuthalapati - - Ranjith Tellakula - - avndp - **Thai** - ButterflyOfFire + - parnikkapore + - Thai Localization + - Aditoo - **Turkish** + - Ali Demirtas - ButterflyOfFire + - Aditoo - **Ukrainian** - - ButterflyOfFire - - Ivan Verchenko - alexcleac -- **Welsh** - ButterflyOfFire - - Jaz-Michael King - - Kevin Beynon - - Owain Rhys Lewis - - Renato "Lond" Cerqueira - - Rhoslyn Prys + - Aditoo + - Ivan Verchenko +- **Welsh** - carl morris + - Jaz-Michael King + - Owain Rhys Lewis + - Rhoslyn Prys + - Aditoo + - ButterflyOfFire + - Renato "Lond" Cerqueira + - Albakham + - Kevin Beynon - **Armenian** + - Aditoo - ButterflyOfFire - **Latvian** + - Aditoo - ButterflyOfFire + - Maigonis - **Tamil** + - Aditoo - ButterflyOfFire - Prasanna Venkadesh diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b10adbbf..3059a5f9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,224 @@ Changelog All notable changes to this project will be documented in this file. +## [Unreleased] +### Added + +- **Add single-column mode in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/10807), [Gargron](https://github.com/tootsuite/mastodon/pull/10848), [Gargron](https://github.com/tootsuite/mastodon/pull/11003), [Gargron](https://github.com/tootsuite/mastodon/pull/10961), [Hanage999](https://github.com/tootsuite/mastodon/pull/10915), [noellabo](https://github.com/tootsuite/mastodon/pull/10917), [abcang](https://github.com/tootsuite/mastodon/pull/10859), [Gargron](https://github.com/tootsuite/mastodon/pull/10820), [Gargron](https://github.com/tootsuite/mastodon/pull/10835), [Gargron](https://github.com/tootsuite/mastodon/pull/10809), [Gargron](https://github.com/tootsuite/mastodon/pull/10963), [noellabo](https://github.com/tootsuite/mastodon/pull/10883), [Hanage999](https://github.com/tootsuite/mastodon/pull/10839)) +- Add waiting time to the list of pending accounts in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10985)) +- Add a keyboard shortcut to hide/show media in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10647), [Gargron](https://github.com/tootsuite/mastodon/pull/10838), [ThibG](https://github.com/tootsuite/mastodon/pull/10872)) +- Add `account_id` param to `GET /api/v1/notifications` ([pwoolcoc](https://github.com/tootsuite/mastodon/pull/10796)) +- Add confirmation modal for unboosting toots in web UI ([aurelien-reeves](https://github.com/tootsuite/mastodon/pull/10287)) +- Add emoji suggestions to content warning and poll option fields in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10555)) +- Add `source` attribute to response of `DELETE /api/v1/statuses/:id` ([ThibG](https://github.com/tootsuite/mastodon/pull/10669)) +- Add some caching for HTML versions of public status pages ([ThibG](https://github.com/tootsuite/mastodon/pull/10701)) + +### Changed + +- **Change default layout to single column in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/10847)) +- **Change light theme** ([Gargron](https://github.com/tootsuite/mastodon/pull/10992), [Gargron](https://github.com/tootsuite/mastodon/pull/10996), [yuzulabo](https://github.com/tootsuite/mastodon/pull/10754), [Gargron](https://github.com/tootsuite/mastodon/pull/10845)) +- **Change preferences page into appearance, notifications, and other** ([Gargron](https://github.com/tootsuite/mastodon/pull/10977), [Gargron](https://github.com/tootsuite/mastodon/pull/10988)) +- Change priority of delete activity forwards for replies and reblogs ([Gargron](https://github.com/tootsuite/mastodon/pull/11002)) +- Change Mastodon logo to use primary text color of the given theme ([Gargron](https://github.com/tootsuite/mastodon/pull/10994)) +- Change reblogs counter to be updated when boosted privately ([Gargron](https://github.com/tootsuite/mastodon/pull/10964)) +- Change bio limit from 160 to 500 characters ([trwnh](https://github.com/tootsuite/mastodon/pull/10790)) +- Change API rate limiting to reduce allowed unauthenticated requests ([ThibG](https://github.com/tootsuite/mastodon/pull/10860), [hinaloe](https://github.com/tootsuite/mastodon/pull/10868), [mayaeh](https://github.com/tootsuite/mastodon/pull/10867)) +- Change help text of `tootctl emoji import` command to specify a gzipped TAR archive is required ([dariusk](https://github.com/tootsuite/mastodon/pull/11000)) +- Change web UI to hide poll options behind content warnings ([ThibG](https://github.com/tootsuite/mastodon/pull/10983)) +- Change silencing to ensure local effects and remote effects are the same for silenced local users ([ThibG](https://github.com/tootsuite/mastodon/pull/10575)) +- Change `tootctl domains purge` to remove custom emoji as well ([Kjwon15](https://github.com/tootsuite/mastodon/pull/10721)) +- Change Docker image to keep `apt` working ([SuperSandro2000](https://github.com/tootsuite/mastodon/pull/10830)) + +### Removed + +- Remove `dist-upgrade` from Docker image ([SuperSandro2000](https://github.com/tootsuite/mastodon/pull/10822)) + +### Fixed + +- Fix RTL layout not being RTL within the columns area in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10990)) +- Fix display of alternative text when a media attachment is not available in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10981)) +- Fix not being able to directly switch between list timelines in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10973)) +- Fix media sensitivity not being maintained in delete & redraft in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10980)) +- Fix emoji picker being always displayed in web UI ([noellabo](https://github.com/tootsuite/mastodon/pull/10979), [yuzulabo](https://github.com/tootsuite/mastodon/pull/10801), [wcpaez](https://github.com/tootsuite/mastodon/pull/10978)) +- Fix potential private status leak through caching ([ThibG](https://github.com/tootsuite/mastodon/pull/10969)) +- Fix refreshing featured toots when the new collection is empty in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10971)) +- Fix undoing domain block also undoing individual moderation on users from before the domain block ([ThibG](https://github.com/tootsuite/mastodon/pull/10660)) +- Fix time not being local in the audit log ([yuzulabo](https://github.com/tootsuite/mastodon/pull/10751)) +- Fix statuses removed by moderation re-appearing on subsequent fetches ([Kjwon15](https://github.com/tootsuite/mastodon/pull/10732)) +- Fix misattribution of inlined announces if `attributedTo` isn't present in ActivityPub ([ThibG](https://github.com/tootsuite/mastodon/pull/10967)) +- Fix `GET /api/v1/polls/:id` not requiring authentication for non-public polls ([Gargron](https://github.com/tootsuite/mastodon/pull/10960)) +- Fix handling of blank poll options in ActivityPub ([ThibG](https://github.com/tootsuite/mastodon/pull/10946)) +- Fix avatar preview aspect ratio on edit profile page ([Kjwon15](https://github.com/tootsuite/mastodon/pull/10931)) +- Fix web push notifications not being sent for polls ([ThibG](https://github.com/tootsuite/mastodon/pull/10864)) +- Fix cut off letters in last paragraph of statuses in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/10821)) + +## [2.8.4] - 2019-05-24 +### Fixed + +- Fix delivery not retrying on some inbox errors that should be retriable ([ThibG](https://github.com/tootsuite/mastodon/pull/10812)) +- Fix unnecessary 5 minute cooldowns on signature verifications in some cases ([ThibG](https://github.com/tootsuite/mastodon/pull/10813)) +- Fix possible race condition when processing statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10815)) + +### Security + +- Require specific OAuth scopes for specific endpoints of the streaming API, instead of merely requiring a token for all endpoints, and allow using WebSockets protocol negotiation to specify the access token instead of using a query string ([ThibG](https://github.com/tootsuite/mastodon/pull/10818)) + +## [2.8.3] - 2019-05-19 +### Added + +- Add `og:image:alt` OpenGraph tag ([BenLubar](https://github.com/tootsuite/mastodon/pull/10779)) +- Add clickable area below avatar in statuses in web UI ([Dar13](https://github.com/tootsuite/mastodon/pull/10766)) +- Add crossed-out eye icon on account gallery in web UI ([Kjwon15](https://github.com/tootsuite/mastodon/pull/10715)) +- Add media description tooltip to thumbnails in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10713)) + +### Changed + +- Change "mark as sensitive" button into a checkbox for clarity ([ThibG](https://github.com/tootsuite/mastodon/pull/10748)) + +### Fixed + +- Fix bug allowing users to publicly boost their private statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10775), [ThibG](https://github.com/tootsuite/mastodon/pull/10783)) +- Fix performance in formatter by a little ([ThibG](https://github.com/tootsuite/mastodon/pull/10765)) +- Fix some colors in the light theme ([yuzulabo](https://github.com/tootsuite/mastodon/pull/10754)) +- Fix some colors of the high contrast theme ([yuzulabo](https://github.com/tootsuite/mastodon/pull/10711)) +- Fix ambivalent active state of poll refresh button in web UI ([MaciekBaron](https://github.com/tootsuite/mastodon/pull/10720)) +- Fix duplicate posting being possible from web UI ([hinaloe](https://github.com/tootsuite/mastodon/pull/10785)) +- Fix "invited by" not showing up in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10791)) + +## [2.8.2] - 2019-05-05 +### Added + +- Add `SOURCE_TAG` environment variable ([ushitora-anqou](https://github.com/tootsuite/mastodon/pull/10698)) + +### Fixed + +- Fix cropped hero image on frontpage ([BaptisteGelez](https://github.com/tootsuite/mastodon/pull/10702)) +- Fix blurhash gem not compiling on some operating systems ([Gargron](https://github.com/tootsuite/mastodon/pull/10700)) +- Fix unexpected CSS animations in some browsers ([ThibG](https://github.com/tootsuite/mastodon/pull/10699)) +- Fix closing video modal scrolling timelines to top ([ThibG](https://github.com/tootsuite/mastodon/pull/10695)) + +## [2.8.1] - 2019-05-04 +### Added + +- Add link to existing domain block when trying to block an already-blocked domain ([ThibG](https://github.com/tootsuite/mastodon/pull/10663)) +- Add button to view context to media modal when opened from account gallery in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10676)) +- Add ability to create multiple-choice polls in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10603)) +- Add `GITHUB_REPOSITORY` and `SOURCE_BASE_URL` environment variables ([rosylilly](https://github.com/tootsuite/mastodon/pull/10600)) +- Add `/interact/` paths to `robots.txt` ([ThibG](https://github.com/tootsuite/mastodon/pull/10666)) +- Add `blurhash` to the Attachment entity in the REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/10630)) + +### Changed + +- Change hidden media to be shown as a blurhash-based colorful gradient instead of a black box in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10630)) +- Change rejected media to be shown as a blurhash-based gradient instead of a list of filenames in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10630)) +- Change e-mail whitelist/blacklist to not be checked when invited ([Gargron](https://github.com/tootsuite/mastodon/pull/10683)) +- Change cache header of REST API results to no-cache ([ThibG](https://github.com/tootsuite/mastodon/pull/10655)) +- Change the "mark media as sensitive" button to be more obvious in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10673), [Gargron](https://github.com/tootsuite/mastodon/pull/10682)) +- Change account gallery in web UI to display 3 columns, open media modal ([Gargron](https://github.com/tootsuite/mastodon/pull/10667), [Gargron](https://github.com/tootsuite/mastodon/pull/10674)) + +### Fixed + +- Fix LDAP/PAM/SAML/CAS users not being pre-approved ([Gargron](https://github.com/tootsuite/mastodon/pull/10621)) +- Fix accounts created through tootctl not being always pre-approved ([Gargron](https://github.com/tootsuite/mastodon/pull/10684)) +- Fix Sidekiq retrying ActivityPub processing jobs that fail validation ([ThibG](https://github.com/tootsuite/mastodon/pull/10614)) +- Fix toots not being scrolled into view sometimes through keyboard selection ([ThibG](https://github.com/tootsuite/mastodon/pull/10593)) +- Fix expired invite links being usable to bypass approval mode ([ThibG](https://github.com/tootsuite/mastodon/pull/10657)) +- Fix not being able to save e-mail preference for new pending accounts ([Gargron](https://github.com/tootsuite/mastodon/pull/10622)) +- Fix upload progressbar when image resizing is involved ([ThibG](https://github.com/tootsuite/mastodon/pull/10632)) +- Fix block action not automatically cancelling pending follow request ([ThibG](https://github.com/tootsuite/mastodon/pull/10633)) +- Fix stoplight logging to stderr separate from Rails logger ([Gargron](https://github.com/tootsuite/mastodon/pull/10624)) +- Fix sign up button not saying sign up when invite is used ([Gargron](https://github.com/tootsuite/mastodon/pull/10623)) +- Fix health checks in Docker Compose configuration ([fabianonline](https://github.com/tootsuite/mastodon/pull/10553)) +- Fix modal items not being scrollable on touch devices ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/10605)) +- Fix Keybase configuration using wrong domain when a web domain is used ([BenLubar](https://github.com/tootsuite/mastodon/pull/10565)) +- Fix avatar GIFs not being animated on-hover on public profiles ([hyenagirl64](https://github.com/tootsuite/mastodon/pull/10549)) +- Fix OpenGraph parser not understanding some valid property meta tags ([da2x](https://github.com/tootsuite/mastodon/pull/10604)) +- Fix wrong fonts being displayed when Roboto is installed on user's machine ([ThibG](https://github.com/tootsuite/mastodon/pull/10594)) +- Fix confirmation modals being too narrow for a secondary action button ([ThibG](https://github.com/tootsuite/mastodon/pull/10586)) + +## [2.8.0] - 2019-04-10 +### Added + +- Add polls ([Gargron](https://github.com/tootsuite/mastodon/pull/10111), [ThibG](https://github.com/tootsuite/mastodon/pull/10155), [Gargron](https://github.com/tootsuite/mastodon/pull/10184), [ThibG](https://github.com/tootsuite/mastodon/pull/10196), [Gargron](https://github.com/tootsuite/mastodon/pull/10248), [ThibG](https://github.com/tootsuite/mastodon/pull/10255), [ThibG](https://github.com/tootsuite/mastodon/pull/10322), [Gargron](https://github.com/tootsuite/mastodon/pull/10138), [Gargron](https://github.com/tootsuite/mastodon/pull/10139), [Gargron](https://github.com/tootsuite/mastodon/pull/10144), [Gargron](https://github.com/tootsuite/mastodon/pull/10145),[Gargron](https://github.com/tootsuite/mastodon/pull/10146), [Gargron](https://github.com/tootsuite/mastodon/pull/10148), [Gargron](https://github.com/tootsuite/mastodon/pull/10151), [ThibG](https://github.com/tootsuite/mastodon/pull/10150), [Gargron](https://github.com/tootsuite/mastodon/pull/10168), [Gargron](https://github.com/tootsuite/mastodon/pull/10165), [Gargron](https://github.com/tootsuite/mastodon/pull/10172), [Gargron](https://github.com/tootsuite/mastodon/pull/10170), [Gargron](https://github.com/tootsuite/mastodon/pull/10171), [Gargron](https://github.com/tootsuite/mastodon/pull/10186), [Gargron](https://github.com/tootsuite/mastodon/pull/10189), [ThibG](https://github.com/tootsuite/mastodon/pull/10200), [rinsuki](https://github.com/tootsuite/mastodon/pull/10203), [Gargron](https://github.com/tootsuite/mastodon/pull/10213), [Gargron](https://github.com/tootsuite/mastodon/pull/10246), [Gargron](https://github.com/tootsuite/mastodon/pull/10265), [Gargron](https://github.com/tootsuite/mastodon/pull/10261), [ThibG](https://github.com/tootsuite/mastodon/pull/10333), [Gargron](https://github.com/tootsuite/mastodon/pull/10352), [ThibG](https://github.com/tootsuite/mastodon/pull/10140), [ThibG](https://github.com/tootsuite/mastodon/pull/10142), [ThibG](https://github.com/tootsuite/mastodon/pull/10141), [ThibG](https://github.com/tootsuite/mastodon/pull/10162), [ThibG](https://github.com/tootsuite/mastodon/pull/10161), [ThibG](https://github.com/tootsuite/mastodon/pull/10158), [ThibG](https://github.com/tootsuite/mastodon/pull/10156), [ThibG](https://github.com/tootsuite/mastodon/pull/10160), [Gargron](https://github.com/tootsuite/mastodon/pull/10185), [Gargron](https://github.com/tootsuite/mastodon/pull/10188), [ThibG](https://github.com/tootsuite/mastodon/pull/10195), [ThibG](https://github.com/tootsuite/mastodon/pull/10208), [Gargron](https://github.com/tootsuite/mastodon/pull/10187), [ThibG](https://github.com/tootsuite/mastodon/pull/10214), [ThibG](https://github.com/tootsuite/mastodon/pull/10209)) +- Add follows & followers managing UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10268), [Gargron](https://github.com/tootsuite/mastodon/pull/10308), [Gargron](https://github.com/tootsuite/mastodon/pull/10404), [Gargron](https://github.com/tootsuite/mastodon/pull/10293)) +- Add identity proof integration with Keybase ([Gargron](https://github.com/tootsuite/mastodon/pull/10297), [xgess](https://github.com/tootsuite/mastodon/pull/10375), [Gargron](https://github.com/tootsuite/mastodon/pull/10338), [Gargron](https://github.com/tootsuite/mastodon/pull/10350), [Gargron](https://github.com/tootsuite/mastodon/pull/10414)) +- Add option to overwrite imported data instead of merging ([Gargron](https://github.com/tootsuite/mastodon/pull/9962)) +- Add featured hashtags to profiles ([Gargron](https://github.com/tootsuite/mastodon/pull/9755), [Gargron](https://github.com/tootsuite/mastodon/pull/10167), [Gargron](https://github.com/tootsuite/mastodon/pull/10249), [ThibG](https://github.com/tootsuite/mastodon/pull/10034)) +- Add admission-based registrations mode ([Gargron](https://github.com/tootsuite/mastodon/pull/10250), [ThibG](https://github.com/tootsuite/mastodon/pull/10269), [Gargron](https://github.com/tootsuite/mastodon/pull/10264), [ThibG](https://github.com/tootsuite/mastodon/pull/10321), [Gargron](https://github.com/tootsuite/mastodon/pull/10349), [Gargron](https://github.com/tootsuite/mastodon/pull/10469)) +- Add support for WebP uploads ([acid-chicken](https://github.com/tootsuite/mastodon/pull/9879)) +- Add "copy link" item to status action bars in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/9983)) +- Add list title editing in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/9748)) +- Add a "Block & Report" button to the block confirmation dialog in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10360)) +- Add disappointed elephant when the page crashes in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10275)) +- Add ability to upload multiple files at once in web UI ([tmm576](https://github.com/tootsuite/mastodon/pull/9856)) +- Add indication when you are not allowed to follow an account in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10420), [Gargron](https://github.com/tootsuite/mastodon/pull/10491)) +- Add validations to admin settings to catch common mistakes ([Gargron](https://github.com/tootsuite/mastodon/pull/10348), [ThibG](https://github.com/tootsuite/mastodon/pull/10354)) +- Add `type`, `limit`, `offset`, `min_id`, `max_id`, `account_id` to search API ([Gargron](https://github.com/tootsuite/mastodon/pull/10091)) +- Add a preferences API so apps can share basic behaviours ([Gargron](https://github.com/tootsuite/mastodon/pull/10109)) +- Add `visibility` param to reblog REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/9851), [ThibG](https://github.com/tootsuite/mastodon/pull/10302)) +- Add `allowfullscreen` attribute to OEmbed iframe ([rinsuki](https://github.com/tootsuite/mastodon/pull/10370)) +- Add `blocked_by` relationship to the REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/10373)) +- Add `tootctl statuses remove` to sweep unreferenced statuses ([Gargron](https://github.com/tootsuite/mastodon/pull/10063)) +- Add `tootctl search deploy` to avoid ugly rake task syntax ([Gargron](https://github.com/tootsuite/mastodon/pull/10403)) +- Add `tootctl self-destruct` to shut down server gracefully ([Gargron](https://github.com/tootsuite/mastodon/pull/10367)) +- Add option to hide application used to toot ([ThibG](https://github.com/tootsuite/mastodon/pull/9897), [rinsuki](https://github.com/tootsuite/mastodon/pull/9994), [hinaloe](https://github.com/tootsuite/mastodon/pull/10086)) +- Add `DB_SSLMODE` configuration variable ([sascha-sl](https://github.com/tootsuite/mastodon/pull/10210)) +- Add click-to-copy UI to invites page ([Gargron](https://github.com/tootsuite/mastodon/pull/10259)) +- Add self-replies fetching ([ThibG](https://github.com/tootsuite/mastodon/pull/10106), [ThibG](https://github.com/tootsuite/mastodon/pull/10128), [ThibG](https://github.com/tootsuite/mastodon/pull/10175), [ThibG](https://github.com/tootsuite/mastodon/pull/10201)) +- Add rate limit for media proxy requests ([Gargron](https://github.com/tootsuite/mastodon/pull/10490)) +- Add `tootctl emoji purge` ([Gargron](https://github.com/tootsuite/mastodon/pull/10481)) +- Add `tootctl accounts approve` ([Gargron](https://github.com/tootsuite/mastodon/pull/10480)) +- Add `tootctl accounts reset-relationships` ([noellabo](https://github.com/tootsuite/mastodon/pull/10483)) + +### Changed + +- Change design of landing page ([Gargron](https://github.com/tootsuite/mastodon/pull/10232), [Gargron](https://github.com/tootsuite/mastodon/pull/10260), [ThibG](https://github.com/tootsuite/mastodon/pull/10284), [ThibG](https://github.com/tootsuite/mastodon/pull/10291), [koyuawsmbrtn](https://github.com/tootsuite/mastodon/pull/10356), [Gargron](https://github.com/tootsuite/mastodon/pull/10245)) +- Change design of profile column in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10337), [Aditoo17](https://github.com/tootsuite/mastodon/pull/10387), [ThibG](https://github.com/tootsuite/mastodon/pull/10390), [mayaeh](https://github.com/tootsuite/mastodon/pull/10379), [ThibG](https://github.com/tootsuite/mastodon/pull/10411)) +- Change language detector threshold from 140 characters to 4 words ([Gargron](https://github.com/tootsuite/mastodon/pull/10376)) +- Change language detector to always kick in for non-latin alphabets ([Gargron](https://github.com/tootsuite/mastodon/pull/10276)) +- Change icons of features on admin dashboard ([Gargron](https://github.com/tootsuite/mastodon/pull/10366)) +- Change DNS timeouts from 1s to 5s ([ThibG](https://github.com/tootsuite/mastodon/pull/10238)) +- Change Docker image to use Ubuntu with jemalloc ([Sir-Boops](https://github.com/tootsuite/mastodon/pull/10100), [BenLubar](https://github.com/tootsuite/mastodon/pull/10212)) +- Change public pages to be cacheable by proxies ([BenLubar](https://github.com/tootsuite/mastodon/pull/9059)) +- Change the 410 gone response for suspended accounts to be cacheable by proxies ([ThibG](https://github.com/tootsuite/mastodon/pull/10339)) +- Change web UI to not not empty timeline of blocked users on block ([ThibG](https://github.com/tootsuite/mastodon/pull/10359)) +- Change JSON serializer to remove unused `@context` values ([Gargron](https://github.com/tootsuite/mastodon/pull/10378)) +- Change GIFV file size limit to be the same as for other videos ([rinsuki](https://github.com/tootsuite/mastodon/pull/9924)) +- Change Webpack to not use @babel/preset-env to compile node_modules ([ykzts](https://github.com/tootsuite/mastodon/pull/10289)) +- Change web UI to use new Web Share Target API ([gol-cha](https://github.com/tootsuite/mastodon/pull/9963)) +- Change ActivityPub reports to have persistent URIs ([ThibG](https://github.com/tootsuite/mastodon/pull/10303)) +- Change `tootctl accounts cull --dry-run` to list accounts that would be deleted ([BenLubar](https://github.com/tootsuite/mastodon/pull/10460)) +- Change format of CSV exports of follows and mutes to include extra settings ([ThibG](https://github.com/tootsuite/mastodon/pull/10495), [ThibG](https://github.com/tootsuite/mastodon/pull/10335)) +- Change ActivityPub collections to be cacheable by proxies ([ThibG](https://github.com/tootsuite/mastodon/pull/10467)) +- Change REST API and public profiles to not return follows/followers for users that have blocked you ([Gargron](https://github.com/tootsuite/mastodon/pull/10491)) +- Change the groupings of menu items in settings navigation ([Gargron](https://github.com/tootsuite/mastodon/pull/10533)) + +### Removed + +- Remove zopfli compression to speed up Webpack from 6min to 1min ([nolanlawson](https://github.com/tootsuite/mastodon/pull/10288)) +- Remove stats.json generation to speed up Webpack ([nolanlawson](https://github.com/tootsuite/mastodon/pull/10290)) + +### Fixed + +- Fix public timelines being broken by new toots when they are not mounted in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10131)) +- Fix quick filter settings not being saved when selecting a different filter in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10296)) +- Fix remote interaction dialogs being indexed by search engines ([Gargron](https://github.com/tootsuite/mastodon/pull/10240)) +- Fix maxed-out invites not showing up as expired in UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10274)) +- Fix scrollbar styles on compose textarea ([Gargron](https://github.com/tootsuite/mastodon/pull/10292)) +- Fix timeline merge workers being queued for remote users ([Gargron](https://github.com/tootsuite/mastodon/pull/10355)) +- Fix alternative relay support regression ([Gargron](https://github.com/tootsuite/mastodon/pull/10398)) +- Fix trying to fetch keys of unknown accounts on a self-delete from them ([ThibG](https://github.com/tootsuite/mastodon/pull/10326)) +- Fix CAS `:service_validate_url` option ([enewhuis](https://github.com/tootsuite/mastodon/pull/10328)) +- Fix race conditions when creating backups ([ThibG](https://github.com/tootsuite/mastodon/pull/10234)) +- Fix whitespace not being stripped out of username before validation ([aurelien-reeves](https://github.com/tootsuite/mastodon/pull/10239)) +- Fix n+1 query when deleting status ([Gargron](https://github.com/tootsuite/mastodon/pull/10247)) +- Fix exiting follows not being rejected when suspending a remote account ([ThibG](https://github.com/tootsuite/mastodon/pull/10230)) +- Fix the underlying button element in a disabled icon button not being disabled ([ThibG](https://github.com/tootsuite/mastodon/pull/10194)) +- Fix race condition when streaming out deleted statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10280)) +- Fix performance of admin federation UI by caching account counts ([Gargron](https://github.com/tootsuite/mastodon/pull/10374)) +- Fix JS error on pages that don't define a CSRF token ([hinaloe](https://github.com/tootsuite/mastodon/pull/10383)) +- Fix `tootctl accounts cull` sometimes removing accounts that are temporarily unreachable ([BenLubar](https://github.com/tootsuite/mastodon/pull/10460)) + ## [2.7.4] - 2019-03-05 ### Fixed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 65366be5f..f944309d5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,9 +18,7 @@ Bug reports and feature suggestions can be submitted to [GitHub Issues](https:// ## Translations -You can submit translations via [Weblate](https://weblate.joinmastodon.org/). They are periodically merged into the codebase. - -[![Mastodon translation statistics by language](https://weblate.joinmastodon.org/widgets/mastodon/-/multi-auto.svg)](https://weblate.joinmastodon.org/) +You can submit translations via pull request. ## Pull requests diff --git a/Dockerfile b/Dockerfile index 6373172fc..3acbc9d4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,6 @@ SHELL ["bash", "-c"] ENV NODE_VER="8.15.0" RUN echo "Etc/UTC" > /etc/localtime && \ apt update && \ - apt -y dist-upgrade && \ apt -y install wget make gcc g++ python && \ cd ~ && \ wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER.tar.gz && \ @@ -80,13 +79,12 @@ ARG GID=991 RUN apt update && \ echo "Etc/UTC" > /etc/localtime && \ ln -s /opt/jemalloc/lib/* /usr/lib/ && \ - apt -y dist-upgrade && \ apt install -y 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 -# Install masto runtime deps +# Install mastodon runtime deps RUN apt -y --no-install-recommends install \ libssl1.1 libpq5 imagemagick ffmpeg \ libicu60 libprotobuf10 libidn11 libyaml-0-2 \ @@ -95,7 +93,7 @@ RUN apt -y --no-install-recommends install \ ln -s /opt/mastodon /mastodon && \ gem install bundler && \ rm -rf /var/cache && \ - rm -rf /var/lib/apt + rm -rf /var/lib/apt/lists/* # Add tini ENV TINI_VERSION="0.18.0" @@ -104,11 +102,11 @@ ADD https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini /tin RUN echo "$TINI_SUM tini" | sha256sum -c - RUN chmod +x /tini -# Copy over masto source, and dependencies from building, and set permissions +# 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 -# Run masto services in prod mode +# Run mastodon services in prod mode ENV RAILS_ENV="production" ENV NODE_ENV="production" diff --git a/Gemfile b/Gemfile index 325ea2c62..4fd981f75 100644 --- a/Gemfile +++ b/Gemfile @@ -15,12 +15,13 @@ gem 'makara', '~> 0.4' gem 'pghero', '~> 2.2' gem 'dotenv-rails', '~> 2.7' -gem 'aws-sdk-s3', '~> 1.36', require: false +gem 'aws-sdk-s3', '~> 1.41', 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' gem 'addressable', '~> 2.6' @@ -29,7 +30,7 @@ gem 'browser' gem 'charlock_holmes', '~> 0.7.6' gem 'iso-639' gem 'chewy', '~> 5.0' -gem 'cld3', '~> 3.2.3' +gem 'cld3', '~> 3.2.4' gem 'devise', '~> 4.6' gem 'devise-two-factor', '~> 3.0' @@ -42,7 +43,7 @@ gem 'omniauth-cas', '~> 1.1' gem 'omniauth-saml', '~> 1.10' gem 'omniauth', '~> 1.9' -gem 'doorkeeper', '~> 5.0' +gem 'doorkeeper', '~> 5.1' gem 'fast_blank', '~> 1.0' gem 'fastimage' gem 'goldfinger', '~> 2.1' @@ -52,7 +53,7 @@ gem 'htmlentities', '~> 4.3' gem 'http', '~> 3.3' gem 'http_accept_language', '~> 2.1' gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2' -gem 'httplog', '~> 1.2' +gem 'httplog', '~> 1.3' gem 'idn-ruby', require: 'idn' gem 'kaminari', '~> 1.1' gem 'link_header', '~> 0.0' @@ -65,7 +66,7 @@ gem 'ox', '~> 2.10' gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c' gem 'pundit', '~> 2.0' gem 'premailer-rails' -gem 'rack-attack', '~> 5.4' +gem 'rack-attack', '~> 6.0' gem 'rack-cors', '~> 1.0', require: 'rack/cors' gem 'rails-i18n', '~> 5.1' gem 'rails-settings-cached', '~> 0.6' @@ -81,9 +82,9 @@ gem 'simple-navigation', '~> 4.0' gem 'simple_form', '~> 4.1' gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'stoplight', '~> 2.1.3' -gem 'strong_migrations', '~> 0.3' +gem 'strong_migrations', '~> 0.4' gem 'tty-command', '~> 0.8', require: false -gem 'tty-prompt', '~> 0.18', require: false +gem 'tty-prompt', '~> 0.19', require: false gem 'twitter-text', '~> 1.14' gem 'tzinfo-data', '~> 1.2019' gem 'webpacker', '~> 4.0' @@ -95,7 +96,7 @@ gem 'rdf-normalize', '~> 0.3' group :development, :test do gem 'fabrication', '~> 2.20' - gem 'fuubar', '~> 2.3' + gem 'fuubar', '~> 2.4' gem 'i18n-tasks', '~> 0.9', require: false gem 'pry-byebug', '~> 3.7' gem 'pry-rails', '~> 0.3' @@ -107,7 +108,7 @@ group :production, :test do end group :test do - gem 'capybara', '~> 3.15' + gem 'capybara', '~> 3.22' gem 'climate_control', '~> 0.2' gem 'faker', '~> 1.9' gem 'microformats', '~> 4.1' @@ -115,7 +116,7 @@ group :test do gem 'rspec-sidekiq', '~> 3.0' gem 'simplecov', '~> 0.16', require: false gem 'webmock', '~> 3.5' - gem 'parallel_tests', '~> 2.28' + gem 'parallel_tests', '~> 2.29' end group :development do @@ -123,14 +124,14 @@ group :development do gem 'annotate', '~> 2.7' gem 'better_errors', '~> 2.5' gem 'binding_of_caller', '~> 0.7' - gem 'bullet', '~> 5.9' + gem 'bullet', '~> 6.0' gem 'letter_opener', '~> 1.7' gem 'letter_opener_web', '~> 1.3' gem 'memory_profiler' - gem 'rubocop', '~> 0.66', require: false + gem 'rubocop', '~> 0.71', require: false + gem 'rubocop-rails', '~> 2.0', require: false gem 'brakeman', '~> 4.5', require: false gem 'bundler-audit', '~> 0.6', require: false - gem 'scss_lint', '~> 0.57', require: false gem 'capistrano', '~> 3.11' gem 'capistrano-rails', '~> 1.4' @@ -142,7 +143,7 @@ group :development do end group :production do - gem 'lograge', '~> 0.10' + gem 'lograge', '~> 0.11' gem 'redis-rails', '~> 5.0' end diff --git a/Gemfile.lock b/Gemfile.lock index 5ef611931..d8a68d8c9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,8 +66,8 @@ GEM public_suffix (>= 2.0.2, < 4.0) airbrussh (1.3.0) sshkit (>= 1.6.1, != 1.7.0) - annotate (2.7.4) - activerecord (>= 3.2, < 6.0) + annotate (2.7.5) + activerecord (>= 3.2, < 7.0) rake (>= 10.4, < 13.0) arel (9.0.0) ast (2.4.0) @@ -75,20 +75,20 @@ GEM encryptor (~> 3.0.0) av (0.9.0) cocaine (~> 0.5.3) - aws-eventstream (1.0.2) - aws-partitions (1.147.0) - aws-sdk-core (3.48.3) + aws-eventstream (1.0.3) + aws-partitions (1.169.0) + aws-sdk-core (3.54.0) aws-eventstream (~> 1.0, >= 1.0.2) aws-partitions (~> 1.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.16.0) - aws-sdk-core (~> 3, >= 3.48.2) + aws-sdk-kms (1.21.0) + aws-sdk-core (~> 3, >= 3.53.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.36.0) - aws-sdk-core (~> 3, >= 3.48.2) + aws-sdk-s3 (1.41.0) + aws-sdk-core (~> 3, >= 3.53.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.0) + aws-sigv4 (~> 1.1) aws-sigv4 (1.1.0) aws-eventstream (~> 1.0, >= 1.0.2) bcrypt (3.1.12) @@ -99,12 +99,14 @@ GEM rack (>= 0.9.0) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) - bootsnap (1.4.2) + blurhash (0.1.3) + ffi (~> 1.10.0) + bootsnap (1.4.4) msgpack (~> 1.0) - brakeman (4.5.0) + brakeman (4.5.1) browser (2.5.3) builder (3.2.3) - bullet (5.9.0) + bullet (6.0.0) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) bundler-audit (0.6.1) @@ -127,13 +129,13 @@ GEM sshkit (~> 1.3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (3.15.0) + capybara (3.22.0) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) - regexp_parser (~> 1.2) + regexp_parser (~> 1.5) xpath (~> 3.2) case_transform (0.2) activesupport @@ -143,8 +145,8 @@ GEM elasticsearch (>= 2.0.0) elasticsearch-dsl chunky_png (1.3.10) - cld3 (3.2.3) - ffi (>= 1.1.0, < 1.10.0) + cld3 (3.2.4) + ffi (>= 1.1.0, < 1.11.0) climate_control (0.2.0) cocaine (0.5.8) climate_control (>= 0.0.3, < 1.0) @@ -184,8 +186,8 @@ GEM docile (1.3.0) domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) - doorkeeper (5.0.2) - railties (>= 4.2) + doorkeeper (5.1.0) + railties (>= 5) dotenv (2.7.2) dotenv-rails (2.7.2) dotenv (= 2.7.2) @@ -205,14 +207,14 @@ GEM et-orbi (1.1.6) tzinfo excon (0.62.0) - fabrication (2.20.1) + fabrication (2.20.2) faker (1.9.3) i18n (>= 0.7) faraday (0.15.0) multipart-post (>= 1.2, < 3) fast_blank (1.0.0) fastimage (2.1.5) - ffi (1.9.25) + ffi (1.10.0) fog-core (2.1.0) builder excon (~> 0.58) @@ -229,7 +231,7 @@ GEM fugit (1.1.6) et-orbi (~> 1.1, >= 1.1.6) raabro (~> 1.1) - fuubar (2.3.2) + fuubar (2.4.0) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) get_process_mem (0.2.3) @@ -240,11 +242,11 @@ GEM http (~> 3.0) nokogiri (~> 1.8) oj (~> 3.0) - hamlit (2.9.2) + hamlit (2.9.3) temple (>= 0.8.0) thor tilt - hamlit-rails (0.2.2) + hamlit-rails (0.2.3) actionpack (>= 4.0.1) activesupport (>= 4.0.1) hamlit (>= 1.2.0) @@ -254,7 +256,7 @@ GEM hashdiff (0.3.7) hashie (3.6.0) heapy (0.1.4) - highline (2.0.0) + highline (2.0.1) hiredis (0.6.3) hkdf (0.3.0) htmlentities (4.3.4) @@ -267,12 +269,12 @@ GEM domain_name (~> 0.5) http-form_data (2.1.1) http_accept_language (2.1.1) - httplog (1.2.2) + httplog (1.3.0) rack (>= 1.0) rainbow (>= 2.0.0) i18n (1.6.0) concurrent-ruby (~> 1.0) - i18n-tasks (0.9.28) + i18n-tasks (0.9.29) activesupport (>= 4.0.2) ast (>= 2.1.0) erubi @@ -318,7 +320,7 @@ GEM letter_opener (~> 1.0) railties (>= 3.2) link_header (0.0.8) - lograge (0.10.0) + lograge (0.11.1) actionpack (>= 4) activesupport (>= 4) railties (>= 4) @@ -346,16 +348,16 @@ GEM mini_mime (1.0.1) mini_portile2 (2.4.0) minitest (5.11.3) - msgpack (1.2.9) + msgpack (1.2.10) multi_json (1.13.1) multipart-post (2.0.0) - necromancer (0.4.0) + necromancer (0.5.0) net-ldap (0.16.1) net-scp (1.2.1) net-ssh (>= 2.6.5) net-ssh (5.0.2) nio4r (2.3.1) - nokogiri (1.10.2) + nokogiri (1.10.3) mini_portile2 (~> 2.4.0) nokogumbo (2.0.0) nokogiri (~> 1.8, >= 1.8.4) @@ -364,7 +366,7 @@ GEM concurrent-ruby (~> 1.0, >= 1.0.2) sidekiq (>= 3.5) statsd-ruby (~> 1.4, >= 1.4.0) - oj (3.7.11) + oj (3.7.12) omniauth (1.9.0) hashie (>= 3.4.6, < 3.7.0) rack (>= 1.6.2, < 3) @@ -380,7 +382,7 @@ GEM addressable (~> 2.5) http (~> 3.0) nokogiri (~> 1.8) - ox (2.10.0) + ox (2.10.1) paperclip (6.0.0) activemodel (>= 4.2.0) activesupport (>= 4.2.0) @@ -390,10 +392,10 @@ GEM paperclip-av-transcoder (0.6.4) av (~> 0.9.0) paperclip (>= 2.5.2) - parallel (1.14.0) - parallel_tests (2.28.0) + parallel (1.17.0) + parallel_tests (2.29.0) parallel - parser (2.6.0.0) + parser (2.6.3.0) ast (~> 2.4.0) pastel (0.7.2) equatable (~> 0.5.0) @@ -418,14 +420,13 @@ GEM pry (~> 0.10) pry-rails (0.3.9) pry (>= 0.10.4) - psych (3.1.0) - public_suffix (3.0.3) + public_suffix (3.1.0) puma (3.12.1) pundit (2.0.1) activesupport (>= 3.0.0) raabro (1.1.6) - rack (2.0.6) - rack-attack (5.4.2) + rack (2.0.7) + rack-attack (6.0.0) rack (>= 1.0, < 3) rack-cors (1.0.3) rack-protection (2.0.5) @@ -469,15 +470,12 @@ GEM thor (>= 0.19.0, < 2.0) rainbow (3.0.0) rake (12.3.2) - rb-fsevent (0.10.3) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) rdf (3.0.9) hamster (~> 3.0) link_header (~> 0.0, >= 0.0.8) rdf-normalize (0.3.3) rdf (>= 2.2, < 4.0) - redis (4.1.0) + redis (4.1.2) redis-actionpack (5.0.2) actionpack (>= 4.0, < 6) redis-rack (>= 1, < 3) @@ -496,7 +494,7 @@ GEM redis-store (>= 1.2, < 2) redis-store (1.5.0) redis (>= 2.2, < 5) - regexp_parser (1.3.0) + regexp_parser (1.5.1) request_store (1.4.1) rack (>= 1.4) responders (2.4.1) @@ -526,15 +524,17 @@ GEM rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) rspec-support (3.8.0) - rubocop (0.66.0) + rubocop (0.71.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) - parser (>= 2.5, != 2.5.1.1) - psych (>= 3.1.0) + parser (>= 2.6) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 1.6) - ruby-progressbar (1.10.0) + unicode-display_width (>= 1.4.0, < 1.7) + rubocop-rails (2.0.0) + rack (>= 2.0) + rubocop (>= 0.70.0) + ruby-progressbar (1.10.1) ruby-saml (1.9.0) nokogiri (>= 1.5.10) rufus-scheduler (3.5.2) @@ -544,15 +544,7 @@ GEM crass (~> 1.0.2) nokogiri (>= 1.8.0) nokogumbo (~> 2.0) - sass (3.6.0) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - scss_lint (0.57.1) - rake (>= 0.9, < 13) - sass (~> 3.5, >= 3.5.5) - sidekiq (5.2.5) + sidekiq (5.2.7) connection_pool (~> 2.2, >= 2.2.2) rack (>= 1.5.0) rack-protection (>= 1.5.0) @@ -564,7 +556,7 @@ GEM rufus-scheduler (~> 3.2) sidekiq (>= 3) tilt (>= 1.4.0) - sidekiq-unique-jobs (6.0.12) + sidekiq-unique-jobs (6.0.13) concurrent-ruby (~> 1.0, >= 1.0.5) sidekiq (>= 4.0, < 7.0) thor (~> 0) @@ -593,9 +585,9 @@ GEM stoplight (2.1.3) streamio-ffmpeg (3.0.2) multi_json (~> 1.8) - strong_migrations (0.3.1) - activerecord (>= 3.2.0) - temple (0.8.0) + strong_migrations (0.4.0) + activerecord (>= 5) + temple (0.8.1) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) terrapin (0.6.0) @@ -603,22 +595,19 @@ GEM thor (0.20.3) thread_safe (0.3.6) tilt (2.0.9) - timers (4.2.0) tty-color (0.4.3) tty-command (0.8.2) pastel (~> 0.7.0) - tty-cursor (0.6.0) - tty-prompt (0.18.1) - necromancer (~> 0.4.0) + tty-cursor (0.7.0) + tty-prompt (0.19.0) + necromancer (~> 0.5.0) pastel (~> 0.7.0) - timers (~> 4.0) - tty-cursor (~> 0.6.0) - tty-reader (~> 0.5.0) - tty-reader (0.5.0) - tty-cursor (~> 0.6.0) - tty-screen (~> 0.6.4) + tty-reader (~> 0.6.0) + tty-reader (0.6.0) + tty-cursor (~> 0.7) + tty-screen (~> 0.7) wisper (~> 2.0.0) - tty-screen (0.6.5) + tty-screen (0.7.0) twitter-text (1.14.7) unf (~> 0.1.0) tzinfo (1.2.5) @@ -628,7 +617,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.5) - unicode-display_width (1.5.0) + unicode-display_width (1.6.0) uniform_notifier (1.12.1) warden (1.2.8) rack (>= 2.0.6) @@ -636,11 +625,11 @@ GEM addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff - webpacker (4.0.2) + webpacker (4.0.7) activesupport (>= 4.2) rack-proxy (>= 0.6.1) railties (>= 4.2) - webpush (0.3.7) + webpush (0.3.8) hkdf (~> 0.2) jwt (~> 2.0) websocket-driver (0.7.0) @@ -658,29 +647,30 @@ DEPENDENCIES active_record_query_trace (~> 1.6) addressable (~> 2.6) annotate (~> 2.7) - aws-sdk-s3 (~> 1.36) + aws-sdk-s3 (~> 1.41) better_errors (~> 2.5) binding_of_caller (~> 0.7) + blurhash (~> 0.1) bootsnap (~> 1.4) brakeman (~> 4.5) browser - bullet (~> 5.9) + bullet (~> 6.0) bundler-audit (~> 0.6) capistrano (~> 3.11) capistrano-rails (~> 1.4) capistrano-rbenv (~> 2.1) capistrano-yarn (~> 2.0) - capybara (~> 3.15) + capybara (~> 3.22) charlock_holmes (~> 0.7.6) chewy (~> 5.0) - cld3 (~> 3.2.3) + cld3 (~> 3.2.4) climate_control (~> 0.2) concurrent-ruby derailed_benchmarks devise (~> 4.6) devise-two-factor (~> 3.0) devise_pam_authenticatable2 (~> 9.2) - doorkeeper (~> 5.0) + doorkeeper (~> 5.1) dotenv-rails (~> 2.7) fabrication (~> 2.20) faker (~> 1.9) @@ -688,7 +678,7 @@ DEPENDENCIES fastimage fog-core (<= 2.1.0) fog-openstack (~> 0.3) - fuubar (~> 2.3) + fuubar (~> 2.4) goldfinger (~> 2.1) hamlit-rails (~> 0.2) hiredis (~> 0.6) @@ -696,7 +686,7 @@ DEPENDENCIES http (~> 3.3) http_accept_language (~> 2.1) http_parser.rb (~> 0.6)! - httplog (~> 1.2) + httplog (~> 1.3) i18n-tasks (~> 0.9) idn-ruby iso-639 @@ -706,7 +696,7 @@ DEPENDENCIES letter_opener (~> 1.7) letter_opener_web (~> 1.3) link_header (~> 0.0) - lograge (~> 0.10) + lograge (~> 0.11) makara (~> 0.4) mario-redis-lock (~> 1.2) memory_profiler @@ -723,7 +713,7 @@ DEPENDENCIES ox (~> 2.10) paperclip (~> 6.0) paperclip-av-transcoder (~> 0.6) - parallel_tests (~> 2.28) + parallel_tests (~> 2.29) pg (~> 1.1) pghero (~> 2.2) pkg-config (~> 1.3) @@ -734,7 +724,7 @@ DEPENDENCIES pry-rails (~> 0.3) puma (~> 3.12) pundit (~> 2.0) - rack-attack (~> 5.4) + rack-attack (~> 6.0) rack-cors (~> 1.0) rails (~> 5.2.3) rails-controller-testing (~> 1.0) @@ -747,9 +737,9 @@ DEPENDENCIES rqrcode (~> 0.10) rspec-rails (~> 3.8) rspec-sidekiq (~> 3.0) - rubocop (~> 0.66) + rubocop (~> 0.71) + rubocop-rails (~> 2.0) sanitize (~> 5.0) - scss_lint (~> 0.57) sidekiq (~> 5.2) sidekiq-bulk (~> 0.2.0) sidekiq-scheduler (~> 3.0) @@ -761,10 +751,10 @@ DEPENDENCIES stackprof stoplight (~> 2.1.3) streamio-ffmpeg (~> 3.0) - strong_migrations (~> 0.3) + strong_migrations (~> 0.4) thor (~> 0.20) tty-command (~> 0.8) - tty-prompt (~> 0.18) + tty-prompt (~> 0.19) twitter-text (~> 1.14) tzinfo-data (~> 1.2019) webmock (~> 3.5) diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index 67bb2c87f..52a51fd62 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -16,7 +16,10 @@ class AboutController < ApplicationController private def new_user - User.new.tap(&:build_account) + User.new.tap do |user| + user.build_account + user.build_invite_request + end end helper_method :new_user diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index abc68d2a4..73a4b1859 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -46,8 +46,6 @@ class AccountsController < ApplicationController end format.json do - mark_cacheable! - render_cached_json(['activitypub', 'actor', @account], content_type: 'application/activity+json') do ActiveModelSerializers::SerializableResource.new(@account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter) end diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb index 995da9c55..012c3c538 100644 --- a/app/controllers/activitypub/collections_controller.rb +++ b/app/controllers/activitypub/collections_controller.rb @@ -6,13 +6,17 @@ class ActivityPub::CollectionsController < Api::BaseController before_action :set_account before_action :set_size before_action :set_statuses + before_action :set_cache_headers def show - render json: collection_presenter, - serializer: ActivityPub::CollectionSerializer, - adapter: ActivityPub::Adapter, - content_type: 'application/activity+json', - skip_activities: true + render_cached_json(['activitypub', 'collection', @account, params[:id]], content_type: 'application/activity+json') do + ActiveModelSerializers::SerializableResource.new( + collection_presenter, + serializer: ActivityPub::CollectionSerializer, + adapter: ActivityPub::Adapter, + skip_activities: true + ) + end end private diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb index 1501b914e..a0b7532c2 100644 --- a/app/controllers/activitypub/inboxes_controller.rb +++ b/app/controllers/activitypub/inboxes_controller.rb @@ -32,7 +32,10 @@ class ActivityPub::InboxesController < Api::BaseController end def body - @body ||= request.body.read.force_encoding('UTF-8') + return @body if defined?(@body) + @body = request.body.read.force_encoding('UTF-8') + request.body.rewind if request.body.respond_to?(:rewind) + @body end def upgrade_account diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index be4289b21..5147afbf7 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -7,8 +7,11 @@ class ActivityPub::OutboxesController < Api::BaseController before_action :set_account before_action :set_statuses + before_action :set_cache_headers def show + expires_in 1.minute, public: true unless page_requested? + render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' end diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index e7795e95c..b0d45ce47 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -48,13 +48,13 @@ module Admin def approve authorize @account.user, :approve? @account.user.approve! - redirect_to admin_accounts_path(pending: '1') + redirect_to admin_pending_accounts_path end def reject authorize @account.user, :reject? SuspendAccountService.new.call(@account, including_user: true, destroy: true, skip_distribution: true) - redirect_to admin_accounts_path(pending: '1') + redirect_to admin_pending_accounts_path end def unsilence diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index 5f307ddee..71597763b 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -13,13 +13,25 @@ module Admin authorize :domain_block, :create? @domain_block = DomainBlock.new(resource_params) + existing_domain_block = resource_params[:domain].present? ? DomainBlock.find_by(domain: resource_params[:domain]) : nil - if @domain_block.save - DomainBlockWorker.perform_async(@domain_block.id) - log_action :create, @domain_block - redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg') - else + if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block) + @domain_block.save + flash[: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 render :new + else + if existing_domain_block.present? + @domain_block = existing_domain_block + @domain_block.update(resource_params) + end + if @domain_block.save + DomainBlockWorker.perform_async(@domain_block.id) + log_action :create, @domain_block + redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg') + else + render :new + end end end @@ -29,7 +41,7 @@ module Admin def destroy authorize @domain_block, :destroy? - UnblockDomainService.new.call(@domain_block, retroactive_unblock?) + UnblockDomainService.new.call(@domain_block) log_action :destroy, @domain_block redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.destroyed_msg') end @@ -41,11 +53,7 @@ module Admin end def resource_params - params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :retroactive) - end - - def retroactive_unblock? - ActiveRecord::Type.lookup(:boolean).cast(resource_params[:retroactive]) + params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports) end end end diff --git a/app/controllers/admin/pending_accounts_controller.rb b/app/controllers/admin/pending_accounts_controller.rb new file mode 100644 index 000000000..b62a9bc84 --- /dev/null +++ b/app/controllers/admin/pending_accounts_controller.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Admin + class PendingAccountsController < BaseController + before_action :set_accounts, only: :index + + def index + @form = Form::AccountBatch.new + end + + def batch + @form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button)) + @form.save + rescue ActionController::ParameterMissing + flash[:alert] = I18n.t('admin.accounts.no_account_selected') + ensure + redirect_to admin_pending_accounts_path(current_params) + end + + def approve_all + Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.pluck(:account_id), action: 'approve').save + redirect_to admin_pending_accounts_path(current_params) + end + + def reject_all + Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.pluck(:account_id), action: 'reject').save + redirect_to admin_pending_accounts_path(current_params) + end + + private + + def set_accounts + @accounts = Account.joins(:user).merge(User.pending.recent).includes(user: :invite_request).page(params[:page]) + end + + def form_account_batch_params + params.require(:form_account_batch).permit(:action, account_ids: []) + end + + def action_from_button + if params[:approve] + 'approve' + elsif params[:reject] + 'reject' + end + end + + def current_params + params.slice(:page).permit(:page) + end + end +end diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 3a92ee4e4..eca558f42 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -9,6 +9,8 @@ class Api::BaseController < ApplicationController skip_before_action :store_current_location skip_before_action :check_user_permissions + before_action :set_cache_headers + protect_from_forgery with: :null_session rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e| @@ -88,4 +90,8 @@ class Api::BaseController < ApplicationController def authorize_if_got_token!(*scopes) doorkeeper_authorize!(*scopes) if doorkeeper_token end + + def set_cache_headers + response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' + end end diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb index ec15debb0..2dabb8398 100644 --- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb @@ -19,11 +19,15 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController end def load_accounts - return [] if @account.user_hides_network? && current_account.id != @account.id + return [] if hide_results? default_accounts.merge(paginated_follows).to_a end + def hide_results? + (@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account)) + end + def default_accounts Account.includes(:active_relationships, :account_stat).references(:active_relationships) end diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb index f3e112f2c..44e89804b 100644 --- a/app/controllers/api/v1/accounts/following_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb @@ -19,11 +19,15 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController end def load_accounts - return [] if @account.user_hides_network? && current_account.id != @account.id + return [] if hide_results? default_accounts.merge(paginated_follows).to_a end + def hide_results? + (@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account)) + end + def default_accounts Account.includes(:passive_relationships, :account_stat).references(:passive_relationships) end diff --git a/app/controllers/api/v1/accounts/identity_proofs_controller.rb b/app/controllers/api/v1/accounts/identity_proofs_controller.rb new file mode 100644 index 000000000..bea51ae11 --- /dev/null +++ b/app/controllers/api/v1/accounts/identity_proofs_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class Api::V1::Accounts::IdentityProofsController < Api::BaseController + before_action :require_user! + before_action :set_account + + respond_to :json + + def index + @proofs = @account.identity_proofs.active + render json: @proofs, each_serializer: REST::IdentityProofSerializer + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end +end diff --git a/app/controllers/api/v1/custom_emojis_controller.rb b/app/controllers/api/v1/custom_emojis_controller.rb index 7bac27da4..1bb19a09d 100644 --- a/app/controllers/api/v1/custom_emojis_controller.rb +++ b/app/controllers/api/v1/custom_emojis_controller.rb @@ -3,6 +3,8 @@ class Api::V1::CustomEmojisController < Api::BaseController respond_to :json + skip_before_action :set_cache_headers + def index render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer) diff --git a/app/controllers/api/v1/instances/activity_controller.rb b/app/controllers/api/v1/instances/activity_controller.rb index e14e0aee8..09edfe365 100644 --- a/app/controllers/api/v1/instances/activity_controller.rb +++ b/app/controllers/api/v1/instances/activity_controller.rb @@ -2,6 +2,7 @@ class Api::V1::Instances::ActivityController < Api::BaseController before_action :require_enabled_api! + skip_before_action :set_cache_headers respond_to :json diff --git a/app/controllers/api/v1/instances/peers_controller.rb b/app/controllers/api/v1/instances/peers_controller.rb index 2070c487d..a8891d126 100644 --- a/app/controllers/api/v1/instances/peers_controller.rb +++ b/app/controllers/api/v1/instances/peers_controller.rb @@ -2,6 +2,7 @@ class Api::V1::Instances::PeersController < Api::BaseController before_action :require_enabled_api! + skip_before_action :set_cache_headers respond_to :json diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb index 5686e8d7c..8c83a1801 100644 --- a/app/controllers/api/v1/instances_controller.rb +++ b/app/controllers/api/v1/instances_controller.rb @@ -2,6 +2,7 @@ class Api::V1::InstancesController < Api::BaseController respond_to :json + skip_before_action :set_cache_headers def show render_cached_json('api:v1:instances', expires_in: 5.minutes) do diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index e2dec62af..bf3002e79 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -44,7 +44,7 @@ class Api::V1::NotificationsController < Api::BaseController end def browserable_account_notifications - current_account.notifications.browserable(exclude_types) + current_account.notifications.browserable(exclude_types, from_account) end def target_statuses_from_notifications @@ -81,6 +81,10 @@ class Api::V1::NotificationsController < Api::BaseController val end + def from_account + params[:account_id] + end + def pagination_params(core_params) params.slice(:limit, :exclude_types).permit(:limit, exclude_types: []).merge(core_params) end diff --git a/app/controllers/api/v1/polls_controller.rb b/app/controllers/api/v1/polls_controller.rb index 4f4a6858d..031e6d42d 100644 --- a/app/controllers/api/v1/polls_controller.rb +++ b/app/controllers/api/v1/polls_controller.rb @@ -1,13 +1,28 @@ # frozen_string_literal: true class Api::V1::PollsController < Api::BaseController + include Authorization + before_action -> { authorize_if_got_token! :read, :'read:statuses' }, only: :show + before_action :set_poll + before_action :refresh_poll respond_to :json def show - @poll = Poll.attached.find(params[:id]) - ActivityPub::FetchRemotePollService.new.call(@poll, current_account) if user_signed_in? && @poll.possibly_stale? render json: @poll, serializer: REST::PollSerializer, include_results: true end + + private + + def set_poll + @poll = Poll.attached.find(params[:id]) + authorize @poll.status, :show? + rescue Mastodon::NotPermittedError + raise ActiveRecord::RecordNotFound + end + + def refresh_poll + ActivityPub::FetchRemotePollService.new.call(@poll, current_account) if user_signed_in? && @poll.possibly_stale? + end end diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb index 1a19bd0ef..1b658f870 100644 --- a/app/controllers/api/v1/push/subscriptions_controller.rb +++ b/app/controllers/api/v1/push/subscriptions_controller.rb @@ -51,6 +51,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController def data_params return {} if params[:data].blank? - params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention]) + params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention, :poll]) end end diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index f9506971a..b0e134554 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -65,7 +65,7 @@ class Api::V1::StatusesController < Api::BaseController RemovalWorker.perform_async(@status.id) - render_empty + render json: @status, serializer: REST::StatusSerializer, source_requested: true end private diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb index fe8e42580..d8153e082 100644 --- a/app/controllers/api/web/push_subscriptions_controller.rb +++ b/app/controllers/api/web/push_subscriptions_controller.rb @@ -22,6 +22,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController favourite: alerts_enabled, reblog: alerts_enabled, mention: alerts_enabled, + poll: alerts_enabled, }, } @@ -57,6 +58,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController end def data_params - @data_params ||= params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention]) + @data_params ||= params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention, :poll]) end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 990aff857..9274d85a9 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -152,11 +152,6 @@ class ApplicationController < ActionController::Base end def mark_cacheable! - skip_session! expires_in 0, public: true end - - def skip_session! - request.session_options[:skip] = true - end end diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index 16a3ec67a..83797cf1f 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -10,6 +10,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController before_action :set_instance_presenter, only: [:new, :create, :update] before_action :set_body_classes, only: [:new, :create, :edit, :update] + def new + super(&:build_invite_request) + end + def destroy not_found end @@ -24,17 +28,17 @@ class Auth::RegistrationsController < Devise::RegistrationsController def build_resource(hash = nil) super(hash) - resource.locale = I18n.locale - resource.invite_code = params[:invite_code] if resource.invite_code.blank? - resource.agreement = true + resource.locale = I18n.locale + resource.invite_code = params[:invite_code] if resource.invite_code.blank? + resource.agreement = true + resource.current_sign_in_ip = request.remote_ip - resource.current_sign_in_ip = request.remote_ip if resource.current_sign_in_ip.nil? resource.build_account if resource.account.nil? end def configure_sign_up_params devise_parameter_sanitizer.permit(:sign_up) do |u| - u.permit({ account_attributes: [:username] }, :email, :password, :password_confirmation, :invite_code) + u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code) end end @@ -87,7 +91,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController end def set_invite - @invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil + invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil + @invite = invite&.valid_for_use? ? invite : nil end def determine_layout diff --git a/app/controllers/concerns/account_controller_concern.rb b/app/controllers/concerns/account_controller_concern.rb index 4f28941ae..1c422096c 100644 --- a/app/controllers/concerns/account_controller_concern.rb +++ b/app/controllers/concerns/account_controller_concern.rb @@ -70,7 +70,6 @@ module AccountControllerConcern def check_account_suspension if @account.suspended? - skip_session! expires_in(3.minutes, public: true) gone end diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index 91566c4fa..90a57197c 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -43,13 +43,7 @@ module SignatureVerification return end - account_stoplight = Stoplight("source:#{request.ip}") { account_from_key_id(signature_params['keyId']) } - .with_fallback { nil } - .with_threshold(1) - .with_cool_off_time(5.minutes.seconds) - .with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) } - - account = account_stoplight.run + account = account_from_key_id(signature_params['keyId']) if account.nil? @signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}" @@ -62,13 +56,7 @@ module SignatureVerification return account unless verify_signature(account, signature, compare_signed_string).nil? - account_stoplight = Stoplight("source:#{request.ip}") { account.possibly_stale? ? account.refresh! : account_refresh_key(account) } - .with_fallback { nil } - .with_threshold(1) - .with_cool_off_time(5.minutes.seconds) - .with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) } - - account = account_stoplight.run + account = stoplight_wrap_request { account.possibly_stale? ? account.refresh! : account_refresh_key(account) } if account.nil? @signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}" @@ -136,14 +124,23 @@ module SignatureVerification def account_from_key_id(key_id) if key_id.start_with?('acct:') - ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, '')) + stoplight_wrap_request { ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, '')) } elsif !ActivityPub::TagManager.instance.local_uri?(key_id) account = ActivityPub::TagManager.instance.uri_to_resource(key_id, Account) - account ||= ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false) + account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false) } account end end + def stoplight_wrap_request(&block) + Stoplight("source:#{request.remote_ip}", &block) + .with_fallback { nil } + .with_threshold(1) + .with_cool_off_time(5.minutes.seconds) + .with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) } + .run + end + def account_refresh_key(account) return if account.local? || !account.activitypub? ActivityPub::FetchRemoteAccountService.new.call(account.uri, only_key: true) diff --git a/app/controllers/custom_css_controller.rb b/app/controllers/custom_css_controller.rb index 31e501609..be768c089 100644 --- a/app/controllers/custom_css_controller.rb +++ b/app/controllers/custom_css_controller.rb @@ -4,7 +4,6 @@ class CustomCssController < ApplicationController before_action :set_cache_headers def show - skip_session! render plain: Setting.custom_css || '', content_type: 'text/css' end end diff --git a/app/controllers/emojis_controller.rb b/app/controllers/emojis_controller.rb index 5d306e600..3feb08132 100644 --- a/app/controllers/emojis_controller.rb +++ b/app/controllers/emojis_controller.rb @@ -7,8 +7,6 @@ class EmojisController < ApplicationController def show respond_to do |format| format.json do - skip_session! - render_cached_json(['activitypub', 'emoji', @emoji], content_type: 'application/activity+json') do ActiveModelSerializers::SerializableResource.new(@emoji, serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter) end diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index aea42bd63..415abe10c 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -3,6 +3,8 @@ class FollowerAccountsController < ApplicationController include AccountControllerConcern + before_action :set_cache_headers + def index respond_to do |format| format.html do @@ -17,6 +19,8 @@ class FollowerAccountsController < ApplicationController format.json do raise Mastodon::NotPermittedError if params[:page].present? && @account.user_hides_network? + expires_in 3.minutes, public: true if params[:page].blank? + render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index 03c4b1046..948725664 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -3,9 +3,13 @@ class FollowingAccountsController < ApplicationController include AccountControllerConcern + before_action :set_cache_headers + def index respond_to do |format| format.html do + mark_cacheable! unless user_signed_in? + next if @account.user_hides_network? follows @@ -15,6 +19,8 @@ class FollowingAccountsController < ApplicationController format.json do raise Mastodon::NotPermittedError if params[:page].present? && @account.user_hides_network? + expires_in 3.minutes, public: true if params[:page].blank? + render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index b5d6460f9..85622a7b5 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -50,7 +50,7 @@ class HomeController < ApplicationController push_subscription: current_account.user.web_push_subscription(current_session), current_account: current_account, token: current_session.token, - admin: Account.find_local(Setting.site_contact_username), + admin: Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')), } end @@ -58,7 +58,7 @@ class HomeController < ApplicationController if request.path.start_with?('/web') new_user_session_path elsif single_user_mode? - short_account_path(Account.local.where(suspended: false).first) + short_account_path(Account.local.without_suspended.first) else about_path end diff --git a/app/controllers/settings/identity_proofs_controller.rb b/app/controllers/settings/identity_proofs_controller.rb index 4a3b89a5e..e22b4d9be 100644 --- a/app/controllers/settings/identity_proofs_controller.rb +++ b/app/controllers/settings/identity_proofs_controller.rb @@ -18,7 +18,12 @@ class Settings::IdentityProofsController < Settings::BaseController provider_username: params[:provider_username] ) - render layout: 'auth' + if current_account.username.casecmp(params[:username]).zero? + render layout: 'auth' + else + flash[:alert] = I18n.t('identity_proofs.errors.wrong_user', proving: params[:username], current: current_account.username) + redirect_to settings_identity_proofs_path + end end def create @@ -26,6 +31,7 @@ class Settings::IdentityProofsController < Settings::BaseController @proof.token = resource_params[:token] if @proof.save + PostStatusService.new.call(current_user.account, text: post_params[:status_text]) if publish_proof? redirect_to @proof.on_success_path(params[:user_agent]) else flash[:alert] = I18n.t('identity_proofs.errors.failed', provider: @proof.provider.capitalize) @@ -36,10 +42,22 @@ class Settings::IdentityProofsController < Settings::BaseController private def check_required_params - redirect_to settings_identity_proofs_path unless [:provider, :provider_username, :token].all? { |k| params[k].present? } + redirect_to settings_identity_proofs_path unless [:provider, :provider_username, :username, :token].all? { |k| params[k].present? } end def resource_params params.require(:account_identity_proof).permit(:provider, :provider_username, :token) end + + def publish_proof? + ActiveModel::Type::Boolean.new.cast(post_params[:post_status]) + end + + def post_params + params.require(:account_identity_proof).permit(:post_status, :status_text) + end + + def set_body_classes + @body_classes = '' + end end diff --git a/app/controllers/settings/notifications_controller.rb b/app/controllers/settings/notifications_controller.rb deleted file mode 100644 index da8a03d96..000000000 --- a/app/controllers/settings/notifications_controller.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -class Settings::NotificationsController < Settings::BaseController - layout 'admin' - - before_action :authenticate_user! - - def show; end - - def update - user_settings.update(user_settings_params.to_h) - - if current_user.save - redirect_to settings_notifications_path, notice: I18n.t('generic.changes_saved_msg') - else - render :show - end - end - - private - - def user_settings - UserSettingsDecorator.new(current_user) - end - - def user_settings_params - params.require(:user).permit( - notification_emails: %i(follow follow_request reblog favourite mention digest report), - interactions: %i(must_be_follower must_be_following must_be_following_dm) - ) - end -end diff --git a/app/controllers/settings/preferences/appearance_controller.rb b/app/controllers/settings/preferences/appearance_controller.rb new file mode 100644 index 000000000..80ea57bd2 --- /dev/null +++ b/app/controllers/settings/preferences/appearance_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Settings::Preferences::AppearanceController < Settings::PreferencesController + private + + def after_update_redirect_path + settings_preferences_appearance_path + end +end diff --git a/app/controllers/settings/preferences/notifications_controller.rb b/app/controllers/settings/preferences/notifications_controller.rb new file mode 100644 index 000000000..a16ae6a67 --- /dev/null +++ b/app/controllers/settings/preferences/notifications_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Settings::Preferences::NotificationsController < Settings::PreferencesController + private + + def after_update_redirect_path + settings_preferences_notifications_path + end +end diff --git a/app/controllers/settings/preferences/other_controller.rb b/app/controllers/settings/preferences/other_controller.rb new file mode 100644 index 000000000..07eb89a76 --- /dev/null +++ b/app/controllers/settings/preferences/other_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Settings::Preferences::OtherController < Settings::PreferencesController + private + + def after_update_redirect_path + settings_preferences_other_path + end +end diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 90967635d..110debd6e 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -12,7 +12,7 @@ class Settings::PreferencesController < Settings::BaseController if current_user.update(user_params) I18n.locale = current_user.locale - redirect_to settings_preferences_path, notice: I18n.t('generic.changes_saved_msg') + redirect_to after_update_redirect_path, notice: I18n.t('generic.changes_saved_msg') else render :show end @@ -20,6 +20,10 @@ class Settings::PreferencesController < Settings::BaseController private + def after_update_redirect_path + settings_preferences_path + end + def user_settings UserSettingsDecorator.new(current_user) end @@ -49,8 +53,9 @@ class Settings::PreferencesController < Settings::BaseController :setting_hide_network, :setting_aggregate_reblogs, :setting_show_application, - notification_emails: %i(follow follow_request reblog favourite mention digest report), - interactions: %i(must_be_follower must_be_following) + :setting_advanced_layout, + notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account), + interactions: %i(must_be_follower must_be_following must_be_following_dm) ) end end diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb index 9ef1e0749..af605b98f 100644 --- a/app/controllers/shares_controller.rb +++ b/app/controllers/shares_controller.rb @@ -21,7 +21,7 @@ class SharesController < ApplicationController push_subscription: current_account.user.web_push_subscription(current_session), current_account: current_account, token: current_session.token, - admin: Account.find_local(Setting.site_contact_username), + admin: Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')), text: text, } end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index fc44d5fb1..ef26691b2 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -27,7 +27,7 @@ class StatusesController < ApplicationController def show respond_to do |format| format.html do - mark_cacheable! unless user_signed_in? + expires_in 10.seconds, public: true if current_account.nil? @body_classes = 'with-modals' @@ -38,8 +38,6 @@ class StatusesController < ApplicationController end format.json do - mark_cacheable! unless @stream_entry.hidden? - render_cached_json(['activitypub', 'note', @status], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter) end @@ -48,8 +46,6 @@ class StatusesController < ApplicationController end def activity - skip_session! - render_cached_json(['activitypub', 'activity', @status], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter) end @@ -58,7 +54,6 @@ class StatusesController < ApplicationController def embed raise ActiveRecord::RecordNotFound if @status.hidden? - skip_session! expires_in 180, public: true response.headers['X-Frame-Options'] = 'ALLOWALL' @autoplay = ActiveModel::Type::Boolean.new.cast(params[:autoplay]) @@ -67,8 +62,6 @@ class StatusesController < ApplicationController end def replies - skip_session! - render json: replies_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb index 8568b151c..0f7e9e0f5 100644 --- a/app/controllers/stream_entries_controller.rb +++ b/app/controllers/stream_entries_controller.rb @@ -15,14 +15,13 @@ class StreamEntriesController < ApplicationController def show respond_to do |format| format.html do - redirect_to short_account_status_url(params[:account_username], @stream_entry.activity) if @type == 'status' + expires_in 5.minutes, public: true unless @stream_entry.hidden? + + redirect_to short_account_status_url(params[:account_username], @stream_entry.activity) end format.atom do - unless @stream_entry.hidden? - skip_session! - expires_in 3.minutes, public: true - end + expires_in 3.minutes, public: true unless @stream_entry.hidden? render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.entry(@stream_entry, true)) end @@ -50,7 +49,7 @@ class StreamEntriesController < ApplicationController def set_stream_entry @stream_entry = @account.stream_entries.where(activity_type: 'Status').find(params[:id]) - @type = @stream_entry.activity_type.downcase + @type = 'status' raise ActiveRecord::RecordNotFound if @stream_entry.activity.nil? authorize @stream_entry.activity, :show? if @stream_entry.hidden? diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index b42b1bbdf..9d113263d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -117,4 +117,9 @@ module ApplicationHelper def storage_host? ENV['S3_ALIAS_HOST'].present? || ENV['S3_CLOUDFRONT_HOST'].present? end + + def quote_wrap(text, line_width: 80, break_sequence: "\n") + text = word_wrap(text, line_width: line_width - 2, break_sequence: break_sequence) + text.split("\n").map { |line| '> ' + line }.join("\n") + end end diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb index 4734e32a4..02a860a74 100644 --- a/app/helpers/stream_entries_helper.rb +++ b/app/helpers/stream_entries_helper.rb @@ -16,24 +16,32 @@ module StreamEntriesHelper if user_signed_in? if account.id == current_user.account_id link_to settings_profile_url, class: 'button logo-button' do - safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('settings.edit_profile')]) + safe_join([svg_logo, t('settings.edit_profile')]) end elsif current_account.following?(account) || current_account.requested?(account) link_to account_unfollow_path(account), class: 'button logo-button button--destructive', data: { method: :post } do - safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.unfollow')]) + safe_join([svg_logo, t('accounts.unfollow')]) end elsif !(account.memorial? || account.moved?) - link_to account_follow_path(account), class: 'button logo-button', data: { method: :post } do - safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.follow')]) + link_to account_follow_path(account), class: "button logo-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post } do + safe_join([svg_logo, t('accounts.follow')]) end end elsif !(account.memorial? || account.moved?) link_to account_remote_follow_path(account), class: 'button logo-button modal-button', target: '_new' do - safe_join([render(file: Rails.root.join('app', 'javascript', 'images', 'logo.svg')), t('accounts.follow')]) + safe_join([svg_logo, t('accounts.follow')]) end end end + def svg_logo + content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo'), 'viewBox' => '0 0 216.4144 232.00976') + end + + def svg_logo_full + content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo-full'), 'viewBox' => '0 0 713.35878 175.8678') + end + def account_badge(account, all: false) if account.bot? content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles') diff --git a/app/javascript/images/logo_full.svg b/app/javascript/images/logo_full.svg index c33883342..03bcf93e3 100644 --- a/app/javascript/images/logo_full.svg +++ b/app/javascript/images/logo_full.svg @@ -1 +1 @@ - + diff --git a/app/javascript/images/logo_transparent.svg b/app/javascript/images/logo_transparent.svg index abd6d1f67..a1e7b403e 100644 --- a/app/javascript/images/logo_transparent.svg +++ b/app/javascript/images/logo_transparent.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/app/javascript/mastodon/actions/alerts.js b/app/javascript/mastodon/actions/alerts.js index 50cd48a9e..b2c7ab76a 100644 --- a/app/javascript/mastodon/actions/alerts.js +++ b/app/javascript/mastodon/actions/alerts.js @@ -34,6 +34,11 @@ export function showAlertForError(error) { if (error.response) { const { data, status, statusText } = error.response; + if (status === 404 || status === 410) { + // Skip these errors as they are reflected in the UI + return {}; + } + let message = statusText; let title = `${status}`; diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index d65d41048..300fb48a9 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -63,6 +63,14 @@ const messages = defineMessages({ uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' }, }); +const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1); + +export const ensureComposeIsVisible = (getState, routerHistory) => { + if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) { + routerHistory.push('/statuses/new'); + } +}; + export function changeCompose(text) { return { type: COMPOSE_CHANGE, @@ -77,9 +85,7 @@ export function replyCompose(status, routerHistory) { status: status, }); - if (!getState().getIn(['compose', 'mounted'])) { - routerHistory.push('/statuses/new'); - } + ensureComposeIsVisible(getState, routerHistory); }; }; @@ -102,9 +108,7 @@ export function mentionCompose(account, routerHistory) { account: account, }); - if (!getState().getIn(['compose', 'mounted'])) { - routerHistory.push('/statuses/new'); - } + ensureComposeIsVisible(getState, routerHistory); }; }; @@ -115,9 +119,7 @@ export function directCompose(account, routerHistory) { account: account, }); - if (!getState().getIn(['compose', 'mounted'])) { - routerHistory.push('/statuses/new'); - } + ensureComposeIsVisible(getState, routerHistory); }; }; @@ -203,8 +205,8 @@ export function uploadCompose(files) { return function (dispatch, getState) { const uploadLimit = 4; const media = getState().getIn(['compose', 'media_attachments']); - const total = Array.from(files).reduce((a, v) => a + v.size, 0); const progress = new Array(files.length).fill(0); + let total = Array.from(files).reduce((a, v) => a + v.size, 0); if (files.length + media.size > uploadLimit) { dispatch(showAlert(undefined, messages.uploadErrorLimit)); @@ -224,6 +226,8 @@ export function uploadCompose(files) { resizeImage(f).then(file => { const data = new FormData(); data.append('file', file); + // Account for disparity in size of original image and resized data + total += file.size - f.size; return api(getState).post('/api/v1/media', data, { onUploadProgress: function({ loaded }){ @@ -381,7 +385,7 @@ export function readyComposeSuggestionsAccounts(token, accounts) { }; }; -export function selectComposeSuggestion(position, token, suggestion) { +export function selectComposeSuggestion(position, token, suggestion, path) { return (dispatch, getState) => { let completion, startPosition; @@ -403,6 +407,7 @@ export function selectComposeSuggestion(position, token, suggestion) { position: startPosition, token, completion, + path, }); }; }; diff --git a/app/javascript/mastodon/actions/identity_proofs.js b/app/javascript/mastodon/actions/identity_proofs.js new file mode 100644 index 000000000..449debf61 --- /dev/null +++ b/app/javascript/mastodon/actions/identity_proofs.js @@ -0,0 +1,30 @@ +import api from '../api'; + +export const IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST = 'IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST'; +export const IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS = 'IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS'; +export const IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL = 'IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL'; + +export const fetchAccountIdentityProofs = accountId => (dispatch, getState) => { + dispatch(fetchAccountIdentityProofsRequest(accountId)); + + api(getState).get(`/api/v1/accounts/${accountId}/identity_proofs`) + .then(({ data }) => dispatch(fetchAccountIdentityProofsSuccess(accountId, data))) + .catch(err => dispatch(fetchAccountIdentityProofsFail(accountId, err))); +}; + +export const fetchAccountIdentityProofsRequest = id => ({ + type: IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST, + id, +}); + +export const fetchAccountIdentityProofsSuccess = (accountId, identity_proofs) => ({ + type: IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS, + accountId, + identity_proofs, +}); + +export const fetchAccountIdentityProofsFail = (accountId, err) => ({ + type: IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL, + accountId, + err, +}); diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js index b670d25c3..7c06670eb 100644 --- a/app/javascript/mastodon/actions/search.js +++ b/app/javascript/mastodon/actions/search.js @@ -37,6 +37,7 @@ export function submitSearch() { params: { q: value, resolve: true, + limit: 5, }, }).then(response => { if (response.data.accounts) { diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 1794538e2..06a19afc3 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -4,6 +4,7 @@ import { evictStatus } from '../storage/modifier'; import { deleteFromTimelines } from './timelines'; import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus } from './importer'; +import { ensureComposeIsVisible } from './compose'; export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'; export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS'; @@ -131,14 +132,15 @@ export function fetchStatusFail(id, error, skipLoading) { }; }; -export function redraft(status) { +export function redraft(status, raw_text) { return { type: REDRAFT, status, + raw_text, }; }; -export function deleteStatus(id, router, withRedraft = false) { +export function deleteStatus(id, routerHistory, withRedraft = false) { return (dispatch, getState) => { let status = getState().getIn(['statuses', id]); @@ -148,17 +150,14 @@ export function deleteStatus(id, router, withRedraft = false) { dispatch(deleteStatusRequest(id)); - api(getState).delete(`/api/v1/statuses/${id}`).then(() => { + api(getState).delete(`/api/v1/statuses/${id}`).then(response => { evictStatus(id); dispatch(deleteStatusSuccess(id)); dispatch(deleteFromTimelines(id)); if (withRedraft) { - dispatch(redraft(status)); - - if (!getState().getIn(['compose', 'mounted'])) { - router.push('/statuses/new'); - } + dispatch(redraft(status, response.data.text)); + ensureComposeIsVisible(getState, routerHistory); } }).catch(error => { dispatch(deleteStatusFail(id, error)); diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index d92385e95..06c21b96b 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -96,7 +96,7 @@ export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done); export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId }); export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); -export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true }); +export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 }); export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done); export const expandHashtagTimeline = (hashtag, { maxId, tags } = {}, done = noOp) => { return expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, { diff --git a/app/javascript/mastodon/components/autosuggest_input.js b/app/javascript/mastodon/components/autosuggest_input.js new file mode 100644 index 000000000..c7d965b53 --- /dev/null +++ b/app/javascript/mastodon/components/autosuggest_input.js @@ -0,0 +1,229 @@ +import React from 'react'; +import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container'; +import AutosuggestEmoji from './autosuggest_emoji'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import { isRtl } from '../rtl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import classNames from 'classnames'; +import { List as ImmutableList } from 'immutable'; + +const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => { + let word; + + let left = str.slice(0, caretPosition).search(/\S+$/); + let right = str.slice(caretPosition).search(/\s/); + + if (right < 0) { + word = str.slice(left); + } else { + word = str.slice(left, right + caretPosition); + } + + if (!word || word.trim().length < 3 || searchTokens.indexOf(word[0]) === -1) { + return [null, null]; + } + + word = word.trim().toLowerCase(); + + if (word.length > 0) { + return [left + 1, word]; + } else { + return [null, null]; + } +}; + +export default class AutosuggestInput extends ImmutablePureComponent { + + static propTypes = { + value: PropTypes.string, + suggestions: ImmutablePropTypes.list, + disabled: PropTypes.bool, + placeholder: PropTypes.string, + onSuggestionSelected: PropTypes.func.isRequired, + onSuggestionsClearRequested: PropTypes.func.isRequired, + onSuggestionsFetchRequested: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + onKeyUp: PropTypes.func, + onKeyDown: PropTypes.func, + autoFocus: PropTypes.bool, + className: PropTypes.string, + id: PropTypes.string, + searchTokens: PropTypes.arrayOf(PropTypes.string), + maxLength: PropTypes.number, + }; + + static defaultProps = { + autoFocus: true, + searchTokens: ImmutableList(['@', ':', '#']), + }; + + state = { + suggestionsHidden: true, + focused: false, + selectedSuggestion: 0, + lastToken: null, + tokenStart: 0, + }; + + onChange = (e) => { + const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart, this.props.searchTokens); + + if (token !== null && this.state.lastToken !== token) { + this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart }); + this.props.onSuggestionsFetchRequested(token); + } else if (token === null) { + this.setState({ lastToken: null }); + this.props.onSuggestionsClearRequested(); + } + + this.props.onChange(e); + } + + onKeyDown = (e) => { + const { suggestions, disabled } = this.props; + const { selectedSuggestion, suggestionsHidden } = this.state; + + if (disabled) { + e.preventDefault(); + return; + } + + if (e.which === 229 || e.isComposing) { + // Ignore key events during text composition + // e.key may be a name of the physical key even in this case (e.x. Safari / Chrome on Mac) + return; + } + + switch(e.key) { + case 'Escape': + if (suggestions.size === 0 || suggestionsHidden) { + document.querySelector('.ui').parentElement.focus(); + } else { + e.preventDefault(); + this.setState({ suggestionsHidden: true }); + } + + break; + case 'ArrowDown': + if (suggestions.size > 0 && !suggestionsHidden) { + e.preventDefault(); + this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) }); + } + + break; + case 'ArrowUp': + if (suggestions.size > 0 && !suggestionsHidden) { + e.preventDefault(); + this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) }); + } + + break; + case 'Enter': + case 'Tab': + // Select suggestion + if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) { + e.preventDefault(); + e.stopPropagation(); + this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion)); + } + + break; + } + + if (e.defaultPrevented || !this.props.onKeyDown) { + return; + } + + this.props.onKeyDown(e); + } + + onBlur = () => { + this.setState({ suggestionsHidden: true, focused: false }); + } + + onFocus = () => { + this.setState({ focused: true }); + } + + onSuggestionClick = (e) => { + const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index')); + e.preventDefault(); + this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion); + this.input.focus(); + } + + componentWillReceiveProps (nextProps) { + if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { + this.setState({ suggestionsHidden: false }); + } + } + + setInput = (c) => { + this.input = c; + } + + renderSuggestion = (suggestion, i) => { + const { selectedSuggestion } = this.state; + let inner, key; + + if (typeof suggestion === 'object') { + inner = ; + key = suggestion.id; + } else if (suggestion[0] === '#') { + inner = suggestion; + key = suggestion; + } else { + inner = ; + key = suggestion; + } + + return ( +
+ {inner} +
+ ); + } + + render () { + const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength } = this.props; + const { suggestionsHidden } = this.state; + const style = { direction: 'ltr' }; + + if (isRtl(value)) { + style.direction = 'rtl'; + } + + return ( +
+ + +
+ {suggestions.map(this.renderSuggestion)} +
+
+ ); + } + +} diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js index a4f5cf50c..b070fe3e5 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.js +++ b/app/javascript/mastodon/components/autosuggest_textarea.js @@ -55,7 +55,8 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { }; state = { - suggestionsHidden: false, + suggestionsHidden: true, + focused: false, selectedSuggestion: 0, lastToken: null, tokenStart: 0, @@ -134,7 +135,14 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { } onBlur = () => { - this.setState({ suggestionsHidden: true }); + this.setState({ suggestionsHidden: true, focused: false }); + } + + onFocus = (e) => { + this.setState({ focused: true }); + if (this.props.onFocus) { + this.props.onFocus(e); + } } onSuggestionClick = (e) => { @@ -145,7 +153,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { } componentWillReceiveProps (nextProps) { - if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) { + if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { this.setState({ suggestionsHidden: false }); } } @@ -184,7 +192,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { } render () { - const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props; + const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children } = this.props; const { suggestionsHidden } = this.state; const style = { direction: 'ltr' }; @@ -192,33 +200,39 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { style.direction = 'rtl'; } - return ( -
-