Compare commits

...

304 Commits

Author SHA1 Message Date
dependabot[bot]
faa5d164f7 Bump @adobe/css-tools from 4.3.0 to 4.3.2
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.3.0 to 4.3.2.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-19 17:22:08 +00:00
Claire
4fdbffc57c Merge pull request #2522 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to b2c5b20ef2
2023-12-19 18:20:34 +01:00
Brian Holley
4259f4a4aa [Glitch] Fix "Hide these posts from home" list setting not refreshing when switching lists
Port 4aa06cbdbf to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-18 20:59:33 +01:00
Claire
a111fd7a0b Merge commit 'b2c5b20ef27edd948eca8d6bd2014b7a5efaec11' into glitch-soc/merge-upstream 2023-12-18 20:47:27 +01:00
pajowu
6261db2a26 [Glitch] Fix modal content not being selectable
Port ed79713f3a to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-18 19:15:57 +01:00
Claire
d6ad9d351e Merge commit 'a36b59be8ad7656b7ceab9751c9ec5b3563e3a30' into glitch-soc/merge-upstream 2023-12-18 19:10:43 +01:00
Claire
60eb57ce1b Fix HAML listing issue 2023-12-18 19:07:15 +01:00
Claire
f5de9fe99a [Glitch] Allow viewing and severing relationships with suspended accounts
Port c451bbe249 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-18 18:49:48 +01:00
Aleks Xhuvani
dee28c3fbf [Glitch] Do not try to update an undefined video element
Port 7840c6b75b to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-18 18:42:30 +01:00
Aleks Xhuvani
372c455218 [Glitch] Add volume saving/reuse to video player
Port 58f01a5c9a to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-18 18:42:07 +01:00
Claire
b474cbbd28 Merge commit '7840c6b75b61e64d89d7fd9f291277fb177e513f' into glitch-soc/merge-upstream
Conflicts:
- `app/controllers/api/v1/accounts/relationships_controller.rb`:
  We differed by listing suspended users when requesting relationships.
  Updated to upstream's code.
2023-12-18 18:40:58 +01:00
Claire
46ddaffd40 Merge commit 'b87bfb8c96c8491f1228e0258d05119f3420db05' into glitch-soc/merge-upstream 2023-12-18 18:34:25 +01:00
Claire
cd2ee0ec5f Merge commit '757d7c73c0ca3750ac9e74a7e962f71c535a7bd0' into glitch-soc/merge-upstream
Conflicts:
- `README.md`:
  Upstream updated its README, we have a completely different one.
  Kept ours.
- `yarn.lock`:
  Upstream changed from yarn 1 to yarn 4, but we had some different
  dependencies.
  Updated to yarn 4 and re-added our dependencies.
2023-12-18 18:25:33 +01:00
Claire
bc33be0342 Merge commit '23e32a4b3031d1da8b911e0145d61b4dd47c4f96' into glitch-soc/merge-upstream
Conflicts:
- `Gemfile.lock`:
  Conflict because we had updated the `json-ld` gem to fix a yanked dependency.
  Kept our version of `json-ld` while updating other dependencies.
