From dac460f43f8a64cdf322070ee960a3249ce2c619 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 1 Jun 2023 10:24:11 +0200 Subject: [PATCH 01/17] Bump workbox from 6.6.1 to 7.0.0 (#25232) --- package.json | 12 +-- yarn.lock | 216 +++++++++++++++++++++++++-------------------------- 2 files changed, 114 insertions(+), 114 deletions(-) diff --git a/package.json b/package.json index bc5fb84bfc..31f2454fee 100644 --- a/package.json +++ b/package.json @@ -137,12 +137,12 @@ "webpack-cli": "^3.3.12", "webpack-merge": "^5.9.0", "wicg-inert": "^3.1.2", - "workbox-expiration": "^6.6.0", - "workbox-precaching": "^6.6.0", - "workbox-routing": "^6.6.0", - "workbox-strategies": "^6.6.0", - "workbox-webpack-plugin": "^6.6.0", - "workbox-window": "^6.6.0", + "workbox-expiration": "^7.0.0", + "workbox-precaching": "^7.0.0", + "workbox-routing": "^7.0.0", + "workbox-strategies": "^7.0.0", + "workbox-webpack-plugin": "^7.0.0", + "workbox-window": "^7.0.0", "ws": "^8.12.1" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index f60676b20b..f992961b92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12252,25 +12252,25 @@ word-wrap@^1.2.3, word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -workbox-background-sync@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.6.1.tgz#08d603a33717ce663e718c30cc336f74909aff2f" - integrity sha512-trJd3ovpWCvzu4sW0E8rV3FUyIcC0W8G+AZ+VcqzzA890AsWZlUGOTSxIMmIHVusUw/FDq1HFWfy/kC/WTRqSg== +workbox-background-sync@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-7.0.0.tgz#2b84b96ca35fec976e3bd2794b70e4acec46b3a5" + integrity sha512-S+m1+84gjdueM+jIKZ+I0Lx0BDHkk5Nu6a3kTVxP4fdj3gKouRNmhO8H290ybnJTOPfBDtTMXSQA/QLTvr7PeA== dependencies: idb "^7.0.1" - workbox-core "6.6.1" + workbox-core "7.0.0" -workbox-broadcast-update@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.6.1.tgz#0fad9454cf8e4ace0c293e5617c64c75d8a8c61e" - integrity sha512-fBhffRdaANdeQ1V8s692R9l/gzvjjRtydBOvR6WCSB0BNE2BacA29Z4r9/RHd9KaXCPl6JTdI9q0bR25YKP8TQ== +workbox-broadcast-update@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-7.0.0.tgz#7f611ca1a94ba8ac0aa40fa171c9713e0f937d22" + integrity sha512-oUuh4jzZrLySOo0tC0WoKiSg90bVAcnE98uW7F8GFiSOXnhogfNDGZelPJa+6KpGBO5+Qelv04Hqx2UD+BJqNQ== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" -workbox-build@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.6.1.tgz#6010e9ce550910156761448f2dbea8cfcf759cb0" - integrity sha512-INPgDx6aRycAugUixbKgiEQBWD0MPZqU5r0jyr24CehvNuLPSXp/wGOpdRJmts656lNiXwqV7dC2nzyrzWEDnw== +workbox-build@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-7.0.0.tgz#02ab5ef2991b3369b8b9395703f08912212769b4" + integrity sha512-CttE7WCYW9sZC+nUYhQg3WzzGPr4IHmrPnjKiu3AMXsiNQKx+l4hHl63WTrnicLmKEKHScWDH8xsGBdrYgtBzg== dependencies: "@apideck/better-ajv-errors" "^0.3.1" "@babel/core" "^7.11.1" @@ -12294,132 +12294,132 @@ workbox-build@6.6.1: strip-comments "^2.0.1" tempy "^0.6.0" upath "^1.2.0" - workbox-background-sync "6.6.1" - workbox-broadcast-update "6.6.1" - workbox-cacheable-response "6.6.1" - workbox-core "6.6.1" - workbox-expiration "6.6.1" - workbox-google-analytics "6.6.1" - workbox-navigation-preload "6.6.1" - workbox-precaching "6.6.1" - workbox-range-requests "6.6.1" - workbox-recipes "6.6.1" - workbox-routing "6.6.1" - workbox-strategies "6.6.1" - workbox-streams "6.6.1" - workbox-sw "6.6.1" - workbox-window "6.6.1" + workbox-background-sync "7.0.0" + workbox-broadcast-update "7.0.0" + workbox-cacheable-response "7.0.0" + workbox-core "7.0.0" + workbox-expiration "7.0.0" + workbox-google-analytics "7.0.0" + workbox-navigation-preload "7.0.0" + workbox-precaching "7.0.0" + workbox-range-requests "7.0.0" + workbox-recipes "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" + workbox-streams "7.0.0" + workbox-sw "7.0.0" + workbox-window "7.0.0" -workbox-cacheable-response@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.6.1.tgz#284c2b86be3f4fd191970ace8c8e99797bcf58e9" - integrity sha512-85LY4veT2CnTCDxaVG7ft3NKaFbH6i4urZXgLiU4AiwvKqS2ChL6/eILiGRYXfZ6gAwDnh5RkuDbr/GMS4KSag== +workbox-cacheable-response@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-7.0.0.tgz#ee27c036728189eed69d25a135013053277482d2" + integrity sha512-0lrtyGHn/LH8kKAJVOQfSu3/80WDc9Ma8ng0p2i/5HuUndGttH+mGMSvOskjOdFImLs2XZIimErp7tSOPmu/6g== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" -workbox-core@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.6.1.tgz#7184776d4134c5ed2f086878c882728fc9084265" - integrity sha512-ZrGBXjjaJLqzVothoE12qTbVnOAjFrHDXpZe7coCb6q65qI/59rDLwuFMO4PcZ7jcbxY+0+NhUVztzR/CbjEFw== +workbox-core@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-7.0.0.tgz#dec114ec923cc2adc967dd9be1b8a0bed50a3545" + integrity sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ== -workbox-expiration@6.6.1, workbox-expiration@^6.6.0: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.6.1.tgz#a841fa36676104426dbfb9da1ef6a630b4f93739" - integrity sha512-qFiNeeINndiOxaCrd2DeL1Xh1RFug3JonzjxUHc5WkvkD2u5abY3gZL1xSUNt3vZKsFFGGORItSjVTVnWAZO4A== +workbox-expiration@7.0.0, workbox-expiration@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-7.0.0.tgz#3d90bcf2a7577241de950f89784f6546b66c2baa" + integrity sha512-MLK+fogW+pC3IWU9SFE+FRStvDVutwJMR5if1g7oBJx3qwmO69BNoJQVaMXq41R0gg3MzxVfwOGKx3i9P6sOLQ== dependencies: idb "^7.0.1" - workbox-core "6.6.1" + workbox-core "7.0.0" -workbox-google-analytics@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.6.1.tgz#a07a6655ab33d89d1b0b0a935ffa5dea88618c5d" - integrity sha512-1TjSvbFSLmkpqLcBsF7FuGqqeDsf+uAXO/pjiINQKg3b1GN0nBngnxLcXDYo1n/XxK4N7RaRrpRlkwjY/3ocuA== +workbox-google-analytics@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-7.0.0.tgz#603b2c4244af1e85de0fb26287d4e17d3293452a" + integrity sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg== dependencies: - workbox-background-sync "6.6.1" - workbox-core "6.6.1" - workbox-routing "6.6.1" - workbox-strategies "6.6.1" + workbox-background-sync "7.0.0" + workbox-core "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" -workbox-navigation-preload@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.6.1.tgz#61a34fe125558dd88cf09237f11bd966504ea059" - integrity sha512-DQCZowCecO+wRoIxJI2V6bXWK6/53ff+hEXLGlQL4Rp9ZaPDLrgV/32nxwWIP7QpWDkVEtllTAK5h6cnhxNxDA== +workbox-navigation-preload@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-7.0.0.tgz#4913878dbbd97057181d57baa18d2bbdde085c6c" + integrity sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" -workbox-precaching@6.6.1, workbox-precaching@^6.6.0: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.6.1.tgz#dedeeba10a2d163d990bf99f1c2066ac0d1a19e2" - integrity sha512-K4znSJ7IKxCnCYEdhNkMr7X1kNh8cz+mFgx9v5jFdz1MfI84pq8C2zG+oAoeE5kFrUf7YkT5x4uLWBNg0DVZ5A== +workbox-precaching@7.0.0, workbox-precaching@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-7.0.0.tgz#3979ba8033aadf3144b70e9fe631d870d5fbaa03" + integrity sha512-EC0vol623LJqTJo1mkhD9DZmMP604vHqni3EohhQVwhJlTgyKyOkMrZNy5/QHfOby+39xqC01gv4LjOm4HSfnA== dependencies: - workbox-core "6.6.1" - workbox-routing "6.6.1" - workbox-strategies "6.6.1" + workbox-core "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" -workbox-range-requests@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.6.1.tgz#ddaf7e73af11d362fbb2f136a9063a4c7f507a39" - integrity sha512-4BDzk28govqzg2ZpX0IFkthdRmCKgAKreontYRC5YsAPB2jDtPNxqx3WtTXgHw1NZalXpcH/E4LqUa9+2xbv1g== +workbox-range-requests@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-7.0.0.tgz#97511901e043df27c1aa422adcc999a7751f52ed" + integrity sha512-SxAzoVl9j/zRU9OT5+IQs7pbJBOUOlriB8Gn9YMvi38BNZRbM+RvkujHMo8FOe9IWrqqwYgDFBfv6sk76I1yaQ== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" -workbox-recipes@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.6.1.tgz#ea70d2b2b0b0bce8de0a9d94f274d4a688e69fae" - integrity sha512-/oy8vCSzromXokDA+X+VgpeZJvtuf8SkQ8KL0xmRivMgJZrjwM3c2tpKTJn6PZA6TsbxGs3Sc7KwMoZVamcV2g== +workbox-recipes@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-7.0.0.tgz#1a6a01c8c2dfe5a41eef0fed3fe517e8a45c6514" + integrity sha512-DntcK9wuG3rYQOONWC0PejxYYIDHyWWZB/ueTbOUDQgefaeIj1kJ7pdP3LZV2lfrj8XXXBWt+JDRSw1lLLOnww== dependencies: - workbox-cacheable-response "6.6.1" - workbox-core "6.6.1" - workbox-expiration "6.6.1" - workbox-precaching "6.6.1" - workbox-routing "6.6.1" - workbox-strategies "6.6.1" + workbox-cacheable-response "7.0.0" + workbox-core "7.0.0" + workbox-expiration "7.0.0" + workbox-precaching "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" -workbox-routing@6.6.1, workbox-routing@^6.6.0: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.6.1.tgz#cba9a1c7e0d1ea11e24b6f8c518840efdc94f581" - integrity sha512-j4ohlQvfpVdoR8vDYxTY9rA9VvxTHogkIDwGdJ+rb2VRZQ5vt1CWwUUZBeD/WGFAni12jD1HlMXvJ8JS7aBWTg== +workbox-routing@7.0.0, workbox-routing@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-7.0.0.tgz#6668438a06554f60645aedc77244a4fe3a91e302" + integrity sha512-8YxLr3xvqidnbVeGyRGkaV4YdlKkn5qZ1LfEePW3dq+ydE73hUUJJuLmGEykW3fMX8x8mNdL0XrWgotcuZjIvA== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" -workbox-strategies@6.6.1, workbox-strategies@^6.6.0: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.6.1.tgz#38d0f0fbdddba97bd92e0c6418d0b1a2ccd5b8bf" - integrity sha512-WQLXkRnsk4L81fVPkkgon1rZNxnpdO5LsO+ws7tYBC6QQQFJVI6v98klrJEjFtZwzw/mB/HT5yVp7CcX0O+mrw== +workbox-strategies@7.0.0, workbox-strategies@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-7.0.0.tgz#dcba32b3f3074476019049cc490fe1a60ea73382" + integrity sha512-dg3qJU7tR/Gcd/XXOOo7x9QoCI9nk74JopaJaYAQ+ugLi57gPsXycVdBnYbayVj34m6Y8ppPwIuecrzkpBVwbA== dependencies: - workbox-core "6.6.1" + workbox-core "7.0.0" -workbox-streams@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.6.1.tgz#b2f7ba7b315c27a6e3a96a476593f99c5d227d26" - integrity sha512-maKG65FUq9e4BLotSKWSTzeF0sgctQdYyTMq529piEN24Dlu9b6WhrAfRpHdCncRS89Zi2QVpW5V33NX8PgH3Q== +workbox-streams@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-7.0.0.tgz#36722aecd04785f88b6f709e541c094fc658c0f9" + integrity sha512-moVsh+5to//l6IERWceYKGiftc+prNnqOp2sgALJJFbnNVpTXzKISlTIsrWY+ogMqt+x1oMazIdHj25kBSq/HQ== dependencies: - workbox-core "6.6.1" - workbox-routing "6.6.1" + workbox-core "7.0.0" + workbox-routing "7.0.0" -workbox-sw@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.6.1.tgz#d4c4ca3125088e8b9fd7a748ed537fa0247bd72c" - integrity sha512-R7whwjvU2abHH/lR6kQTTXLHDFU2izht9kJOvBRYK65FbwutT4VvnUAJIgHvfWZ/fokrOPhfoWYoPCMpSgUKHQ== +workbox-sw@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-7.0.0.tgz#7350126411e3de1409f7ec243df8d06bb5b08b86" + integrity sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA== -workbox-webpack-plugin@^6.6.0: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.6.1.tgz#4f81cc1ad4e5d2cd7477a86ba83c84ee2d187531" - integrity sha512-zpZ+ExFj9NmiI66cFEApyjk7hGsfJ1YMOaLXGXBoZf0v7Iu6hL0ZBe+83mnDq3YYWAfA3fnyFejritjOHkFcrA== +workbox-webpack-plugin@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-7.0.0.tgz#6c61661a2cacde1239192a5877a041a2943d1a55" + integrity sha512-R1ZzCHPfzeJjLK2/TpKUhxSQ3fFDCxlWxgRhhSjMQLz3G2MlBnyw/XeYb34e7SGgSv0qG22zEhMIzjMNqNeKbw== dependencies: fast-json-stable-stringify "^2.1.0" pretty-bytes "^5.4.1" upath "^1.2.0" webpack-sources "^1.4.3" - workbox-build "6.6.1" + workbox-build "7.0.0" -workbox-window@6.6.1, workbox-window@^6.6.0: - version "6.6.1" - resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.6.1.tgz#f22a394cbac36240d0dadcbdebc35f711bb7b89e" - integrity sha512-wil4nwOY58nTdCvif/KEZjQ2NP8uk3gGeRNy2jPBbzypU4BT4D9L8xiwbmDBpZlSgJd2xsT9FvSNU0gsxV51JQ== +workbox-window@7.0.0, workbox-window@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-7.0.0.tgz#a683ab33c896e4f16786794eac7978fc98a25d08" + integrity sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA== dependencies: "@types/trusted-types" "^2.0.2" - workbox-core "6.6.1" + workbox-core "7.0.0" wrap-ansi@^5.1.0: version "5.1.0" From 942d850b0a08bed29cf849c3dd09f4c2710df731 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Thu, 1 Jun 2023 12:14:49 +0200 Subject: [PATCH 02/17] Allow carets in URL search params (#25216) --- config/initializers/twitter_regex.rb | 2 +- spec/services/fetch_link_card_service_spec.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/config/initializers/twitter_regex.rb b/config/initializers/twitter_regex.rb index 6a7723fd21..e65b05dfde 100644 --- a/config/initializers/twitter_regex.rb +++ b/config/initializers/twitter_regex.rb @@ -25,7 +25,7 @@ module Twitter::TwitterText \) /iox UCHARS = '\u{A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}\u{10000}-\u{1FFFD}\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}\u{40000}-\u{4FFFD}\u{50000}-\u{5FFFD}\u{60000}-\u{6FFFD}\u{70000}-\u{7FFFD}\u{80000}-\u{8FFFD}\u{90000}-\u{9FFFD}\u{A0000}-\u{AFFFD}\u{B0000}-\u{BFFFD}\u{C0000}-\u{CFFFD}\u{D0000}-\u{DFFFD}\u{E1000}-\u{EFFFD}\u{E000}-\u{F8FF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}' - REGEXEN[:valid_url_query_chars] = /[a-z0-9!?\*'\(\);:&=\+\$\/%#\[\]\-_\.,~|@#{UCHARS}]/iou + REGEXEN[:valid_url_query_chars] = /[a-z0-9!?\*'\(\);:&=\+\$\/%#\[\]\-_\.,~|@\^#{UCHARS}]/iou REGEXEN[:valid_url_query_ending_chars] = /[a-z0-9_&=#\/\-#{UCHARS}]/iou REGEXEN[:valid_url_path] = /(?: (?: diff --git a/spec/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb index 7016ecd3f4..6495b323c4 100644 --- a/spec/services/fetch_link_card_service_spec.rb +++ b/spec/services/fetch_link_card_service_spec.rb @@ -12,6 +12,7 @@ RSpec.describe FetchLinkCardService, type: :service do stub_request(:get, 'http://example.com/koi8-r').to_return(request_fixture('koi8-r.txt')) stub_request(:get, 'http://example.com/日本語').to_return(request_fixture('sjis.txt')) stub_request(:get, 'https://github.com/qbi/WannaCry').to_return(status: 404) + stub_request(:get, 'http://example.com/test?data=file.gpx%5E1').to_return(status: 200) stub_request(:get, 'http://example.com/test-').to_return(request_fixture('idn.txt')) stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt')) @@ -87,6 +88,15 @@ RSpec.describe FetchLinkCardService, type: :service do expect(a_request(:get, 'http://example.com/sjis')).to_not have_been_made end end + + context do + let(:status) { Fabricate(:status, text: 'test http://example.com/test?data=file.gpx^1') } + + it 'does fetch URLs with a caret in search params' do + expect(a_request(:get, 'http://example.com/test?data=file.gpx')).to_not have_been_made + expect(a_request(:get, 'http://example.com/test?data=file.gpx%5E1')).to have_been_made.once + end + end end context 'with a remote status' do From b3f9a0da520e8024de94b30f801e4ebb83336e8b Mon Sep 17 00:00:00 2001 From: "S.H" Date: Thu, 1 Jun 2023 20:46:19 +0900 Subject: [PATCH 03/17] Fix using Date.now() instead of intl.now() (#25234) --- app/javascript/mastodon/components/poll.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/components/poll.jsx b/app/javascript/mastodon/components/poll.jsx index fd2efd59c5..dfc4034fa3 100644 --- a/app/javascript/mastodon/components/poll.jsx +++ b/app/javascript/mastodon/components/poll.jsx @@ -57,9 +57,9 @@ class Poll extends ImmutablePureComponent { }; static getDerivedStateFromProps (props, state) { - const { poll, intl } = props; + const { poll } = props; const expires_at = poll.get('expires_at'); - const expired = poll.get('expired') || expires_at !== null && (new Date(expires_at)).getTime() < intl.now(); + const expired = poll.get('expired') || expires_at !== null && (new Date(expires_at)).getTime() < Date.now(); return (expired === state.expired) ? null : { expired }; } @@ -76,10 +76,10 @@ class Poll extends ImmutablePureComponent { } _setupTimer () { - const { poll, intl } = this.props; + const { poll } = this.props; clearTimeout(this._timer); if (!this.state.expired) { - const delay = (new Date(poll.get('expires_at'))).getTime() - intl.now(); + const delay = (new Date(poll.get('expires_at'))).getTime() - Date.now(); this._timer = setTimeout(() => { this.setState({ expired: true }); }, delay); From 3182ecc24c50485d5ce0456a13810023529bf71b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 14:27:58 +0200 Subject: [PATCH 04/17] Bump puma from 6.2.2 to 6.3.0 (#25229) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 5aad3f615b..62e45a5f30 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ ruby '>= 3.0.0' gem 'pkg-config', '~> 1.5' -gem 'puma', '~> 6.2' +gem 'puma', '~> 6.3' gem 'rails', '~> 6.1.7' gem 'sprockets', '~> 3.7.2' gem 'thor', '~> 1.2' diff --git a/Gemfile.lock b/Gemfile.lock index 6371bb872a..e1c4cc45c2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -501,7 +501,7 @@ GEM premailer (~> 1.7, >= 1.7.9) private_address_check (0.5.0) public_suffix (5.0.1) - puma (6.2.2) + puma (6.3.0) nio4r (~> 2.0) pundit (2.3.0) activesupport (>= 3.0.0) @@ -847,7 +847,7 @@ DEPENDENCIES premailer-rails private_address_check (~> 0.5) public_suffix (~> 5.0) - puma (~> 6.2) + puma (~> 6.3) pundit (~> 2.3) rack (~> 2.2.7) rack-attack (~> 6.6) From 2a353200ad819f6ef5d94d265cc131c969ce4be3 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Thu, 1 Jun 2023 09:29:29 -0300 Subject: [PATCH 05/17] Add test coverage for `Mastodon::CLI::Accounts#rotate` (#25212) --- spec/lib/mastodon/cli/accounts_spec.rb | 74 ++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb index d62aaadd7b..ba49e480ad 100644 --- a/spec/lib/mastodon/cli/accounts_spec.rb +++ b/spec/lib/mastodon/cli/accounts_spec.rb @@ -924,4 +924,78 @@ describe Mastodon::CLI::Accounts do end end end + + describe '#rotate' do + context 'when neither username nor --all option are given' do + it 'exits with an error message' do + expect { cli.rotate }.to output( + a_string_including('No account(s) given') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when a username is given' do + let(:account) { Fabricate(:account) } + + it 'correctly rotates keys for the specified account' do + old_private_key = account.private_key + old_public_key = account.public_key + + cli.rotate(account.username) + account.reload + + expect(account.private_key).to_not eq(old_private_key) + expect(account.public_key).to_not eq(old_public_key) + end + + it 'broadcasts the new keys for the specified account' do + allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_in) + + cli.rotate(account.username) + + expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_in).with(anything, account.id, anything).once + end + + context 'when the given username is not found' do + it 'exits with an error message when the specified username is not found' do + expect { cli.rotate('non_existent_username') }.to output( + a_string_including('No such account') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + + context 'when --all option is provided' do + let(:accounts) { Fabricate.times(3, :account) } + let(:options) { { all: true } } + + before do + allow(Account).to receive(:local).and_return(Account.where(id: accounts.map(&:id))) + cli.options = { all: true } + end + + it 'correctly rotates keys for all local accounts' do + old_private_keys = accounts.map(&:private_key) + old_public_keys = accounts.map(&:public_key) + + cli.rotate + accounts.each(&:reload) + + expect(accounts.map(&:private_key)).to_not eq(old_private_keys) + expect(accounts.map(&:public_key)).to_not eq(old_public_keys) + end + + it 'broadcasts the new keys for each account' do + allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_in) + + cli.rotate + + accounts.each do |account| + expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_in).with(anything, account.id, anything).once + end + end + end + end end From 35c1c3e57a416370fdd62720d85c54490d6c8e20 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 1 Jun 2023 08:31:24 -0400 Subject: [PATCH 06/17] Add CLI area progress bar helper (#25208) --- lib/mastodon/cli/base.rb | 27 +++++++++++++++-- .../cli/{helper.rb => progress_helper.rb} | 30 +++++++++---------- 2 files changed, 39 insertions(+), 18 deletions(-) rename lib/mastodon/cli/{helper.rb => progress_helper.rb} (80%) diff --git a/lib/mastodon/cli/base.rb b/lib/mastodon/cli/base.rb index f3c9fea921..32aff2fcc5 100644 --- a/lib/mastodon/cli/base.rb +++ b/lib/mastodon/cli/base.rb @@ -4,16 +4,39 @@ require_relative '../../../config/boot' require_relative '../../../config/environment' require 'thor' -require_relative 'helper' +require_relative 'progress_helper' module Mastodon module CLI class Base < Thor - include CLI::Helper + include ProgressHelper def self.exit_on_failure? true end + + private + + def pastel + @pastel ||= Pastel.new + end + + def dry_run? + options[:dry_run] + end + + def dry_run_mode_suffix + dry_run? ? ' (DRY RUN)' : '' + end + + def reset_connection_pools! + ActiveRecord::Base.establish_connection( + ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first.configuration_hash + .dup + .tap { |config| config['pool'] = options[:concurrency] + 1 } + ) + RedisConfiguration.establish_pool(options[:concurrency]) + end end end end diff --git a/lib/mastodon/cli/helper.rb b/lib/mastodon/cli/progress_helper.rb similarity index 80% rename from lib/mastodon/cli/helper.rb rename to lib/mastodon/cli/progress_helper.rb index 78931b9a22..1fa2745c18 100644 --- a/lib/mastodon/cli/helper.rb +++ b/lib/mastodon/cli/progress_helper.rb @@ -9,23 +9,19 @@ HttpLog.configuration.logger = dev_null Paperclip.options[:log] = false Chewy.logger = dev_null -module Mastodon::CLI - module Helper - def dry_run? - options[:dry_run] - end +require 'ruby-progressbar/outputs/null' - def dry_run_mode_suffix - dry_run? ? ' (DRY RUN)' : '' - end +module Mastodon::CLI + module ProgressHelper + PROGRESS_FORMAT = '%c/%u |%b%i| %e' def create_progress_bar(total = nil) - ProgressBar.create(total: total, format: '%c/%u |%b%i| %e') - end - - def reset_connection_pools! - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Rails.env].dup.tap { |config| config['pool'] = options[:concurrency] + 1 }) - RedisConfiguration.establish_pool(options[:concurrency]) + ProgressBar.create( + { + total: total, + format: PROGRESS_FORMAT, + }.merge(progress_output_options) + ) end def parallelize_with_progress(scope) @@ -82,8 +78,10 @@ module Mastodon::CLI [total.value, aggregate.value] end - def pastel - @pastel ||= Pastel.new + private + + def progress_output_options + Rails.env.test? ? { output: ProgressBar::Outputs::Null } : {} end end end From b489c200a63f914749485a0b8f07eeafdfd756e5 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 1 Jun 2023 08:32:34 -0400 Subject: [PATCH 07/17] Update yarn.lock for audit issues (#25152) --- yarn.lock | 51 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/yarn.lock b/yarn.lock index f992961b92..3ae6343635 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1743,6 +1743,15 @@ "@jridgewell/set-array" "^1.0.0" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/gen-mapping@^0.3.2": version "0.3.2" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" @@ -1767,6 +1776,14 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/source-map@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.3.tgz#8108265659d4c33e72ffe14e33d6cc5eb59f2fda" + integrity sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/sourcemap-codec@1.4.14": version "1.4.14" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" @@ -3007,9 +3024,9 @@ ansi-regex@^2.0.0: integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" @@ -7845,9 +7862,9 @@ loader-utils@^1.2.3, loader-utils@^1.4.0: json5 "^1.0.1" loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" @@ -10767,7 +10784,7 @@ source-map@^0.7.3: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== -source-map@^0.8.0-beta.0, source-map@~0.8.0-beta.0: +source-map@^0.8.0-beta.0: version "0.8.0-beta.0" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== @@ -10848,9 +10865,9 @@ sprintf-js@~1.0.2: integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= ssri@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808" - integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA== + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== dependencies: minipass "^3.1.1" @@ -11344,13 +11361,13 @@ terser-webpack-plugin@^1.4.3, terser-webpack-plugin@^4.2.3: webpack-sources "^1.4.3" terser@^5.0.0, terser@^5.3.4: - version "5.13.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.13.1.tgz#66332cdc5a01b04a224c9fad449fc1a18eaa1799" - integrity sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA== + version "5.17.6" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.6.tgz#d810e75e1bb3350c799cd90ebefe19c9412c12de" + integrity sha512-V8QHcs8YuyLkLHsJO5ucyff1ykrLVsR4dNnS//L5Y3NiSXpbK1J+WMVUs67eI0KTxs9JtHhgEQpXQVHlHI92DQ== dependencies: + "@jridgewell/source-map" "^0.3.2" acorn "^8.5.0" commander "^2.20.0" - source-map "~0.8.0-beta.0" source-map-support "~0.5.20" tesseract.js-core@^2.2.0: @@ -12470,9 +12487,9 @@ write-file-atomic@^5.0.1: signal-exit "^4.0.1" ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + version "6.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" + integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== dependencies: async-limiter "~1.0.0" From cd4f0feab89af1ff9b1e18a8640b4bcab915cb1d Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 1 Jun 2023 08:35:05 -0400 Subject: [PATCH 08/17] Extract verify options method in search cli (#25121) --- lib/mastodon/cli/search.rb | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/lib/mastodon/cli/search.rb b/lib/mastodon/cli/search.rb index 6f48dcb096..8d7b7202fa 100644 --- a/lib/mastodon/cli/search.rb +++ b/lib/mastodon/cli/search.rb @@ -29,15 +29,7 @@ module Mastodon::CLI database will be imported into the indices, unless overridden with --no-import. LONG_DESC def deploy - if options[:concurrency] < 1 - say('Cannot run with this concurrency setting, must be at least 1', :red) - exit(1) - end - - if options[:batch_size] < 1 - say('Cannot run with this batch_size setting, must be at least 1', :red) - exit(1) - end + verify_deploy_options! indices = if options[:only] options[:only].map { |str| "#{str.camelize}Index".constantize } @@ -98,5 +90,26 @@ module Mastodon::CLI say("Indexed #{added} records, de-indexed #{removed}", :green, true) end + + private + + def verify_deploy_options! + verify_deploy_concurrency! + verify_deploy_batch_size! + end + + def verify_deploy_concurrency! + return unless options[:concurrency] < 1 + + say('Cannot run with this concurrency setting, must be at least 1', :red) + exit(1) + end + + def verify_deploy_batch_size! + return unless options[:batch_size] < 1 + + say('Cannot run with this batch_size setting, must be at least 1', :red) + exit(1) + end end end From d91607feee4113dfecb2cf2bc45c1be9b2ce7dc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 14:39:43 +0200 Subject: [PATCH 09/17] Bump sidekiq-scheduler from 5.0.2 to 5.0.3 (#25228) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e1c4cc45c2..7d04d875c5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -649,7 +649,7 @@ GEM redis (>= 4.5.0, < 5) sidekiq-bulk (0.2.0) sidekiq - sidekiq-scheduler (5.0.2) + sidekiq-scheduler (5.0.3) rufus-scheduler (~> 3.2) sidekiq (>= 6, < 8) tilt (>= 1.4.0) From 8884d1ece0eabd769e616f36785b6a7b14985469 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 1 Jun 2023 14:47:31 +0200 Subject: [PATCH 10/17] Add support for importing lists (#25203) --- .../settings/imports_controller.rb | 4 ++ app/models/bulk_import.rb | 1 + app/models/form/import.rb | 19 +++-- app/services/bulk_import_row_service.rb | 8 ++- app/services/bulk_import_service.rb | 22 ++++++ app/views/settings/imports/index.html.haml | 2 +- spec/fixtures/files/lists.csv | 3 + spec/models/form/import_spec.rb | 10 +++ spec/services/bulk_import_row_service_spec.rb | 72 +++++++++++++++++++ 9 files changed, 132 insertions(+), 9 deletions(-) create mode 100644 spec/fixtures/files/lists.csv diff --git a/app/controllers/settings/imports_controller.rb b/app/controllers/settings/imports_controller.rb index bdbf8796fe..983caf22fa 100644 --- a/app/controllers/settings/imports_controller.rb +++ b/app/controllers/settings/imports_controller.rb @@ -12,6 +12,7 @@ class Settings::ImportsController < Settings::BaseController muting: 'muted_accounts_failures.csv', domain_blocking: 'blocked_domains_failures.csv', bookmarks: 'bookmarks_failures.csv', + lists: 'lists_failures.csv', }.freeze TYPE_TO_HEADERS_MAP = { @@ -20,6 +21,7 @@ class Settings::ImportsController < Settings::BaseController muting: ['Account address', 'Hide notifications'], domain_blocking: false, bookmarks: false, + lists: false, }.freeze def index @@ -49,6 +51,8 @@ class Settings::ImportsController < Settings::BaseController csv << [row.data['domain']] when :bookmarks csv << [row.data['uri']] + when :lists + csv << [row.data['list_name'], row.data['acct']] end end end diff --git a/app/models/bulk_import.rb b/app/models/bulk_import.rb index af9a9670bf..810e471849 100644 --- a/app/models/bulk_import.rb +++ b/app/models/bulk_import.rb @@ -30,6 +30,7 @@ class BulkImport < ApplicationRecord muting: 2, domain_blocking: 3, bookmarks: 4, + lists: 5, } enum state: { diff --git a/app/models/form/import.rb b/app/models/form/import.rb index 750ef84be4..2fc74715b5 100644 --- a/app/models/form/import.rb +++ b/app/models/form/import.rb @@ -18,6 +18,7 @@ class Form::Import muting: ['Account address', 'Hide notifications'], domain_blocking: ['#domain'], bookmarks: ['#uri'], + lists: ['List name', 'Account address'], }.freeze KNOWN_FIRST_HEADERS = EXPECTED_HEADERS_BY_TYPE.values.map(&:first).uniq.freeze @@ -30,6 +31,7 @@ class Form::Import 'Hide notifications' => 'hide_notifications', '#domain' => 'domain', '#uri' => 'uri', + 'List name' => 'list_name', }.freeze class EmptyFileError < StandardError; end @@ -48,6 +50,7 @@ class Form::Import return :muting if data.original_filename&.start_with?('mutes') || data.original_filename&.start_with?('muted_accounts') return :domain_blocking if data.original_filename&.start_with?('domain_blocks') || data.original_filename&.start_with?('blocked_domains') return :bookmarks if data.original_filename&.start_with?('bookmarks') + return :lists if data.original_filename&.start_with?('lists') end # Whether the uploaded CSV file seems to correspond to a different import type than the one selected @@ -76,14 +79,16 @@ class Form::Import private - def default_csv_header + def default_csv_headers case type.to_sym when :following, :blocking, :muting - 'Account address' + ['Account address'] when :domain_blocking - '#domain' + ['#domain'] when :bookmarks - '#uri' + ['#uri'] + when :lists + ['List name', 'Account address'] end end @@ -98,7 +103,7 @@ class Form::Import field&.split(',')&.map(&:strip)&.presence when 'Account address' field.strip.gsub(/\A@/, '') - when '#domain', '#uri' + when '#domain', '#uri', 'List name' field.strip else field @@ -109,7 +114,7 @@ class Form::Import @csv_data.take(1) # Ensure the headers are read raise EmptyFileError if @csv_data.headers == true - @csv_data = CSV.open(data.path, encoding: 'UTF-8', skip_blanks: true, headers: [default_csv_header], converters: csv_converter) unless KNOWN_FIRST_HEADERS.include?(@csv_data.headers&.first) + @csv_data = CSV.open(data.path, encoding: 'UTF-8', skip_blanks: true, headers: default_csv_headers, converters: csv_converter) unless KNOWN_FIRST_HEADERS.include?(@csv_data.headers&.first) @csv_data end @@ -133,7 +138,7 @@ class Form::Import def validate_data return if data.nil? return errors.add(:data, I18n.t('imports.errors.too_large')) if data.size > FILE_SIZE_LIMIT - return errors.add(:data, I18n.t('imports.errors.incompatible_type')) unless csv_data.headers.include?(default_csv_header) + return errors.add(:data, I18n.t('imports.errors.incompatible_type')) unless default_csv_headers.all? { |header| csv_data.headers.include?(header) } errors.add(:data, I18n.t('imports.errors.over_rows_processing_limit', count: ROWS_PROCESSING_LIMIT)) if csv_row_count > ROWS_PROCESSING_LIMIT diff --git a/app/services/bulk_import_row_service.rb b/app/services/bulk_import_row_service.rb index 4046ef4eed..ef4c18e789 100644 --- a/app/services/bulk_import_row_service.rb +++ b/app/services/bulk_import_row_service.rb @@ -7,7 +7,7 @@ class BulkImportRowService @type = row.bulk_import.type.to_sym case @type - when :following, :blocking, :muting + when :following, :blocking, :muting, :lists target_acct = @data['acct'] target_domain = domain(target_acct) @target_account = stoplight_wrap_request(target_domain) { ResolveAccountService.new.call(target_acct, { check_delivery_availability: true }) } @@ -33,6 +33,12 @@ class BulkImportRowService return false unless StatusPolicy.new(@account, @target_status).show? @account.bookmarks.find_or_create_by!(status: @target_status) + when :lists + list = @account.owned_lists.find_or_create_by!(title: @data['list_name']) + + FollowService.new.call(@account, @target_account) unless @account.id == @target_account.id + + list.accounts << @target_account end true diff --git a/app/services/bulk_import_service.rb b/app/services/bulk_import_service.rb index 2701b0c7e0..5c14adc499 100644 --- a/app/services/bulk_import_service.rb +++ b/app/services/bulk_import_service.rb @@ -16,6 +16,8 @@ class BulkImportService < BaseService import_domain_blocks! when :bookmarks import_bookmarks! + when :lists + import_lists! end @import.update!(state: :finished, finished_at: Time.now.utc) if @import.processed_items == @import.total_items @@ -157,4 +159,24 @@ class BulkImportService < BaseService [row.id] end end + + def import_lists! + rows = @import.rows.to_a + + if @import.overwrite? + included_lists = rows.map { |row| row.data['list_name'] }.uniq + + @account.owned_lists.where.not(title: included_lists).destroy_all + + # As list membership changes do not retroactively change timeline + # contents, simplify things by just clearing everything + @account.owned_lists.find_each do |list| + list.list_accounts.destroy_all + end + end + + Import::RowWorker.push_bulk(rows) do |row| + [row.id] + end + end end diff --git a/app/views/settings/imports/index.html.haml b/app/views/settings/imports/index.html.haml index 02c3f4eb3f..5f7950b598 100644 --- a/app/views/settings/imports/index.html.haml +++ b/app/views/settings/imports/index.html.haml @@ -3,7 +3,7 @@ = simple_form_for @import, url: settings_imports_path do |f| .field-group - = f.input :type, as: :grouped_select, collection: { constructive: %i(following bookmarks), destructive: %i(muting blocking domain_blocking) }, wrapper: :with_block_label, include_blank: false, label_method: ->(type) { I18n.t("imports.types.#{type}") }, group_label_method: ->(group) { I18n.t("imports.type_groups.#{group.first}") }, group_method: :last, hint: t('imports.preface') + = f.input :type, as: :grouped_select, collection: { constructive: %i(following bookmarks lists), destructive: %i(muting blocking domain_blocking) }, wrapper: :with_block_label, include_blank: false, label_method: ->(type) { I18n.t("imports.types.#{type}") }, group_label_method: ->(group) { I18n.t("imports.type_groups.#{group.first}") }, group_method: :last, hint: t('imports.preface') .fields-row .fields-group.fields-row__column.fields-row__column-6 diff --git a/spec/fixtures/files/lists.csv b/spec/fixtures/files/lists.csv new file mode 100644 index 0000000000..3155ed6d57 --- /dev/null +++ b/spec/fixtures/files/lists.csv @@ -0,0 +1,3 @@ +Mastodon project,gargron@example.com +Mastodon project,mastodon@example.com +test,foo@example.com diff --git a/spec/models/form/import_spec.rb b/spec/models/form/import_spec.rb index e1fea4205c..52cf1c96ed 100644 --- a/spec/models/form/import_spec.rb +++ b/spec/models/form/import_spec.rb @@ -86,6 +86,7 @@ RSpec.describe Form::Import do it_behaves_like 'too many CSV rows', 'muting', 'imports.txt', 1 it_behaves_like 'too many CSV rows', 'domain_blocking', 'domain_blocks.csv', 2 it_behaves_like 'too many CSV rows', 'bookmarks', 'bookmark-imports.txt', 3 + it_behaves_like 'too many CSV rows', 'lists', 'lists.csv', 2 # Importing list of addresses with no headers into various types it_behaves_like 'valid import', 'following', 'imports.txt' @@ -98,6 +99,9 @@ RSpec.describe Form::Import do # Importing bookmarks list with no headers into expected type it_behaves_like 'valid import', 'bookmarks', 'bookmark-imports.txt' + # Importing lists with no headers into expected type + it_behaves_like 'valid import', 'lists', 'lists.csv' + # Importing followed accounts with headers into various compatible types it_behaves_like 'valid import', 'following', 'following_accounts.csv' it_behaves_like 'valid import', 'blocking', 'following_accounts.csv' @@ -273,6 +277,12 @@ RSpec.describe Form::Import do { 'acct' => 'user@test.com', 'hide_notifications' => false }, ] + it_behaves_like 'on successful import', 'lists', 'merge', 'lists.csv', [ + { 'acct' => 'gargron@example.com', 'list_name' => 'Mastodon project' }, + { 'acct' => 'mastodon@example.com', 'list_name' => 'Mastodon project' }, + { 'acct' => 'foo@example.com', 'list_name' => 'test' }, + ] + # Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users # # https://github.com/mastodon/mastodon/issues/20571 diff --git a/spec/services/bulk_import_row_service_spec.rb b/spec/services/bulk_import_row_service_spec.rb index 5bbe6b0042..5e09845b53 100644 --- a/spec/services/bulk_import_row_service_spec.rb +++ b/spec/services/bulk_import_row_service_spec.rb @@ -91,5 +91,77 @@ RSpec.describe BulkImportRowService do end end end + + context 'when importing a list row' do + let(:import_type) { 'lists' } + let(:target_account) { Fabricate(:account) } + let(:data) do + { 'acct' => target_account.acct, 'list_name' => 'my list' } + end + + shared_examples 'common behavior' do + context 'when the target account is already followed' do + before do + account.follow!(target_account) + end + + it 'returns true' do + expect(subject.call(import_row)).to be true + end + + it 'adds the target account to the list' do + expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + end + end + + context 'when the user already requested to follow the target account' do + before do + account.request_follow!(target_account) + end + + it 'returns true' do + expect(subject.call(import_row)).to be true + end + + it 'adds the target account to the list' do + expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + end + end + + context 'when the target account is neither followed nor requested' do + it 'returns true' do + expect(subject.call(import_row)).to be true + end + + it 'adds the target account to the list' do + expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + end + end + + context 'when the target account is the user themself' do + let(:target_account) { account } + + it 'returns true' do + expect(subject.call(import_row)).to be true + end + + it 'adds the target account to the list' do + expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + end + end + end + + context 'when the list does not exist yet' do + include_examples 'common behavior' + end + + context 'when the list exists' do + before do + Fabricate(:list, account: account, title: 'my list') + end + + include_examples 'common behavior' + end + end end end From 5fae2de454806730742b7be7435ae1c4fb97cf3c Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 1 Jun 2023 14:47:55 +0200 Subject: [PATCH 11/17] Fix overflow behavior of account rows (#25131) --- app/javascript/mastodon/components/account.jsx | 10 +++++++--- app/javascript/styles/mastodon/components.scss | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/components/account.jsx b/app/javascript/mastodon/components/account.jsx index 68a456c1b1..ea863f5d18 100644 --- a/app/javascript/mastodon/components/account.jsx +++ b/app/javascript/mastodon/components/account.jsx @@ -143,7 +143,7 @@ class Account extends ImmutablePureComponent { const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at')); if (firstVerifiedField) { - verification = <>· ; + verification = ; } return ( @@ -154,9 +154,13 @@ class Account extends ImmutablePureComponent { -
+
- {!minimal && <> {verification} {muteTimeRemaining}} + {!minimal && ( +
+ {verification} {muteTimeRemaining} +
+ )}
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 45e7f7e7b0..6c76ddd4dd 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -7814,13 +7814,28 @@ noscript { } } +.account__contents { + overflow: hidden; +} + +.account__details { + display: flex; + flex-wrap: wrap; + column-gap: 1em; +} + .verified-badge { display: inline-flex; align-items: center; color: $valid-value-color; gap: 4px; overflow: hidden; - text-overflow: ellipsis; + white-space: nowrap; + + > span { + overflow: hidden; + text-overflow: ellipsis; + } a { color: inherit; From b0780cfeeda641645ea65da257a72ec507e71647 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Fri, 2 Jun 2023 15:00:27 +0200 Subject: [PATCH 12/17] Fix `/share` and cleanup and reorganize frontend locale loading (#25240) --- .prettierignore | 2 +- app/helpers/react_component_helper.rb | 2 +- app/javascript/mastodon/actions/streaming.js | 9 +- .../mastodon/containers/admin_component.jsx | 11 +- .../mastodon/containers/compose_container.jsx | 16 +- .../mastodon/containers/mastodon.jsx | 14 +- .../mastodon/containers/media_container.jsx | 11 +- app/javascript/mastodon/load_locale.js | 14 -- .../mastodon/locales/global_locale.ts | 22 ++ app/javascript/mastodon/locales/index.js | 22 -- app/javascript/mastodon/locales/index.ts | 5 + .../mastodon/locales/intl_provider.tsx | 56 +++++ .../mastodon/locales/load_locale.ts | 29 +++ .../mastodon/locales/locale-data/README.md | 221 ------------------ .../mastodon/locales/locale-data/co.js | 110 --------- .../mastodon/locales/locale-data/oc.js | 110 --------- .../mastodon/locales/locale-data/sa.js | 98 -------- app/javascript/packs/admin.jsx | 4 +- app/javascript/packs/application.js | 17 +- app/javascript/packs/public.jsx | 3 +- app/javascript/packs/share.jsx | 3 +- jest.config.js | 1 - package.json | 1 + spec/helpers/react_component_helper_spec.rb | 2 +- yarn.lock | 7 + 25 files changed, 152 insertions(+), 638 deletions(-) delete mode 100644 app/javascript/mastodon/load_locale.js create mode 100644 app/javascript/mastodon/locales/global_locale.ts delete mode 100644 app/javascript/mastodon/locales/index.js create mode 100644 app/javascript/mastodon/locales/index.ts create mode 100644 app/javascript/mastodon/locales/intl_provider.tsx create mode 100644 app/javascript/mastodon/locales/load_locale.ts delete mode 100644 app/javascript/mastodon/locales/locale-data/README.md delete mode 100644 app/javascript/mastodon/locales/locale-data/co.js delete mode 100644 app/javascript/mastodon/locales/locale-data/oc.js delete mode 100644 app/javascript/mastodon/locales/locale-data/sa.js diff --git a/.prettierignore b/.prettierignore index 2ea4075333..91029f665d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -61,7 +61,7 @@ docker-compose.override.yml /app/javascript/mastodon/features/emoji/emoji_map.json # Ignore locale files -/app/javascript/mastodon/locales +/app/javascript/mastodon/locales/*.json /config/locales # Ignore vendored CSS reset diff --git a/app/helpers/react_component_helper.rb b/app/helpers/react_component_helper.rb index fc08de13dd..ce616e8306 100644 --- a/app/helpers/react_component_helper.rb +++ b/app/helpers/react_component_helper.rb @@ -11,7 +11,7 @@ module ReactComponentHelper end def react_admin_component(name, props = {}) - data = { 'admin-component': name.to_s.camelcase, props: Oj.dump({ locale: I18n.locale }.merge(props)) } + data = { 'admin-component': name.to_s.camelcase, props: Oj.dump(props) } div_tag_with_data(data) end diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index 562e72655c..9daeb3c60f 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -24,8 +24,6 @@ import { fillListTimelineGaps, } from './timelines'; -const { messages } = getLocale(); - /** * @param {number} max * @returns {number} @@ -43,8 +41,10 @@ const randomUpTo = max => * @param {function(object): boolean} [options.accept] * @returns {function(): void} */ -export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) => - connectStream(channelName, params, (dispatch, getState) => { +export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) => { + const { messages } = getLocale(); + + return connectStream(channelName, params, (dispatch, getState) => { const locale = getState().getIn(['meta', 'locale']); // @ts-expect-error @@ -121,6 +121,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti }, }; }); +}; /** * @param {Function} dispatch diff --git a/app/javascript/mastodon/containers/admin_component.jsx b/app/javascript/mastodon/containers/admin_component.jsx index 562151fe24..7400111293 100644 --- a/app/javascript/mastodon/containers/admin_component.jsx +++ b/app/javascript/mastodon/containers/admin_component.jsx @@ -1,24 +1,19 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { IntlProvider } from 'react-intl'; - -import { getLocale, onProviderError } from '../locales'; - -const { messages } = getLocale(); +import { IntlProvider } from 'mastodon/locales'; export default class AdminComponent extends PureComponent { static propTypes = { - locale: PropTypes.string.isRequired, children: PropTypes.node.isRequired, }; render () { - const { locale, children } = this.props; + const { children } = this.props; return ( - + {children} ); diff --git a/app/javascript/mastodon/containers/compose_container.jsx b/app/javascript/mastodon/containers/compose_container.jsx index 751015d18d..f76550678e 100644 --- a/app/javascript/mastodon/containers/compose_container.jsx +++ b/app/javascript/mastodon/containers/compose_container.jsx @@ -1,18 +1,14 @@ -import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { IntlProvider } from 'react-intl'; - import { Provider } from 'react-redux'; import { fetchCustomEmojis } from '../actions/custom_emojis'; import { hydrateStore } from '../actions/store'; import Compose from '../features/standalone/compose'; import initialState from '../initial_state'; -import { getLocale, onProviderError } from '../locales'; +import { IntlProvider } from '../locales'; import { store } from '../store'; -const { messages } = getLocale(); if (initialState) { store.dispatch(hydrateStore(initialState)); @@ -20,17 +16,11 @@ if (initialState) { store.dispatch(fetchCustomEmojis()); -export default class TimelineContainer extends PureComponent { - - static propTypes = { - locale: PropTypes.string.isRequired, - }; +export default class ComposeContainer extends PureComponent { render () { - const { locale } = this.props; - return ( - + diff --git a/app/javascript/mastodon/containers/mastodon.jsx b/app/javascript/mastodon/containers/mastodon.jsx index c4d4611a2d..4538db050d 100644 --- a/app/javascript/mastodon/containers/mastodon.jsx +++ b/app/javascript/mastodon/containers/mastodon.jsx @@ -1,8 +1,6 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { IntlProvider } from 'react-intl'; - import { Helmet } from 'react-helmet'; import { BrowserRouter, Route } from 'react-router-dom'; @@ -16,11 +14,9 @@ import { connectUserStream } from 'mastodon/actions/streaming'; import ErrorBoundary from 'mastodon/components/error_boundary'; import UI from 'mastodon/features/ui'; import initialState, { title as siteTitle } from 'mastodon/initial_state'; -import { getLocale, onProviderError } from 'mastodon/locales'; +import { IntlProvider } from 'mastodon/locales'; import { store } from 'mastodon/store'; -const { messages } = getLocale(); - const title = process.env.NODE_ENV === 'production' ? siteTitle : `${siteTitle} (Dev)`; const hydrateAction = hydrateStore(initialState); @@ -40,10 +36,6 @@ const createIdentityContext = state => ({ export default class Mastodon extends PureComponent { - static propTypes = { - locale: PropTypes.string.isRequired, - }; - static childContextTypes = { identity: PropTypes.shape({ signedIn: PropTypes.bool.isRequired, @@ -79,10 +71,8 @@ export default class Mastodon extends PureComponent { } render () { - const { locale } = this.props; - return ( - + diff --git a/app/javascript/mastodon/containers/media_container.jsx b/app/javascript/mastodon/containers/media_container.jsx index 84eab1cae1..fba3c5df78 100644 --- a/app/javascript/mastodon/containers/media_container.jsx +++ b/app/javascript/mastodon/containers/media_container.jsx @@ -2,8 +2,6 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; import { createPortal } from 'react-dom'; -import { IntlProvider } from 'react-intl'; - import { fromJS } from 'immutable'; import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag'; @@ -14,17 +12,14 @@ import Audio from 'mastodon/features/audio'; import Card from 'mastodon/features/status/components/card'; import MediaModal from 'mastodon/features/ui/components/media_modal'; import Video from 'mastodon/features/video'; -import { getLocale, onProviderError } from 'mastodon/locales'; +import { IntlProvider } from 'mastodon/locales'; import { getScrollbarWidth } from 'mastodon/utils/scrollbar'; -const { messages } = getLocale(); - const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Hashtag, Audio }; export default class MediaContainer extends PureComponent { static propTypes = { - locale: PropTypes.string.isRequired, components: PropTypes.object.isRequired, }; @@ -73,7 +68,7 @@ export default class MediaContainer extends PureComponent { }; render () { - const { locale, components } = this.props; + const { components } = this.props; let handleOpenVideo; @@ -83,7 +78,7 @@ export default class MediaContainer extends PureComponent { } return ( - + <> {[].map.call(components, (component, i) => { const componentName = component.getAttribute('data-component'); diff --git a/app/javascript/mastodon/load_locale.js b/app/javascript/mastodon/load_locale.js deleted file mode 100644 index cb14acd622..0000000000 --- a/app/javascript/mastodon/load_locale.js +++ /dev/null @@ -1,14 +0,0 @@ -import { setLocale } from "./locales"; - -export async function loadLocale() { - const locale = document.querySelector('html').lang || 'en'; - - const localeData = await import( - /* webpackMode: "lazy" */ - /* webpackChunkName: "locale/[request]" */ - /* webpackInclude: /\.json$/ */ - /* webpackPreload: true */ - `mastodon/locales/${locale}.json`); - - setLocale({ messages: localeData }); -} diff --git a/app/javascript/mastodon/locales/global_locale.ts b/app/javascript/mastodon/locales/global_locale.ts new file mode 100644 index 0000000000..01133ca239 --- /dev/null +++ b/app/javascript/mastodon/locales/global_locale.ts @@ -0,0 +1,22 @@ +export interface LocaleData { + locale: string; + messages: Record; +} + +let loadedLocale: LocaleData; + +export function setLocale(locale: LocaleData) { + loadedLocale = locale; +} + +export function getLocale() { + if (!loadedLocale && process.env.NODE_ENV === 'development') { + throw new Error('getLocale() called before any locale has been set'); + } + + return loadedLocale; +} + +export function isLocaleLoaded() { + return !!loadedLocale; +} diff --git a/app/javascript/mastodon/locales/index.js b/app/javascript/mastodon/locales/index.js deleted file mode 100644 index 6e57e3ddc4..0000000000 --- a/app/javascript/mastodon/locales/index.js +++ /dev/null @@ -1,22 +0,0 @@ -let theLocale; - -export function setLocale(locale) { - theLocale = locale; -} - -export function getLocale() { - return theLocale; -} - -export function onProviderError(error) { - // Silent the error, like upstream does - if(process.env.NODE_ENV === 'production') return; - - // This browser does not advertise Intl support for this locale, we only print a warning - // As-per the spec, the browser should select the best matching locale - if(typeof error === "object" && error.message.match("MISSING_DATA")) { - console.warn(error.message); - } - - console.error(error); -} diff --git a/app/javascript/mastodon/locales/index.ts b/app/javascript/mastodon/locales/index.ts new file mode 100644 index 0000000000..63f45c3047 --- /dev/null +++ b/app/javascript/mastodon/locales/index.ts @@ -0,0 +1,5 @@ +export type { LocaleData } from './global_locale'; +export { setLocale, getLocale, isLocaleLoaded } from './global_locale'; +export { loadLocale } from './load_locale'; + +export { IntlProvider } from './intl_provider'; diff --git a/app/javascript/mastodon/locales/intl_provider.tsx b/app/javascript/mastodon/locales/intl_provider.tsx new file mode 100644 index 0000000000..1ea77c798e --- /dev/null +++ b/app/javascript/mastodon/locales/intl_provider.tsx @@ -0,0 +1,56 @@ +import { useEffect, useState } from 'react'; + +import { IntlProvider as BaseIntlProvider } from 'react-intl'; + +import { getLocale, isLocaleLoaded } from './global_locale'; +import { loadLocale } from './load_locale'; + +function onProviderError(error: unknown) { + // Silent the error, like upstream does + if (process.env.NODE_ENV === 'production') return; + + // This browser does not advertise Intl support for this locale, we only print a warning + // As-per the spec, the browser should select the best matching locale + if ( + error && + typeof error === 'object' && + error instanceof Error && + error.message.match('MISSING_DATA') + ) { + console.warn(error.message); + } + + console.error(error); +} + +export const IntlProvider: React.FC< + Omit, 'locale' | 'messages'> +> = ({ children, ...props }) => { + const [localeLoaded, setLocaleLoaded] = useState(false); + + useEffect(() => { + async function loadLocaleData() { + if (!isLocaleLoaded()) { + await loadLocale(); + } + + setLocaleLoaded(true); + } + void loadLocaleData(); + }, []); + + if (!localeLoaded) return null; + + const { locale, messages } = getLocale(); + + return ( + + {children} + + ); +}; diff --git a/app/javascript/mastodon/locales/load_locale.ts b/app/javascript/mastodon/locales/load_locale.ts new file mode 100644 index 0000000000..8a69123174 --- /dev/null +++ b/app/javascript/mastodon/locales/load_locale.ts @@ -0,0 +1,29 @@ +import { Semaphore } from 'async-mutex'; + +import type { LocaleData } from './global_locale'; +import { isLocaleLoaded, setLocale } from './global_locale'; + +const localeLoadingSemaphore = new Semaphore(1); + +export async function loadLocale() { + const locale = document.querySelector('html')?.lang || 'en'; + + // We use a Semaphore here so only one thing can try to load the locales at + // the same time. If one tries to do it while its in progress, it will wait + // for the initial load to finish before it is resumed (and will see that locale + // data is already loaded) + await localeLoadingSemaphore.runExclusive(async () => { + // if the locale is already set, then do nothing + if (isLocaleLoaded()) return; + + const localeData = (await import( + /* webpackMode: "lazy" */ + /* webpackChunkName: "locale/[request]" */ + /* webpackInclude: /\.json$/ */ + /* webpackPreload: true */ + `mastodon/locales/${locale}.json` + )) as LocaleData['messages']; + + setLocale({ messages: localeData, locale }); + }); +} diff --git a/app/javascript/mastodon/locales/locale-data/README.md b/app/javascript/mastodon/locales/locale-data/README.md deleted file mode 100644 index 83368fae7d..0000000000 --- a/app/javascript/mastodon/locales/locale-data/README.md +++ /dev/null @@ -1,221 +0,0 @@ -# Custom Locale Data - -This folder is used to store custom locale data. These custom locale data are -not yet provided by [Unicode Common Locale Data Repository](http://cldr.unicode.org/development/new-cldr-developers) -and hence not provided in [react-intl/locale-data/*](https://github.com/yahoo/react-intl). - -The locale data should support [Locale Data APIs](https://github.com/yahoo/react-intl/wiki/API#locale-data-apis) -of the react-intl library. - -It is recommended to start your custom locale data from this sample English -locale data ([*](#plural-rules)): - -```javascript -/*eslint eqeqeq: "off"*/ -/*eslint no-nested-ternary: "off"*/ - -export default [ - { - locale: "en", - pluralRuleFunction: function(e, a) { - var n = String(e).split("."), - l = !n[1], - o = Number(n[0]) == e, - t = o && n[0].slice(-1), - r = o && n[0].slice(-2); - return a ? 1 == t && 11 != r ? "one" : 2 == t && 12 != r ? "two" : 3 == t && 13 != r ? "few" : "other" : 1 == e && l ? "one" : "other" - }, - fields: { - year: { - displayName: "year", - relative: { - 0: "this year", - 1: "next year", - "-1": "last year" - }, - relativeTime: { - future: { - one: "in {0} year", - other: "in {0} years" - }, - past: { - one: "{0} year ago", - other: "{0} years ago" - } - } - }, - month: { - displayName: "month", - relative: { - 0: "this month", - 1: "next month", - "-1": "last month" - }, - relativeTime: { - future: { - one: "in {0} month", - other: "in {0} months" - }, - past: { - one: "{0} month ago", - other: "{0} months ago" - } - } - }, - day: { - displayName: "day", - relative: { - 0: "today", - 1: "tomorrow", - "-1": "yesterday" - }, - relativeTime: { - future: { - one: "in {0} day", - other: "in {0} days" - }, - past: { - one: "{0} day ago", - other: "{0} days ago" - } - } - }, - hour: { - displayName: "hour", - relativeTime: { - future: { - one: "in {0} hour", - other: "in {0} hours" - }, - past: { - one: "{0} hour ago", - other: "{0} hours ago" - } - } - }, - minute: { - displayName: "minute", - relativeTime: { - future: { - one: "in {0} minute", - other: "in {0} minutes" - }, - past: { - one: "{0} minute ago", - other: "{0} minutes ago" - } - } - }, - second: { - displayName: "second", - relative: { - 0: "now" - }, - relativeTime: { - future: { - one: "in {0} second", - other: "in {0} seconds" - }, - past: { - one: "{0} second ago", - other: "{0} seconds ago" - } - } - } - } - } -] - -``` - -## Notes - -### Plural Rules - -The function `pluralRuleFunction()` should return the key to proper string of -a plural form(s). The purpose of the function is to provide key of translate -strings of correct plural form according. The different forms are described in -[CLDR's Plural Rules][cldr-plural-rules], - -[cldr-plural-rules]: http://cldr.unicode.org/index/cldr-spec/plural-rules - -#### Quick Overview on CLDR Rules - -Let's take English as an example. - -When you describe a number, you can be either describe it as: -* Cardinals: 1st, 2nd, 3rd ... 11th, 12th ... 21st, 22nd, 23nd .... -* Ordinals: 1, 2, 3 ... - -In any of these cases, the nouns will reflect the number with singular or plural -form. For example: -* in 0 days -* in 1 day -* in 2 days - -The `pluralRuleFunction` receives 2 parameters: -* `e`: a string representation of the number. Such as, "`1`", "`2`", "`2.1`". -* `a`: `true` if this is "cardinal" type of description. `false` for ordinal and other case. - -#### How you should write `pluralRuleFunction` - -The first rule to write pluralRuleFunction is never translate the output string -into your language. [Plural Rules][cldr-plural-rules] specified you should use -these as the return values: - - * "`zero`" - * "`one`" (singular) - * "`two`" (dual) - * "`few`" (paucal) - * "`many`" (also used for fractions if they have a separate class) - * "`other`" (required—general plural form—also used if the language only has a single form) - -Again, we'll use English as the example here. - -Let's read the `return` statement in the pluralRuleFunction above: -```javascript - return a ? 1 == t && 11 != r ? "one" : 2 == t && 12 != r ? "two" : 3 == t && 13 != r ? "few" : "other" : 1 == e && l ? "one" : "other" -``` - -This nested ternary is hard to read. It basically means: -```javascript -// e: the number variable to examine -// a: "true" if cardinals -// l: "true" if the variable e has nothin after decimal mark (e.g. "1.0" would be false) -// o: "true" if the variable e is an integer -// t: the "ones" of the number. e.g. "3" for number "9123" -// r: the "ones" and "tens" of the number. e.g. "23" for number "9123" -if (a == true) { - if (t == 1 && r != 11) { - return "one"; // i.e. 1st, 21st, 101st, 121st ... - } else if (t == 2 && r != 12) { - return "two"; // i.e. 2nd, 22nd, 102nd, 122nd ... - } else if (t == 3 && r != 13) { - return "few"; // i.e. 3rd, 23rd, 103rd, 123rd ... - } else { - return "other"; // i.e. 4th, 11th, 12th, 24th ... - } -} else { - if (e == 1 && l) { - return "one"; // i.e. 1 day - } else { - return "other"; // i.e. 0 days, 2 days, 3 days - } -} -``` - -If your language, like French, do not have complicated cardinal rules, you may -use the French's version of it: -```javascript -function (e, a) { - return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other"; -} -``` - -If your language, like Chinese, do not have any pluralization rule at all you -may use the Chinese's version of it: -```javascript -function (e, a) { - return "other"; -} -``` diff --git a/app/javascript/mastodon/locales/locale-data/co.js b/app/javascript/mastodon/locales/locale-data/co.js deleted file mode 100644 index dff8a54dac..0000000000 --- a/app/javascript/mastodon/locales/locale-data/co.js +++ /dev/null @@ -1,110 +0,0 @@ -/*eslint eqeqeq: "off"*/ -/*eslint no-nested-ternary: "off"*/ -/*eslint quotes: "off"*/ - -const rules = [{ - locale: "co", - pluralRuleFunction: function (e, a) { - return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other"; - }, - fields: { - year: { - displayName: "annu", - relative: { - 0: "quist'annu", - 1: "l'annu chì vene", - "-1": "l'annu passatu", - }, - relativeTime: { - future: { - one: "in {0} annu", - other: "in {0} anni", - }, - past: { - one: "{0} annu fà", - other: "{0} anni fà", - }, - }, - }, - month: { - displayName: "mese", - relative: { - 0: "Questu mese", - 1: "u mese chì vene", - "-1": "u mese passatu", - }, - relativeTime: { - future: { - one: "in {0} mese", - other: "in {0} mesi", - }, - past: { - one: "{0} mese fà", - other: "{0} mesi fà", - }, - }, - }, - day: { - displayName: "ghjornu", - relative: { - 0: "oghje", - 1: "dumane", - "-1": "eri", - }, - relativeTime: { - future: { - one: "in {0} ghjornu", - other: "in {0} ghjornu", - }, - past: { - one: "{0} ghjornu fà", - other: "{0} ghjorni fà", - }, - }, - }, - hour: { - displayName: "ora", - relativeTime: { - future: { - one: "in {0} ora", - other: "in {0} ore", - }, - past: { - one: "{0} ora fà", - other: "{0} ore fà", - }, - }, - }, - minute: { - displayName: "minuta", - relativeTime: { - future: { - one: "in {0} minuta", - other: "in {0} minute", - }, - past: { - one: "{0} minuta fà", - other: "{0} minute fà", - }, - }, - }, - second: { - displayName: "siconda", - relative: { - 0: "avà", - }, - relativeTime: { - future: { - one: "in {0} siconda", - other: "in {0} siconde", - }, - past: { - one: "{0} siconda fà", - other: "{0} siconde fà", - }, - }, - }, - }, -}]; - -export default rules; diff --git a/app/javascript/mastodon/locales/locale-data/oc.js b/app/javascript/mastodon/locales/locale-data/oc.js deleted file mode 100644 index 6ab306b8cf..0000000000 --- a/app/javascript/mastodon/locales/locale-data/oc.js +++ /dev/null @@ -1,110 +0,0 @@ -/*eslint eqeqeq: "off"*/ -/*eslint no-nested-ternary: "off"*/ -/*eslint quotes: "off"*/ - -const rules = [{ - locale: "oc", - pluralRuleFunction: function (e, a) { - return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other"; - }, - fields: { - year: { - displayName: "an", - relative: { - 0: "ongan", - 1: "l'an que ven", - "-1": "l'an passat", - }, - relativeTime: { - future: { - one: "d’aquí {0} an", - other: "d’aquí {0} ans", - }, - past: { - one: "fa {0} an", - other: "fa {0} ans", - }, - }, - }, - month: { - displayName: "mes", - relative: { - 0: "aqueste mes", - 1: "lo mes que ven", - "-1": "lo mes passat", - }, - relativeTime: { - future: { - one: "d’aquí {0} mes", - other: "d’aquí {0} meses", - }, - past: { - one: "fa {0} mes", - other: "fa {0} meses", - }, - }, - }, - day: { - displayName: "jorn", - relative: { - 0: "uèi", - 1: "deman", - "-1": "ièr", - }, - relativeTime: { - future: { - one: "d’aquí {0} jorn", - other: "d’aquí {0} jorns", - }, - past: { - one: "fa {0} jorn", - other: "fa {0} jorns", - }, - }, - }, - hour: { - displayName: "ora", - relativeTime: { - future: { - one: "d’aquí {0} ora", - other: "d’aquí {0} oras", - }, - past: { - one: "fa {0} ora", - other: "fa {0} oras", - }, - }, - }, - minute: { - displayName: "minuta", - relativeTime: { - future: { - one: "d’aquí {0} minuta", - other: "d’aquí {0} minutas", - }, - past: { - one: "fa {0} minuta", - other: "fa {0} minutas", - }, - }, - }, - second: { - displayName: "segonda", - relative: { - 0: "ara", - }, - relativeTime: { - future: { - one: "d’aquí {0} segonda", - other: "d’aquí {0} segondas", - }, - past: { - one: "fa {0} segonda", - other: "fa {0} segondas", - }, - }, - }, - }, -}]; - -export default rules; diff --git a/app/javascript/mastodon/locales/locale-data/sa.js b/app/javascript/mastodon/locales/locale-data/sa.js deleted file mode 100644 index 65e09e97f2..0000000000 --- a/app/javascript/mastodon/locales/locale-data/sa.js +++ /dev/null @@ -1,98 +0,0 @@ -/*eslint eqeqeq: "off"*/ -/*eslint no-nested-ternary: "off"*/ -/*eslint quotes: "off"*/ -/*eslint comma-dangle: "off"*/ - -const rules = [ - { - locale: "sa", - fields: { - year: { - displayName: "year", - relative: { - 0: "this year", - 1: "next year", - "-1": "last year" - }, - relativeTime: { - future: { - other: "+{0} y" - }, - past: { - other: "-{0} y" - } - } - }, - month: { - displayName: "month", - relative: { - 0: "this month", - 1: "next month", - "-1": "last month" - }, - relativeTime: { - future: { - other: "+{0} m" - }, - past: { - other: "-{0} m" - } - } - }, - day: { - displayName: "day", - relative: { - 0: "अद्य", - 1: "श्वः", - "-1": "गतदिनम्" - }, - relativeTime: { - future: { - other: "+{0} d" - }, - past: { - other: "-{0} d" - } - } - }, - hour: { - displayName: "hour", - relativeTime: { - future: { - other: "+{0} h" - }, - past: { - other: "-{0} h" - } - } - }, - minute: { - displayName: "minute", - relativeTime: { - future: { - other: "+{0} min" - }, - past: { - other: "-{0} min" - } - } - }, - second: { - displayName: "second", - relative: { - 0: "now" - }, - relativeTime: { - future: { - other: "+{0} s" - }, - past: { - other: "-{0} s" - } - } - } - } - } -]; - -export default rules; diff --git a/app/javascript/packs/admin.jsx b/app/javascript/packs/admin.jsx index 9bb4d4dbf3..ebcc6903f8 100644 --- a/app/javascript/packs/admin.jsx +++ b/app/javascript/packs/admin.jsx @@ -229,14 +229,14 @@ ready(() => { [].forEach.call(document.querySelectorAll('[data-admin-component]'), element => { const componentName = element.getAttribute('data-admin-component'); - const { locale, ...componentProps } = JSON.parse(element.getAttribute('data-props')); + const componentProps = JSON.parse(element.getAttribute('data-props')); import('../mastodon/containers/admin_component').then(({ default: AdminComponent }) => { return import('../mastodon/components/admin/' + componentName).then(({ default: Component }) => { const root = createRoot(element); root.render ( - + , ); diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 01ab8f8f4b..f26321c41a 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -1,14 +1,15 @@ import './public-path'; +import main from "mastodon/main" + import { start } from '../mastodon/common'; -import { loadLocale } from '../mastodon/load_locale'; +import { loadLocale } from '../mastodon/locales'; import { loadPolyfills } from '../mastodon/polyfills'; start(); -loadPolyfills().then(loadLocale).then(async () => { - const { default: main } = await import('mastodon/main'); - - return main(); -}).catch(e => { - console.error(e); -}); +loadPolyfills() + .then(loadLocale) + .then(main) + .catch(e => { + console.error(e); + }); diff --git a/app/javascript/packs/public.jsx b/app/javascript/packs/public.jsx index 22e6b01a1f..da43bba7d6 100644 --- a/app/javascript/packs/public.jsx +++ b/app/javascript/packs/public.jsx @@ -15,8 +15,7 @@ import { start } from '../mastodon/common'; import { timeAgoString } from '../mastodon/components/relative_timestamp'; import emojify from '../mastodon/features/emoji/emoji'; import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions'; -import { loadLocale } from '../mastodon/load_locale'; -import { getLocale } from '../mastodon/locales'; +import { loadLocale, getLocale } from '../mastodon/locales'; import { loadPolyfills } from '../mastodon/polyfills'; import ready from '../mastodon/ready'; diff --git a/app/javascript/packs/share.jsx b/app/javascript/packs/share.jsx index f9fc785618..0f3b84549d 100644 --- a/app/javascript/packs/share.jsx +++ b/app/javascript/packs/share.jsx @@ -3,7 +3,6 @@ import { createRoot } from 'react-dom/client'; import { start } from '../mastodon/common'; import ComposeContainer from '../mastodon/containers/compose_container'; -import { loadLocale } from '../mastodon/load_locale'; import { loadPolyfills } from '../mastodon/polyfills'; import ready from '../mastodon/ready'; @@ -26,6 +25,6 @@ function main() { ready(loaded); } -loadPolyfills().then(loadLocale).then(main).catch(error => { +loadPolyfills().then(main).catch(error => { console.error(error); }); diff --git a/jest.config.js b/jest.config.js index 42c2b41522..f611812ef6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -13,7 +13,6 @@ const config = { collectCoverageFrom: [ 'app/javascript/mastodon/**/*.{js,jsx,ts,tsx}', '!app/javascript/mastodon/features/emoji/emoji_compressed.js', - '!app/javascript/mastodon/locales/locale-data/*.js', '!app/javascript/mastodon/service_worker/entry.js', '!app/javascript/mastodon/test_setup.js', ], diff --git a/package.json b/package.json index 31f2454fee..a08b485fd7 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@reduxjs/toolkit": "^1.9.5", "abortcontroller-polyfill": "^1.7.5", "arrow-key-navigation": "^1.2.0", + "async-mutex": "^0.4.0", "autoprefixer": "^10.4.14", "axios": "^1.4.0", "babel-loader": "^8.3.0", diff --git a/spec/helpers/react_component_helper_spec.rb b/spec/helpers/react_component_helper_spec.rb index 3f133bff9a..28208b619b 100644 --- a/spec/helpers/react_component_helper_spec.rb +++ b/spec/helpers/react_component_helper_spec.rb @@ -33,7 +33,7 @@ describe ReactComponentHelper do it 'returns a tag with data attributes' do expect(parsed_html.div['data-admin-component']).to eq('Name') - expect(parsed_html.div['data-props']).to eq('{"locale":"en","one":"two"}') + expect(parsed_html.div['data-props']).to eq('{"one":"two"}') end end diff --git a/yarn.lock b/yarn.lock index 3ae6343635..fd751c0419 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3273,6 +3273,13 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async-mutex@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.0.tgz#ae8048cd4d04ace94347507504b3cf15e631c25f" + integrity sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA== + dependencies: + tslib "^2.4.0" + async@^2.6.2: version "2.6.4" resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" From acc419b81be647336059ed8048dd88c1c1c1e95a Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Fri, 2 Jun 2023 09:40:23 -0400 Subject: [PATCH 13/17] Fix spacing of middle dots in the detailed status meta section (#25247) --- .../mastodon/features/status/components/detailed_status.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx index 83a566710d..ddda6eaac6 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.jsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx @@ -217,7 +217,7 @@ class DetailedStatus extends ImmutablePureComponent { } else if (this.context.router) { reblogLink = ( <> - · + {' · '} @@ -229,7 +229,7 @@ class DetailedStatus extends ImmutablePureComponent { } else { reblogLink = ( <> - · + {' · '} @@ -263,7 +263,7 @@ class DetailedStatus extends ImmutablePureComponent { if (status.get('edited_at')) { edited = ( <> - · + {' · '} ); From 94329f28e1d6edace2667daeaf0097f895e4940c Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 2 Jun 2023 18:09:08 +0200 Subject: [PATCH 14/17] =?UTF-8?q?Change=20wording=20of=20=E2=80=9CContent?= =?UTF-8?q?=20cache=20retention=20period=E2=80=9D=20setting=20to=20highlig?= =?UTF-8?q?ht=20destructive=20implications=20(#23261)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/settings/content_retention/show.html.haml | 2 +- config/initializers/simple_form.rb | 10 ++++++++++ config/locales/simple_form.en.yml | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/views/admin/settings/content_retention/show.html.haml b/app/views/admin/settings/content_retention/show.html.haml index 36856127f2..de34b5ee3c 100644 --- a/app/views/admin/settings/content_retention/show.html.haml +++ b/app/views/admin/settings/content_retention/show.html.haml @@ -15,7 +15,7 @@ .fields-group = f.input :media_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } - = f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } + = f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }, hint: false, warning_hint: t('simple_form.hints.form_admin_settings.content_cache_retention_period') = f.input :backups_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } .actions diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb index 92cffc5a2a..74034f36fd 100644 --- a/config/initializers/simple_form.rb +++ b/config/initializers/simple_form.rb @@ -19,8 +19,17 @@ module RecommendedComponent end end +module WarningHintComponent + def warning_hint(_wrapper_options = nil) + @warning_hint ||= begin + options[:warning_hint].to_s.html_safe if options[:warning_hint].present? + end + end +end + SimpleForm.include_component(AppendComponent) SimpleForm.include_component(RecommendedComponent) +SimpleForm.include_component(WarningHintComponent) SimpleForm.setup do |config| # Wrappers are used by the form builder to generate a @@ -101,6 +110,7 @@ SimpleForm.setup do |config| b.use :html5 b.use :label b.use :hint, wrap_with: { tag: :span, class: :hint } + b.use :warning_hint, wrap_with: { tag: :span, class: [:hint, 'warning-hint'] } b.use :input, wrap_with: { tag: :div, class: :label_input } b.use :error, wrap_with: { tag: :span, class: :error } end diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index b646a15e26..9c747e595d 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -78,7 +78,7 @@ en: backups_retention_period: Keep generated user archives for the specified number of days. bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations. closed_registrations_message: Displayed when sign-ups are closed - content_cache_retention_period: Posts from other servers will be deleted after the specified number of days when set to a positive value. This may be irreversible. + content_cache_retention_period: All posts and boosts from other servers will be deleted after the specified number of days. Some posts may not be recoverable. All related bookmarks, favourites and boosts will also be lost and impossible to undo. custom_css: You can apply custom styles on the web version of Mastodon. mascot: Overrides the illustration in the advanced web interface. media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand. From 0766c9a631e45ff66603ff10fa69808b8452d0b3 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 2 Jun 2023 18:35:37 +0200 Subject: [PATCH 15/17] Add card with who invited you to join when displaying rules on sign-up (#23475) --- app/javascript/styles/mastodon/accounts.scss | 14 ++------------ app/javascript/styles/mastodon/forms.scss | 4 ++++ app/views/application/_card.html.haml | 6 ++++-- app/views/auth/registrations/rules.html.haml | 10 ++++++++-- config/locales/en.yml | 3 +++ 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index 8b7b634071..b50306deda 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -3,11 +3,8 @@ display: block; text-decoration: none; color: inherit; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - - @media screen and (max-width: $no-gap-breakpoint) { - box-shadow: none; - } + overflow: hidden; + border-radius: 4px; &:hover, &:active, @@ -22,7 +19,6 @@ height: 130px; position: relative; background: darken($ui-base-color, 12%); - border-radius: 4px 4px 0 0; img { display: block; @@ -30,7 +26,6 @@ height: 100%; margin: 0; object-fit: cover; - border-radius: 4px 4px 0 0; } @media screen and (width <= 600px) { @@ -45,11 +40,6 @@ justify-content: flex-start; align-items: center; background: lighten($ui-base-color, 4%); - border-radius: 0 0 4px 4px; - - @media screen and (max-width: $no-gap-breakpoint) { - border-radius: 0; - } .avatar { flex: 0 0 auto; diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 57f077c4e8..d63a42557f 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -137,6 +137,10 @@ code { color: $secondary-text-color; margin-bottom: 30px; + &.invited-by { + margin-bottom: 15px; + } + a { color: $highlight-text-color; } diff --git a/app/views/application/_card.html.haml b/app/views/application/_card.html.haml index 719856d495..1b3dd889c1 100644 --- a/app/views/application/_card.html.haml +++ b/app/views/application/_card.html.haml @@ -1,9 +1,11 @@ - account_url = local_assigns[:admin] ? admin_account_path(account.id) : ActivityPub::TagManager.instance.url_for(account) +- compact ||= false .card.h-card = link_to account_url, target: '_blank', rel: 'noopener noreferrer' do - .card__img - = image_tag account.header.url, alt: '' + - unless compact + .card__img + = image_tag account.header.url, alt: '' .card__bar .avatar = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo' diff --git a/app/views/auth/registrations/rules.html.haml b/app/views/auth/registrations/rules.html.haml index ab3fa864ab..234f4a601d 100644 --- a/app/views/auth/registrations/rules.html.haml +++ b/app/views/auth/registrations/rules.html.haml @@ -7,8 +7,14 @@ .simple_form = render 'auth/shared/progress', stage: 'rules' - %h1.title= t('auth.rules.title') - %p.lead= t('auth.rules.preamble', domain: site_hostname) + - if @invite.present? && @invite.autofollow? + %h1.title= t('auth.rules.title_invited') + %p.lead.invited-by= t('auth.rules.invited_by', domain: site_hostname) + = render 'application/card', account: @invite.user.account, compact: true + %p.lead= t('auth.rules.preamble_invited', domain: site_hostname) + - else + %h1.title= t('auth.rules.title') + %p.lead= t('auth.rules.preamble', domain: site_hostname) %ol.rules-list - @rules.each do |rule| diff --git a/config/locales/en.yml b/config/locales/en.yml index 6a8da6e60d..2c292c42d4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1031,8 +1031,11 @@ en: rules: accept: Accept back: Back + invited_by: 'You can join %{domain} thanks to the invitation you have received from:' preamble: These are set and enforced by the %{domain} moderators. + preamble_invited: Before you proceed, please consider the ground rules set by the moderators of %{domain}. title: Some ground rules. + title_invited: You've been invited. security: Security set_new_password: Set new password setup: From 768b00c4d0c05c35c2c6c9bc8b4a821f1bde119d Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Fri, 2 Jun 2023 13:58:18 -0400 Subject: [PATCH 16/17] =?UTF-8?q?Consistently=20use=20middle=20dot=20(?= =?UTF-8?q?=C2=B7)=20instead=20of=20bullet=20(=E2=80=A2)=20to=20separate?= =?UTF-8?q?=20items=20(#25248)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.js | 9 ++++++ .haml-lint.yml | 5 +++ .rubocop.yml | 4 +++ .../_email_domain_block.html.haml | 2 +- .../_domain_block.html.haml | 6 ++-- app/views/admin/instances/_instance.html.haml | 2 +- app/views/admin/instances/show.html.haml | 2 +- app/views/admin/ip_blocks/_ip_block.html.haml | 2 +- app/views/admin/roles/_role.html.haml | 2 +- .../trends/links/_preview_card.html.haml | 10 +++--- .../admin/trends/statuses/_status.html.haml | 10 +++--- app/views/admin/trends/tags/_tag.html.haml | 6 ++-- app/views/admin/webhooks/_webhook.html.haml | 2 +- .../admin_mailer/_new_trending_links.text.erb | 4 +-- .../_new_trending_statuses.text.erb | 2 +- .../admin_mailer/_new_trending_tags.text.erb | 2 +- .../authorized_applications/index.html.haml | 2 +- lib/linter/haml_middle_dot.rb | 26 ++++++++++++++++ lib/linter/rubocop_middle_dot.rb | 31 +++++++++++++++++++ 19 files changed, 102 insertions(+), 27 deletions(-) create mode 100644 lib/linter/haml_middle_dot.rb create mode 100644 lib/linter/rubocop_middle_dot.rb diff --git a/.eslintrc.js b/.eslintrc.js index 24961cdd9d..91dcd8e60c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -81,6 +81,15 @@ module.exports = { { property: 'substring', message: 'Use .slice instead of .substring.' }, { property: 'substr', message: 'Use .slice instead of .substr.' }, ], + 'no-restricted-syntax': [ + 'error', + { + // eslint-disable-next-line no-restricted-syntax + selector: 'Literal[value=/•/], JSXText[value=/•/]', + // eslint-disable-next-line no-restricted-syntax + message: "Use '·' (middle dot) instead of '•' (bullet)", + }, + ], 'no-self-assign': 'off', 'no-unused-expressions': 'error', 'no-unused-vars': 'off', diff --git a/.haml-lint.yml b/.haml-lint.yml index 12ca463422..d1ed30b260 100644 --- a/.haml-lint.yml +++ b/.haml-lint.yml @@ -4,6 +4,11 @@ exclude: - 'vendor/**/*' - lib/templates/haml/scaffold/_form.html.haml +require: + - ./lib/linter/haml_middle_dot.rb + linters: AltText: enabled: true + MiddleDot: + enabled: true diff --git a/.rubocop.yml b/.rubocop.yml index bd561df1d2..eff89bdaee 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -11,6 +11,7 @@ require: - rubocop-rspec - rubocop-performance - rubocop-capybara + - ./lib/linter/rubocop_middle_dot AllCops: TargetRubyVersion: 3.0 # Set to minimum supported version of CI @@ -205,3 +206,6 @@ Style/TrailingCommaInArrayLiteral: # https://docs.rubocop.org/rubocop/cops_style.html#styletrailingcommainhashliteral Style/TrailingCommaInHashLiteral: EnforcedStyleForMultiline: 'comma' + +Style/MiddleDot: + Enabled: true diff --git a/app/views/admin/email_domain_blocks/_email_domain_block.html.haml b/app/views/admin/email_domain_blocks/_email_domain_block.html.haml index c5a55bc27c..7cb973c4b4 100644 --- a/app/views/admin/email_domain_blocks/_email_domain_block.html.haml +++ b/app/views/admin/email_domain_blocks/_email_domain_block.html.haml @@ -9,6 +9,6 @@ - if email_domain_block.parent.present? = t('admin.email_domain_blocks.resolved_through_html', domain: content_tag(:samp, email_domain_block.parent.domain)) - • + · = t('admin.email_domain_blocks.attempts_over_week', count: email_domain_block.history.reduce(0) { |sum, day| sum + day.accounts }) diff --git a/app/views/admin/export_domain_blocks/_domain_block.html.haml b/app/views/admin/export_domain_blocks/_domain_block.html.haml index 5d4b6c4d0d..cdce4fd28a 100644 --- a/app/views/admin/export_domain_blocks/_domain_block.html.haml +++ b/app/views/admin/export_domain_blocks/_domain_block.html.haml @@ -17,11 +17,11 @@ %br/ - = f.object.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' • ') + = f.object.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ') - if f.object.public_comment.present? - • + · = f.object.public_comment - if existing_relationships - • + · = fa_icon 'warning fw' = t('admin.export_domain_blocks.import.existing_relationships_warning') diff --git a/app/views/admin/instances/_instance.html.haml b/app/views/admin/instances/_instance.html.haml index 93f9bd4181..65cf789ce3 100644 --- a/app/views/admin/instances/_instance.html.haml +++ b/app/views/admin/instances/_instance.html.haml @@ -6,7 +6,7 @@ %small - if instance.domain_block - = instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' • ') + = instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ') - elsif instance.domain_allow = t('admin.accounts.whitelisted') - else diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml index ab290912e2..6d67d389d2 100644 --- a/app/views/admin/instances/show.html.haml +++ b/app/views/admin/instances/show.html.haml @@ -58,7 +58,7 @@ %td= @instance.domain_block.public_comment %tr %th= t('admin.instances.content_policies.policy') - %td= @instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' • ') + %td= @instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ') = link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@instance.domain_block), class: 'button' = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: 'button', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete } diff --git a/app/views/admin/ip_blocks/_ip_block.html.haml b/app/views/admin/ip_blocks/_ip_block.html.haml index b8d3ac0e86..3dc6f8f8e5 100644 --- a/app/views/admin/ip_blocks/_ip_block.html.haml +++ b/app/views/admin/ip_blocks/_ip_block.html.haml @@ -5,7 +5,7 @@ .pending-account__header %samp= link_to "#{ip_block.ip}/#{ip_block.ip.prefix}", admin_accounts_path(ip: "#{ip_block.ip}/#{ip_block.ip.prefix}") - if ip_block.comment.present? - • + · = ip_block.comment %br/ = t("simple_form.labels.ip_block.severities.#{ip_block.severity}") diff --git a/app/views/admin/roles/_role.html.haml b/app/views/admin/roles/_role.html.haml index 798d8d8b4f..d6c6b62c81 100644 --- a/app/views/admin/roles/_role.html.haml +++ b/app/views/admin/roles/_role.html.haml @@ -24,7 +24,7 @@ = t('admin.roles.everyone_full_description_html') - else = link_to t('admin.roles.assigned_users', count: role.users.count), admin_accounts_path(role_ids: role.id) - • + · %abbr{ title: role.permissions_as_keys.map { |privilege| I18n.t("admin.roles.privileges.#{privilege}") }.join(', ') }= t('admin.roles.permissions_count', count: role.permissions_as_keys.size) %div = table_link_to 'pencil', t('admin.accounts.edit'), edit_admin_role_path(role) if can?(:update, role) diff --git a/app/views/admin/trends/links/_preview_card.html.haml b/app/views/admin/trends/links/_preview_card.html.haml index 8812feb316..1ca3483715 100644 --- a/app/views/admin/trends/links/_preview_card.html.haml +++ b/app/views/admin/trends/links/_preview_card.html.haml @@ -10,21 +10,21 @@ - if preview_card.provider_name.present? = preview_card.provider_name - • + · - if preview_card.language.present? = standard_locale_name(preview_card.language) - • + · = t('admin.trends.links.shared_by_over_week', count: preview_card.history.reduce(0) { |sum, day| sum + day.accounts }) - if preview_card.trend.allowed? - • + · %abbr{ title: t('admin.trends.tags.current_score', score: preview_card.trend.score) }= t('admin.trends.tags.trending_rank', rank: preview_card.trend.rank) - if preview_card.decaying? - • + · = t('admin.trends.tags.peaked_on_and_decaying', date: l(preview_card.max_score_at.to_date, format: :short)) - elsif preview_card.requires_review? - • + · = t('admin.trends.pending_review') diff --git a/app/views/admin/trends/statuses/_status.html.haml b/app/views/admin/trends/statuses/_status.html.haml index f35e13d128..98f2e77090 100644 --- a/app/views/admin/trends/statuses/_status.html.haml +++ b/app/views/admin/trends/statuses/_status.html.haml @@ -17,17 +17,17 @@ = t('admin.trends.statuses.shared_by', count: status.reblogs_count + status.favourites_count, friendly_count: friendly_number_to_human(status.reblogs_count + status.favourites_count)) - if status.account.domain.present? - • + · = status.account.domain - if status.language.present? - • + · = standard_locale_name(status.language) - if status.trendable? && !status.account.discoverable? - • + · = t('admin.trends.statuses.not_discoverable') - if status.trend.allowed? - • + · %abbr{ title: t('admin.trends.tags.current_score', score: status.trend.score) }= t('admin.trends.tags.trending_rank', rank: status.trend.rank) - elsif status.requires_review? - • + · = t('admin.trends.pending_review') diff --git a/app/views/admin/trends/tags/_tag.html.haml b/app/views/admin/trends/tags/_tag.html.haml index a30666a08b..3bbdd08db8 100644 --- a/app/views/admin/trends/tags/_tag.html.haml +++ b/app/views/admin/trends/tags/_tag.html.haml @@ -13,12 +13,12 @@ = t('admin.trends.tags.used_by_over_week', count: tag.history.reduce(0) { |sum, day| sum + day.accounts }) - if tag.trendable? && (rank = Trends.tags.rank(tag.id)) - • + · %abbr{ title: t('admin.trends.tags.current_score', score: Trends.tags.score(tag.id)) }= t('admin.trends.tags.trending_rank', rank: rank + 1) - if tag.decaying? - • + · = t('admin.trends.tags.peaked_on_and_decaying', date: l(tag.max_score_at.to_date, format: :short)) - elsif tag.requires_review? - • + · = t('admin.trends.pending_review') diff --git a/app/views/admin/webhooks/_webhook.html.haml b/app/views/admin/webhooks/_webhook.html.haml index d94a41eb3d..6b3e49eba0 100644 --- a/app/views/admin/webhooks/_webhook.html.haml +++ b/app/views/admin/webhooks/_webhook.html.haml @@ -10,7 +10,7 @@ - else %span.negative-hint= t('admin.webhooks.disabled') - • + · %abbr{ title: webhook.events.join(', ') }= t('admin.webhooks.enabled_events', count: webhook.events.size) diff --git a/app/views/admin_mailer/_new_trending_links.text.erb b/app/views/admin_mailer/_new_trending_links.text.erb index 602e12793e..85f3f8039d 100644 --- a/app/views/admin_mailer/_new_trending_links.text.erb +++ b/app/views/admin_mailer/_new_trending_links.text.erb @@ -1,8 +1,8 @@ <%= raw t('admin_mailer.new_trends.new_trending_links.title') %> <% @links.each do |link| %> -- <%= link.title %> • <%= link.url %> - <%= standard_locale_name(link.language) %> • <%= raw t('admin.trends.links.usage_comparison', today: link.history.get(Time.now.utc).accounts, yesterday: link.history.get(Time.now.utc - 1.day).accounts) %> • <%= t('admin.trends.tags.current_score', score: link.trend.score.round(2)) %> +- <%= link.title %> · <%= link.url %> + <%= standard_locale_name(link.language) %> · <%= raw t('admin.trends.links.usage_comparison', today: link.history.get(Time.now.utc).accounts, yesterday: link.history.get(Time.now.utc - 1.day).accounts) %> · <%= t('admin.trends.tags.current_score', score: link.trend.score.round(2)) %> <% end %> <%= raw t('application_mailer.view')%> <%= admin_trends_links_url %> diff --git a/app/views/admin_mailer/_new_trending_statuses.text.erb b/app/views/admin_mailer/_new_trending_statuses.text.erb index 1ed3ae8573..eedbfff9d9 100644 --- a/app/views/admin_mailer/_new_trending_statuses.text.erb +++ b/app/views/admin_mailer/_new_trending_statuses.text.erb @@ -2,7 +2,7 @@ <% @statuses.each do |status| %> - <%= ActivityPub::TagManager.instance.url_for(status) %> - <%= standard_locale_name(status.language) %> • <%= raw t('admin.trends.tags.current_score', score: status.trend.score.round(2)) %> + <%= standard_locale_name(status.language) %> · <%= raw t('admin.trends.tags.current_score', score: status.trend.score.round(2)) %> <% end %> <%= raw t('application_mailer.view')%> <%= admin_trends_statuses_url %> diff --git a/app/views/admin_mailer/_new_trending_tags.text.erb b/app/views/admin_mailer/_new_trending_tags.text.erb index 363df369d5..d528ab8eb7 100644 --- a/app/views/admin_mailer/_new_trending_tags.text.erb +++ b/app/views/admin_mailer/_new_trending_tags.text.erb @@ -2,7 +2,7 @@ <% @tags.each do |tag| %> - #<%= tag.display_name %> - <%= raw t('admin.trends.tags.usage_comparison', today: tag.history.get(Time.now.utc).accounts, yesterday: tag.history.get(Time.now.utc - 1.day).accounts) %> • <%= t('admin.trends.tags.current_score', score: Trends.tags.score(tag.id).round(2)) %> + <%= raw t('admin.trends.tags.usage_comparison', today: tag.history.get(Time.now.utc).accounts, yesterday: tag.history.get(Time.now.utc - 1.day).accounts) %> · <%= t('admin.trends.tags.current_score', score: Trends.tags.score(tag.id).round(2)) %> <% end %> <% if @lowest_trending_tag %> diff --git a/app/views/oauth/authorized_applications/index.html.haml b/app/views/oauth/authorized_applications/index.html.haml index 55d8524dbe..689f051029 100644 --- a/app/views/oauth/authorized_applications/index.html.haml +++ b/app/views/oauth/authorized_applications/index.html.haml @@ -23,7 +23,7 @@ - else = t('doorkeeper.authorized_applications.index.never_used') - • + · = t('doorkeeper.authorized_applications.index.authorized_at', date: l(application.created_at.to_date)) diff --git a/lib/linter/haml_middle_dot.rb b/lib/linter/haml_middle_dot.rb new file mode 100644 index 0000000000..3b27711521 --- /dev/null +++ b/lib/linter/haml_middle_dot.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module HamlLint + # Bans the usage of “•” (bullet) in HTML/HAML in favor of “·” (middle dot) in anything that will end up as a text node. (including string literals in Ruby code) + class Linter::MiddleDot < Linter + include LinterRegistry + + # rubocop:disable Style/MiddleDot + BULLET = '•' + # rubocop:enable Style/MiddleDot + MIDDLE_DOT = '·' + MESSAGE = "Use '#{MIDDLE_DOT}' (middle dot) instead of '#{BULLET}' (bullet)".freeze + + def visit_plain(node) + return unless node.text.include?(BULLET) + + record_lint(node, MESSAGE) + end + + def visit_script(node) + return unless node.script.include?(BULLET) + + record_lint(node, MESSAGE) + end + end +end diff --git a/lib/linter/rubocop_middle_dot.rb b/lib/linter/rubocop_middle_dot.rb new file mode 100644 index 0000000000..3a1d97c0c9 --- /dev/null +++ b/lib/linter/rubocop_middle_dot.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Style + # Bans the usage of “•” (bullet) in HTML/HAML in favor of “·” (middle dot) in string literals + class MiddleDot < Base + extend AutoCorrector + extend Util + + # rubocop:disable Style/MiddleDot + BULLET = '•' + # rubocop:enable Style/MiddleDot + MIDDLE_DOT = '·' + MESSAGE = "Use '#{MIDDLE_DOT}' (middle dot) instead of '#{BULLET}' (bullet)".freeze + + def on_str(node) + # Constants like __FILE__ are handled as strings, + # but don't respond to begin. + return unless node.loc.respond_to?(:begin) && node.loc.begin + + return unless node.value.include?(BULLET) + + add_offense(node, message: MESSAGE) do |corrector| + corrector.replace(node, node.source.gsub(BULLET, MIDDLE_DOT)) + end + end + end + end + end +end From aea67d448bc7974aa4c342f5c1987ec4ee2681ea Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Fri, 2 Jun 2023 14:01:36 -0400 Subject: [PATCH 17/17] Cleanup old translationRunner (#25241) --- config/webpack/translationRunner.js | 3 --- package.json | 1 - 2 files changed, 4 deletions(-) delete mode 100644 config/webpack/translationRunner.js diff --git a/config/webpack/translationRunner.js b/config/webpack/translationRunner.js deleted file mode 100644 index 77534c9de3..0000000000 --- a/config/webpack/translationRunner.js +++ /dev/null @@ -1,3 +0,0 @@ -console.error("The localisation functionality has been refactored, please see the Localisation section in the development documentation (https://docs.joinmastodon.org/dev/code/#localizations)"); - -process.exit(1); diff --git a/package.json b/package.json index a08b485fd7..49e9c7f743 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "lint:sass": "stylelint \"**/*.{css,scss}\" && prettier --check \"**/*.{css,scss}\"", "lint:yml": "prettier --check \"**/*.{yaml,yml}\"", "lint": "yarn lint:js && yarn lint:json && yarn lint:sass && yarn lint:yml", - "manage:translations": "node ./config/webpack/translationRunner.js", "postversion": "git push --tags", "prepare": "husky install", "start": "node ./streaming/index.js",