2023-12-18 18:07:24 +01:00
Claire
3d3fa75c81 Reduce composer differences with upstream and simplify code (#2518) 2023-12-18 13:20:08 +01:00
Claire
18856371be Merge pull request #2520 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to f476d9dab2
2023-12-18 13:19:45 +01:00
Claire
33dd5d8179 Fix Ruby lint issues 2023-12-17 23:17:55 +01:00
Claire
4113fbf6e8 Merge commit 'f476d9dab2f5cca6ae44b95961df6b6557d66dab' into glitch-soc/merge-upstream
Conflicts:
- `lib/sanitize_ext/sanitize_config.rb`:
  Upstream enforced new code style rules, where we had different code.
  Applied the new code style rules.
2023-12-17 23:04:16 +01:00
Claire
c8fe36c349 Merge pull request #2517 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to 3bf896c973
2023-12-17 20:06:18 +01:00
mogaminsk
cc265f760e [Glitch] Fix inserting emojis from emoji picker fails with TypeError
Port ac8e4ed38d to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-17 18:03:22 +01:00
Claire
bb4fa0c374 [Glitch] Rewrite AutosuggestTextarea as Functional Component
Port 9c8891b39a to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-17 17:59:33 +01:00
Renaud Chaput
e22c3cd768 [Glitch] Improve Babel configuration and automatically load polyfills
Port 0e3401bc1c to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-17 17:50:31 +01:00
Claire
ddf3ad9541 Merge commit '3bf896c973404261f4f7b25c25ea22adb1a85e7d' into glitch-soc/main
Conflicts:
- `package.json`:
  Upstream removed a dependency textually close to a glitch-only dependency.
  Updated as upstream while keeping our dependency.
2023-12-17 17:43:30 +01:00
Claire
537b88330d Merge pull request #2514 from ClearlyClaire/glitch-soc/main
Merge upstream changes up to a916251d8a
2023-12-17 17:42:22 +01:00
Claire
f62bafc7a1 Fix HAML linting issue 2023-12-17 16:37:11 +01:00
Claire
1474318691 Merge commit 'a916251d8a8fffcaeb6be80eacf50138a53650dc' into glitch-soc/main
Conflicts:
- `app/models/trends/statuses.rb`:
  Upstream fixed a bug in the trending post condition.
  Glitch-soc's condition is different because we potentially allow CWed content
  to trend.
  Ported upstream's fix while keeping glitch-soc's change.
- `config/initializers/content_security_policy.rb`:
  Kept our version for now, we will switch to upstream later down the road.
2023-12-17 15:32:29 +01:00
Claire
b7248485b1 Merge pull request #2477 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to 2e6bf60f15
2023-12-12 19:45:18 +01:00
Claire
9f92b05bd2 Merge commit '2e6bf60f1549e5c1f1cfea2d614f978bea17b8a2' into glitch-soc/merge-upstream
Conflicts:
- `README.md`:
  Upstream has updated their README but we have a completely different one.
  Kept our version of `README.md`
2023-12-10 18:05:02 +01:00
Claire
98f50429d5 Merge pull request #2511 from ClearlyClaire/glitch-soc/cleanup-2
Further reduce code differences with upstream
2023-12-10 18:03:04 +01:00
Claire
df5c64fe57 Further reduce differences with upstream 2023-12-09 21:19:43 +01:00
Claire
cc1d68ace8 [Glitch] Show announcements in reverse chronological order
Port f1f0400adc and 8e2530ea16 to glitch-soc

Co-Authored-By: Darius Kazemi <darius.kazemi@gmail.com>
2023-12-09 21:19:43 +01:00
Claire
4b2ddaf106 Further reduce differences with upstream 2023-12-09 21:19:43 +01:00
Claire
408d4710ed Further reduce differences with upstream 2023-12-09 20:58:50 +01:00
Claire
a27abb4802 Further reduce code differences with upstream (#2509) 2023-12-09 20:29:23 +01:00
Claire
1ddf2012ee Fix status avatar size discrepancies (#2510)
Follow-up to #2508
2023-12-09 20:29:15 +01:00
Claire
b2647bc3f2 [Glitch] Update Avatar, AvatarComposite, and AvatarOverlay components (#2508)
Various ports including 8dfe5179ee,
d1de7fb7fa and
9f8d34620b.

Co-authored-by: Eugen Rochko <eugen@zeonfederated.com>
Co-authored-by: fusagiko / takayamaki <24884114+takayamaki@users.noreply.github.com>
2023-12-09 18:33:42 +01:00
Claire
c0e562916c Fix glitch-soc being uninstallable because of yanked dependency (#2507) 2023-12-06 13:32:27 +01:00
Claire
9fcf5d4192 [Glitch] Fix emoji picker button scrolling with textarea content in single-column view (#2501)
Port bbea052935 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-04 13:30:27 +01:00
Essem
a46b6af1d6 Fix constant redirects to onboarding page (#2505) 2023-12-04 13:30:17 +01:00
Claire
23ee393fdd Merge pull request #2499 from ClearlyClaire/glitch-soc/port-toasts
Port upstream's toast changes
2023-12-03 20:57:42 +01:00
Claire
13902903d3 Merge pull request #2497 from ClearlyClaire/glitch-soc/ports/account_notes-typescript
Port upstream's TypeScript refactor of account_notes
2023-12-03 20:57:23 +01:00
Eugen Rochko
cede2f533c [Glitch] Fix toast saying "published" instead of "saved" after editing post in web UI
Port 71641766f2 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 19:39:08 +01:00
Renaud Chaput
9ac73a1fbf [Glitch] Change eslint config to autofix missing comma and indentation in JS files
Partial port of 774e1189d2 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 16:44:40 +01:00
Christian Schmidt
ea004108b8 [Glitch] Make notification respect reduce-motion
Port 6d0767558a to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 16:38:41 +01:00
Stanislas Signoud
811b8b200e [Glitch] Use invariant colors on notification toasts
Port ca955ada0b to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 16:38:41 +01:00
Eugen Rochko
3bbe39f233 [Glitch] Add toast with option to open post after publishing in web UI
Port a7ca33ad96 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 16:38:41 +01:00
Claire
046cb408b7 [Glitch] Fix front-end bug when processing relationship-related account actions
Port 287520453c to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 13:19:10 +01:00
Renaud Chaput
c3a0d5aca3 [Glitch] Fix Redux types
Port 0712cc2b99 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 13:19:10 +01:00
Renaud Chaput
6fb5fafd28 [Glitch] Convert actions/account_notes into Typescript
Port bd06c13204 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 13:19:10 +01:00
Claire
c82d4cfb71 Merge pull request #2493 from ClearlyClaire/glitch-soc/even-more-painful-backports
Port onboarding changes from upstream
2023-12-03 13:18:50 +01:00
Claire
ee58f680e8 [Glitch] Fix autocomplete suggestions being cut off in compose form
Port 6833732852 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
Claire
6269a5336f [Glitch] Change composer highlight border size to be more noticeable
Port aa4c9730f6 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
Claire
204fe83fcc [Glitch] Fix compose textarea scroll behavior
Port 746979f75d to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
Claire
a8f6a5b40f Update translation strings 2023-12-03 11:23:12 +01:00
Claire
786b42e2b5 Fix SCSS and JS linting issues 2023-12-03 11:23:12 +01:00
Claire
a0943b8f6d Remove glitch-soc's old onboarding modal 2023-12-03 11:23:12 +01:00
Renaud Chaput
6dc812dd51 [Glitch] Upgrade to react-router v5 in onboarding code
Port 1b70d7ed7c to glitch-soc

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
Eugen Rochko
06e819537b [Glitch] Change labels and styles on the onboarding screen in web UI
Port a985d587e1 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
Eugen Rochko
d5bad93460 [Glitch] Change "Follow 7 people" to "Find at least 7 people to follow" in web UI
Port 4a5464f360 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
たいち ひ
c2c25122e8 [Glitch] Rewrite <Check /> as FC
Port 6fdbee240c to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
Renaud Chaput
a1667ba796 [Glitch] Upgrade react-intl usage in onboarding code
Port remaining of 44cd88adc4 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
Claire
e47c582283 Fix import order 2023-12-03 11:23:12 +01:00
Renaud Chaput
ba4c8a9b41 [Glitch] Use the new JSX transform in onboarding code
Port the remaining of 8f66126b10 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
Steven Munn
6e4497ab2d [Glitch] Fix spelling of "Lets" on the onboarding page after clicking the confirmation email
Port 52d36f0f98 to glitch-soc

Co-authored-by: Steven Munn <stevenjmunn@gmail.com>
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
Emelia Smith
1cb8df655b [Glitch] Fix Onboarding Errors
Port b8a2430642 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
Renaud Chaput
0b1556b7f7 [Glitch] Enforce stricter rules for Typescript files
Port c8181eb0a4 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
Eugen Rochko
542f2fb1e0 [Glitch] Add default post text to onboarding flow in web UI
Port 8979b70975 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
Eugen Rochko
e0b401e295 [Glitch] Add more tips to onboarding flow in web UI
Port c35e3cb6ac to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
Eugen Rochko
4537b4b961 [Glitch] Add new onboarding flow to web UI
Port 0461f83320 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-03 11:23:12 +01:00
Claire
335cfab32f Change account note design to match upstream's (#2495) 2023-12-03 11:22:38 +01:00
Claire
046141d2a4 Fix i18n unused check being tripped by no in YAML files (#2496) 2023-12-03 11:21:34 +01:00
Claire
f1241b4a3a Fix translation string for status.favourite not having been changed everywhere (#2494) 2023-12-03 09:51:29 +01:00
Claire
edd96ce786 Merge pull request #2492 from ClearlyClaire/glitch-soc/painful-backports
Port account rows design change from upstream
2023-12-03 09:51:07 +01:00
Claire
21df2a68ac Hide followers count when hidden by instance or user 2023-12-02 17:38:07 +01:00
Claire
09062d393f Fix more styling issues 2023-12-02 17:38:07 +01:00
Claire
28d4f3ab70 Fix account component styling wrt. upstream 2023-12-02 17:05:54 +01:00
fusagiko / takayamaki
dc917cfcdf [Glitch] Fix account.jsx imports (#25541)
Port remaining part of e0d230fb37,
20e85c0e83 and
9d45a444f9 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-02 16:59:40 +01:00
Eugen Rochko
17372a3ec0 [Glitch] Change labels and styles on the onboarding screen in Account component
Partial port of a985d587e1 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-02 16:59:40 +01:00
Eugen Rochko
0421b44f22 [Glitch] Change follow button in account row to be more obvious in web UI (#24956)
Port 0ddc895282 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-02 16:59:40 +01:00
Claire
f1691eca55 [Glitch] Fix overflow behavior of account rows
Port 5fae2de454 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-02 16:59:40 +01:00
Renaud Chaput
98a7b95058 [Glitch] Update inconsistent defaultMessage in Account component
Partial port of e58c36d308 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-02 16:59:40 +01:00
Claire
b047b981cd [Glitch] Enforce import order with ESLint
Port d27216dc46 to glitch-soc
2023-12-02 16:59:40 +01:00
Emelia Smith
b8cbaba283 [Glitch] Split EmptyAccount out of Account component
Partial port of b8a2430642 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-02 16:59:40 +01:00
Claire
32ec0d2472 [Glitch] Fix verified badge in account lists potentially including rel="me" links
Port 55e7c08a83 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-02 16:59:40 +01:00
Renaud Chaput
e7ec2641a0 [Glitch] Fix linting issues with VerifiedBadge component
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-02 16:59:40 +01:00
fusagiko / takayamaki
2efb22f455 [Glitch] Rewrite VerifiedBadge component as function component
Port 140aa6b054 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-02 16:59:40 +01:00
Eugen Rochko
00db5c8ade [Glitch] Split VerifiedBadge from Account component
Partial port of 0461f83320 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-02 16:59:40 +01:00
Eugen Rochko
e0aba64a64 [Glitch] Fix regressions from change in account row design in web UI
Port 46483ae849

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-02 16:59:40 +01:00
Eugen Rochko
a262f990f8 Change design of account rows in web UI (#24247)
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-12-02 16:59:40 +01:00
Claire
73a94c3b3f Fix self-destruct page not using theme styles (#2490) 2023-12-02 14:54:35 +01:00
Plastikmensch
5b0382abc5 Remove redundant asset preload (#2488)
* Remove redundant asset preload

preloading these is already handled by glitch-soc theming system, meaning glitch packs get preloaded and if the user is signed in, vanilla packs.

The theming system preloads these unconditionally though.

Signed-off-by: Plastikmensch <plastikmensch@users.noreply.github.com>

* Remove preload of getting started

This matches upstreams preloads

Signed-off-by: Plastikmensch <plastikmensch@users.noreply.github.com>

---------

Signed-off-by: Plastikmensch <plastikmensch@users.noreply.github.com>
2023-12-02 14:54:26 +01:00
Claire
660372d130 Revert recent CSP changes (#2485)
* Revert "Fix image and media loading when using external storage server"

This reverts commit 6cfa0245ca.

* Revert "Change glitch-soc's CSP config to match upstream's closer (#2474)"

This reverts commit d59196e170.
2023-11-26 15:32:35 +01:00
Jeong Arm
b3581d1e2f Fix @rails/ujs import on public.jsx (#2482)
Related: 8a131fb7bc
2023-11-24 17:40:31 +01:00
Claire
a21fe8687e Merge pull request #2480 from ClearlyClaire/glitch-soc/fixes/csp
Fix image and media loading when using external storage server
2023-11-21 17:36:09 +01:00
Claire
6cfa0245ca Fix image and media loading when using external storage server
Fixes #2479
2023-11-21 13:45:29 +01:00
Claire
769ab0ce47 Merge pull request #2475 from ClearlyClaire/glitch-soc/cleanup
Further reduce differences with upstream
2023-11-20 13:39:56 +01:00
Claire
f00fcda785 Reduce differences with upstream in Account component 2023-11-20 13:24:02 +01:00
Claire
9ab1aa15e9 Change ReplyIndicator implementation and markup to match upstream's 2023-11-20 13:24:02 +01:00
Claire
d59196e170 Change glitch-soc's CSP config to match upstream's closer (#2474) 2023-11-20 13:02:49 +01:00
Claire
c34a3a83e1 Merge pull request #2471 from ClearlyClaire/glitch-soc/cleanup
Clean up some more and reduce unwarranted differences with upstream further
2023-11-19 21:05:34 +01:00
Claire
d3ae5b21d2 Reduce code and markup discrepancies on reply indicator 2023-11-16 18:33:16 +01:00
Claire
e023acfd00 Remove unnecessary proptype discrepancy 2023-11-16 17:57:13 +01:00
Claire
7f5b164326 Merge pull request #2470 from ClearlyClaire/glitch-soc/cleanup
Clean up some more and reduce unwarranted differences with upstream further
2023-11-16 17:49:36 +01:00
Claire
36f25ea067 Fix more import discrepancies 2023-11-15 22:50:23 +01:00
Claire
4101057b9a Fix various code discrepancies 2023-11-15 22:50:23 +01:00
Claire
09a5a78527 Fix discrepancy for autosuggest-emoji class 2023-11-15 22:50:23 +01:00
Claire
f69f1e9429 Fix code discrepancies with upstream 2023-11-15 21:52:19 +01:00
Claire
d358a3cc61 Merge pull request #2468 from ClearlyClaire/glitch-soc/cleanup
Clean up some more and reduce unwarranted differences with upstream further
2023-11-15 21:49:50 +01:00
Claire
61df4f9cbf Merge pull request #2469 from ClearlyClaire/glitch-soc/fixes/compose-form-checkbox
[Glitch] Change compose form checkbox to native input with `appearance: none`
2023-11-15 20:27:37 +01:00
Claire
0d877a3076 [Glitch] Change compose form checkbox to native input with appearance: none
Port d3b4d4d4f3 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-11-15 17:20:44 +01:00
Claire
e7b3598f04 No need to have a separate localStorage key for home instance 2023-11-15 17:16:21 +01:00
Claire
0c727a6790 Fix more import style discrepancies 2023-11-15 17:16:21 +01:00
Claire
50eb673494 Fix some markup discrepancies 2023-11-15 17:16:21 +01:00
Claire
349579e318 Fix more code discrepancies 2023-11-15 17:16:21 +01:00
Claire
1023c2f90b Fix more whitespace and comment discrepancies 2023-11-15 17:16:21 +01:00
Claire
7e5d00720b Merge pull request #2391 from ClearlyClaire/glitch-soc/port-upstream-hashtags
Port upstream's hashtag handling to glitch-soc
2023-11-15 17:15:21 +01:00
Claire
7a877ae3d8 Merge pull request #2467 from ClearlyClaire/glitch-soc/cleanup
Misc cleanup and backports
2023-11-15 17:14:14 +01:00
Claire
cd3a636b7f [Glitch] Fix some remote posts getting truncated
Port 4d59dfb1c6 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-11-15 12:59:06 +01:00
Eugen Rochko
6a8623588a [Glitch] Fix colors and typography on hashtag bar in web UI
Port 10b06436d1 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-11-15 12:56:58 +01:00
Claire
c226d00490 Fix interaction between CWs and hashtag bars 2023-11-15 12:56:58 +01:00
Claire
7ae45676c8 Fix hashtag bar styling 2023-11-15 12:56:58 +01:00
Claire
45690b01d1 [Glitch] Change hashtag bar tags to be de-emphasized
Port 613cfd625c to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-11-15 12:56:58 +01:00
Renaud Chaput
fc514fa8c6 [Glitch] Better hashtag normalization when processing a post
Port 58acaa9ae6 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-11-15 12:56:58 +01:00
Renaud Chaput
18462ee4b6 [Glitch] Remove hashtags from the last line of a status if it only contains hashtags
Port 061fd66ee6 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-11-15 12:56:58 +01:00
Claire
2ce03420d6 [Glitch] Fix case-insensitive comparison of hashtags to do case-folding
Port 3ed2bf92d0 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-11-15 12:56:58 +01:00
Claire
fe8d9f6221 [Glitch] Fix hashtag bar sometimes including tags that appear in the post's body
Port f0862bcf98

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-11-15 12:56:58 +01:00
Claire
a81ed84453 [Glitch] Add display of out-of-band hashtags in the web interface
Port df6e719898 to glitch-soc

Co-authored-by: Eugen Rochko <eugen@zeonfederated.com>
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-11-15 12:56:58 +01:00
Claire
3b210e093a Fix more code style discrepancies 2023-11-15 12:53:21 +01:00
Claire
542d95c2bc Fix import style discrepancy 2023-11-15 12:53:21 +01:00
Claire
8b24a9a507 Fix missed relative import discrepancy 2023-11-15 12:53:21 +01:00
Claire
b1f0457cb8 Fix whitespace and comment discrepancies 2023-11-15 12:53:21 +01:00
Eugen Rochko
08ac91c40b [Glitch] Fix follow relationships not loading after notifications fetch
Port 4f7f6b3922 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-11-15 12:53:21 +01:00
Eugen Rochko
103c0ca4f7 [Glitch] Hide loading bar on status interactions
Port eb2425b53b to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-11-15 12:53:21 +01:00
Claire
21653beb30 Reduce more unwarranted differences with upstream 2023-11-15 12:53:21 +01:00
Claire
7b922c2d90 Reduce unwarranted differences with upstream
Ports part of 0758b00bfd and 897199910f
2023-11-15 12:53:21 +01:00
Claire
2f61b65b1e Remove dead code 2023-11-15 12:53:11 +01:00
Claire
9bbb0f13e0 Fix inconsistent React imports in JSX files (#2466) 2023-11-15 12:52:30 +01:00
Claire
a97b722ad5 Reduce differences with upstream due to import style (#2465)
Fix relative / VS absolute style imports, and fix whitespace discrepancies
2023-11-15 12:01:51 +01:00
Claire
f7d3b74e03 Merge pull request #2389 from glitch-soc/i18n/crowdin/translations
New Crowdin Translations (automated)
2023-11-15 09:42:30 +01:00
Claire
02582f7e50 Merge pull request #2464 from neatchee/pr/glitch/mobile_singlecolumn_warning_fix
Fix recurring "switch to advanced interface" warning by adding missing…
2023-11-15 07:21:16 +01:00
Matt Jankowski
b2c5b20ef2 Fix RSpec/AnyInstance cop (#27810) 2023-11-14 14:52:59 +00:00
Matt Jankowski
d562fb8459 Specs for minimal CSP policy in Api:: controllers (#27845) 2023-11-14 14:34:30 +00:00
renovate[bot]
4eb4e8b22c Update Yarn to v4.0.2 (#27851)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-14 11:07:13 +00:00
github-actions[bot]
c1e071f634 New Crowdin Translations (automated) (#27848)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-11-14 10:53:47 +00:00
Matt Jankowski
1f8173ac5a Extract private methods in api/v1/instances/domain_blocks (#27844) 2023-11-14 10:31:59 +00:00
renovate[bot]
373aa95ddd Update formatjs monorepo (#27849)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-14 10:30:39 +00:00
Matt Jankowski
5e2ecc736d Remove double subject in api/v1/accounts/relationships spec (#27839) 2023-11-14 10:29:33 +00:00
Brian Holley
4aa06cbdbf Fix "Hide these posts from home" list setting not refreshing when switching lists (#27763) 2023-11-14 00:39:54 +00:00
Matt Jankowski
b7807f3d84 Use normalizes to prepare Webhook#events value (#27605) 2023-11-13 22:47:44 +00:00
neatchee
bd6da814b5 Fix recurring "switch to advanced interface" warning by adding missing class to the navigation-panel__banner element 2023-11-13 13:20:51 -08:00
Claire
bac9e0b55d Add variable delay before link verification of remote account links (#27774) 2023-11-13 16:17:05 +00:00
Matt Jankowski
49ba5a9f94 Use hash_including to check AccountFilter setup in admin/accounts controller spec (#27838) 2023-11-13 16:01:24 +00:00
Matt Jankowski
0945e25b8f Add Api::V1::Statuses::BaseController base controller class (#27794) 2023-11-13 14:53:22 +00:00
renovate[bot]
da59407520 Update eslint (non-major) (#27831)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-13 14:40:50 +00:00
renovate[bot]
a1b48460e4 Update DefinitelyTyped types (non-major) (#27830)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-13 14:40:10 +00:00
Eugen Rochko
7e3c10dec6 Add icons for private and disabled boost in web UI (#27817)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2023-11-13 14:39:45 +00:00
Matt Jankowski
a36b59be8a Spec coverage for api/v1/trends controllers (#27837) 2023-11-13 14:32:36 +00:00
renovate[bot]
0c98a9d9be Update devDependencies (non-major) (#25612)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-13 14:01:07 +00:00
pajowu
ed79713f3a Fix modal content not being selectable (#27813) 2023-11-13 13:27:50 +00:00
Claire
07a4059901 Add support for invite codes in the registration API (#27805) 2023-11-13 13:27:00 +00:00
renovate[bot]
5bca5c4c5b Update formatjs monorepo (#27823)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-13 13:24:14 +00:00
github-actions[bot]
e5a7b73ef4 New Crowdin Translations (automated) (#27815)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-11-13 13:21:02 +00:00
renovate[bot]
a7117bbef6 Update dependency @rails/ujs to v7.1.2 (#27811)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-13 13:13:57 +00:00
renovate[bot]
3b989e4d64 Update dependency rails to v7.1.2 (#27812)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-13 13:13:53 +00:00
Renaud Chaput
da4f37020b Fix Jest config (#27834) 2023-11-13 12:19:41 +00:00
Eugen Rochko
0d14fcebae Change link previews to keep original URL from the status (#27312) 2023-11-13 09:58:28 +00:00
GitHub Actions
46a28fc41f New Crowdin translations 2023-11-11 04:27:29 +00:00
Matt Jankowski
9dc3ce878b Speed-up in Settings:: controllers specs (#27808) 2023-11-10 15:13:42 +00:00
github-actions[bot]
ac62b995ef New Crowdin Translations (automated) (#27804)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-11-10 13:07:04 +00:00
Matt Jankowski
43e2f763b2 Extract crutches_active_mentions from FeedManager (#27785) 2023-11-10 12:47:38 +00:00
Matt Jankowski
ac69f90098 Add Api::V1::Instances::BaseController base controller class (#27797) 2023-11-10 12:46:00 +00:00
renovate[bot]
fba838d61a Update dependency @material-symbols/svg-600 to ^0.14.0 (#27803)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-10 12:45:19 +00:00
Aleks Xhuvani
7840c6b75b Do not try to update an undefined video element (#27798) 2023-11-09 22:35:53 +00:00
Matt Jankowski
e79bf1b1e7 Minor speed improvement on controllers/accounts spec (#27679) 2023-11-09 16:21:11 +00:00
Matt Jankowski
9429e30d75 Disable sidekiq unique jobs in test env (#27737) 2023-11-09 16:19:04 +00:00
Claire
c451bbe249 Allow viewing and severing relationships with suspended accounts (#27667) 2023-11-09 14:50:25 +00:00
Michael Stanclift
b87bfb8c96 Fix Yarn version in devcontainer (#27788) 2023-11-09 14:30:07 +00:00
Matt Jankowski
548bb30b2a Consolidate html page title output logic into helper (#27563) 2023-11-09 13:05:57 +00:00
Claire
9b06c0f24a Fix Web UI not displaying appropriate explanation when a user hides their follows/followers (#27791) 2023-11-09 12:58:02 +00:00
Matt Jankowski
63c9102f8a Fix RSpec/MessageChain cop (#27776) 2023-11-09 12:57:23 +00:00
renovate[bot]
dec2796a4a Update dependency lint-staged to v15 (#27407)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-09 10:16:53 +00:00
renovate[bot]
4812832620 Update dependency axios to v1.6.1 (#27773)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-09 10:16:44 +00:00
renovate[bot]
23d88aa943 Update babel monorepo to v7.23.3 (#27789)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-09 10:16:36 +00:00
Yamagishi Kazutoshi
220a2e8fbf Run yarn dedupe when updating with Renovate (#27786) 2023-11-09 10:15:58 +00:00
github-actions[bot]
d6c971e158 New Crowdin Translations (automated) (#27787)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-11-09 09:54:14 +00:00
Matt Jankowski
ce1bd81c85 Reduce complexity in StatusCacheHydrator (#27783) 2023-11-09 09:53:44 +00:00
Matt Jankowski
c9204b792c Fix Rails/I18nLocaleTexts cop (#27779) 2023-11-09 09:46:39 +00:00
Matt Jankowski
c875dfc90b Fix Lint/UnusedBlockArgument cop (#27777) 2023-11-09 09:43:26 +00:00
renovate[bot]
5af47fbff8 Update dependency fog-openstack to v1 (#25968)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-09 09:06:57 +00:00
Matt Jankowski
69d00e2721 Fix RSpec/InstanceVariable cop (#27766) 2023-11-08 15:42:30 +00:00
Matt Jankowski
4329616c53 Ignore block result of send method and remove rubocop:disable in deepl spec (#27741) 2023-11-08 14:43:17 +00:00
Jaehong Kang
c73d5a6075 Using Sidekiq concurrency for default db pool value (#26488) 2023-11-08 13:15:43 +00:00
Matt Jankowski
13c3e59601 Use helper method to build batched status edits in admin/statuses/show (#27739) 2023-11-08 13:04:52 +00:00
Matt Jankowski
b05575e242 Move RSpec config for streaming/search managers to be near classes (#27761) 2023-11-08 13:04:17 +00:00
Matt Jankowski
ce91d14d48 Fix Style/WordArray cop (#27770) 2023-11-08 13:03:44 +00:00
Matt Jankowski
c03bd2a238 Don't stub SUT in FollowLimitValidator spec (#27760) 2023-11-08 12:49:46 +00:00
github-actions[bot]
5c328ae674 New Crowdin Translations (automated) (#27768)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-11-08 12:40:09 +00:00
Matt Jankowski
9b7b4d748f Simplify request cache spec shared examples (#27673) 2023-11-08 12:37:29 +00:00
Matt Jankowski
7d6bcc0615 Remove unmatched rubocop:enable declaration (#27769) 2023-11-08 12:02:09 +00:00
Matt Jankowski
33cc3ae8fa Fix Style/StabbyLambdaParentheses cop (#27771) 2023-11-08 12:01:18 +00:00
Renaud Chaput
757d7c73c0 Upgrade to Yarn 4, remove support for Node 16 (#27073) 2023-11-08 10:57:21 +00:00
Claire
23e32a4b30 Fix format-dependent redirects being cached regardless of requested format (#27632) 2023-11-08 10:31:05 +00:00
Matt Jankowski
e545978076 Use framework helpers instead of i-vars in controller specs (#27767) 2023-11-08 08:17:43 +00:00
Matt Jankowski
790f6356be Deduplicate yarn lock file (#27670) 2023-11-08 07:49:06 +00:00
João Pedro Marques
d3cd37d73e Feature - Prevents multiple audio/video attachments from being played at the same time (#24717) 2023-11-07 23:37:58 +00:00
renovate[bot]
389a6cc4c0 Update dependency net-http to '~> 0.4.0' (#27721)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 22:07:12 +00:00
renovate[bot]
14e49e5e36 Update dependency react-select to v5.8.0 (#27722)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 22:05:36 +00:00
renovate[bot]
565d9a6cf3 Update dependency sanitize to v6.1.0 (#27723)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 22:05:13 +00:00
renovate[bot]
8b0d86d407 Update dependency simple_form to v5.3.0 (#27725)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 22:04:40 +00:00
renovate[bot]
ac9f92bd73 Update formatjs monorepo (#27746)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 22:04:25 +00:00
renovate[bot]
b36b35318a Update dependency selenium-webdriver to v4.15.0 (#27649)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 22:03:31 +00:00
renovate[bot]
bcdfb72686 Update dependency json-ld-preloaded to v3.3.0 (#26763)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 22:03:09 +00:00
renovate[bot]
4b8aee7ad8 Update dependency thor to v1.3.0 (#27464)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 22:02:28 +00:00
renovate[bot]
1341790207 Update dependency sidekiq-unique-jobs to v7.1.30 (#26091)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 22:02:04 +00:00
renovate[bot]
b2619a10a5 Update dependency json-ld to v3.3.0 (#26762)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 22:01:09 +00:00
renovate[bot]
110ace0474 Update dependency json-schema to v4.1.1 (#26933)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 22:00:34 +00:00
renovate[bot]
feea257f44 Update dependency aws-sdk-s3 to v1.136.0 (#26999)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 22:00:13 +00:00
renovate[bot]
b41e9b1d29 Update dependency strong_migrations to v1.6.4 (#27631)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 21:59:35 +00:00
Jasmin Johal
27cbdc6f22 Change alt text to empty string for avatars (#21875)
Co-authored-by: Renaud Chaput <renchap@gmail.com>
2023-11-07 21:46:08 +00:00
Aleks Xhuvani
58f01a5c9a Add volume saving/reuse to video player (#27488) 2023-11-07 18:15:38 +00:00
Matt Jankowski
ee57bb4b44 Account statuses filter spec speedup (#27674) 2023-11-07 15:47:01 +00:00
Matt Jankowski
ae7c20b00c Move search and streaming spec manager classes to separate support files (#27727) 2023-11-07 15:25:49 +00:00
Matt Jankowski
02d27de5ce Move i18n locale configuration to separate initializer (#27571) 2023-11-07 15:22:14 +00:00
Matt Jankowski
370802ce48 Add coverage for CLI::Maintenance#fix_duplicates command (#25252) 2023-11-07 15:21:58 +00:00
Matt Jankowski
1d51e10510 Consolidate JSON parsing in serializers specs (#27693) 2023-11-07 15:20:24 +00:00
Matt Jankowski
a688a9ed20 Use strings instead of numeric literals and remove rubocop:disable in cache spec (#27742) 2023-11-07 15:20:00 +00:00
Matt Jankowski
dc0bf87090 Remove unused before block from settings/branding spec (#27759) 2023-11-07 15:19:17 +00:00
Renaud Chaput
f476d9dab2 Fix the notificationsUpdate call (#27758) 2023-11-07 14:18:59 +00:00
Matt Jankowski
45770c9306 Fix Performance/MapMethodChain cop (#27744) 2023-11-07 13:01:09 +00:00
Matt Jankowski
bbad5b6456 Remove false positive cop detection (#27457) 2023-11-07 10:44:15 +00:00
Matt Jankowski
49e2772064 Fix RSpec/MessageSpies cop (#27751) 2023-11-07 09:46:28 +00:00
Renaud Chaput
ae0d551d33 Do not copy public/packs-test into Docker (#27736) 2023-11-07 09:22:04 +00:00
Matt Jankowski
2862ad701f Stub controller methods and remove rubocop:disable in captcha feature spec (#27743) 2023-11-07 09:15:30 +00:00
Matt Jankowski
2d39268bc5 Fix Lint/OrAssignmentToConstant cop (#27750) 2023-11-07 09:11:50 +00:00
Matt Jankowski
cfa14ec6d1 Fix Lint/EmptyBlock cop (#27748) 2023-11-07 09:11:04 +00:00
Matt Jankowski
b06284c572 Fix RSpec/HookArgument cop (#27747) 2023-11-07 09:10:36 +00:00
github-actions[bot]
1b28ab7263 New Crowdin Translations (automated) (#27687)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-11-07 09:03:59 +00:00
Matt Jankowski
d6f50839e1 Fix RSpec/SpecFilePathFormat cops (#27730) 2023-11-06 16:25:40 +00:00
Matt Jankowski
c501d626e8 Fix Rails/BulkChangeTable cop (#26890) 2023-11-06 16:15:48 +00:00
Matt Jankowski
0c4e7c06dc Fix Rails/FindEach cop (#26886) 2023-11-06 15:53:29 +00:00
Matt Jankowski
fe26f33e0a Fix Rails/RedundantActiveRecordAllMethod cop (#26885) 2023-11-06 15:51:52 +00:00
Matt Jankowski
949f5eb860 Fix RSpec/MetadataStyle cop in spec/ (#27729) 2023-11-06 14:28:20 +00:00
zunda
e4e752c26e Adjust transform origin for favorite star (#27700) 2023-11-06 11:14:46 +00:00
renovate[bot]
9c34bb4d54 Update Node.js to v20.9 (#27714)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-06 10:27:42 +00:00
renovate[bot]
0ca27b2d81 Update DefinitelyTyped types (non-major) (#27713)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-06 10:27:19 +00:00
renovate[bot]
22b4713d2e Update eslint (non-major) (#27715)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-06 10:26:18 +00:00
Renaud Chaput
3bf896c973 Disable Babel polyfill injection in dev (#27691) 2023-11-06 10:24:41 +00:00
renovate[bot]
1416745a2b Update dependency faker to v3.2.2 (#27718)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-06 09:54:59 +00:00
renovate[bot]
afd1371fa3 Update dependency rubocop to v1.57.2 (#27719)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-06 09:53:13 +00:00
renovate[bot]
ef140da349 Update dependency discard to v1.3.0 (#27720)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-06 09:52:50 +00:00
Claire
c0989b78f8 Fix incoming status creation date not being restricted to standard ISO8601 (#27655) 2023-11-06 09:28:14 +00:00
Renaud Chaput
6712bf86cd Fixes website not loading for unlogged users (#27698) 2023-11-04 21:52:56 +00:00
Renaud Chaput
3bf2a7296e Use Immutable Record for accounts in Redux state (#26559) 2023-11-03 15:00:03 +00:00
Matt Jankowski
9d799d40ba Reduce CI job matrix job count (#27660) 2023-11-03 14:58:33 +00:00
Matt Jankowski
5d9e71ebe0 Archive uploaded CI assets into single file between build/test (#27668) 2023-11-03 14:12:14 +00:00
github-actions[bot]
2d548e273e New Crowdin Translations (automated) (#27646)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-11-03 10:10:13 +00:00
Claire
0337df3a42 Fix posts from threads received out-of-order sometimes not being inserted into timelines (#27653) 2023-11-02 14:58:37 +00:00
Renaud Chaput
2aa28e06d1 Mark version 4.0 as no longer supported (#27627) 2023-11-01 13:53:14 +00:00
mogaminsk
ac8e4ed38d Fix inserting emojis from emoji picker fails with TypeError (#27647) 2023-11-01 07:22:02 +00:00
Renaud Chaput
277e6968f5 Use helpers to check environment in frontend (#27633) 2023-10-31 16:05:44 +00:00
Matt Jankowski
7ef56d6e50 Move json_ld context loaders to config/initializers (#27590) 2023-10-31 15:21:23 +00:00
Matt Jankowski
3107a9410c Silence deprecation warning about secrets/credentials with Devise patch (#27578) 2023-10-31 11:10:15 +00:00
Matt Jankowski
b264318431 Update strong_migrations to version 1.3.0 (#25991) 2023-10-31 11:09:32 +00:00
Renaud Chaput
0e3401bc1c Improve Babel configuration and automatically load polyfills (#27333) 2023-10-31 10:55:13 +00:00
Claire
9c8891b39a Rewrite AutosuggestTextarea as Functional Component (#27618) 2023-10-31 10:17:37 +00:00
Matt Jankowski
a916251d8a Update haml-lint line length configuration to match rubocop value (#27570) 2023-10-31 09:47:16 +00:00
Claire
d649bbf28f Add some more tests and clean up domain block controller (#27469) 2023-10-31 09:40:30 +00:00
github-actions[bot]
e5b7ae9576 New Crowdin Translations (automated) (#27630)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-31 08:43:17 +00:00
renovate[bot]
147417a6d0 Update dependency rspec-sidekiq to v4.1.0 (#27593)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-31 08:26:21 +00:00
renovate[bot]
204c00b5c6 Update dependency bootsnap to '~> 1.17.0' (#27617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-31 08:24:04 +00:00
Matt Jankowski
beee9ea991 Fix RSpec/LetSetup cop in spec/controllers/admin area (#27619) 2023-10-31 08:22:19 +00:00
Claire
6c52f8286b Fix posts from force-sensitized accounts being able to trend (#27620) 2023-10-30 22:32:25 +00:00
renovate[bot]
b8adb08f92 Update dependency axios to v1.6.0 (#27580)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-30 22:31:32 +00:00
renovate[bot]
372494e553 Update dependency punycode to v2.3.1 (#27625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-30 22:31:22 +00:00
renovate[bot]
547d3c1b9b Update dependency core-js to v3.33.2 (#27624)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-30 22:30:26 +00:00
renovate[bot]
c2cc1df5ef Update dependency @types/react to v18.2.33 (#27615)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-30 22:30:02 +00:00
github-actions[bot]
a918208ec6 New Crowdin Translations (automated) (#27596)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-30 14:19:25 +00:00
renovate[bot]
bf1d452978 Update libretranslate/libretranslate Docker tag to v1.4.1 (#27616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-30 14:19:20 +00:00
Ricardo Trindade
33f8c1c5eb Remove version check from update cache_concern.rb (#27592) 2023-10-30 14:04:12 +00:00
Matt Jankowski
eae5c7334a Extract class from CSP configuration/initialization (#26905) 2023-10-27 16:20:40 +00:00
Matt Jankowski
2e6bf60f15 Use deliveries.size in mailer-related examples in controller specs (#27589) 2023-10-27 15:33:52 +00:00
Jonathan de Jong
1cc512909c Have Follow activities bypass availability (#27586)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2023-10-27 14:55:00 +00:00
Claire
93e4cdc31b Fix hashtag matching pattern matching some URLs (#27584) 2023-10-27 14:04:51 +00:00
SouthFox
08bdd5751e Fix account click on detailed status (#27587) 2023-10-27 14:03:21 +00:00
renovate[bot]
15ef654e9a Update dependency pundit to v2.3.1 (#27585)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-27 13:43:00 +00:00
Renaud Chaput
13d310e64d Simplify column headers (#27557) 2023-10-27 13:21:07 +00:00
Matt Jankowski
1f5187e2e2 Misc spec/refactor to user mailer and user mailer spec (#27486) 2023-10-27 09:57:16 +00:00
renovate[bot]
37929b9707 Update libretranslate/libretranslate Docker tag to v1.4.0 (#27504)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-27 09:56:45 +00:00
github-actions[bot]
8ca16f032e New Crowdin Translations (automated) (#27583)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-27 09:38:04 +00:00
Claire
bbf46cc418 Fix error and incorrect URLs in /api/v1/accounts/:id/featured_tags for remote accounts (#27459) 2023-10-27 08:35:21 +00:00
Jeong Arm
8f998cd96a Handle featured collections without items (#27581) 2023-10-27 02:36:22 +00:00
Eugen Rochko
fa7e64df1d Fix various icon styles in web UI (#27579) 2023-10-26 23:37:58 +00:00
Matt Jankowski
12550a6a28 Use Rails.env.local? shorthand method to check env (#27519) 2023-10-26 21:20:41 +00:00
Matt Jankowski
4aa05d45fc Capture minimum postgres version 12 (#27528) 2023-10-26 20:35:15 +00:00
Simon Rapilly
2d8f759a34 Add HTML lang attribute to preview card descriptions (#27503) 2023-10-26 20:34:15 +00:00
Claire
d2f52f7f64 Fix report processing notice not mentioning the report number when performing a custom action (#27442) 2023-10-26 17:03:31 +00:00
Mark T. Tomczak
ba8dcb50fe Issue 26048: swap "muting" and "blocking" list options in settings -> Data Exports (#26088) 2023-10-26 14:08:25 +00:00
Claire
75255c01fc Fix error when trying to delete already-deleted file with OpenStack Swift (#27569) 2023-10-26 13:09:48 +00:00
github-actions[bot]
3427b51d63 New Crowdin Translations (automated) (#27567)
Co-authored-by: GitHub Actions <noreply@github.com>
2023-10-26 11:05:47 +00:00
Renaud Chaput
537442853f Use a context to propagate column-related Props, and remove forceUpdate usage (#27548) 2023-10-26 11:00:10 +00:00
Matt Jankowski
3ca974e101 Use next keyword in field loop in admin/accounts/index view (#27559) 2023-10-26 10:52:14 +00:00
renovate[bot]
400f5c9174 Update dependency @material-symbols/svg-600 to v0.13.2 (#27565)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-26 10:49:39 +00:00
renovate[bot]
6e018f7228 Update dependency sass to v1.69.5 (#27566)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-26 10:48:55 +00:00
Claire
49b8433c56 Fix confusing screen when visiting a confirmation link for an already-confirmed email (#27368) 2023-10-25 21:33:44 +00:00
823 changed files with 27402 additions and 19816 deletions

View File

@@ -4,7 +4,7 @@ FROM mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye
# Install Rails
# RUN gem install rails webdrivers
ARG NODE_VERSION="16"
ARG NODE_VERSION="20"
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
# [Optional] Uncomment this section to install additional OS packages.
@@ -15,6 +15,6 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
RUN gem install foreman
# [Optional] Uncomment this line to install global node packages.
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g yarn" 2>&1
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && corepack enable" 2>&1
COPY welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt

View File

@@ -70,7 +70,7 @@ services:
hard: -1
libretranslate:
image: libretranslate/libretranslate:v1.3.12
image: libretranslate/libretranslate:v1.4.1
restart: unless-stopped
volumes:
- lt-data:/home/libretranslate/.local

View File

@@ -11,7 +11,8 @@ bundle install
git checkout -- Gemfile.lock
# Fetch Javascript dependencies
yarn --frozen-lockfile
corepack prepare
yarn install --immutable
# [re]create, migrate, and seed the test database
RAILS_ENV=test ./bin/rails db:setup

View File

@@ -8,6 +8,7 @@
public/system
public/assets
public/packs
public/packs-test
node_modules
neo4j
vendor/bundle

View File

@@ -1,5 +1,5 @@
# Node.js
NODE_ENV=tests
# In test, compile the NodeJS code as if we are in production
NODE_ENV=production
# Federation
LOCAL_DOMAIN=cb6e6126.ngrok.io
LOCAL_HTTPS=true

View File

@@ -11,9 +11,32 @@ runs:
- name: Set up Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: '.nvmrc'
# The following is needed because we can not use `cache: true` for `setup-node`, as it does not support Corepack yet and mess up with the cache location if ran after Node is installed
- name: Enable corepack
shell: bash
run: corepack enable
- name: Get yarn cache directory path
id: yarn-cache-dir-path
shell: bash
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install all yarn packages
shell: bash
run: yarn --frozen-lockfile ${{ inputs.onlyProduction != 'false' && '--production' || '' }}
run: yarn install --immutable
if: inputs.onlyProduction == 'false'
- name: Install all production yarn packages
shell: bash
run: yarn workspaces focus --production
if: inputs.onlyProduction != 'false'

View File

@@ -12,6 +12,7 @@
// If we do not want a package to be grouped with others, we need to set its groupName
// to `null` after any other rule set it to something.
dependencyDashboardHeader: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more. Before approving any upgrade: read the description and comments in the [`renovate.json5` file](https://github.com/mastodon/mastodon/blob/main/.github/renovate.json5).',
postUpdateOptions: ['yarnDedupeHighest'],
packageRules: [
{
// Require Dependency Dashboard Approval for major version bumps of these node packages

View File

@@ -48,12 +48,15 @@ jobs:
run: |-
./bin/rails assets:precompile
- name: Archive asset artifacts
run: |
tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs*
- uses: actions/upload-artifact@v3
if: matrix.mode == 'test'
with:
path: |-
./public/assets
./public/packs-test
./artifacts.tar.gz
name: ${{ github.sha }}
retention-days: 0
@@ -102,7 +105,6 @@ jobs:
SAML_ENABLED: true
CAS_ENABLED: true
BUNDLE_WITH: 'pam_authentication test'
CI_JOBS: ${{ matrix.ci_job }}/4
GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }}
strategy:
@@ -112,19 +114,18 @@ jobs:
- '3.0'
- '3.1'
- '.ruby-version'
ci_job:
- 1
- 2
- 3
- 4
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3
with:
path: './public'
path: './'
name: ${{ github.sha }}
- name: Expand archived asset artifacts
run: |
tar xvzf artifacts.tar.gz
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby
with:
@@ -134,7 +135,7 @@ jobs:
- name: Load database schema
run: './bin/rails db:create db:schema:load db:seed'
- run: bundle exec rake rspec_chunked
- run: bin/rspec
test-e2e:
name: End to End testing

9
.gitignore vendored
View File

@@ -55,6 +55,15 @@ npm-debug.log
yarn-error.log
yarn-debug.log
# From https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Ignore vagrant log files
*-cloudimg-console.log

View File

@@ -12,3 +12,5 @@ linters:
enabled: true
MiddleDot:
enabled: true
LineLength:
max: 320

View File

@@ -1,21 +1,33 @@
# This configuration was generated by
# `haml-lint --auto-gen-config`
# on 2023-10-25 08:29:48 -0400 using Haml-Lint version 0.51.0.
# on 2023-10-26 09:32:34 -0400 using Haml-Lint version 0.51.0.
# The point is for the user to remove these configuration records
# one by one as the lints are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of Haml-Lint, may require this file to be generated again.
linters:
# Offense count: 945
# Offense count: 16
LineLength:
enabled: false
exclude:
- 'app/views/admin/account_actions/new.html.haml'
- 'app/views/admin/accounts/index.html.haml'
- 'app/views/admin/ip_blocks/new.html.haml'
- 'app/views/admin/roles/_form.html.haml'
- 'app/views/admin/settings/discovery/show.html.haml'
- 'app/views/auth/registrations/edit.html.haml'
- 'app/views/auth/registrations/new.html.haml'
- 'app/views/filters/_filter_fields.html.haml'
- 'app/views/media/player.html.haml'
- 'app/views/settings/applications/_fields.html.haml'
- 'app/views/settings/imports/index.html.haml'
- 'app/views/settings/preferences/appearance/show.html.haml'
- 'app/views/settings/preferences/notifications/show.html.haml'
- 'app/views/settings/preferences/other/show.html.haml'
# Offense count: 10
# Offense count: 9
RuboCop:
exclude:
- 'app/views/admin/accounts/_buttons.html.haml'
- 'app/views/admin/accounts/_local_account.html.haml'
- 'app/views/admin/accounts/index.html.haml'
- 'app/views/admin/roles/_form.html.haml'
- 'app/views/layouts/application.html.haml'

View File

@@ -27,7 +27,7 @@ AllCops:
- 'node_modules/**/*'
- 'Vagrantfile'
- 'vendor/**/*'
- 'lib/json_ld/*' # Generated files
- 'config/initializers/json_ld*' # Generated files
- 'lib/mastodon/migration_helpers.rb' # Vendored from GitLab
- 'lib/templates/**/*'
@@ -109,16 +109,11 @@ Rails/Exit:
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecfilepath
RSpec/FilePath:
CustomTransform:
ActivityPub: activitypub # Ignore the snake_case due to the amount of files to rename
ActivityPub: activitypub
DeepL: deepl
FetchOEmbedService: fetch_oembed_service
JsonLdHelper: jsonld_helper
OEmbedController: oembed_controller
OStatus: ostatus
NodeInfoController: nodeinfo_controller # NodeInfo isn't snake_cased for any of the instances
Exclude:
- 'spec/config/initializers/rack_attack_spec.rb' # namespaces usually have separate folder
- 'spec/lib/sanitize_config_spec.rb' # namespaces usually have separate folder
# Reason:
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnamedsubject
@@ -135,6 +130,16 @@ RSpec/NotToNot:
RSpec/Rails/HttpStatus:
EnforcedStyle: numeric
# Reason: Match overrides from Rspec/FilePath rule above
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecspecfilepathformat
RSpec/SpecFilePathFormat:
CustomTransform:
ActivityPub: activitypub
DeepL: deepl
FetchOEmbedService: fetch_oembed_service
OEmbedController: oembed_controller
OStatus: ostatus
# Reason:
# https://docs.rubocop.org/rubocop/cops_style.html#styleclassandmodulechildren
Style/ClassAndModuleChildren:

View File

@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp`
# using RuboCop version 1.57.1.
# using RuboCop version 1.57.2.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@@ -20,34 +20,10 @@ Layout/LineLength:
Exclude:
- 'app/models/account.rb'
# Configuration parameters: AllowComments, AllowEmptyLambdas.
Lint/EmptyBlock:
Exclude:
- 'spec/controllers/api/v2/search_controller_spec.rb'
- 'spec/fabricators/access_token_fabricator.rb'
- 'spec/fabricators/conversation_fabricator.rb'
- 'spec/fabricators/system_key_fabricator.rb'
- 'spec/lib/activitypub/adapter_spec.rb'
- 'spec/models/user_role_spec.rb'
Lint/NonLocalExitFromIterator:
Exclude:
- 'app/helpers/jsonld_helper.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
Lint/OrAssignmentToConstant:
Exclude:
- 'lib/sanitize_ext/sanitize_config.rb'
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
Lint/UnusedBlockArgument:
Exclude:
- 'config/initializers/content_security_policy.rb'
- 'config/initializers/doorkeeper.rb'
- 'config/initializers/paperclip.rb'
- 'config/initializers/simple_form.rb'
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 144
@@ -67,76 +43,12 @@ Metrics/CyclomaticComplexity:
Metrics/PerceivedComplexity:
Max: 27
Performance/MapMethodChain:
Exclude:
- 'app/models/feed.rb'
- 'lib/mastodon/cli/maintenance.rb'
- 'spec/services/bulk_import_service_spec.rb'
- 'spec/services/import_service_spec.rb'
RSpec/AnyInstance:
Exclude:
- 'spec/controllers/activitypub/inboxes_controller_spec.rb'
- 'spec/controllers/admin/accounts_controller_spec.rb'
- 'spec/controllers/admin/resets_controller_spec.rb'
- 'spec/controllers/admin/settings/branding_controller_spec.rb'
- 'spec/controllers/auth/sessions_controller_spec.rb'
- 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb'
- 'spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb'
- 'spec/lib/request_spec.rb'
- 'spec/lib/status_filter_spec.rb'
- 'spec/models/account_spec.rb'
- 'spec/models/setting_spec.rb'
- 'spec/services/activitypub/process_collection_service_spec.rb'
- 'spec/validators/follow_limit_validator_spec.rb'
- 'spec/workers/activitypub/delivery_worker_spec.rb'
- 'spec/workers/web/push_notification_worker_spec.rb'
# Configuration parameters: CountAsOne.
RSpec/ExampleLength:
Max: 22
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: implicit, each, example
RSpec/HookArgument:
Exclude:
- 'spec/controllers/api/v1/streaming_controller_spec.rb'
- 'spec/controllers/well_known/webfinger_controller_spec.rb'
- 'spec/helpers/instance_helper_spec.rb'
- 'spec/models/user_spec.rb'
- 'spec/rails_helper.rb'
- 'spec/serializers/activitypub/note_serializer_spec.rb'
- 'spec/serializers/activitypub/update_poll_serializer_spec.rb'
- 'spec/services/import_service_spec.rb'
# Configuration parameters: AssignmentOnly.
RSpec/InstanceVariable:
Exclude:
- 'spec/controllers/api/v1/streaming_controller_spec.rb'
- 'spec/controllers/auth/confirmations_controller_spec.rb'
- 'spec/controllers/auth/passwords_controller_spec.rb'
- 'spec/controllers/auth/sessions_controller_spec.rb'
- 'spec/controllers/concerns/export_controller_concern_spec.rb'
- 'spec/controllers/home_controller_spec.rb'
- 'spec/controllers/settings/two_factor_authentication/webauthn_credentials_controller_spec.rb'
- 'spec/controllers/statuses_cleanup_controller_spec.rb'
- 'spec/models/concerns/account_finder_concern_spec.rb'
- 'spec/models/concerns/account_interactions_spec.rb'
- 'spec/models/public_feed_spec.rb'
- 'spec/serializers/activitypub/note_serializer_spec.rb'
- 'spec/serializers/activitypub/update_poll_serializer_spec.rb'
- 'spec/services/remove_status_service_spec.rb'
- 'spec/services/search_service_spec.rb'
- 'spec/services/unblock_domain_service_spec.rb'
RSpec/LetSetup:
Exclude:
- 'spec/controllers/admin/accounts_controller_spec.rb'
- 'spec/controllers/admin/action_logs_controller_spec.rb'
- 'spec/controllers/admin/instances_controller_spec.rb'
- 'spec/controllers/admin/reports/actions_controller_spec.rb'
- 'spec/controllers/admin/statuses_controller_spec.rb'
- 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb'
- 'spec/controllers/api/v1/filters_controller_spec.rb'
- 'spec/controllers/api/v2/admin/accounts_controller_spec.rb'
@@ -177,30 +89,6 @@ RSpec/LetSetup:
- 'spec/services/unsuspend_account_service_spec.rb'
- 'spec/workers/scheduler/user_cleanup_scheduler_spec.rb'
RSpec/MessageChain:
Exclude:
- 'spec/models/concerns/remotable_spec.rb'
- 'spec/models/session_activation_spec.rb'
- 'spec/models/setting_spec.rb'
# Configuration parameters: EnforcedStyle.
# SupportedStyles: have_received, receive
RSpec/MessageSpies:
Exclude:
- 'spec/controllers/admin/accounts_controller_spec.rb'
- 'spec/helpers/admin/account_moderation_notes_helper_spec.rb'
- 'spec/lib/webfinger_resource_spec.rb'
- 'spec/models/admin/account_action_spec.rb'
- 'spec/models/concerns/remotable_spec.rb'
- 'spec/models/follow_request_spec.rb'
- 'spec/models/identity_spec.rb'
- 'spec/models/session_activation_spec.rb'
- 'spec/models/setting_spec.rb'
- 'spec/services/activitypub/fetch_replies_service_spec.rb'
- 'spec/services/activitypub/process_collection_service_spec.rb'
- 'spec/spec_helper.rb'
- 'spec/validators/status_length_validator_spec.rb'
RSpec/MultipleExpectations:
Max: 8
@@ -217,13 +105,6 @@ Rails/ApplicationController:
Exclude:
- 'app/controllers/health_controller.rb'
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: Severity.
Rails/DuplicateAssociation:
Exclude:
- 'app/serializers/activitypub/collection_serializer.rb'
- 'app/serializers/activitypub/note_serializer.rb'
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/HasAndBelongsToMany:
@@ -247,11 +128,6 @@ Rails/HasManyOrHasOneDependent:
- 'app/models/user.rb'
- 'app/models/web/push_subscription.rb'
Rails/I18nLocaleTexts:
Exclude:
- 'lib/tasks/mastodon.rake'
- 'spec/helpers/flashes_helper_spec.rb'
# Configuration parameters: Include.
# Include: app/controllers/**/*.rb, app/mailers/**/*.rb
Rails/LexicallyScopedActionFilter:
@@ -338,7 +214,6 @@ Rails/SkipsModelValidations:
- 'db/post_migrate/20221101190723_backfill_admin_action_logs.rb'
- 'db/post_migrate/20221206114142_backfill_admin_action_logs_again.rb'
- 'lib/mastodon/cli/accounts.rb'
- 'lib/mastodon/cli/main.rb'
- 'lib/mastodon/cli/maintenance.rb'
- 'spec/lib/activitypub/activity/follow_spec.rb'
- 'spec/services/follow_service_spec.rb'
@@ -431,7 +306,6 @@ Style/FetchEnvVar:
- 'config/initializers/3_omniauth.rb'
- 'config/initializers/blacklists.rb'
- 'config/initializers/cache_buster.rb'
- 'config/initializers/content_security_policy.rb'
- 'config/initializers/devise.rb'
- 'config/initializers/paperclip.rb'
- 'config/initializers/vapid.rb'
@@ -629,14 +503,6 @@ Style/SingleArgumentDig:
Exclude:
- 'lib/webpacker/manifest_extensions.rb'
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: require_parentheses, require_no_parentheses
Style/StabbyLambdaParentheses:
Exclude:
- 'config/environments/production.rb'
- 'config/initializers/content_security_policy.rb'
# This cop supports safe autocorrection (--autocorrect).
Style/StderrPuts:
Exclude:
@@ -695,5 +561,3 @@ Style/TrailingCommaInHashLiteral:
Style/WordArray:
Exclude:
- 'app/helpers/languages_helper.rb'
- 'spec/controllers/settings/imports_controller_spec.rb'
- 'spec/models/form/import_spec.rb'

3
.watchmanconfig Normal file
View File

@@ -0,0 +1,3 @@
{
"ignore_dirs": ["node_modules/", "public/"]
}

0
.yarn/.gitkeep Normal file
View File

View File

@@ -0,0 +1,13 @@
diff --git a/lib/index.js b/lib/index.js
index 16ed6be8be8f555cc99096c2ff60954b42dc313d..d009c069770d066ad0db7ad02de1ea473a29334e 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -99,7 +99,7 @@ function lodash(_ref) {
var node = _ref3;
- if ((0, _types.isModuleDeclaration)(node)) {
+ if ((0, _types.isImportDeclaration)(node) || (0, _types.isExportDeclaration)(node)) {
isModule = true;
break;
}

View File

@@ -0,0 +1,22 @@
diff --git a/dist/index.js b/dist/index.js
index 57e375592d984e9a429bcd9f800fa2d15cd662e4..0c47d96df3608e23adfd77d887a8f72abbd501c0 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
});
exports.default = void 0;
-var _crypto = _interopRequireDefault(require("crypto"));
+var _createHash = _interopRequireDefault(require("webpack/lib/util/createHash"));
var _path = _interopRequireDefault(require("path"));
@@ -227,7 +227,7 @@ class CompressionPlugin {
originalAlgorithm: this.options.algorithm,
compressionOptions: this.options.compressionOptions,
name,
- contentHash: _crypto.default.createHash("md4").update(input).digest("hex")
+ contentHash: _createHash.default("md4").update(input).digest("hex")
};
} else {
cacheData.name = (0, _serializeJavascript.default)({

View File

@@ -1,49 +0,0 @@
# test directories
__tests__
test
tests
powered-test
# asset directories
docs
doc
website
images
# assets
# examples
example
examples
# code coverage directories
coverage
.nyc_output
# build scripts
Makefile
Gulpfile.js
Gruntfile.js
# configs
.tern-project
.gitattributes
.editorconfig
.*ignore
.eslintrc
.jshintrc
.flowconfig
.documentup.json
.yarn-metadata.json
.*.yml
*.yml
# misc
*.gz
*.md
# for specific ignore
!.svgo.yml
!sass-lint/**/*.yml
# breaks lint-staged or generally anything using https://github.com/eemeli/yaml/issues/384
!**/yaml/dist/**/doc

1
.yarnrc.yml Normal file
View File

@@ -0,0 +1 @@
nodeLinker: node-modules

View File

@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.4
# This needs to be bookworm-slim because the Ruby image is built on bookworm-slim
ARG NODE_VERSION="20.8-bookworm-slim"
ARG NODE_VERSION="20.9-bookworm-slim"
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.2-slim as ruby
FROM node:${NODE_VERSION} as build
@@ -13,7 +13,6 @@ ENV DEBIAN_FRONTEND="noninteractive" \
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
WORKDIR /opt/mastodon
COPY Gemfile* package.json yarn.lock /opt/mastodon/
# hadolint ignore=DL3008
RUN apt-get update && \
@@ -36,8 +35,14 @@ RUN apt-get update && \
bundle config set --local deployment 'true' && \
bundle config set --local without 'development test' && \
bundle config set silence_root_warning true && \
bundle install -j"$(nproc)" && \
yarn install --pure-lockfile --production --network-timeout 600000 && \
corepack enable
COPY Gemfile* package.json yarn.lock .yarnrc.yml /opt/mastodon/
COPY .yarn /opt/mastodon/.yarn
RUN bundle install -j"$(nproc)"
RUN yarn workspaces focus --all --production && \
yarn cache clean
FROM node:${NODE_VERSION}
@@ -78,7 +83,8 @@ RUN apt-get update && \
tzdata \
libreadline8 \
tini && \
ln -s /opt/mastodon /mastodon
ln -s /opt/mastodon /mastodon && \
corepack enable
# Note: no, cleaning here since Debian does this automatically
# See the file /etc/apt/apt.conf.d/docker-clean within the Docker image's filesystem

11
Gemfile
View File

@@ -16,14 +16,14 @@ gem 'dotenv-rails', '~> 2.8'
gem 'aws-sdk-s3', '~> 1.123', require: false
gem 'fog-core', '<= 2.4.0'
gem 'fog-openstack', '~> 0.3', require: false
gem 'fog-openstack', '~> 1.0', require: false
gem 'kt-paperclip', '~> 7.2'
gem 'md-paperclip-azure', '~> 2.2', require: false
gem 'blurhash', '~> 0.1'
gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.8'
gem 'bootsnap', '~> 1.16.0', require: false
gem 'bootsnap', '~> 1.17.0', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'chewy', '~> 7.3'
@@ -88,7 +88,7 @@ gem 'simple-navigation', '~> 4.4'
gem 'simple_form', '~> 5.2'
gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie'
gem 'stoplight', '~> 3.0.1'
gem 'strong_migrations', '~> 0.8'
gem 'strong_migrations', '1.6.4'
gem 'tty-prompt', '~> 0.23', require: false
gem 'twitter-text', '~> 3.1.0'
gem 'tzinfo-data', '~> 1.2023'
@@ -103,9 +103,6 @@ gem 'rdf-normalize', '~> 0.5'
gem 'private_address_check', '~> 0.5'
group :test do
# Used to split testing into chunks in CI
gem 'rspec_chunked', '~> 0.6'
# Adds RSpec Error/Warning annotations to GitHub PRs on the Files tab
gem 'rspec-github', '~> 2.4', require: false
@@ -198,7 +195,7 @@ gem 'xorcist', '~> 1.1'
gem 'cocoon', '~> 1.2'
gem 'net-http', '~> 0.3.2'
gem 'net-http', '~> 0.4.0'
gem 'rubyzip', '~> 2.3'
gem 'hcaptcha', '~> 7.1'

View File

@@ -39,50 +39,51 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.1.1)
actionpack (= 7.1.1)
activesupport (= 7.1.1)
actioncable (7.1.2)
actionpack (= 7.1.2)
activesupport (= 7.1.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.1.1)
actionpack (= 7.1.1)
activejob (= 7.1.1)
activerecord (= 7.1.1)
activestorage (= 7.1.1)
activesupport (= 7.1.1)
actionmailbox (7.1.2)
actionpack (= 7.1.2)
activejob (= 7.1.2)
activerecord (= 7.1.2)
activestorage (= 7.1.2)
activesupport (= 7.1.2)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.1.1)
actionpack (= 7.1.1)
actionview (= 7.1.1)
activejob (= 7.1.1)
activesupport (= 7.1.1)
actionmailer (7.1.2)
actionpack (= 7.1.2)
actionview (= 7.1.2)
activejob (= 7.1.2)
activesupport (= 7.1.2)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.2)
actionpack (7.1.1)
actionview (= 7.1.1)
activesupport (= 7.1.1)
actionpack (7.1.2)
actionview (= 7.1.2)
activesupport (= 7.1.2)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
actiontext (7.1.1)
actionpack (= 7.1.1)
activerecord (= 7.1.1)
activestorage (= 7.1.1)
activesupport (= 7.1.1)
actiontext (7.1.2)
actionpack (= 7.1.2)
activerecord (= 7.1.2)
activestorage (= 7.1.2)
activesupport (= 7.1.2)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.1.1)
activesupport (= 7.1.1)
actionview (7.1.2)
activesupport (= 7.1.2)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
@@ -92,22 +93,22 @@ GEM
activemodel (>= 4.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.1.1)
activesupport (= 7.1.1)
activejob (7.1.2)
activesupport (= 7.1.2)
globalid (>= 0.3.6)
activemodel (7.1.1)
activesupport (= 7.1.1)
activerecord (7.1.1)
activemodel (= 7.1.1)
activesupport (= 7.1.1)
activemodel (7.1.2)
activesupport (= 7.1.2)
activerecord (7.1.2)
activemodel (= 7.1.2)
activesupport (= 7.1.2)
timeout (>= 0.4.0)
activestorage (7.1.1)
actionpack (= 7.1.1)
activejob (= 7.1.1)
activerecord (= 7.1.1)
activesupport (= 7.1.1)
activestorage (7.1.2)
actionpack (= 7.1.2)
activejob (= 7.1.2)
activerecord (= 7.1.2)
activesupport (= 7.1.2)
marcel (~> 1.0)
activesupport (7.1.1)
activesupport (7.1.2)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
@@ -130,8 +131,8 @@ GEM
attr_required (1.0.1)
awrence (1.2.1)
aws-eventstream (1.2.0)
aws-partitions (1.809.0)
aws-sdk-core (3.181.0)
aws-partitions (1.828.0)
aws-sdk-core (3.183.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
@@ -139,7 +140,7 @@ GEM
aws-sdk-kms (1.71.0)
aws-sdk-core (~> 3, >= 3.177.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.133.0)
aws-sdk-s3 (1.136.0)
aws-sdk-core (~> 3, >= 3.181.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.6)
@@ -154,6 +155,7 @@ GEM
net-http-persistent (~> 4.0)
nokogiri (~> 1, >= 1.10.8)
base64 (0.1.1)
bcp47_spec (0.2.1)
bcrypt (3.1.19)
better_errors (2.10.1)
erubi (>= 1.0.0)
@@ -171,7 +173,7 @@ GEM
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
blurhash (0.1.7)
bootsnap (1.16.0)
bootsnap (1.17.0)
msgpack (~> 1.2)
brakeman (6.0.1)
browser (5.3.1)
@@ -217,7 +219,7 @@ GEM
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
date (3.3.3)
date (3.3.4)
debug_inspector (1.1.0)
devise (4.9.3)
bcrypt (~> 3.0)
@@ -235,7 +237,7 @@ GEM
devise (>= 4.0.0)
rpam2 (~> 4.0)
diff-lcs (1.5.0)
discard (1.2.1)
discard (1.3.0)
activerecord (>= 4.2, < 8)
docile (1.4.0)
domain_name (0.5.20190701)
@@ -262,9 +264,9 @@ GEM
erubi (1.12.0)
et-orbi (1.2.7)
tzinfo
excon (0.100.0)
excon (0.104.0)
fabrication (2.30.0)
faker (3.2.1)
faker (3.2.2)
i18n (>= 1.8.11, < 2)
faraday (1.10.3)
faraday-em_http (~> 1.0)
@@ -297,19 +299,18 @@ GEM
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
fog-core (2.1.0)
fog-core (2.3.0)
builder
excon (~> 0.58)
formatador (~> 0.2)
excon (~> 0.71)
formatador (>= 0.2, < 2.0)
mime-types
fog-json (1.2.0)
fog-core
multi_json (~> 1.10)
fog-openstack (0.3.10)
fog-core (>= 1.45, <= 2.1.0)
fog-openstack (1.1.0)
fog-core (~> 2.1)
fog-json (>= 1.0)
ipaddress (>= 0.8)
formatador (0.3.0)
formatador (1.1.0)
fugit (1.8.1)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
@@ -369,29 +370,28 @@ GEM
terminal-table (>= 1.5.1)
idn-ruby (0.1.5)
io-console (0.6.0)
ipaddress (0.8.3)
irb (1.8.1)
irb (1.8.3)
rdoc
reline (>= 0.3.8)
jmespath (1.6.2)
json (2.6.3)
json-canonicalization (0.3.2)
json-canonicalization (1.0.0)
json-jwt (1.15.3)
activesupport (>= 4.2)
aes_key_wrap
bindata
httpclient
json-ld (3.2.5)
json-ld (3.3.1)
htmlentities (~> 4.3)
json-canonicalization (~> 0.3, >= 0.3.2)
json-canonicalization (~> 1.0)
link_header (~> 0.0, >= 0.0.8)
multi_json (~> 1.15)
rack (>= 2.2, < 4)
rdf (~> 3.2, >= 3.2.10)
json-ld-preloaded (3.2.2)
json-ld (~> 3.2)
rdf (~> 3.2)
json-schema (4.0.0)
rdf (~> 3.3)
json-ld-preloaded (3.3.0)
json-ld (~> 3.3)
rdf (~> 3.3)
json-schema (4.1.1)
addressable (>= 2.8)
jsonapi-renderer (0.2.2)
jwt (2.7.1)
@@ -451,25 +451,25 @@ GEM
memory_profiler (1.0.1)
mime-types (3.5.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2023.0808)
mime-types-data (3.2023.1003)
mini_mime (1.1.5)
mini_portile2 (2.8.4)
mini_portile2 (2.8.5)
minitest (5.20.0)
msgpack (1.7.1)
msgpack (1.7.2)
multi_json (1.15.0)
multipart-post (2.3.0)
mutex_m (0.1.2)
net-http (0.3.2)
net-http (0.4.0)
uri
net-http-persistent (4.0.2)
connection_pool (~> 2.2)
net-imap (0.4.1)
net-imap (0.4.4)
date
net-protocol
net-ldap (0.18.0)
net-pop (0.1.2)
net-protocol
net-protocol (0.2.1)
net-protocol (0.2.2)
timeout
net-smtp (0.4.0)
net-protocol
@@ -527,15 +527,15 @@ GEM
net-smtp
premailer (~> 1.7, >= 1.7.9)
private_address_check (0.5.0)
psych (5.1.1)
psych (5.1.1.1)
stringio
public_suffix (5.0.3)
puma (6.4.0)
nio4r (~> 2.0)
pundit (2.3.0)
pundit (2.3.1)
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.7.1)
racc (1.7.3)
rack (2.2.8)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
@@ -558,20 +558,20 @@ GEM
rackup (1.0.0)
rack (< 3)
webrick
rails (7.1.1)
actioncable (= 7.1.1)
actionmailbox (= 7.1.1)
actionmailer (= 7.1.1)
actionpack (= 7.1.1)
actiontext (= 7.1.1)
actionview (= 7.1.1)
activejob (= 7.1.1)
activemodel (= 7.1.1)
activerecord (= 7.1.1)
activestorage (= 7.1.1)
activesupport (= 7.1.1)
rails (7.1.2)
actioncable (= 7.1.2)
actionmailbox (= 7.1.2)
actionmailer (= 7.1.2)
actionpack (= 7.1.2)
actiontext (= 7.1.2)
actionview (= 7.1.2)
activejob (= 7.1.2)
activemodel (= 7.1.2)
activerecord (= 7.1.2)
activestorage (= 7.1.2)
activesupport (= 7.1.2)
bundler (>= 1.15.0)
railties (= 7.1.1)
railties (= 7.1.2)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@@ -586,9 +586,9 @@ GEM
rails-i18n (7.0.8)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
railties (7.1.1)
actionpack (= 7.1.1)
activesupport (= 7.1.1)
railties (7.1.2)
actionpack (= 7.1.2)
activesupport (= 7.1.2)
irb
rackup (>= 1.0.0)
rake (>= 12.2)
@@ -596,7 +596,8 @@ GEM
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.0.6)
rdf (3.2.11)
rdf (3.3.1)
bcp47_spec (~> 0.2)
link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.6.1)
rdf (~> 3.2)
@@ -631,7 +632,7 @@ GEM
rspec-support (~> 3.12.0)
rspec-github (2.4.0)
rspec-core (~> 3.0)
rspec-mocks (3.12.5)
rspec-mocks (3.12.6)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-rails (6.0.3)
@@ -642,15 +643,13 @@ GEM
rspec-expectations (~> 3.12)
rspec-mocks (~> 3.12)
rspec-support (~> 3.12)
rspec-sidekiq (4.0.1)
rspec-sidekiq (4.1.0)
rspec-core (~> 3.0)
rspec-expectations (~> 3.0)
rspec-mocks (~> 3.0)
sidekiq (>= 5, < 8)
rspec-support (3.12.1)
rspec_chunked (0.6)
rubocop (1.57.1)
base64 (~> 0.1.1)
rubocop (1.57.2)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
@@ -661,21 +660,21 @@ GEM
rubocop-ast (>= 1.28.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.29.0)
rubocop-ast (1.30.0)
parser (>= 3.2.1.0)
rubocop-capybara (2.19.0)
rubocop (~> 1.41)
rubocop-factory_bot (2.23.1)
rubocop-factory_bot (2.24.0)
rubocop (~> 1.33)
rubocop-performance (1.19.1)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.20.2)
rubocop-rails (2.22.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-rspec (2.23.2)
rubocop (~> 1.33)
rubocop-rspec (2.25.0)
rubocop (~> 1.40)
rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22)
ruby-prof (1.6.3)
@@ -689,13 +688,13 @@ GEM
fugit (~> 1.1, >= 1.1.6)
safety_net_attestation (0.4.0)
jwt (~> 2.0)
sanitize (6.0.2)
sanitize (6.1.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
scenic (1.7.0)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
selenium-webdriver (4.13.1)
selenium-webdriver (4.15.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
@@ -710,7 +709,7 @@ GEM
rufus-scheduler (~> 3.2)
sidekiq (>= 6, < 8)
tilt (>= 1.4.0)
sidekiq-unique-jobs (7.1.29)
sidekiq-unique-jobs (7.1.30)
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
concurrent-ruby (~> 1.0, >= 1.0.5)
redis (< 5.0)
@@ -718,7 +717,7 @@ GEM
thor (>= 0.20, < 3.0)
simple-navigation (4.4.0)
activesupport (>= 2.3.2)
simple_form (5.2.0)
simple_form (5.3.0)
actionpack (>= 5.2)
activemodel (>= 5.2)
simplecov (0.22.0)
@@ -739,8 +738,8 @@ GEM
statsd-ruby (1.5.0)
stoplight (3.0.2)
redlock (~> 1.0)
stringio (3.0.8)
strong_migrations (0.8.0)
stringio (3.0.9)
strong_migrations (1.6.4)
activerecord (>= 5.2)
swd (1.3.0)
activesupport (>= 3)
@@ -753,9 +752,9 @@ GEM
terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0)
test-prof (1.2.3)
thor (1.2.2)
thor (1.3.0)
tilt (2.3.0)
timeout (0.4.0)
timeout (0.4.1)
tpm-key_attestation (0.12.0)
bindata (~> 2.4)
openssl (> 2.0)
@@ -833,7 +832,7 @@ DEPENDENCIES
better_errors (~> 2.9)
binding_of_caller (~> 1.0)
blurhash (~> 0.1)
bootsnap (~> 1.16.0)
bootsnap (~> 1.17.0)
brakeman (~> 6.0)
browser
bundler-audit (~> 0.9)
@@ -858,7 +857,7 @@ DEPENDENCIES
fast_blank (~> 1.0)
fastimage
fog-core (<= 2.4.0)
fog-openstack (~> 0.3)
fog-openstack (~> 1.0)
fuubar (~> 2.5)
haml-rails (~> 2.0)
haml_lint
@@ -883,7 +882,7 @@ DEPENDENCIES
md-paperclip-azure (~> 2.2)
memory_profiler
mime-types (~> 3.5.0)
net-http (~> 0.3.2)
net-http (~> 0.4.0)
net-ldap (~> 0.18)
nokogiri (~> 1.15)
nsa!
@@ -919,7 +918,6 @@ DEPENDENCIES
rspec-github (~> 2.4)
rspec-rails (~> 6.0)
rspec-sidekiq (~> 4.0)
rspec_chunked (~> 0.6)
rubocop
rubocop-capybara
rubocop-performance
@@ -942,7 +940,7 @@ DEPENDENCIES
sprockets-rails (~> 3.4)
stackprof
stoplight (~> 3.0.1)
strong_migrations (~> 0.8)
strong_migrations (= 1.6.4)
test-prof
thor (~> 1.2)
tty-prompt (~> 0.23)

View File

@@ -17,6 +17,6 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
| ------- | ---------------- |
| 4.2.x | Yes |
| 4.1.x | Yes |
| 4.0.x | Until 2023-10-31 |
| 4.0.x | No |
| 3.5.x | Until 2023-12-31 |
| < 3.5 | No |

4
Vagrantfile vendored
View File

@@ -112,11 +112,11 @@ bundle install
# Install node modules
sudo corepack enable
yarn set version classic
corepack prepare
yarn install
# Build Mastodon
export RAILS_ENV=development
export RAILS_ENV=development
export $(cat ".env.vagrant" | xargs)
bundle exec rails db:setup

View File

@@ -53,7 +53,7 @@ class PublicStatusesIndex < Chewy::Index
index_scope ::Status.unscoped
.kept
.indexable
.includes(:media_attachments, :preloadable_poll, :preview_cards, :tags)
.includes(:media_attachments, :preloadable_poll, :tags, preview_cards_status: :preview_card)
root date_detection: false do
field(:id, type: 'long')

View File

@@ -50,7 +50,7 @@ class StatusesIndex < Chewy::Index
},
}
index_scope ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :preview_cards, :local_mentioned, :local_favorited, :local_reblogged, :local_bookmarked, :tags, preloadable_poll: :local_voters), delete_if: ->(status) { status.searchable_by.empty? }
index_scope ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :local_mentioned, :local_favorited, :local_reblogged, :local_bookmarked, :tags, preview_cards_status: :preview_card, preloadable_poll: :local_voters), delete_if: ->(status) { status.searchable_by.empty? }
root date_detection: false do
field(:id, type: 'long')

View File

@@ -21,7 +21,7 @@ module Admin
account_action.save!
if account_action.with_report?
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: params[:report_id])
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id])
else
redirect_to admin_account_path(@account.id)
end

View File

@@ -20,7 +20,7 @@ class Admin::Disputes::AppealsController < Admin::BaseController
authorize @appeal, :approve?
log_action :reject, @appeal
@appeal.reject!(current_account)
UserMailer.appeal_rejected(@appeal.account.user, @appeal)
UserMailer.appeal_rejected(@appeal.account.user, @appeal).deliver_later
redirect_to disputes_strike_path(@appeal.strike)
end

View File

@@ -33,7 +33,7 @@ module Admin
# Disallow accidentally downgrading a domain block
if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
@domain_block.save
@domain_block.validate
flash.now[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe
@domain_block.errors.delete(:domain)
return render :new

View File

@@ -31,6 +31,11 @@ module Admin
private
def batched_ordered_status_edits
@status.edits.reorder(nil).includes(:account, status: [:account]).find_each(order: :asc)
end
helper_method :batched_ordered_status_edits
def admin_status_batch_action_params
params.require(:admin_status_batch_action).permit(status_ids: [])
end

View File

@@ -7,6 +7,7 @@ class Api::BaseController < ApplicationController
include RateLimitHeaders
include AccessTokenTrackingConcern
include ApiCachingConcern
include Api::ContentSecurityPolicy
skip_before_action :require_functional!, unless: :limited_federation_mode?
@@ -17,26 +18,6 @@ class Api::BaseController < ApplicationController
protect_from_forgery with: :null_session
content_security_policy do |p|
# Set every directive that does not have a fallback
p.default_src :none
p.frame_ancestors :none
p.form_action :none
# Disable every directive with a fallback to cut on response size
p.base_uri false
p.font_src false
p.img_src false
p.style_src false
p.media_src false
p.frame_src false
p.manifest_src false
p.connect_src false
p.script_src false
p.child_src false
p.worker_src false
end
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
render json: { error: e.to_s }, status: 422
end

View File

@@ -5,10 +5,11 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
before_action :require_user!
def index
accounts = Account.where(id: account_ids).select('id')
scope = Account.where(id: account_ids).select('id')
scope.merge!(Account.without_suspended) unless truthy_param?(:with_suspended)
# .where doesn't guarantee that our results are in the same order
# we requested them, so return the "right" order to the requestor.
@accounts = accounts.index_by(&:id).values_at(*account_ids).compact
@accounts = scope.index_by(&:id).values_at(*account_ids).compact
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
end

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Api::V1::AccountsController < Api::BaseController
include RegistrationHelper
before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:create, :follow, :unfollow, :remove_from_followers, :block, :unblock, :mute, :unmute]
before_action -> { doorkeeper_authorize! :follow, :write, :'write:follows' }, only: [:follow, :unfollow, :remove_from_followers]
before_action -> { doorkeeper_authorize! :follow, :write, :'write:mutes' }, only: [:mute, :unmute]
@@ -90,18 +92,14 @@ class Api::V1::AccountsController < Api::BaseController
end
def account_params
params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone)
params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone, :invite_code)
end
def invite
Invite.find_by(code: params[:invite_code]) if params[:invite_code].present?
end
def check_enabled_registrations
forbidden if single_user_mode? || omniauth_only? || !allowed_registrations?
end
def allowed_registrations?
Setting.registrations_mode != 'none'
end
def omniauth_only?
ENV['OMNIAUTH_ONLY'] == 'true'
forbidden unless allowed_registration?(request.remote_ip, invite)
end
end

View File

@@ -41,10 +41,10 @@ class Api::V1::ConversationsController < Api::BaseController
account: :account_stat,
last_status: [
:media_attachments,
:preview_cards,
:status_stat,
:tags,
{
preview_cards_status: :preview_card,
active_mentions: [account: :account_stat],
account: :account_stat,
},

View File

@@ -1,12 +1,8 @@
# frozen_string_literal: true
class Api::V1::Instances::ActivityController < Api::BaseController
class Api::V1::Instances::ActivityController < Api::V1::Instances::BaseController
before_action :require_enabled_api!
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
vary_by ''
def show
cache_even_if_authenticated!
render_with_cache json: :activity, expires_in: 1.day

View File

@@ -0,0 +1,8 @@
# frozen_string_literal: true
class Api::V1::Instances::BaseController < Api::BaseController
skip_before_action :require_authenticated_user!,
unless: :limited_federation_mode?
vary_by ''
end

View File

@@ -1,8 +1,6 @@
# frozen_string_literal: true
class Api::V1::Instances::DomainBlocksController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
class Api::V1::Instances::DomainBlocksController < Api::V1::Instances::BaseController
before_action :require_enabled_api!
before_action :set_domain_blocks
@@ -15,7 +13,7 @@ class Api::V1::Instances::DomainBlocksController < Api::BaseController
cache_if_unauthenticated!
end
render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: (Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?))
render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: show_rationale_in_response?
end
private
@@ -27,4 +25,16 @@ class Api::V1::Instances::DomainBlocksController < Api::BaseController
def set_domain_blocks
@domain_blocks = DomainBlock.with_user_facing_limitations.by_severity
end
def show_rationale_in_response?
always_show_rationale? || show_rationale_for_user?
end
def always_show_rationale?
Setting.show_domain_blocks_rationale == 'all'
end
def show_rationale_for_user?
Setting.show_domain_blocks_rationale == 'users' && user_signed_in?
end
end

View File

@@ -1,13 +1,10 @@
# frozen_string_literal: true
class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
class Api::V1::Instances::ExtendedDescriptionsController < Api::V1::Instances::BaseController
skip_around_action :set_locale
before_action :set_extended_description
vary_by ''
# Override `current_user` to avoid reading session cookies unless in whitelist mode
def current_user
super if limited_federation_mode?

View File

@@ -1,13 +1,10 @@
# frozen_string_literal: true
class Api::V1::Instances::LanguagesController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
class Api::V1::Instances::LanguagesController < Api::V1::Instances::BaseController
skip_around_action :set_locale
before_action :set_languages
vary_by ''
def show
cache_even_if_authenticated!
render json: @languages, each_serializer: REST::LanguageSerializer

View File

@@ -1,13 +1,10 @@
# frozen_string_literal: true
class Api::V1::Instances::PeersController < Api::BaseController
class Api::V1::Instances::PeersController < Api::V1::Instances::BaseController
before_action :require_enabled_api!
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
skip_around_action :set_locale
vary_by ''
# Override `current_user` to avoid reading session cookies unless in whitelist mode
def current_user
super if limited_federation_mode?

View File

@@ -1,12 +1,8 @@
# frozen_string_literal: true
class Api::V1::Instances::PrivacyPoliciesController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
class Api::V1::Instances::PrivacyPoliciesController < Api::V1::Instances::BaseController
before_action :set_privacy_policy
vary_by ''
def show
cache_even_if_authenticated!
render json: @privacy_policy, serializer: REST::PrivacyPolicySerializer

View File

@@ -1,13 +1,10 @@
# frozen_string_literal: true
class Api::V1::Instances::RulesController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
class Api::V1::Instances::RulesController < Api::V1::Instances::BaseController
skip_around_action :set_locale
before_action :set_rules
vary_by ''
# Override `current_user` to avoid reading session cookies unless in whitelist mode
def current_user
super if limited_federation_mode?

View File

@@ -1,12 +1,8 @@
# frozen_string_literal: true
class Api::V1::Instances::TranslationLanguagesController < Api::BaseController
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
class Api::V1::Instances::TranslationLanguagesController < Api::V1::Instances::BaseController
before_action :set_languages
vary_by ''
def show
cache_even_if_authenticated!
render json: @languages

View File

@@ -0,0 +1,30 @@
# frozen_string_literal: true
class Api::V1::InvitesController < Api::BaseController
include RegistrationHelper
skip_before_action :require_authenticated_user!
skip_around_action :set_locale
before_action :set_invite
before_action :check_enabled_registrations!
# Override `current_user` to avoid reading session cookies
def current_user; end
def show
render json: { invite_code: params[:invite_code], instance_api_url: api_v2_instance_url }, status: 200
end
private
def set_invite
@invite = Invite.find_by!(code: params[:invite_code])
end
def check_enabled_registrations!
return render json: { error: I18n.t('invites.invalid') }, status: 401 unless @invite.valid_for_use?
raise Mastodon::NotPermittedError unless allowed_registration?(request.remote_ip, @invite)
end
end

View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true
class Api::V1::Statuses::BaseController < Api::BaseController
include Authorization
before_action :set_status
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@@ -1,11 +1,9 @@
# frozen_string_literal: true
class Api::V1::Statuses::BookmarksController < Api::BaseController
include Authorization
class Api::V1::Statuses::BookmarksController < Api::V1::Statuses::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' }
before_action :require_user!
before_action :set_status, only: [:create]
skip_before_action :set_status, only: [:destroy]
def create
current_account.bookmarks.find_or_create_by!(account: current_account, status: @status)
@@ -28,13 +26,4 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
rescue Mastodon::NotPermittedError
not_found
end
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@@ -1,10 +1,7 @@
# frozen_string_literal: true
class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
include Authorization
class Api::V1::Statuses::FavouritedByAccountsController < Api::V1::Statuses::BaseController
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
before_action :set_status
after_action :insert_pagination_headers
def index
@@ -61,13 +58,6 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end

View File

@@ -1,11 +1,9 @@
# frozen_string_literal: true
class Api::V1::Statuses::FavouritesController < Api::BaseController
include Authorization
class Api::V1::Statuses::FavouritesController < Api::V1::Statuses::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
before_action :require_user!
before_action :set_status, only: [:create]
skip_before_action :set_status, only: [:destroy]
def create
FavouriteService.new.call(current_account, @status)
@@ -30,13 +28,4 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
rescue Mastodon::NotPermittedError
not_found
end
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@@ -1,10 +1,7 @@
# frozen_string_literal: true
class Api::V1::Statuses::HistoriesController < Api::BaseController
include Authorization
class Api::V1::Statuses::HistoriesController < Api::V1::Statuses::BaseController
before_action -> { authorize_if_got_token! :read, :'read:statuses' }
before_action :set_status
def show
cache_if_unauthenticated!
@@ -16,11 +13,4 @@ class Api::V1::Statuses::HistoriesController < Api::BaseController
def status_edits
@status.edits.includes(:account, status: [:account]).to_a.presence || [@status.build_snapshot(at_time: @status.edited_at || @status.created_at)]
end
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@@ -1,11 +1,8 @@
# frozen_string_literal: true
class Api::V1::Statuses::MutesController < Api::BaseController
include Authorization
class Api::V1::Statuses::MutesController < Api::V1::Statuses::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:mutes' }
before_action :require_user!
before_action :set_status
before_action :set_conversation
def create
@@ -24,13 +21,6 @@ class Api::V1::Statuses::MutesController < Api::BaseController
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
def set_conversation
@conversation = @status.conversation
raise Mastodon::ValidationError if @conversation.nil?

View File

@@ -1,11 +1,8 @@
# frozen_string_literal: true
class Api::V1::Statuses::PinsController < Api::BaseController
include Authorization
class Api::V1::Statuses::PinsController < Api::V1::Statuses::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }
before_action :require_user!
before_action :set_status
def create
StatusPin.create!(account: current_account, status: @status)
@@ -26,10 +23,6 @@ class Api::V1::Statuses::PinsController < Api::BaseController
private
def set_status
@status = Status.find(params[:status_id])
end
def distribute_add_activity!
json = ActiveModelSerializers::SerializableResource.new(
@status,

View File

@@ -1,10 +1,7 @@
# frozen_string_literal: true
class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
include Authorization
class Api::V1::Statuses::RebloggedByAccountsController < Api::V1::Statuses::BaseController
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
before_action :set_status
after_action :insert_pagination_headers
def index
@@ -57,13 +54,6 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end

View File

@@ -1,13 +1,13 @@
# frozen_string_literal: true
class Api::V1::Statuses::ReblogsController < Api::BaseController
include Authorization
class Api::V1::Statuses::ReblogsController < Api::V1::Statuses::BaseController
include Redisable
include Lockable
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }
before_action :require_user!
before_action :set_reblog, only: [:create]
skip_before_action :set_status
override_rate_limit_headers :create, family: :statuses

View File

@@ -1,21 +1,9 @@
# frozen_string_literal: true
class Api::V1::Statuses::SourcesController < Api::BaseController
include Authorization
class Api::V1::Statuses::SourcesController < Api::V1::Statuses::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
before_action :set_status
def show
render json: @status, serializer: REST::StatusSourceSerializer
end
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end

View File

@@ -1,10 +1,7 @@
# frozen_string_literal: true
class Api::V1::Statuses::TranslationsController < Api::BaseController
include Authorization
class Api::V1::Statuses::TranslationsController < Api::V1::Statuses::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
before_action :set_status
before_action :set_translation
rescue_from TranslationService::NotConfiguredError, with: :not_found
@@ -24,13 +21,6 @@ class Api::V1::Statuses::TranslationsController < Api::BaseController
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
def set_translation
@translation = TranslateStatusService.new.call(@status, content_locale)
end

View File

@@ -176,7 +176,10 @@ class ApplicationController < ActionController::Base
return unless self_destruct?
respond_to do |format|
format.any { render 'errors/self_destruct', layout: 'auth', status: 410, formats: [:html] }
format.any do
use_pack 'error'
render 'errors/self_destruct', layout: 'auth', status: 410, formats: [:html]
end
format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[410] }, status: code }
end
end

View File

@@ -40,6 +40,12 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
show
end
def redirect_to_app?
truthy_param?(:redirect_to_app)
end
helper_method :redirect_to_app?
private
def require_captcha_if_needed!
@@ -87,7 +93,7 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
end
def after_confirmation_path_for(_resource_name, user)
if user.created_by_application && truthy_param?(:redirect_to_app)
if user.created_by_application && redirect_to_app?
user.created_by_application.confirmation_redirect_uri
elsif user_signed_in?
web_url('start')

View File

@@ -1,6 +1,7 @@
# frozen_string_literal: true
class Auth::RegistrationsController < Devise::RegistrationsController
include RegistrationHelper
include RegistrationSpamConcern
layout :determine_layout
@@ -83,19 +84,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def check_enabled_registrations
redirect_to root_path if single_user_mode? || omniauth_only? || !allowed_registrations? || ip_blocked?
end
def allowed_registrations?
Setting.registrations_mode != 'none' || @invite&.valid_for_use?
end
def omniauth_only?
ENV['OMNIAUTH_ONLY'] == 'true'
end
def ip_blocked?
IpBlock.where(severity: :sign_up_block).where('ip >>= ?', request.remote_ip.to_s).exists?
redirect_to root_path unless allowed_registration?(request.remote_ip, @invite)
end
def invite_code

View File

@@ -0,0 +1,27 @@
# frozen_string_literal: true
module Api::ContentSecurityPolicy
extend ActiveSupport::Concern
included do
content_security_policy do |policy|
# Set every directive that does not have a fallback
policy.default_src :none
policy.frame_ancestors :none
policy.form_action :none
# Disable every directive with a fallback to cut on response size
policy.base_uri false
policy.font_src false
policy.img_src false
policy.style_src false
policy.media_src false
policy.frame_src false
policy.manifest_src false
policy.connect_src false
policy.script_src false
policy.child_src false
policy.worker_src false
end
end
end

View File

@@ -92,18 +92,10 @@ module CacheConcern
arguments
end
if Rails.gem_version >= Gem::Version.new('7.0')
def attributes_for_database(record)
attributes = record.attributes_for_database
attributes.transform_values! { |attr| attr.is_a?(::ActiveModel::Type::Binary::Data) ? attr.to_s : attr }
attributes
end
else
def attributes_for_database(record)
attributes = record.instance_variable_get(:@attributes).send(:attributes).transform_values(&:value_for_database)
attributes.transform_values! { |attr| attr.is_a?(::ActiveModel::Type::Binary::Data) ? attr.to_s : attr }
attributes
end
def attributes_for_database(record)
attributes = record.attributes_for_database
attributes.transform_values! { |attr| attr.is_a?(::ActiveModel::Type::Binary::Data) ? attr.to_s : attr }
attributes
end
def deserialize_record(class_name, attributes_from_database, new_record = false) # rubocop:disable Style/OptionalBooleanParameter

View File

@@ -6,7 +6,7 @@ module Admin::AccountModerationNotesHelper
link_to path || admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
safe_join([
image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'),
image_tag(account.avatar.url, width: 15, height: 15, alt: '', class: 'avatar'),
content_tag(:span, account.acct, class: 'username'),
], ' ')
end

View File

@@ -91,6 +91,14 @@ module ApplicationHelper
end
end
def html_title
safe_join(
[content_for(:page_title).to_s.chomp, title]
.select(&:present?),
' - '
)
end
def title
Rails.env.production? ? site_title : "#{site_title} (Dev)"
end

View File

@@ -298,5 +298,3 @@ module LanguagesHelper
locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
end
end
# rubocop:enable Metrics/ModuleLength

View File

@@ -0,0 +1,21 @@
# frozen_string_literal: true
module RegistrationHelper
extend ActiveSupport::Concern
def allowed_registration?(remote_ip, invite)
!Rails.configuration.x.single_user_mode && !omniauth_only? && (registrations_open? || invite&.valid_for_use?) && !ip_blocked?(remote_ip)
end
def registrations_open?
Setting.registrations_mode != 'none'
end
def omniauth_only?
ENV['OMNIAUTH_ONLY'] == 'true'
end
def ip_blocked?(remote_ip)
IpBlock.where(severity: :sign_up_block).exists?(['ip >>= ?', remote_ip.to_s])
end
end

View File

@@ -25,7 +25,7 @@ module SettingsHelper
return if account.nil?
link_to ActivityPub::TagManager.instance.url_for(account), class: 'name-tag', title: account.acct do
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: '', class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
end
end
end

View File

@@ -1,69 +0,0 @@
import api from '../api';
export const ACCOUNT_NOTE_SUBMIT_REQUEST = 'ACCOUNT_NOTE_SUBMIT_REQUEST';
export const ACCOUNT_NOTE_SUBMIT_SUCCESS = 'ACCOUNT_NOTE_SUBMIT_SUCCESS';
export const ACCOUNT_NOTE_SUBMIT_FAIL = 'ACCOUNT_NOTE_SUBMIT_FAIL';
export const ACCOUNT_NOTE_INIT_EDIT = 'ACCOUNT_NOTE_INIT_EDIT';
export const ACCOUNT_NOTE_CANCEL = 'ACCOUNT_NOTE_CANCEL';
export const ACCOUNT_NOTE_CHANGE_COMMENT = 'ACCOUNT_NOTE_CHANGE_COMMENT';
export function submitAccountNote() {
return (dispatch, getState) => {
dispatch(submitAccountNoteRequest());
const id = getState().getIn(['account_notes', 'edit', 'account_id']);
api(getState).post(`/api/v1/accounts/${id}/note`, {
comment: getState().getIn(['account_notes', 'edit', 'comment']),
}).then(response => {
dispatch(submitAccountNoteSuccess(response.data));
}).catch(error => dispatch(submitAccountNoteFail(error)));
};
}
export function submitAccountNoteRequest() {
return {
type: ACCOUNT_NOTE_SUBMIT_REQUEST,
};
}
export function submitAccountNoteSuccess(relationship) {
return {
type: ACCOUNT_NOTE_SUBMIT_SUCCESS,
relationship,
};
}
export function submitAccountNoteFail(error) {
return {
type: ACCOUNT_NOTE_SUBMIT_FAIL,
error,
};
}
export function initEditAccountNote(account) {
return (dispatch, getState) => {
const comment = getState().getIn(['relationships', account.get('id'), 'note']);
dispatch({
type: ACCOUNT_NOTE_INIT_EDIT,
account,
comment,
});
};
}
export function cancelAccountNote() {
return {
type: ACCOUNT_NOTE_CANCEL,
};
}
export function changeAccountNoteComment(comment) {
return {
type: ACCOUNT_NOTE_CHANGE_COMMENT,
comment,
};
}

View File

@@ -0,0 +1,18 @@
import { createAppAsyncThunk } from 'flavours/glitch/store/typed_functions';
import api from '../api';
export const submitAccountNote = createAppAsyncThunk(
'account_note/submit',
async (args: { id: string; value: string }, { getState }) => {
// TODO: replace `unknown` with `ApiRelationshipJSON` when it is merged
const response = await api(getState).post<unknown>(
`/api/v1/accounts/${args.id}/note`,
{
comment: args.value,
},
);
return { relationship: response.data };
},
);

View File

@@ -106,7 +106,6 @@ export function fetchAccount(id) {
api(getState).get(`/api/v1/accounts/${id}`).then(response => {
dispatch(importFetchedAccount(response.data));
}).then(() => {
dispatch(fetchAccountSuccess());
}).catch(error => {
dispatch(fetchAccountFail(id, error));
@@ -568,7 +567,7 @@ export function fetchRelationships(accountIds) {
dispatch(fetchRelationshipsRequest(newAccountIds));
api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
api(getState).get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
dispatch(fetchRelationshipsSuccess(response.data));
}).catch(error => {
dispatch(fetchRelationshipsFail(error));

View File

@@ -12,52 +12,48 @@ export const ALERT_DISMISS = 'ALERT_DISMISS';
export const ALERT_CLEAR = 'ALERT_CLEAR';
export const ALERT_NOOP = 'ALERT_NOOP';
export function dismissAlert(alert) {
return {
type: ALERT_DISMISS,
alert,
};
}
export const dismissAlert = alert => ({
type: ALERT_DISMISS,
alert,
});
export function clearAlert() {
return {
type: ALERT_CLEAR,
};
}
export const clearAlert = () => ({
type: ALERT_CLEAR,
});
export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage, message_values = undefined) {
return {
type: ALERT_SHOW,
title,
message,
message_values,
};
}
export const showAlert = alert => ({
type: ALERT_SHOW,
alert,
});
export function showAlertForError(error, skipNotFound = false) {
export const showAlertForError = (error, skipNotFound = false) => {
if (error.response) {
const { data, status, statusText, headers } = error.response;
// Skip these errors as they are reflected in the UI
if (skipNotFound && (status === 404 || status === 410)) {
// Skip these errors as they are reflected in the UI
return { type: ALERT_NOOP };
}
// Rate limit errors
if (status === 429 && headers['x-ratelimit-reset']) {
const reset_date = new Date(headers['x-ratelimit-reset']);
return showAlert(messages.rateLimitedTitle, messages.rateLimitedMessage, { 'retry_time': reset_date });
return showAlert({
title: messages.rateLimitedTitle,
message: messages.rateLimitedMessage,
values: { 'retry_time': new Date(headers['x-ratelimit-reset']) },
});
}
let message = statusText;
let title = `${status}`;
if (data.error) {
message = data.error;
}
return showAlert(title, message);
} else {
console.error(error);
return showAlert();
return showAlert({
title: `${status}`,
message: data.error || statusText,
});
}
}
console.error(error);
return showAlert({
title: messages.unexpectedTitle,
message: messages.unexpectedMessage,
});
};

View File

@@ -84,10 +84,14 @@ export const COMPOSE_CHANGE_MEDIA_DESCRIPTION = 'COMPOSE_CHANGE_MEDIA_DESCRIPTIO
export const COMPOSE_CHANGE_MEDIA_FOCUS = 'COMPOSE_CHANGE_MEDIA_FOCUS';
export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
export const COMPOSE_FOCUS = 'COMPOSE_FOCUS';
const messages = defineMessages({
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
open: { id: 'compose.published.open', defaultMessage: 'Open' },
published: { id: 'compose.published.body', defaultMessage: 'Post published.' },
saved: { id: 'compose.saved.body', defaultMessage: 'Post saved.' },
});
export const ensureComposeIsVisible = (getState, routerHistory) => {
@@ -144,6 +148,15 @@ export function resetCompose() {
};
}
export const focusCompose = (routerHistory, defaultText) => dispatch => {
dispatch({
type: COMPOSE_FOCUS,
defaultText,
});
ensureComposeIsVisible(routerHistory);
};
export function mentionCompose(account, routerHistory) {
return (dispatch, getState) => {
dispatch({
@@ -238,9 +251,9 @@ export function submitCompose(routerHistory) {
return;
}
// To make the app more responsive, immediately get the status into the columns
const insertIfOnline = (timelineId) => {
// To make the app more responsive, immediately push the status
// into the columns
const insertIfOnline = timelineId => {
const timeline = getState().getIn(['timelines', timelineId]);
if (timeline && timeline.get('items').size > 0 && timeline.getIn(['items', 0]) !== null && timeline.get('online')) {
@@ -264,6 +277,13 @@ export function submitCompose(routerHistory) {
} else if (statusId === null && response.data.visibility === 'direct') {
insertIfOnline('direct');
}
dispatch(showAlert({
message: statusId === null ? messages.published : messages.saved,
action: messages.open,
dismissAfter: 10000,
onClick: () => routerHistory.push(`/@${response.data.account.username}/${response.data.id}`),
}));
}).catch(function (error) {
dispatch(submitComposeFail(error));
});
@@ -300,18 +320,19 @@ export function doodleSet(options) {
export function uploadCompose(files) {
return function (dispatch, getState) {
const uploadLimit = 4;
const media = getState().getIn(['compose', 'media_attachments']);
const pending = getState().getIn(['compose', 'pending_media_attachments']);
const media = getState().getIn(['compose', 'media_attachments']);
const pending = getState().getIn(['compose', 'pending_media_attachments']);
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 + pending > uploadLimit) {
dispatch(showAlert(undefined, messages.uploadErrorLimit));
dispatch(showAlert({ message: messages.uploadErrorLimit }));
return;
}
if (getState().getIn(['compose', 'poll'])) {
dispatch(showAlert(undefined, messages.uploadErrorPoll));
dispatch(showAlert({ message: messages.uploadErrorPoll }));
return;
}
@@ -641,8 +662,9 @@ export function selectComposeSuggestion(position, token, suggestion, path) {
return (dispatch, getState) => {
let completion;
if (suggestion.type === 'emoji') {
dispatch(useEmoji(suggestion));
completion = suggestion.native || suggestion.colons;
dispatch(useEmoji(suggestion));
} else if (suggestion.type === 'hashtag') {
completion = `#${suggestion.name}`;
} else if (suggestion.type === 'account') {

View File

@@ -1,31 +0,0 @@
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,
skipNotFound: true,
});

View File

@@ -1,8 +1,8 @@
import escapeTextContentForBrowser from 'escape-html';
import emojify from 'flavours/glitch/features/emoji/emoji';
import { autoHideCW } from 'flavours/glitch/utils/content_warning';
import { unescapeHTML } from 'flavours/glitch/utils/html';
import emojify from '../../features/emoji/emoji';
import { autoHideCW } from '../../utils/content_warning';
import { unescapeHTML } from '../../utils/html';
const domParser = new DOMParser();

View File

@@ -83,6 +83,7 @@ export function reblogRequest(status) {
return {
type: REBLOG_REQUEST,
status: status,
skipLoading: true,
};
}
@@ -90,6 +91,7 @@ export function reblogSuccess(status) {
return {
type: REBLOG_SUCCESS,
status: status,
skipLoading: true,
};
}
@@ -98,6 +100,7 @@ export function reblogFail(status, error) {
type: REBLOG_FAIL,
status: status,
error: error,
skipLoading: true,
};
}
@@ -105,6 +108,7 @@ export function unreblogRequest(status) {
return {
type: UNREBLOG_REQUEST,
status: status,
skipLoading: true,
};
}
@@ -112,6 +116,7 @@ export function unreblogSuccess(status) {
return {
type: UNREBLOG_SUCCESS,
status: status,
skipLoading: true,
};
}
@@ -120,6 +125,7 @@ export function unreblogFail(status, error) {
type: UNREBLOG_FAIL,
status: status,
error: error,
skipLoading: true,
};
}
@@ -153,6 +159,7 @@ export function favouriteRequest(status) {
return {
type: FAVOURITE_REQUEST,
status: status,
skipLoading: true,
};
}
@@ -160,6 +167,7 @@ export function favouriteSuccess(status) {
return {
type: FAVOURITE_SUCCESS,
status: status,
skipLoading: true,
};
}
@@ -168,6 +176,7 @@ export function favouriteFail(status, error) {
type: FAVOURITE_FAIL,
status: status,
error: error,
skipLoading: true,
};
}
@@ -175,6 +184,7 @@ export function unfavouriteRequest(status) {
return {
type: UNFAVOURITE_REQUEST,
status: status,
skipLoading: true,
};
}
@@ -182,6 +192,7 @@ export function unfavouriteSuccess(status) {
return {
type: UNFAVOURITE_SUCCESS,
status: status,
skipLoading: true,
};
}
@@ -190,6 +201,7 @@ export function unfavouriteFail(status, error) {
type: UNFAVOURITE_FAIL,
status: status,
error: error,
skipLoading: true,
};
}
@@ -199,7 +211,7 @@ export function bookmark(status) {
api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function (response) {
dispatch(importFetchedStatus(response.data));
dispatch(bookmarkSuccess(status));
dispatch(bookmarkSuccess(status, response.data));
}).catch(function (error) {
dispatch(bookmarkFail(status, error));
});
@@ -212,7 +224,7 @@ export function unbookmark(status) {
api(getState).post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(unbookmarkSuccess(status));
dispatch(unbookmarkSuccess(status, response.data));
}).catch(error => {
dispatch(unbookmarkFail(status, error));
});
@@ -226,10 +238,11 @@ export function bookmarkRequest(status) {
};
}
export function bookmarkSuccess(status) {
export function bookmarkSuccess(status, response) {
return {
type: BOOKMARK_SUCCESS,
status: status,
response: response,
};
}
@@ -248,10 +261,11 @@ export function unbookmarkRequest(status) {
};
}
export function unbookmarkSuccess(status) {
export function unbookmarkSuccess(status, response) {
return {
type: UNBOOKMARK_SUCCESS,
status: status,
response: response,
};
}
@@ -444,6 +458,7 @@ export function pinRequest(status) {
return {
type: PIN_REQUEST,
status,
skipLoading: true,
};
}
@@ -451,6 +466,7 @@ export function pinSuccess(status) {
return {
type: PIN_SUCCESS,
status,
skipLoading: true,
};
}
@@ -459,6 +475,7 @@ export function pinFail(status, error) {
type: PIN_FAIL,
status,
error,
skipLoading: true,
};
}
@@ -479,6 +496,7 @@ export function unpinRequest(status) {
return {
type: UNPIN_REQUEST,
status,
skipLoading: true,
};
}
@@ -486,6 +504,7 @@ export function unpinSuccess(status) {
return {
type: UNPIN_SUCCESS,
status,
skipLoading: true,
};
}
@@ -494,5 +513,6 @@ export function unpinFail(status, error) {
type: UNPIN_FAIL,
status,
error,
skipLoading: true,
};
}

View File

@@ -1,9 +1,8 @@
import { openModal } from 'flavours/glitch/actions/modal';
import api, { getLinks } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';
import { openModal } from './modal';
export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST';
export const MUTES_FETCH_SUCCESS = 'MUTES_FETCH_SUCCESS';

View File

@@ -5,10 +5,10 @@ import { List as ImmutableList } from 'immutable';
import { compareId } from 'flavours/glitch/compare_id';
import { usePendingItems as preferPendingItems } from 'flavours/glitch/initial_state';
import { unescapeHTML } from 'flavours/glitch/utils/html';
import { requestNotificationPermission } from 'flavours/glitch/utils/notifications';
import api, { getLinks } from '../api';
import { unescapeHTML } from '../utils/html';
import { requestNotificationPermission } from '../utils/notifications';
import { fetchFollowRequests, fetchRelationships } from './accounts';
import {
@@ -21,10 +21,7 @@ import { submitMarkers } from './markers';
import { register as registerPushNotifications } from './push_notifications';
import { saveSettings } from './settings';
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
// tracking the notif cleaning request
@@ -65,7 +62,7 @@ defineMessages({
const fetchRelatedRelationships = (dispatch, notifications) => {
const accountIds = notifications.filter(item => ['follow', 'follow_request', 'admin.sign_up'].indexOf(item.type) !== -1).map(item => item.account.id);
if (accountIds > 0) {
if (accountIds.length > 0) {
dispatch(fetchRelationships(accountIds));
}
};
@@ -131,6 +128,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : '');
const notify = new Notification(title, { body, icon: notification.account.avatar, tag: notification.id });
notify.addEventListener('click', () => {
window.focus();
notify.close();
@@ -141,7 +139,6 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
const excludeTypesFromFilter = filter => {
const allTypes = ImmutableList([
'follow',

View File

@@ -1,16 +1,8 @@
import { openModal } from './modal';
import { changeSetting, saveSettings } from './settings';
export function showOnboardingOnce() {
return (dispatch, getState) => {
const alreadySeen = getState().getIn(['settings', 'onboarded']);
export const INTRODUCTION_VERSION = 20181216044202;
if (!alreadySeen) {
dispatch(openModal({
modalType: 'ONBOARDING',
}));
dispatch(changeSetting(['onboarded'], true));
dispatch(saveSettings());
}
};
}
export const closeOnboarding = () => dispatch => {
dispatch(changeSetting(['introductionVersion'], INTRODUCTION_VERSION));
dispatch(saveSettings());
};

View File

@@ -1,10 +1,8 @@
import { me } from 'flavours/glitch/initial_state';
import api from '../api';
import { me } from '../initial_state';
import { importFetchedStatuses } from './importer';
export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST';
export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS';
export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL';

View File

@@ -1,5 +1,7 @@
import api from '../../api';
import { me } from '../../initial_state';
import { pushNotificationsSetting } from '../../settings';
import { decode as decodeBase64 } from '../../utils/base64';
import { setBrowserSupport, setSubscription, clearSubscription } from './setter';
@@ -10,13 +12,7 @@ const urlBase64ToUint8Array = (base64String) => {
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
return decodeBase64(base64);
};
const getApplicationServerKey = () => document.querySelector('[name="applicationServerKey"]').getAttribute('content');
@@ -36,7 +32,7 @@ const subscribe = (registration) =>
const unsubscribe = ({ registration, subscription }) =>
subscription ? subscription.unsubscribe().then(() => registration) : registration;
const sendSubscriptionToBackend = (getState, subscription, me) => {
const sendSubscriptionToBackend = (subscription) => {
const params = { subscription };
if (me) {
@@ -46,7 +42,7 @@ const sendSubscriptionToBackend = (getState, subscription, me) => {
}
}
return api(getState).post('/api/web/push_subscriptions', params).then(response => response.data);
return api().post('/api/web/push_subscriptions', params).then(response => response.data);
};
// Last one checks for payload support: https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/#no-payload
@@ -55,7 +51,6 @@ const supportsPushNotifications = ('serviceWorker' in navigator && 'PushManager'
export function register () {
return (dispatch, getState) => {
dispatch(setBrowserSupport(supportsPushNotifications));
const me = getState().getIn(['meta', 'me']);
if (supportsPushNotifications) {
if (!getApplicationServerKey()) {
@@ -79,13 +74,13 @@ export function register () {
} else {
// Something went wrong, try to subscribe again
return unsubscribe({ registration, subscription }).then(subscribe).then(
subscription => sendSubscriptionToBackend(getState, subscription, me));
subscription => sendSubscriptionToBackend(subscription));
}
}
// No subscription, try to subscribe
return subscribe(registration).then(
subscription => sendSubscriptionToBackend(getState, subscription, me));
subscription => sendSubscriptionToBackend(subscription));
})
.then(subscription => {
// If we got a PushSubscription (and not a subscription object from the backend)
@@ -128,10 +123,9 @@ export function saveSettings() {
const alerts = state.get('alerts');
const data = { alerts };
api(getState).put(`/api/web/push_subscriptions/${subscription.get('id')}`, {
api().put(`/api/web/push_subscriptions/${subscription.get('id')}`, {
data,
}).then(() => {
const me = getState().getIn(['meta', 'me']);
if (me) {
pushNotificationsSetting.set(me, data);
}

View File

@@ -26,7 +26,7 @@ const debouncedSave = debounce((dispatch, getState) => {
const data = getState().get('settings').filter((_, path) => path !== 'saved').toJS();
api(getState).put('/api/web/settings', { data })
api().put('/api/web/settings', { data })
.then(() => dispatch({ type: SETTING_SAVE }))
.catch(error => dispatch(showAlertForError(error)));
}, 5000, { trailing: true });

View File

@@ -1,7 +1,6 @@
// @ts-check
import { getLocale } from 'flavours/glitch/locales';
import { getLocale } from '../locales';
import { connectStream } from '../stream';
import {
@@ -68,8 +67,8 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
// @ts-expect-error
if (pollingId) {
clearTimeout(pollingId);
pollingId = null;
// @ts-ignore
clearTimeout(pollingId); pollingId = null;
}
if (options.fillGaps) {
@@ -86,8 +85,8 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
}
},
onReceive (data) {
switch(data.event) {
onReceive(data) {
switch (data.event) {
case 'update':
// @ts-expect-error
dispatch(updateTimeline(timelineId, JSON.parse(data.payload), options.accept));

View File

@@ -1,75 +0,0 @@
// @ts-check
import axios from 'axios';
import LinkHeader from 'http-link-header';
import ready from './ready';
/**
* @param {import('axios').AxiosResponse} response
* @returns {LinkHeader}
*/
export const getLinks = response => {
const value = response.headers.link;
if (!value) {
return new LinkHeader();
}
return LinkHeader.parse(value);
};
/** @type {import('axios').RawAxiosRequestHeaders} */
const csrfHeader = {};
/**
* @returns {void}
*/
const setCSRFHeader = () => {
/** @type {HTMLMetaElement | null} */
const csrfToken = document.querySelector('meta[name=csrf-token]');
if (csrfToken) {
csrfHeader['X-CSRF-Token'] = csrfToken.content;
}
};
ready(setCSRFHeader);
/**
* @param {() => import('immutable').Map<string,any>} getState
* @returns {import('axios').RawAxiosRequestHeaders}
*/
const authorizationHeaderFromState = getState => {
const accessToken = getState && getState().getIn(['meta', 'access_token'], '');
if (!accessToken) {
return {};
}
return {
'Authorization': `Bearer ${accessToken}`,
};
};
/**
* @param {() => import('immutable').Map<string,any>} getState
* @returns {import('axios').AxiosInstance}
*/
export default function api(getState) {
return axios.create({
headers: {
...csrfHeader,
...authorizationHeaderFromState(getState),
},
transformResponse: [
function (data) {
try {
return JSON.parse(data);
} catch {
return data;
}
},
],
});
}

View File

@@ -0,0 +1,63 @@
import type { AxiosResponse, RawAxiosRequestHeaders } from 'axios';
import axios from 'axios';
import LinkHeader from 'http-link-header';
import ready from './ready';
import type { GetState } from './store';
export const getLinks = (response: AxiosResponse) => {
const value = response.headers.link as string | undefined;
if (!value) {
return new LinkHeader();
}
return LinkHeader.parse(value);
};
const csrfHeader: RawAxiosRequestHeaders = {};
const setCSRFHeader = () => {
const csrfToken = document.querySelector<HTMLMetaElement>(
'meta[name=csrf-token]',
);
if (csrfToken) {
csrfHeader['X-CSRF-Token'] = csrfToken.content;
}
};
void ready(setCSRFHeader);
const authorizationHeaderFromState = (getState?: GetState) => {
const accessToken =
getState && (getState().meta.get('access_token', '') as string);
if (!accessToken) {
return {};
}
return {
Authorization: `Bearer ${accessToken}`,
} as RawAxiosRequestHeaders;
};
// eslint-disable-next-line import/no-default-export
export default function api(getState: GetState) {
return axios.create({
headers: {
...csrfHeader,
...authorizationHeaderFromState(getState),
},
transformResponse: [
function (data: unknown) {
try {
return JSON.parse(data as string) as unknown;
} catch {
return data;
}
},
],
});
}

View File

@@ -0,0 +1,214 @@
import { fromJS } from 'immutable';
import type { StatusLike } from '../hashtag_bar';
import { computeHashtagBarForStatus } from '../hashtag_bar';
function createStatus(
content: string,
hashtags: string[],
hasMedia = false,
spoilerText?: string,
) {
return fromJS({
tags: hashtags.map((name) => ({ name })),
contentHtml: content,
media_attachments: hasMedia ? ['fakeMedia'] : [],
spoiler_text: spoilerText,
}) as unknown as StatusLike; // need to force the type here, as it is not properly defined
}
describe('computeHashtagBarForStatus', () => {
it('does nothing when there are no tags', () => {
const status = createStatus('<p>Simple text</p>', []);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Simple text</p>"`,
);
});
it('displays out of band hashtags in the bar', () => {
const status = createStatus(
'<p>Simple text <a href="test">#hashtag</a></p>',
['hashtag', 'test'],
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual(['test']);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Simple text <a href="test">#hashtag</a></p>"`,
);
});
it('does not truncate the contents when the last child is a text node', () => {
const status = createStatus(
'this is a #<a class="zrl" href="https://example.com/search?tag=test">test</a>. Some more text',
['test'],
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"this is a #<a class="zrl" href="https://example.com/search?tag=test">test</a>. Some more text"`,
);
});
it('extract tags from the last line', () => {
const status = createStatus(
'<p>Simple text</p><p><a href="test">#hashtag</a></p>',
['hashtag'],
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual(['hashtag']);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Simple text</p>"`,
);
});
it('does not include tags from content', () => {
const status = createStatus(
'<p>Simple text with a <a href="test">#hashtag</a></p><p><a href="test">#hashtag</a></p>',
['hashtag'],
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Simple text with a <a href="test">#hashtag</a></p>"`,
);
});
it('works with one line status and hashtags', () => {
const status = createStatus(
'<p><a href="test">#test</a>. And another <a href="test">#hashtag</a></p>',
['hashtag', 'test'],
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p><a href="test">#test</a>. And another <a href="test">#hashtag</a></p>"`,
);
});
it('de-duplicate accentuated characters with case differences', () => {
const status = createStatus(
'<p>Text</p><p><a href="test">#éaa</a> <a href="test">#Éaa</a></p>',
['éaa'],
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual(['Éaa']);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Text</p>"`,
);
});
it('handles server-side normalized tags with accentuated characters', () => {
const status = createStatus(
'<p>Text</p><p><a href="test">#éaa</a> <a href="test">#Éaa</a></p>',
['eaa'], // The server may normalize the hashtags in the `tags` attribute
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual(['Éaa']);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Text</p>"`,
);
});
it('does not display in bar a hashtag in content with a case difference', () => {
const status = createStatus(
'<p>Text <a href="test">#Éaa</a></p><p><a href="test">#éaa</a></p>',
['éaa'],
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Text <a href="test">#Éaa</a></p>"`,
);
});
it('does not modify a status with a line of hashtags only', () => {
const status = createStatus(
'<p><a href="test">#test</a> <a href="test">#hashtag</a></p>',
['test', 'hashtag'],
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p><a href="test">#test</a> <a href="test">#hashtag</a></p>"`,
);
});
it('puts the hashtags in the bar if a status content has hashtags in the only line and has a media', () => {
const status = createStatus(
'<p>This is my content! <a href="test">#hashtag</a></p>',
['hashtag'],
true,
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>This is my content! <a href="test">#hashtag</a></p>"`,
);
});
it('puts the hashtags in the bar if a status content is only hashtags and has a media', () => {
const status = createStatus(
'<p><a href="test">#test</a> <a href="test">#hashtag</a></p>',
['test', 'hashtag'],
true,
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual(['test', 'hashtag']);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(`""`);
});
it('does not use the hashtag bar if the status content is only hashtags, has a CW and a media', () => {
const status = createStatus(
'<p><a href="test">#test</a> <a href="test">#hashtag</a></p>',
['test', 'hashtag'],
true,
'My CW text',
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p><a href="test">#test</a> <a href="test">#hashtag</a></p>"`,
);
});
});

View File

@@ -1,30 +1,35 @@
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { Skeleton } from 'flavours/glitch/components/skeleton';
import { me } from 'flavours/glitch/initial_state';
import { EmptyAccount } from 'flavours/glitch/components/empty_account';
import { ShortNumber } from 'flavours/glitch/components/short_number';
import { VerifiedBadge } from 'flavours/glitch/components/verified_badge';
import { me } from '../initial_state';
import { Avatar } from './avatar';
import { Button } from './button';
import { FollowersCounter } from './counters';
import { DisplayName } from './display_name';
import { IconButton } from './icon_button';
import Permalink from './permalink';
import { RelativeTimestamp } from './relative_timestamp';
const messages = defineMessages({
follow: { id: 'account.follow', defaultMessage: 'Follow' },
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'Mute notifications from @{name}' },
unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'Unmute notifications from @{name}' },
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
mute_notifications: { id: 'account.mute_notifications_short', defaultMessage: 'Mute notifications' },
unmute_notifications: { id: 'account.unmute_notifications_short', defaultMessage: 'Unmute notifications' },
mute: { id: 'account.mute_short', defaultMessage: 'Mute' },
block: { id: 'account.block_short', defaultMessage: 'Block' },
});
class Account extends ImmutablePureComponent {
@@ -38,15 +43,13 @@ class Account extends ImmutablePureComponent {
onMuteNotifications: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
hidden: PropTypes.bool,
small: PropTypes.bool,
actionIcon: PropTypes.string,
actionTitle: PropTypes.string,
minimal: PropTypes.bool,
defaultAction: PropTypes.string,
onActionClick: PropTypes.func,
withBio: PropTypes.bool,
};
static defaultProps = {
size: 36,
size: 46,
};
handleFollow = () => {
@@ -69,34 +72,11 @@ class Account extends ImmutablePureComponent {
this.props.onMuteNotifications(this.props.account, false);
};
handleAction = () => {
this.props.onActionClick(this.props.account);
};
render () {
const {
account,
hidden,
intl,
small,
onActionClick,
actionIcon,
actionTitle,
defaultAction,
size,
} = this.props;
const { account, intl, hidden, withBio, defaultAction, size, minimal } = this.props;
if (!account) {
return (
<div className='account'>
<div className='account__wrapper'>
<div className='account__display-name'>
<div className='account__avatar-wrapper'><Skeleton width={36} height={36} /></div>
<DisplayName />
</div>
</div>
</div>
);
return <EmptyAccount size={size} minimal={minimal} />;
}
if (hidden) {
@@ -110,78 +90,89 @@ class Account extends ImmutablePureComponent {
let buttons;
if (onActionClick) {
if (actionIcon) {
buttons = <IconButton icon={actionIcon} title={actionTitle} onClick={this.handleAction} />;
}
} else if (account.get('id') !== me && !small && account.get('relationship', null) !== null) {
if (account.get('id') !== me && account.get('relationship', null) !== null) {
const following = account.getIn(['relationship', 'following']);
const requested = account.getIn(['relationship', 'requested']);
const blocking = account.getIn(['relationship', 'blocking']);
const muting = account.getIn(['relationship', 'muting']);
if (requested) {
buttons = <IconButton disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />;
buttons = <Button text={intl.formatMessage(messages.cancel_follow_request)} onClick={this.handleFollow} />;
} else if (blocking) {
buttons = <IconButton active icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
buttons = <Button text={intl.formatMessage(messages.unblock)} onClick={this.handleBlock} />;
} else if (muting) {
let hidingNotificationsButton;
if (account.getIn(['relationship', 'muting_notifications'])) {
hidingNotificationsButton = <IconButton active icon='bell' title={intl.formatMessage(messages.unmute_notifications, { name: account.get('username') })} onClick={this.handleUnmuteNotifications} />;
hidingNotificationsButton = <Button text={intl.formatMessage(messages.unmute_notifications)} onClick={this.handleUnmuteNotifications} />;
} else {
hidingNotificationsButton = <IconButton active icon='bell-slash' title={intl.formatMessage(messages.mute_notifications, { name: account.get('username') })} onClick={this.handleMuteNotifications} />;
hidingNotificationsButton = <Button text={intl.formatMessage(messages.mute_notifications)} onClick={this.handleMuteNotifications} />;
}
buttons = (
<>
<IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />
<Button text={intl.formatMessage(messages.unmute)} onClick={this.handleMute} />
{hidingNotificationsButton}
</>
);
} else if (defaultAction === 'mute') {
buttons = <IconButton icon='volume-off' title={intl.formatMessage(messages.mute, { name: account.get('username') })} onClick={this.handleMute} />;
buttons = <Button title={intl.formatMessage(messages.mute)} onClick={this.handleMute} />;
} else if (defaultAction === 'block') {
buttons = <IconButton icon='lock' title={intl.formatMessage(messages.block, { name: account.get('username') })} onClick={this.handleBlock} />;
} else if (!account.get('moved') || following) {
buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
buttons = <Button text={intl.formatMessage(messages.block)} onClick={this.handleBlock} />;
} else if (!account.get('suspended') && !account.get('moved') || following) {
buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
}
}
let mute_expires_at;
let muteTimeRemaining;
if (account.get('mute_expires_at')) {
mute_expires_at = <div><RelativeTimestamp timestamp={account.get('mute_expires_at')} futureDate /></div>;
muteTimeRemaining = <>· <RelativeTimestamp timestamp={account.get('mute_expires_at')} futureDate /></>;
}
return small ? (
<Permalink
className='account small'
href={account.get('url')}
to={`/@${account.get('acct')}`}
>
<div className='account__avatar-wrapper'>
<Avatar
account={account}
size={24}
/>
</div>
<DisplayName
account={account}
inline
/>
</Permalink>
) : (
<div className='account'>
let verification;
const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at'));
if (firstVerifiedField) {
verification = <VerifiedBadge link={firstVerifiedField.get('value')} />;
}
return (
<div className={classNames('account', { 'account--minimal': minimal })}>
<div className='account__wrapper'>
<Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/@${account.get('acct')}`}>
<div className='account__avatar-wrapper'><Avatar account={account} size={size} /></div>
{mute_expires_at}
<DisplayName account={account} />
<div className='account__avatar-wrapper'>
<Avatar account={account} size={size} />
</div>
<div className='account__contents'>
<DisplayName account={account} inline />
{!minimal && (
<div className='account__details'>
{account.get('followers_count') !== -1 && (
<ShortNumber value={account.get('followers_count')} renderer={FollowersCounter} />
)} {verification} {muteTimeRemaining}
</div>
)}
</div>
</Permalink>
{buttons ?
{!minimal && (
<div className='account__relationship'>
{buttons}
</div>
: null}
)}
</div>
{withBio && (account.get('note').length > 0 ? (
<div
className='account__note translate'
dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
/>
) : (
<div className='account__note account__note--missing'><FormattedMessage id='account.no_bio' defaultMessage='No description provided.' /></div>
))}
</div>
);
}

View File

@@ -1,5 +1,4 @@
import { useCallback, useState } from 'react';
import * as React from 'react';
import { TransitionMotion, spring } from 'react-motion';

View File

@@ -1,9 +1,10 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { unicodeMapping } from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light';
import { assetHost } from 'flavours/glitch/utils/config';
import { unicodeMapping } from '../features/emoji/emoji_unicode_mapping_light';
export default class AutosuggestEmoji extends PureComponent {
static propTypes = {
@@ -27,7 +28,7 @@ export default class AutosuggestEmoji extends PureComponent {
}
return (
<div className='emoji'>
<div className='autosuggest-emoji'>
<img
className='emojione'
src={url}

View File

@@ -5,7 +5,7 @@ import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container';
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
import AutosuggestEmoji from './autosuggest_emoji';
import { AutosuggestHashtag } from './autosuggest_hashtag';

View File

@@ -1,13 +1,13 @@
import PropTypes from 'prop-types';
import { useCallback, useRef, useState, useEffect, forwardRef } from 'react';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Textarea from 'react-textarea-autosize';
import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container';
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
import AutosuggestEmoji from './autosuggest_emoji';
import { AutosuggestHashtag } from './autosuggest_hashtag';
@@ -37,54 +37,46 @@ const textAtCursorMatchesToken = (str, caretPosition) => {
}
};
export default class AutosuggestTextarea extends ImmutablePureComponent {
const AutosuggestTextarea = forwardRef(({
value,
suggestions,
disabled,
placeholder,
onSuggestionSelected,
onSuggestionsClearRequested,
onSuggestionsFetchRequested,
onChange,
onKeyUp,
onKeyDown,
onPaste,
onFocus,
autoFocus = true,
lang,
children,
}, textareaRef) => {
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,
onPaste: PropTypes.func.isRequired,
autoFocus: PropTypes.bool,
lang: PropTypes.string,
};
const [suggestionsHidden, setSuggestionsHidden] = useState(true);
const [selectedSuggestion, setSelectedSuggestion] = useState(0);
const lastTokenRef = useRef(null);
const tokenStartRef = useRef(0);
static defaultProps = {
autoFocus: true,
};
state = {
suggestionsHidden: true,
focused: false,
selectedSuggestion: 0,
lastToken: null,
tokenStart: 0,
};
onChange = (e) => {
const handleChange = useCallback((e) => {
const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
if (token !== null && this.state.lastToken !== token) {
this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart });
this.props.onSuggestionsFetchRequested(token);
if (token !== null && lastTokenRef.current !== token) {
tokenStartRef.current = tokenStart;
lastTokenRef.current = token;
setSelectedSuggestion(0);
onSuggestionsFetchRequested(token);
} else if (token === null) {
this.setState({ lastToken: null });
this.props.onSuggestionsClearRequested();
lastTokenRef.current = null;
onSuggestionsClearRequested();
}
this.props.onChange(e);
};
onKeyDown = (e) => {
const { suggestions, disabled } = this.props;
const { selectedSuggestion, suggestionsHidden } = this.state;
onChange(e);
}, [onSuggestionsFetchRequested, onSuggestionsClearRequested, onChange, setSelectedSuggestion]);
const handleKeyDown = useCallback((e) => {
if (disabled) {
e.preventDefault();
return;
@@ -102,80 +94,75 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
document.querySelector('.ui').parentElement.focus();
} else {
e.preventDefault();
this.setState({ suggestionsHidden: true });
setSuggestionsHidden(true);
}
break;
case 'ArrowDown':
if (suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
setSelectedSuggestion(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) });
setSelectedSuggestion(Math.max(selectedSuggestion - 1, 0));
}
break;
case 'Enter':
case 'Tab':
// Select suggestion
if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
if (lastTokenRef.current !== null && suggestions.size > 0 && !suggestionsHidden) {
e.preventDefault();
e.stopPropagation();
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
onSuggestionSelected(tokenStartRef.current, lastTokenRef.current, suggestions.get(selectedSuggestion));
}
break;
}
if (e.defaultPrevented || !this.props.onKeyDown) {
if (e.defaultPrevented || !onKeyDown) {
return;
}
this.props.onKeyDown(e);
};
onKeyDown(e);
}, [disabled, suggestions, suggestionsHidden, selectedSuggestion, setSelectedSuggestion, setSuggestionsHidden, onSuggestionSelected, onKeyDown]);
onBlur = () => {
this.setState({ suggestionsHidden: true, focused: false });
};
const handleBlur = useCallback(() => {
setSuggestionsHidden(true);
}, [setSuggestionsHidden]);
onFocus = (e) => {
this.setState({ focused: true });
if (this.props.onFocus) {
this.props.onFocus(e);
const handleFocus = useCallback((e) => {
if (onFocus) {
onFocus(e);
}
};
}, [onFocus]);
onSuggestionClick = (e) => {
const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
const handleSuggestionClick = useCallback((e) => {
const suggestion = suggestions.get(e.currentTarget.getAttribute('data-index'));
e.preventDefault();
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
this.textarea.focus();
};
onSuggestionSelected(tokenStartRef.current, lastTokenRef.current, suggestion);
textareaRef.current?.focus();
}, [suggestions, onSuggestionSelected, textareaRef]);
UNSAFE_componentWillReceiveProps (nextProps) {
if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) {
this.setState({ suggestionsHidden: false });
}
}
setTextarea = (c) => {
this.textarea = c;
};
onPaste = (e) => {
const handlePaste = useCallback((e) => {
if (e.clipboardData && e.clipboardData.files.length === 1) {
this.props.onPaste(e.clipboardData.files);
onPaste(e.clipboardData.files);
e.preventDefault();
}
};
}, [onPaste]);
renderSuggestion = (suggestion, i) => {
const { selectedSuggestion } = this.state;
// Show the suggestions again whenever they change and the textarea is focused
useEffect(() => {
if (suggestions.size > 0 && textareaRef.current === document.activeElement) {
setSuggestionsHidden(false);
}
}, [suggestions, textareaRef, setSuggestionsHidden]);
const renderSuggestion = (suggestion, i) => {
let inner, key;
if (suggestion.type === 'emoji') {
@@ -190,50 +177,64 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
}
return (
<div role='button' tabIndex={0} key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
<div role='button' tabIndex={0} key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={handleSuggestionClick}>
{inner}
</div>
);
};
render () {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, lang, children } = this.props;
const { suggestionsHidden } = this.state;
return [
<div className='compose-form__autosuggest-wrapper' key='autosuggest-wrapper'>
<div className='autosuggest-textarea'>
<label>
<span style={{ display: 'none' }}>{placeholder}</span>
return [
<div className='compose-form__autosuggest-wrapper' key='autosuggest-wrapper'>
<div className='autosuggest-textarea'>
<label>
<span style={{ display: 'none' }}>{placeholder}</span>
<Textarea
ref={textareaRef}
className='autosuggest-textarea__textarea'
disabled={disabled}
placeholder={placeholder}
autoFocus={autoFocus}
value={value}
onChange={handleChange}
onKeyDown={handleKeyDown}
onKeyUp={onKeyUp}
onFocus={handleFocus}
onBlur={handleBlur}
onPaste={handlePaste}
dir='auto'
aria-autocomplete='list'
lang={lang}
/>
</label>
</div>
{children}
</div>,
<Textarea
ref={this.setTextarea}
className='autosuggest-textarea__textarea'
disabled={disabled}
placeholder={placeholder}
autoFocus={autoFocus}
value={value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onKeyUp={onKeyUp}
onFocus={this.onFocus}
onBlur={this.onBlur}
onPaste={this.onPaste}
dir='auto'
aria-autocomplete='list'
lang={lang}
/>
</label>
</div>
{children}
</div>,
<div className='autosuggest-textarea__suggestions-wrapper' key='suggestions-wrapper'>
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
{suggestions.map(renderSuggestion)}
</div>
</div>,
];
});
<div className='autosuggest-textarea__suggestions-wrapper' key='suggestions-wrapper'>
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
{suggestions.map(this.renderSuggestion)}
</div>
</div>,
];
}
AutosuggestTextarea.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,
onPaste: PropTypes.func.isRequired,
onFocus:PropTypes.func,
children: PropTypes.node,
autoFocus: PropTypes.bool,
lang: PropTypes.string,
};
}
export default AutosuggestTextarea;

View File

@@ -1,55 +1,48 @@
import * as React from 'react';
import classNames from 'classnames';
import { useHovering } from 'flavours/glitch/hooks/useHovering';
import { autoPlayGif } from 'flavours/glitch/initial_state';
import type { Account } from 'flavours/glitch/types/resources';
import { useHovering } from '../hooks/useHovering';
import { autoPlayGif } from '../initial_state';
import type { Account } from '../types/resources';
interface Props {
account: Account | undefined;
className?: string;
account: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
size: number;
style?: React.CSSProperties;
inline?: boolean;
animate?: boolean;
}
export const Avatar: React.FC<Props> = ({
account,
className,
animate = autoPlayGif,
size = 20,
inline = false,
style: styleFromParent,
}) => {
const { hovering, handleMouseEnter, handleMouseLeave } =
useHovering(autoPlayGif);
const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(animate);
const style = {
...styleFromParent,
width: `${size}px`,
height: `${size}px`,
backgroundSize: `${size}px ${size}px`,
};
if (account) {
style.backgroundImage = `url(${account.get(
hovering ? 'avatar' : 'avatar_static',
)})`;
}
const src =
hovering || animate
? account?.get('avatar')
: account?.get('avatar_static');
return (
<div
className={classNames(
'account__avatar',
{ 'account__avatar-inline': inline },
className,
)}
className={classNames('account__avatar', {
'account__avatar-inline': inline,
})}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
style={style}
data-avatar-of={account && `@${account.get('acct')}`}
role='img'
aria-label={account?.get('acct')}
/>
>
{src && <img src={src} alt={account?.get('acct')} />}
</div>
);
};

View File

@@ -3,7 +3,9 @@ import { PureComponent } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { autoPlayGif } from 'flavours/glitch/initial_state';
import { autoPlayGif } from '../initial_state';
import { Avatar } from './avatar';
export default class AvatarComposite extends PureComponent {
@@ -76,12 +78,12 @@ export default class AvatarComposite extends PureComponent {
bottom: bottom,
width: `${width}%`,
height: `${height}%`,
backgroundSize: 'cover',
backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`,
};
return (
<div key={account.get('id')} style={style} data-avatar-of={`@${account.get('acct')}`} />
<div key={account.get('id')} style={style}>
<Avatar account={account} animate={animate} />
</div>
);
}

View File

@@ -1,39 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { autoPlayGif } from 'flavours/glitch/initial_state';
export default class AvatarOverlay extends PureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
friend: ImmutablePropTypes.map.isRequired,
animate: PropTypes.bool,
};
static defaultProps = {
animate: autoPlayGif,
};
render() {
const { account, friend, animate } = this.props;
const baseStyle = {
backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`,
};
const overlayStyle = {
backgroundImage: `url(${friend.get(animate ? 'avatar' : 'avatar_static')})`,
};
return (
<div className='account__avatar-overlay'>
<div className='account__avatar-overlay-base' style={baseStyle} data-avatar-of={`@${account.get('acct')}`} />
<div className='account__avatar-overlay-overlay' style={overlayStyle} data-avatar-of={`@${friend.get('acct')}`} />
</div>
);
}
}

View File

@@ -0,0 +1,56 @@
import { useHovering } from '../hooks/useHovering';
import { autoPlayGif } from '../initial_state';
import type { Account } from '../types/resources';
interface Props {
account: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
friend: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
size?: number;
baseSize?: number;
overlaySize?: number;
}
export const AvatarOverlay: React.FC<Props> = ({
account,
friend,
size = 46,
baseSize = 36,
overlaySize = 24,
}) => {
const { hovering, handleMouseEnter, handleMouseLeave } =
useHovering(autoPlayGif);
const accountSrc = hovering
? account?.get('avatar')
: account?.get('avatar_static');
const friendSrc = hovering
? friend?.get('avatar')
: friend?.get('avatar_static');
return (
<div
className='account__avatar-overlay'
style={{ width: size, height: size }}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div className='account__avatar-overlay-base'>
<div
className='account__avatar'
style={{ width: `${baseSize}px`, height: `${baseSize}px` }}
data-avatar-of={`@${account?.get('acct')}`}
>
{accountSrc && <img src={accountSrc} alt={account?.get('acct')} />}
</div>
</div>
<div className='account__avatar-overlay-overlay'>
<div
className='account__avatar'
style={{ width: `${overlaySize}px`, height: `${overlaySize}px` }}
data-avatar-of={`@${friend?.get('acct')}`}
>
{friendSrc && <img src={friendSrc} alt={friend?.get('acct')} />}
</div>
</div>
</div>
);
};

View File

@@ -1,5 +1,4 @@
import { useRef, useEffect } from 'react';
import * as React from 'react';
import { memo, useRef, useEffect } from 'react';
import { decode } from 'blurhash';
@@ -44,6 +43,6 @@ const Blurhash: React.FC<Props> = ({
);
};
const MemoizedBlurhash = React.memo(Blurhash);
const MemoizedBlurhash = memo(Blurhash);
export { MemoizedBlurhash as Blurhash };

View File

@@ -1,7 +0,0 @@
const Check = () => (
<svg width='14' height='11' viewBox='0 0 14 11'>
<path d='M11.264 0L5.26 6.004 2.103 2.847 0 4.95l5.26 5.26 8.108-8.107L11.264 0' fill='currentColor' fillRule='evenodd' />
</svg>
);
export default Check;

View File

@@ -0,0 +1,13 @@
export const Check: React.FC = () => (
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 20 20'
fill='currentColor'
>
<path
fillRule='evenodd'
d='M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z'
clipRule='evenodd'
/>
</svg>
);

View File

@@ -12,7 +12,6 @@ export default class Column extends PureComponent {
static propTypes = {
children: PropTypes.node,
extraClasses: PropTypes.string,
name: PropTypes.string,
label: PropTypes.string,
bindToDocument: PropTypes.bool,
};
@@ -62,10 +61,10 @@ export default class Column extends PureComponent {
}
render () {
const { children, extraClasses, name, label } = this.props;
const { label, children, extraClasses } = this.props;
return (
<div role='region' aria-label={label} data-column={name} className={`column ${extraClasses || ''}`} ref={this.setRef}>
<div role='region' aria-label={label} className={`column ${extraClasses || ''}`} ref={this.setRef}>
{children}
</div>
);

View File

@@ -13,13 +13,16 @@ export class ColumnBackButton extends PureComponent {
static propTypes = {
multiColumn: PropTypes.bool,
onClick: PropTypes.func,
...WithRouterPropTypes,
};
handleClick = () => {
const { history } = this.props;
const { onClick, history } = this.props;
if (history.location?.state?.fromMastodon) {
if (onClick) {
onClick();
} else if (history.location?.state?.fromMastodon) {
history.goBack();
} else {
history.push('/');

View File

@@ -4,9 +4,8 @@ import classNames from 'classnames';
import type { List } from 'immutable';
import type { Account } from 'flavours/glitch/types/resources';
import { autoPlayGif } from '../initial_state';
import type { Account } from '../types/resources';
import { Skeleton } from './skeleton';

View File

@@ -1,5 +1,4 @@
import { useCallback } from 'react';
import * as React from 'react';
import { defineMessages, useIntl } from 'react-intl';

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